PageRenderTime 241ms CodeModel.GetById 100ms app.highlight 14ms RepoModel.GetById 91ms app.codeStats 0ms

/lib/php-css-parser/OutputFormat.php

https://bitbucket.org/moodle/moodle
PHP | 322 lines | 218 code | 48 blank | 56 comment | 28 complexity | 243a592e1993c829acf4091894bb6f03 MD5 | raw file
  1<?php
  2
  3namespace Sabberworm\CSS;
  4
  5use Sabberworm\CSS\Parsing\OutputException;
  6
  7/**
  8 * Class OutputFormat
  9 *
 10 * @method OutputFormat setSemicolonAfterLastRule( bool $bSemicolonAfterLastRule ) Set whether semicolons are added after last rule.
 11 */
 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
 43	// Content injected in and around @-rule blocks.
 44	public $sBeforeAtRuleBlock = '';
 45	public $sAfterAtRuleBlock = '';
 46
 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 = ' ';
 55
 56	// Content injected in and around declaration blocks.
 57	public $sBeforeDeclarationBlock = '';
 58	public $sAfterDeclarationBlockSelectors = '';
 59	public $sAfterDeclarationBlock = '';
 60
 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	}
158
159	/**
160	 * Create format.
161	 *
162	 * @return OutputFormat Format.
163	 */
164	public static function create() {
165		return new OutputFormat();
166	}
167
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	}
178
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	}
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	}
322}