From aa00e4c0745db04526d50be98b45a5e209f2adc3 Mon Sep 17 00:00:00 2001 From: petkodimitrov Date: Thu, 28 Mar 2024 18:50:07 +0200 Subject: [PATCH 1/2] Parse escape quoted values --- src/Value/CSSString.php | 11 +++++++---- src/Value/Value.php | 33 ++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index da498d41..75842577 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -15,6 +15,8 @@ */ class CSSString extends PrimitiveValue { + const PARSE_QUOTE_STRINGS = ['"', "'", '\"', "\'"]; + /** * @var string */ @@ -40,11 +42,12 @@ public function __construct($sString, $iLineNo = 0) public static function parse(ParserState $oParserState) { $sBegin = $oParserState->peek(); + if ($sBegin === '\\') { + $sBegin = $oParserState->peek(2); + } $sQuote = null; - if ($sBegin === "'") { - $sQuote = "'"; - } elseif ($sBegin === '"') { - $sQuote = '"'; + if (in_array($sBegin, self::PARSE_QUOTE_STRINGS)) { + $sQuote = $sBegin; } if ($sQuote !== null) { $oParserState->consume($sQuote); diff --git a/src/Value/Value.php b/src/Value/Value.php index 5b52a22d..33e8942d 100644 --- a/src/Value/Value.php +++ b/src/Value/Value.php @@ -14,6 +14,9 @@ */ abstract class Value implements Renderable { + const PARSE_TERMINATE_STRINGS = ['}',';','!',')', '\\']; + const PARSE_QUOTE_STRINGS = ['"', "'", '\"', "\'"]; + /** * @var int */ @@ -41,12 +44,7 @@ public static function parseValue(ParserState $oParserState, array $aListDelimit $aStack = []; $oParserState->consumeWhiteSpace(); //Build a list of delimiters and parsed values - while ( - !($oParserState->comes('}') || $oParserState->comes(';') || $oParserState->comes('!') - || $oParserState->comes(')') - || $oParserState->comes('\\') - || $oParserState->isEnd()) - ) { + while (self::continueParsing($oParserState)) { if (count($aStack) > 0) { $bFoundDelimiter = false; foreach ($aListDelimiters as $sDelimiter) { @@ -154,7 +152,10 @@ public static function parsePrimitiveValue(ParserState $oParserState) $oValue = Size::parse($oParserState); } elseif ($oParserState->comes('#') || $oParserState->comes('rgb', true) || $oParserState->comes('hsl', true)) { $oValue = Color::parse($oParserState); - } elseif ($oParserState->comes("'") || $oParserState->comes('"')) { + } elseif ( + in_array($oParserState->peek(), self::PARSE_QUOTE_STRINGS) + || in_array($oParserState->peek(2), self::PARSE_QUOTE_STRINGS) + ) { $oValue = CSSString::parse($oParserState); } elseif ($oParserState->comes("progid:") && $oParserState->getSettings()->bLenientParsing) { $oValue = self::parseMicrosoftFilter($oParserState); @@ -209,4 +210,22 @@ public function getLineNo() { return $this->iLineNo; } + + /** + * @return bool + * + * @throws UnexpectedEOFException + * @throws UnexpectedTokenException + */ + private static function continueParsing(ParserState $oParserState) + { + if ($oParserState->isEnd()) { + return false; + } + $sPeekOne = $oParserState->peek(); + if ($sPeekOne === '\\') { + return in_array($oParserState->peek(2), self::PARSE_QUOTE_STRINGS); + } + return !in_array($sPeekOne, self::PARSE_TERMINATE_STRINGS); + } } From 6178a81920d7bc242dbe7b99c1f2d62f70f2f9d8 Mon Sep 17 00:00:00 2001 From: petkodimitrov Date: Fri, 29 Mar 2024 17:14:57 +0200 Subject: [PATCH 2/2] Add escaped quotes parsing unit test --- tests/ParserTest.php | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index a48ac0e7..dc0371d9 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -1242,6 +1242,37 @@ public function lonelyImport() self::assertSame($sExpected, $oDoc->render()); } + /** + * @test + */ + public function parseForEscapedQuotes() + { + $preParseCss = sprintf( + "%s%s%s%s%s%s%s", + '.fonts-first {font-family: Roboto, "Fira Mono", \"Liberation Serif\";}', + PHP_EOL, + ".font-second {font-family: Roboto, 'Fira Mono', \'Liberation Serif\';}", + PHP_EOL, + '.bgpic-first {background-image: url(\"pic.webp\");}', + PHP_EOL, + ".bgpic-second {background-image: url(\'pic.webp\');}" + ); + $expectedCss = sprintf( + "%s%s%s%s%s%s%s", + '.fonts-first {font-family: Roboto,"Fira Mono","Liberation Serif";}', + PHP_EOL, + '.font-second {font-family: Roboto,"Fira Mono","Liberation Serif";}', + PHP_EOL, + '.bgpic-first {background-image: url("pic.webp");}', + PHP_EOL, + '.bgpic-second {background-image: url("pic.webp");}' + ); + $parser = new Parser($preParseCss); + $document = $parser->parse(); + $postParseCss = $document->render(); + self::assertEquals($expectedCss, $postParseCss); + } + public function escapedSpecialCaseTokens() { $oDoc = $this->parsedStructureForFile('escaped-tokens');