Skip to content

Commit b02cd79

Browse files
committed
Split classes into their own files
Satisfy the 1-class-per-file requirement
1 parent fd61888 commit b02cd79

9 files changed

+396
-386
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
<?php
2+
3+
final class WP_CSS_Attribute_Selector implements WP_CSS_HTML_Processor_Matcher {
4+
public function matches( WP_HTML_Processor $processor ): bool {
5+
$att_value = $processor->get_attribute( $this->name );
6+
if ( null === $att_value ) {
7+
return false;
8+
}
9+
10+
if ( null === $this->value ) {
11+
return true;
12+
}
13+
14+
if ( true === $att_value ) {
15+
$att_value = '';
16+
}
17+
18+
$case_insensitive = self::MODIFIER_CASE_INSENSITIVE === $this->modifier;
19+
20+
switch ( $this->matcher ) {
21+
case self::MATCH_EXACT:
22+
return $case_insensitive
23+
? 0 === strcasecmp( $att_value, $this->value )
24+
: $att_value === $this->value;
25+
26+
case self::MATCH_ONE_OF_EXACT:
27+
foreach ( $this->whitespace_delimited_list( $att_value ) as $val ) {
28+
if (
29+
$case_insensitive
30+
? 0 === strcasecmp( $val, $this->value )
31+
: $val === $this->value
32+
) {
33+
return true;
34+
}
35+
}
36+
return false;
37+
38+
case self::MATCH_EXACT_OR_EXACT_WITH_HYPHEN:
39+
// Attempt the full match first
40+
if (
41+
$case_insensitive
42+
? 0 === strcasecmp( $att_value, $this->value )
43+
: $att_value === $this->value
44+
) {
45+
return true;
46+
}
47+
48+
// Partial match
49+
if ( strlen( $att_value ) < strlen( $this->value ) + 1 ) {
50+
return false;
51+
}
52+
53+
$starts_with = "{$this->value}-";
54+
return 0 === substr_compare( $att_value, $starts_with, 0, strlen( $starts_with ), $case_insensitive );
55+
56+
case self::MATCH_PREFIXED_BY:
57+
return 0 === substr_compare( $att_value, $this->value, 0, strlen( $this->value ), $case_insensitive );
58+
59+
case self::MATCH_SUFFIXED_BY:
60+
return 0 === substr_compare( $att_value, $this->value, -strlen( $this->value ), null, $case_insensitive );
61+
62+
case self::MATCH_CONTAINS:
63+
return false !== (
64+
$case_insensitive
65+
? stripos( $att_value, $this->value )
66+
: strpos( $att_value, $this->value )
67+
);
68+
}
69+
70+
throw new Exception( 'Unreachable' );
71+
}
72+
73+
/**
74+
* @param string $input
75+
*
76+
* @return Generator<string>
77+
*/
78+
private function whitespace_delimited_list( string $input ): Generator {
79+
$offset = strspn( $input, WP_CSS_Selector::WHITESPACE_CHARACTERS );
80+
81+
while ( $offset < strlen( $input ) ) {
82+
// Find the byte length until the next boundary.
83+
$length = strcspn( $input, WP_CSS_Selector::WHITESPACE_CHARACTERS, $offset );
84+
if ( 0 === $length ) {
85+
return;
86+
}
87+
88+
$value = substr( $input, $offset, $length );
89+
$offset += $length + strspn( $input, WP_CSS_Selector::WHITESPACE_CHARACTERS, $offset + $length );
90+
91+
yield $value;
92+
}
93+
}
94+
95+
/**
96+
* [att=val]
97+
* Represents an element with the att attribute whose value is exactly "val".
98+
*/
99+
const MATCH_EXACT = 'MATCH_EXACT';
100+
101+
/**
102+
* [attr~=value]
103+
* Represents elements with an attribute name of attr whose value is a
104+
* whitespace-separated list of words, one of which is exactly value.
105+
*/
106+
const MATCH_ONE_OF_EXACT = 'MATCH_ONE_OF_EXACT';
107+
108+
/**
109+
* [attr|=value]
110+
* Represents elements with an attribute name of attr whose value can be exactly value or
111+
* can begin with value immediately followed by a hyphen, - (U+002D). It is often used for
112+
* language subcode matches.
113+
*/
114+
const MATCH_EXACT_OR_EXACT_WITH_HYPHEN = 'MATCH_EXACT_OR_EXACT_WITH_HYPHEN';
115+
116+
/**
117+
* [attr^=value]
118+
* Represents elements with an attribute name of attr whose value is prefixed (preceded)
119+
* by value.
120+
*/
121+
const MATCH_PREFIXED_BY = 'MATCH_PREFIXED_BY';
122+
123+
/**
124+
* [attr$=value]
125+
* Represents elements with an attribute name of attr whose value is suffixed (followed)
126+
* by value.
127+
*/
128+
const MATCH_SUFFIXED_BY = 'MATCH_SUFFIXED_BY';
129+
130+
/**
131+
* [attr*=value]
132+
* Represents elements with an attribute name of attr whose value contains at least one
133+
* occurrence of value within the string.
134+
*/
135+
const MATCH_CONTAINS = 'MATCH_CONTAINS';
136+
137+
/**
138+
* Modifier for case sensitive matching
139+
* [attr=value s]
140+
*/
141+
const MODIFIER_CASE_SENSITIVE = 'case-sensitive';
142+
143+
/**
144+
* Modifier for case insensitive matching
145+
* [attr=value i]
146+
*/
147+
const MODIFIER_CASE_INSENSITIVE = 'case-insensitive';
148+
149+
150+
/**
151+
* The attribute name.
152+
*
153+
* @var string
154+
*/
155+
public $name;
156+
157+
/**
158+
* The attribute matcher.
159+
*
160+
* @var null|self::MATCH_*
161+
*/
162+
public $matcher;
163+
164+
/**
165+
* The attribute value.
166+
*
167+
* @var string|null
168+
*/
169+
public $value;
170+
171+
/**
172+
* The attribute modifier.
173+
*
174+
* @var null|self::MODIFIER_*
175+
*/
176+
public $modifier;
177+
178+
/**
179+
* @param string $name
180+
* @param null|self::MATCH_* $matcher
181+
* @param null|string $value
182+
* @param null|self::MODIFIER_* $modifier
183+
*/
184+
public function __construct( string $name, ?string $matcher = null, ?string $value = null, ?string $modifier = null ) {
185+
$this->name = $name;
186+
$this->matcher = $matcher;
187+
$this->value = $value;
188+
$this->modifier = $modifier;
189+
}
190+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
final class WP_CSS_Class_Selector implements WP_CSS_HTML_Processor_Matcher {
4+
public function matches( WP_HTML_Processor $processor ): bool {
5+
return (bool) $processor->has_class( $this->ident );
6+
}
7+
8+
/** @var string */
9+
public $ident;
10+
11+
public function __construct( string $ident ) {
12+
$this->ident = $ident;
13+
}
14+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
/**
4+
* This corresponds to <complex-selector> in the grammar.
5+
*
6+
* > <complex-selector> = <compound-selector> [ <combinator>? <compound-selector> ] *
7+
*/
8+
final class WP_CSS_Complex_Selector implements WP_CSS_HTML_Processor_Matcher {
9+
public function matches( WP_HTML_Processor $processor ): bool {
10+
// First selector must match this location.
11+
if ( ! $this->selectors[0]->matches( $processor ) ) {
12+
return false;
13+
}
14+
15+
if ( count( $this->selectors ) === 1 ) {
16+
return true;
17+
}
18+
19+
/** @var array<string> $breadcrumbs */
20+
$breadcrumbs = array_slice( array_reverse( $processor->get_breadcrumbs() ), 1 );
21+
$selectors = array_slice( $this->selectors, 1 );
22+
return $this->explore_matches( $selectors, $breadcrumbs );
23+
}
24+
25+
/**
26+
* This only looks at breadcrumbs and can therefore only support type selectors.
27+
*
28+
* @param array<WP_CSS_Compound_Selector|self::COMBINATOR_*> $selectors
29+
* @param array<string> $breadcrumbs
30+
*/
31+
private function explore_matches( array $selectors, array $breadcrumbs ): bool {
32+
if ( array() === $selectors ) {
33+
return true;
34+
}
35+
if ( array() === $breadcrumbs ) {
36+
return false;
37+
}
38+
39+
/** @var self::COMBINATOR_* $combinator */
40+
$combinator = $selectors[0];
41+
/** @var WP_CSS_Compound_Selector $selector */
42+
$selector = $selectors[1];
43+
44+
switch ( $combinator ) {
45+
case self::COMBINATOR_CHILD:
46+
if ( '*' === $selector->type_selector->ident || strcasecmp( $breadcrumbs[0], $selector->type_selector->ident ) === 0 ) {
47+
return $this->explore_matches( array_slice( $selectors, 2 ), array_slice( $breadcrumbs, 1 ) );
48+
}
49+
return $this->explore_matches( $selectors, array_slice( $breadcrumbs, 1 ) );
50+
51+
case self::COMBINATOR_DESCENDANT:
52+
// Find _all_ the breadcrumbs that match and recurse from each of them.
53+
for ( $i = 0; $i < count( $breadcrumbs ); $i++ ) {
54+
if ( '*' === $selector->type_selector->ident || strcasecmp( $breadcrumbs[ $i ], $selector->type_selector->ident ) === 0 ) {
55+
$next_crumbs = array_slice( $breadcrumbs, $i + 1 );
56+
if ( $this->explore_matches( array_slice( $selectors, 2 ), $next_crumbs ) ) {
57+
return true;
58+
}
59+
}
60+
}
61+
return false;
62+
63+
default:
64+
throw new Exception( "Combinator '{$combinator}' is not supported yet." );
65+
}
66+
}
67+
68+
const COMBINATOR_CHILD = '>';
69+
const COMBINATOR_DESCENDANT = ' ';
70+
const COMBINATOR_NEXT_SIBLING = '+';
71+
const COMBINATOR_SUBSEQUENT_SIBLING = '~';
72+
73+
/**
74+
* even indexes are WP_CSS_Compound_Selector, odd indexes are string combinators.
75+
* In reverse order to match the current element and then work up the tree.
76+
* Any non-final selector is a type selector.
77+
*
78+
* @var array<WP_CSS_Compound_Selector|self::COMBINATOR_*>
79+
*/
80+
public $selectors = array();
81+
82+
/**
83+
* @param array<WP_CSS_Compound_Selector|self::COMBINATOR_*> $selectors
84+
*/
85+
public function __construct( array $selectors ) {
86+
$this->selectors = array_reverse( $selectors );
87+
}
88+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/**
4+
* This corresponds to <compound-selector> in the grammar.
5+
*
6+
* > <compound-selector> = [ <type-selector>? <subclass-selector>* ]!
7+
*/
8+
final class WP_CSS_Compound_Selector implements WP_CSS_HTML_Processor_Matcher {
9+
public function matches( WP_HTML_Processor $processor ): bool {
10+
if ( $this->type_selector ) {
11+
if ( ! $this->type_selector->matches( $processor ) ) {
12+
return false;
13+
}
14+
}
15+
if ( null !== $this->subclass_selectors ) {
16+
foreach ( $this->subclass_selectors as $subclass_selector ) {
17+
if ( ! $subclass_selector->matches( $processor ) ) {
18+
return false;
19+
}
20+
}
21+
}
22+
return true;
23+
}
24+
25+
/** @var WP_CSS_Type_Selector|null */
26+
public $type_selector;
27+
28+
/** @var array<WP_CSS_ID_Selector|WP_CSS_Class_Selector|WP_CSS_Attribute_Selector>|null */
29+
public $subclass_selectors;
30+
31+
/**
32+
* @param WP_CSS_Type_Selector|null $type_selector
33+
* @param array<WP_CSS_ID_Selector|WP_CSS_Class_Selector|WP_CSS_Attribute_Selector> $subclass_selectors
34+
*/
35+
public function __construct( ?WP_CSS_Type_Selector $type_selector, array $subclass_selectors ) {
36+
$this->type_selector = $type_selector;
37+
$this->subclass_selectors = array() === $subclass_selectors ? null : $subclass_selectors;
38+
}
39+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
final class WP_CSS_ID_Selector implements WP_CSS_HTML_Processor_Matcher {
4+
/** @var string */
5+
public $ident;
6+
7+
public function __construct( string $ident ) {
8+
$this->ident = $ident;
9+
}
10+
11+
public function matches( WP_HTML_Processor $processor ): bool {
12+
$id = $processor->get_attribute( 'id' );
13+
if ( ! is_string( $id ) ) {
14+
return false;
15+
}
16+
17+
$case_insensitive = method_exists( $processor, 'is_quirks_mode' ) && $processor->is_quirks_mode();
18+
return $case_insensitive
19+
? 0 === strcasecmp( $id, $this->ident )
20+
: $processor->get_attribute( 'id' ) === $this->ident;
21+
}
22+
}

0 commit comments

Comments
 (0)