Skip to content

Commit 44a978c

Browse files
authored
Fix the handling of escaping in table cells (#391)
Using `str_replace` to perform the un-escaping is flawed because it performs each of its search in order, which was not handling properly the case of escaped backslashes. Replacing escape sequences requires handling each backslash a single time, to avoid considering the output of an escaped backslash as belong to another escape sequence (or considering a backslash as being the start of an escape sequence instead of being escape, depending on the order). The solution for that is to use `preg_replace` matching a backslash and its following character, thanks to the fact that this function guarantees that it does not have overlapping matches for the regex. I extracted that parsing to a private method for 2 reasons: 1. we need an assertion to assume that the returned value is not `null` (here, I'm assuming that the regex does not fail. I could turn that in an exception if you prefer, but we already do the same assumption for the splitting of cells) 2. a oneliner (which would use `??` to handle the null values) would make that line quite hard to read
1 parent 85febc9 commit 44a978c

File tree

2 files changed

+17
-2
lines changed

2 files changed

+17
-2
lines changed

src/Lexer.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,7 @@ protected function scanTableRow()
753753

754754
$token = $this->takeToken('TableRow');
755755
if ($this->compatibilityMode->shouldSupportNewlineEscapeSequenceInTableCell()) {
756-
$columns = array_map(static fn ($column) => trim(str_replace(['\\|', '\n', '\\\\'], ['|', "\n", '\\'], $column), ' '), $rawColumns);
756+
$columns = array_map($this->parseTableCell(...), $rawColumns);
757757
} else {
758758
$columns = array_map(static fn ($column) => trim(str_replace(['\\|', '\\\\'], ['|', '\\'], $column)), $rawColumns);
759759
}
@@ -764,6 +764,22 @@ protected function scanTableRow()
764764
return $token;
765765
}
766766

767+
private function parseTableCell(string $cell): string
768+
{
769+
$value = preg_replace_callback('/\\\\./', function (array $matches) {
770+
return match ($matches[0]) {
771+
'\\n' => "\n",
772+
'\\\\' => '\\',
773+
'\\|' => '|',
774+
default => $matches[0],
775+
};
776+
}, $cell);
777+
778+
assert($value !== null);
779+
780+
return trim($value, ' ');
781+
}
782+
767783
/**
768784
* Scans Tags from input & returns it if found.
769785
*

tests/Cucumber/CompatibilityTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class CompatibilityTest extends TestCase
5959
'gherkin-32' => [
6060
'complex_background.feature' => 'Rule keyword not supported',
6161
'docstrings.feature' => 'Escaped delimiters in docstrings are not unescaped',
62-
'escaped_pipes.feature' => 'Escaped newlines in table cells are not unescaped',
6362
'rule.feature' => 'Rule keyword not supported',
6463
'rule_with_tag.feature' => 'Rule keyword not supported',
6564
'tags.feature' => 'Rule keyword not supported',

0 commit comments

Comments
 (0)