Skip to content

Add line number support in the sabberworm CSS parser #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
173c7ae
store line numbers in css objects
sidkshatriya May 30, 2016
6e65e00
Allow line num to be initialized in the Parser contructor
sidkshatriya May 31, 2016
4972aeb
line number for AtRule should be the number of its identifier
sidkshatriya May 31, 2016
b8d3287
Update tests with line numbers
sidkshatriya Jun 29, 2016
1e1a3a7
Merge branch 'master' into line-number-support-in-css-objects
sidkshatriya Jun 29, 2016
230ef27
Revert "Update tests with line numbers"
sidkshatriya Jun 30, 2016
1920219
CSSList should also implement Renderable
sidkshatriya Jun 30, 2016
8f554da
Rename getLineNum() to getLineNo() and make getLineNo a method in the…
sidkshatriya Jun 30, 2016
4ef7e03
Rule class should implement Renderable
sidkshatriya Jun 30, 2016
ab8c45c
Variable renaming and removing a bit of redundant code
sidkshatriya Jun 30, 2016
b55cee9
Add line number support to UnexpectedTokenException and OutputException
sidkshatriya Jun 30, 2016
faa6dc7
Add method setLineNo() which will be useful for synthetically setting…
sidkshatriya Jun 30, 2016
616bc2c
Remove getLineNo() from class Size as it already there in the class V…
sidkshatriya Jun 30, 2016
fa8ffba
Add method setLineNo to set each component of the class Color
sidkshatriya Jun 30, 2016
ec657c2
Ignore line numbers in ParserTest::testColorParsing()
sidkshatriya Jun 30, 2016
7045ea0
Rename iLineNum to iLineNo
sidkshatriya Jun 30, 2016
349d5ba
Opening braces on the same line for a method
sidkshatriya Jun 30, 2016
d7aa43e
Provide a getter for the OutputException and UnexpectedTokenException…
sidkshatriya Jun 30, 2016
83866af
Opening braces on the same line of method (more)
sidkshatriya Jun 30, 2016
82dcd54
Bug: iLineNo and not getLine()
sidkshatriya Jun 30, 2016
1c572eb
Add new class SourceException that is the parent exception class for …
sidkshatriya Jun 30, 2016
45f7b3b
Use SourceException class instead of raw \Exception
sidkshatriya Jun 30, 2016
61f90b8
Remove setLineNo() method from everywhere
sidkshatriya Jun 30, 2016
24bb671
Pass in the line number to the color test so that it never fails
sidkshatriya Jun 30, 2016
63386df
Remove redundant stuff from UnexpectedTokenException
sidkshatriya Jun 30, 2016
e282cb3
Parser intial line number should be 1 and not 0
sidkshatriya Jun 30, 2016
1644d6a
Add a test for line numbers
sidkshatriya Jun 30, 2016
a6c461e
Use array() instead of [] for PHP 5.3
sidkshatriya Jun 30, 2016
0be01bc
Fix line numbers and some more [] to array()
sidkshatriya Jun 30, 2016
5814549
SourceException and not \Exception
sidkshatriya Jun 30, 2016
134ee42
Yet another missed [] to array() conversion
sidkshatriya Jun 30, 2016
12c945f
Add test for multiline color rule
sidkshatriya Jun 30, 2016
2f06322
Adding a test for exception as suggested by @sabberworm https://githu…
sidkshatriya Jun 30, 2016
7132bed
Minor fixes and tweaks
sidkshatriya Jun 30, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/Sabberworm/CSS/CSSList/AtRuleBlockList.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class AtRuleBlockList extends CSSBlockList implements AtRule {
private $sType;
private $sArgs;

public function __construct($sType, $sArgs = '') {
parent::__construct();
public function __construct($sType, $sArgs = '', $iLineNo = 0) {
parent::__construct($iLineNo);
$this->sType = $sType;
$this->sArgs = $sArgs;
}
Expand Down
4 changes: 4 additions & 0 deletions lib/Sabberworm/CSS/CSSList/CSSBlockList.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
* Most CSSLists conform to this category but some at-rules (such as @keyframes) do not.
*/
abstract class CSSBlockList extends CSSList {
public function __construct($iLineNo = 0) {
parent::__construct($iLineNo);
}

protected function allDeclarationBlocks(&$aResult) {
foreach ($this->aContents as $mContent) {
if ($mContent instanceof DeclarationBlock) {
Expand Down
17 changes: 12 additions & 5 deletions lib/Sabberworm/CSS/CSSList/CSSList.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,30 @@

namespace Sabberworm\CSS\CSSList;

use Sabberworm\CSS\Renderable;
use Sabberworm\CSS\RuleSet\DeclarationBlock;
use Sabberworm\CSS\RuleSet\RuleSet;
use Sabberworm\CSS\Property\Selector;
use Sabberworm\CSS\Rule\Rule;
use Sabberworm\CSS\Value\ValueList;
use Sabberworm\CSS\Value\CSSFunction;

/**
* A CSSList is the most generic container available. Its contents include RuleSet as well as other CSSList objects.
* Also, it may contain Import and Charset objects stemming from @-rules.
*/
abstract class CSSList {
abstract class CSSList implements Renderable {

protected $aContents;
protected $iLineNo;

public function __construct() {
public function __construct($iLineNo = 0) {
$this->aContents = array();
$this->iLineNo = $iLineNo;
}

/**
* @return int
*/
public function getLineNo() {
return $this->iLineNo;
}

public function append($oItem) {
Expand Down
7 changes: 7 additions & 0 deletions lib/Sabberworm/CSS/CSSList/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
* The root CSSList of a parsed file. Contains all top-level css contents, mostly declaration blocks, but also any @-rules encountered.
*/
class Document extends CSSBlockList {
/**
* Document constructor.
* @param int $iLineNo
*/
public function __construct($iLineNo = 0) {
parent::__construct($iLineNo);
}

/**
* Gets all DeclarationBlock objects recursively.
Expand Down
4 changes: 2 additions & 2 deletions lib/Sabberworm/CSS/CSSList/KeyFrame.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ class KeyFrame extends CSSList implements AtRule {
private $vendorKeyFrame;
private $animationName;

public function __construct() {
parent::__construct();
public function __construct($iLineNo = 0) {
parent::__construct($iLineNo);
$this->vendorKeyFrame = null;
$this->animationName = null;
}
Expand Down
74 changes: 45 additions & 29 deletions lib/Sabberworm/CSS/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Sabberworm\CSS\CSSList\CSSList;
use Sabberworm\CSS\CSSList\Document;
use Sabberworm\CSS\CSSList\KeyFrame;
use Sabberworm\CSS\Parsing\SourceException;
use Sabberworm\CSS\Property\AtRule;
use Sabberworm\CSS\Property\Import;
use Sabberworm\CSS\Property\Charset;
Expand Down Expand Up @@ -34,10 +35,20 @@ class Parser {
private $iLength;
private $blockRules;
private $aSizeUnits;
private $iLineNo;

public function __construct($sText, Settings $oParserSettings = null) {
/**
* Parser constructor.
* Note that that iLineNo starts from 1 and not 0
*
* @param $sText
* @param Settings|null $oParserSettings
* @param int $iLineNo
*/
public function __construct($sText, Settings $oParserSettings = null, $iLineNo = 1) {
$this->sText = $sText;
$this->iCurrentPosition = 0;
$this->iLineNo = $iLineNo;
if ($oParserSettings === null) {
$oParserSettings = Settings::create();
}
Expand Down Expand Up @@ -66,7 +77,7 @@ public function getCharset() {

public function parse() {
$this->setCharset($this->oParserSettings->sDefaultCharset);
$oResult = new Document();
$oResult = new Document($this->iLineNo);
$this->parseDocument($oResult);
return $oResult;
}
Expand Down Expand Up @@ -98,7 +109,7 @@ private function parseList(CSSList $oList, $bIsRoot = false) {
$this->consumeWhiteSpace();
}
if (!$bIsRoot) {
throw new \Exception("Unexpected end of document");
throw new SourceException("Unexpected end of document", $this->iLineNo);
}
}

Expand All @@ -107,18 +118,18 @@ private function parseListItem(CSSList $oList, $bIsRoot = false) {
$oAtRule = $this->parseAtRule();
if($oAtRule instanceof Charset) {
if(!$bIsRoot) {
throw new UnexpectedTokenException('@charset may only occur in root document', '', 'custom');
throw new UnexpectedTokenException('@charset may only occur in root document', '', 'custom', $this->iLineNo);
}
if(count($oList->getContents()) > 0) {
throw new UnexpectedTokenException('@charset must be the first parseable token in a document', '', 'custom');
throw new UnexpectedTokenException('@charset must be the first parseable token in a document', '', 'custom', $this->iLineNo);
}
$this->setCharset($oAtRule->getCharset()->getString());
}
return $oAtRule;
} else if ($this->comes('}')) {
$this->consume('}');
if ($bIsRoot) {
throw new \Exception("Unopened {");
throw new SourceException("Unopened {", $this->iLineNo);
} else {
return null;
}
Expand All @@ -130,6 +141,7 @@ private function parseListItem(CSSList $oList, $bIsRoot = false) {
private function parseAtRule() {
$this->consume('@');
$sIdentifier = $this->parseIdentifier();
$iIdentifierLineNum = $this->iLineNo;
$this->consumeWhiteSpace();
if ($sIdentifier === 'import') {
$oLocation = $this->parseURLValue();
Expand All @@ -139,14 +151,14 @@ private function parseAtRule() {
$sMediaQuery = $this->consumeUntil(';');
}
$this->consume(';');
return new Import($oLocation, $sMediaQuery);
return new Import($oLocation, $sMediaQuery, $iIdentifierLineNum);
} else if ($sIdentifier === 'charset') {
$sCharset = $this->parseStringValue();
$this->consumeWhiteSpace();
$this->consume(';');
return new Charset($sCharset);
return new Charset($sCharset, $iIdentifierLineNum);
} else if ($this->identifierIs($sIdentifier, 'keyframes')) {
$oResult = new KeyFrame();
$oResult = new KeyFrame($iIdentifierLineNum);
$oResult->setVendorKeyFrame($sIdentifier);
$oResult->setAnimationName(trim($this->consumeUntil('{', false, true)));
$this->consumeWhiteSpace();
Expand All @@ -161,12 +173,12 @@ private function parseAtRule() {
}
$this->consume(';');
if ($sPrefix !== null && !is_string($sPrefix)) {
throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom');
throw new UnexpectedTokenException('Wrong namespace prefix', $sPrefix, 'custom', $iIdentifierLineNum);
}
if (!($mUrl instanceof CSSString || $mUrl instanceof URL)) {
throw new UnexpectedTokenException('Wrong namespace url of invalid type', $mUrl, 'custom');
throw new UnexpectedTokenException('Wrong namespace url of invalid type', $mUrl, 'custom', $iIdentifierLineNum);
}
return new CSSNamespace($mUrl, $sPrefix);
return new CSSNamespace($mUrl, $sPrefix, $iIdentifierLineNum);
} else {
//Unknown other at rule (font-face or such)
$sArgs = trim($this->consumeUntil('{', false, true));
Expand All @@ -179,10 +191,10 @@ private function parseAtRule() {
}
}
if($bUseRuleSet) {
$oAtRule = new AtRuleSet($sIdentifier, $sArgs);
$oAtRule = new AtRuleSet($sIdentifier, $sArgs, $iIdentifierLineNum);
$this->parseRuleSet($oAtRule);
} else {
$oAtRule = new AtRuleBlockList($sIdentifier, $sArgs);
$oAtRule = new AtRuleBlockList($sIdentifier, $sArgs, $iIdentifierLineNum);
$this->parseList($oAtRule);
}
return $oAtRule;
Expand All @@ -192,7 +204,7 @@ private function parseAtRule() {
private function parseIdentifier($bAllowFunctions = true, $bIgnoreCase = true) {
$sResult = $this->parseCharacter(true);
if ($sResult === null) {
throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier');
throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo);
}
$sCharacter = null;
while (($sCharacter = $this->parseCharacter(true)) !== null) {
Expand All @@ -204,7 +216,7 @@ private function parseIdentifier($bAllowFunctions = true, $bIgnoreCase = true) {
if ($bAllowFunctions && $this->comes('(')) {
$this->consume('(');
$aArguments = $this->parseValue(array('=', ' ', ','));
$sResult = new CSSFunction($sResult, $aArguments);
$sResult = new CSSFunction($sResult, $aArguments, ',', $this->iLineNo);
$this->consume(')');
}
return $sResult;
Expand Down Expand Up @@ -232,13 +244,13 @@ private function parseStringValue() {
while (!$this->comes($sQuote)) {
$sContent = $this->parseCharacter(false);
if ($sContent === null) {
throw new \Exception("Non-well-formed quoted string {$this->peek(3)}");
throw new SourceException("Non-well-formed quoted string {$this->peek(3)}", $this->iLineNo);
}
$sResult .= $sContent;
}
$this->consume($sQuote);
}
return new CSSString($sResult);
return new CSSString($sResult, $this->iLineNo);
}

private function parseCharacter($bIsForIdentifier) {
Expand Down Expand Up @@ -287,7 +299,7 @@ private function parseCharacter($bIsForIdentifier) {
}

private function parseSelector() {
$oResult = new DeclarationBlock();
$oResult = new DeclarationBlock($this->iLineNo);
$oResult->setSelector($this->consumeUntil('{', false, true));
$this->consumeWhiteSpace();
$this->parseRuleSet($oResult);
Expand Down Expand Up @@ -333,7 +345,7 @@ private function parseRuleSet($oRuleSet) {
}

private function parseRule() {
$oRule = new Rule($this->parseIdentifier());
$oRule = new Rule($this->parseIdentifier(), $this->iLineNo);
$this->consumeWhiteSpace();
$this->consume(':');
$oValue = $this->parseValue(self::listDelimiterForRule($oRule->getRule()));
Expand Down Expand Up @@ -387,7 +399,7 @@ private function parseValue($aListDelimiters) {
break;
}
}
$oList = new RuleValueList($sDelimiter);
$oList = new RuleValueList($sDelimiter, $this->iLineNo);
for ($i = $iStartPosition - 1; $i - $iStartPosition + 1 < $iLength * 2; $i+=2) {
$oList->addListComponent($aStack[$i]);
}
Expand Down Expand Up @@ -445,7 +457,7 @@ private function parseNumericValue($bForColor = false) {
}
}
}
return new Size(floatval($sSize), $sUnit, $bForColor);
return new Size(floatval($sSize), $sUnit, $bForColor, $this->iLineNo);
}

private function parseColorValue() {
Expand All @@ -456,7 +468,7 @@ private function parseColorValue() {
if ($this->strlen($sValue) === 3) {
$sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2];
}
$aColor = array('r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true), 'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true), 'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true));
$aColor = array('r' => new Size(intval($sValue[0] . $sValue[1], 16), null, true, $this->iLineNo), 'g' => new Size(intval($sValue[2] . $sValue[3], 16), null, true, $this->iLineNo), 'b' => new Size(intval($sValue[4] . $sValue[5], 16), null, true, $this->iLineNo));
} else {
$sColorMode = $this->parseIdentifier(false);
$this->consumeWhiteSpace();
Expand All @@ -472,7 +484,7 @@ private function parseColorValue() {
}
$this->consume(')');
}
return new Color($aColor);
return new Color($aColor, $this->iLineNo);
}

private function parseURLValue() {
Expand All @@ -483,7 +495,7 @@ private function parseURLValue() {
$this->consume('(');
}
$this->consumeWhiteSpace();
$oResult = new URL($this->parseStringValue());
$oResult = new URL($this->parseStringValue(), $this->iLineNo);
if ($bUseUrl) {
$this->consumeWhiteSpace();
$this->consume(')');
Expand Down Expand Up @@ -516,17 +528,21 @@ private function peek($iLength = 1, $iOffset = 0) {

private function consume($mValue = 1) {
if (is_string($mValue)) {
$iLineCount = substr_count($mValue, "\n");
$iLength = $this->strlen($mValue);
if (!$this->streql($this->substr($this->iCurrentPosition, $iLength), $mValue)) {
throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)));
throw new UnexpectedTokenException($mValue, $this->peek(max($iLength, 5)), $this->iLineNo);
}
$this->iLineNo += $iLineCount;
$this->iCurrentPosition += $this->strlen($mValue);
return $mValue;
} else {
if ($this->iCurrentPosition + $mValue > $this->iLength) {
throw new UnexpectedTokenException($mValue, $this->peek(5), 'count');
throw new UnexpectedTokenException($mValue, $this->peek(5), 'count', $this->iLineNo);
}
$sResult = $this->substr($this->iCurrentPosition, $mValue);
$iLineCount = substr_count($sResult, "\n");
$this->iLineNo += $iLineCount;
$this->iCurrentPosition += $mValue;
return $sResult;
}
Expand All @@ -537,7 +553,7 @@ private function consumeExpression($mExpression) {
if (preg_match($mExpression, $this->inputLeft(), $aMatches, PREG_OFFSET_CAPTURE) === 1) {
return $this->consume($aMatches[0][0]);
}
throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression');
throw new UnexpectedTokenException($mExpression, $this->peek(5), 'expression', $this->iLineNo);
}

private function consumeWhiteSpace() {
Expand Down Expand Up @@ -595,7 +611,7 @@ private function consumeUntil($aEnd, $bIncludeEnd = false, $consumeEnd = false)
}

$this->iCurrentPosition = $start;
throw new UnexpectedTokenException('One of ("'.implode('","', $aEnd).'")', $this->peek(5), 'search');
throw new UnexpectedTokenException('One of ("'.implode('","', $aEnd).'")', $this->peek(5), 'search', $this->iLineNo);
}

private function inputLeft() {
Expand Down
5 changes: 4 additions & 1 deletion lib/Sabberworm/CSS/Parsing/OutputException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
/**
* Thrown if the CSS parsers attempts to print something invalid
*/
class OutputException extends \Exception {
class OutputException extends SourceException {
public function __construct($sMessage, $iLineNo = 0) {
parent::__construct($sMessage, $iLineNo);
}
}
18 changes: 18 additions & 0 deletions lib/Sabberworm/CSS/Parsing/SourceException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Sabberworm\CSS\Parsing;

class SourceException extends \Exception {
private $iLineNo;
public function __construct($sMessage, $iLineNo = 0) {
$this->iLineNo = $iLineNo;
if (!empty($iLineNo)) {
$sMessage .= " [line no: $iLineNo]";
}
parent::__construct($sMessage);
}

public function getLineNo() {
return $this->iLineNo;
}
}
Loading