1111use Sabberworm \CSS \Property \Selector \Combinator ;
1212use Sabberworm \CSS \Property \Selector \Component ;
1313use Sabberworm \CSS \Property \Selector \CompoundSelector ;
14- use Sabberworm \CSS \Property \Selector \SpecificityCalculator ;
1514use Sabberworm \CSS \Renderable ;
15+ use Sabberworm \CSS \Settings ;
1616
1717use function Safe \preg_match ;
18- use function Safe \preg_replace ;
1918
2019/**
2120 * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this
2221 * class.
2322 */
2423class Selector implements Renderable
2524{
25+ use ShortClassNameProvider;
26+
2627 /**
2728 * @internal since 8.5.2
2829 */
@@ -54,9 +55,9 @@ class Selector implements Renderable
5455 /ux ' ;
5556
5657 /**
57- * @var string
58+ * @var list<Component>
5859 */
59- private $ selector ;
60+ private $ components = [] ;
6061
6162 /**
6263 * @internal since V8.8.0
@@ -69,9 +70,13 @@ public static function isValid(string $selector): bool
6970 return $ numberOfMatches === 1 ;
7071 }
7172
72- final public function __construct (string $ selector )
73+ final public function __construct (string $ selector = '' )
7374 {
74- $ this ->setSelector ($ selector );
75+ // Allow construction of empty object for content to be set via `setComponents()`.
76+ // (`setSelector()` will throw an exception when provided with an empty string.)
77+ if ($ selector !== '' ) {
78+ $ this ->setSelector ($ selector );
79+ }
7580 }
7681
7782 /**
@@ -134,45 +139,73 @@ public static function parse(ParserState $parserState, array &$comments = []): s
134139 );
135140 }
136141
137- $ selectorString = '' ;
138- foreach ($ selectorParts as $ selectorPart ) {
139- $ selectorPartValue = $ selectorPart ->getValue ();
140- if (\in_array ($ selectorPartValue , ['> ' , '+ ' , '~ ' ], true )) {
141- $ selectorString .= ' ' . $ selectorPartValue . ' ' ;
142- } else {
143- $ selectorString .= $ selectorPartValue ;
144- }
145- }
142+ return (new static ())->setComponents ($ selectorParts );
143+ }
146144
147- return new static ($ selectorString );
145+ /**
146+ * @return list<Component>
147+ */
148+ public function getComponents (): array
149+ {
150+ return $ this ->components ;
151+ }
152+
153+ /**
154+ * @param list<Component> $components
155+ * This should be an alternating sequence of `CompoundSelector` and `Combinator`, starting and ending with a
156+ * `CompoundSelector`, and may be a single `CompoundSelector`.
157+ */
158+ public function setComponents (array $ components ): self
159+ {
160+ $ this ->components = $ components ;
161+
162+ return $ this ;
148163 }
149164
150165 public function getSelector (): string
151166 {
152- return $ this ->selector ;
167+ return $ this ->render ( new OutputFormat ()) ;
153168 }
154169
155170 public function setSelector (string $ selector ): void
156171 {
157- $ selector = \trim ($ selector );
172+ $ parserState = new ParserState ($ selector , Settings::create ());
173+
174+ $ components = self ::parseComponents ($ parserState );
158175
159- $ hasAttribute = \strpos ($ selector , '[ ' ) !== false ;
176+ // Check that the selector has been fully parsed:
177+ if (!$ parserState ->isEnd ()) {
178+ throw new UnexpectedTokenException (
179+ 'EOF ' ,
180+ $ parserState ->peek (5 ),
181+ 'literal '
182+ );
183+ }
160184
161- // Whitespace can't be adjusted within an attribute selector, as it would change its meaning
162- $ this ->selector = !$ hasAttribute ? preg_replace ('/ \\s++/ ' , ' ' , $ selector ) : $ selector ;
185+ $ this ->components = $ components ;
163186 }
164187
165188 /**
166189 * @return int<0, max>
167190 */
168191 public function getSpecificity (): int
169192 {
170- return SpecificityCalculator::calculate ($ this ->selector );
193+ return \array_sum (\array_map (
194+ static function (Component $ component ): int {
195+ return $ component ->getSpecificity ();
196+ },
197+ $ this ->components
198+ ));
171199 }
172200
173201 public function render (OutputFormat $ outputFormat ): string
174202 {
175- return $ this ->getSelector ();
203+ return \implode ('' , \array_map (
204+ static function (Component $ component ) use ($ outputFormat ): string {
205+ return $ component ->render ($ outputFormat );
206+ },
207+ $ this ->components
208+ ));
176209 }
177210
178211 /**
@@ -182,6 +215,14 @@ public function render(OutputFormat $outputFormat): string
182215 */
183216 public function getArrayRepresentation (): array
184217 {
185- throw new \BadMethodCallException ('`getArrayRepresentation` is not yet implemented for ` ' . self ::class . '` ' );
218+ return [
219+ 'class ' => $ this ->getShortClassName (),
220+ 'components ' => \array_map (
221+ static function (Component $ component ): array {
222+ return $ component ->getArrayRepresentation ();
223+ },
224+ $ this ->components
225+ ),
226+ ];
186227 }
187228}
0 commit comments