1<?php /** @noinspection PhpUnused */
2
3/**
4* An ast visitor that compiles a css selector string
5*
6* @license http://www.opensource.org/licenses/mit-license.php The MIT License
7* @copyright Copyright 2010-2014 PhpCss Team
8*/
9namespace PhpCss\Ast\Visitor  {
10
11  use LogicException;
12  use PhpCss\Ast;
13
14  /**
15  * An ast visitor that compiles a css selector string
16  */
17  class Css extends Overload {
18
19    private $_buffer = '';
20    private $_inSelectorSequence = FALSE;
21
22    /**
23    * Clear the visitor object to visit another selector group
24    */
25    public function clear(): void {
26      $this->_buffer = '';
27    }
28
29    /**
30    * Return the collected selector string
31    */
32    public function __toString() {
33      return $this->_buffer;
34    }
35
36    /**
37    * Validate the buffer before visiting a Ast\Selector\Group.
38    * If the buffer already contains data, throw an exception.
39    *
40    * @throws LogicException
41    * @param Ast\Selector\Group $group
42    * @return boolean
43    */
44    public function visitEnterSelectorGroup(Ast\Selector\Group $group): bool {
45      if (!empty($this->_buffer)) {
46        throw new LogicException(
47          sprintf(
48            'Visitor buffer already contains data, can not visit "%s"',
49            get_class($group)
50          )
51        );
52      }
53      return TRUE;
54    }
55
56    /**
57    * If here is already data in the buffer, add a separator before starting the next.
58    *
59    * @return boolean
60    */
61    public function visitEnterSelectorSequence(): bool {
62      if ($this->_inSelectorSequence) {
63        $this->_buffer .= ', ';
64      }
65      $this->_inSelectorSequence = TRUE;
66      return TRUE;
67    }
68
69    /**
70    * Output the universal selector to the buffer
71    *
72    * @param Ast\Selector\Simple\Universal $universal
73    * @return boolean
74    */
75    public function visitSelectorSimpleUniversal(Ast\Selector\Simple\Universal $universal): bool {
76      if (!empty($universal->namespacePrefix) && $universal->namespacePrefix !== '*') {
77        $this->_buffer .= $universal->namespacePrefix.'|*';
78      } else {
79        $this->_buffer .= '*';
80      }
81      return TRUE;
82    }
83
84    /**
85    * Output the type selector to the buffer
86    *
87    * @param Ast\Selector\Simple\Type $type
88    * @return boolean
89    */
90    public function visitSelectorSimpleType(Ast\Selector\Simple\Type $type): bool {
91      if (!empty($type->namespacePrefix) && $type->namespacePrefix !== '*') {
92        $this->_buffer .= $type->namespacePrefix.'|'.$type->elementName;
93      } else {
94        $this->_buffer .= $type->elementName;
95      }
96      return TRUE;
97    }
98
99    /**
100    * Output the class selector to the buffer
101    *
102    * @param Ast\Selector\Simple\Id $id
103    * @return boolean
104    */
105    public function visitSelectorSimpleId(Ast\Selector\Simple\Id $id): bool {
106      $this->_buffer .= '#'.$id->id;
107      return TRUE;
108    }
109
110    /**
111    * Output the class selector to the buffer
112    *
113    * @param Ast\Selector\Simple\ClassName $class
114    * @return boolean
115    */
116    public function visitSelectorSimpleClassName(Ast\Selector\Simple\ClassName $class): bool {
117      $this->_buffer .= '.'.$class->className;
118      return TRUE;
119    }
120
121    public function visitEnterSelectorCombinatorDescendant(): bool {
122      if ($this->_buffer !== '') {
123        $this->_buffer .= ' ';
124      }
125      $this->_inSelectorSequence = FALSE;
126      return TRUE;
127    }
128
129    public function visitEnterSelectorCombinatorChild(): bool {
130      $this->_buffer .= ($this->_buffer !== '') ? ' > ' : '> ';
131      $this->_inSelectorSequence = FALSE;
132      return TRUE;
133    }
134
135    public function visitEnterSelectorCombinatorFollower(): bool {
136      $this->_buffer .= ($this->_buffer !== '') ? ' ~ ' : '~ ';
137      $this->_inSelectorSequence = FALSE;
138      return TRUE;
139    }
140
141    public function visitEnterSelectorCombinatorNext(): bool {
142      $this->_buffer .= ($this->_buffer !== '') ? ' + ' : '+ ';
143      $this->_inSelectorSequence = FALSE;
144      return TRUE;
145    }
146
147    public function visitSelectorSimpleAttribute(
148      Ast\Selector\Simple\Attribute $attribute
149    ): bool {
150      $this->_buffer .= '[';
151      $this->_buffer .= $attribute->name;
152      if ($attribute->match !== Ast\Selector\Simple\Attribute::MATCH_EXISTS) {
153        $operatorStrings = [
154          Ast\Selector\Simple\Attribute::MATCH_PREFIX => '^=',
155          Ast\Selector\Simple\Attribute::MATCH_SUFFIX => '$=',
156          Ast\Selector\Simple\Attribute::MATCH_SUBSTRING => '*=',
157          Ast\Selector\Simple\Attribute::MATCH_EQUALS => '=',
158          Ast\Selector\Simple\Attribute::MATCH_INCLUDES => '~=',
159          Ast\Selector\Simple\Attribute::MATCH_DASHMATCH => '|='
160        ];
161        $this->_buffer .= $operatorStrings[$attribute->match];
162        $this->_buffer .= $this->quoteString($attribute->literal->value);
163      }
164      $this->_buffer .= ']';
165      return TRUE;
166    }
167
168    public function visitSelectorSimplePseudoClass(
169      Ast\Selector\Simple\PseudoClass $class
170    ): void {
171      $this->_buffer .= ':'.$class->name;
172      if ($class->parameter) {
173        $this->_buffer .= '(';
174        $this->visit($class->parameter);
175        $this->_buffer .= ')';
176      }
177    }
178
179    public function visitValueLiteral(
180      Ast\Value\Literal $literal
181    ): void {
182      $this->_buffer .= $this->quoteString($literal->value);
183    }
184
185    public function visitValueNumber(
186      Ast\Value\Number $literal
187    ): void {
188      $this->_buffer .= $literal->value;
189    }
190
191    public function visitEnterSelectorSimplePseudoClass(
192      Ast\Selector\Simple\PseudoClass $class
193    ): bool {
194      $this->_buffer .= ':'.$class->name.'(';
195      return TRUE;
196    }
197
198    public function visitLeaveSelectorSimplePseudoClass(): void {
199      $this->_buffer .= ')';
200    }
201
202    public function visitValuePosition(
203      Ast\Value\Position $position
204    ): void {
205      $repeatsOddEven = $position->repeat === 2;
206      if ($repeatsOddEven && $position->add === 1) {
207        $this->_buffer .= 'odd';
208      } elseif ($repeatsOddEven && $position->add === 0) {
209        $this->_buffer .= 'even';
210      } elseif ($position->repeat === 0) {
211        $this->_buffer .= $position->add;
212      } elseif ($position->repeat === 1) {
213        $this->_buffer .= 'n';
214        if ($position->add !== 0) {
215          $this->_buffer .= $position->add >= 0
216            ? '+'.$position->add : $position->add;
217        }
218      } else {
219        $this->_buffer .= $position->repeat.'n';
220        if ($position->add !== 0) {
221          $this->_buffer .= $position->add >= 0
222            ? '+'.$position->add : $position->add;
223        }
224      }
225    }
226
227    public function visitValueLanguage(
228      Ast\Value\Language $language
229    ): void {
230      $this->_buffer .= ':lang('.$language->language.')';
231    }
232
233    public function visitSelectorSimplePseudoElement(
234      Ast\Selector\Simple\PseudoElement $element
235    ): void {
236      $this->_buffer .= '::'.$element->name;
237    }
238
239    private function quoteString(string $string): string {
240      return '"'.str_replace(array('\\', '"'), array('\\\\', '\\"'), $string).'"';
241    }
242  }
243}
244