From e1ecb78edee861dcfcf7d97dca090af22fae8b87 Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Mon, 18 Jul 2016 16:44:59 +0800 Subject: [PATCH] Support for IE hack parsing in lenient mode This allows for \0 and \9 to remain as they are intended. And will throw an exception when they are found in strict mode. --- lib/Sabberworm/CSS/Parser.php | 13 ++++++++++++- lib/Sabberworm/CSS/Rule/Rule.php | 17 +++++++++++++++++ tests/Sabberworm/CSS/ParserTest.php | 14 ++++++++++++++ tests/files/ie-hacks.css | 9 +++++++++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/files/ie-hacks.css diff --git a/lib/Sabberworm/CSS/Parser.php b/lib/Sabberworm/CSS/Parser.php index ea68fee8e..bcff0b62f 100644 --- a/lib/Sabberworm/CSS/Parser.php +++ b/lib/Sabberworm/CSS/Parser.php @@ -255,6 +255,10 @@ private function parseStringValue() { private function parseCharacter($bIsForIdentifier) { if ($this->peek() === '\\') { + if ($bIsForIdentifier && $this->oParserSettings->bLenientParsing && ($this->comes('\0') || $this->comes('\9'))) { + // Non-strings can contain \0 or \9 which is an IE hack supported in lenient parsing. + return null; + } $this->consume('\\'); if ($this->comes('\n') || $this->comes('\r')) { return ''; @@ -350,6 +354,13 @@ private function parseRule() { $this->consume(':'); $oValue = $this->parseValue(self::listDelimiterForRule($oRule->getRule())); $oRule->setValue($oValue); + if ($this->oParserSettings->bLenientParsing) { + while ($this->comes('\\')) { + $this->consume('\\'); + $oRule->addIeHack($this->consume()); + $this->consumeWhiteSpace(); + } + } if ($this->comes('!')) { $this->consume('!'); $this->consumeWhiteSpace(); @@ -367,7 +378,7 @@ private function parseValue($aListDelimiters) { $aStack = array(); $this->consumeWhiteSpace(); //Build a list of delimiters and parsed values - while (!($this->comes('}') || $this->comes(';') || $this->comes('!') || $this->comes(')'))) { + while (!($this->comes('}') || $this->comes(';') || $this->comes('!') || $this->comes(')') || $this->comes('\\'))) { if (count($aStack) > 0) { $bFoundDelimiter = false; foreach ($aListDelimiters as $sDelimiter) { diff --git a/lib/Sabberworm/CSS/Rule/Rule.php b/lib/Sabberworm/CSS/Rule/Rule.php index d0d0cbd6d..2323518ce 100644 --- a/lib/Sabberworm/CSS/Rule/Rule.php +++ b/lib/Sabberworm/CSS/Rule/Rule.php @@ -15,12 +15,14 @@ class Rule implements Renderable { private $sRule; private $mValue; private $bIsImportant; + private $aIeHack; protected $iLineNo; public function __construct($sRule, $iLineNo = 0) { $this->sRule = $sRule; $this->mValue = null; $this->bIsImportant = false; + $this->aIeHack = array(); $this->iLineNo = $iLineNo; } @@ -127,6 +129,18 @@ public function addValue($mValue, $sType = ' ') { } } + public function addIeHack($iModifier) { + $this->aIeHack[] = $iModifier; + } + + public function setIeHack(array $aModifiers) { + $this->aIeHack = $aModifiers; + } + + public function getIeHack() { + return $this->aIeHack; + } + public function setIsImportant($bIsImportant) { $this->bIsImportant = $bIsImportant; } @@ -146,6 +160,9 @@ public function render(\Sabberworm\CSS\OutputFormat $oOutputFormat) { } else { $sResult .= $this->mValue; } + if (!empty($this->aIeHack)) { + $sResult .= ' \\' . implode('\\', $this->aIeHack); + } if ($this->bIsImportant) { $sResult .= ' !important'; } diff --git a/tests/Sabberworm/CSS/ParserTest.php b/tests/Sabberworm/CSS/ParserTest.php index 8d333bbf9..c634ca7af 100644 --- a/tests/Sabberworm/CSS/ParserTest.php +++ b/tests/Sabberworm/CSS/ParserTest.php @@ -505,4 +505,18 @@ function testUnexpectedTokenExceptionLineNo() { throw $e; } } + + /** + * @expectedException Sabberworm\CSS\Parsing\UnexpectedTokenException + */ + function testIeHacksStrictParsing() { + // We can't strictly parse IE hacks. + $this->parsedStructureForFile('ie-hacks', Settings::create()->beStrict()); + } + + function testIeHacksParsing() { + $oDoc = $this->parsedStructureForFile('ie-hacks', Settings::create()->withLenientParsing(true)); + $sExpected = 'p {padding-right: .75rem \9;background-image: none \9;color: red \9\0;background-color: red \9\0;background-color: red \9\0 !important;content: "red \0";content: "red઼";}'; + $this->assertEquals($sExpected, $oDoc->render()); + } } diff --git a/tests/files/ie-hacks.css b/tests/files/ie-hacks.css new file mode 100644 index 000000000..3f5f215eb --- /dev/null +++ b/tests/files/ie-hacks.css @@ -0,0 +1,9 @@ +p { + padding-right: .75rem \9; + background-image: none \9; + color:red\9\0; + background-color:red \9 \0; + background-color:red \9 \0 !important; + content: "red \9\0"; + content: "red\0abc"; +}