Skip to content

Commit d883376

Browse files
authored
Merge pull request #10 from RedberryProducts/copilot/sub-pr-9
Escape pipe characters and newlines in markdown table cells
2 parents bb1443e + 13b9251 commit d883376

File tree

2 files changed

+342
-1
lines changed

2 files changed

+342
-1
lines changed

src/Services/PropertiesTable.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ public function convertPropertiesToMarkdownTable(array $properties): string
2929
continue;
3030
}
3131

32-
$markdown .= "| {$name} | {$value} |\n";
32+
// Escape special characters in property name and value
33+
$escapedName = $this->escapeTableCellCharacters($name);
34+
$escapedValue = $this->escapeTableCellCharacters($value);
35+
36+
$markdown .= "| {$escapedName} | {$escapedValue} |\n";
3337
}
3438

3539
return $markdown."\n";
@@ -289,4 +293,21 @@ private function formatUser(?array $user): string
289293

290294
return $user['name'] ?? 'Unknown';
291295
}
296+
297+
/**
298+
* Escape special characters in markdown table cells
299+
*
300+
* @param string $text Text to escape
301+
* @return string Escaped text
302+
*/
303+
private function escapeTableCellCharacters(string $text): string
304+
{
305+
// Escape pipe characters to prevent table structure corruption
306+
$text = str_replace('|', '\|', $text);
307+
308+
// Replace newline characters with spaces to maintain table structure
309+
$text = str_replace(["\r\n", "\r", "\n"], ' ', $text);
310+
311+
return $text;
312+
}
292313
}

tests/Services/PropertiesTableTest.php

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,3 +489,323 @@
489489

