<?php
namespace Sabberworm\CSS;
< use Sabberworm\CSS\Parsing\OutputException;
<
/**
* Class OutputFormat
*
< * @method OutputFormat setSemicolonAfterLastRule( bool $bSemicolonAfterLastRule ) Set whether semicolons are added after last rule.
> * @method OutputFormat setSemicolonAfterLastRule(bool $bSemicolonAfterLastRule) Set whether semicolons are added after
> * last rule.
*/
< class OutputFormat {
> class OutputFormat
> {
/**
< * Value format
> * Value format: `"` means double-quote, `'` means single-quote
> *
> * @var string
*/
< // " means double-quote, ' means single-quote
public $sStringQuotingType = '"';
< // Output RGB colors in hash notation if possible
>
> /**
> * Output RGB colors in hash notation if possible
> *
> * @var string
> */
public $bRGBHashNotation = true;
/**
* Declaration format
> *
*/
> * Semicolon after the last rule of a declaration block can be omitted. To do that, set this false.
// Semicolon after the last rule of a declaration block can be omitted. To do that, set this false.
> *
public $bSemicolonAfterLastRule = true;
> * @var bool
< // Semicolon after the last rule of a declaration block can be omitted. To do that, set this false.
/**
* Spacing
* Note that these strings are not sanity-checked: the value should only consist of whitespace
* Any newline character will be indented according to the current level.
* The triples (After, Before, Between) can be set using a wildcard (e.g. `$oFormat->set('Space*Rules', "\n");`)
*/
public $sSpaceAfterRuleName = ' ';
> /**
public $sSpaceBeforeRules = '';
> * @var string
public $sSpaceAfterRules = '';
> */
public $sSpaceBetweenRules = '';
>
> /**
public $sSpaceBeforeBlocks = '';
> * @var string
public $sSpaceAfterBlocks = '';
> */
public $sSpaceBetweenBlocks = "\n";
>
> /**
// Content injected in and around @-rule blocks.
> * @var string
public $sBeforeAtRuleBlock = '';
> */
public $sAfterAtRuleBlock = '';
> /**
> * @var string
// This is what’s printed before and after the comma if a declaration block contains multiple selectors.
> */
public $sSpaceBeforeSelectorSeparator = '';
>
public $sSpaceAfterSelectorSeparator = ' ';
> /**
// This is what’s printed after the comma of value lists
> * @var string
public $sSpaceBeforeListArgumentSeparator = '';
> */
public $sSpaceAfterListArgumentSeparator = '';
>
> /**
public $sSpaceBeforeOpeningBrace = ' ';
> * @var string
> */
< // Content injected in and around @-rule blocks.
> /**
> * Content injected in and around at-rule blocks.
> *
> * @var string
> */
public $sBeforeDeclarationBlock = '';
>
public $sAfterDeclarationBlockSelectors = '';
> /**
public $sAfterDeclarationBlock = '';
> * @var string
> */
< // This is what’s printed before and after the comma if a declaration block contains multiple selectors.
> /**
> * This is what’s printed before and after the comma if a declaration block contains multiple selectors.
> *
> * @var string
> */
* Indentation
>
*/
> /**
// Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings.
> * @var string
public $sIndentation = "\t";
> */
< // This is what’s printed after the comma of value lists
>
> /**
> * This is what’s printed after the comma of value lists
> *
> * @var string
> */
/**
>
* Output exceptions.
> /**
*/
> * @var string
public $bIgnoreExceptions = false;
> */
> /**
> * @var string
private $oFormatter = null;
> */
< // Content injected in and around declaration blocks.
> /**
> * Content injected in and around declaration blocks.
> *
> * @var string
> */
private $iIndentationLevel = 0;
>
> /**
public function __construct() {
> * @var string
}
> */
>
public function get($sName) {
> /**
$aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i');
> * @var string
foreach($aVarPrefixes as $sPrefix) {
> */
< * Indentation
> * Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings.
> *
> * @var string
< // Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings.
return $this->$sFieldName;
> *
}
> * @var bool
<
> /**
> * @var OutputFormatter|null
> */
return null;
>
}
> /**
> * @var OutputFormat|null
public function set($aNames, $mValue) {
> */
$aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i');
>
if(is_string($aNames) && strpos($aNames, '*') !== false) {
> /**
$aNames = array(str_replace('*', 'Before', $aNames), str_replace('*', 'Between', $aNames), str_replace('*', 'After', $aNames));
> * @var int
} else if(!is_array($aNames)) {
> */
< public function __construct() {
> public function __construct()
> {
< public function get($sName) {
< $aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i');
> /**
> * @param string $sName
> *
> * @return string|null
> */
> public function get($sName)
> {
> $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i'];
< public function set($aNames, $mValue) {
< $aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i');
> /**
> * @param array<array-key, string>|string $aNames
> * @param mixed $mValue
> *
> * @return self|false
> */
> public function set($aNames, $mValue)
> {
> $aVarPrefixes = ['a', 's', 'm', 'b', 'f', 'o', 'c', 'i'];
< $aNames = array(str_replace('*', 'Before', $aNames), str_replace('*', 'Between', $aNames), str_replace('*', 'After', $aNames));
> $aNames =
> [
> str_replace('*', 'Before', $aNames),
> str_replace('*', 'Between', $aNames),
> str_replace('*', 'After', $aNames),
> ];
< $aNames = array($aNames);
> $aNames = [$aNames];
$this->$sFieldName = $mValue;
$bDidReplace = true;
}
}
if($bDidReplace) {
return $this;
}
}
// Break the chain so the user knows this option is invalid
return false;
}
< public function __call($sMethodName, $aArguments) {
> /**
> * @param string $sMethodName
> * @param array<array-key, mixed> $aArguments
> *
> * @return mixed
> *
> * @throws \Exception
> */
> public function __call($sMethodName, array $aArguments)
> {
if(strpos($sMethodName, 'set') === 0) {
return $this->set(substr($sMethodName, 3), $aArguments[0]);
} else if(strpos($sMethodName, 'get') === 0) {
return $this->get(substr($sMethodName, 3));
< } else if(method_exists('\\Sabberworm\\CSS\\OutputFormatter', $sMethodName)) {
< return call_user_func_array(array($this->getFormatter(), $sMethodName), $aArguments);
> } elseif (method_exists(OutputFormatter::class, $sMethodName)) {
> return call_user_func_array([$this->getFormatter(), $sMethodName], $aArguments);
} else {
throw new \Exception('Unknown OutputFormat method called: '.$sMethodName);
}
}
< public function indentWithTabs($iNumber = 1) {
> /**
> * @param int $iNumber
> *
> * @return self
> */
> public function indentWithTabs($iNumber = 1)
> {
return $this->setIndentation(str_repeat("\t", $iNumber));
}
< public function indentWithSpaces($iNumber = 2) {
> /**
> * @param int $iNumber
> *
> * @return self
> */
> public function indentWithSpaces($iNumber = 2)
> {
return $this->setIndentation(str_repeat(" ", $iNumber));
}
< public function nextLevel() {
> /**
> * @return OutputFormat
> */
> public function nextLevel()
> {
if($this->oNextLevelFormat === null) {
$this->oNextLevelFormat = clone $this;
$this->oNextLevelFormat->iIndentationLevel++;
$this->oNextLevelFormat->oFormatter = null;
}
return $this->oNextLevelFormat;
}
< public function beLenient() {
> /**
> * @return void
> */
> public function beLenient()
> {
$this->bIgnoreExceptions = true;
}
< public function getFormatter() {
> /**
> * @return OutputFormatter
> */
> public function getFormatter()
> {
if($this->oFormatter === null) {
$this->oFormatter = new OutputFormatter($this);
}
return $this->oFormatter;
}
< public function level() {
> /**
> * @return int
> */
> public function level()
> {
return $this->iIndentationLevel;
}
/**
< * Create format.
> * Creates an instance of this class without any particular formatting settings.
*
< * @return OutputFormat Format.
> * @return self
*/
< public static function create() {
> public static function create()
> {
return new OutputFormat();
}
/**
< * Create compact format.
> * Creates an instance of this class with a preset for compact formatting.
*
< * @return OutputFormat Format.
> * @return self
*/
< public static function createCompact() {
> public static function createCompact()
> {
$format = self::create();
< $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('')->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator('');
> $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('')
> ->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator('');
return $format;
}
/**
< * Create pretty format.
> * Creates an instance of this class with a preset for pretty formatting.
*
< * @return OutputFormat Format.
> * @return self
*/
< public static function createPretty() {
> public static function createPretty()
> {
$format = self::create();
< $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n")->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' '));
> $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n")
> ->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']);
return $format;
}
< }
<
< class OutputFormatter {
< private $oFormat;
<
< public function __construct(OutputFormat $oFormat) {
< $this->oFormat = $oFormat;
< }
<
< public function space($sName, $sType = null) {
< $sSpaceString = $this->oFormat->get("Space$sName");
< // If $sSpaceString is an array, we have multple values configured depending on the type of object the space applies to
< if(is_array($sSpaceString)) {
< if($sType !== null && isset($sSpaceString[$sType])) {
< $sSpaceString = $sSpaceString[$sType];
< } else {
< $sSpaceString = reset($sSpaceString);
< }
< }
< return $this->prepareSpace($sSpaceString);
< }
<
< public function spaceAfterRuleName() {
< return $this->space('AfterRuleName');
< }
<
< public function spaceBeforeRules() {
< return $this->space('BeforeRules');
< }
<
< public function spaceAfterRules() {
< return $this->space('AfterRules');
< }
<
< public function spaceBetweenRules() {
< return $this->space('BetweenRules');
< }
<
< public function spaceBeforeBlocks() {
< return $this->space('BeforeBlocks');
< }
<
< public function spaceAfterBlocks() {
< return $this->space('AfterBlocks');
< }
<
< public function spaceBetweenBlocks() {
< return $this->space('BetweenBlocks');
< }
<
< public function spaceBeforeSelectorSeparator() {
< return $this->space('BeforeSelectorSeparator');
< }
<
< public function spaceAfterSelectorSeparator() {
< return $this->space('AfterSelectorSeparator');
< }
<
< public function spaceBeforeListArgumentSeparator($sSeparator) {
< return $this->space('BeforeListArgumentSeparator', $sSeparator);
< }
<
< public function spaceAfterListArgumentSeparator($sSeparator) {
< return $this->space('AfterListArgumentSeparator', $sSeparator);
< }
<
< public function spaceBeforeOpeningBrace() {
< return $this->space('BeforeOpeningBrace');
< }
<
< /**
< * Runs the given code, either swallowing or passing exceptions, depending on the bIgnoreExceptions setting.
< */
< public function safely($cCode) {
< if($this->oFormat->get('IgnoreExceptions')) {
< // If output exceptions are ignored, run the code with exception guards
< try {
< return $cCode();
< } catch (OutputException $e) {
< return null;
< } //Do nothing
< } else {
< // Run the code as-is
< return $cCode();
< }
< }
<
< /**
< * Clone of the implode function but calls ->render with the current output format instead of __toString()
< */
< public function implode($sSeparator, $aValues, $bIncreaseLevel = false) {
< $sResult = '';
< $oFormat = $this->oFormat;
< if($bIncreaseLevel) {
< $oFormat = $oFormat->nextLevel();
< }
< $bIsFirst = true;
< foreach($aValues as $mValue) {
< if($bIsFirst) {
< $bIsFirst = false;
< } else {
< $sResult .= $sSeparator;
< }
< if($mValue instanceof \Sabberworm\CSS\Renderable) {
< $sResult .= $mValue->render($oFormat);
< } else {
< $sResult .= $mValue;
< }
< }
< return $sResult;
< }
<
< public function removeLastSemicolon($sString) {
< if($this->oFormat->get('SemicolonAfterLastRule')) {
< return $sString;
< }
< $sString = explode(';', $sString);
< if(count($sString) < 2) {
< return $sString[0];
< }
< $sLast = array_pop($sString);
< $sNextToLast = array_pop($sString);
< array_push($sString, $sNextToLast.$sLast);
< return implode(';', $sString);
< }
<
< private function prepareSpace($sSpaceString) {
< return str_replace("\n", "\n".$this->indent(), $sSpaceString);
< }
<
< private function indent() {
< return str_repeat($this->oFormat->sIndentation, $this->oFormat->level());
< }
}