MDL-65759 library: Update php-css-parser to 8.3.0
[moodle.git] / lib / php-css-parser / OutputFormat.php
1 <?php
3 namespace Sabberworm\CSS;
5 use Sabberworm\CSS\Parsing\OutputException;
7 /**
8  * Class OutputFormat
9  *
10  * @method OutputFormat setSemicolonAfterLastRule( bool $bSemicolonAfterLastRule ) Set whether semicolons are added after last rule.
11  */
12 class OutputFormat {
13         /**
14         * Value format
15         */
16         // " means double-quote, ' means single-quote
17         public $sStringQuotingType = '"';
18         // Output RGB colors in hash notation if possible
19         public $bRGBHashNotation = true;
20         
21         /**
22         * Declaration format
23         */
24         // Semicolon after the last rule of a declaration block can be omitted. To do that, set this false.
25         public $bSemicolonAfterLastRule = true;
26         
27         /**
28         * Spacing
29         * Note that these strings are not sanity-checked: the value should only consist of whitespace
30         * Any newline character will be indented according to the current level.
31         * The triples (After, Before, Between) can be set using a wildcard (e.g. `$oFormat->set('Space*Rules', "\n");`)
32         */
33         public $sSpaceAfterRuleName = ' ';
35         public $sSpaceBeforeRules = '';
36         public $sSpaceAfterRules = '';
37         public $sSpaceBetweenRules = '';
39         public $sSpaceBeforeBlocks = '';
40         public $sSpaceAfterBlocks = '';
41         public $sSpaceBetweenBlocks = "\n";
43         // Content injected in and around @-rule blocks.
44         public $sBeforeAtRuleBlock = '';
45         public $sAfterAtRuleBlock = '';
47         // This is what’s printed before and after the comma if a declaration block contains multiple selectors.
48         public $sSpaceBeforeSelectorSeparator = '';
49         public $sSpaceAfterSelectorSeparator = ' ';
50         // This is what’s printed after the comma of value lists
51         public $sSpaceBeforeListArgumentSeparator = '';
52         public $sSpaceAfterListArgumentSeparator = '';
53         
54         public $sSpaceBeforeOpeningBrace = ' ';
56         // Content injected in and around declaration blocks.
57         public $sBeforeDeclarationBlock = '';
58         public $sAfterDeclarationBlockSelectors = '';
59         public $sAfterDeclarationBlock = '';
61         /**
62         * Indentation
63         */
64         // Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings.
65         public $sIndentation = "\t";
66         
67         /**
68         * Output exceptions.
69         */
70         public $bIgnoreExceptions = false;
71         
72         
73         private $oFormatter = null;
74         private $oNextLevelFormat = null;
75         private $iIndentationLevel = 0;
76         
77         public function __construct() {
78         }
79         
80         public function get($sName) {
81                 $aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i');
82                 foreach($aVarPrefixes as $sPrefix) {
83                         $sFieldName = $sPrefix.ucfirst($sName);
84                         if(isset($this->$sFieldName)) {
85                                 return $this->$sFieldName;
86                         }
87                 }
88                 return null;
89         }
90         
91         public function set($aNames, $mValue) {
92                 $aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i');
93                 if(is_string($aNames) && strpos($aNames, '*') !== false) {
94                         $aNames = array(str_replace('*', 'Before', $aNames), str_replace('*', 'Between', $aNames), str_replace('*', 'After', $aNames));
95                 } else if(!is_array($aNames)) {
96                         $aNames = array($aNames);
97                 }
98                 foreach($aVarPrefixes as $sPrefix) {
99                         $bDidReplace = false;
100                         foreach($aNames as $sName) {
101                                 $sFieldName = $sPrefix.ucfirst($sName);
102                                 if(isset($this->$sFieldName)) {
103                                         $this->$sFieldName = $mValue;
104                                         $bDidReplace = true;
105                                 }
106                         }
107                         if($bDidReplace) {
108                                 return $this;
109                         }
110                 }
111                 // Break the chain so the user knows this option is invalid
112                 return false;
113         }
114         
115         public function __call($sMethodName, $aArguments) {
116                 if(strpos($sMethodName, 'set') === 0) {
117                         return $this->set(substr($sMethodName, 3), $aArguments[0]);
118                 } else if(strpos($sMethodName, 'get') === 0) {
119                         return $this->get(substr($sMethodName, 3));
120                 } else if(method_exists('\\Sabberworm\\CSS\\OutputFormatter', $sMethodName)) {
121                         return call_user_func_array(array($this->getFormatter(), $sMethodName), $aArguments);
122                 } else {
123                         throw new \Exception('Unknown OutputFormat method called: '.$sMethodName);
124                 }
125         }
126         
127         public function indentWithTabs($iNumber = 1) {
128                 return $this->setIndentation(str_repeat("\t", $iNumber));
129         }
130         
131         public function indentWithSpaces($iNumber = 2) {
132                 return $this->setIndentation(str_repeat(" ", $iNumber));
133         }
134         
135         public function nextLevel() {
136                 if($this->oNextLevelFormat === null) {
137                         $this->oNextLevelFormat = clone $this;
138                         $this->oNextLevelFormat->iIndentationLevel++;
139                         $this->oNextLevelFormat->oFormatter = null;
140                 }
141                 return $this->oNextLevelFormat;
142         }
143         
144         public function beLenient() {
145                 $this->bIgnoreExceptions = true;
146         }
147         
148         public function getFormatter() {
149                 if($this->oFormatter === null) {
150                         $this->oFormatter = new OutputFormatter($this);
151                 }
152                 return $this->oFormatter;
153         }
154         
155         public function level() {
156                 return $this->iIndentationLevel;
157         }
159         /**
160          * Create format.
161          *
162          * @return OutputFormat Format.
163          */
164         public static function create() {
165                 return new OutputFormat();
166         }
168         /**
169          * Create compact format.
170          *
171          * @return OutputFormat Format.
172          */
173         public static function createCompact() {
174                 $format = self::create();
175                 $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('')->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator('');
176                 return $format;
177         }
179         /**
180          * Create pretty format.
181          *
182          * @return OutputFormat Format.
183          */
184         public static function createPretty() {
185                 $format = self::create();
186                 $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n")->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
187                 return $format;
188         }
191 class OutputFormatter {
192         private $oFormat;
193         
194         public function __construct(OutputFormat $oFormat) {
195                 $this->oFormat = $oFormat;
196         }
197         
198         public function space($sName, $sType = null) {
199                 $sSpaceString = $this->oFormat->get("Space$sName");
200                 // If $sSpaceString is an array, we have multple values configured depending on the type of object the space applies to
201                 if(is_array($sSpaceString)) {
202                         if($sType !== null && isset($sSpaceString[$sType])) {
203                                 $sSpaceString = $sSpaceString[$sType];
204                         } else {
205                                 $sSpaceString = reset($sSpaceString);
206                         }
207                 }
208                 return $this->prepareSpace($sSpaceString);
209         }
210         
211         public function spaceAfterRuleName() {
212                 return $this->space('AfterRuleName');
213         }
214         
215         public function spaceBeforeRules() {
216                 return $this->space('BeforeRules');
217         }
218         
219         public function spaceAfterRules() {
220                 return $this->space('AfterRules');
221         }
222         
223         public function spaceBetweenRules() {
224                 return $this->space('BetweenRules');
225         }
226         
227         public function spaceBeforeBlocks() {
228                 return $this->space('BeforeBlocks');
229         }
230         
231         public function spaceAfterBlocks() {
232                 return $this->space('AfterBlocks');
233         }
234         
235         public function spaceBetweenBlocks() {
236                 return $this->space('BetweenBlocks');
237         }
238         
239         public function spaceBeforeSelectorSeparator() {
240                 return $this->space('BeforeSelectorSeparator');
241         }
243         public function spaceAfterSelectorSeparator() {
244                 return $this->space('AfterSelectorSeparator');
245         }
247         public function spaceBeforeListArgumentSeparator($sSeparator) {
248                 return $this->space('BeforeListArgumentSeparator', $sSeparator);
249         }
251         public function spaceAfterListArgumentSeparator($sSeparator) {
252                 return $this->space('AfterListArgumentSeparator', $sSeparator);
253         }
255         public function spaceBeforeOpeningBrace() {
256                 return $this->space('BeforeOpeningBrace');
257         }
259         /**
260         * Runs the given code, either swallowing or passing exceptions, depending on the bIgnoreExceptions setting.
261         */
262         public function safely($cCode) {
263                 if($this->oFormat->get('IgnoreExceptions')) {
264                         // If output exceptions are ignored, run the code with exception guards
265                         try {
266                                 return $cCode();
267                         } catch (OutputException $e) {
268                                 return null;
269                         } //Do nothing
270                 } else {
271                         // Run the code as-is
272                         return $cCode();
273                 }
274         }
276         /**
277         * Clone of the implode function but calls ->render with the current output format instead of __toString()
278         */
279         public function implode($sSeparator, $aValues, $bIncreaseLevel = false) {
280                 $sResult = '';
281                 $oFormat = $this->oFormat;
282                 if($bIncreaseLevel) {
283                         $oFormat = $oFormat->nextLevel();
284                 }
285                 $bIsFirst = true;
286                 foreach($aValues as $mValue) {
287                         if($bIsFirst) {
288                                 $bIsFirst = false;
289                         } else {
290                                 $sResult .= $sSeparator;
291                         }
292                         if($mValue instanceof \Sabberworm\CSS\Renderable) {
293                                 $sResult .= $mValue->render($oFormat);
294                         } else {
295                                 $sResult .= $mValue;
296                         }
297                 }
298                 return $sResult;
299         }
300         
301         public function removeLastSemicolon($sString) {
302                 if($this->oFormat->get('SemicolonAfterLastRule')) {
303                         return $sString;
304                 }
305                 $sString = explode(';', $sString);
306                 if(count($sString) < 2) {
307                         return $sString[0];
308                 }
309                 $sLast = array_pop($sString);
310                 $sNextToLast = array_pop($sString);
311                 array_push($sString, $sNextToLast.$sLast);
312                 return implode(';', $sString);
313         }
315         private function prepareSpace($sSpaceString) {
316                 return str_replace("\n", "\n".$this->indent(), $sSpaceString);
317         }
319         private function indent() {
320                 return str_repeat($this->oFormat->sIndentation, $this->oFormat->level());
321         }