Skip to content

Commit b7e032e

Browse files
committed
Clean up and document attribute selector
1 parent 32778c8 commit b7e032e

File tree

1 file changed

+122
-92
lines changed

1 file changed

+122
-92
lines changed
Lines changed: 122 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,23 @@
11
<?php
2-
2+
/**
3+
* HTML API: WP_CSS_Attribute_Selector class
4+
*
5+
* @package WordPress
6+
* @subpackage HTML-API
7+
* @since TBD
8+
*/
9+
10+
/**
11+
* CSS attribute selector.
12+
*
13+
* This class implements a CSS attribute selector and is used to test for matching HTML tags
14+
* in a {@see WP_HTML_Tag_Processor}.
15+
*
16+
* @since TBD
17+
*
18+
* @access private
19+
*/
320
final class WP_CSS_Attribute_Selector implements WP_CSS_HTML_Tag_Processor_Matcher {
4-
const WHITESPACE_CHARACTERS = " \t\r\n\f";
5-
6-
public function matches( WP_HTML_Tag_Processor $processor ): bool {
7-
$att_value = $processor->get_attribute( $this->name );
8-
if ( null === $att_value ) {
9-
return false;
10-
}
11-
12-
if ( null === $this->value ) {
13-
return true;
14-
}
15-
16-
if ( true === $att_value ) {
17-
$att_value = '';
18-
}
19-
20-
$case_insensitive = self::MODIFIER_CASE_INSENSITIVE === $this->modifier;
21-
22-
switch ( $this->matcher ) {
23-
case self::MATCH_EXACT:
24-
return $case_insensitive
25-
? 0 === strcasecmp( $att_value, $this->value )
26-
: $att_value === $this->value;
27-
28-
case self::MATCH_ONE_OF_EXACT:
29-
foreach ( $this->whitespace_delimited_list( $att_value ) as $val ) {
30-
if (
31-
$case_insensitive
32-
? 0 === strcasecmp( $val, $this->value )
33-
: $val === $this->value
34-
) {
35-
return true;
36-
}
37-
}
38-
return false;
39-
40-
case self::MATCH_EXACT_OR_EXACT_WITH_HYPHEN:
41-
// Attempt the full match first
42-
if (
43-
$case_insensitive
44-
? 0 === strcasecmp( $att_value, $this->value )
45-
: $att_value === $this->value
46-
) {
47-
return true;
48-
}
49-
50-
// Partial match
51-
if ( strlen( $att_value ) < strlen( $this->value ) + 1 ) {
52-
return false;
53-
}
54-
55-
$starts_with = "{$this->value}-";
56-
return 0 === substr_compare( $att_value, $starts_with, 0, strlen( $starts_with ), $case_insensitive );
57-
58-
case self::MATCH_PREFIXED_BY:
59-
return 0 === substr_compare( $att_value, $this->value, 0, strlen( $this->value ), $case_insensitive );
60-
61-
case self::MATCH_SUFFIXED_BY:
62-
return 0 === substr_compare( $att_value, $this->value, -strlen( $this->value ), null, $case_insensitive );
63-
64-
case self::MATCH_CONTAINS:
65-
return false !== (
66-
$case_insensitive
67-
? stripos( $att_value, $this->value )
68-
: strpos( $att_value, $this->value )
69-
);
70-
}
71-
}
72-
73-
/**
74-
* @param string $input
75-
*
76-
* @return Generator<string>
77-
*/
78-
private function whitespace_delimited_list( string $input ): Generator {
79-
// Start by skipping whitespace.
80-
$offset = strspn( $input, self::WHITESPACE_CHARACTERS );
81-
82-
while ( $offset < strlen( $input ) ) {
83-
// Find the byte length until the next boundary.
84-
$length = strcspn( $input, self::WHITESPACE_CHARACTERS, $offset );
85-
$value = substr( $input, $offset, $length );
86-
87-
// Move past trailing whitespace.
88-
$offset += $length + strspn( $input, self::WHITESPACE_CHARACTERS, $offset + $length );
89-
90-
yield $value;
91-
}
92-
}
93-
9421
/**
9522
* [att=val]
9623
* Represents an element with the att attribute whose value is exactly "val".
@@ -145,36 +72,41 @@ private function whitespace_delimited_list( string $input ): Generator {
14572
*/
14673
const MODIFIER_CASE_INSENSITIVE = 'case-insensitive';
14774

148-
14975
/**
15076
* The attribute name.
15177
*
15278
* @var string
79+
* @readonly
15380
*/
15481
public $name;
15582

15683
/**
15784
* The attribute matcher.
15885
*
15986
* @var null|self::MATCH_*
87+
* @readonly
16088
*/
16189
public $matcher;
16290

16391
/**
16492
* The attribute value.
16593
*
16694
* @var string|null
95+
* @readonly
16796
*/
16897
public $value;
16998

17099
/**
171100
* The attribute modifier.
172101
*
173102
* @var null|self::MODIFIER_*
103+
* @readonly
174104
*/
175105
public $modifier;
176106

177107
/**
108+
* Constructor.
109+
*
178110
* @param string $name
179111
* @param null|self::MATCH_* $matcher
180112
* @param null|string $value
@@ -186,4 +118,102 @@ public function __construct( string $name, ?string $matcher = null, ?string $val
186118
$this->value = $value;
187119
$this->modifier = $modifier;
188120
}
121+
122+
/**
123+
* Determines if the processor's current position matches the selector.
124+
*
125+
* @param WP_HTML_Tag_Processor $processor
126+
* @return bool True if the processor's current position matches the selector.
127+
*/
128+
public function matches( WP_HTML_Tag_Processor $processor ): bool {
129+
$att_value = $processor->get_attribute( $this->name );
130+
if ( null === $att_value ) {
131+
return false;
132+
}
133+
134+
if ( null === $this->value ) {
135+
return true;
136+
}
137+
138+
if ( true === $att_value ) {
139+
$att_value = '';
140+
}
141+
142+
$case_insensitive = self::MODIFIER_CASE_INSENSITIVE === $this->modifier;
143+
144+
switch ( $this->matcher ) {
145+
case self::MATCH_EXACT:
146+
return $case_insensitive
147+
? 0 === strcasecmp( $att_value, $this->value )
148+
: $att_value === $this->value;
149+
150+
case self::MATCH_ONE_OF_EXACT:
151+
foreach ( $this->whitespace_delimited_list( $att_value ) as $val ) {
152+
if (
153+
$case_insensitive
154+
? 0 === strcasecmp( $val, $this->value )
155+
: $val === $this->value
156+
) {
157+
return true;
158+
}
159+
}
160+
return false;
161+
162+
case self::MATCH_EXACT_OR_EXACT_WITH_HYPHEN:
163+
// Attempt the full match first
164+
if (
165+
$case_insensitive
166+
? 0 === strcasecmp( $att_value, $this->value )
167+
: $att_value === $this->value
168+
) {
169+
return true;
170+
}
171+
172+
// Partial match
173+
if ( strlen( $att_value ) < strlen( $this->value ) + 1 ) {
174+
return false;
175+
}
176+
177+
$starts_with = "{$this->value}-";
178+
return 0 === substr_compare( $att_value, $starts_with, 0, strlen( $starts_with ), $case_insensitive );
179+
180+
case self::MATCH_PREFIXED_BY:
181+
return 0 === substr_compare( $att_value, $this->value, 0, strlen( $this->value ), $case_insensitive );
182+
183+
case self::MATCH_SUFFIXED_BY:
184+
return 0 === substr_compare( $att_value, $this->value, -strlen( $this->value ), null, $case_insensitive );
185+
186+
case self::MATCH_CONTAINS:
187+
return false !== (
188+
$case_insensitive
189+
? stripos( $att_value, $this->value )
190+
: strpos( $att_value, $this->value )
191+
);
192+
}
193+
}
194+
195+
/**
196+
* Splits a string into a list of whitespace delimited values.
197+
*
198+
* This is useful for the {@see WP_CSS_Attribute_Selector::MATCH_ONE_OF_EXACT} matcher.
199+
*
200+
* @param string $input
201+
*
202+
* @return Generator<string>
203+
*/
204+
private function whitespace_delimited_list( string $input ): Generator {
205+
// Start by skipping whitespace.
206+
$offset = strspn( $input, " \t\r\n\f" );
207+
208+
while ( $offset < strlen( $input ) ) {
209+
// Find the byte length until the next boundary.
210+
$length = strcspn( $input, " \t\r\n\f", $offset );
211+
$value = substr( $input, $offset, $length );
212+
213+
// Move past trailing whitespace.
214+
$offset += $length + strspn( $input, " \t\r\n\f", $offset + $length );
215+
216+
yield $value;
217+
}
218+
}
189219
}

0 commit comments

Comments
 (0)