diff --git a/lib/Sabberworm/CSS/OutputFormat.php b/lib/Sabberworm/CSS/OutputFormat.php index 1b179840..45b97f52 100644 --- a/lib/Sabberworm/CSS/OutputFormat.php +++ b/lib/Sabberworm/CSS/OutputFormat.php @@ -286,4 +286,4 @@ private function prepareSpace($sSpaceString) { private function indent() { return str_repeat($this->oFormat->sIndentation, $this->oFormat->level()); } -} \ No newline at end of file +} diff --git a/lib/Sabberworm/CSS/Parser.php b/lib/Sabberworm/CSS/Parser.php index 65ea2f0d..cb22f0d1 100644 --- a/lib/Sabberworm/CSS/Parser.php +++ b/lib/Sabberworm/CSS/Parser.php @@ -14,7 +14,9 @@ use Sabberworm\CSS\CSSList\AtRuleBlockList; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\Value\CSSFunction; +use Sabberworm\CSS\Value\CalcFunction; use Sabberworm\CSS\Value\RuleValueList; +use Sabberworm\CSS\Value\CalcRuleValueList; use Sabberworm\CSS\Value\Size; use Sabberworm\CSS\Value\Color; use Sabberworm\CSS\Value\URL; @@ -434,6 +436,8 @@ private function parsePrimitiveValue() { $oValue = $this->parseColorValue(); } else if ($this->comes('url', true)) { $oValue = $this->parseURLValue(); + } else if ($this->comes('calc', true) || $this->comes('-webkit-calc', true) || $this->comes('-moz-calc', true)) { + $oValue = $this->parseCalcValue(); } else if ($this->comes("'") || $this->comes('"')) { $oValue = $this->parseStringValue(); } else if ($this->comes("progid:") && $this->oParserSettings->bLenientParsing) { @@ -520,6 +524,42 @@ private function parseURLValue() { return $oResult; } + private function parseCalcValue() { + $aOperators = array('+', '-', '*', '/', '(', ')'); + $sFunction = trim($this->consumeUntil('(', false, true)); + $oCalcList = new CalcRuleValueList($this->iLineNo); + $oList = new RuleValueList(',', $this->iLineNo); + $iNestingLevel = 0; + $iLastComponentType = NULL; + while(!$this->comes(')') || $iNestingLevel > 0) { + $this->consumeWhiteSpace(); + if (in_array($this->peek(), $aOperators)) { + if (($this->comes('-') || $this->comes('+'))) { + if ($this->peek(1, -1) != ' ' || !($this->comes('- ') || $this->comes('+ '))) { + throw new UnexpectedTokenException(" {$this->peek()} ", $this->peek(1, -1) . $this->peek(2), 'literal', $this->iLineNo); + } + } else if ($this->comes('(')) { + $iNestingLevel++; + } else if ($this->comes(')')) { + $iNestingLevel--; + } + $oCalcList->addListComponent($this->consume(1)); + $iLastComponentType = CalcFunction::T_OPERATOR; + } else { + $oVal = $this->parsePrimitiveValue(); + if ($iLastComponentType == CalcFunction::T_OPERAND) { + throw new UnexpectedTokenException(sprintf('Next token was expected to be an operand of type %s. Instead "%s" was found.', implode(', ', $aOperators), $oVal), '', 'custom', $this->iLineNo); + } + + $oCalcList->addListComponent($oVal); + $iLastComponentType = CalcFunction::T_OPERAND; + } + } + $oList->addListComponent($oCalcList); + $this->consume(')'); + return new CalcFunction($sFunction, $oList, ',', $this->iLineNo); + } + /** * Tests an identifier for a given value. Since identifiers are all keywords, they can be vendor-prefixed. We need to check for these versions too. */ diff --git a/lib/Sabberworm/CSS/Value/CSSFunction.php b/lib/Sabberworm/CSS/Value/CSSFunction.php index 3633abc7..941df236 100644 --- a/lib/Sabberworm/CSS/Value/CSSFunction.php +++ b/lib/Sabberworm/CSS/Value/CSSFunction.php @@ -4,7 +4,7 @@ class CSSFunction extends ValueList { - private $sName; + protected $sName; public function __construct($sName, $aArguments, $sSeparator = ',', $iLineNo = 0) { if($aArguments instanceof RuleValueList) { @@ -37,4 +37,4 @@ public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { return "{$this->sName}({$aArguments})"; } -} \ No newline at end of file +} diff --git a/lib/Sabberworm/CSS/Value/CalcFunction.php b/lib/Sabberworm/CSS/Value/CalcFunction.php new file mode 100644 index 00000000..0dba1b85 --- /dev/null +++ b/lib/Sabberworm/CSS/Value/CalcFunction.php @@ -0,0 +1,8 @@ +implode(' ', $this->aComponents); + } + +} diff --git a/tests/Sabberworm/CSS/ParserTest.php b/tests/Sabberworm/CSS/ParserTest.php index 6df3e872..ec3b9f84 100644 --- a/tests/Sabberworm/CSS/ParserTest.php +++ b/tests/Sabberworm/CSS/ParserTest.php @@ -387,6 +387,20 @@ function testUrlInFile() { $this->assertSame($sExpected, $oDoc->render()); } + function testCalcInFile() { + $oDoc = $this->parsedStructureForFile('calc', Settings::create()->withMultibyteSupport(true)); + $sExpected = 'div {width: calc(100% / 4);} +div {height: -webkit-calc(9 / 16 * 100%) !important;width: -moz-calc(( 50px - 50% ) * 2);}'; + $this->assertSame($sExpected, $oDoc->render()); + } + + /** + * @expectedException Sabberworm\CSS\Parsing\UnexpectedTokenException + */ + function testCalcFailure() { + $this->parsedStructureForFile('-calc-no-space-around-minus', Settings::create()->withLenientParsing(false)); + } + function testUrlInFileMbOff() { $oDoc = $this->parsedStructureForFile('url', Settings::create()->withMultibyteSupport(false)); $sExpected = 'body {background: #fff url("http://somesite.com/images/someimage.gif") repeat top center;} diff --git a/tests/files/-calc-no-space-around-minus.css b/tests/files/-calc-no-space-around-minus.css new file mode 100644 index 00000000..9414ad16 --- /dev/null +++ b/tests/files/-calc-no-space-around-minus.css @@ -0,0 +1 @@ +div { width: calc(50% -8px); } diff --git a/tests/files/calc.css b/tests/files/calc.css new file mode 100644 index 00000000..3ad06fdd --- /dev/null +++ b/tests/files/calc.css @@ -0,0 +1,5 @@ +div { width: calc(100% / 4); } +div { + height: -webkit-calc(9/16 * 100%)!important; + width: -moz-calc((50px - 50%)*2); +}