Skip to content

Commit 15f2c06

Browse files
authored
Merge pull request #54 from elbformat/fix-xpath-quotes
Fix escaping of xpath expressions with quotes
2 parents 4212011 + 2c6292d commit 15f2c06

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed

doc/changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## v1.5.11
4+
* Fix: Escaping of quotes in xpath expressions.
5+
36
## v1.5.10
47
* Fix: Mailer deprecation warnings for implicit nullable parameter.
58
* Feature: Allow mailer to fail

doc/development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Use docker for local tasks
44
```bash
5-
docker-compose run php sh
5+
docker compose run php sh
66
# Install dependencies
77
composer install
88
# Run unittests

src/Context/HtmlContext.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ protected function mustContainTag(string $tagName, array $attr = [], ?string $co
113113
$crawler = $this->getCrawler();
114114
$xPath = '//'.$tagName;
115115
foreach ($attr as $attrName => $attrVal) {
116-
$xPath .= sprintf('[@%s="%s"]', $attrName, $attrVal);
116+
$xPath .= sprintf('[@%s=%s]', $attrName, $this->escapeXpathValue($attrVal));
117117
}
118118
$elements = $crawler->filterXPath($xPath);
119119

@@ -136,4 +136,29 @@ protected function mustContainTag(string $tagName, array $attr = [], ?string $co
136136

137137
return null;
138138
}
139+
140+
protected function escapeXpathValue(string $value): string
141+
{
142+
// Inspired by https://stackoverflow.com/questions/1341847/apostrophe-in-xpath-query/1352556#1352556
143+
// use "
144+
if (!str_contains($value, '"')) {
145+
return '"'.$value.'"';
146+
}
147+
// use '
148+
if (!str_contains($value, "'")) {
149+
return "'".$value."'";
150+
}
151+
// use concat
152+
$parts = explode('"', $value);
153+
$attrVal = 'concat("'.array_shift($parts).'"';
154+
foreach ($parts as $part) {
155+
$attrVal .= ",'\"'";
156+
if ('' !== $part) {
157+
$attrVal .= ',"'.$part.'"';
158+
}
159+
}
160+
$attrVal .= ')';
161+
162+
return $attrVal;
163+
}
139164
}

tests/Context/HtmlContextTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ public function testISeeBeforeFails(): void
7979
$this->htmlContext->iSeeBefore('World', 'Hello');
8080
}
8181

82+
public function testISeeBeforeNotFound1(): void
83+
{
84+
$this->setDom('<p>Hello World</p>');
85+
$this->expectExceptionMessage('"Me" not found');
86+
$this->htmlContext->iSeeBefore('Me', 'World');
87+
}
88+
89+
public function testISeeBeforeNotFound2(): void
90+
{
91+
$this->setDom('<p>Hello World</p>');
92+
$this->expectExceptionMessage('"Me" not found');
93+
$this->htmlContext->iSeeBefore('Hello', 'Me');
94+
}
95+
8296
public function testISeeATag(): void
8397
{
8498
$this->setDom('<a href="/test"></a>');
@@ -103,6 +117,22 @@ public function testISeeATagWithContentComplex(): void
103117
$this->htmlContext->iSeeATag('a', $table, 'Hello World');
104118
}
105119

120+
public function testISeeATagWithDifferentQuotes(): void
121+
{
122+
$this->setDom('<span text="Hello &quot;World&apos;"></span>');
123+
$table = new TableNode([0 => ['text', 'Hello "World\'']]);
124+
$this->expectNotToPerformAssertions();
125+
$this->htmlContext->iSeeATag('span', $table);
126+
}
127+
128+
public function testISeeATagWithSingleQuote(): void
129+
{
130+
$this->setDom('<span text="Hello &quot;World&quot;"></span>');
131+
$table = new TableNode([0 => ['text', 'Hello "World"']]);
132+
$this->expectNotToPerformAssertions();
133+
$this->htmlContext->iSeeATag('span', $table);
134+
}
135+
106136
public function testISeeATagFails(): void
107137
{
108138
$this->setDom('<a href="/test">Hello World</a>');

0 commit comments

Comments
 (0)