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 ;
16+ use Sabberworm \CSS \ShortClassNameProvider ;
1617
1718use function Safe \preg_match ;
18- use function Safe \preg_replace ;
1919
2020/**
2121 * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this
2222 * class.
2323 */
2424class Selector implements Renderable
2525{
26+ use ShortClassNameProvider;
27+
2628 /**
2729 * @internal since 8.5.2
2830 */
@@ -54,9 +56,9 @@ class Selector implements Renderable
5456 /ux ' ;
5557
5658 /**
57- * @var string
59+ * @var list<Component>
5860 */
59- private $ selector ;
61+ private $ components = [] ;
6062
6163 /**
6264 * @internal since V8.8.0
@@ -72,9 +74,13 @@ public static function isValid(string $selector): bool
7274 /**
7375 * @throws \UnexpectedValueException if the selector is not valid
7476 */
75- final public function __construct (string $ selector )
77+ final public function __construct (string $ selector = '' )
7678 {
77- $ this ->setSelector ($ selector );
79+ // Allow construction of empty object for content to be set via `setComponents()`.
80+ // (`setSelector()` will throw an exception when provided with an empty string.)
81+ if ($ selector !== '' ) {
82+ $ this ->setSelector ($ selector );
83+ }
7884 }
7985
8086 /**
@@ -137,52 +143,76 @@ public static function parse(ParserState $parserState, array &$comments = []): s
137143 );
138144 }
139145
140- $ selectorString = '' ;
141- foreach ($ selectorParts as $ selectorPart ) {
142- $ selectorPartValue = $ selectorPart ->getValue ();
143- if (\in_array ($ selectorPartValue , ['> ' , '+ ' , '~ ' ], true )) {
144- $ selectorString .= ' ' . $ selectorPartValue . ' ' ;
145- } else {
146- $ selectorString .= $ selectorPartValue ;
147- }
148- }
146+ return (new static ())->setComponents ($ selectorParts );
147+ }
149148
150- return new static ($ selectorString );
149+ /**
150+ * @return list<Component>
151+ */
152+ public function getComponents (): array
153+ {
154+ return $ this ->components ;
155+ }
156+
157+ /**
158+ * @param list<Component> $components
159+ * This should be an alternating sequence of `CompoundSelector` and `Combinator`, starting and ending with a
160+ * `CompoundSelector`, and may be a single `CompoundSelector`.
161+ */
162+ public function setComponents (array $ components ): self
163+ {
164+ $ this ->components = $ components ;
165+
166+ return $ this ;
151167 }
152168
153169 public function getSelector (): string
154170 {
155- return $ this ->selector ;
171+ return $ this ->render ( new OutputFormat ()) ;
156172 }
157173
158174 /**
159175 * @throws \UnexpectedValueException if the selector is not valid
160176 */
161177 public function setSelector (string $ selector ): void
162178 {
163- if (!self ::isValid ($ selector )) {
164- throw new \UnexpectedValueException ("Selector ` $ selector` is not valid. " );
165- }
179+ $ parserState = new ParserState ($ selector , Settings::create ());
166180
167- $ selector = \trim ( $ selector );
181+ $ components = self :: parseComponents ( $ parserState );
168182
169- $ hasAttribute = \strpos ($ selector , '[ ' ) !== false ;
183+ // Check that the selector has been fully parsed:
184+ if (!$ parserState ->isEnd ()) {
185+ throw new UnexpectedTokenException (
186+ 'EOF ' ,
187+ $ parserState ->peek (5 ),
188+ 'literal '
189+ );
190+ }
170191
171- // Whitespace can't be adjusted within an attribute selector, as it would change its meaning
172- $ this ->selector = !$ hasAttribute ? preg_replace ('/ \\s++/ ' , ' ' , $ selector ) : $ selector ;
192+ $ this ->components = $ components ;
173193 }
174194
175195 /**
176196 * @return int<0, max>
177197 */
178198 public function getSpecificity (): int
179199 {
180- return SpecificityCalculator::calculate ($ this ->selector );
200+ return \array_sum (\array_map (
201+ static function (Component $ component ): int {
202+ return $ component ->getSpecificity ();
203+ },
204+ $ this ->components
205+ ));
181206 }
182207
183208 public function render (OutputFormat $ outputFormat ): string
184209 {
185- return $ this ->getSelector ();
210+ return \implode ('' , \array_map (
211+ static function (Component $ component ) use ($ outputFormat ): string {
212+ return $ component ->render ($ outputFormat );
213+ },
214+ $ this ->components
215+ ));
186216 }
187217
188218 /**
@@ -192,6 +222,14 @@ public function render(OutputFormat $outputFormat): string
192222 */
193223 public function getArrayRepresentation (): array
194224 {
195- throw new \BadMethodCallException ('`getArrayRepresentation` is not yet implemented for ` ' . self ::class . '` ' );
225+ return [
226+ 'class ' => $ this ->getShortClassName (),
227+ 'components ' => \array_map (
228+ static function (Component $ component ): array {
229+ return $ component ->getArrayRepresentation ();
230+ },
231+ $ this ->components
232+ ),
233+ ];
196234 }
197235}
0 commit comments