490490
expect($result)->toContain('| Description | This is a description |');
491491
});
492+
493+
test('escapes pipe characters in property names', function () {
494+
$properties = [
495+
'Name | Title' => [
496+
'id' => 'title',
497+
'type' => 'title',
498+
'title' => [
499+
['plain_text' => 'Test Page'],
500+
],
501+
],
502+
];
503+
504+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
505+
506+
expect($result)->toContain('| Name \| Title | Test Page |');
507+
});
508+
509+
test('escapes pipe characters in property values', function () {
510+
$properties = [
511+
'Description' => [
512+
'id' => 'rich',
513+
'type' => 'rich_text',
514+
'rich_text' => [
515+
['plain_text' => 'Value with | pipe'],
516+
],
517+
],
518+
];
519+
520+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
521+
522+
expect($result)->toContain('| Description | Value with \| pipe |');
523+
});
524+
525+
test('escapes multiple pipe characters in property names and values', function () {
526+
$properties = [
527+
'A | B | C' => [
528+
'id' => 'title',
529+
'type' => 'title',
530+
'title' => [
531+
['plain_text' => 'X | Y | Z'],
532+
],
533+
],
534+
];
535+
536+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
537+
538+
expect($result)->toContain('| A \| B \| C | X \| Y \| Z |');
539+
});
540+
541+
test('escapes pipe characters in select property values', function () {
542+
$properties = [
543+
'Type' => [
544+
'id' => 'select',
545+
'type' => 'select',
546+
'select' => [
547+
'id' => 'LOQu',
548+
'name' => 'option-1 | option-2',
549+
'color' => 'pink',
550+
],
551+
],
552+
];
553+
554+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
555+
556+
expect($result)->toContain('| Type | option-1 \| option-2 |');
557+
});
558+
559+
test('escapes pipe characters in multi_select property values', function () {
560+
$properties = [
561+
'Tags' => [
562+
'id' => 'multi_select',
563+
'type' => 'multi_select',
564+
'multi_select' => [
565+
[
566+
'id' => 'tag1',
567+
'name' => 'tag | 1',
568+
'color' => 'blue',
569+
],
570+
[
571+
'id' => 'tag2',
572+
'name' => 'tag | 2',
573+
'color' => 'green',
574+
],
575+
],
576+
],
577+
];
578+
579+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
580+
581+
expect($result)->toContain('| Tags | tag \| 1, tag \| 2 |');
582+
});
583+
584+
test('escapes pipe characters in people names', function () {
585+
$properties = [
586+
'Person' => [
587+
'id' => 'people',
588+
'type' => 'people',
589+
'people' => [
590+
['name' => 'John | Doe'],
591+
['name' => 'Jane | Smith'],
592+
],
593+
],
594+
];
595+
596+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
597+
598+
expect($result)->toContain('| Person | John \| Doe, Jane \| Smith |');
599+
});
600+
601+
test('escapes pipe characters in file names', function () {
602+
$properties = [
603+
'Files' => [
604+
'id' => 'files',
605+
'type' => 'files',
606+
'files' => [
607+
[
608+
'name' => 'file | name.pdf',
609+
'type' => 'external',
610+
'external' => [
611+
'url' => 'https://example.com/file.pdf',
612+
],
613+
],
614+
],
615+
],
616+
];
617+
618+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
619+
620+
expect($result)->toContain('| Files | [file \| name.pdf](https://example.com/file.pdf) |');
621+
});
622+
623+
test('escapes pipe characters in user names', function () {
624+
$properties = [
625+
'Created By' => [
626+
'id' => 'created_by',
627+
'type' => 'created_by',
628+
'created_by' => [
629+
'id' => 'user-id',
630+
'name' => 'John | Doe',
631+
],
632+
],
633+
];
634+
635+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
636+
637+
expect($result)->toContain('| Created By | John \| Doe |');
638+
});
639+
640+
test('escapes pipe characters in formula string values', function () {
641+
$properties = [
642+
'Calculated' => [
643+
'id' => 'formula',
644+
'type' => 'formula',
645+
'formula' => [
646+
'type' => 'string',
647+
'string' => 'Result | Text',
648+
],
649+
],
650+
];
651+
652+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
653+
654+
expect($result)->toContain('| Calculated | Result \| Text |');
655+
});
656+
657+
test('replaces newline characters with spaces in property values', function () {
658+
$properties = [
659+
'Description' => [
660+
'id' => 'rich',
661+
'type' => 'rich_text',
662+
'rich_text' => [
663+
['plain_text' => "Line 1\nLine 2"],
664+
],
665+
],
666+
];
667+
668+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
669+
670+
expect($result)->toContain('| Description | Line 1 Line 2 |');
671+
});
672+
673+
test('replaces carriage return with spaces in property values', function () {
674+
$properties = [
675+
'Description' => [
676+
'id' => 'rich',
677+
'type' => 'rich_text',
678+
'rich_text' => [
679+
['plain_text' => "Line 1\rLine 2"],
680+
],
681+
],
682+
];
683+
684+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
685+
686+
expect($result)->toContain('| Description | Line 1 Line 2 |');
687+
});
688+
689+
test('replaces CRLF with spaces in property values', function () {
690+
$properties = [
691+
'Description' => [
692+
'id' => 'rich',
693+
'type' => 'rich_text',
694+
'rich_text' => [
695+
['plain_text' => "Line 1\r\nLine 2"],
696+
],
697+
],
698+
];
699+
700+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
701+
702+
expect($result)->toContain('| Description | Line 1 Line 2 |');
703+
});
704+
705+
test('replaces multiple newlines with spaces in property values', function () {
706+
$properties = [
707+
'Description' => [
708+
'id' => 'rich',
709+
'type' => 'rich_text',
710+
'rich_text' => [
711+
['plain_text' => "Line 1\n\nLine 2\n\nLine 3"],
712+
],
713+
],
714+
];
715+
716+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
717+
718+
expect($result)->toContain('| Description | Line 1 Line 2 Line 3 |');
719+
});
720+
721+
test('replaces newlines in property names', function () {
722+
$properties = [
723+
"Multi\nLine\nName" => [
724+
'id' => 'title',
725+
'type' => 'title',
726+
'title' => [
727+
['plain_text' => 'Test Value'],
728+
],
729+
],
730+
];
731+
732+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
733+
734+
expect($result)->toContain('| Multi Line Name | Test Value |');
735+
});
736+
737+
test('handles both newlines and pipe characters together', function () {
738+
$properties = [
739+
"Name | Title\nWith Newline" => [
740+
'id' => 'rich',
741+
'type' => 'rich_text',
742+
'rich_text' => [
743+
['plain_text' => "Value | with\npipe and\nnewlines"],
744+
],
745+
],
746+
];
747+
748+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
749+
750+
expect($result)->toContain('| Name \| Title With Newline | Value \| with pipe and newlines |');
751+
});
752+
753+
test('replaces newlines in select property values', function () {
754+
$properties = [
755+
'Type' => [
756+
'id' => 'select',
757+
'type' => 'select',
758+
'select' => [
759+
'id' => 'LOQu',
760+
'name' => "Option\nWith\nNewlines",
761+
'color' => 'pink',
762+
],
763+
],
764+
];
765+
766+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
767+
768+
expect($result)->toContain('| Type | Option With Newlines |');
769+
});
770+
771+
test('replaces newlines in multi_select property values', function () {
772+
$properties = [
773+
'Tags' => [
774+
'id' => 'multi_select',
775+
'type' => 'multi_select',
776+
'multi_select' => [
777+
[
778+
'id' => 'tag1',
779+
'name' => "Tag\n1",
780+
'color' => 'blue',
781+
],
782+
[
783+
'id' => 'tag2',
784+
'name' => "Tag\n2",
785+
'color' => 'green',
786+
],
787+
],
788+
],
789+
];
790+
791+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
792+
793+
expect($result)->toContain('| Tags | Tag 1, Tag 2 |');
794+
});
795+
796+
test('replaces newlines in people names', function () {
797+
$properties = [
798+
'Person' => [
799+
'id' => 'people',
800+
'type' => 'people',
801+
'people' => [
802+
['name' => "John\nDoe"],
803+
['name' => "Jane\nSmith"],
804+
],
805+
],
806+
];
807+
808+
$result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties);
809+
810+
expect($result)->toContain('| Person | John Doe, Jane Smith |');
811+
});

0 commit comments

Comments
 (0)