From 589fe509b60bd008e65ae5290c8f5e33fb150083 Mon Sep 17 00:00:00 2001 From: Ruud van Lent Date: Thu, 23 Dec 2021 16:36:28 +0100 Subject: [PATCH 1/7] retain CSSList and Rule comments when parsing --- src/CSSList/CSSList.php | 10 +++++++++- src/Rule/Rule.php | 1 - src/RuleSet/RuleSet.php | 10 +++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 946740a4..eea16c00 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -89,7 +89,6 @@ public static function parseList(ParserState $oParserState, CSSList $oList) $oListItem->setComments($comments); $oList->append($oListItem); } - $oParserState->consumeWhiteSpace(); } if (!$bIsRoot && !$bLenientParsing) { throw new SourceException("Unexpected end of document", $oParserState->currentLine()); @@ -412,6 +411,15 @@ public function render(OutputFormat $oOutputFormat) } foreach ($this->aContents as $oContent) { $sRendered = $oOutputFormat->safely(function () use ($oNextLevel, $oContent) { + $sResult = ''; + $aComments = $oContent->getComments(); + + foreach ($aComments as $oComment) + { + $sResult .= $oComment->render($oNextLevel); + $sResult .= $oNextLevel->spaceAfterBlocks(); + } + return $sResult . $oContent->render($oNextLevel); return $oContent->render($oNextLevel); }); if ($sRendered === null) { diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index c1ea6df7..52b97f38 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -106,7 +106,6 @@ public static function parse(ParserState $oParserState) while ($oParserState->comes(';')) { $oParserState->consume(';'); } - $oParserState->consumeWhiteSpace(); return $oRule; } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 9404bb0b..b30e7fdc 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -273,7 +273,15 @@ public function render(OutputFormat $oOutputFormat) foreach ($this->aRules as $aRules) { foreach ($aRules as $oRule) { $sRendered = $oOutputFormat->safely(function () use ($oRule, $oOutputFormat) { - return $oRule->render($oOutputFormat->nextLevel()); + $sResult = ''; + $aComments = $oRule->getComments(); + + foreach ($aComments as $oComment) + { + $sResult .= $oComment->render($oOutputFormat); + $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); + } + return $sResult . $oRule->render($oOutputFormat->nextLevel()); }); if ($sRendered === null) { continue; From cbb0a9d07564017ad42ea6da7498e6b10bc5d40a Mon Sep 17 00:00:00 2001 From: Ruud van Lent Date: Thu, 23 Dec 2021 16:47:12 +0100 Subject: [PATCH 2/7] oeps: remove replaced return line --- src/CSSList/CSSList.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index eea16c00..d8f41f2e 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -420,7 +420,6 @@ public function render(OutputFormat $oOutputFormat) $sResult .= $oNextLevel->spaceAfterBlocks(); } return $sResult . $oContent->render($oNextLevel); - return $oContent->render($oNextLevel); }); if ($sRendered === null) { continue; From 6b6640f34795d887d510f1883452c1f7db5fe197 Mon Sep 17 00:00:00 2001 From: Ruud van Lent Date: Fri, 24 Dec 2021 16:32:41 +0100 Subject: [PATCH 3/7] Fixed Unit testing + Fixed issue when unit testing + added empty line between comments except last one --- src/CSSList/CSSList.php | 12 ++++++++++-- src/Rule/Rule.php | 1 + src/RuleSet/RuleSet.php | 6 +++++- tests/ParserTest.php | 38 +++++++++++++++++++++++++++++++------- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index d8f41f2e..c1e42952 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -69,8 +69,11 @@ public static function parseList(ParserState $oParserState, CSSList $oList) $oParserState = new ParserState($oParserState, Settings::create()); } $bLenientParsing = $oParserState->getSettings()->bLenientParsing; + $comments=[]; while (!$oParserState->isEnd()) { - $comments = $oParserState->consumeWhiteSpace(); + if (empty($comments)) { + $comments = $oParserState->consumeWhiteSpace(); + } $oListItem = null; if ($bLenientParsing) { try { @@ -89,6 +92,7 @@ public static function parseList(ParserState $oParserState, CSSList $oList) $oListItem->setComments($comments); $oList->append($oListItem); } + $comments = $oParserState->consumeWhiteSpace(); } if (!$bIsRoot && !$bLenientParsing) { throw new SourceException("Unexpected end of document", $oParserState->currentLine()); @@ -413,11 +417,15 @@ public function render(OutputFormat $oOutputFormat) $sRendered = $oOutputFormat->safely(function () use ($oNextLevel, $oContent) { $sResult = ''; $aComments = $oContent->getComments(); + $c = count($aComments); - foreach ($aComments as $oComment) + foreach ($aComments as $i => $oComment) { $sResult .= $oComment->render($oNextLevel); $sResult .= $oNextLevel->spaceAfterBlocks(); + if ($c-1 !== $i) { + $sResult .= $oNextLevel->spaceAfterBlocks(); + } } return $sResult . $oContent->render($oNextLevel); }); diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index 52b97f38..c1ea6df7 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -106,6 +106,7 @@ public static function parse(ParserState $oParserState) while ($oParserState->comes(';')) { $oParserState->consume(';'); } + $oParserState->consumeWhiteSpace(); return $oRule; } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index b30e7fdc..24f1e98d 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -275,11 +275,15 @@ public function render(OutputFormat $oOutputFormat) $sRendered = $oOutputFormat->safely(function () use ($oRule, $oOutputFormat) { $sResult = ''; $aComments = $oRule->getComments(); + $c = count($aComments); - foreach ($aComments as $oComment) + foreach ($aComments as $i => $oComment) { $sResult .= $oComment->render($oOutputFormat); $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); + if ($c-1 !== $i) { + $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); + } } return $sResult . $oRule->render($oOutputFormat->nextLevel()); }); diff --git a/tests/ParserTest.php b/tests/ParserTest.php index ab247c3d..a81b2f6a 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -309,7 +309,17 @@ public function manipulation() . "\n" . ' domain(mozilla.org),' . "\n" - . ' regexp("https:.*") {body {color: purple;background: yellow;}}' + . ' regexp("https:.*") {/* CSS rules here apply to:' + . "\n" + . ' + The page "https://www.w3.org/".' + . "\n" + . ' + Any page whose URL begins with "https://www.w3.org/Style/"' + . "\n" + . ' + Any page whose URL\'s host is "mozilla.org" or ends with' + . "\n" + . ' ".mozilla.org"' + . "\n" + . ' + Any page whose URL starts with "https:" *//* make the above-mentioned pages really ugly */body {color: purple;background: yellow;}}' . "\n" . '@media screen and (orientation: landscape) {@-ms-viewport {width: 1024px;height: 768px;}}' . "\n" @@ -348,7 +358,17 @@ public function manipulation() . "\n" . ' domain(mozilla.org),' . "\n" - . ' regexp("https:.*") {#my_id body {color: purple;background: yellow;}}' + . ' regexp("https:.*") {/* CSS rules here apply to:' + . "\n" + . ' + The page "https://www.w3.org/".' + . "\n" + . ' + Any page whose URL begins with "https://www.w3.org/Style/"' + . "\n" + . ' + Any page whose URL\'s host is "mozilla.org" or ends with' + . "\n" + . ' ".mozilla.org"' + . "\n" + . ' + Any page whose URL starts with "https:" *//* make the above-mentioned pages really ugly */#my_id body {color: purple;background: yellow;}}' . "\n" . '@media screen and (orientation: landscape) {@-ms-viewport {width: 1024px;height: 768px;}}' . "\n" @@ -532,9 +552,9 @@ public function createShorthands() public function namespaces() { $oDoc = $this->parsedStructureForFile('namespaces'); - $sExpected = '@namespace toto "http://toto.example.org"; + $sExpected = '/* From the spec at https://www.w3.org/TR/css3-namespace/ */@namespace toto "http://toto.example.org"; @namespace "http://example.com/foo"; -@namespace foo url("http://www.example.com/"); +/* From an introduction at https://www.blooberry.com/indexdot/css/syntax/atrules/namespace.htm */@namespace foo url("http://www.example.com/"); @namespace foo url("http://www.example.com/"); foo|test {gaga: 1;} |test {gaga: 2;}'; @@ -626,9 +646,13 @@ public function selectorRemoval() public function comments() { $oDoc = $this->parsedStructureForFile('comments'); - $sExpected = '@import url("some/url.css") screen; -.foo, #bar {background-color: #000;} -@media screen {#foo.bar {position: absolute;}}'; + $sExpected = <<render()); } From 9f34e8b6707150d58ff2f7722f266f476debdef5 Mon Sep 17 00:00:00 2001 From: Ruud van Lent Date: Tue, 28 Dec 2021 14:01:24 +0100 Subject: [PATCH 4/7] codestyle --- src/CSSList/CSSList.php | 7 +++---- src/RuleSet/RuleSet.php | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index c1e42952..6bfb1dc2 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -69,7 +69,7 @@ public static function parseList(ParserState $oParserState, CSSList $oList) $oParserState = new ParserState($oParserState, Settings::create()); } $bLenientParsing = $oParserState->getSettings()->bLenientParsing; - $comments=[]; + $comments = []; while (!$oParserState->isEnd()) { if (empty($comments)) { $comments = $oParserState->consumeWhiteSpace(); @@ -419,11 +419,10 @@ public function render(OutputFormat $oOutputFormat) $aComments = $oContent->getComments(); $c = count($aComments); - foreach ($aComments as $i => $oComment) - { + foreach ($aComments as $i => $oComment) { $sResult .= $oComment->render($oNextLevel); $sResult .= $oNextLevel->spaceAfterBlocks(); - if ($c-1 !== $i) { + if ($c - 1 !== $i) { $sResult .= $oNextLevel->spaceAfterBlocks(); } } diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 24f1e98d..0587296b 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -277,11 +277,10 @@ public function render(OutputFormat $oOutputFormat) $aComments = $oRule->getComments(); $c = count($aComments); - foreach ($aComments as $i => $oComment) - { + foreach ($aComments as $i => $oComment) { $sResult .= $oComment->render($oOutputFormat); $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); - if ($c-1 !== $i) { + if ($c - 1 !== $i) { $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); } } From 6c03912f28803df324f8ec8d3620a4e780787b44 Mon Sep 17 00:00:00 2001 From: Raphael Schweikert Date: Tue, 28 Dec 2021 16:08:41 +0100 Subject: [PATCH 5/7] Make parsedStructureForFile a static function so it can be used from other tests --- tests/ParserTest.php | 112 +++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/tests/ParserTest.php b/tests/ParserTest.php index a81b2f6a..fc4e6897 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -89,7 +89,7 @@ public function files() */ public function colorParsing() { - $oDoc = $this->parsedStructureForFile('colortest'); + $oDoc = self::parsedStructureForFile('colortest'); foreach ($oDoc->getAllRuleSets() as $oRuleSet) { if (!$oRuleSet instanceof DeclarationBlock) { continue; @@ -169,7 +169,7 @@ public function colorParsing() */ public function unicodeParsing() { - $oDoc = $this->parsedStructureForFile('unicode'); + $oDoc = self::parsedStructureForFile('unicode'); foreach ($oDoc->getAllDeclarationBlocks() as $oRuleSet) { $sSelector = $oRuleSet->getSelectors(); $sSelector = $sSelector[0]->getSelector(); @@ -220,7 +220,7 @@ public function unicodeParsing() */ public function unicodeRangeParsing() { - $oDoc = $this->parsedStructureForFile('unicode-range'); + $oDoc = self::parsedStructureForFile('unicode-range'); $sExpected = "@font-face {unicode-range: U+0100-024F,U+0259,U+1E??-2EFF,U+202F;}"; self::assertSame($sExpected, $oDoc->render()); } @@ -230,7 +230,7 @@ public function unicodeRangeParsing() */ public function specificity() { - $oDoc = $this->parsedStructureForFile('specificity'); + $oDoc = self::parsedStructureForFile('specificity'); $oDeclarationBlock = $oDoc->getAllDeclarationBlocks(); $oDeclarationBlock = $oDeclarationBlock[0]; $aSelectors = $oDeclarationBlock->getSelectors(); @@ -282,7 +282,7 @@ public function specificity() */ public function manipulation() { - $oDoc = $this->parsedStructureForFile('atrules'); + $oDoc = self::parsedStructureForFile('atrules'); self::assertSame( '@charset "utf-8";' . "\n" @@ -376,7 +376,7 @@ public function manipulation() $oDoc->render() ); - $oDoc = $this->parsedStructureForFile('values'); + $oDoc = self::parsedStructureForFile('values'); self::assertSame( '#header {margin: 10px 2em 1cm 2%;font-family: Verdana,Helvetica,"Gill Sans",sans-serif;' . 'font-size: 10px;color: red !important;background-color: green;' @@ -408,7 +408,7 @@ public function manipulation() */ public function ruleGetters() { - $oDoc = $this->parsedStructureForFile('values'); + $oDoc = self::parsedStructureForFile('values'); $aBlocks = $oDoc->getAllDeclarationBlocks(); $oHeaderBlock = $aBlocks[0]; $oBodyBlock = $aBlocks[1]; @@ -431,7 +431,7 @@ public function ruleGetters() */ public function slashedValues() { - $oDoc = $this->parsedStructureForFile('slashed'); + $oDoc = self::parsedStructureForFile('slashed'); self::assertSame( '.test {font: 12px/1.5 Verdana,Arial,sans-serif;border-radius: 5px 10px 5px 10px/10px 5px 10px 5px;}', $oDoc->render() @@ -472,7 +472,7 @@ public function slashedValues() */ public function functionSyntax() { - $oDoc = $this->parsedStructureForFile('functions'); + $oDoc = self::parsedStructureForFile('functions'); $sExpected = 'div.main {background-image: linear-gradient(#000,#fff);}' . "\n" . '.collapser::before, .collapser::-moz-before, .collapser::-webkit-before {content: "»";font-size: 1.2em;' @@ -510,7 +510,7 @@ public function functionSyntax() */ public function expandShorthands() { - $oDoc = $this->parsedStructureForFile('expand-shorthands'); + $oDoc = self::parsedStructureForFile('expand-shorthands'); $sExpected = 'body {font: italic 500 14px/1.618 "Trebuchet MS",Georgia,serif;border: 2px solid #f0f;' . 'background: #ccc url("/images/foo.png") no-repeat left top;margin: 1em !important;' . 'padding: 2px 6px 3px;}'; @@ -534,7 +534,7 @@ public function expandShorthands() */ public function createShorthands() { - $oDoc = $this->parsedStructureForFile('create-shorthands'); + $oDoc = self::parsedStructureForFile('create-shorthands'); $sExpected = 'body {font-size: 2em;font-family: Helvetica,Arial,sans-serif;font-weight: bold;' . 'border-width: 2px;border-color: #999;border-style: dotted;background-color: #fff;' . 'background-image: url("foobar.png");background-repeat: repeat-y;margin-top: 2px;margin-right: 3px;' @@ -551,7 +551,7 @@ public function createShorthands() */ public function namespaces() { - $oDoc = $this->parsedStructureForFile('namespaces'); + $oDoc = self::parsedStructureForFile('namespaces'); $sExpected = '/* From the spec at https://www.w3.org/TR/css3-namespace/ */@namespace toto "http://toto.example.org"; @namespace "http://example.com/foo"; /* From an introduction at https://www.blooberry.com/indexdot/css/syntax/atrules/namespace.htm */@namespace foo url("http://www.example.com/"); @@ -566,7 +566,7 @@ public function namespaces() */ public function innerColors() { - $oDoc = $this->parsedStructureForFile('inner-color'); + $oDoc = self::parsedStructureForFile('inner-color'); $sExpected = 'test {background: -webkit-gradient(linear,0 0,0 bottom,from(#006cad),to(hsl(202,100%,49%)));}'; self::assertSame($sExpected, $oDoc->render()); } @@ -576,7 +576,7 @@ public function innerColors() */ public function prefixedGradient() { - $oDoc = $this->parsedStructureForFile('webkit'); + $oDoc = self::parsedStructureForFile('webkit'); $sExpected = '.test {background: -webkit-linear-gradient(top right,white,black);}'; self::assertSame($sExpected, $oDoc->render()); } @@ -586,7 +586,7 @@ public function prefixedGradient() */ public function listValueRemoval() { - $oDoc = $this->parsedStructureForFile('atrules'); + $oDoc = self::parsedStructureForFile('atrules'); foreach ($oDoc->getContents() as $oItem) { if ($oItem instanceof AtRule) { $oDoc->remove($oItem); @@ -595,7 +595,7 @@ public function listValueRemoval() } self::assertSame('html, body {font-size: -.6em;}', $oDoc->render()); - $oDoc = $this->parsedStructureForFile('nested'); + $oDoc = self::parsedStructureForFile('nested'); foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { $oDoc->removeDeclarationBlockBySelector($oBlock, false); break; @@ -607,7 +607,7 @@ public function listValueRemoval() $oDoc->render() ); - $oDoc = $this->parsedStructureForFile('nested'); + $oDoc = self::parsedStructureForFile('nested'); foreach ($oDoc->getAllDeclarationBlocks() as $oBlock) { $oDoc->removeDeclarationBlockBySelector($oBlock, true); break; @@ -626,7 +626,7 @@ public function listValueRemoval() */ public function selectorRemoval() { - $oDoc = $this->parsedStructureForFile('1readme'); + $oDoc = self::parsedStructureForFile('1readme'); $aBlocks = $oDoc->getAllDeclarationBlocks(); $oBlock1 = $aBlocks[0]; self::assertTrue($oBlock1->removeSelector('html')); @@ -645,7 +645,7 @@ public function selectorRemoval() */ public function comments() { - $oDoc = $this->parsedStructureForFile('comments'); + $oDoc = self::parsedStructureForFile('comments'); $sExpected = <<parsedStructureForFile('url', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('url', Settings::create()->withMultibyteSupport(true)); $sExpected = 'body {background: #fff url("https://somesite.com/images/someimage.gif") repeat top center;} body {background-url: url("https://somesite.com/images/someimage.gif");}'; self::assertSame($sExpected, $oDoc->render()); @@ -672,7 +672,7 @@ public function urlInFile() */ public function hexAlphaInFile() { - $oDoc = $this->parsedStructureForFile('hex-alpha', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('hex-alpha', Settings::create()->withMultibyteSupport(true)); $sExpected = 'div {background: rgba(17,34,51,.27);} div {background: rgba(17,34,51,.27);}'; self::assertSame($sExpected, $oDoc->render()); @@ -683,7 +683,7 @@ public function hexAlphaInFile() */ public function calcInFile() { - $oDoc = $this->parsedStructureForFile('calc', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('calc', Settings::create()->withMultibyteSupport(true)); $sExpected = 'div {width: calc(100% / 4);} div {margin-top: calc(-120% - 4px);} div {height: -webkit-calc(9 / 16 * 100%) !important;width: -moz-calc(( 50px - 50% ) * 2);} @@ -696,7 +696,7 @@ public function calcInFile() */ public function calcNestedInFile() { - $oDoc = $this->parsedStructureForFile('calc-nested', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('calc-nested', Settings::create()->withMultibyteSupport(true)); $sExpected = '.test {font-size: calc(( 3 * 4px ) + -2px);top: calc(200px - calc(20 * 3px));}'; self::assertSame($sExpected, $oDoc->render()); } @@ -706,7 +706,7 @@ public function calcNestedInFile() */ public function gridLineNameInFile() { - $oDoc = $this->parsedStructureForFile('grid-linename', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('grid-linename', Settings::create()->withMultibyteSupport(true)); $sExpected = "div {grid-template-columns: [linename] 100px;}\n" . "span {grid-template-columns: [linename1 linename2] 100px;}"; self::assertSame($sExpected, $oDoc->render()); @@ -717,7 +717,7 @@ public function gridLineNameInFile() */ public function emptyGridLineNameLenientInFile() { - $oDoc = $this->parsedStructureForFile('empty-grid-linename'); + $oDoc = self::parsedStructureForFile('empty-grid-linename'); $sExpected = '.test {grid-template-columns: [] 100px;}'; self::assertSame($sExpected, $oDoc->render()); } @@ -727,7 +727,7 @@ public function emptyGridLineNameLenientInFile() */ public function invalidGridLineNameInFile() { - $oDoc = $this->parsedStructureForFile('invalid-grid-linename', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('invalid-grid-linename', Settings::create()->withMultibyteSupport(true)); $sExpected = "div {}"; self::assertSame($sExpected, $oDoc->render()); } @@ -737,7 +737,7 @@ public function invalidGridLineNameInFile() */ public function unmatchedBracesInFile() { - $oDoc = $this->parsedStructureForFile('unmatched_braces', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('unmatched_braces', Settings::create()->withMultibyteSupport(true)); $sExpected = 'button, input, checkbox, textarea {outline: 0;margin: 0;}'; self::assertSame($sExpected, $oDoc->render()); } @@ -747,13 +747,13 @@ public function unmatchedBracesInFile() */ public function invalidSelectorsInFile() { - $oDoc = $this->parsedStructureForFile('invalid-selectors', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('invalid-selectors', Settings::create()->withMultibyteSupport(true)); $sExpected = '@keyframes mymove {from {top: 0px;}} #test {color: white;background: green;} #test {display: block;background: white;color: black;}'; self::assertSame($sExpected, $oDoc->render()); - $oDoc = $this->parsedStructureForFile('invalid-selectors-2', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('invalid-selectors-2', Settings::create()->withMultibyteSupport(true)); $sExpected = '@media only screen and (max-width: 1215px) {.breadcrumb {padding-left: 10px;} .super-menu > li:first-of-type {border-left-width: 0;} .super-menu > li:last-of-type {border-right-width: 0;} @@ -768,12 +768,12 @@ public function invalidSelectorsInFile() */ public function selectorEscapesInFile() { - $oDoc = $this->parsedStructureForFile('selector-escapes', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('selector-escapes', Settings::create()->withMultibyteSupport(true)); $sExpected = '#\# {color: red;} .col-sm-1\/5 {width: 20%;}'; self::assertSame($sExpected, $oDoc->render()); - $oDoc = $this->parsedStructureForFile('invalid-selectors-2', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('invalid-selectors-2', Settings::create()->withMultibyteSupport(true)); $sExpected = '@media only screen and (max-width: 1215px) {.breadcrumb {padding-left: 10px;} .super-menu > li:first-of-type {border-left-width: 0;} .super-menu > li:last-of-type {border-right-width: 0;} @@ -788,7 +788,7 @@ public function selectorEscapesInFile() */ public function identifierEscapesInFile() { - $oDoc = $this->parsedStructureForFile('identifier-escapes', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('identifier-escapes', Settings::create()->withMultibyteSupport(true)); $sExpected = 'div {font: 14px Font Awesome\ 5 Pro;font: 14px Font Awesome\} 5 Pro;' . 'font: 14px Font Awesome\; 5 Pro;f\;ont: 14px Font Awesome\; 5 Pro;}'; self::assertSame($sExpected, $oDoc->render()); @@ -799,7 +799,7 @@ public function identifierEscapesInFile() */ public function selectorIgnoresInFile() { - $oDoc = $this->parsedStructureForFile('selector-ignores', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('selector-ignores', Settings::create()->withMultibyteSupport(true)); $sExpected = '.some[selectors-may=\'contain-a-{\'] {}' . "\n" . '.this-selector .valid {width: 100px;}' @@ -813,7 +813,7 @@ public function selectorIgnoresInFile() */ public function keyframeSelectors() { - $oDoc = $this->parsedStructureForFile( + $oDoc = self::parsedStructureForFile( 'keyframe-selector-validation', Settings::create()->withMultibyteSupport(true) ); @@ -832,7 +832,7 @@ public function keyframeSelectors() */ public function lineNameFailure() { - $this->parsedStructureForFile('-empty-grid-linename', Settings::create()->withLenientParsing(false)); + self::parsedStructureForFile('-empty-grid-linename', Settings::create()->withLenientParsing(false)); } /** @@ -842,7 +842,7 @@ public function lineNameFailure() */ public function calcFailure() { - $this->parsedStructureForFile('-calc-no-space-around-minus', Settings::create()->withLenientParsing(false)); + self::parsedStructureForFile('-calc-no-space-around-minus', Settings::create()->withLenientParsing(false)); } /** @@ -850,7 +850,7 @@ public function calcFailure() */ public function urlInFileMbOff() { - $oDoc = $this->parsedStructureForFile('url', Settings::create()->withMultibyteSupport(false)); + $oDoc = self::parsedStructureForFile('url', Settings::create()->withMultibyteSupport(false)); $sExpected = 'body {background: #fff url("https://somesite.com/images/someimage.gif") repeat top center;}' . "\n" . 'body {background-url: url("https://somesite.com/images/someimage.gif");}'; @@ -862,7 +862,7 @@ public function urlInFileMbOff() */ public function emptyFile() { - $oDoc = $this->parsedStructureForFile('-empty', Settings::create()->withMultibyteSupport(true)); + $oDoc = self::parsedStructureForFile('-empty', Settings::create()->withMultibyteSupport(true)); $sExpected = ''; self::assertSame($sExpected, $oDoc->render()); } @@ -872,7 +872,7 @@ public function emptyFile() */ public function emptyFileMbOff() { - $oDoc = $this->parsedStructureForFile('-empty', Settings::create()->withMultibyteSupport(false)); + $oDoc = self::parsedStructureForFile('-empty', Settings::create()->withMultibyteSupport(false)); $sExpected = ''; self::assertSame($sExpected, $oDoc->render()); } @@ -882,7 +882,7 @@ public function emptyFileMbOff() */ public function charsetLenient1() { - $oDoc = $this->parsedStructureForFile('-charset-after-rule', Settings::create()->withLenientParsing(true)); + $oDoc = self::parsedStructureForFile('-charset-after-rule', Settings::create()->withLenientParsing(true)); $sExpected = '#id {prop: var(--val);}'; self::assertSame($sExpected, $oDoc->render()); } @@ -892,7 +892,7 @@ public function charsetLenient1() */ public function charsetLenient2() { - $oDoc = $this->parsedStructureForFile('-charset-in-block', Settings::create()->withLenientParsing(true)); + $oDoc = self::parsedStructureForFile('-charset-in-block', Settings::create()->withLenientParsing(true)); $sExpected = '@media print {}'; self::assertSame($sExpected, $oDoc->render()); } @@ -902,7 +902,7 @@ public function charsetLenient2() */ public function trailingWhitespace() { - $oDoc = $this->parsedStructureForFile('trailing-whitespace', Settings::create()->withLenientParsing(false)); + $oDoc = self::parsedStructureForFile('trailing-whitespace', Settings::create()->withLenientParsing(false)); $sExpected = 'div {width: 200px;}'; self::assertSame($sExpected, $oDoc->render()); } @@ -914,7 +914,7 @@ public function trailingWhitespace() */ public function charsetFailure1() { - $this->parsedStructureForFile('-charset-after-rule', Settings::create()->withLenientParsing(false)); + self::parsedStructureForFile('-charset-after-rule', Settings::create()->withLenientParsing(false)); } /** @@ -924,7 +924,7 @@ public function charsetFailure1() */ public function charsetFailure2() { - $this->parsedStructureForFile('-charset-in-block', Settings::create()->withLenientParsing(false)); + self::parsedStructureForFile('-charset-in-block', Settings::create()->withLenientParsing(false)); } /** @@ -934,7 +934,7 @@ public function charsetFailure2() */ public function unopenedClosingBracketFailure() { - $this->parsedStructureForFile('-unopened-close-brackets', Settings::create()->withLenientParsing(false)); + self::parsedStructureForFile('-unopened-close-brackets', Settings::create()->withLenientParsing(false)); } /** @@ -947,7 +947,7 @@ public function unopenedClosingBracketFailure() */ public function missingPropertyValueStrict() { - $this->parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(false)); + self::parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(false)); } /** @@ -959,7 +959,7 @@ public function missingPropertyValueStrict() */ public function missingPropertyValueLenient() { - $parsed = $this->parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(true)); + $parsed = self::parsedStructureForFile('missing-property-value', Settings::create()->withLenientParsing(true)); $rulesets = $parsed->getAllRuleSets(); self::assertCount(1, $rulesets); $block = $rulesets[0]; @@ -980,7 +980,7 @@ public function missingPropertyValueLenient() * * @return Document parsed document */ - private function parsedStructureForFile($sFileName, $oSettings = null) + public static function parsedStructureForFile($sFileName, $oSettings = null) { $sFile = __DIR__ . "/fixtures/$sFileName.css"; $oParser = new Parser(file_get_contents($sFile), $oSettings); @@ -994,7 +994,7 @@ private function parsedStructureForFile($sFileName, $oSettings = null) */ public function lineNumbersParsing() { - $oDoc = $this->parsedStructureForFile('line-numbers'); + $oDoc = self::parsedStructureForFile('line-numbers'); // array key is the expected line number $aExpected = [ 1 => [Charset::class], @@ -1069,7 +1069,7 @@ public function unexpectedTokenExceptionLineNo() public function ieHacksStrictParsing() { // We can't strictly parse IE hacks. - $this->parsedStructureForFile('ie-hacks', Settings::create()->beStrict()); + self::parsedStructureForFile('ie-hacks', Settings::create()->beStrict()); } /** @@ -1077,7 +1077,7 @@ public function ieHacksStrictParsing() */ public function ieHacksParsing() { - $oDoc = $this->parsedStructureForFile('ie-hacks', Settings::create()->withLenientParsing(true)); + $oDoc = self::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઼";}'; self::assertSame($sExpected, $oDoc->render()); @@ -1090,7 +1090,7 @@ public function ieHacksParsing() */ public function commentExtracting() { - $oDoc = $this->parsedStructureForFile('comments'); + $oDoc = self::parsedStructureForFile('comments'); $aNodes = $oDoc->getContents(); // Import property. @@ -1164,7 +1164,7 @@ public function topLevelCommentExtracting() */ public function microsoftFilterStrictParsing() { - $oDoc = $this->parsedStructureForFile('ms-filter', Settings::create()->beStrict()); + $oDoc = self::parsedStructureForFile('ms-filter', Settings::create()->beStrict()); } /** @@ -1172,7 +1172,7 @@ public function microsoftFilterStrictParsing() */ public function microsoftFilterParsing() { - $oDoc = $this->parsedStructureForFile('ms-filter'); + $oDoc = self::parsedStructureForFile('ms-filter'); $sExpected = '.test {filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#80000000",' . 'endColorstr="#00000000",GradientType=1);}'; self::assertSame($sExpected, $oDoc->render()); @@ -1183,7 +1183,7 @@ public function microsoftFilterParsing() */ public function largeSizeValuesInFile() { - $oDoc = $this->parsedStructureForFile('large-z-index', Settings::create()->withMultibyteSupport(false)); + $oDoc = self::parsedStructureForFile('large-z-index', Settings::create()->withMultibyteSupport(false)); $sExpected = '.overlay {z-index: 10000000000000000000000;}'; self::assertSame($sExpected, $oDoc->render()); } @@ -1193,7 +1193,7 @@ public function largeSizeValuesInFile() */ public function lonelyImport() { - $oDoc = $this->parsedStructureForFile('lonely-import'); + $oDoc = self::parsedStructureForFile('lonely-import'); $sExpected = "@import url(\"example.css\") only screen and (max-width: 600px);"; self::assertSame($sExpected, $oDoc->render()); } From afabc04f124de0c9e3807c69489762ab66c99469 Mon Sep 17 00:00:00 2001 From: Raphael Schweikert Date: Tue, 28 Dec 2021 16:49:18 +0100 Subject: [PATCH 6/7] Make Charset actually return/take string, not CSSString as documented --- src/CSSList/CSSList.php | 2 +- src/Property/Charset.php | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 6bfb1dc2..9d4a5352 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -128,7 +128,7 @@ private static function parseListItem(ParserState $oParserState, CSSList $oList) $oParserState->currentLine() ); } - $oParserState->setCharset($oAtRule->getCharset()->getString()); + $oParserState->setCharset($oAtRule->getCharset()); } return $oAtRule; } elseif ($oParserState->comes('}')) { diff --git a/src/Property/Charset.php b/src/Property/Charset.php index 3ee0c3d0..f09ab1a5 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -4,6 +4,7 @@ use Sabberworm\CSS\Comment\Comment; use Sabberworm\CSS\OutputFormat; +use Sabberworm\CSS\Value\CSSString; /** * Class representing an `@charset` rule. @@ -16,9 +17,9 @@ class Charset implements AtRule { /** - * @var string + * @var CSSString */ - private $sCharset; + private $oCharset; /** * @var int @@ -31,12 +32,12 @@ class Charset implements AtRule protected $aComments; /** - * @param string $sCharset + * @param CSSString $oCharset * @param int $iLineNo */ - public function __construct($sCharset, $iLineNo = 0) + public function __construct(CSSString $oCharset, $iLineNo = 0) { - $this->sCharset = $sCharset; + $this->oCharset = $oCharset; $this->iLineNo = $iLineNo; $this->aComments = []; } @@ -50,13 +51,14 @@ public function getLineNo() } /** - * @param string $sCharset + * @param string|CSSString $oCharset * * @return void */ public function setCharset($sCharset) { - $this->sCharset = $sCharset; + $sCharset = $sCharset instanceof CSSString ? $sCharset : new CSSString($sCharset); + $this->oCharset = $sCharset; } /** @@ -64,7 +66,7 @@ public function setCharset($sCharset) */ public function getCharset() { - return $this->sCharset; + return $this->oCharset->getString(); } /** @@ -96,7 +98,7 @@ public function atRuleName() */ public function atRuleArgs() { - return $this->sCharset; + return $this->oCharset; } /** From d82e40851558041c1d2911427df7cc7bac4a5bd1 Mon Sep 17 00:00:00 2001 From: Raphael Schweikert Date: Tue, 28 Dec 2021 17:25:25 +0100 Subject: [PATCH 7/7] Make rendering comments an option in OutputFormat --- src/CSSList/AtRuleBlockList.php | 5 +- src/CSSList/CSSList.php | 30 ++++------- src/CSSList/Document.php | 2 +- src/CSSList/KeyFrame.php | 5 +- src/OutputFormat.php | 22 +++++++-- src/OutputFormatter.php | 23 +++++++++ src/Parsing/ParserState.php | 8 +-- src/Property/Charset.php | 2 +- src/Property/Import.php | 2 +- src/Rule/Rule.php | 4 +- src/RuleSet/AtRuleSet.php | 5 +- src/RuleSet/DeclarationBlock.php | 5 +- src/RuleSet/RuleSet.php | 22 +++------ tests/Comment/CommentTest.php | 85 ++++++++++++++++++++++++++++++++ tests/ParserTest.php | 44 +++++------------ tests/fixtures/comments.css | 3 +- 16 files changed, 176 insertions(+), 91 deletions(-) create mode 100644 tests/Comment/CommentTest.php diff --git a/src/CSSList/AtRuleBlockList.php b/src/CSSList/AtRuleBlockList.php index 218adb9a..598fefc1 100644 --- a/src/CSSList/AtRuleBlockList.php +++ b/src/CSSList/AtRuleBlockList.php @@ -61,13 +61,14 @@ public function __toString() */ public function render(OutputFormat $oOutputFormat) { + $sResult = $oOutputFormat->comments($this); + $sResult .= $oOutputFormat->sBeforeAtRuleBlock; $sArgs = $this->sArgs; if ($sArgs) { $sArgs = ' ' . $sArgs; } - $sResult = $oOutputFormat->sBeforeAtRuleBlock; $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= parent::render($oOutputFormat); + $sResult .= $this->renderListContents($oOutputFormat); $sResult .= '}'; $sResult .= $oOutputFormat->sAfterAtRuleBlock; return $sResult; diff --git a/src/CSSList/CSSList.php b/src/CSSList/CSSList.php index 9d4a5352..f21d7f75 100644 --- a/src/CSSList/CSSList.php +++ b/src/CSSList/CSSList.php @@ -69,11 +69,9 @@ public static function parseList(ParserState $oParserState, CSSList $oList) $oParserState = new ParserState($oParserState, Settings::create()); } $bLenientParsing = $oParserState->getSettings()->bLenientParsing; - $comments = []; + $aComments = []; while (!$oParserState->isEnd()) { - if (empty($comments)) { - $comments = $oParserState->consumeWhiteSpace(); - } + $aComments = array_merge($aComments, $oParserState->consumeWhiteSpace()); $oListItem = null; if ($bLenientParsing) { try { @@ -89,11 +87,12 @@ public static function parseList(ParserState $oParserState, CSSList $oList) return; } if ($oListItem) { - $oListItem->setComments($comments); + $oListItem->addComments($aComments); $oList->append($oListItem); } - $comments = $oParserState->consumeWhiteSpace(); + $aComments = $oParserState->consumeWhiteSpace(); } + $oList->addComments($aComments); if (!$bIsRoot && !$bLenientParsing) { throw new SourceException("Unexpected end of document", $oParserState->currentLine()); } @@ -175,10 +174,10 @@ private static function parseAtRule(ParserState $oParserState) $oParserState->consumeUntil([';', ParserState::EOF], true, true); return new Import($oLocation, $sMediaQuery ?: null, $iIdentifierLineNum); } elseif ($sIdentifier === 'charset') { - $sCharset = CSSString::parse($oParserState); + $oCharsetString = CSSString::parse($oParserState); $oParserState->consumeWhiteSpace(); $oParserState->consumeUntil([';', ParserState::EOF], true, true); - return new Charset($sCharset, $iIdentifierLineNum); + return new Charset($oCharsetString, $iIdentifierLineNum); } elseif (self::identifierIs($sIdentifier, 'keyframes')) { $oResult = new KeyFrame($iIdentifierLineNum); $oResult->setVendorKeyFrame($sIdentifier); @@ -405,7 +404,7 @@ public function __toString() /** * @return string */ - public function render(OutputFormat $oOutputFormat) + protected function renderListContents(OutputFormat $oOutputFormat) { $sResult = ''; $bIsFirst = true; @@ -415,18 +414,7 @@ public function render(OutputFormat $oOutputFormat) } foreach ($this->aContents as $oContent) { $sRendered = $oOutputFormat->safely(function () use ($oNextLevel, $oContent) { - $sResult = ''; - $aComments = $oContent->getComments(); - $c = count($aComments); - - foreach ($aComments as $i => $oComment) { - $sResult .= $oComment->render($oNextLevel); - $sResult .= $oNextLevel->spaceAfterBlocks(); - if ($c - 1 !== $i) { - $sResult .= $oNextLevel->spaceAfterBlocks(); - } - } - return $sResult . $oContent->render($oNextLevel); + return $oContent->render($oNextLevel); }); if ($sRendered === null) { continue; diff --git a/src/CSSList/Document.php b/src/CSSList/Document.php index 91ab2c6b..b2f50434 100644 --- a/src/CSSList/Document.php +++ b/src/CSSList/Document.php @@ -159,7 +159,7 @@ public function render(OutputFormat $oOutputFormat = null) if ($oOutputFormat === null) { $oOutputFormat = new OutputFormat(); } - return parent::render($oOutputFormat); + return $oOutputFormat->comments($this) . $this->renderListContents($oOutputFormat); } /** diff --git a/src/CSSList/KeyFrame.php b/src/CSSList/KeyFrame.php index d9420e9c..caef7b3d 100644 --- a/src/CSSList/KeyFrame.php +++ b/src/CSSList/KeyFrame.php @@ -72,8 +72,9 @@ public function __toString() */ public function render(OutputFormat $oOutputFormat) { - $sResult = "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= parent::render($oOutputFormat); + $sResult = $oOutputFormat->comments($this); + $sResult .= "@{$this->vendorKeyFrame} {$this->animationName}{$oOutputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= $this->renderListContents($oOutputFormat); $sResult .= '}'; return $sResult; } diff --git a/src/OutputFormat.php b/src/OutputFormat.php index 595d3064..96f26e14 100644 --- a/src/OutputFormat.php +++ b/src/OutputFormat.php @@ -143,6 +143,13 @@ class OutputFormat */ public $bIgnoreExceptions = false; + /** + * Render comments for lists and RuleSets + * + * @var bool + */ + public $bRenderComments = false; + /** * @var OutputFormatter|null */ @@ -314,8 +321,12 @@ public static function create() public static function createCompact() { $format = self::create(); - $format->set('Space*Rules', "")->set('Space*Blocks', "")->setSpaceAfterRuleName('') - ->setSpaceBeforeOpeningBrace('')->setSpaceAfterSelectorSeparator(''); + $format->set('Space*Rules', "") + ->set('Space*Blocks', "") + ->setSpaceAfterRuleName('') + ->setSpaceBeforeOpeningBrace('') + ->setSpaceAfterSelectorSeparator('') + ->setRenderComments(false); return $format; } @@ -327,8 +338,11 @@ public static function createCompact() public static function createPretty() { $format = self::create(); - $format->set('Space*Rules', "\n")->set('Space*Blocks', "\n") - ->setSpaceBetweenBlocks("\n\n")->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']); + $format->set('Space*Rules', "\n") + ->set('Space*Blocks', "\n") + ->setSpaceBetweenBlocks("\n\n") + ->set('SpaceAfterListArgumentSeparator', ['default' => '', ',' => ' ']) + ->setRenderComments(true); return $format; } } diff --git a/src/OutputFormatter.php b/src/OutputFormatter.php index 535feca7..7418494c 100644 --- a/src/OutputFormatter.php +++ b/src/OutputFormatter.php @@ -2,6 +2,7 @@ namespace Sabberworm\CSS; +use Sabberworm\CSS\Comment\Commentable; use Sabberworm\CSS\Parsing\OutputException; class OutputFormatter @@ -211,6 +212,28 @@ public function removeLastSemicolon($sString) return implode(';', $sString); } + /** + * + * @param array $aComments + * @return string + */ + public function comments(Commentable $oCommentable) + { + if (!$this->oFormat->bRenderComments) { + return ''; + } + + $sResult = ''; + $aComments = $oCommentable->getComments(); + $iLastCommentIndex = count($aComments) - 1; + + foreach ($aComments as $i => $oComment) { + $sResult .= $oComment->render($this->oFormat); + $sResult .= $i === $iLastCommentIndex ? $this->spaceAfterBlocks() : $this->spaceBetweenBlocks(); + } + return $sResult; + } + /** * @param string $sSpaceString * diff --git a/src/Parsing/ParserState.php b/src/Parsing/ParserState.php index e7d85ee0..df18f557 100644 --- a/src/Parsing/ParserState.php +++ b/src/Parsing/ParserState.php @@ -204,7 +204,7 @@ public function parseCharacter($bIsForIdentifier) */ public function consumeWhiteSpace() { - $comments = []; + $aComments = []; do { while (preg_match('/\\s/isSu', $this->peek()) === 1) { $this->consume(1); @@ -214,16 +214,16 @@ public function consumeWhiteSpace() $oComment = $this->consumeComment(); } catch (UnexpectedEOFException $e) { $this->iCurrentPosition = $this->iLength; - return; + return $aComments; } } else { $oComment = $this->consumeComment(); } if ($oComment !== false) { - $comments[] = $oComment; + $aComments[] = $oComment; } } while ($oComment !== false); - return $comments; + return $aComments; } /** diff --git a/src/Property/Charset.php b/src/Property/Charset.php index f09ab1a5..26e1b250 100644 --- a/src/Property/Charset.php +++ b/src/Property/Charset.php @@ -82,7 +82,7 @@ public function __toString() */ public function render(OutputFormat $oOutputFormat) { - return "@charset {$this->sCharset->render($oOutputFormat)};"; + return "{$oOutputFormat->comments($this)}@charset {$this->oCharset->render($oOutputFormat)};"; } /** diff --git a/src/Property/Import.php b/src/Property/Import.php index a2253016..43d9f0d2 100644 --- a/src/Property/Import.php +++ b/src/Property/Import.php @@ -83,7 +83,7 @@ public function __toString() */ public function render(OutputFormat $oOutputFormat) { - return "@import " . $this->oLocation->render($oOutputFormat) + return $oOutputFormat->comments($this) . "@import " . $this->oLocation->render($oOutputFormat) . ($this->sMediaQuery === null ? '' : ' ' . $this->sMediaQuery) . ';'; } diff --git a/src/Rule/Rule.php b/src/Rule/Rule.php index c1ea6df7..ac60aa4c 100644 --- a/src/Rule/Rule.php +++ b/src/Rule/Rule.php @@ -346,8 +346,8 @@ public function __toString() */ public function render(OutputFormat $oOutputFormat) { - $sResult = "{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}"; - if ($this->mValue instanceof Value) { //Can also be a ValueList + $sResult = "{$oOutputFormat->comments($this)}{$this->sRule}:{$oOutputFormat->spaceAfterRuleName()}"; + if ($this->mValue instanceof Value) { // Can also be a ValueList $sResult .= $this->mValue->render($oOutputFormat); } else { $sResult .= $this->mValue; diff --git a/src/RuleSet/AtRuleSet.php b/src/RuleSet/AtRuleSet.php index 88bc5bd3..50958ef2 100644 --- a/src/RuleSet/AtRuleSet.php +++ b/src/RuleSet/AtRuleSet.php @@ -61,12 +61,13 @@ public function __toString() */ public function render(OutputFormat $oOutputFormat) { + $sResult = $oOutputFormat->comments($this); $sArgs = $this->sArgs; if ($sArgs) { $sArgs = ' ' . $sArgs; } - $sResult = "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; - $sResult .= parent::render($oOutputFormat); + $sResult .= "@{$this->sType}$sArgs{$oOutputFormat->spaceBeforeOpeningBrace()}{"; + $sResult .= $this->renderRules($oOutputFormat); $sResult .= '}'; return $sResult; } diff --git a/src/RuleSet/DeclarationBlock.php b/src/RuleSet/DeclarationBlock.php index c27cdd4c..3bebda67 100644 --- a/src/RuleSet/DeclarationBlock.php +++ b/src/RuleSet/DeclarationBlock.php @@ -812,18 +812,19 @@ public function __toString() */ public function render(OutputFormat $oOutputFormat) { + $sResult = $oOutputFormat->comments($this); if (count($this->aSelectors) === 0) { // If all the selectors have been removed, this declaration block becomes invalid throw new OutputException("Attempt to print declaration block with missing selector", $this->iLineNo); } - $sResult = $oOutputFormat->sBeforeDeclarationBlock; + $sResult .= $oOutputFormat->sBeforeDeclarationBlock; $sResult .= $oOutputFormat->implode( $oOutputFormat->spaceBeforeSelectorSeparator() . ',' . $oOutputFormat->spaceAfterSelectorSeparator(), $this->aSelectors ); $sResult .= $oOutputFormat->sAfterDeclarationBlockSelectors; $sResult .= $oOutputFormat->spaceBeforeOpeningBrace() . '{'; - $sResult .= parent::render($oOutputFormat); + $sResult .= $this->renderRules($oOutputFormat); $sResult .= '}'; $sResult .= $oOutputFormat->sAfterDeclarationBlock; return $sResult; diff --git a/src/RuleSet/RuleSet.php b/src/RuleSet/RuleSet.php index 0587296b..e7f4b82d 100644 --- a/src/RuleSet/RuleSet.php +++ b/src/RuleSet/RuleSet.php @@ -266,34 +266,24 @@ public function __toString() /** * @return string */ - public function render(OutputFormat $oOutputFormat) + protected function renderRules(OutputFormat $oOutputFormat) { $sResult = ''; $bIsFirst = true; + $oNextLevel = $oOutputFormat->nextLevel(); foreach ($this->aRules as $aRules) { foreach ($aRules as $oRule) { - $sRendered = $oOutputFormat->safely(function () use ($oRule, $oOutputFormat) { - $sResult = ''; - $aComments = $oRule->getComments(); - $c = count($aComments); - - foreach ($aComments as $i => $oComment) { - $sResult .= $oComment->render($oOutputFormat); - $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); - if ($c - 1 !== $i) { - $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); - } - } - return $sResult . $oRule->render($oOutputFormat->nextLevel()); + $sRendered = $oNextLevel->safely(function () use ($oRule, $oNextLevel) { + return $oRule->render($oNextLevel); }); if ($sRendered === null) { continue; } if ($bIsFirst) { $bIsFirst = false; - $sResult .= $oOutputFormat->nextLevel()->spaceBeforeRules(); + $sResult .= $oNextLevel->spaceBeforeRules(); } else { - $sResult .= $oOutputFormat->nextLevel()->spaceBetweenRules(); + $sResult .= $oNextLevel->spaceBetweenRules(); } $sResult .= $sRendered; } diff --git a/tests/Comment/CommentTest.php b/tests/Comment/CommentTest.php new file mode 100644 index 00000000..d4d67b09 --- /dev/null +++ b/tests/Comment/CommentTest.php @@ -0,0 +1,85 @@ +render(OutputFormat::createPretty())); + self::assertSame( + '/** Number 11 **//**' . "\n" + . ' * Comments' . "\n" + . ' *//* Hell */@import url("some/url.css") screen;' + . '/* Number 4 *//* Number 5 */.foo,#bar{' + . '/* Number 6 */background-color:#000;}@media screen{' + . '/** Number 10 **/#foo.bar{/** Number 10b **/position:absolute;}}', + $oCss->render(OutputFormat::createCompact()->setRenderComments(true)) + ); + } + + /** + * @test + */ + public function stripCommentsFromOutput() + { + $oCss = TestsParserTest::parsedStructureForFile('comments'); + self::assertSame(' +@import url("some/url.css") screen; + +.foo, #bar { + background-color: #000; +} + +@media screen { + #foo.bar { + position: absolute; + } +} +', $oCss->render(OutputFormat::createPretty()->setRenderComments(false))); + self::assertSame( + '@import url("some/url.css") screen;' + . '.foo,#bar{background-color:#000;}' + . '@media screen{#foo.bar{position:absolute;}}', + $oCss->render(OutputFormat::createCompact()) + ); + } +} diff --git a/tests/ParserTest.php b/tests/ParserTest.php index fc4e6897..c7023cbc 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Sabberworm\CSS\CSSList\Document; use Sabberworm\CSS\CSSList\KeyFrame; +use Sabberworm\CSS\OutputFormat; use Sabberworm\CSS\Parser; use Sabberworm\CSS\Parsing\UnexpectedTokenException; use Sabberworm\CSS\Property\AtRule; @@ -309,17 +310,7 @@ public function manipulation() . "\n" . ' domain(mozilla.org),' . "\n" - . ' regexp("https:.*") {/* CSS rules here apply to:' - . "\n" - . ' + The page "https://www.w3.org/".' - . "\n" - . ' + Any page whose URL begins with "https://www.w3.org/Style/"' - . "\n" - . ' + Any page whose URL\'s host is "mozilla.org" or ends with' - . "\n" - . ' ".mozilla.org"' - . "\n" - . ' + Any page whose URL starts with "https:" *//* make the above-mentioned pages really ugly */body {color: purple;background: yellow;}}' + . ' regexp("https:.*") {body {color: purple;background: yellow;}}' . "\n" . '@media screen and (orientation: landscape) {@-ms-viewport {width: 1024px;height: 768px;}}' . "\n" @@ -358,22 +349,12 @@ public function manipulation() . "\n" . ' domain(mozilla.org),' . "\n" - . ' regexp("https:.*") {/* CSS rules here apply to:' - . "\n" - . ' + The page "https://www.w3.org/".' - . "\n" - . ' + Any page whose URL begins with "https://www.w3.org/Style/"' - . "\n" - . ' + Any page whose URL\'s host is "mozilla.org" or ends with' - . "\n" - . ' ".mozilla.org"' - . "\n" - . ' + Any page whose URL starts with "https:" *//* make the above-mentioned pages really ugly */#my_id body {color: purple;background: yellow;}}' + . ' regexp("https:.*") {#my_id body {color: purple;background: yellow;}}' . "\n" . '@media screen and (orientation: landscape) {@-ms-viewport {width: 1024px;height: 768px;}}' . "\n" . '@region-style #intro {#my_id p {color: blue;}}', - $oDoc->render() + $oDoc->render(OutputFormat::create()->setRenderComments(false)) ); $oDoc = self::parsedStructureForFile('values'); @@ -552,9 +533,9 @@ public function createShorthands() public function namespaces() { $oDoc = self::parsedStructureForFile('namespaces'); - $sExpected = '/* From the spec at https://www.w3.org/TR/css3-namespace/ */@namespace toto "http://toto.example.org"; + $sExpected = '@namespace toto "http://toto.example.org"; @namespace "http://example.com/foo"; -/* From an introduction at https://www.blooberry.com/indexdot/css/syntax/atrules/namespace.htm */@namespace foo url("http://www.example.com/"); +@namespace foo url("http://www.example.com/"); @namespace foo url("http://www.example.com/"); foo|test {gaga: 1;} |test {gaga: 2;}'; @@ -647,11 +628,9 @@ public function comments() { $oDoc = self::parsedStructureForFile('comments'); $sExpected = <<render()); } @@ -1095,8 +1074,9 @@ public function commentExtracting() // Import property. $importComments = $aNodes[0]->getComments(); - self::assertCount(1, $importComments); - self::assertSame("*\n * Comments Hell.\n ", $importComments[0]->getComment()); + self::assertCount(2, $importComments); + self::assertSame("*\n * Comments\n ", $importComments[0]->getComment()); + self::assertSame(" Hell ", $importComments[1]->getComment()); // Declaration block. $fooBarBlock = $aNodes[1]; diff --git a/tests/fixtures/comments.css b/tests/fixtures/comments.css index ea136bcc..6e443b69 100644 --- a/tests/fixtures/comments.css +++ b/tests/fixtures/comments.css @@ -1,6 +1,7 @@ /** - * Comments Hell. + * Comments */ + /* Hell */ @import /* Number 1 */"some/url.css"/* Number 2 */ screen/* Number 3 */; .foo, /* Number 4 */ #bar/* Number 5 */ {