-
Notifications
You must be signed in to change notification settings - Fork 379
Expand file tree
/
Copy pathphp-css-parser-commit-10a2501.patch
More file actions
143 lines (137 loc) · 5.76 KB
/
Copy pathphp-css-parser-commit-10a2501.patch
File metadata and controls
143 lines (137 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
From fa139f65c5b098ae652c970b25e6eb03fc495eb4 Mon Sep 17 00:00:00 2001
From: Weston Ruter <weston@xwp.co>
Date: Wed, 25 Jul 2018 10:38:59 -0700
Subject: [PATCH 1/2] Fix parsing CSS selectors which contain commas
---
.../CSS/RuleSet/DeclarationBlock.php | 60 ++++++++++++++++++-
tests/Sabberworm/CSS/ParserTest.php | 6 ++
tests/files/specificity.css | 4 +-
3 files changed, 68 insertions(+), 2 deletions(-)
diff --git a/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php b/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php
index e18f5d8..26c2e12 100644
--- a/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php
+++ b/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php
@@ -28,7 +28,19 @@ public function setSelectors($mSelector) {
if (is_array($mSelector)) {
$this->aSelectors = $mSelector;
} else {
- $this->aSelectors = explode(',', $mSelector);
+ list( $sSelectors, $aPlaceholders ) = $this->addSelectorExpressionPlaceholders( $mSelector );
+ if ( empty( $aPlaceholders ) ) {
+ $this->aSelectors = explode(',', $sSelectors);
+ } else {
+ $aSearches = array_keys( $aPlaceholders );
+ $aReplaces = array_values( $aPlaceholders );
+ $this->aSelectors = array_map(
+ function( $sSelector ) use ( $aSearches, $aReplaces ) {
+ return str_replace( $aSearches, $aReplaces, $sSelector );
+ },
+ explode(',', $sSelectors)
+ );
+ }
}
foreach ($this->aSelectors as $iKey => $mSelector) {
if (!($mSelector instanceof Selector)) {
@@ -37,6 +49,52 @@ public function setSelectors($mSelector) {
}
}
+ /**
+ * Add placeholders for parenthetical/bracketed expressions in selectors which may contain commas that break exploding.
+ *
+ * This prevents a single selector like `.widget:not(.foo, .bar)` from erroneously getting parsed in setSelectors as
+ * two selectors `.widget:not(.foo` and `.bar)`.
+ *
+ * @param string $sSelectors Selectors.
+ * @return array First array value is the selectors with placeholders, and second value is the array of placeholders mapped to the original expressions.
+ */
+ private function addSelectorExpressionPlaceholders( $sSelectors ) {
+ $iOffset = 0;
+ $aPlaceholders = array();
+
+ while ( preg_match( '/\(|\[/', $sSelectors, $aMatches, PREG_OFFSET_CAPTURE, $iOffset ) ) {
+ $sMatchString = $aMatches[0][0];
+ $iMatchOffset = $aMatches[0][1];
+ $iStyleLength = strlen( $sSelectors );
+ $iOpenParens = 1;
+ $iStartOffset = $iMatchOffset + strlen( $sMatchString );
+ $iFinalOffset = $iStartOffset;
+ for ( ; $iFinalOffset < $iStyleLength; $iFinalOffset++ ) {
+ if ( '(' === $sSelectors[ $iFinalOffset ] || '[' === $sSelectors[ $iFinalOffset ] ) {
+ $iOpenParens++;
+ } elseif ( ')' === $sSelectors[ $iFinalOffset ] || ']' === $sSelectors[ $iFinalOffset ] ) {
+ $iOpenParens--;
+ }
+
+ // Found the end of the expression, so replace it with a placeholder.
+ if ( 0 === $iOpenParens ) {
+ $sMatchedExpr = substr( $sSelectors, $iMatchOffset, $iFinalOffset - $iMatchOffset + 1 );
+ $sPlaceholder = sprintf( '{placeholder:%d}', count( $aPlaceholders ) + 1 );
+ $aPlaceholders[ $sPlaceholder ] = $sMatchedExpr;
+
+ // Update the CSS to replace the matched calc() with the placeholder function.
+ $sSelectors = substr( $sSelectors, 0, $iMatchOffset ) . $sPlaceholder . substr( $sSelectors, $iFinalOffset + 1 );
+ // Update offset based on difference of length of placeholder vs original matched calc().
+ $iFinalOffset += strlen( $sPlaceholder ) - strlen( $sMatchedExpr );
+ break;
+ }
+ }
+ // Start matching at the next byte after the match.
+ $iOffset = $iFinalOffset + 1;
+ }
+ return array( $sSelectors, $aPlaceholders );
+ }
+
// remove one of the selector of the block
public function removeSelector($mSelector) {
if($mSelector instanceof Selector) {
diff --git a/tests/Sabberworm/CSS/ParserTest.php b/tests/Sabberworm/CSS/ParserTest.php
index 43c22e2..a5073be 100644
--- a/tests/Sabberworm/CSS/ParserTest.php
+++ b/tests/Sabberworm/CSS/ParserTest.php
@@ -148,6 +148,12 @@ function testSpecificity() {
case "li.green":
$this->assertSame(11, $oSelector->getSpecificity());
break;
+ case "div:not(.foo[title=\"a,b\"], .bar)":
+ $this->assertSame(31, $oSelector->getSpecificity());
+ break;
+ case "div[title=\"a,b\"]":
+ $this->assertSame(11, $oSelector->getSpecificity());
+ break;
default:
$this->fail("specificity: untested selector " . $oSelector->getSelector());
}
diff --git a/tests/files/specificity.css b/tests/files/specificity.css
index 82a2939..df03ff0 100644
--- a/tests/files/specificity.css
+++ b/tests/files/specificity.css
@@ -2,6 +2,8 @@
#file,
.help:hover,
li.green,
-ol li::before {
+ol li::before,
+div:not(.foo[title="a,b"], .bar),
+div[title="a,b"] {
font-family: Helvetica;
}
From 10a2501c119abafced3e4014aa3c0a3453a86f67 Mon Sep 17 00:00:00 2001
From: Weston Ruter <westonruter@google.com>
Date: Mon, 20 Apr 2020 13:53:44 -0700
Subject: [PATCH 2/2] Update SELECTOR_VALIDATION_RX to account for
parenthetical groups
---
lib/Sabberworm/CSS/Property/Selector.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/Sabberworm/CSS/Property/Selector.php b/lib/Sabberworm/CSS/Property/Selector.php
index bd04b88..0f1c643 100644
--- a/lib/Sabberworm/CSS/Property/Selector.php
+++ b/lib/Sabberworm/CSS/Property/Selector.php
@@ -43,6 +43,7 @@ class Selector {
[a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters
(?:\\\\.)? # a single escaped character
(?:([\'"]).*?(?<!\\\\)\2)? # a quoted text like [id="example"]
+ (?:\(.*?\))? # an argument for pseudo selector like :not(a,b) or :lang(en,es).
)*
)$
/ux';