diff --git a/lib/Sabberworm/CSS/CSSList/CSSList.php b/lib/Sabberworm/CSS/CSSList/CSSList.php index bc90460b..97cd599b 100644 --- a/lib/Sabberworm/CSS/CSSList/CSSList.php +++ b/lib/Sabberworm/CSS/CSSList/CSSList.php @@ -2,6 +2,7 @@ namespace Sabberworm\CSS\CSSList; +use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\RuleSet\RuleSet; @@ -90,24 +91,29 @@ public function removeDeclarationBlockBySelector($mSelector, $bRemoveAll = false } public function __toString() { - return $this->render(new \Sabberworm\CSS\OutputFormat()); + return $this->render(new OutputFormat()); } - public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { + /** + * {@inheritdoc} + */ + public function render(OutputFormat $oOutputFormat) { $sResult = ''; $bIsFirst = true; $oNextLevel = $oOutputFormat; - if(!$this->isRootList()) { + if (!$this->isRootList()) { $oNextLevel = $oOutputFormat->nextLevel(); } foreach ($this->aContents as $oContent) { - $sRendered = $oOutputFormat->safely(function() use ($oNextLevel, $oContent) { - return $oContent->render($oNextLevel); - }); - if($sRendered === null) { + $sRendered = $oOutputFormat->safely( + function () use ($oNextLevel, $oContent) { + return $oContent->render($oNextLevel); + } + ); + if ($sRendered === null) { continue; } - if($bIsFirst) { + if ($bIsFirst) { $bIsFirst = false; $sResult .= $oNextLevel->spaceBeforeBlocks(); } else { @@ -116,14 +122,14 @@ public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { $sResult .= $sRendered; } - if(!$bIsFirst) { + if (!$bIsFirst) { // Had some output $sResult .= $oOutputFormat->spaceAfterBlocks(); } return $sResult; } - + /** * Return true if the list can not be further outdented. Only important when rendering. */ diff --git a/lib/Sabberworm/CSS/CSSList/Document.php b/lib/Sabberworm/CSS/CSSList/Document.php index bd4a23ee..30b29467 100644 --- a/lib/Sabberworm/CSS/CSSList/Document.php +++ b/lib/Sabberworm/CSS/CSSList/Document.php @@ -2,6 +2,8 @@ namespace Sabberworm\CSS\CSSList; +use Sabberworm\CSS\OutputFormat; + /** * The root CSSList of a parsed file. Contains all top-level css contents, mostly declaration blocks, but also any @-rules encountered. */ @@ -90,10 +92,16 @@ public function createShorthands() { } } - // Override render() to make format argument optional - public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat = null) { + /** + * {@inheritdoc} + * + * @param OutputFormat|null $oOutputFormat + * + * @return string + */ + public function render(OutputFormat $oOutputFormat = null) { if($oOutputFormat === null) { - $oOutputFormat = new \Sabberworm\CSS\OutputFormat(); + $oOutputFormat = new OutputFormat(); } return parent::render($oOutputFormat); } @@ -102,4 +110,4 @@ public function isRootList() { return true; } -} \ No newline at end of file +} diff --git a/lib/Sabberworm/CSS/Comment/Commentable.php b/lib/Sabberworm/CSS/Comment/Commentable.php index 3100f17a..68540f02 100644 --- a/lib/Sabberworm/CSS/Comment/Commentable.php +++ b/lib/Sabberworm/CSS/Comment/Commentable.php @@ -2,22 +2,20 @@ namespace Sabberworm\CSS\Comment; -interface Commentable { - +interface Commentable +{ /** - * @param array $aComments Array of comments. + * @param Comment[] $aComments Array of comments. */ public function addComments(array $aComments); /** - * @return array + * @return Comment[] */ public function getComments(); /** - * @param array $aComments Array containing Comment objects. + * @param Comment[] $aComments Array containing Comment objects. */ public function setComments(array $aComments); - - } diff --git a/lib/Sabberworm/CSS/OutputFormat.php b/lib/Sabberworm/CSS/OutputFormat.php index 1b179840..ab4753ca 100644 --- a/lib/Sabberworm/CSS/OutputFormat.php +++ b/lib/Sabberworm/CSS/OutputFormat.php @@ -2,29 +2,39 @@ namespace Sabberworm\CSS; -use Sabberworm\CSS\Parsing\OutputException; - +/** + * Output format configuration + */ class OutputFormat { + /** - * Value format - */ - // " means double-quote, ' means single-quote + * String quoting type. + * " means double-quote, ' means single-quote + * @var string + */ public $sStringQuotingType = '"'; - // Output RGB colors in hash notation if possible + + /** + * Output RGB colors in hash notation if possible + * @var bool + */ public $bRGBHashNotation = true; - + + // Declaration format + /** - * 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. + * @var bool + */ public $bSemicolonAfterLastRule = true; - + /** - * 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");`) - */ + * 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 = ''; @@ -35,34 +45,58 @@ class OutputFormat { public $sSpaceAfterBlocks = ''; public $sSpaceBetweenBlocks = "\n"; - // This is what’s printed before and after the comma if a declaration block contains multiple selectors. + /** + * Printed before and after the comma if a declaration block contains multiple selectors. + * @var string + */ public $sSpaceBeforeSelectorSeparator = ''; + /** + * Printed after the comma if a declaration block contains multiple selectors. + * @var string + */ public $sSpaceAfterSelectorSeparator = ' '; - // This is what’s printed after the comma of value lists + + /** + * Printed before the comma of value lists. + * @var string + */ public $sSpaceBeforeListArgumentSeparator = ''; + + /** + * Printed after the comma of value lists. + * @var string + */ public $sSpaceAfterListArgumentSeparator = ''; - + public $sSpaceBeforeOpeningBrace = ' '; - + /** - * Indentation - */ - // Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings. + * Indentation character(s) per level. Only applicable if newlines are used in any of the spacing settings. + * @var string + */ public $sIndentation = "\t"; - + /** - * Output exceptions. - */ + * Indicates if comments should be kept or thrown away + * @var bool + */ + private $bKeepComments = false; + + /** + * Output exceptions. + * @var bool + */ public $bIgnoreExceptions = false; - - + private $oFormatter = null; private $oNextLevelFormat = null; private $iIndentationLevel = 0; - - public function __construct() { - } - + + /** + * @param $sName + * + * @return mixed|null + */ public function get($sName) { $aVarPrefixes = array('a', 's', 'm', 'b', 'f', 'o', 'c', 'i'); foreach($aVarPrefixes as $sPrefix) { @@ -73,217 +107,182 @@ public function get($sName) { } return null; } - - public function set($aNames, $mValue) { + + /** + * @param $aNames + * @param $mValue + * + * @return $this|false + */ + 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)); - } else if(!is_array($aNames)) { + if (is_string($aNames) && strpos($aNames, '*') !== false) { + $aNames = array( + str_replace('*', 'Before', $aNames), + str_replace('*', 'Between', $aNames), + str_replace('*', 'After', $aNames), + ); + } elseif (!is_array($aNames)) { $aNames = array($aNames); } - foreach($aVarPrefixes as $sPrefix) { + foreach ($aVarPrefixes as $sPrefix) { $bDidReplace = false; - foreach($aNames as $sName) { + foreach ($aNames as $sName) { $sFieldName = $sPrefix.ucfirst($sName); - if(isset($this->$sFieldName)) { + if (isset($this->$sFieldName)) { $this->$sFieldName = $mValue; $bDidReplace = true; } } - if($bDidReplace) { + if ($bDidReplace) { return $this; } } // Break the chain so the user knows this option is invalid return false; } - - public function __call($sMethodName, $aArguments) { - if(strpos($sMethodName, 'set') === 0) { + + /** + * @param string $sMethodName + * @param array $aArguments + * + * @return false|mixed|null|OutputFormat + * @throws \Exception + */ + public function __call($sMethodName, $aArguments) + { + if (strpos($sMethodName, 'set') === 0) { return $this->set(substr($sMethodName, 3), $aArguments[0]); - } else if(strpos($sMethodName, 'get') === 0) { + } elseif (strpos($sMethodName, 'get') === 0) { return $this->get(substr($sMethodName, 3)); - } else if(method_exists('\\Sabberworm\\CSS\\OutputFormatter', $sMethodName)) { + } elseif (method_exists('\\Sabberworm\\CSS\\OutputFormatter', $sMethodName)) { return call_user_func_array(array($this->getFormatter(), $sMethodName), $aArguments); } else { throw new \Exception('Unknown OutputFormat method called: '.$sMethodName); } } - - public function indentWithTabs($iNumber = 1) { + + /** + * Sets indentation to a number of tabs + * + * @param int $iNumber [default=1] Number of tabs to indent with + * + * @return $this|false + */ + public function indentWithTabs($iNumber = 1) + { return $this->setIndentation(str_repeat("\t", $iNumber)); } - - public function indentWithSpaces($iNumber = 2) { + + /** + * Sets indentation to a number of spaces + * @param int $iNumber [default=2] Number of spaces to indent with + * + * @return $this|false + */ + public function indentWithSpaces($iNumber = 2) + { return $this->setIndentation(str_repeat(" ", $iNumber)); } - - public function nextLevel() { - if($this->oNextLevelFormat === null) { + + /** + * @return null|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() { + + /** + * Activates exception ignoring + */ + public function beLenient() + { $this->bIgnoreExceptions = true; } - - public function getFormatter() { - if($this->oFormatter === null) { + + /** + * @return null|OutputFormatter + */ + public function getFormatter() + { + if ($this->oFormatter === null) { $this->oFormatter = new OutputFormatter($this); } return $this->oFormatter; } - - public function level() { - return $this->iIndentationLevel; - } - - public static function create() { - return new OutputFormat(); - } - - public static function createCompact() { - return self::create()->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('')->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator(''); - } - - public static function createPretty() { - return self::create()->set('Space*Rules', "\n")->set('Space*Blocks', "\n")->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', array('default' => '', ',' => ' ')); - } -} - -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); + /** + * @return int + */ + public function level() + { + return $this->iIndentationLevel; } - public function spaceBeforeOpeningBrace() { - return $this->space('BeforeOpeningBrace'); + /** + * Indicates if comments should be kept or thrown away + * @param bool $toggle + * @return $this + */ + public function setKeepComments($toggle) + { + $this->bKeepComments = $toggle; + return $this; } /** - * 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(); - } + * Indicates if comments should be kept or thrown away + * @return bool + */ + public function getKeepComments() + { + return $this->bKeepComments; } /** - * 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); + * @return OutputFormat + */ + public static function create() + { + return new OutputFormat(); } - private function prepareSpace($sSpaceString) { - return str_replace("\n", "\n".$this->indent(), $sSpaceString); + /** + * @return OutputFormat|false + */ + public static function createCompact() + { + return self::create() + ->set('Space*Rules', "") + ->set('Space*Blocks', "") + ->setSpaceAfterRuleName('') + ->setSpaceBeforeOpeningBrace('') + ->setSpaceAfterSelectorSeparator(''); } - private function indent() { - return str_repeat($this->oFormat->sIndentation, $this->oFormat->level()); + /** + * @return OutputFormat|false + */ + public static function createPretty() + { + return self::create() + ->setKeepComments(true) + ->set('Space*Rules', "\n") + ->set('Space*Blocks', "\n") + ->setSpaceBetweenBlocks("\n\n") + ->set( + 'SpaceAfterListArgumentSeparator', + array( + 'default' => '', + ',' => ' ' + ) + ); } -} \ No newline at end of file +} diff --git a/lib/Sabberworm/CSS/OutputFormatter.php b/lib/Sabberworm/CSS/OutputFormatter.php new file mode 100644 index 00000000..fde6923e --- /dev/null +++ b/lib/Sabberworm/CSS/OutputFormatter.php @@ -0,0 +1,236 @@ +oFormat = $oFormat; + } + + /** + * @param $sName + * @param string|null $sType + * + * @return string + */ + public function space($sName, $sType = null) + { + $sSpaceString = $this->oFormat->get("Space$sName"); + // If $sSpaceString is an array, we have multiple 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); + } + + /** + * @return mixed + */ + public function spaceAfterRuleName() + { + return $this->space('AfterRuleName'); + } + + /** + * @return string + */ + public function spaceBeforeRules() + { + return $this->space('BeforeRules'); + } + + /** + * @return string + */ + public function spaceAfterRules() + { + return $this->space('AfterRules'); + } + + /** + * @return string + */ + public function spaceBetweenRules() + { + return $this->space('BetweenRules'); + } + + /** + * @return string + */ + public function spaceBeforeBlocks() + { + return $this->space('BeforeBlocks'); + } + + /** + * @return string + */ + public function spaceAfterBlocks() + { + return $this->space('AfterBlocks'); + } + + /** + * @return string + */ + public function spaceBetweenBlocks() + { + return $this->space('BetweenBlocks'); + } + + /** + * @return string + */ + public function spaceBeforeSelectorSeparator() + { + return $this->space('BeforeSelectorSeparator'); + } + + /** + * @return string + */ + public function spaceAfterSelectorSeparator() + { + return $this->space('AfterSelectorSeparator'); + } + + /** + * @param string $sSeparator + * + * @return string + */ + public function spaceBeforeListArgumentSeparator($sSeparator) + { + return $this->space('BeforeListArgumentSeparator', $sSeparator); + } + + /** + * @param string $sSeparator + * + * @return string + */ + public function spaceAfterListArgumentSeparator($sSeparator) + { + return $this->space('AfterListArgumentSeparator', $sSeparator); + } + + /** + * @return string + */ + public function spaceBeforeOpeningBrace() + { + return $this->space('BeforeOpeningBrace'); + } + + /** + * Calls the given function, either swallowing or passing exceptions, depending on the bIgnoreExceptions setting. + * + * @param string $cCode Function name + * + * @return mixed + * + * @throws OutputException + */ + 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() + * + * @param string $sSeparator + * @param string[] $aValues + * @param bool $bIncreaseLevel + * + * @return string + */ + 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; + } + + /** + * @param $sString + * + * @return array|string + */ + 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); + } + + /** + * @param $sSpaceString + * + * @return string + */ + private function prepareSpace($sSpaceString) + { + return str_replace("\n", "\n".$this->indent(), $sSpaceString); + } + + /** + * Returns the indentation character, repeated as many times as the current level + * @return string + */ + private function indent() + { + return str_repeat($this->oFormat->sIndentation, $this->oFormat->level()); + } +} diff --git a/lib/Sabberworm/CSS/Renderable.php b/lib/Sabberworm/CSS/Renderable.php index 3ac06652..99c829bb 100644 --- a/lib/Sabberworm/CSS/Renderable.php +++ b/lib/Sabberworm/CSS/Renderable.php @@ -2,8 +2,26 @@ namespace Sabberworm\CSS; +use Sabberworm\CSS\OutputFormat; + interface Renderable { + + /** + * @return string + */ public function __toString(); - public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat); + + /** + * Renders this component + * + * @param OutputFormat $oOutputFormat Formatting options + * + * @return string Rendered CSS + */ + public function render(OutputFormat $oOutputFormat); + + /** + * @return int Line number + */ public function getLineNo(); -} \ No newline at end of file +} diff --git a/lib/Sabberworm/CSS/Rule/Rule.php b/lib/Sabberworm/CSS/Rule/Rule.php index 3e485375..f0b2e82e 100644 --- a/lib/Sabberworm/CSS/Rule/Rule.php +++ b/lib/Sabberworm/CSS/Rule/Rule.php @@ -2,6 +2,8 @@ namespace Sabberworm\CSS\Rule; +use Sabberworm\CSS\Comment\Comment; +use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Value\RuleValueList; use Sabberworm\CSS\Value\Value; @@ -152,11 +154,17 @@ public function getIsImportant() { return $this->bIsImportant; } + /** + * {@inheritdoc} + */ public function __toString() { - return $this->render(new \Sabberworm\CSS\OutputFormat()); + return $this->render(new OutputFormat()); } - public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { + /** + * {@inheritdoc} + */ + public function render(OutputFormat $oOutputFormat) { $sResult = "{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}"; if ($this->mValue instanceof Value) { //Can also be a ValueList $sResult .= $this->mValue->render($oOutputFormat); @@ -174,21 +182,21 @@ public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { } /** - * @param array $aComments Array of comments. + * {@inheritdoc} */ public function addComments(array $aComments) { $this->aComments = array_merge($this->aComments, $aComments); } /** - * @return array + * {@inheritdoc} */ public function getComments() { return $this->aComments; } /** - * @param array $aComments Array containing Comment objects. + * {@inheritdoc} */ public function setComments(array $aComments) { $this->aComments = $aComments; diff --git a/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php b/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php index e18f5d82..21e54a88 100644 --- a/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php +++ b/lib/Sabberworm/CSS/RuleSet/DeclarationBlock.php @@ -2,6 +2,8 @@ namespace Sabberworm\CSS\RuleSet; +use Sabberworm\CSS\Comment\Comment; +use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Property\Selector; use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\Value\RuleValueList; @@ -87,7 +89,7 @@ public function expandShorthands() { public function createShorthands() { $this->createBackgroundShorthand(); $this->createDimensionsShorthand(); - // border must be shortened after dimensions + // border must be shortened after dimensions $this->createBorderShorthand(); $this->createFontShorthand(); $this->createListStyleShorthand(); @@ -446,7 +448,7 @@ public function createBorderShorthand() { /* * Looks for long format CSS dimensional properties - * (margin, padding, border-color, border-style and border-width) + * (margin, padding, border-color, border-style and border-width) * and converts them into shorthand CSS properties. * */ @@ -501,7 +503,7 @@ public function createDimensionsShorthand() { $oNewRule->addValue($aValues['bottom']); } } else { - // No sides are equal + // No sides are equal $oNewRule->addValue($aValues['top']); $oNewRule->addValue($aValues['left']); $oNewRule->addValue($aValues['bottom']); @@ -516,8 +518,8 @@ public function createDimensionsShorthand() { } /** - * Looks for long format CSS font properties (e.g. font-weight) and - * tries to convert them into a shorthand CSS font property. + * Looks for long format CSS font properties (e.g. font-weight) and + * tries to convert them into a shorthand CSS font property. * At least font-size AND font-family must be present in order to create a shorthand declaration. * */ public function createFontShorthand() { @@ -591,15 +593,40 @@ public function createFontShorthand() { } public function __toString() { - return $this->render(new \Sabberworm\CSS\OutputFormat()); + return $this->render(new OutputFormat()); } - public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { + /** + * {@inheritdoc} + */ + public function render(OutputFormat $oOutputFormat) { if(count($this->aSelectors) === 0) { // If all the selectors have been removed, this declaration block becomes invalid throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo); } - $sResult = $oOutputFormat->implode($oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(), $this->aSelectors) . $oOutputFormat->spaceBeforeOpeningBrace() . '{'; + + $sResult = ''; + + // render comments + if ($oOutputFormat->getKeepComments()) { + $comments = $this->getCommentsBefore(); + if (!empty($comments)) { + $sResult .= implode( + '', + array_map( + function (Comment $comment) use ($oOutputFormat) { + return '/*' . $comment->getComment() . "*/\n"; + }, + $comments + ) + ); + } + } + + $sResult .= $oOutputFormat->implode( + $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(), + $this->aSelectors + ) . $oOutputFormat->spaceBeforeOpeningBrace() . '{'; $sResult .= parent::render($oOutputFormat); $sResult .= '}'; return $sResult; diff --git a/lib/Sabberworm/CSS/RuleSet/RuleSet.php b/lib/Sabberworm/CSS/RuleSet/RuleSet.php index 124be88d..b141c3b2 100644 --- a/lib/Sabberworm/CSS/RuleSet/RuleSet.php +++ b/lib/Sabberworm/CSS/RuleSet/RuleSet.php @@ -2,6 +2,7 @@ namespace Sabberworm\CSS\RuleSet; +use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\Rule\Rule; use Sabberworm\CSS\Renderable; use Sabberworm\CSS\Comment\Commentable; @@ -49,9 +50,17 @@ public function addRule(Rule $oRule, Rule $oSibling = null) { /** * Returns all rules matching the given rule name - * @param (null|string|Rule) $mRule pattern to search for. If null, returns all rules. if the pattern ends with a dash, all rules starting with the pattern are returned as well as one matching the pattern with the dash excluded. passing a Rule behaves like calling getRules($mRule->getRule()). - * @example $oRuleSet->getRules('font-') //returns an array of all rules either beginning with font- or matching font. + * + * @param (null|string|Rule) $mRule pattern to search for. + * If null, returns all rules. if the pattern ends with a dash, all rules starting with the pattern are + * returned as well as one matching the pattern with the dash excluded. + * Passing a Rule behaves like calling getRules($mRule->getRule()). + * + * @example $oRuleSet->getRules('font-') //returns an array of all rules either beginning with font- or matching + * font. * @example $oRuleSet->getRules('font') //returns array(0 => $oRule, …) or array(). + * + * @return array */ public function getRules($mRule = null) { if ($mRule instanceof Rule) { @@ -79,9 +88,19 @@ public function setRules(array $aRules) { } /** - * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name as keys. This method exists mainly for backwards-compatibility and is really only partially useful. - * @param (string) $mRule pattern to search for. If null, returns all rules. if the pattern ends with a dash, all rules starting with the pattern are returned as well as one matching the pattern with the dash excluded. passing a Rule behaves like calling getRules($mRule->getRule()). - * Note: This method loses some information: Calling this (with an argument of 'background-') on a declaration block like { background-color: green; background-color; rgba(0, 127, 0, 0.7); } will only yield an associative array containing the rgba-valued rule while @link{getRules()} would yield an indexed array containing both. + * Returns all rules matching the given pattern and returns them in an associative array with the rule’s name as + * keys. This method exists mainly for backwards-compatibility and is really only partially useful. + * + * @param (string) $mRule pattern to search for. + * If null, returns all rules. if the pattern ends with a dash, all rules starting with the pattern are + * returned as well as one matching the pattern with the dash excluded. + * Passing a Rule behaves like calling getRules($mRule->getRule()). + * Note: This method loses some information: Calling this (with an argument of 'background-') + * on a declaration block like { background-color: green; background-color; rgba(0, 127, 0, 0.7); } + * will only yield an associative array containing the rgba-valued rule while @link{getRules()} + * would yield an indexed array containing both. + * + * @return array */ public function getRulesAssoc($mRule = null) { $aResult = array(); @@ -92,9 +111,19 @@ public function getRulesAssoc($mRule = null) { } /** - * Remove a rule from this RuleSet. This accepts all the possible values that @link{getRules()} accepts. If given a Rule, it will only remove this particular rule (by identity). If given a name, it will remove all rules by that name. Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would remove all rules with the same name. To get the old behvaiour, use removeRule($oRule->getRule()). - * @param (null|string|Rule) $mRule pattern to remove. If $mRule is null, all rules are removed. If the pattern ends in a dash, all rules starting with the pattern are removed as well as one matching the pattern with the dash excluded. Passing a Rule behaves matches by identity. - */ + * Remove a rule from this RuleSet. + * + * This accepts all the possible values that @link{getRules()} accepts. + * If given a Rule, it will only remove this particular rule (by identity). + * If given a name, it will remove all rules by that name. + * Note: this is different from pre-v.2.0 behaviour of PHP-CSS-Parser, where passing a Rule instance would + * remove all rules with the same name. To get the old behvaiour, use removeRule($oRule->getRule()). + * + * @param (null|string|Rule) $mRule pattern to remove. + * If $mRule is null, all rules are removed. If the pattern ends in a dash, all rules starting with the pattern + * are removed as well as one matching the pattern with the dash excluded. + * Passing a Rule behaves matches by identity. + */ public function removeRule($mRule) { if($mRule instanceof Rule) { $sRule = $mRule->getRule(); @@ -140,7 +169,7 @@ public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { $sResult .= $sRendered; } } - + if(!$bIsFirst) { // Had some output $sResult .= $oOutputFormat->spaceAfterRules(); @@ -170,4 +199,21 @@ public function setComments(array $aComments) { $this->aComments = $aComments; } + /** + * Returns all comments that were declared before this Rule + * @return Comment[] + */ + public function getCommentsBefore() + { + $lineNo = $this->getLineNo(); + $return = array(); + /** @var Comment $comment */ + foreach ($this->aComments as $comment) { + if ($comment->getLineNo() <= $lineNo) { + $return[] = $comment; + } + } + + return $return; + } } diff --git a/tests/Sabberworm/CSS/OutputFormatTest.php b/tests/Sabberworm/CSS/OutputFormatTest.php index 238b5ba5..267124e9 100644 --- a/tests/Sabberworm/CSS/OutputFormatTest.php +++ b/tests/Sabberworm/CSS/OutputFormatTest.php @@ -2,13 +2,22 @@ namespace Sabberworm\CSS; -use Sabberworm\CSS\Parser; -use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\CSSList\Document; -global $TEST_CSS; +class OutputFormatTest extends \PHPUnit_Framework_TestCase +{ + private $oParser; + + /** + * @var Document + */ + private $oDocument; -$TEST_CSS = <<oParser = new Parser($TEST_CSS); + protected function setUp() + { + $this->oParser = new Parser(self::$testCSS); $this->oDocument = $this->oParser->parse(); } - public function testPlain() { - $this->assertSame('.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render()); + public function testPlain() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render() + ); } - public function testCompact() { - $this->assertSame('.main,.test{font:italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background:white;}@media screen{.main{background-size:100% 100%;font-size:1.3em;background-color:#fff;}}', $this->oDocument->render(OutputFormat::createCompact())); + public function testCompact() + { + $this->assertSame( + '.main,.test{font:italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background:white;}@media screen{.main{background-size:100% 100%;font-size:1.3em;background-color:#fff;}}', + $this->oDocument->render(OutputFormat::createCompact()) + ); } - public function testPretty() { - global $TEST_CSS; - $this->assertSame($TEST_CSS, $this->oDocument->render(OutputFormat::createPretty())); + public function testCompactWithComments() + { + $expected = <<assertSame( + $expected, + $this->oDocument->render( + OutputFormat::createCompact() + ->setKeepComments(true) + ) + ); } - - public function testSpaceAfterListArgumentSeparator() { - $this->assertSame('.main, .test {font: italic normal bold 16px/ 1.2 "Helvetica", Verdana, sans-serif;background: white;} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setSpaceAfterListArgumentSeparator(" "))); + + public function testPretty() + { + $this->assertSame( + self::$testCSS, + $this->oDocument->render(OutputFormat::createPretty()) + ); + } + + public function testSpaceAfterListArgumentSeparator() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/ 1.2 "Helvetica", Verdana, sans-serif;background: white;} +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render(OutputFormat::create()->setSpaceAfterListArgumentSeparator(" ")) + ); } - public function testSpaceAfterListArgumentSeparatorComplex() { - $this->assertSame('.main, .test {font: italic normal bold 16px/1.2 "Helvetica", Verdana, sans-serif;background: white;} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setSpaceAfterListArgumentSeparator(array('default' => ' ', ',' => "\t", '/' => '', ' ' => '')))); + public function testSpaceAfterListArgumentSeparatorComplex() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/1.2 "Helvetica", Verdana, sans-serif;background: white;} +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render( + OutputFormat::create()->setSpaceAfterListArgumentSeparator( + array('default' => ' ', ',' => "\t", '/' => '', ' ' => '') + ) + ) + ); } - public function testSpaceAfterSelectorSeparator() { - $this->assertSame('.main, + public function testSpaceAfterSelectorSeparator() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setSpaceAfterSelectorSeparator("\n"))); +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render(OutputFormat::create()->setSpaceAfterSelectorSeparator("\n")) + ); } - public function testStringQuotingType() { - $this->assertSame('.main, .test {font: italic normal bold 16px/1.2 \'Helvetica\',Verdana,sans-serif;background: white;} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setStringQuotingType("'"))); + public function testStringQuotingType() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/1.2 \'Helvetica\',Verdana,sans-serif;background: white;} +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render(OutputFormat::create()->setStringQuotingType("'")) + ); } - public function testRGBHashNotation() { - $this->assertSame('.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: rgb(255,255,255);}}', $this->oDocument->render(OutputFormat::create()->setRGBHashNotation(false))); + public function testRGBHashNotation() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: rgb(255,255,255);}}', + $this->oDocument->render(OutputFormat::create()->setRGBHashNotation(false)) + ); } - public function testSemicolonAfterLastRule() { - $this->assertSame('.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff}}', $this->oDocument->render(OutputFormat::create()->setSemicolonAfterLastRule(false))); + public function testSemicolonAfterLastRule() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white} +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff}}', + $this->oDocument->render(OutputFormat::create()->setSemicolonAfterLastRule(false)) + ); } - public function testSpaceAfterRuleName() { - $this->assertSame('.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setSpaceAfterRuleName("\t"))); + public function testSpaceAfterRuleName() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render(OutputFormat::create()->setSpaceAfterRuleName("\t")) + ); } - public function testSpaceRules() { - $this->assertSame('.main, .test { + public function testSpaceRules() + { + $this->assertSame( + '.main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; background: white; } @@ -93,20 +160,28 @@ public function testSpaceRules() { background-size: 100% 100%; font-size: 1.3em; background-color: #fff; - }}', $this->oDocument->render(OutputFormat::create()->set('Space*Rules', "\n"))); + }}', + $this->oDocument->render(OutputFormat::create()->set('Space*Rules', "\n")) + ); } - public function testSpaceBlocks() { - $this->assertSame(' + public function testSpaceBlocks() + { + $this->assertSame( + ' .main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} @media screen { .main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;} } -', $this->oDocument->render(OutputFormat::create()->set('Space*Blocks', "\n"))); +', + $this->oDocument->render(OutputFormat::create()->set('Space*Blocks', "\n")) + ); } - public function testSpaceBoth() { - $this->assertSame(' + public function testSpaceBoth() + { + $this->assertSame( + ' .main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; background: white; @@ -118,15 +193,23 @@ public function testSpaceBoth() { background-color: #fff; } } -', $this->oDocument->render(OutputFormat::create()->set('Space*Rules', "\n")->set('Space*Blocks', "\n"))); +', + $this->oDocument->render(OutputFormat::create()->set('Space*Rules', "\n")->set('Space*Blocks', "\n")) + ); } - public function testSpaceBetweenBlocks() { - $this->assertSame('.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;}@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setSpaceBetweenBlocks(''))); + public function testSpaceBetweenBlocks() + { + $this->assertSame( + '.main, .test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;}@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render(OutputFormat::create()->setSpaceBetweenBlocks('')) + ); } - public function testIndentation() { - $this->assertSame(' + public function testIndentation() + { + $this->assertSame( + ' .main, .test { font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif; background: white; @@ -138,33 +221,49 @@ public function testIndentation() { background-color: #fff; } } -', $this->oDocument->render(OutputFormat::create()->set('Space*Rules', "\n")->set('Space*Blocks', "\n")->setIndentation(''))); +', + $this->oDocument->render( + OutputFormat::create()->set('Space*Rules', "\n")->set('Space*Blocks', "\n")->setIndentation('') + ) + ); } - - public function testSpaceBeforeBraces() { - $this->assertSame('.main, .test{font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} -@media screen{.main{background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setSpaceBeforeOpeningBrace(''))); + + public function testSpaceBeforeBraces() + { + $this->assertSame( + '.main, .test{font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} +@media screen{.main{background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render(OutputFormat::create()->setSpaceBeforeOpeningBrace('')) + ); } - + /** - * @expectedException Sabberworm\CSS\Parsing\OutputException - */ - public function testIgnoreExceptionsOff() { + * @expectedException Sabberworm\CSS\Parsing\OutputException + */ + public function testIgnoreExceptionsOff() + { $aBlocks = $this->oDocument->getAllDeclarationBlocks(); $oFirstBlock = $aBlocks[0]; $oFirstBlock->removeSelector('.main'); - $this->assertSame('.test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} -@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setIgnoreExceptions(false))); + $this->assertSame( + '.test {font: italic normal bold 16px/1.2 "Helvetica",Verdana,sans-serif;background: white;} +@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render(OutputFormat::create()->setIgnoreExceptions(false)) + ); $oFirstBlock->removeSelector('.test'); $this->oDocument->render(OutputFormat::create()->setIgnoreExceptions(false)); } - public function testIgnoreExceptionsOn() { + public function testIgnoreExceptionsOn() + { $aBlocks = $this->oDocument->getAllDeclarationBlocks(); $oFirstBlock = $aBlocks[0]; $oFirstBlock->removeSelector('.main'); $oFirstBlock->removeSelector('.test'); - $this->assertSame('@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', $this->oDocument->render(OutputFormat::create()->setIgnoreExceptions(true))); + $this->assertSame( + '@media screen {.main {background-size: 100% 100%;font-size: 1.3em;background-color: #fff;}}', + $this->oDocument->render(OutputFormat::create()->setIgnoreExceptions(true)) + ); } -} \ No newline at end of file +}