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"; +}