Skip to content

Commit d7091aa

Browse files
committed
More accurate parsing of calc functions
1 parent 6f64709 commit d7091aa

File tree

7 files changed

+51
-27
lines changed

7 files changed

+51
-27
lines changed

lib/Sabberworm/CSS/OutputFormat.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,6 @@ class OutputFormat {
4141
// This is what’s printed after the comma of value lists
4242
public $sSpaceBeforeListArgumentSeparator = '';
4343
public $sSpaceAfterListArgumentSeparator = '';
44-
45-
// This is what’s printed after the operators in a calc function
46-
public $sSpaceBeforeCalcListArgumentSeparator = ' ';
47-
public $sSpaceAfterCalcListArgumentSeparator = ' ';
4844

4945
public $sSpaceBeforeOpeningBrace = ' ';
5046

@@ -223,14 +219,6 @@ public function spaceAfterListArgumentSeparator($sSeparator) {
223219
return $this->space('AfterListArgumentSeparator', $sSeparator);
224220
}
225221

226-
public function spaceBeforeCalcListArgumentSeparator($sSeparator) {
227-
return $this->space('BeforeCalcListArgumentSeparator', $sSeparator);
228-
}
229-
230-
public function spaceAfterCalcListArgumentSeparator($sSeparator) {
231-
return $this->space('AfterCalcListArgumentSeparator', $sSeparator);
232-
}
233-
234222
public function spaceBeforeOpeningBrace() {
235223
return $this->space('BeforeOpeningBrace');
236224
}

lib/Sabberworm/CSS/Parser.php

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ private function parseRule() {
374374
return $oRule;
375375
}
376376

377-
private function parseValue($aListDelimiters, $ruleValueListClass = '\Sabberworm\CSS\Value\RuleValueList') {
377+
private function parseValue($aListDelimiters) {
378378
$aStack = array();
379379
$this->consumeWhiteSpace();
380380
//Build a list of delimiters and parsed values
@@ -410,7 +410,7 @@ private function parseValue($aListDelimiters, $ruleValueListClass = '\Sabberworm
410410
break;
411411
}
412412
}
413-
$oList = new $ruleValueListClass($sDelimiter, $this->iLineNo);
413+
$oList = new RuleValueList($sDelimiter, $this->iLineNo);
414414
for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i+=2) {
415415
$oList->addListComponent($aStack[$i]);
416416
}
@@ -525,10 +525,39 @@ private function parseURLValue() {
525525
}
526526

527527
private function parseCalcValue() {
528-
$func = trim($this->consumeUntil('(', false, true));
529-
$aArguments = $this->parseValue(array('+', '-', '*', '/', ' '), '\Sabberworm\CSS\Value\CalcRuleValueList');
530-
$this->consumeUntil(')', false, true);
531-
return new CalcFunction($func, $aArguments, ',', $this->iLineNo);
528+
$aOperators = array('+', '-', '*', '/', '(', ')');
529+
$sFunction = trim($this->consumeUntil('(', false, true));
530+
$oCalcList = new CalcRuleValueList($this->iLineNo);
531+
$oList = new RuleValueList(',', $this->iLineNo);
532+
$iNestingLevel = 0;
533+
$iLastComponentType = NULL;
534+
while(!$this->comes(')') || $iNestingLevel > 0) {
535+
$this->consumeWhiteSpace();
536+
if (in_array($this->peek(), $aOperators)) {
537+
if (($this->comes('-') || $this->comes('+'))) {
538+
if ($this->peek(1, -1) != ' ' || !($this->comes('- ') || $this->comes('+ '))) {
539+
throw new UnexpectedTokenException(" {$this->peek()} ", $this->peek(1, -1) . $this->peek(2), 'literal', $this->iLineNo);
540+
}
541+
} else if ($this->comes('(')) {
542+
$iNestingLevel++;
543+
} else if ($this->comes(')')) {
544+
$iNestingLevel--;
545+
}
546+
$oCalcList->addListComponent($this->consume(1));
547+
$iLastComponentType = CalcFunction::T_OPERATOR;
548+
} else {
549+
$oVal = $this->parsePrimitiveValue();
550+
if ($iLastComponentType == CalcFunction::T_OPERAND) {
551+
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);
552+
}
553+
554+
$oCalcList->addListComponent($oVal);
555+
$iLastComponentType = CalcFunction::T_OPERAND;
556+
}
557+
}
558+
$oList->addListComponent($oCalcList);
559+
$this->consume(')');
560+
return new CalcFunction($sFunction, $oList, ',', $this->iLineNo);
532561
}
533562

534563
/**

lib/Sabberworm/CSS/Value/CalcFunction.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33
namespace Sabberworm\CSS\Value;
44

55
class CalcFunction extends CSSFunction {
6-
7-
public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) {
8-
$aArguments = $oOutputFormat->implode($oOutputFormat->spaceBeforeCalcListArgumentSeparator($this->sSeparator) . $this->sSeparator . $oOutputFormat->spaceAfterCalcListArgumentSeparator($this->sSeparator), $this->aComponents);
9-
return "{$this->sName}({$aArguments})";
10-
}
11-
6+
const T_OPERAND = 1;
7+
const T_OPERATOR = 2;
128
}

lib/Sabberworm/CSS/Value/CalcRuleValueList.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
namespace Sabberworm\CSS\Value;
44

55
class CalcRuleValueList extends RuleValueList {
6+
public function __construct($iLineNo = 0) {
7+
parent::__construct(array(), ',', $iLineNo);
8+
}
69

710
public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) {
8-
return $oOutputFormat->implode($oOutputFormat->spaceBeforeCalcListArgumentSeparator($this->sSeparator) . $this->sSeparator . $oOutputFormat->spaceAfterCalcListArgumentSeparator($this->sSeparator), $this->aComponents);
11+
return $oOutputFormat->implode(' ', $this->aComponents);
912
}
1013

1114
}

tests/Sabberworm/CSS/ParserTest.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,17 @@ function testUrlInFile() {
390390
function testCalcInFile() {
391391
$oDoc = $this->parsedStructureForFile('calc', Settings::create()->withMultibyteSupport(true));
392392
$sExpected = 'div {width: calc(100% / 4);}
393-
div {height: -webkit-calc(9 / 16 * 100%) !important;width: -moz-calc(50px - 50%);}';
393+
div {height: -webkit-calc(9 / 16 * 100%) !important;width: -moz-calc(( 50px - 50% ) * 2);}';
394394
$this->assertSame($sExpected, $oDoc->render());
395395
}
396396

397+
/**
398+
* @expectedException Sabberworm\CSS\Parsing\UnexpectedTokenException
399+
*/
400+
function testCalcFailure() {
401+
$this->parsedStructureForFile('-calc-no-space-around-minus', Settings::create()->withLenientParsing(false));
402+
}
403+
397404
function testUrlInFileMbOff() {
398405
$oDoc = $this->parsedStructureForFile('url', Settings::create()->withMultibyteSupport(false));
399406
$sExpected = 'body {background: #fff url("http://somesite.com/images/someimage.gif") repeat top center;}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
div { width: calc(50% -8px); }

tests/files/calc.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
div { width: calc(100% / 4); }
22
div {
33
height: -webkit-calc(9/16 * 100%)!important;
4-
width: -moz-calc(50px-50%);
4+
width: -moz-calc((50px - 50%)*2);
55
}

0 commit comments

Comments
 (0)