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 ;
1616use Sabberworm \CSS \ShortClassNameProvider ;
1717
1818use function Safe \preg_match ;
19- use function Safe \preg_replace ;
2019
2120/**
2221 * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this
@@ -59,9 +58,9 @@ class Selector implements Renderable
5958 /ux ' ;
6059
6160 /**
62- * @var non-empty-string
61+ * @var list<Component>
6362 */
64- private $ selector ;
63+ private $ components = [] ;
6564
6665 /**
6766 * @internal since V8.8.0
@@ -79,9 +78,13 @@ public static function isValid(string $selector): bool
7978 *
8079 * @throws \UnexpectedValueException if the selector is not valid
8180 */
82- final public function __construct (string $ selector )
81+ final public function __construct (string $ selector = '' )
8382 {
84- $ this ->setSelector ($ selector );
83+ // Allow construction of empty object for content to be set via `setComponents()`.
84+ // (`setSelector()` will throw an exception when provided with an empty string.)
85+ if ($ selector !== '' ) {
86+ $ this ->setSelector ($ selector );
87+ }
8588 }
8689
8790 /**
@@ -144,25 +147,35 @@ public static function parse(ParserState $parserState, array &$comments = []): s
144147 );
145148 }
146149
147- $ selectorString = '' ;
148- foreach ($ selectorParts as $ selectorPart ) {
149- $ selectorPartValue = $ selectorPart ->getValue ();
150- if (\in_array ($ selectorPartValue , ['> ' , '+ ' , '~ ' ], true )) {
151- $ selectorString .= ' ' . $ selectorPartValue . ' ' ;
152- } else {
153- $ selectorString .= $ selectorPartValue ;
154- }
155- }
150+ return (new static ())->setComponents ($ selectorParts );
151+ }
156152
157- return new static ($ selectorString );
153+ /**
154+ * @return list<Component>
155+ */
156+ public function getComponents (): array
157+ {
158+ return $ this ->components ;
159+ }
160+
161+ /**
162+ * @param list<Component> $components
163+ * This should be an alternating sequence of `CompoundSelector` and `Combinator`, starting and ending with a
164+ * `CompoundSelector`, and may be a single `CompoundSelector`.
165+ */
166+ public function setComponents (array $ components ): self
167+ {
168+ $ this ->components = $ components ;
169+
170+ return $ this ;
158171 }
159172
160173 /**
161174 * @return non-empty-string
162175 */
163176 public function getSelector (): string
164177 {
165- return $ this ->selector ;
178+ return $ this ->render ( new OutputFormat ()) ;
166179 }
167180
168181 /**
@@ -172,29 +185,43 @@ public function getSelector(): string
172185 */
173186 public function setSelector (string $ selector ): void
174187 {
175- if (!self ::isValid ($ selector )) {
176- throw new \UnexpectedValueException ("Selector ` $ selector` is not valid. " );
177- }
188+ $ parserState = new ParserState ($ selector , Settings::create ());
178189
179- $ selector = \trim ( $ selector );
190+ $ components = self :: parseComponents ( $ parserState );
180191
181- $ hasAttribute = \strpos ($ selector , '[ ' ) !== false ;
192+ // Check that the selector has been fully parsed:
193+ if (!$ parserState ->isEnd ()) {
194+ throw new UnexpectedTokenException (
195+ 'EOF ' ,
196+ $ parserState ->peek (5 ),
197+ 'literal '
198+ );
199+ }
182200
183- // Whitespace can't be adjusted within an attribute selector, as it would change its meaning
184- $ this ->selector = !$ hasAttribute ? preg_replace ('/ \\s++/ ' , ' ' , $ selector ) : $ selector ;
201+ $ this ->components = $ components ;
185202 }
186203
187204 /**
188205 * @return int<0, max>
189206 */
190207 public function getSpecificity (): int
191208 {
192- return SpecificityCalculator::calculate ($ this ->selector );
209+ return \array_sum (\array_map (
210+ static function (Component $ component ): int {
211+ return $ component ->getSpecificity ();
212+ },
213+ $ this ->components
214+ ));
193215 }
194216
195217 public function render (OutputFormat $ outputFormat ): string
196218 {
197- return $ this ->getSelector ();
219+ return \implode ('' , \array_map (
220+ static function (Component $ component ) use ($ outputFormat ): string {
221+ return $ component ->render ($ outputFormat );
222+ },
223+ $ this ->components
224+ ));
198225 }
199226
200227 /**
@@ -206,6 +233,12 @@ public function getArrayRepresentation(): array
206233 {
207234 return [
208235 'class ' => $ this ->getShortClassName (),
236+ 'components ' => \array_map (
237+ static function (Component $ component ): array {
238+ return $ component ->getArrayRepresentation ();
239+ },
240+ $ this ->components
241+ ),
209242 ];
210243 }
211244}
0 commit comments