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
@@ -69,9 +71,13 @@ public static function isValid(string $selector): bool
6971 return $ numberOfMatches === 1 ;
7072 }
7173
72- final public function __construct (string $ selector )
74+ final public function __construct (string $ selector = '' )
7375 {
74- $ this ->setSelector ($ selector );
76+ // Allow construction of empty object for content to be set via `setComponents()`.
77+ // (`setSelector()` will throw an exception when provided with an empty string.)
78+ if ($ selector !== '' ) {
79+ $ this ->setSelector ($ selector );
80+ }
7581 }
7682
7783 /**
@@ -134,45 +140,73 @@ public static function parse(ParserState $parserState, array &$comments = []): s
134140 );
135141 }
136142
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- }
143+ return (new static ())->setComponents ($ selectorParts );
144+ }
146145
147- return new static ($ selectorString );
146+ /**
147+ * @return list<Component>
148+ */
149+ public function getComponents (): array
150+ {
151+ return $ this ->components ;
152+ }
153+
154+ /**
155+ * @param list<Component> $components
156+ * This should be an alternating sequence of `CompoundSelector` and `Combinator`, starting and ending with a
157+ * `CompoundSelector`, and may be a single `CompoundSelector`.
158+ */
159+ public function setComponents (array $ components ): self
160+ {
161+ $ this ->components = $ components ;
162+
163+ return $ this ;
148164 }
149165
150166 public function getSelector (): string
151167 {
152- return $ this ->selector ;
168+ return $ this ->render ( new OutputFormat ()) ;
153169 }
154170
155171 public function setSelector (string $ selector ): void
156172 {
157- $ selector = \trim ($ selector );
173+ $ parserState = new ParserState ($ selector , Settings::create ());
174+
175+ $ components = self ::parseComponents ($ parserState );
158176
159- $ hasAttribute = \strpos ($ selector , '[ ' ) !== false ;
177+ // Check that the selector has been fully parsed:
178+ if (!$ parserState ->isEnd ()) {
179+ throw new UnexpectedTokenException (
180+ 'EOF ' ,
181+ $ parserState ->peek (5 ),
182+ 'literal '
183+ );
184+ }
160185
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 ;
186+ $ this ->components = $ components ;
163187 }
164188
165189 /**
166190 * @return int<0, max>
167191 */
168192 public function getSpecificity (): int
169193 {
170- return SpecificityCalculator::calculate ($ this ->selector );
194+ return \array_sum (\array_map (
195+ static function (Component $ component ): int {
196+ return $ component ->getSpecificity ();
197+ },
198+ $ this ->components
199+ ));
171200 }
172201
173202 public function render (OutputFormat $ outputFormat ): string
174203 {
175- return $ this ->getSelector ();
204+ return \implode ('' , \array_map (
205+ static function (Component $ component ) use ($ outputFormat ): string {
206+ return $ component ->render ($ outputFormat );
207+ },
208+ $ this ->components
209+ ));
176210 }
177211
178212 /**
@@ -182,6 +216,14 @@ public function render(OutputFormat $outputFormat): string
182216 */
183217 public function getArrayRepresentation (): array
184218 {
185- throw new \BadMethodCallException ('`getArrayRepresentation` is not yet implemented for ` ' . self ::class . '` ' );
219+ return [
220+ 'class ' => $ this ->getShortClassName (),
221+ 'components ' => \array_map (
222+ static function (Component $ component ): array {
223+ return $ component ->getArrayRepresentation ();
224+ },
225+ $ this ->components
226+ ),
227+ ];
186228 }
187229}
0 commit comments