From 55c55b861731d7fa84ba6fb3dc2b32455659d3f8 Mon Sep 17 00:00:00 2001 From: 8ctopus Date: Thu, 25 Dec 2025 14:24:35 +0400 Subject: [PATCH 1/2] [FEATURE] Do not escape characters that do not need escaping in CSS string --- src/Value/CSSString.php | 29 ++++++++++++++++++++++++++++- tests/ParserTest.php | 4 ++-- tests/fixtures/unicode.css | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 569311d77..7421b5573 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -90,8 +90,35 @@ public function getString(): string */ public function render(OutputFormat $outputFormat): string { - $string = \addslashes($this->string); + $string = $this->escape($this->string, $outputFormat->getStringQuotingType()); $string = \str_replace("\n", '\\A', $string); return $outputFormat->getStringQuotingType() . $string . $outputFormat->getStringQuotingType(); } + + private function escape(string $str, string $quote) : string + { + $matches = [ + 0x0, // null + 0x5c, // \ + ]; + + $matches[] = $quote === '"' ? 0x22 : 0x27; + + $length = strlen($str); + $output = ''; + $previous = 0x0; + + for ($i = 0; $i < $length; ++$i) { + $current = ord($str[$i]); + + if (in_array($current, $matches, true) && $previous !== 0x5c) { + $output .= '\\'; + } + + $output .= $str[$i]; + $previous = $current; + } + + return $output; + } } diff --git a/tests/ParserTest.php b/tests/ParserTest.php index 97e0d09b5..d05dc8a88 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -206,10 +206,10 @@ public function unicodeParsing(): void self::assertSame('"\\"\\""', $firstContentRuleAsString); } if ($selector === '.test-9') { - self::assertSame('"\\"\\\'"', $firstContentRuleAsString); + self::assertSame('"\\"\'"', $firstContentRuleAsString); } if ($selector === '.test-10') { - self::assertSame('"\\\'\\\\"', $firstContentRuleAsString); + self::assertSame('"\'\\\\"', $firstContentRuleAsString); } if ($selector === '.test-11') { self::assertSame('"test"', $firstContentRuleAsString); diff --git a/tests/fixtures/unicode.css b/tests/fixtures/unicode.css index 24823200b..8d60c7fe2 100644 --- a/tests/fixtures/unicode.css +++ b/tests/fixtures/unicode.css @@ -5,7 +5,7 @@ .test-6 { content: "\00A5" } /* Same as "¥" */ .test-7 { content: '\a' } /* Same as "\A" (Newline) */ .test-8 { content: "\"\22" } /* Same as "\"\"" */ -.test-9 { content: "\"\27" } /* Same as ""\"\'"" */ +.test-9 { content: "\"\27" } /* Same as ""\"'"" */ .test-10 { content: "\'\\" } /* Same as "'\" */ .test-11 { content: "\test" } /* Same as "test" */ From f724347fc0e200ac07acb67c84041fc11eb1baa1 Mon Sep 17 00:00:00 2001 From: 8ctopus Date: Fri, 26 Dec 2025 11:23:05 +0400 Subject: [PATCH 2/2] [CLEANUP] use \ prefix when calling a global function --- src/Value/CSSString.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Value/CSSString.php b/src/Value/CSSString.php index 7421b5573..066988220 100644 --- a/src/Value/CSSString.php +++ b/src/Value/CSSString.php @@ -104,14 +104,14 @@ private function escape(string $str, string $quote) : string $matches[] = $quote === '"' ? 0x22 : 0x27; - $length = strlen($str); + $length = \strlen($str); $output = ''; $previous = 0x0; for ($i = 0; $i < $length; ++$i) { - $current = ord($str[$i]); + $current = \ord($str[$i]); - if (in_array($current, $matches, true) && $previous !== 0x5c) { + if (\in_array($current, $matches, true) && $previous !== 0x5c) { $output .= '\\'; }