MDL-65759 library: Update php-css-parser to 8.3.0
[moodle.git] / lib / php-css-parser / OutputFormat.php
CommitLineData
fbe18cc0
FM
1<?php
2
3namespace Sabberworm\CSS;
4
5use Sabberworm\CSS\Parsing\OutputException;
6
376eb156
MM
7/**
8 * Class OutputFormat
9 *
10 * @method OutputFormat setSemicolonAfterLastRule( bool $bSemicolonAfterLastRule ) Set whether semicolons are added after last rule.
11 */
fbe18cc0
FM
12class 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 = ' ';
34
35 public $sSpaceBeforeRules = '';
36 public $sSpaceAfterRules = '';
37 public $sSpaceBetweenRules = '';
38
39 public $sSpaceBeforeBlocks = '';
40 public $sSpaceAfterBlocks = '';
41 public $sSpaceBetweenBlocks = "\n";
42
376eb156
MM
43 // Content injected in and around @-rule blocks.
44 public $sBeforeAtRuleBlock = '';
45 public $sAfterAtRuleBlock = '';
46
fbe18cc0
FM
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 = ' ';
376eb156
MM
55
56 // Content injected in and around declaration blocks.
57 public $sBeforeDeclarationBlock = '';
58 public $sAfterDeclarationBlockSelectors = '';
59 public $sAfterDeclarationBlock = '';
60
fbe18cc0
FM
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 }
376eb156
MM
158
159 /**
160 * Create format.
161 *
162 * @return OutputFormat Format.
163 */
fbe18cc0
FM
164 public static function create() {
165 return new OutputFormat();
166 }
376eb156
MM
167
168 /**
169 * Create compact format.
170 *
171 * @return OutputFormat Format.
172 */
fbe18cc0 173 public static function createCompact() {
376eb156
MM
174 $format = self::create();
175 $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('')->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator('');
176 return $format;
fbe18cc0 177 }
376eb156
MM
178
179 /**
180 * Create pretty format.
181 *
182 * @return OutputFormat Format.
183 */
fbe18cc0 184 public static function createPretty() {
376eb156
MM
185 $format = self::create();
186 $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n")->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
187 return $format;
fbe18cc0
FM
188 }
189}
190
191class 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 }
242
243 public function spaceAfterSelectorSeparator() {
244 return $this->space('AfterSelectorSeparator');
245 }
246
247 public function spaceBeforeListArgumentSeparator($sSeparator) {
248 return $this->space('BeforeListArgumentSeparator', $sSeparator);
249 }
250
251 public function spaceAfterListArgumentSeparator($sSeparator) {
252 return $this->space('AfterListArgumentSeparator', $sSeparator);
253 }
254
255 public function spaceBeforeOpeningBrace() {
256 return $this->space('BeforeOpeningBrace');
257 }
258
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 }
275
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 }
314
315 private function prepareSpace($sSpaceString) {
316 return str_replace("\n", "\n".$this->indent(), $sSpaceString);
317 }
318
319 private function indent() {
320 return str_repeat($this->oFormat->sIndentation, $this->oFormat->level());
321 }
376eb156 322}