diff --git a/src/Services/PropertiesTable.php b/src/Services/PropertiesTable.php index abed15c..30f00a3 100644 --- a/src/Services/PropertiesTable.php +++ b/src/Services/PropertiesTable.php @@ -29,7 +29,11 @@ public function convertPropertiesToMarkdownTable(array $properties): string continue; } - $markdown .= "| {$name} | {$value} |\n"; + // Escape special characters in property name and value + $escapedName = $this->escapeTableCellCharacters($name); + $escapedValue = $this->escapeTableCellCharacters($value); + + $markdown .= "| {$escapedName} | {$escapedValue} |\n"; } return $markdown."\n"; @@ -289,4 +293,21 @@ private function formatUser(?array $user): string return $user['name'] ?? 'Unknown'; } + + /** + * Escape special characters in markdown table cells + * + * @param string $text Text to escape + * @return string Escaped text + */ + private function escapeTableCellCharacters(string $text): string + { + // Escape pipe characters to prevent table structure corruption + $text = str_replace('|', '\|', $text); + + // Replace newline characters with spaces to maintain table structure + $text = str_replace(["\r\n", "\r", "\n"], ' ', $text); + + return $text; + } } diff --git a/tests/Services/PropertiesTableTest.php b/tests/Services/PropertiesTableTest.php index 474b0fd..2cda6d0 100644 --- a/tests/Services/PropertiesTableTest.php +++ b/tests/Services/PropertiesTableTest.php @@ -489,3 +489,323 @@ expect($result)->toContain('| Description | This is a description |'); }); + +test('escapes pipe characters in property names', function () { + $properties = [ + 'Name | Title' => [ + 'id' => 'title', + 'type' => 'title', + 'title' => [ + ['plain_text' => 'Test Page'], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Name \| Title | Test Page |'); +}); + +test('escapes pipe characters in property values', function () { + $properties = [ + 'Description' => [ + 'id' => 'rich', + 'type' => 'rich_text', + 'rich_text' => [ + ['plain_text' => 'Value with | pipe'], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Description | Value with \| pipe |'); +}); + +test('escapes multiple pipe characters in property names and values', function () { + $properties = [ + 'A | B | C' => [ + 'id' => 'title', + 'type' => 'title', + 'title' => [ + ['plain_text' => 'X | Y | Z'], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| A \| B \| C | X \| Y \| Z |'); +}); + +test('escapes pipe characters in select property values', function () { + $properties = [ + 'Type' => [ + 'id' => 'select', + 'type' => 'select', + 'select' => [ + 'id' => 'LOQu', + 'name' => 'option-1 | option-2', + 'color' => 'pink', + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Type | option-1 \| option-2 |'); +}); + +test('escapes pipe characters in multi_select property values', function () { + $properties = [ + 'Tags' => [ + 'id' => 'multi_select', + 'type' => 'multi_select', + 'multi_select' => [ + [ + 'id' => 'tag1', + 'name' => 'tag | 1', + 'color' => 'blue', + ], + [ + 'id' => 'tag2', + 'name' => 'tag | 2', + 'color' => 'green', + ], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Tags | tag \| 1, tag \| 2 |'); +}); + +test('escapes pipe characters in people names', function () { + $properties = [ + 'Person' => [ + 'id' => 'people', + 'type' => 'people', + 'people' => [ + ['name' => 'John | Doe'], + ['name' => 'Jane | Smith'], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Person | John \| Doe, Jane \| Smith |'); +}); + +test('escapes pipe characters in file names', function () { + $properties = [ + 'Files' => [ + 'id' => 'files', + 'type' => 'files', + 'files' => [ + [ + 'name' => 'file | name.pdf', + 'type' => 'external', + 'external' => [ + 'url' => 'https://example.com/file.pdf', + ], + ], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Files | [file \| name.pdf](https://example.com/file.pdf) |'); +}); + +test('escapes pipe characters in user names', function () { + $properties = [ + 'Created By' => [ + 'id' => 'created_by', + 'type' => 'created_by', + 'created_by' => [ + 'id' => 'user-id', + 'name' => 'John | Doe', + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Created By | John \| Doe |'); +}); + +test('escapes pipe characters in formula string values', function () { + $properties = [ + 'Calculated' => [ + 'id' => 'formula', + 'type' => 'formula', + 'formula' => [ + 'type' => 'string', + 'string' => 'Result | Text', + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Calculated | Result \| Text |'); +}); + +test('replaces newline characters with spaces in property values', function () { + $properties = [ + 'Description' => [ + 'id' => 'rich', + 'type' => 'rich_text', + 'rich_text' => [ + ['plain_text' => "Line 1\nLine 2"], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Description | Line 1 Line 2 |'); +}); + +test('replaces carriage return with spaces in property values', function () { + $properties = [ + 'Description' => [ + 'id' => 'rich', + 'type' => 'rich_text', + 'rich_text' => [ + ['plain_text' => "Line 1\rLine 2"], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Description | Line 1 Line 2 |'); +}); + +test('replaces CRLF with spaces in property values', function () { + $properties = [ + 'Description' => [ + 'id' => 'rich', + 'type' => 'rich_text', + 'rich_text' => [ + ['plain_text' => "Line 1\r\nLine 2"], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Description | Line 1 Line 2 |'); +}); + +test('replaces multiple newlines with spaces in property values', function () { + $properties = [ + 'Description' => [ + 'id' => 'rich', + 'type' => 'rich_text', + 'rich_text' => [ + ['plain_text' => "Line 1\n\nLine 2\n\nLine 3"], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Description | Line 1 Line 2 Line 3 |'); +}); + +test('replaces newlines in property names', function () { + $properties = [ + "Multi\nLine\nName" => [ + 'id' => 'title', + 'type' => 'title', + 'title' => [ + ['plain_text' => 'Test Value'], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Multi Line Name | Test Value |'); +}); + +test('handles both newlines and pipe characters together', function () { + $properties = [ + "Name | Title\nWith Newline" => [ + 'id' => 'rich', + 'type' => 'rich_text', + 'rich_text' => [ + ['plain_text' => "Value | with\npipe and\nnewlines"], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Name \| Title With Newline | Value \| with pipe and newlines |'); +}); + +test('replaces newlines in select property values', function () { + $properties = [ + 'Type' => [ + 'id' => 'select', + 'type' => 'select', + 'select' => [ + 'id' => 'LOQu', + 'name' => "Option\nWith\nNewlines", + 'color' => 'pink', + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Type | Option With Newlines |'); +}); + +test('replaces newlines in multi_select property values', function () { + $properties = [ + 'Tags' => [ + 'id' => 'multi_select', + 'type' => 'multi_select', + 'multi_select' => [ + [ + 'id' => 'tag1', + 'name' => "Tag\n1", + 'color' => 'blue', + ], + [ + 'id' => 'tag2', + 'name' => "Tag\n2", + 'color' => 'green', + ], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Tags | Tag 1, Tag 2 |'); +}); + +test('replaces newlines in people names', function () { + $properties = [ + 'Person' => [ + 'id' => 'people', + 'type' => 'people', + 'people' => [ + ['name' => "John\nDoe"], + ['name' => "Jane\nSmith"], + ], + ], + ]; + + $result = $this->propertiesTable->convertPropertiesToMarkdownTable($properties); + + expect($result)->toContain('| Person | John Doe, Jane Smith |'); +});