From a929e7af12b4e447d22db1ecb9493d20e22fa3d2 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Mon, 23 Mar 2026 09:10:21 +0100 Subject: [PATCH 01/11] WIP Get started with supporting nested rules phax/ph-css#94 Implements: * Nested style rules (https://drafts.csswg.org/css-nesting-1/#syntax) * The nesting selector (https://drafts.csswg.org/css-nesting-1/#nest-selector) TODO / decisions: * Add support for nested group rules (https://drafts.csswg.org/css-nesting-1/#conditionals) * How should the domain model be extended? * (a) Do we want to preserve the order of interspersed declarations and nested rules? * (b) Does it need to be API compatible? Or can there be minor changes? * (c) Do we want to preserve the distinction between style rule's "style" and "cssRules" attribute from the CSSOM (https://drafts.csswg.org/css-nesting-1/#nested-declarations)? * The easiest way would be to add a list of style rules, a list of media rules etc. to the "CSSStyleRule" class, which fulfills (b) but not (a) and (c). * A couple of test failures related to parsing errors. Since rules can now be nested, edge cases how invalid CSS is handled differ slightly. * Make sure nested rules appear in the output of the getCSSAsString method --- .gitignore | 1 + .../com/helger/css/decl/CSSDeclaration.java | 8 +-- .../css/decl/CSSSelectorSimpleMember.java | 12 +++- .../com/helger/css/decl/CSSStyleRule.java | 72 +++++++++++++++++++ .../css/handler/CSSNodeToDomainObject.java | 26 ++++++- .../com/helger/css/handler/ECSSNodeType.java | 1 + ph-css/src/main/jjtree/ParserCSS30.jjt | 20 ++++-- .../com/helger/css/decl/CSSStyleRuleTest.java | 7 ++ 8 files changed, 133 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 33adcb10..110e61ab 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ target/ zz *.iml +.idea \ No newline at end of file diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSDeclaration.java b/ph-css/src/main/java/com/helger/css/decl/CSSDeclaration.java index 13e3ee3f..09dd3120 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSDeclaration.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSDeclaration.java @@ -35,7 +35,7 @@ import com.helger.css.property.ECSSProperty; /** - * Represents a single element in a CSS style rule. (eg. color:red; + * Represents a single element in a CSS style rule. (e.g. color:red; * or background:uri(a.gif) !important;)
* Instances of this class are mutable since 3.7.4. * @@ -99,7 +99,7 @@ public final String getProperty () @NonNull private static String _unifyProperty (@NonNull final String sProperty) { - // CSS variables are case sensitive (see issue 63) + // CSS variables are case-sensitive (see issue 63) if (sProperty.startsWith ("--")) return sProperty; return sProperty.toLowerCase (Locale.ROOT); @@ -107,7 +107,7 @@ private static String _unifyProperty (@NonNull final String sProperty) /** * Check if this declaration has the specified property. The comparison is - * case insensitive! + * case-insensitive! * * @param sProperty * The property to check. May not be null. @@ -123,7 +123,7 @@ public final boolean hasProperty (@NonNull final String sProperty) /** * Check if this declaration has the specified property. The comparison is - * case insensitive! + * case-insensitive! * * @param eProperty * The property to check. May not be null. diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorSimpleMember.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorSimpleMember.java index 0fdcc4f4..57ee4bbc 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorSimpleMember.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorSimpleMember.java @@ -55,11 +55,11 @@ public String getValue () } /** - * @return true if it is no hash, no class and no pseudo selector + * @return true if it is no hash, no class, no pseudo, and no nesting selector */ public boolean isElementName () { - return !isHash () && !isClass () && !isPseudo (); + return !isHash () && !isClass () && !isPseudo () && !isNesting(); } /** @@ -86,6 +86,14 @@ public boolean isPseudo () return m_sValue.charAt (0) == ':'; } + /** + * @return true if it is a nesting selector + */ + public boolean isNesting () + { + return m_sValue.charAt (0) == '&'; + } + @NonNull @Nonempty public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java index 988bdbaf..4c2cdfe9 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java @@ -47,6 +47,7 @@ public class CSSStyleRule implements ICSSTopLevelRule, IHasCSSDeclarations m_aSelectors = new CommonsArrayList <> (); private final CSSDeclarationContainer m_aDeclarations = new CSSDeclarationContainer (); + private final ICommonsList m_aRules = new CommonsArrayList <> (); private CSSSourceLocation m_aSourceLocation; public CSSStyleRule () @@ -139,6 +140,77 @@ public ICommonsList getAllSelectors () return m_aSelectors.getClone (); } + public boolean hasRules () + { + return m_aRules.isNotEmpty (); + } + + @Nonnegative + public int getRuleCount () + { + return m_aRules.size (); + } + + @NonNull + public CSSStyleRule addRule (@NonNull final CSSStyleRule aRule) + { + ValueEnforcer.notNull (aRule, "Rule"); + + m_aRules.add (aRule); + return this; + } + + @NonNull + public CSSStyleRule addRule (@Nonnegative final int nIndex, @NonNull final CSSStyleRule aRule) + { + ValueEnforcer.isGE0 (nIndex, "Index"); + ValueEnforcer.notNull (aRule, "Rule"); + + if (nIndex >= getRuleCount ()) + m_aRules.add (aRule); + else + m_aRules.add (nIndex, aRule); + return this; + } + + @NonNull + public EChange removeRule (@NonNull final CSSStyleRule aRule) + { + return m_aRules.removeObject (aRule); + } + + @NonNull + public EChange removeRule (@Nonnegative final int nRuleIndex) + { + return m_aRules.removeAtIndex (nRuleIndex); + } + + /** + * Remove all rules. + * + * @return {@link EChange#CHANGED} if any rule was removed, + * {@link EChange#UNCHANGED} otherwise. Never null. + * @since 3.7.3 + */ + @NonNull + public EChange removeAllRules () + { + return m_aRules.removeAll (); + } + + @Nullable + public CSSStyleRule getRuleAtIndex (@Nonnegative final int nRuleIndex) + { + return m_aRules.getAtIndex (nRuleIndex); + } + + @NonNull + @ReturnsMutableCopy + public ICommonsList getAllRules () + { + return m_aRules.getClone (); + } + @NonNull public CSSStyleRule addDeclaration (@NonNull final CSSDeclaration aDeclaration) { diff --git a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java index d34adccc..2217ce64 100644 --- a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java +++ b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java @@ -232,7 +232,8 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) if (ECSSNodeType.NAMESPACEPREFIX.isNode (aNode) || ECSSNodeType.ELEMENTNAME.isNode (aNode) || ECSSNodeType.HASH.isNode (aNode) || - ECSSNodeType.CLASS.isNode (aNode)) + ECSSNodeType.CLASS.isNode (aNode) || + ECSSNodeType.NESTING.isNode (aNode)) { if (nChildCount != 0) _throwUnexpectedChildrenCount (aNode, "CSS simple selector member expected 0 children and got " + nChildCount); @@ -776,6 +777,26 @@ private void _readStyleDeclarationList (@NonNull final CSSNode aNode, } } + private void _readStyleDeclarationListRules (@NonNull final CSSNode aNode, + @NonNull final Consumer aConsumer) + { + _expectNodeType (aNode, ECSSNodeType.STYLEDECLARATIONLIST); + // Read all contained declarations + final int nDecls = aNode.jjtGetNumChildren (); + for (int nDecl = 0; nDecl < nDecls; ++nDecl) + { + final CSSNode aChildNode = aNode.jjtGetChild (nDecl); + if (ECSSNodeType.STYLERULE.isNode (aChildNode)) + { + final CSSStyleRule aRule = _createStyleRule (aChildNode); + if (aRule != null) + aConsumer.accept (aRule); + } + // else + // ignore ERROR_SKIP to and all "@" things + } + } + @Nullable private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode) { @@ -801,6 +822,9 @@ private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode) { // Read all contained declarations _readStyleDeclarationList (aChildNode, ret::addDeclaration); + + // Read all contained rules + _readStyleDeclarationListRules (aChildNode, ret::addRule); } else if (!ECSSNodeType.isErrorNode (aChildNode)) diff --git a/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java b/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java index 40b7591d..a2ff9254 100644 --- a/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java +++ b/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java @@ -56,6 +56,7 @@ public enum ECSSNodeType ELEMENTNAME (ParserCSS30TreeConstants.JJTELEMENTNAME), HASH (ParserCSS30TreeConstants.JJTIDSELECTOR), CLASS (ParserCSS30TreeConstants.JJTCLASS), + NESTING (ParserCSS30TreeConstants.JJTNESTING), PSEUDO (ParserCSS30TreeConstants.JJTPSEUDOCLASSSELECTOR), HOST (ParserCSS30TreeConstants.JJTHOST), HOSTCONTEXT (ParserCSS30TreeConstants.JJTHOSTCONTEXT), diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index a15a6714..b9a23d46 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -202,6 +202,7 @@ TOKEN : | < GREATER: ">" > | < TILDE: "~" > | < DOLLAR: "$" > +| < AMPERSAND: "&" > | < HASH: "#" > | < INCLUDES: "~=" > | < DASHMATCH: "|=" > @@ -911,6 +912,11 @@ void typeSelector() #void : {} elementName() } +void nestingSelector() #Nesting : {} +{ + { jjtThis.setText ("&"); } +} + void idSelector() : {} { { jjtThis.setText (token.image); } @@ -1129,6 +1135,7 @@ void simpleSelectorSequence() #void : {} ( idSelector() | classSelector() | attributeSelector() + | nestingSelector() | pseudoClassSelector() | funcNot() )* @@ -1136,6 +1143,7 @@ void simpleSelectorSequence() #void : {} | ( idSelector() | classSelector() | attributeSelector() + | nestingSelector() | pseudoClassSelector() | funcNot() )+ @@ -1213,8 +1221,10 @@ try{ void styleDeclarationOrRule() #void : {} { - ( styleDeclaration() - | ( mediaRule() { errorUnexpectedRule ("@media", "media rule in the middle of a rule-set is not allowed!"); } + ( styleDeclaration() ( LOOKAHEAD( ()* ( | ) ) ()* | ()* ()* ) + | ()* // final semicolon from single line comment + | ( styleRule() + | mediaRule() { errorUnexpectedRule ("@media", "media rule in the middle of a rule-set is not allowed!"); } | pageRule() { errorUnexpectedRule ("@page", "page rule in the middle of a rule-set is not allowed!"); } | fontfaceRule() { errorUnexpectedRule ("@font-face", "font-face rule in the middle of a rule-set is not allowed!"); } | keyframesRule() { errorUnexpectedRule ("@keyframes", "keyframes rule in the middle of a rule-set is not allowed!"); } @@ -1234,11 +1244,7 @@ CSSNode styleDeclarationList() : {} { try { ( )* - ( styleDeclarationOrRule() )? - ( - ( )* - ( styleDeclarationOrRule() )? - )* + ( styleDeclarationOrRule() )* } catch (/*final*/ ParseException ex) { if (m_bBrowserCompliantMode) browserCompliantSkipDecl (ex); diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java index 2e0ad226..71cccc6a 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java @@ -111,4 +111,11 @@ public void testRead2 () ECSSAttributeCase.CASE_INSENSITIVE))); TestHelper.testDefaultImplementationWithEqualContentObject (aSR, aCreated); } + + @Test + public void testRead3 () + { + CSSStyleRule aSR = _parse ("div { color: red; .foobar { color: green; #id { color: red } color: white } }"); + System.out.println(aSR); + } } From 4cb3e704c7665bd0eac095df2d967dccf060e176 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Mon, 23 Mar 2026 09:26:16 +0100 Subject: [PATCH 02/11] Add basic tests for nested rules and nesting selector --- .../com/helger/css/decl/CSSSelectorTest.java | 16 ++++++++++++++++ .../com/helger/css/decl/CSSStyleRuleTest.java | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java index 58208d43..06f4c460 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java @@ -82,5 +82,21 @@ public void testRead () assertTrue (aSel.getMemberAtIndex (2) instanceof CSSSelectorSimpleMember); assertEquals ("div", aSel.getMemberAtIndex (2).getAsCSSString ()); assertEquals ("#id~div", aSel.getAsCSSString ()); + + aSel = _parse ("&.foo { color:red }"); + assertEquals (2, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals ("&", aSel.getMemberAtIndex (0).getAsCSSString ()); + assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelectorSimpleMember); + assertEquals (".foo", aSel.getMemberAtIndex (1).getAsCSSString ()); + + aSel = _parse (".foo & { color:red }"); + assertEquals (3, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals (".foo", aSel.getMemberAtIndex (0).getAsCSSString ()); + assertTrue (aSel.getMemberAtIndex (1) instanceof ECSSSelectorCombinator); + assertEquals (" ", aSel.getMemberAtIndex (1).getAsCSSString ()); + assertTrue (aSel.getMemberAtIndex (2) instanceof CSSSelectorSimpleMember); + assertEquals ("&", aSel.getMemberAtIndex (2).getAsCSSString ()); } } diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java index 71cccc6a..4dd319bd 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java @@ -115,7 +115,21 @@ public void testRead2 () @Test public void testRead3 () { - CSSStyleRule aSR = _parse ("div { color: red; .foobar { color: green; #id { color: red } color: white } }"); - System.out.println(aSR); + CSSStyleRule aSR = _parse ("div { color: red; .foobar { color: green; #id { color: blue } color: white; } }"); + assertEquals (1, aSR.getDeclarationCount ()); + assertEquals (1, aSR.getRuleCount ()); + + assertEquals ("color:red", aSR.getDeclarationAtIndex(0).getAsCSSString()); + + assertEquals (2, aSR.getRuleAtIndex (0).getDeclarationCount()); + assertEquals (1, aSR.getRuleAtIndex (0).getRuleCount()); + + assertEquals ("color:green", aSR.getRuleAtIndex (0).getDeclarationAtIndex(0).getAsCSSString()); + assertEquals ("color:white", aSR.getRuleAtIndex (0).getDeclarationAtIndex(1).getAsCSSString()); + + assertEquals (1, aSR.getRuleAtIndex (0).getRuleAtIndex(0).getDeclarationCount()); + assertEquals (0, aSR.getRuleAtIndex (0).getRuleAtIndex(0).getRuleCount()); + + assertEquals ("color:blue", aSR.getRuleAtIndex (0).getRuleAtIndex(0).getDeclarationAtIndex(0).getAsCSSString()); } } From b9bc6f5a09c1b79e11a6d04439739581bb53dcd9 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 3 Apr 2026 14:59:26 +0100 Subject: [PATCH 03/11] Use CSSNestedDeclarations to represent nested declarations, fix parsing issues, update getAsCssString Add CSSNestedDeclarations class to represent nested declarations, like the CSSOM does. CSSStyleRule now has a list of ICSSNestedRule, which includes various rules such as CSSStyleRule or CSSMediaRule. See https://drafts.csswg.org/css-nesting-1/#conditionals Update CSSNodeToDomainObject to create CSSNestedDeclarations instances where needed. Add support for parsing nested at-rules, e.g.: .foo { @media print {} } Fix parsing issue where the parser confused an element selector with a style declaration, e.g. such as in the following 2 cases: * .foo { p { color: red } } * .foo { p: value; } Add a lookahead that checks if a style declaration follows, and only consume it in that case. Otherwise, proceed to look for rules and nested declarations. Add a new parser rule for consuming a style declaration with nested elements. Not all rules support nesting, e.g. @font-face. For these rules, we do want to keep the previous behavior where it throws on nested rules. Minor change to how whitespace is handled: getAsCssString only outputs itself as a string, it does not output any leading or trailing whitespace or newlines. Space and lines around an CSS element should be handled by the container that contains the element, as it may depend on the context of that container. --- .../java/com/helger/css/ICSSWriteable.java | 9 + .../com/helger/css/ICSSWriterSettings.java | 37 +- .../css/decl/AbstractHasTopLevelRules.java | 52 +++ .../css/decl/CSSDeclarationContainer.java | 15 +- .../com/helger/css/decl/CSSFontFaceRule.java | 22 +- .../com/helger/css/decl/CSSImportRule.java | 2 +- .../com/helger/css/decl/CSSKeyframesRule.java | 31 +- .../com/helger/css/decl/CSSLayerRule.java | 127 +++++- .../com/helger/css/decl/CSSMediaRule.java | 20 +- .../com/helger/css/decl/CSSNamespaceRule.java | 2 +- .../css/decl/CSSNestedDeclarations.java | 215 +++++++++ .../java/com/helger/css/decl/CSSPageRule.java | 61 ++- .../css/decl/CSSSelectorSimpleMember.java | 2 + .../com/helger/css/decl/CSSStyleRule.java | 173 ++++++-- .../com/helger/css/decl/CSSSupportsRule.java | 18 +- .../com/helger/css/decl/CSSUnknownRule.java | 10 +- .../com/helger/css/decl/CSSViewportRule.java | 16 +- .../com/helger/css/decl/CSSWritableList.java | 8 +- .../com/helger/css/decl/ICSSNestedRule.java | 49 +++ .../com/helger/css/decl/ICSSTopLevelRule.java | 29 +- .../helger/css/decl/IHasCSSNestedRules.java | 90 ++++ .../css/handler/CSSNodeToDomainObject.java | 91 +++- .../com/helger/css/handler/ECSSNodeType.java | 1 + .../java/com/helger/css/writer/CSSWriter.java | 64 ++- .../helger/css/writer/CSSWriterSettings.java | 80 +++- ph-css/src/main/jjtree/ParserCSS30.jjt | 65 ++- .../helger/css/decl/CSSImportRuleTest.java | 4 +- .../com/helger/css/decl/CSSLayerRuleTest.java | 68 +++ .../com/helger/css/decl/CSSMediaRuleTest.java | 68 +++ .../com/helger/css/decl/CSSStyleRuleTest.java | 175 +++++++- .../helger/css/decl/CSSSupportsRuleTest.java | 41 ++ .../css/writer/CSSWriterSettingsTest.java | 30 +- .../com/helger/css/writer/CSSWriterTest.java | 416 ++++++++++++++++++ 33 files changed, 1882 insertions(+), 209 deletions(-) create mode 100644 ph-css/src/main/java/com/helger/css/decl/CSSNestedDeclarations.java create mode 100644 ph-css/src/main/java/com/helger/css/decl/ICSSNestedRule.java create mode 100644 ph-css/src/main/java/com/helger/css/decl/IHasCSSNestedRules.java create mode 100644 ph-css/src/test/java/com/helger/css/decl/CSSLayerRuleTest.java create mode 100644 ph-css/src/test/java/com/helger/css/decl/CSSMediaRuleTest.java diff --git a/ph-css/src/main/java/com/helger/css/ICSSWriteable.java b/ph-css/src/main/java/com/helger/css/ICSSWriteable.java index d50bf51e..fac59ec6 100644 --- a/ph-css/src/main/java/com/helger/css/ICSSWriteable.java +++ b/ph-css/src/main/java/com/helger/css/ICSSWriteable.java @@ -32,6 +32,9 @@ public interface ICSSWriteable * Get the contents of this object as a serialized CSS string for writing to * an output using the default writer settings. * + *

The general contract is that this method writes only the content of this object, but not any surrounding context. + * In particular, this method does not add any leading or trailing space or newlines. + * * @return The content of this object as CSS string. Never null. * @see #getAsCSSString(ICSSWriterSettings, int) * @since 6.0.0 @@ -46,6 +49,9 @@ default String getAsCSSString () * Get the contents of this object as a serialized CSS string for writing to * an output. * + *

The general contract is that this method writes only the content of this object, but not any surrounding context. + * In particular, this method does not add any leading or trailing space or newlines. + * * @param aSettings * The settings to be used to format the output. May not be * null. @@ -63,6 +69,9 @@ default String getAsCSSString (@NonNull final ICSSWriterSettings aSettings) * Get the contents of this object as a serialized CSS string for writing to * an output. * + *

The general contract is that this method writes only the content of this object, but not any surrounding context. + * In particular, this method does not add any leading or trailing space or newlines. + * * @param aSettings * The settings to be used to format the output. May not be * null. diff --git a/ph-css/src/main/java/com/helger/css/ICSSWriterSettings.java b/ph-css/src/main/java/com/helger/css/ICSSWriterSettings.java index 88e46c41..419b5752 100644 --- a/ph-css/src/main/java/com/helger/css/ICSSWriterSettings.java +++ b/ph-css/src/main/java/com/helger/css/ICSSWriterSettings.java @@ -16,6 +16,16 @@ */ package com.helger.css; +import com.helger.css.decl.CSSFontFaceRule; +import com.helger.css.decl.CSSKeyframesRule; +import com.helger.css.decl.CSSLayerRule; +import com.helger.css.decl.CSSMediaRule; +import com.helger.css.decl.CSSNamespaceRule; +import com.helger.css.decl.CSSNestedDeclarations; +import com.helger.css.decl.CSSPageRule; +import com.helger.css.decl.CSSSupportsRule; +import com.helger.css.decl.CSSUnknownRule; +import com.helger.css.decl.CSSViewportRule; import org.jspecify.annotations.NonNull; import com.helger.annotation.Nonempty; @@ -73,42 +83,53 @@ public interface ICSSWriterSettings boolean isQuoteURLs (); /** - * @return true if @namespace rules should be written, false if not + * @return true if {@link CSSNamespaceRule @namespace rules} should be written, false if not */ boolean isWriteNamespaceRules (); /** - * @return true if @font-face rules should be written, false if not + * @return true if {@link CSSNestedDeclarations nested declarations} should be written, + * false if not + */ + boolean isWriteNestedDeclarations(); + + /** + * @return true if {@link CSSFontFaceRule @font-face rules} should be written, false if not */ boolean isWriteFontFaceRules (); /** - * @return true if @keyframes rules should be written, false if not + * @return true if {@link CSSKeyframesRule @keyframes rules} should be written, false if not */ boolean isWriteKeyframesRules (); /** - * @return true if @media rules should be written, false if not + * @return true if {@link CSSLayerRule @layer rules} should be written, false if not + */ + boolean isWriteLayerRules (); + + /** + * @return true if {@link CSSMediaRule @media rules} should be written, false if not */ boolean isWriteMediaRules (); /** - * @return true if @page rules should be written, false if not + * @return true if {@link CSSPageRule @page rules} should be written, false if not */ boolean isWritePageRules (); /** - * @return true if @viewport rules should be written, false if not + * @return true if {@link CSSViewportRule @viewport rules} should be written, false if not */ boolean isWriteViewportRules (); /** - * @return true if @supports rules should be written, false if not + * @return true if {@link CSSSupportsRule @supports rules} should be written, false if not */ boolean isWriteSupportsRules (); /** - * @return true if unknown @ rules should be written, false if not + * @return true if {@link CSSUnknownRule unknown @ rules} should be written, false if not */ boolean isWriteUnknownRules (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/AbstractHasTopLevelRules.java b/ph-css/src/main/java/com/helger/css/decl/AbstractHasTopLevelRules.java index 8d92c3c4..bc0c9bc9 100644 --- a/ph-css/src/main/java/com/helger/css/decl/AbstractHasTopLevelRules.java +++ b/ph-css/src/main/java/com/helger/css/decl/AbstractHasTopLevelRules.java @@ -207,6 +207,58 @@ public ICommonsList getAllRules (@NonNull final Predicate true if at least one layer rule is contained, false + * otherwise. + * @since 8.2.0 + */ + public boolean hasLayerRules () + { + return m_aRules.containsAny (CSSLayerRule.class::isInstance); + } + + /** + * Get the number of top-level rules that are layer rules (implementing {@link CSSLayerRule}). + * + * @return The number of contained layer rules. Always ≥ 0. + * @since 8.2.0 + */ + @Nonnegative + public int getLayerRuleCount () + { + return m_aRules.getCount (CSSLayerRule.class::isInstance); + } + + /** + * Get the layer rule at the specified index. + * + * @param nIndex + * The index to be resolved. Should be ≥ 0 and < {@link #getStyleRuleCount()}. + * @return The layer rule at the given index, or null if an invalid index was specified. + * @since 8.2.0 + */ + @Nullable + public CSSLayerRule getLayerRuleAtIndex (@Nonnegative final int nIndex) + { + return m_aRules.getAtIndexMapped (CSSLayerRule.class::isInstance, nIndex, CSSLayerRule.class::cast); + } + + /** + * Get a list of all top-level rules that are layer rules (implementing {@link CSSLayerRule}). + * + * @return A copy of all contained layer rules. Never null. + * @since 8.2.0 + */ + @NonNull + @ReturnsMutableCopy + public ICommonsList getAllLayerRules () + { + return m_aRules.getAllMapped (CSSLayerRule.class::isInstance, CSSLayerRule.class::cast); + } + /** * Check if at least one of the top-level rules is a style rule (implementing * {@link CSSStyleRule}). diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSDeclarationContainer.java b/ph-css/src/main/java/com/helger/css/decl/CSSDeclarationContainer.java index cba24b6c..e3764622 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSDeclarationContainer.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSDeclarationContainer.java @@ -36,6 +36,13 @@ public class CSSDeclarationContainer extends CSSDeclarationList public CSSDeclarationContainer () {} + @NonNull + @Nonempty + public String getDeclarationsAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) + { + return super.getAsCSSString (aSettings, nIndentLevel); + } + @Override @NonNull @Nonempty @@ -55,16 +62,18 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn { // A single declaration aSB.append (bOptimizedOutput ? "{" : " { "); - aSB.append (super.getAsCSSString (aSettings, nIndentLevel)); + aSB.append (super.getAsCSSString (aSettings, nIndentLevel + 1)); aSB.append (bOptimizedOutput ? "}" : " }"); } else { // More than one declaration aSB.append (bOptimizedOutput ? "{" : " {" + aSettings.getNewLineString ()); - aSB.append (super.getAsCSSString (aSettings, nIndentLevel)); if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel)); + aSB.append (aSettings.getIndent (nIndentLevel + 1)); + aSB.append (super.getAsCSSString (aSettings, nIndentLevel + 1)); + if (!bOptimizedOutput) + aSB.append(aSettings.getNewLineString()).append (aSettings.getIndent (nIndentLevel)); aSB.append ('}'); } } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSFontFaceRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSFontFaceRule.java index 10b428b0..019cb72e 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSFontFaceRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSFontFaceRule.java @@ -34,13 +34,15 @@ import com.helger.css.ICSSWriterSettings; /** - * Represents a single @font-face rule.
- * Example:
- * @font-face { - font-family: 'icons'; - src: url(path/to/font.woff) format('woff'); - unicode-range: U+E000-E005; -} + * Represents a single @font-face rule. + * + *

Example: + * + *

@font-face {
+  font-family: 'icons';
+  src: url(path/to/font.woff) format('woff');
+  unicode-range: U+E000-E005;
+}
* * @author Philip Helger */ @@ -166,11 +168,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (aSettings.isRemoveUnnecessaryCode () && !hasDeclarations ()) return ""; - final StringBuilder aSB = new StringBuilder (m_sDeclaration); - aSB.append (m_aDeclarations.getAsCSSString (aSettings, nIndentLevel)); - if (!aSettings.isOptimizedOutput ()) - aSB.append (aSettings.getNewLineString ()); - return aSB.toString (); + return m_sDeclaration + m_aDeclarations.getAsCSSString(aSettings, nIndentLevel); } @Nullable diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSImportRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSImportRule.java index d4b5cf5f..d03e757b 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSImportRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSImportRule.java @@ -234,7 +234,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn aSB.append (aMediaQuery.getAsCSSString (aSettings, nIndentLevel)); } } - return aSB.append (';').append (aSettings.getNewLineString ()).toString (); + return aSB.append (';').toString (); } @Nullable diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSKeyframesRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSKeyframesRule.java index a094735c..a8cd9c6d 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSKeyframesRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSKeyframesRule.java @@ -35,13 +35,14 @@ import com.helger.css.ICSSWriterSettings; /** - * Represents a single @keyframes rule.
- * Example:
- * @keyframes identifier { + * Represents a single @keyframes rule. + * + *

Example: + * + *

@keyframes identifier {
   0% { top: 0; left: 0; }
   30% { top: 50px; }
- }
- *
+}
* @author Philip Helger */ @NotThreadSafe @@ -161,14 +162,17 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (!aSettings.isWriteKeyframesRules ()) return ""; - if (aSettings.isRemoveUnnecessaryCode () && m_aBlocks.isEmpty ()) + int nBlockCount = m_aBlocks.size (); + boolean bFirst = true; + + if (aSettings.isRemoveUnnecessaryCode () && nBlockCount == 0) return ""; final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); final StringBuilder aSB = new StringBuilder (m_sDeclaration); aSB.append (' ').append (m_sAnimationName).append (bOptimizedOutput ? "{" : " {"); - if (!bOptimizedOutput) + if (!bOptimizedOutput && nBlockCount > 0) aSB.append (aSettings.getNewLineString ()); // Add all blocks @@ -177,18 +181,19 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn final String sBlockCSS = aBlock.getAsCSSString (aSettings, nIndentLevel + 1); if (StringHelper.isNotEmpty (sBlockCSS)) { + if (bFirst) + bFirst = false; + else + if (!bOptimizedOutput) + aSB.append (aSettings.getNewLineString ()); if (!bOptimizedOutput) aSB.append (aSettings.getIndent (nIndentLevel + 1)); aSB.append (sBlockCSS); - if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); } } - if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel)); + if (!bOptimizedOutput && nBlockCount > 0) + aSB.append (aSettings.getNewLineString ()).append (aSettings.getIndent (nIndentLevel)); aSB.append ('}'); - if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); return aSB.toString (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java index 6edab84c..e76ef713 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java @@ -16,6 +16,7 @@ */ package com.helger.css.decl; +import com.helger.base.state.EChange; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -33,7 +34,7 @@ import com.helger.css.ICSSWriterSettings; @NotThreadSafe -public class CSSLayerRule extends AbstractHasTopLevelRules implements ICSSTopLevelRule, ICSSSourceLocationAware +public class CSSLayerRule extends AbstractHasTopLevelRules implements ICSSTopLevelRule, ICSSNestedRule, ICSSSourceLocationAware { private final ICommonsList m_aSelectors; private CSSSourceLocation m_aSourceLocation; @@ -50,6 +51,121 @@ public CSSLayerRule (@NonNull final Iterable aSelectors) m_aSelectors = new CommonsArrayList <> (aSelectors); } + /** + * Checks if at least one selector is present. + * @return true if at least one selector is present, false otherwise. + * @since 8.2.0 + */ + public boolean hasSelectors () + { + return m_aSelectors.isNotEmpty (); + } + + /** + * Gets the number of selectors. + * @return The number of selectors. Always ≥ 0. + * @since 8.2.0 + */ + @Nonnegative + public int getSelectorCount () + { + return m_aSelectors.size (); + } + + /** + * Adds a selector to the end of the selector list. + * @param aSelector The selector to be added. Must not be null. + * @return This rule for chaining. Never null. + * @since 8.2.0 + */ + @NonNull + public CSSLayerRule addSelector (@NonNull final String aSelector) + { + ValueEnforcer.notNull (aSelector, "Selector"); + + m_aSelectors.add (aSelector); + return this; + } + + /** + * Adds a selector at the specified index. If the index is greater than the current number of selectors, the selector + * is added at the end of the list. + * @param nIndex The index at which the selector should be added. Must be ≥ 0. + * @param aSelector The selector to be added. Must not be null. + * @return This rule for chaining. Never null. + * @since 8.2.0 + */ + @NonNull + public CSSLayerRule addSelector (@Nonnegative final int nIndex, @NonNull final String aSelector) + { + ValueEnforcer.isGE0 (nIndex, "Index"); + ValueEnforcer.notNull (aSelector, "Selector"); + + if (nIndex >= getSelectorCount ()) + m_aSelectors.add (aSelector); + else + m_aSelectors.add (nIndex, aSelector); + return this; + } + + /** + * Remove the specified selector, if present. + * + * @param aSelector The selector to be removed. Must not be null. + * @return {@link EChange#CHANGED} if the selector was removed, {@link EChange#UNCHANGED} if the selector was not found. + * Never null. + * @since 8.2.0 + */ + @NonNull + public EChange removeSelector (@NonNull final String aSelector) + { + return m_aSelectors.removeObject (aSelector); + } + + /** + * Removes the selector at the specified index. + * + * @param nSelectorIndex The index of the selector to be removed. Must be ≥ 0. + * @return {@link EChange#CHANGED} if the selector was removed, {@link EChange#UNCHANGED} if the index was ≥ the + * number of selectors. Never null. + * @since 8.2.0 + */ + @NonNull + public EChange removeSelector (@Nonnegative final int nSelectorIndex) + { + return m_aSelectors.removeAtIndex (nSelectorIndex); + } + + /** + * Removes all selectors. + * + * @return {@link EChange#CHANGED} if any selector was removed, + * {@link EChange#UNCHANGED} otherwise. Never null. + * @since 8.2.0 + */ + @NonNull + public EChange removeAllSelectors () + { + return m_aSelectors.removeAll (); + } + + /** + * Gets the selector at the specified index. + * + * @param nSelectorIndex The index of the selector to be retrieved. Must be ≥ 0. + * @return The selector at the specified index, or null if the index is ≥ the number of selectors. + * @since 8.2.0 + */ + @Nullable + public String getSelectorAtIndex (@Nonnegative final int nSelectorIndex) + { + return m_aSelectors.getAtIndex (nSelectorIndex); + } + + /** + * Gets a copy of all selectors. Modifications to the returned list do not affect this rule, and vice versa. + * @return A list of all selectors. Never null. + */ @NonNull @ReturnsMutableCopy public ICommonsList getAllSelectors () @@ -60,6 +176,10 @@ public ICommonsList getAllSelectors () @NonNull public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) { + // Always ignore layer rules? + if (!aSettings.isWriteLayerRules ()) + return ""; + final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); final StringBuilder aSB = new StringBuilder ("@layer "); @@ -103,13 +223,10 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn } } if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel)); + aSB.append(aSettings.getNewLineString()).append (aSettings.getIndent (nIndentLevel)); aSB.append ('}'); } - if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); - return aSB.toString (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSMediaRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSMediaRule.java index 23997ab5..eac3d5eb 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSMediaRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSMediaRule.java @@ -36,18 +36,20 @@ /** * Represents a single @media rule: a list of style rules only valid for certain - * media.
- * Example:
- * @media print { + * media. + * + *

Example: + * + *

@media print {
   div#footer {
     display: none;
   }
-}
+}
* * @author Philip Helger */ @NotThreadSafe -public class CSSMediaRule extends AbstractHasTopLevelRules implements ICSSTopLevelRule, ICSSSourceLocationAware +public class CSSMediaRule extends AbstractHasTopLevelRules implements ICSSTopLevelRule, ICSSNestedRule, ICSSSourceLocationAware { private final ICommonsList m_aMediaQueries = new CommonsArrayList <> (); private CSSSourceLocation m_aSourceLocation; @@ -201,7 +203,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (nRuleCount == 0) { - aSB.append (bOptimizedOutput ? "{}" : " {}" + aSettings.getNewLineString ()); + aSB.append (bOptimizedOutput ? "{}" : " {}"); } else { @@ -217,7 +219,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn bFirst = false; else if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); + aSB.append (aSettings.getNewLineString ()).append (aSettings.getNewLineString ()); if (!bOptimizedOutput) aSB.append (aSettings.getIndent (nIndentLevel + 1)); @@ -225,10 +227,8 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn } } if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel)); + aSB.append(aSettings.getNewLineString()).append (aSettings.getIndent (nIndentLevel)); aSB.append ('}'); - if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); } return aSB.toString (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSNamespaceRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSNamespaceRule.java index ac4ac99d..4cb44af5 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSNamespaceRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSNamespaceRule.java @@ -120,7 +120,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn aSB.append (CSSURLHelper.getAsCSSURL (m_sURL, false)); else aSB.append ("\"\""); - return aSB.append (';').append (aSettings.getNewLineString ()).toString (); + return aSB.append (';').toString (); } @Nullable diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSNestedDeclarations.java b/ph-css/src/main/java/com/helger/css/decl/CSSNestedDeclarations.java new file mode 100644 index 00000000..adfb6cc8 --- /dev/null +++ b/ph-css/src/main/java/com/helger/css/decl/CSSNestedDeclarations.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2014-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.css.decl; + +import com.helger.annotation.Nonnegative; +import com.helger.annotation.concurrent.NotThreadSafe; +import com.helger.annotation.style.ReturnsMutableCopy; +import com.helger.base.hashcode.HashCodeGenerator; +import com.helger.base.state.EChange; +import com.helger.base.tostring.ToStringGenerator; +import com.helger.collection.commons.ICommonsList; +import com.helger.css.CSSSourceLocation; +import com.helger.css.ICSSSourceLocationAware; +import com.helger.css.ICSSWriterSettings; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +/** + * Represents nested style declarations. When nesting rules, all CSS style declarations after nested rules are wrapped + * within a nested declarations block, in accordance with the CSS Nesting Module Level 1 specification. A nested + * declarations instance consists of a number of declarations (the styles to be applied to the selected elements). + * + *

Example: + * + *

div {
+  color: red;
+  span {
+    color: green;
+  }
+  color: blue;
+}
+ * + * In the above example, color: blue; will be placed inside a nested declarations instances, as a child + * of a {@link CSSStyleRule}. The resulting object structure will look like this: + * + *
    + *
  • A {@link CSSStyleRule} representing the entire div { ... } block + *
      + *
    • The {@link CSSStyleRule#getAllDeclarations()} with color: red;
    • + *
    • The {@link CSSStyleRule#getAllRules()} with
    • + *
        + *
      • A nested {@link CSSStyleRule} represent span { color: green; } + *
      • A nested {@link CSSNestedDeclarations} representing color: blue;
      • + *
      + *
    + *
+ * @author Philip Helger + * @since 8.2.0 + */ +@NotThreadSafe +public class CSSNestedDeclarations implements ICSSNestedRule, IHasCSSDeclarations , ICSSSourceLocationAware +{ + private final CSSDeclarationContainer m_aDeclarations = new CSSDeclarationContainer (); + private CSSSourceLocation m_aSourceLocation; + + /** + * Creates a new, empty instance with no declarations. + */ + public CSSNestedDeclarations() + {} + + @Override + @NonNull + public CSSNestedDeclarations addDeclaration (@NonNull final CSSDeclaration aDeclaration) + { + m_aDeclarations.addDeclaration (aDeclaration); + return this; + } + + @Override + @NonNull + public CSSNestedDeclarations addDeclaration (@Nonnegative final int nIndex, @NonNull final CSSDeclaration aNewDeclaration) + { + m_aDeclarations.addDeclaration (nIndex, aNewDeclaration); + return this; + } + + @Override + @NonNull + public EChange removeDeclaration (@NonNull final CSSDeclaration aDeclaration) + { + return m_aDeclarations.removeDeclaration (aDeclaration); + } + + @Override + @NonNull + public EChange removeDeclaration (@Nonnegative final int nDeclarationIndex) + { + return m_aDeclarations.removeDeclaration (nDeclarationIndex); + } + + @Override + @NonNull + public EChange removeAllDeclarations () + { + return m_aDeclarations.removeAllDeclarations (); + } + + @Override + @NonNull + @ReturnsMutableCopy + public ICommonsList getAllDeclarations () + { + return m_aDeclarations.getAllDeclarations (); + } + + @Override + @Nullable + public CSSDeclaration getDeclarationAtIndex (@Nonnegative final int nIndex) + { + return m_aDeclarations.getDeclarationAtIndex (nIndex); + } + + @Override + @NonNull + public CSSNestedDeclarations setDeclarationAtIndex (@Nonnegative final int nIndex, @NonNull final CSSDeclaration aNewDeclaration) + { + m_aDeclarations.setDeclarationAtIndex (nIndex, aNewDeclaration); + return this; + } + + @Override + public boolean hasDeclarations () + { + return m_aDeclarations.hasDeclarations (); + } + + @Override + @Nonnegative + public int getDeclarationCount () + { + return m_aDeclarations.getDeclarationCount (); + } + + @Override + @Nullable + public CSSDeclaration getDeclarationOfPropertyName (@Nullable final String sPropertyName) + { + return m_aDeclarations.getDeclarationOfPropertyName (sPropertyName); + } + + @Override + @NonNull + @ReturnsMutableCopy + public ICommonsList getAllDeclarationsOfPropertyName (@Nullable final String sPropertyName) + { + return m_aDeclarations.getAllDeclarationsOfPropertyName (sPropertyName); + } + + @Override + @NonNull + public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) + { + // Always ignore nested declarations? + if (!aSettings.isWriteNestedDeclarations ()) + return ""; + + if (aSettings.isRemoveUnnecessaryCode () && !hasDeclarations ()) + return ""; + + return m_aDeclarations.getDeclarationsAsCSSString(aSettings, nIndentLevel); + } + + @Override + @Nullable + public final CSSSourceLocation getSourceLocation () + { + return m_aSourceLocation; + } + + @Override + public final void setSourceLocation (@Nullable final CSSSourceLocation aSourceLocation) + { + m_aSourceLocation = aSourceLocation; + } + + @Override + public boolean equals (final Object o) + { + if (o == this) + return true; + if (o == null || !getClass ().equals (o.getClass ())) + return false; + final CSSNestedDeclarations rhs = (CSSNestedDeclarations) o; + return m_aDeclarations.equals (rhs.m_aDeclarations); + } + + @Override + public int hashCode () + { + return new HashCodeGenerator (this).append (m_aDeclarations).getHashCode (); + } + + @Override + public String toString () + { + return new ToStringGenerator (this).append ("declarations", m_aDeclarations) + .appendIfNotNull ("SourceLocation", m_aSourceLocation) + .getToString (); + } +} diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSPageRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSPageRule.java index e9dd1297..02b678f3 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSPageRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSPageRule.java @@ -16,6 +16,7 @@ */ package com.helger.css.decl; +import com.helger.css.CCSS; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -34,12 +35,13 @@ import com.helger.css.ICSSWriterSettings; /** - * Represents a single @page rule.
- * Example:
- * @page { + * Represents a single @page rule. + *

Example: + * + *

@page {
   size: auto;
   margin: 10%;
-}
+}
* * @author Philip Helger */ @@ -170,23 +172,23 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn { // A single declaration aSB.append (bOptimizedOutput ? "{" : " { "); - aSB.append (m_aMembers.getAsCSSString (aSettings, nIndentLevel)); + aSB.append (getPageRuleMemberAsCSS(aSettings, nIndentLevel + 1)); aSB.append (bOptimizedOutput ? "}" : " }"); } else { // More than one declaration aSB.append (bOptimizedOutput ? "{" : " {" + aSettings.getNewLineString ()); - aSB.append (m_aMembers.getAsCSSString (aSettings, nIndentLevel)); + if (!bOptimizedOutput) { + aSB.append (aSettings.getIndent(nIndentLevel + 1)); + } + aSB.append (getPageRuleMemberAsCSS(aSettings, nIndentLevel + 1)); if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel)); + aSB.append (aSettings.getNewLineString ()).append (aSettings.getIndent (nIndentLevel)); aSB.append ('}'); } } - if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); - return aSB.toString (); } @@ -225,4 +227,43 @@ public String toString () .appendIfNotNull ("SourceLocation", m_aSourceLocation) .getToString (); } + + private String getPageRuleMemberAsCSS(@NonNull ICSSWriterSettings aSettings, int nIndentLevel) { + final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); + + final int nDeclCount = m_aMembers.size (); + if (nDeclCount == 0) + return ""; + if (nDeclCount == 1) + { + // A single element + final StringBuilder aSB = new StringBuilder (); + aSB.append (m_aMembers.get (0).getAsCSSString (aSettings, nIndentLevel)); + // No ';' at the last entry + if (m_aMembers.get (0) instanceof CSSDeclaration) + if (!bOptimizedOutput) + aSB.append (CCSS.DEFINITION_END); + return aSB.toString (); + } + + // More than one element + final StringBuilder aSB = new StringBuilder (); + int nIndex = 0; + for (final ICSSPageRuleMember aElement : m_aMembers) + { + // Indentation + if (!bOptimizedOutput && nIndex != 0) + aSB.append (aSettings.getIndent (nIndentLevel)); + // Emit the main element plus the semicolon + aSB.append (aElement.getAsCSSString (aSettings, nIndentLevel )); + // No ';' at the last decl + if (aElement instanceof CSSDeclaration) + if (!bOptimizedOutput || nIndex < nDeclCount - 1) + aSB.append (CCSS.DEFINITION_END); + if (!bOptimizedOutput && nIndex != m_aMembers.size() -1) + aSB.append (aSettings.getNewLineString ()); + ++nIndex; + } + return aSB.toString (); + } } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorSimpleMember.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorSimpleMember.java index 57ee4bbc..6418752b 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorSimpleMember.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorSimpleMember.java @@ -87,7 +87,9 @@ public boolean isPseudo () } /** + * Checks if this selector represents the nesting selector &. * @return true if it is a nesting selector + * @since 8.2.0 */ public boolean isNesting () { diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java index 4c2cdfe9..e7406bdb 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java @@ -16,6 +16,7 @@ */ package com.helger.css.decl; +import com.helger.css.CCSS; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -34,20 +35,29 @@ /** * Represents a single CSS style rule. A style rule consists of a number of - * selectors (determine the element to which the style rule applies) and a - * number of declarations (the rules to be applied to the selected elements). - *
- * Example:
- * div { color: red; } + * {@link CSSSelector selectors} (determines the elements to which + * the style rule applies), a number of {@link CSSDeclarationContainer declarations} + * (the styles to be applied to the selected elements), and a number of + * {@link ICSSNestedRule nested rules} (the rules nested within the style rule, + * e.g. media rules, supports rules, or nested declarations). + * + *

Example: + * + *

div {
+  color: red;
+  &:hover {
+    color: blue;
+  }
+}
* * @author Philip Helger */ @NotThreadSafe -public class CSSStyleRule implements ICSSTopLevelRule, IHasCSSDeclarations , ICSSSourceLocationAware +public class CSSStyleRule implements ICSSTopLevelRule, ICSSNestedRule, IHasCSSDeclarations , IHasCSSNestedRules, ICSSSourceLocationAware { private final ICommonsList m_aSelectors = new CommonsArrayList <> (); private final CSSDeclarationContainer m_aDeclarations = new CSSDeclarationContainer (); - private final ICommonsList m_aRules = new CommonsArrayList <> (); + private final ICommonsList m_aRules = new CommonsArrayList <> (); private CSSSourceLocation m_aSourceLocation; public CSSStyleRule () @@ -140,19 +150,22 @@ public ICommonsList getAllSelectors () return m_aSelectors.getClone (); } - public boolean hasRules () + @Override + public boolean hasRules() { return m_aRules.isNotEmpty (); } + @Override @Nonnegative - public int getRuleCount () + public int getRuleCount() { return m_aRules.size (); } + @Override @NonNull - public CSSStyleRule addRule (@NonNull final CSSStyleRule aRule) + public CSSStyleRule addRule(@NonNull final ICSSNestedRule aRule) { ValueEnforcer.notNull (aRule, "Rule"); @@ -160,8 +173,9 @@ public CSSStyleRule addRule (@NonNull final CSSStyleRule aRule) return this; } + @Override @NonNull - public CSSStyleRule addRule (@Nonnegative final int nIndex, @NonNull final CSSStyleRule aRule) + public CSSStyleRule addRule(@Nonnegative final int nIndex, @NonNull final ICSSNestedRule aRule) { ValueEnforcer.isGE0 (nIndex, "Index"); ValueEnforcer.notNull (aRule, "Rule"); @@ -173,44 +187,43 @@ public CSSStyleRule addRule (@Nonnegative final int nIndex, @NonNull final CSSSt return this; } + @Override @NonNull - public EChange removeRule (@NonNull final CSSStyleRule aRule) + public EChange removeRule(@NonNull final ICSSNestedRule aRule) { return m_aRules.removeObject (aRule); } + @Override @NonNull - public EChange removeRule (@Nonnegative final int nRuleIndex) + public EChange removeRule(@Nonnegative final int nRuleIndex) { return m_aRules.removeAtIndex (nRuleIndex); } - /** - * Remove all rules. - * - * @return {@link EChange#CHANGED} if any rule was removed, - * {@link EChange#UNCHANGED} otherwise. Never null. - * @since 3.7.3 - */ + @Override @NonNull - public EChange removeAllRules () + public EChange removeAllRules() { return m_aRules.removeAll (); } + @Override @Nullable - public CSSStyleRule getRuleAtIndex (@Nonnegative final int nRuleIndex) + public ICSSNestedRule getRuleAtIndex(@Nonnegative final int nRuleIndex) { return m_aRules.getAtIndex (nRuleIndex); } + @Override @NonNull @ReturnsMutableCopy - public ICommonsList getAllRules () + public ICommonsList getAllRules() { return m_aRules.getClone (); } + @Override @NonNull public CSSStyleRule addDeclaration (@NonNull final CSSDeclaration aDeclaration) { @@ -218,6 +231,7 @@ public CSSStyleRule addDeclaration (@NonNull final CSSDeclaration aDeclaration) return this; } + @Override @NonNull public CSSStyleRule addDeclaration (@Nonnegative final int nIndex, @NonNull final CSSDeclaration aNewDeclaration) { @@ -225,24 +239,28 @@ public CSSStyleRule addDeclaration (@Nonnegative final int nIndex, @NonNull fina return this; } + @Override @NonNull public EChange removeDeclaration (@NonNull final CSSDeclaration aDeclaration) { return m_aDeclarations.removeDeclaration (aDeclaration); } + @Override @NonNull public EChange removeDeclaration (@Nonnegative final int nDeclarationIndex) { return m_aDeclarations.removeDeclaration (nDeclarationIndex); } + @Override @NonNull public EChange removeAllDeclarations () { return m_aDeclarations.removeAllDeclarations (); } + @Override @NonNull @ReturnsMutableCopy public ICommonsList getAllDeclarations () @@ -250,12 +268,14 @@ public ICommonsList getAllDeclarations () return m_aDeclarations.getAllDeclarations (); } + @Override @Nullable public CSSDeclaration getDeclarationAtIndex (@Nonnegative final int nIndex) { return m_aDeclarations.getDeclarationAtIndex (nIndex); } + @Override @NonNull public CSSStyleRule setDeclarationAtIndex (@Nonnegative final int nIndex, @NonNull final CSSDeclaration aNewDeclaration) { @@ -263,23 +283,27 @@ public CSSStyleRule setDeclarationAtIndex (@Nonnegative final int nIndex, @NonNu return this; } + @Override public boolean hasDeclarations () { return m_aDeclarations.hasDeclarations (); } + @Override @Nonnegative public int getDeclarationCount () { return m_aDeclarations.getDeclarationCount (); } + @Override @Nullable public CSSDeclaration getDeclarationOfPropertyName (@Nullable final String sPropertyName) { return m_aDeclarations.getDeclarationOfPropertyName (sPropertyName); } + @Override @NonNull @ReturnsMutableCopy public ICommonsList getAllDeclarationsOfPropertyName (@Nullable final String sPropertyName) @@ -287,6 +311,16 @@ public ICommonsList getAllDeclarationsOfPropertyName (@Nullable return m_aDeclarations.getAllDeclarationsOfPropertyName (sPropertyName); } + /** + * Get the selectors as a serialized CSS string for writing to an output. + * + * @param aSettings + * The settings to be used to format the output. May not be + * null. + * @param nIndentLevel + * The current indentation level + * @return The content of the selectors as CSS string. Never null. + */ @NonNull public String getSelectorsAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) { @@ -308,32 +342,111 @@ public String getSelectorsAsCSSString (@NonNull final ICSSWriterSettings aSettin return aSB.toString (); } + @Override @NonNull public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) { - if (aSettings.isRemoveUnnecessaryCode () && !hasDeclarations ()) + if (aSettings.isRemoveUnnecessaryCode () && !hasDeclarations () && !hasRules()) return ""; final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); + final int nDeclCount = m_aDeclarations.getDeclarationCount (); + final int nRuleCount = m_aRules.size (); + final int nElementCount = nDeclCount + nRuleCount; final StringBuilder aSB = new StringBuilder (); // Append the selectors aSB.append (getSelectorsAsCSSString (aSettings, nIndentLevel)); + // Append the opening brace + if (nElementCount == 0) + aSB.append (bOptimizedOutput ? "{" : " {"); + else + if (nElementCount == 1) + aSB.append (bOptimizedOutput ? "{" : " { "); + else + aSB.append (bOptimizedOutput ? "{" : " {" + aSettings.getNewLineString ()); + // Append the declarations - aSB.append (m_aDeclarations.getAsCSSString (aSettings, nIndentLevel)); - if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); + if (nDeclCount == 1 && nRuleCount == 0) + { + aSB.append (m_aDeclarations.get(0).getAsCSSString (aSettings, nIndentLevel)); + // No ';' at the last entry + if (!bOptimizedOutput) + aSB.append (CCSS.DEFINITION_END); + } + else + if (nDeclCount >= 1) { + int nIndex = 0; + for (final CSSDeclaration aDeclaration : m_aDeclarations) + { + // Indentation + if (!bOptimizedOutput) + aSB.append (aSettings.getIndent (nIndentLevel + 1)); + // Emit the main element plus the semicolon + aSB.append (aDeclaration.getAsCSSString (aSettings, nIndentLevel + 1)); + // No ';' at the last decl + if (!bOptimizedOutput || nIndex < nDeclCount - 1 || nRuleCount > 0) + aSB.append (CCSS.DEFINITION_END); + // No line break at the last decl + if (!bOptimizedOutput && nIndex != nDeclCount - 1) + aSB.append (aSettings.getNewLineString ()); + ++nIndex; + } + } + + // Empty line between declarations and nested rules + if (!bOptimizedOutput && (nDeclCount > 0 && nRuleCount > 0)) + aSB.append (aSettings.getNewLineString ()).append (aSettings.getNewLineString ()); + + // Append the rules + if (nRuleCount > 0) + { + boolean bFirst = true; + int nRuleIndex = 0; + for (final ICSSNestedRule aRule : m_aRules) + { + if (bFirst) + bFirst = false; + else + if (!bOptimizedOutput) + aSB.append (aSettings.getNewLineString ()).append (aSettings.getNewLineString ()); + + if (!bOptimizedOutput) + aSB.append (aSettings.getIndent (nIndentLevel + 1)); + aSB.append(aRule.getAsCSSString(aSettings, nIndentLevel + 1)); + // When outputting optimized, no semicolon is added after the last declaration + // But when there are more rules, we need a semicolon as a separator + if (bOptimizedOutput && aRule instanceof CSSNestedDeclarations && nRuleIndex != nRuleCount - 1) { + aSB.append (CCSS.DEFINITION_END); + } + + ++nRuleIndex; + } + } + + if (!bOptimizedOutput && nElementCount > 0) + // Add space if there is exactly one declaration and no rules. Otherwise, add a line break + if (nElementCount == 1 && nRuleCount == 0) + aSB.append(" "); + else + aSB.append(aSettings.getNewLineString()).append(aSettings.getIndent(nIndentLevel)); + + // Append the closing brace + aSB.append ("}"); + return aSB.toString (); } + @Override @Nullable public final CSSSourceLocation getSourceLocation () { return m_aSourceLocation; } + @Override public final void setSourceLocation (@Nullable final CSSSourceLocation aSourceLocation) { m_aSourceLocation = aSourceLocation; @@ -347,13 +460,14 @@ public boolean equals (final Object o) if (o == null || !getClass ().equals (o.getClass ())) return false; final CSSStyleRule rhs = (CSSStyleRule) o; - return m_aSelectors.equals (rhs.m_aSelectors) && m_aDeclarations.equals (rhs.m_aDeclarations); + return m_aSelectors.equals (rhs.m_aSelectors) && m_aDeclarations.equals (rhs.m_aDeclarations) + && m_aRules.equals (rhs.m_aRules); } @Override public int hashCode () { - return new HashCodeGenerator (this).append (m_aSelectors).append (m_aDeclarations).getHashCode (); + return new HashCodeGenerator (this).append (m_aSelectors).append (m_aDeclarations).append(m_aRules).getHashCode (); } @Override @@ -361,6 +475,7 @@ public String toString () { return new ToStringGenerator (this).append ("selectors", m_aSelectors) .append ("declarations", m_aDeclarations) + .append ("rules", m_aRules) .appendIfNotNull ("SourceLocation", m_aSourceLocation) .getToString (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSupportsRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSSupportsRule.java index 85ca62c2..5c21b0a4 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSSupportsRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSSupportsRule.java @@ -36,16 +36,18 @@ /** * Represents a single @supports rule: a list of style rules only valid when a certain - * declaration is available. See {@link com.helger.css.ECSSSpecification#CSS3_CONDITIONAL}
- * Example:
- * @supports (transition-property: color) { + * declaration is available. See {@link com.helger.css.ECSSSpecification#CSS3_CONDITIONAL} + * + *

Example: + * + *

@supports (transition-property: color) {
   div { color:red; }
-}
+}
* * @author Philip Helger */ @NotThreadSafe -public class CSSSupportsRule extends AbstractHasTopLevelRules implements ICSSTopLevelRule, ICSSSourceLocationAware +public class CSSSupportsRule extends AbstractHasTopLevelRules implements ICSSTopLevelRule, ICSSNestedRule, ICSSSourceLocationAware { private final ICommonsList m_aConditionMembers = new CommonsArrayList <> (); private CSSSourceLocation m_aSourceLocation; @@ -152,7 +154,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (nRuleCount == 0) { - aSB.append (bOptimizedOutput ? "{}" : " {}" + aSettings.getNewLineString ()); + aSB.append (bOptimizedOutput ? "{}" : " {}"); } else { @@ -176,10 +178,8 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn } } if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel)); + aSB.append (aSettings.getNewLineString ()).append (aSettings.getIndent (nIndentLevel)); aSB.append ('}'); - if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); } return aSB.toString (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSUnknownRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSUnknownRule.java index 5f163c57..f66f8af3 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSUnknownRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSUnknownRule.java @@ -36,7 +36,7 @@ * @author Philip Helger */ @NotThreadSafe -public class CSSUnknownRule implements ICSSTopLevelRule, ICSSSourceLocationAware +public class CSSUnknownRule implements ICSSTopLevelRule, ICSSNestedRule, ICSSSourceLocationAware { private final String m_sDeclaration; private String m_sParameterList; @@ -117,20 +117,18 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (StringHelper.isEmpty (m_sBody)) { - aSB.append (bOptimizedOutput ? "{}" : " {}" + aSettings.getNewLineString ()); + aSB.append (bOptimizedOutput ? "{}" : " {}"); } else { // At least one rule present aSB.append (bOptimizedOutput ? "{" : " {" + aSettings.getNewLineString ()); if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel)); + aSB.append (aSettings.getIndent (nIndentLevel + 1)); aSB.append (m_sBody); if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel)); + aSB.append (aSettings.getNewLineString ()).append (aSettings.getIndent (nIndentLevel)); aSB.append ('}'); - if (!bOptimizedOutput) - aSB.append (aSettings.getNewLineString ()); } return aSB.toString (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSViewportRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSViewportRule.java index a820ad07..e3d1757a 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSViewportRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSViewportRule.java @@ -34,9 +34,13 @@ import com.helger.css.ICSSWriterSettings; /** - * Represents a single @viewport rule.
- * Example:
- * @viewport { width: device-width; } + * Represents a single @viewport rule. + * + *

Example: + * + *

@viewport {
+  width: device-width;
+}
* * @author Philip Helger */ @@ -157,11 +161,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (aSettings.isRemoveUnnecessaryCode () && !hasDeclarations ()) return ""; - final StringBuilder aSB = new StringBuilder (m_sDeclaration); - aSB.append (m_aDeclarations.getAsCSSString (aSettings, nIndentLevel)); - if (!aSettings.isOptimizedOutput ()) - aSB.append (aSettings.getNewLineString ()); - return aSB.toString (); + return m_sDeclaration + m_aDeclarations.getAsCSSString(aSettings, nIndentLevel); } @Nullable diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSWritableList.java b/ph-css/src/main/java/com/helger/css/decl/CSSWritableList.java index 44ffaf8e..cc026a75 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSWritableList.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSWritableList.java @@ -74,14 +74,14 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn for (final DATATYPE aElement : this) { // Indentation - if (!bOptimizedOutput) - aSB.append (aSettings.getIndent (nIndentLevel + 1)); + if (!bOptimizedOutput && nIndex != 0) + aSB.append (aSettings.getIndent (nIndentLevel)); // Emit the main element plus the semicolon - aSB.append (aElement.getAsCSSString (aSettings, nIndentLevel + 1)); + aSB.append (aElement.getAsCSSString (aSettings, nIndentLevel )); // No ';' at the last decl if (!bOptimizedOutput || nIndex < nDeclCount - 1) aSB.append (CCSS.DEFINITION_END); - if (!bOptimizedOutput) + if (!bOptimizedOutput && nIndex != size() - 1) aSB.append (aSettings.getNewLineString ()); ++nIndex; } diff --git a/ph-css/src/main/java/com/helger/css/decl/ICSSNestedRule.java b/ph-css/src/main/java/com/helger/css/decl/ICSSNestedRule.java new file mode 100644 index 00000000..4beb8f3d --- /dev/null +++ b/ph-css/src/main/java/com/helger/css/decl/ICSSNestedRule.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014-2026 Philip Helger (www.helger.com) + * philip[at]helger[dot]com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.helger.css.decl; + +import com.helger.annotation.style.MustImplementEqualsAndHashcode; +import com.helger.css.ICSSWriteable; + +/** + * Marker interface for all nested CSS elements that can occur in any order + * + *
    + *
  • layer rules - {@link CSSLayerRule} + *
  • media rules - {@link CSSMediaRule} + *
  • nested declarations - {@link CSSNestedDeclarations} + *
  • style rules - {@link CSSStyleRule} + *
  • supports rules - {@link CSSSupportsRule} + *
  • unknown rules - {@link CSSUnknownRule} + *
+ * + * To easily iterate over all rules contained in a {@link CascadingStyleSheet} + * you can use the + * {@link com.helger.css.decl.visit.CSSVisitor#visitCSS(CascadingStyleSheet, com.helger.css.decl.visit.ICSSVisitor) CSSVisitor#visitCSS(sheet, visitor)} + * method. An empty stub implementation of + * {@link com.helger.css.decl.visit.ICSSVisitor ICSSVisitor} is the class + * {@link com.helger.css.decl.visit.DefaultCSSVisitor DefaultCSSVisitor} which is a good basis for + * your own implementations. + * + * @author Philip Helger + * @since 8.2.0 + */ +@MustImplementEqualsAndHashcode +public interface ICSSNestedRule extends ICSSWriteable +{ + /* empty */ +} diff --git a/ph-css/src/main/java/com/helger/css/decl/ICSSTopLevelRule.java b/ph-css/src/main/java/com/helger/css/decl/ICSSTopLevelRule.java index 933cc1cc..3e983546 100644 --- a/ph-css/src/main/java/com/helger/css/decl/ICSSTopLevelRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/ICSSTopLevelRule.java @@ -20,28 +20,27 @@ import com.helger.css.ICSSWriteable; /** - *

* Marker interface for all top level CSS elements that can occur in any order - *

+ * *
    - *
  • font face rules - {@link CSSFontFaceRule}
  • - *
  • keyframes rules - {@link CSSKeyframesRule}
  • - *
  • media rules - {@link CSSMediaRule}
  • - *
  • page rules - {@link CSSPageRule}
  • - *
  • style rules - {@link CSSStyleRule}
  • - *
  • supports rules - {@link CSSSupportsRule}
  • - *
  • unknown rules - {@link CSSUnknownRule}
  • - *
  • viewport rules - {@link CSSViewportRule}
  • + *
  • font face rules - {@link CSSFontFaceRule} + *
  • keyframes rules - {@link CSSKeyframesRule} + *
  • layer rules - {@link CSSLayerRule} + *
  • media rules - {@link CSSMediaRule} + *
  • page rules - {@link CSSPageRule} + *
  • style rules - {@link CSSStyleRule} + *
  • supports rules - {@link CSSSupportsRule} + *
  • unknown rules - {@link CSSUnknownRule} + *
  • viewport rules - {@link CSSViewportRule} *
- *

+ * * To easily iterate over all rules contained in a {@link CascadingStyleSheet} * you can use the - * {@link com.helger.css.decl.visit.CSSVisitor#visitCSS(CascadingStyleSheet, com.helger.css.decl.visit.ICSSVisitor)} + * {@link com.helger.css.decl.visit.CSSVisitor#visitCSS(CascadingStyleSheet, com.helger.css.decl.visit.ICSSVisitor) CSSVisitor#visitCSS(sheet, visitor)} * method. An empty stub implementation of - * {@link com.helger.css.decl.visit.ICSSVisitor} is the class - * {@link com.helger.css.decl.visit.DefaultCSSVisitor} which is a good basis for + * {@link com.helger.css.decl.visit.ICSSVisitor ICSSVisitor} is the class + * {@link com.helger.css.decl.visit.DefaultCSSVisitor DefaultCSSVisitor} which is a good basis for * your own implementations. - *

* * @author Philip Helger */ diff --git a/ph-css/src/main/java/com/helger/css/decl/IHasCSSNestedRules.java b/ph-css/src/main/java/com/helger/css/decl/IHasCSSNestedRules.java new file mode 100644 index 00000000..cd93f975 --- /dev/null +++ b/ph-css/src/main/java/com/helger/css/decl/IHasCSSNestedRules.java @@ -0,0 +1,90 @@ +package com.helger.css.decl; + +import com.helger.annotation.Nonnegative; +import com.helger.annotation.style.ReturnsMutableCopy; +import com.helger.base.state.EChange; +import com.helger.collection.commons.ICommonsList; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +/** + * Sanity interface for all objects having nested CSS rules. + * + * @param + * Implementation type + * @author Philip Helger + * @since 8.2.0 + */ +public interface IHasCSSNestedRules { + /** + * Checks if this element has any nested rules. + * @return true if at least one nested rule is present, false otherwise. Never + * null. + */ + boolean hasRules(); + + /** + * Gets the number of nested rules contained in this element. + * @return The number of nested rules. Never negative. + */ + @Nonnegative + int getRuleCount(); + + /** + * Adds a rule to the list of nested rules. The rule is added at the end of the list. + * @param aRule The rule to be added. Must not be null. + * @return This element for chaining. Never null. + */ + @NonNull IMPLTYPE addRule(@NonNull ICSSNestedRule aRule); + + /** + * Adds a rule to the list of nested rules at the specified index. + * @param nIndex The index at which the rule should be added. If equal to or greater than the current number of rules, + * the rule will be added at the end of the list. Must not be negative. + * @param aRule The rule to be added. Must not be null. + * @return This element for chaining. Never null. + */ + @NonNull IMPLTYPE addRule(@Nonnegative int nIndex, @NonNull ICSSNestedRule aRule); + + /** + * Removes a rule from the list of nested rules, if present. + * @param aRule The rule to be removed. Must not be null. + * @return {@link EChange#CHANGED CHANGED} if the rule was removed, {@link EChange#UNCHANGED UNCHANGED} otherwise. + * Never null. + */ + @NonNull EChange removeRule(@NonNull ICSSNestedRule aRule); + + /** + * Removes a rule from the list of nested rules at the specified index. + * @param nRuleIndex The index of the rule to be removed. Values equal to or greater than the current number of rules + * will be ignored. Must not be negative. + * @return {@link EChange#CHANGED CHANGED} if the rule was removed, {@link EChange#UNCHANGED UNCHANGED} otherwise. + * Never null. + */ + @NonNull EChange removeRule(@Nonnegative int nRuleIndex); + + /** + * Remove all nested rules. + * + * @return {@link EChange#CHANGED CHANGED} if any rule was removed, {@link EChange#UNCHANGED UNCHANGED} otherwise. + * Never null. + */ + @NonNull EChange removeAllRules(); + + /** + * Gets a nested rule at the specified index. + * @param nRuleIndex The index of the rule to be retrieved. If equal to or greater than the current number of rules, + * null will be returned. Must not be negative. + * @return This rule for chaining. null if not found. + */ + @Nullable ICSSNestedRule getRuleAtIndex(@Nonnegative int nRuleIndex); + + /** + * Gets all nested rules contained in this element. Modifications to the returned list will not affect this style + * rule, and vice versa. + * @return A list of nested rules. Never null. + */ + @NonNull + @ReturnsMutableCopy + ICommonsList getAllRules(); +} diff --git a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java index 2217ce64..36bd9e59 100644 --- a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java +++ b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java @@ -777,24 +777,86 @@ private void _readStyleDeclarationList (@NonNull final CSSNode aNode, } } - private void _readStyleDeclarationListRules (@NonNull final CSSNode aNode, - @NonNull final Consumer aConsumer) + private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aNode, + @NonNull final Consumer aDeclarationConsumer, + @NonNull final Consumer aNestedRuleConsumer) { - _expectNodeType (aNode, ECSSNodeType.STYLEDECLARATIONLIST); - // Read all contained declarations + _expectNodeType (aNode, ECSSNodeType.STYLEDECLARATIONLISTWITHNESTED); + // Read all contained declarations and rules final int nDecls = aNode.jjtGetNumChildren (); + CSSNestedDeclarations aNestedDeclarations = null; for (int nDecl = 0; nDecl < nDecls; ++nDecl) { final CSSNode aChildNode = aNode.jjtGetChild (nDecl); - if (ECSSNodeType.STYLERULE.isNode (aChildNode)) + if (ECSSNodeType.STYLEDECLARATION.isNode (aChildNode)) { - final CSSStyleRule aRule = _createStyleRule (aChildNode); - if (aRule != null) - aConsumer.accept (aRule); + final CSSDeclaration aDeclaration = _createDeclaration (aChildNode); + if (aDeclaration != null) { + // declarations that appear at the start are added as declarations of the style rule + // declarations that appear interspersed with other rules are wrapped in a nested declarations element + if (aNestedDeclarations != null) { + aNestedDeclarations.addDeclaration(aDeclaration); + } else { + aDeclarationConsumer.accept (aDeclaration); + } + } } - // else - // ignore ERROR_SKIP to and all "@" things + else + if (ECSSNodeType.STYLERULE.isNode (aChildNode)) + { + if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) + aNestedRuleConsumer.accept (aNestedDeclarations); + final CSSStyleRule aRule = _createStyleRule (aChildNode); + if (aRule != null) + aNestedRuleConsumer.accept (aRule); + aNestedDeclarations = new CSSNestedDeclarations(); + } + else + if (ECSSNodeType.MEDIARULE.isNode (aChildNode)) + { + if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) + aNestedRuleConsumer.accept (aNestedDeclarations); + final CSSMediaRule aRule = _createMediaRule (aChildNode); + if (aRule != null) + aNestedRuleConsumer.accept (aRule); + aNestedDeclarations = new CSSNestedDeclarations(); + } + else + if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode)) + { + if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) + aNestedRuleConsumer.accept (aNestedDeclarations); + final CSSSupportsRule aRule = _createSupportsRule (aChildNode); + if (aRule != null) + aNestedRuleConsumer.accept (aRule); + aNestedDeclarations = new CSSNestedDeclarations(); + } + else + if (ECSSNodeType.LAYERRULE.isNode (aChildNode)) + { + if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) + aNestedRuleConsumer.accept (aNestedDeclarations); + final CSSLayerRule aRule = _createLayerRule (aChildNode); + if (aRule != null) + aNestedRuleConsumer.accept (aRule); + aNestedDeclarations = new CSSNestedDeclarations(); + } + else + if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode)) + { + if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) + aNestedRuleConsumer.accept (aNestedDeclarations); + final CSSUnknownRule aRule = _createUnknownRule (aChildNode); + if (aRule != null) + aNestedRuleConsumer.accept (aRule); + aNestedDeclarations = new CSSNestedDeclarations(); + } + // else + // ignore ERROR_SKIP to and all unsupported nested "@" rules } + // append trailing declarations if there are any + if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) + aNestedRuleConsumer.accept (aNestedDeclarations); } @Nullable @@ -818,13 +880,10 @@ private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode) { // OK, we're after the selectors bSelectors = false; - if (ECSSNodeType.STYLEDECLARATIONLIST.isNode (aChildNode)) + if (ECSSNodeType.STYLEDECLARATIONLISTWITHNESTED.isNode (aChildNode)) { - // Read all contained declarations - _readStyleDeclarationList (aChildNode, ret::addDeclaration); - - // Read all contained rules - _readStyleDeclarationListRules (aChildNode, ret::addRule); + // Read all contained declarations and nested rules + _readStyleDeclarationListWithNestedRules (aChildNode, ret::addDeclaration, ret::addRule); } else if (!ECSSNodeType.isErrorNode (aChildNode)) diff --git a/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java b/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java index a2ff9254..004604fb 100644 --- a/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java +++ b/ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java @@ -50,6 +50,7 @@ public enum ECSSNodeType SELECTOR (ParserCSS30TreeConstants.JJTSELECTOR), RELATIVESELECTOR (ParserCSS30TreeConstants.JJTRELATIVESELECTOR), STYLEDECLARATIONLIST (ParserCSS30TreeConstants.JJTSTYLEDECLARATIONLIST), + STYLEDECLARATIONLISTWITHNESTED (ParserCSS30TreeConstants.JJTSTYLEDECLARATIONLISTWITHNESTED), STYLEDECLARATION (ParserCSS30TreeConstants.JJTSTYLEDECLARATION), // style rule -- selector NAMESPACEPREFIX (ParserCSS30TreeConstants.JJTNAMESPACEPREFIX), diff --git a/ph-css/src/main/java/com/helger/css/writer/CSSWriter.java b/ph-css/src/main/java/com/helger/css/writer/CSSWriter.java index a5dafbab..7c32701f 100644 --- a/ph-css/src/main/java/com/helger/css/writer/CSSWriter.java +++ b/ph-css/src/main/java/com/helger/css/writer/CSSWriter.java @@ -44,7 +44,7 @@ @NotThreadSafe public class CSSWriter { - /** By default optimized output is disabled */ + /** By default, optimized output is disabled */ public static final boolean DEFAULT_OPTIMIZED_OUTPUT = CSSWriterSettings.DEFAULT_OPTIMIZED_OUTPUT; private final CSSWriterSettings m_aSettings; @@ -89,7 +89,7 @@ public CSSWriter (@NonNull final CSSWriterSettings aSettings) } /** - * Check if the header text should be emitted. By default it is enabled, if non-optimized output + * Check if the header text should be emitted. By default, it is enabled, if non-optimized output * is desired. * * @return true if the header text should be emitted, false if not. @@ -100,7 +100,7 @@ public boolean isWriteHeaderText () } /** - * Determine whether the file header should be written or not. By default it is enabled, if + * Determine whether the file header should be written or not. By default, it is enabled, if * non-optimized output is desired. * * @param bWriteHeaderText @@ -126,7 +126,7 @@ public String getHeaderText () /** * Set a custom header text that should be emitted. This text may be multi line separated by the - * '\n' character. It will emitted if {@link #isWriteHeaderText()} returns true. + * '\n' character. It will be emitted if {@link #isWriteHeaderText()} returns true. * * @param sHeaderText * The header text to be emitted. May be null. @@ -140,7 +140,7 @@ public CSSWriter setHeaderText (@Nullable final String sHeaderText) } /** - * Check if the footer text should be emitted. By default it is enabled, if non-optimized output + * Check if the footer text should be emitted. By default, it is enabled, if non-optimized output * is desired. * * @return true if the footer text should be emitted, false if not. @@ -151,7 +151,7 @@ public boolean isWriteFooterText () } /** - * Determine whether the file footer should be written or not. By default it is enabled, if + * Determine whether the file footer should be written or not. By default, it is enabled, if * non-optimized output is desired. * * @param bWriteFooterText @@ -177,7 +177,7 @@ public String getFooterText () /** * Set a custom footer text that should be emitted. This text may be multi line separated by the - * '\n' character. It will emitted if {@link #isWriteFooterText()} returns true. + * '\n' character. It will be emitted if {@link #isWriteFooterText()} returns true. * * @param sFooterText * The footer text to be emitted. May be null. @@ -191,7 +191,7 @@ public CSSWriter setFooterText (@Nullable final String sFooterText) } /** - * @return The current defined content charset for the CSS. By default it is null. + * @return The current defined content charset for the CSS. By default, it is null. */ @Nullable public String getContentCharset () @@ -201,7 +201,7 @@ public String getContentCharset () /** * Define the content charset to be used. If not null and not empty, the - * @charset element is emitted into the CSS. By default no charset is defined.
+ * @charset element is emitted into the CSS. By default, no charset is defined.
* Important: this does not define the encoding of the output - it is just a declarative * marker inside the code. Best practice is to use the same encoding for the CSS and the * respective writer! @@ -235,7 +235,7 @@ public CSSWriterSettings getSettings () * @param aCSS * The CSS to write. May not be null. * @param aWriter - * The write to write the text to. May not be null. Is automatically closed + * The writer to write the text to. May not be null. Is automatically closed * after the writing! * @throws IOException * In case writing fails. @@ -268,32 +268,58 @@ public void writeCSS (@NonNull final CascadingStyleSheet aCSS, @NonNull @WillClo aWriter.write (sNewLineString); } + int nRulesEmitted = 0; + // Charset? Must be the first element before the import if (StringHelper.isNotEmpty (m_sContentCharset)) { aWriter.write ("@charset \"" + m_sContentCharset + "\";"); - if (!bOptimizedOutput) - aWriter.write (sNewLineString); + ++nRulesEmitted; } // Import rules - int nRulesEmitted = 0; + boolean bFirst = true; final ICommonsList aImportRules = aCSS.getAllImportRules (); if (aImportRules.isNotEmpty ()) + { + if (!bOptimizedOutput && nRulesEmitted > 0) + { + aWriter.write(sNewLineString); + aWriter.write(sNewLineString); + } for (final CSSImportRule aImportRule : aImportRules) { + if (bFirst) + bFirst = false; + else + if (!bOptimizedOutput) + aWriter.write (sNewLineString); aWriter.write (aImportRule.getAsCSSString (m_aSettings)); ++nRulesEmitted; } + } // Namespace rules + bFirst = true; final ICommonsList aNamespaceRules = aCSS.getAllNamespaceRules (); if (aNamespaceRules.isNotEmpty ()) + { + if (!bOptimizedOutput && nRulesEmitted > 0) + { + aWriter.write(sNewLineString); + aWriter.write(sNewLineString); + } for (final CSSNamespaceRule aNamespaceRule : aNamespaceRules) { + if (bFirst) + bFirst = false; + else + if (!bOptimizedOutput) + aWriter.write (sNewLineString); aWriter.write (aNamespaceRule.getAsCSSString (m_aSettings)); ++nRulesEmitted; } + } // Main CSS rules for (final ICSSTopLevelRule aRule : aCSS.getAllRules ()) @@ -302,13 +328,21 @@ public void writeCSS (@NonNull final CascadingStyleSheet aCSS, @NonNull @WillClo if (StringHelper.isNotEmpty (sRuleCSS)) { if (!bOptimizedOutput && nRulesEmitted > 0) - aWriter.write (sNewLineString); + { + aWriter.write(sNewLineString); + aWriter.write(sNewLineString); + } aWriter.write (sRuleCSS); ++nRulesEmitted; } } + // Newline after all rules + if (!bOptimizedOutput && nRulesEmitted > 0){ + aWriter.write (sNewLineString); + } + // Write file footer if (m_bWriteFooterText && StringHelper.isNotEmpty (m_sFooterText)) { @@ -360,7 +394,7 @@ public String getCSSAsString (@NonNull final CascadingStyleSheet aCSS) * @param aCSS * The CSS to write. May not be null. * @param aWriter - * The write to write the text to. May not be null. Is automatically closed + * The writer to write the text to. May not be null. Is automatically closed * after the writing! * @throws IOException * In case writing fails. diff --git a/ph-css/src/main/java/com/helger/css/writer/CSSWriterSettings.java b/ph-css/src/main/java/com/helger/css/writer/CSSWriterSettings.java index 9997b1b8..dcb0c4bb 100644 --- a/ph-css/src/main/java/com/helger/css/writer/CSSWriterSettings.java +++ b/ph-css/src/main/java/com/helger/css/writer/CSSWriterSettings.java @@ -16,6 +16,8 @@ */ package com.helger.css.writer; +import com.helger.css.decl.CSSLayerRule; +import com.helger.css.decl.CSSNestedDeclarations; import org.jspecify.annotations.NonNull; import com.helger.annotation.Nonempty; @@ -41,31 +43,33 @@ public class CSSWriterSettings implements ICSSWriterSettings, ICloneable true to write nested declarations, false to ignore them. + * @return This instance for chaining + * @since 8.2.0 + */ + @NonNull + public final CSSWriterSettings setWriteNestedDeclarations(final boolean bWriteNestedDeclarations) + { + m_bWriteNestedDeclarations = bWriteNestedDeclarations; + return this; + } + public final boolean isWriteFontFaceRules () { return m_bWriteFontFaceRules; @@ -239,6 +266,25 @@ public final CSSWriterSettings setWriteKeyframesRules (final boolean bWriteKeyfr return this; } + @Override + public final boolean isWriteLayerRules () + { + return m_bWriteLayerRules; + } + + /** + * Configures whether {@link CSSLayerRule @layer rules} are written. + * @param bWriteLayerRules true to write layer rules, false to ignore them. + * @return This instance for chaining + * @since 8.2.0 + */ + @NonNull + public final CSSWriterSettings setWriteLayerRules (final boolean bWriteLayerRules) + { + m_bWriteLayerRules = bWriteLayerRules; + return this; + } + public final boolean isWriteMediaRules () { return m_bWriteMediaRules; @@ -320,8 +366,10 @@ public boolean equals (final Object o) m_sIndent.equals (rhs.m_sIndent) && m_bQuoteURLs == rhs.m_bQuoteURLs && m_bWriteNamespaceRules == rhs.m_bWriteNamespaceRules && + m_bWriteNestedDeclarations == rhs.m_bWriteNestedDeclarations && m_bWriteFontFaceRules == rhs.m_bWriteFontFaceRules && m_bWriteKeyframesRules == rhs.m_bWriteKeyframesRules && + m_bWriteLayerRules == rhs.m_bWriteLayerRules && m_bWriteMediaRules == rhs.m_bWriteMediaRules && m_bWritePageRules == rhs.m_bWritePageRules && m_bWriteViewportRules == rhs.m_bWriteViewportRules && @@ -338,8 +386,10 @@ public int hashCode () .append (m_sIndent) .append (m_bQuoteURLs) .append (m_bWriteNamespaceRules) + .append (m_bWriteNestedDeclarations) .append (m_bWriteFontFaceRules) .append (m_bWriteKeyframesRules) + .append (m_bWriteLayerRules) .append (m_bWriteMediaRules) .append (m_bWritePageRules) .append (m_bWriteViewportRules) @@ -357,8 +407,10 @@ public String toString () .append ("Indent", m_sIndent) .append ("QuoteURLs", m_bQuoteURLs) .append ("WriteNamespaceRules", m_bWriteNamespaceRules) + .append ("WriteNestedDeclarations", m_bWriteNestedDeclarations) .append ("WriteFontFaceRules", m_bWriteFontFaceRules) .append ("WriteKeyframesRules", m_bWriteKeyframesRules) + .append ("WriteLayerRules", m_bWriteLayerRules) .append ("WriteMediaRules", m_bWriteMediaRules) .append ("WritePageRules", m_bWritePageRules) .append ("WriteViewportRules", m_bWriteViewportRules) diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index b9a23d46..cc53973d 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -1221,10 +1221,8 @@ try{ void styleDeclarationOrRule() #void : {} { - ( styleDeclaration() ( LOOKAHEAD( ()* ( | ) ) ()* | ()* ()* ) - | ()* // final semicolon from single line comment - | ( styleRule() - | mediaRule() { errorUnexpectedRule ("@media", "media rule in the middle of a rule-set is not allowed!"); } + ( styleDeclaration() + | ( mediaRule() { errorUnexpectedRule ("@media", "media rule in the middle of a rule-set is not allowed!"); } | pageRule() { errorUnexpectedRule ("@page", "page rule in the middle of a rule-set is not allowed!"); } | fontfaceRule() { errorUnexpectedRule ("@font-face", "font-face rule in the middle of a rule-set is not allowed!"); } | keyframesRule() { errorUnexpectedRule ("@keyframes", "keyframes rule in the middle of a rule-set is not allowed!"); } @@ -1240,11 +1238,52 @@ void styleDeclarationOrRule() #void : {} ) } +void styleDeclarationOrRuleWithNested() #void : {} +{ + ( LOOKAHEAD( property() ) styleDeclaration() ( LOOKAHEAD( ()* ( | ) ) ()* | ()* ()* ) + | ()* // final semicolon from single line comment + | ( styleRule() + | mediaRule() { } + | pageRule() { errorUnexpectedRule ("@page", "page rule is not allowed as a nested rule!"); } + | fontfaceRule() { errorUnexpectedRule ("@font-face", "font-face rule is not allowed as a nested rule!"); } + | keyframesRule() { errorUnexpectedRule ("@keyframes", "keyframes rule is not allowed as a nested rule!"); } + | viewportRule() { errorUnexpectedRule ("@viewport", "viewport rule is not allowed as a nested rule!"); } + | supportsRule() { } + | unknownRule() { } + | charsetRule() { errorUnexpectedRule ("@charset", "charset rule is not allowed as a nested rule!"); } + | importRule() { errorUnexpectedRule ("@import", "import rule is not allowed as a nested rule!"); } + | namespaceRule() { errorUnexpectedRule ("@namespace", "namespace rule is not allowed as a nested rule!"); } + | layerRule() { } + ) + ( | | )* + ) +} + CSSNode styleDeclarationList() : {} { try { ( )* - ( styleDeclarationOrRule() )* + ( styleDeclarationOrRule() )? + ( + ( )* + ( styleDeclarationOrRule() )? + )* +} catch (/*final*/ ParseException ex) { + if (m_bBrowserCompliantMode) + browserCompliantSkipDecl (ex); + else { + errorSkipTo (ex, RBRACE); + token_source.backup(1); + } +} + { return jjtThis; } +} + +CSSNode styleDeclarationListWithNested() : {} +{ +try { + ( )* + ( styleDeclarationOrRuleWithNested() )* } catch (/*final*/ ParseException ex) { if (m_bBrowserCompliantMode) browserCompliantSkipDecl (ex); @@ -1262,6 +1301,20 @@ void styleDeclarationBlock() #void : {} try { styleDeclarationList() +} catch (/*final*/ ParseException ex) { + if (m_bBrowserCompliantMode) + browserCompliantSkipDecl (ex); + else + errorSkipTo (ex, RBRACE); +} +} + +void styleDeclarationBlockWithNested() #void : {} +{ + +try { + styleDeclarationListWithNested() + } catch (/*final*/ ParseException ex) { if (m_bBrowserCompliantMode) browserCompliantSkipDecl (ex); @@ -1280,7 +1333,7 @@ try{ selector() ( )* )* - styleDeclarationBlock() + styleDeclarationBlockWithNested() } catch (/*final*/ ParseException ex) { if (m_bBrowserCompliantMode) browserCompliantSkipInSelector (ex); diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSImportRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSImportRuleTest.java index 1a962e85..17520aed 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSImportRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSImportRuleTest.java @@ -76,8 +76,8 @@ public void testCreate () { final CSSImportRule aImportRule = new CSSImportRule ("a.gif"); final CSSWriterSettings aSettings = new CSSWriterSettings ( false); - assertEquals ("@import url(a.gif);\n", aImportRule.getAsCSSString (aSettings)); + assertEquals ("@import url(a.gif);", aImportRule.getAsCSSString (aSettings)); aSettings.setQuoteURLs (true); - assertEquals ("@import url('a.gif');\n", aImportRule.getAsCSSString (aSettings)); + assertEquals ("@import url('a.gif');", aImportRule.getAsCSSString (aSettings)); } } diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSLayerRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSLayerRuleTest.java new file mode 100644 index 00000000..f21d587e --- /dev/null +++ b/ph-css/src/test/java/com/helger/css/decl/CSSLayerRuleTest.java @@ -0,0 +1,68 @@ +package com.helger.css.decl; + +import com.helger.css.reader.CSSReader; +import org.jspecify.annotations.NonNull; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Test class for {@link CSSLayerRule}. + * + * @author Philip Helger + */ +public class CSSLayerRuleTest { + @NonNull + private static CSSLayerRule _parse (@NonNull final String sCSS) + { + final CascadingStyleSheet aCSS = CSSReader.readFromString (sCSS); + assertNotNull (sCSS, aCSS); + assertTrue (aCSS.hasLayerRules ()); + assertEquals (1, aCSS.getLayerRuleCount ()); + final CSSLayerRule ret = aCSS.getAllLayerRules ().get (0); + assertNotNull (ret); + return ret; + } + + @Test + public void testRead1 () + { + CSSLayerRule aSR = _parse (String.join("\n", List.of( + "@layer state {", + " .foo {", + " color: white;", + " .bar {", + " color: orange", + " }", + " color: black;", + " }", + "}"))); + assertEquals (1, aSR.getSelectorCount()); + assertEquals (1, aSR.getRuleCount ()); + + assertEquals ("state", aSR.getSelectorAtIndex(0)); + + assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); + assertEquals (2, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + + assertEquals (".foo", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:white", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getRuleCount()); + assertEquals (".bar", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:orange", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1) instanceof CSSNestedDeclarations); + assertEquals (1, ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationCount()); + assertEquals ("color:black", ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationAtIndex(0).getAsCSSString()); + } +} diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSMediaRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSMediaRuleTest.java new file mode 100644 index 00000000..82118947 --- /dev/null +++ b/ph-css/src/test/java/com/helger/css/decl/CSSMediaRuleTest.java @@ -0,0 +1,68 @@ +package com.helger.css.decl; + +import com.helger.css.reader.CSSReader; +import org.jspecify.annotations.NonNull; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Test class for {@link CSSMediaRule}. + * + * @author Philip Helger + */ +public class CSSMediaRuleTest { + @NonNull + private static CSSMediaRule _parse (@NonNull final String sCSS) + { + final CascadingStyleSheet aCSS = CSSReader.readFromString (sCSS); + assertNotNull (sCSS, aCSS); + assertTrue (aCSS.hasMediaRules ()); + assertEquals (1, aCSS.getMediaRuleCount ()); + final CSSMediaRule ret = aCSS.getAllMediaRules ().get (0); + assertNotNull (ret); + return ret; + } + + @Test + public void testRead1 () + { + CSSMediaRule aSR = _parse (String.join("\n", List.of( + "@media print {", + " .foo {", + " color: white;", + " .bar {", + " color: orange", + " }", + " color: black;", + " }", + "}"))); + assertEquals(1, aSR.getMediaQueryCount()); + assertEquals (1, aSR.getRuleCount ()); + + assertEquals("print", aSR.getMediaQueryAtIndex(0).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); + assertEquals (2, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + + assertEquals (".foo", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:white", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getRuleCount()); + assertEquals (".bar", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:orange", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1) instanceof CSSNestedDeclarations); + assertEquals (1, ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationCount()); + assertEquals ("color:black", ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationAtIndex(0).getAsCSSString()); + } +} diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java index 4dd319bd..6d3839f0 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java @@ -115,21 +115,178 @@ public void testRead2 () @Test public void testRead3 () { - CSSStyleRule aSR = _parse ("div { color: red; .foobar { color: green; #id { color: blue } color: white; } }"); - assertEquals (1, aSR.getDeclarationCount ()); + CSSStyleRule aSR = _parse ("div { p { color: red; } }"); + assertEquals (1, aSR.getSelectorCount ()); + assertEquals (0, aSR.getDeclarationCount ()); assertEquals (1, aSR.getRuleCount ()); + assertEquals("div", aSR.getSelectorAtIndex(0).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + assertEquals ("p", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:red", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); + } + + @Test + public void testRead4 () + { + CSSStyleRule aSR = _parse (""" + div { + color: red; + p: dummy; + p { + color: dummy; + } + .foobar { + color: green; + #id { + color: blue + } + color: white + } + color: yellow; + @media print { + .print { + color: black; + &:hover { + color: orange; + font-size: 20px; + } + } + } + @layer state { + .alert { + background-color: brown; + p { + border: medium solid limegreen; + } + } + } + }"""); + assertEquals (2, aSR.getDeclarationCount ()); + assertEquals (5, aSR.getRuleCount ()); + + assertEquals ("color:red", aSR.getDeclarationAtIndex(0).getAsCSSString()); + assertEquals ("p:dummy", aSR.getDeclarationAtIndex(1).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + assertEquals ("p", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:dummy", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (1) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (1)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (1)).getDeclarationCount()); + assertEquals (2, ((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleCount()); + assertEquals (".foobar", ((CSSStyleRule)aSR.getRuleAtIndex (1)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:green", ((CSSStyleRule)aSR.getRuleAtIndex (1)).getDeclarationAtIndex(0).getAsCSSString()); + assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(0)).getRuleCount()); + assertEquals ("color:blue", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(1) instanceof CSSNestedDeclarations); + assertEquals (1, ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(1)).getDeclarationCount()); + assertEquals ("color:white", ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(1)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (2) instanceof CSSNestedDeclarations); + assertEquals (1, ((CSSNestedDeclarations)aSR.getRuleAtIndex (2)).getDeclarationCount()); + assertEquals ("color:yellow", ((CSSNestedDeclarations)aSR.getRuleAtIndex (2)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (3) instanceof CSSMediaRule); + assertEquals (1, ((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleCount()); + assertTrue (((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getDeclarationCount()); + assertEquals (1, ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleCount()); + assertEquals (".print", ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:black", ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + assertTrue (((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getSelectorCount()); + assertEquals (2, ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getRuleCount()); + assertEquals ("&:hover", ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:orange", ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + assertEquals ("font-size:20px", ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationAtIndex(1).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (4) instanceof CSSLayerRule); + assertEquals (1, ((CSSLayerRule)aSR.getRuleAtIndex (4)).getSelectorCount()); + assertEquals (1, ((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleCount()); + assertEquals ("state", ((CSSLayerRule)aSR.getRuleAtIndex (4)).getSelectorAtIndex(0)); + assertTrue (((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getDeclarationCount()); + assertEquals (1, ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleCount()); + assertEquals (".alert", ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("background-color:brown", ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + assertTrue (((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getRuleCount()); + assertEquals ("p", ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("border:medium solid limegreen", ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + } + + @Test + public void testRead5 () + { + CSSStyleRule aSR = _parse (""" + div { + color: red; + .a1 { color: green; } + color: blue; + .a2 { color: orange; } + color: yellow; + .a3 { color: white; } + color: cyan; + } + """); + assertEquals (1, aSR.getSelectorCount ()); + assertEquals (1, aSR.getDeclarationCount ()); + assertEquals (6, aSR.getRuleCount ()); + + assertEquals ("div", aSR.getSelectorAtIndex(0).getAsCSSString()); assertEquals ("color:red", aSR.getDeclarationAtIndex(0).getAsCSSString()); - assertEquals (2, aSR.getRuleAtIndex (0).getDeclarationCount()); - assertEquals (1, aSR.getRuleAtIndex (0).getRuleCount()); + assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + assertEquals (".a1", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:green", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (1) instanceof CSSNestedDeclarations); + assertEquals (1, ((CSSNestedDeclarations)aSR.getRuleAtIndex (1)).getDeclarationCount()); + assertEquals ("color:blue", ((CSSNestedDeclarations)aSR.getRuleAtIndex (1)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (2) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (2)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (2)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (2)).getRuleCount()); + assertEquals (".a2", ((CSSStyleRule)aSR.getRuleAtIndex (2)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:orange", ((CSSStyleRule)aSR.getRuleAtIndex (2)).getDeclarationAtIndex(0).getAsCSSString()); - assertEquals ("color:green", aSR.getRuleAtIndex (0).getDeclarationAtIndex(0).getAsCSSString()); - assertEquals ("color:white", aSR.getRuleAtIndex (0).getDeclarationAtIndex(1).getAsCSSString()); + assertTrue (aSR.getRuleAtIndex (3) instanceof CSSNestedDeclarations); + assertEquals (1, ((CSSNestedDeclarations)aSR.getRuleAtIndex (3)).getDeclarationCount()); + assertEquals ("color:yellow", ((CSSNestedDeclarations)aSR.getRuleAtIndex (3)).getDeclarationAtIndex(0).getAsCSSString()); - assertEquals (1, aSR.getRuleAtIndex (0).getRuleAtIndex(0).getDeclarationCount()); - assertEquals (0, aSR.getRuleAtIndex (0).getRuleAtIndex(0).getRuleCount()); + assertTrue (aSR.getRuleAtIndex (4) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (4)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (4)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (4)).getRuleCount()); + assertEquals (".a3", ((CSSStyleRule)aSR.getRuleAtIndex (4)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:white", ((CSSStyleRule)aSR.getRuleAtIndex (4)).getDeclarationAtIndex(0).getAsCSSString()); - assertEquals ("color:blue", aSR.getRuleAtIndex (0).getRuleAtIndex(0).getDeclarationAtIndex(0).getAsCSSString()); + assertTrue (aSR.getRuleAtIndex (5) instanceof CSSNestedDeclarations); + assertEquals (1, ((CSSNestedDeclarations)aSR.getRuleAtIndex (5)).getDeclarationCount()); + assertEquals ("color:cyan", ((CSSNestedDeclarations)aSR.getRuleAtIndex (5)).getDeclarationAtIndex(0).getAsCSSString()); } } diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSSupportsRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSSupportsRuleTest.java index e8b6e735..b66b510c 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSSupportsRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSSupportsRuleTest.java @@ -28,6 +28,8 @@ import com.helger.css.reader.CSSReader; import com.helger.unittest.support.TestHelper; +import java.util.List; + /** * Test class for {@link CSSSupportsRule}. * @@ -128,4 +130,43 @@ public void testRead2 () .addDeclaration ("color", CSSExpression.createSimple ("red"), false)); TestHelper.testDefaultImplementationWithEqualContentObject (aSR, aCreated); } + + @Test + public void testRead3 () + { + CSSSupportsRule aSR = _parse (String.join("\n", List.of( + "@supports(column-count: 1) {", + " .foo {", + " color: white;", + " .bar {", + " color: orange", + " }", + " color: black;", + " }", + "}"))); + + assertEquals (1, aSR.getSupportsConditionMemberCount ()); + assertEquals (1, aSR.getRuleCount ()); + + assertEquals ("(column-count:1)", aSR.getSupportsConditionMemberAtIndex (0).getAsCSSString()); + + assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); + assertEquals (2, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + + assertEquals (".foo", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals (".foo", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); + + assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorCount()); + assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationCount()); + assertEquals (0, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getRuleCount()); + assertEquals (".bar", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("color:orange", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + + assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1) instanceof CSSNestedDeclarations); + assertEquals (1, ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationCount()); + assertEquals ("color:black", ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationAtIndex(0).getAsCSSString()); + } } diff --git a/ph-css/src/test/java/com/helger/css/writer/CSSWriterSettingsTest.java b/ph-css/src/test/java/com/helger/css/writer/CSSWriterSettingsTest.java index 0959b6cc..b1aa3d06 100644 --- a/ph-css/src/test/java/com/helger/css/writer/CSSWriterSettingsTest.java +++ b/ph-css/src/test/java/com/helger/css/writer/CSSWriterSettingsTest.java @@ -17,10 +17,12 @@ package com.helger.css.writer; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import com.helger.base.system.ENewLineMode; import org.jspecify.annotations.NonNull; import org.junit.Test; @@ -36,19 +38,21 @@ public final class CSSWriterSettingsTest extends AbstractCSSTestCase { private static void _checkDefault (@NonNull final ICSSWriterSettings aSettings) { - assertTrue (CSSWriterSettings.DEFAULT_OPTIMIZED_OUTPUT == aSettings.isOptimizedOutput ()); - assertTrue (CSSWriterSettings.DEFAULT_REMOVE_UNNECESSARY_CODE == aSettings.isRemoveUnnecessaryCode ()); - assertSame (CSSWriterSettings.DEFAULT_NEW_LINE_MODE, aSettings.getNewLineMode ()); - assertEquals (CSSWriterSettings.DEFAULT_INDENT, aSettings.getIndent (1)); - assertTrue (CSSWriterSettings.DEFAULT_QUOTE_URLS == aSettings.isQuoteURLs ()); - assertTrue (CSSWriterSettings.DEFAULT_WRITE_NAMESPACE_RULES == aSettings.isWriteNamespaceRules ()); - assertTrue (CSSWriterSettings.DEFAULT_WRITE_FONT_FACE_RULES == aSettings.isWriteFontFaceRules ()); - assertTrue (CSSWriterSettings.DEFAULT_WRITE_KEYFRAMES_RULES == aSettings.isWriteKeyframesRules ()); - assertTrue (CSSWriterSettings.DEFAULT_WRITE_MEDIA_RULES == aSettings.isWriteMediaRules ()); - assertTrue (CSSWriterSettings.DEFAULT_WRITE_PAGE_RULES == aSettings.isWritePageRules ()); - assertTrue (CSSWriterSettings.DEFAULT_WRITE_VIEWPORT_RULES == aSettings.isWriteViewportRules ()); - assertTrue (CSSWriterSettings.DEFAULT_WRITE_SUPPORTS_RULES == aSettings.isWriteSupportsRules ()); - assertTrue (CSSWriterSettings.DEFAULT_WRITE_UNKNOWN_RULES == aSettings.isWriteUnknownRules ()); + assertFalse (aSettings.isOptimizedOutput ()); + assertFalse (aSettings.isRemoveUnnecessaryCode ()); + assertSame (ENewLineMode.UNIX, aSettings.getNewLineMode ()); + assertEquals (" ", aSettings.getIndent (1)); + assertFalse (aSettings.isQuoteURLs ()); + assertTrue (aSettings.isWriteNamespaceRules ()); + assertTrue (aSettings.isWriteNestedDeclarations ()); + assertTrue (aSettings.isWriteFontFaceRules ()); + assertTrue (aSettings.isWriteKeyframesRules ()); + assertTrue (aSettings.isWriteLayerRules ()); + assertTrue (aSettings.isWriteMediaRules ()); + assertTrue (aSettings.isWritePageRules ()); + assertTrue (aSettings.isWriteViewportRules ()); + assertTrue (aSettings.isWriteSupportsRules ()); + assertTrue (aSettings.isWriteUnknownRules ()); } @Test diff --git a/ph-css/src/test/java/com/helger/css/writer/CSSWriterTest.java b/ph-css/src/test/java/com/helger/css/writer/CSSWriterTest.java index aa51271f..1a48d0dd 100644 --- a/ph-css/src/test/java/com/helger/css/writer/CSSWriterTest.java +++ b/ph-css/src/test/java/com/helger/css/writer/CSSWriterTest.java @@ -26,6 +26,8 @@ import com.helger.css.decl.CascadingStyleSheet; import com.helger.css.reader.CSSReader; +import java.util.List; + /** * Test class for class {@link CSSWriter}. * @@ -394,4 +396,418 @@ public void testWriteCertainRules () assertNotNull (aCSS); assertEquals ("h1{color:red;margin:1px}h2{color:red;margin:1px}", aWriter.getCSSAsString (aCSS)); } + + @Test + public void testAllRulesWithPrettyPrinting () + { + final CascadingStyleSheet aCSS = CSSReader.readFromString (""" + @charset "UTF-8"; + @import url("x.css"); + @import url("y.css"); + @namespace url(http://www.w3.org/1999/xhtml); + @namespace svg url(http://www.w3.org/2000/svg); + div { + color: red; + p: dummy; + div {} + p { + color: dummy; + } + span { + color: dummy; + margin: 0; + } + .foobar { + color: green; + element {} + #id { + color: blue + } + .class { + color: blue; + padding: 0; + } + color: white + } + color: yellow; + background-color: purple; + font-size: 12px; + @media print {} + @media print { + .print { + color: white; + } + } + @media print { + .print { + color: black; + &:hover { + color: orange; + font-size: 20px; + } + } + .pretty-print { + color: pink; + } + } + @layer state; + @layer state { .alert { color: green; } } + @layer state { + .alert { + background-color: brown; + p { + border: medium solid limegreen; + } + } + .warning { + background-color: red; + } + } + } + @font-face {} + @font-face { font-family: x; } + @font-face { + font-family: x; + src: url(x.woff2) format("woff2"); + } + @keyframes anim1 {} + @keyframes anim2 { from { opacity: 0.5; }} + @keyframes anim3 { from { opacity: 0; } to { opacity: 1; font-size: 12px; } } + @supports (display: grid) {} + @supports (display: grid) { .grid { display: grid; gap: 10px; } } + @supports (display: grid) { + .grid { + display: grid; + } + .grid { + gap: 10px; + } + } + @page {} + @page :first {} + @page :first { margin: 0; } + @page :first { + margin: 0; + padding: 0; + } + @page :first { + @top-center { content: "Preliminary edition" } + } + @page :first { + @bottom-left { } + @top-center { content: "Preliminary edition" } + @bottom-center { content: counter(page); color: violet; } + } + @viewport {} + @viewport { width: device-width; } + @viewport { + width: device-width; + height: device-height; + } + @unknown {} + @unknown { a: b; } + @unknown { a: b; c: d; } + @unknown { + a: b; + c: d; + } + @unknown { .foo { a: b; c: d; } }"""); + + assertNotNull (aCSS); + final CSSWriterSettings aSettings = new CSSWriterSettings ().setOptimizedOutput (false); + final String sPrinted = new CSSWriter (aSettings).setFooterText("end-of-file").setContentCharset ("utf-8").getCSSAsString (aCSS); + + assertEquals (""" + /* + * THIS FILE IS GENERATED - DO NOT EDIT + */ + @charset "utf-8"; + + @import url(x.css); + @import url(y.css); + + @namespace url(http://www.w3.org/1999/xhtml); + @namespace svg url(http://www.w3.org/2000/svg); + + div { + color:red; + p:dummy; + + div {} + + p { color:dummy; } + + span { + color:dummy; + margin:0; + } + + .foobar { + color:green; + + element {} + + #id { color:blue; } + + .class { + color:blue; + padding:0; + } + + color:white; + } + + color:yellow; + background-color:purple; + font-size:12px; + + @media print {} + + @media print { + .print { color:white; } + } + + @media print { + .print { + color:black; + + &:hover { + color:orange; + font-size:20px; + } + } + + .pretty-print { color:pink; } + } + + @layer state; + + @layer state { + .alert { color:green; } + } + + @layer state { + .alert { + background-color:brown; + + p { border:medium solid limegreen; } + } + .warning { background-color:red; } + } + } + + @font-face {} + + @font-face { font-family:x; } + + @font-face { + font-family:x; + src:url(x.woff2) format("woff2"); + } + + @keyframes anim1 {} + + @keyframes anim2 { + from { opacity:0.5; } + } + + @keyframes anim3 { + from { opacity:0; } + to { + opacity:1; + font-size:12px; + } + } + + @supports (display:grid) {} + + @supports (display:grid) { + .grid { + display:grid; + gap:10px; + } + } + + @supports (display:grid) { + .grid { display:grid; } + .grid { gap:10px; } + } + + @page {} + + @page :first {} + + @page :first { margin:0; } + + @page :first { + margin:0; + padding:0; + } + + @page :first { @top-center { content:"Preliminary edition"; } } + + @page :first { + @bottom-left {} + @top-center { content:"Preliminary edition"; } + @bottom-center { + content:counter(page); + color:violet; + } + } + + @viewport {} + + @viewport { width:device-width; } + + @viewport { + width:device-width; + height:device-height; + } + + @unknown {} + + @unknown { + a: b; + } + + @unknown { + a: b; c: d; + } + + @unknown { + a: b; + c: d; + } + + @unknown { + .foo { a: b; c: d; } + } + /* + * end-of-file + */ + """, sPrinted); + } + + @Test + public void testAllRulesWithOptimizedPrinting () + { + final CascadingStyleSheet aCSS = CSSReader.readFromString (""" + @charset "UTF-8"; + @import url("x.css"); + @import url("y.css"); + @namespace url(http://www.w3.org/1999/xhtml); + @namespace svg url(http://www.w3.org/2000/svg); + div { + color: red; + p: dummy; + div {} + p { + color: dummy; + } + span { + color: dummy; + margin: 0; + } + .foobar { + color: green; + element {} + #id { + color: blue + } + .class { + color: blue; + padding: 0; + } + color: white + } + color: yellow; + background-color: purple; + font-size: 12px; + @media print {} + @media print { + .print { + color: white; + } + } + @media print { + .print { + color: black; + &:hover { + color: orange; + font-size: 20px; + } + } + .pretty-print { + color: pink; + } + } + @layer state; + @layer state { .alert { color: green; } } + @layer state { + .alert { + background-color: brown; + p { + border: medium solid limegreen; + } + } + .warning { + background-color: red; + } + } + } + @font-face {} + @font-face { font-family: x; } + @font-face { + font-family: x; + src: url(x.woff2) format("woff2"); + } + @keyframes anim1 {} + @keyframes anim2 { from { opacity: 0.5; }} + @keyframes anim3 { from { opacity: 0; } to { opacity: 1; font-size: 12px; } } + @supports (display: grid) {} + @supports (display: grid) { .grid { display: grid; gap: 10px; } } + @supports (display: grid) { + .grid { + display: grid; + } + .grid { + gap: 10px; + } + } + @page {} + @page :first {} + @page :first { margin: 0; } + @page :first { + margin: 0; + padding: 0; + } + @page :first { + @top-center { content: "Preliminary edition" } + } + @page :first { + @bottom-left { } + @top-center { content: "Preliminary edition" } + @bottom-center { content: counter(page); color: violet; } + } + @viewport {} + @viewport { width: device-width; } + @viewport { + width: device-width; + height: device-height; + } + @unknown {} + @unknown { a: b; } + @unknown { a: b; c: d; } + @unknown { + a: b; + c: d; + } + @unknown { .foo { a: b; c: d; } }"""); + + assertNotNull (aCSS); + final CSSWriterSettings aSettings = new CSSWriterSettings ().setOptimizedOutput (true); + final String sPrinted = new CSSWriter (aSettings).setFooterText("end-of-file").setContentCharset ("utf-8").getCSSAsString (aCSS); + + assertEquals ("@charset \"utf-8\";@import url(x.css);@import url(y.css);@namespace url(http://www.w3.org/1999/xhtml);@namespace svg url(http://www.w3.org/2000/svg);div{color:red;p:dummy;div{}p{color:dummy}span{color:dummy;margin:0}.foobar{color:green;element{}#id{color:blue}.class{color:blue;padding:0}color:white}color:yellow;background-color:purple;font-size:12px;@media print{}@media print{.print{color:white}}@media print{.print{color:black;&:hover{color:orange;font-size:20px}}.pretty-print{color:pink}}@layer state;@layer state{.alert{color:green}}@layer state{.alert{background-color:brown;p{border:medium solid limegreen}}.warning{background-color:red}}}@font-face{}@font-face{font-family:x}@font-face{font-family:x;src:url(x.woff2) format(\"woff2\")}@keyframes anim1{}@keyframes anim2{from{opacity:0.5}}@keyframes anim3{from{opacity:0}to{opacity:1;font-size:12px}}@supports (display:grid){}@supports (display:grid){.grid{display:grid;gap:10px}}@supports (display:grid){.grid{display:grid}.grid{gap:10px}}@page{}@page :first{}@page :first{margin:0}@page :first{margin:0;padding:0}@page :first{@top-center{content:\"Preliminary edition\"}}@page :first{@bottom-left{}@top-center{content:\"Preliminary edition\"}@bottom-center{content:counter(page);color:violet}}@viewport{}@viewport{width:device-width}@viewport{width:device-width;height:device-height}@unknown{}@unknown{a: b;}@unknown{a: b; c: d;}@unknown{a: b;\n c: d;}@unknown{.foo { a: b; c: d; }}", sPrinted); + } } From e1a2682d11fb4b1bb2198fbcdfbdd99d55f7e7f8 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 3 Apr 2026 15:01:27 +0100 Subject: [PATCH 04/11] Update wpt CSS test css3-modsel-156* & is now a valid selector member. The test suite has changed the tests to use % instead of &, which is still invalid. See: https://github.com/web-platform-tests/wpt/blob/master/css/selectors/old-tests/css3-modsel-156.xml --- .../css30/bad_but_browsercompliant/css3-modsel-156.css | 2 +- .../css30/bad_but_browsercompliant/css3-modsel-156b.css | 2 +- .../css30/bad_but_browsercompliant/css3-modsel-156c.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156.css b/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156.css index 3383020e..0e811325 100644 --- a/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156.css +++ b/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156.css @@ -15,4 +15,4 @@ * limitations under the License. */ p { background: lime; } - foo & address, p { background: red; } + foo % address, p { background: red; } diff --git a/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156b.css b/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156b.css index 4fe7dd32..2895b911 100644 --- a/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156b.css +++ b/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156b.css @@ -14,5 +14,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - foo & address, p { background: red; } + foo % address, p { background: red; } p { background: lime; } diff --git a/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156c.css b/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156c.css index 36003477..cc021182 100644 --- a/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156c.css +++ b/ph-css/src/test/resources/testfiles/css30/bad_but_browsercompliant/css3-modsel-156c.css @@ -14,5 +14,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - foo & address, p { background: red ! important; } + foo % address, p { background: red ! important; } p { background: lime; } From 4716f2db19e11a3212d06977f15899df02292d95 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 3 Apr 2026 15:35:06 +0100 Subject: [PATCH 05/11] Update CSSVisitor for nested declarations and rules --- .../com/helger/css/decl/visit/CSSVisitor.java | 84 ++++++++++++++++++- .../css/decl/visit/CSSVisitorForUrl.java | 10 +++ .../css/decl/visit/DefaultCSSVisitor.java | 11 ++- .../helger/css/decl/visit/ICSSVisitor.java | 16 ++++ .../css/decl/visit/CSSVisitorFuncTest.java | 23 +++++ .../MockCountingDeclarationsVisitor.java | 26 ++++++ ...MockCountingNestedDeclarationsVisitor.java | 36 ++++++++ 7 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 ph-css/src/test/java/com/helger/css/decl/visit/MockCountingDeclarationsVisitor.java create mode 100644 ph-css/src/test/java/com/helger/css/decl/visit/MockCountingNestedDeclarationsVisitor.java diff --git a/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitor.java b/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitor.java index 42ad533c..9223c886 100644 --- a/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitor.java +++ b/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitor.java @@ -16,6 +16,9 @@ */ package com.helger.css.decl.visit; +import com.helger.css.decl.CSSNestedDeclarations; +import com.helger.css.decl.ICSSNestedRule; +import com.helger.css.decl.IHasCSSNestedRules; import org.jspecify.annotations.NonNull; import com.helger.annotation.concurrent.Immutable; @@ -85,7 +88,7 @@ public static void visitNamespaceRule (@NonNull final CSSNamespaceRule aNamespac } /** - * Visit all declarations contained in the passed declaration container. + * Visit all declarations contained in the given declaration container. * * @param aHasDeclarations * The declarations to be visited. May not be null. @@ -100,6 +103,22 @@ public static void visitAllDeclarations (@NonNull final IHasCSSDeclarations aVisitor.onDeclaration (aDeclaration); } + /** + * Visit all nested rules contained in the given rule container. + * + * @param aHasNestedRules + * The nested rules to be visited. May not be null. + * @param aVisitor + * The visitor to be invoked on each nested rule. May not be + * null. + */ + public static void visitAllNestedRules (@NonNull final IHasCSSNestedRules aHasNestedRules, @NonNull final ICSSVisitor aVisitor) + { + // for all nested rules + for (final ICSSNestedRule aNestedRule : aHasNestedRules.getAllRules ()) + visitNestedRule (aNestedRule, aVisitor); + } + /** * Visit all elements of a single style rule. * @@ -119,6 +138,9 @@ public static void visitStyleRule (@NonNull final CSSStyleRule aStyleRule, @NonN // for all declarations visitAllDeclarations (aStyleRule, aVisitor); + + // for all nested rules + visitAllNestedRules (aStyleRule, aVisitor); } finally { @@ -311,6 +333,23 @@ public static void visitLayerRule (@NonNull final CSSLayerRule aLayerRule, @NonN } } + /** + * Visit all elements of a single nested declarations. + * + * @param aNestedDeclarations + * The nested declarations to visit. May not be null. + * @param aVisitor + * The visitor to use. May not be null. + */ + public static void visitNestedDeclarations (@NonNull final CSSNestedDeclarations aNestedDeclarations, @NonNull final ICSSVisitor aVisitor) + { + aVisitor.onBeginNestedDeclarations(aNestedDeclarations); + for (final CSSDeclaration aDeclaration : aNestedDeclarations.getAllDeclarations()) { + aVisitor.onDeclaration(aDeclaration); + } + aVisitor.onEndNestedDeclarations(aNestedDeclarations); + } + /** * Visit all elements of a single unknown @ rule. * @@ -383,6 +422,49 @@ public static void visitTopLevelRule (@NonNull final ICSSTopLevelRule aTopLevelR throw new IllegalStateException ("Top level rule " + aTopLevelRule + " is unsupported!"); } + /** + * Visit all elements of a single {@link ICSSNestedRule nested rule}. + * + * @param aNestedRule + * The nested rule to visit. May not be null. + * @param aVisitor + * The visitor to use. May not be null. + */ + public static void visitNestedRule (@NonNull final ICSSNestedRule aNestedRule, @NonNull final ICSSVisitor aVisitor) + { + if (aNestedRule instanceof CSSStyleRule) + { + visitStyleRule ((CSSStyleRule) aNestedRule, aVisitor); + } + else + if (aNestedRule instanceof CSSMediaRule) + { + visitMediaRule ((CSSMediaRule) aNestedRule, aVisitor); + } + else + if (aNestedRule instanceof CSSSupportsRule) + { + visitSupportsRule ((CSSSupportsRule) aNestedRule, aVisitor); + } + else + if (aNestedRule instanceof CSSLayerRule) + { + visitLayerRule ((CSSLayerRule) aNestedRule, aVisitor); + } + else + if (aNestedRule instanceof CSSNestedDeclarations) + { + visitNestedDeclarations ((CSSNestedDeclarations) aNestedRule, aVisitor); + } + else + if (aNestedRule instanceof CSSUnknownRule) + { + visitUnknownRule ((CSSUnknownRule) aNestedRule, aVisitor); + } + else + throw new IllegalStateException ("Nested rule " + aNestedRule + " is unsupported!"); + } + /** * Visit all CSS elements in the order of their declaration. import rules come * first, namespace rules come next and all other top-level rules in the order diff --git a/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitorForUrl.java b/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitorForUrl.java index 3d055e50..21830bfc 100644 --- a/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitorForUrl.java +++ b/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitorForUrl.java @@ -245,6 +245,16 @@ public void onEndLayerRule (@NonNull final CSSLayerRule aLayerRule) m_aTopLevelRule.pop(); } + @Override + public void onBeginNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) { + // no action + } + + @Override + public void onEndNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) { + // no action + } + public void onUnknownRule (@NonNull final CSSUnknownRule aUnknownRule) { // no action diff --git a/ph-css/src/main/java/com/helger/css/decl/visit/DefaultCSSVisitor.java b/ph-css/src/main/java/com/helger/css/decl/visit/DefaultCSSVisitor.java index d857624b..6d0c7a9f 100644 --- a/ph-css/src/main/java/com/helger/css/decl/visit/DefaultCSSVisitor.java +++ b/ph-css/src/main/java/com/helger/css/decl/visit/DefaultCSSVisitor.java @@ -16,6 +16,7 @@ */ package com.helger.css.decl.visit; +import com.helger.css.decl.CSSNestedDeclarations; import org.jspecify.annotations.NonNull; import com.helger.annotation.concurrent.Immutable; @@ -147,7 +148,15 @@ public void onBeginLayerRule (@NonNull final CSSLayerRule aLayerRule) @OverrideOnDemand public void onEndLayerRule (@NonNull final CSSLayerRule aLayerRule) {} - + + @Override + public void onBeginNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) + {} + + @Override + public void onEndNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) + {} + @OverrideOnDemand public void onUnknownRule (@NonNull final CSSUnknownRule aUnknownRule) {} diff --git a/ph-css/src/main/java/com/helger/css/decl/visit/ICSSVisitor.java b/ph-css/src/main/java/com/helger/css/decl/visit/ICSSVisitor.java index 002f7d14..3d5faaae 100644 --- a/ph-css/src/main/java/com/helger/css/decl/visit/ICSSVisitor.java +++ b/ph-css/src/main/java/com/helger/css/decl/visit/ICSSVisitor.java @@ -16,6 +16,7 @@ */ package com.helger.css.decl.visit; +import com.helger.css.decl.CSSNestedDeclarations; import org.jspecify.annotations.NonNull; import com.helger.css.decl.CSSDeclaration; @@ -262,6 +263,21 @@ public interface ICSSVisitor */ void onEndLayerRule (@NonNull CSSLayerRule aLayerRule); + /** + * Called when a nested declarations rule starts. + * @param aNestedDeclarations + * The nested declarations. Never null. + */ + void onBeginNestedDeclarations (@NonNull CSSNestedDeclarations aNestedDeclarations); + + /** + * Called when a nested declarations rule ends. + * + * @param aNestedDeclarations + * The nested declarations. Never null. + */ + void onEndNestedDeclarations (@NonNull CSSNestedDeclarations aNestedDeclarations); + // unknown rules /** * Called when an unknown rule is encountered. diff --git a/ph-css/src/test/java/com/helger/css/decl/visit/CSSVisitorFuncTest.java b/ph-css/src/test/java/com/helger/css/decl/visit/CSSVisitorFuncTest.java index 1346dbd8..977ccd3d 100644 --- a/ph-css/src/test/java/com/helger/css/decl/visit/CSSVisitorFuncTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/visit/CSSVisitorFuncTest.java @@ -21,7 +21,9 @@ import java.io.File; import java.nio.charset.StandardCharsets; +import java.util.List; +import com.helger.css.decl.CSSStyleRule; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -105,4 +107,25 @@ public void testVisitConstantCSS () CSSVisitor.visitCSSUrl (aCSS, aVisitor); assertEquals (8, aVisitor.getCount ()); } + + @Test + public void testVisitNestedDeclarations() { + CascadingStyleSheet aSheet = CSSReader.readFromString(".foo { color: red; .bar { color: green; } color: blue; }"); + CSSStyleRule aStyleRule = aSheet.getStyleRuleAtIndex(0); + MockCountingNestedDeclarationsVisitor aVisitor = new MockCountingNestedDeclarationsVisitor(); + CSSVisitor.visitStyleRule(aStyleRule, aVisitor); + assertEquals(1, aVisitor.getBeginNestedDeclarationsCount()); + assertEquals(1, aVisitor.getEndNestedDeclarationsCount()); + assertEquals(List.of("color:blue;"), aVisitor.getNestedDeclarations()); + } + + @Test + public void testVisitDeclarations() { + CascadingStyleSheet aSheet = CSSReader.readFromString(".foo { color: red; .bar { color: green; } color: blue; }"); + CSSStyleRule aStyleRule = aSheet.getStyleRuleAtIndex(0); + MockCountingDeclarationsVisitor aVisitor = new MockCountingDeclarationsVisitor(); + CSSVisitor.visitStyleRule(aStyleRule, aVisitor); + assertEquals(3, aVisitor.getDeclarationCount()); + assertEquals(List.of("color:red", "color:green", "color:blue"), aVisitor.getDeclarations()); + } } diff --git a/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingDeclarationsVisitor.java b/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingDeclarationsVisitor.java new file mode 100644 index 00000000..67d52e56 --- /dev/null +++ b/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingDeclarationsVisitor.java @@ -0,0 +1,26 @@ +package com.helger.css.decl.visit; + +import com.helger.css.decl.CSSDeclaration; +import org.jspecify.annotations.NonNull; + +import java.util.ArrayList; +import java.util.List; + +class MockCountingDeclarationsVisitor extends DefaultCSSVisitor { + private int m_nDeclaration = 0; + private final List declarations = new ArrayList<>(); + + @Override + public void onDeclaration(@NonNull CSSDeclaration aDeclaration) { + m_nDeclaration++; + declarations.add(aDeclaration.getAsCSSString()); + } + + public int getDeclarationCount() { + return m_nDeclaration; + } + + public List getDeclarations() { + return declarations; + } +} diff --git a/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingNestedDeclarationsVisitor.java b/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingNestedDeclarationsVisitor.java new file mode 100644 index 00000000..53dafa0b --- /dev/null +++ b/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingNestedDeclarationsVisitor.java @@ -0,0 +1,36 @@ +package com.helger.css.decl.visit; + +import com.helger.css.decl.CSSNestedDeclarations; +import org.jspecify.annotations.NonNull; + +import java.util.ArrayList; +import java.util.List; + +class MockCountingNestedDeclarationsVisitor extends DefaultCSSVisitor { + private int m_nBeginNestedDeclarations = 0; + private int m_nEndNestedDeclarations = 0; + private final List nestedDeclaration = new ArrayList<>(); + + @Override + public void onBeginNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) { + m_nBeginNestedDeclarations++; + } + + @Override + public void onEndNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) { + m_nEndNestedDeclarations++; + nestedDeclaration.add(aNestedDeclarations.getAsCSSString()); + } + + public int getBeginNestedDeclarationsCount() { + return m_nBeginNestedDeclarations; + } + + public int getEndNestedDeclarationsCount() { + return m_nEndNestedDeclarations; + } + + public List getNestedDeclarations() { + return nestedDeclaration; + } +} From 790a5357393c506285748d5c47019c76d083a40a Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 3 Apr 2026 17:06:14 +0100 Subject: [PATCH 06/11] Add support for relative selectors in nested style rules support for relative selectors in nested style rules, e.g. .foo { > .bar { ... } } When a style rule is nested, it allows a , not just a . But at the top-level, a is not allowed, i.e. the following is invalid: > .bar { ... } To prevent the grammar from blowing up, always allow in a style rule, even at the top-level. Check for disallowed at the top level when mapping the parsed AST to the domain model. Issue a CSS interpretation error if such an invalid relative selector is encountered. --- .../css/handler/CSSNodeToDomainObject.java | 63 +++++--- ph-css/src/main/jjtree/ParserCSS30.jjt | 9 +- .../com/helger/css/decl/CSSSelectorTest.java | 148 ++++++++++++++++-- .../CollectingCSSInterpretErrorHandler.java | 38 +++++ 4 files changed, 213 insertions(+), 45 deletions(-) create mode 100644 ph-css/src/test/java/com/helger/css/utils/CollectingCSSInterpretErrorHandler.java diff --git a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java index 36bd9e59..bf655ad1 100644 --- a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java +++ b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java @@ -779,7 +779,8 @@ private void _readStyleDeclarationList (@NonNull final CSSNode aNode, private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aNode, @NonNull final Consumer aDeclarationConsumer, - @NonNull final Consumer aNestedRuleConsumer) + @NonNull final Consumer aNestedRuleConsumer, + final int nStyleRuleCount) { _expectNodeType (aNode, ECSSNodeType.STYLEDECLARATIONLISTWITHNESTED); // Read all contained declarations and rules @@ -806,7 +807,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN { if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) aNestedRuleConsumer.accept (aNestedDeclarations); - final CSSStyleRule aRule = _createStyleRule (aChildNode); + final CSSStyleRule aRule = _createStyleRule (aChildNode, nStyleRuleCount + 1); if (aRule != null) aNestedRuleConsumer.accept (aRule); aNestedDeclarations = new CSSNestedDeclarations(); @@ -816,7 +817,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN { if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) aNestedRuleConsumer.accept (aNestedDeclarations); - final CSSMediaRule aRule = _createMediaRule (aChildNode); + final CSSMediaRule aRule = _createMediaRule (aChildNode, nStyleRuleCount); if (aRule != null) aNestedRuleConsumer.accept (aRule); aNestedDeclarations = new CSSNestedDeclarations(); @@ -826,7 +827,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN { if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) aNestedRuleConsumer.accept (aNestedDeclarations); - final CSSSupportsRule aRule = _createSupportsRule (aChildNode); + final CSSSupportsRule aRule = _createSupportsRule (aChildNode, nStyleRuleCount); if (aRule != null) aNestedRuleConsumer.accept (aRule); aNestedDeclarations = new CSSNestedDeclarations(); @@ -836,7 +837,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN { if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) aNestedRuleConsumer.accept (aNestedDeclarations); - final CSSLayerRule aRule = _createLayerRule (aChildNode); + final CSSLayerRule aRule = _createLayerRule (aChildNode, nStyleRuleCount); if (aRule != null) aNestedRuleConsumer.accept (aRule); aNestedDeclarations = new CSSNestedDeclarations(); @@ -860,7 +861,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN } @Nullable - private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode) + private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode, final int nStyleRuleCount) { _expectNodeType (aNode, ECSSNodeType.STYLERULE); final CSSStyleRule ret = new CSSStyleRule (); @@ -876,6 +877,16 @@ private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode) ret.addSelector (_createSelector (aChildNode)); } + else if (ECSSNodeType.RELATIVESELECTOR.isNode (aChildNode)) + { + if (!bSelectors) + m_aErrorHandler.onCSSInterpretationError ("Found a selector after a declaration!"); + + if (nStyleRuleCount == 0) + m_aErrorHandler.onCSSInterpretationError ("Relative selectors are not allowed at the top level!"); + + ret.addSelector (_createRelativeSelector (aChildNode)); + } else { // OK, we're after the selectors @@ -883,7 +894,7 @@ private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode) if (ECSSNodeType.STYLEDECLARATIONLISTWITHNESTED.isNode (aChildNode)) { // Read all contained declarations and nested rules - _readStyleDeclarationListWithNestedRules (aChildNode, ret::addDeclaration, ret::addRule); + _readStyleDeclarationListWithNestedRules (aChildNode, ret::addDeclaration, ret::addRule, nStyleRuleCount); } else if (!ECSSNodeType.isErrorNode (aChildNode)) @@ -964,7 +975,7 @@ private CSSPageRule _createPageRule (@NonNull final CSSNode aNode) } @NonNull - private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode) + private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode, final int nStyleRuleCount) { _expectNodeType (aNode, ECSSNodeType.MEDIARULE); final CSSMediaRule ret = new CSSMediaRule (); @@ -980,7 +991,7 @@ private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode) else if (ECSSNodeType.STYLERULE.isNode (aChildNode)) { - final CSSStyleRule aStyleRule = _createStyleRule (aChildNode); + final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, nStyleRuleCount); if (aStyleRule != null) ret.addRule (aStyleRule); } @@ -988,7 +999,7 @@ private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode) if (ECSSNodeType.MEDIARULE.isNode (aChildNode)) { // Nested media rules are OK! - ret.addRule (_createMediaRule (aChildNode)); + ret.addRule (_createMediaRule (aChildNode, nStyleRuleCount)); } else if (ECSSNodeType.PAGERULE.isNode (aChildNode)) @@ -1004,10 +1015,10 @@ private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode) ret.addRule (_createViewportRule (aChildNode)); else if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode)) - ret.addRule (_createSupportsRule (aChildNode)); + ret.addRule (_createSupportsRule (aChildNode, nStyleRuleCount)); else if (ECSSNodeType.LAYERRULE.isNode (aChildNode)) - ret.addRule (_createLayerRule (aChildNode)); + ret.addRule (_createLayerRule (aChildNode, nStyleRuleCount)); else if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode)) { @@ -1155,7 +1166,7 @@ private CSSFontFaceRule _createFontFaceRule (@NonNull final CSSNode aNode) } @NonNull - private CSSLayerRule _createLayerRule (@NonNull final CSSNode aNode) + private CSSLayerRule _createLayerRule (@NonNull final CSSNode aNode, final int nStyleRuleCount) { _expectNodeType (aNode, ECSSNodeType.LAYERRULE); final int nChildCount = aNode.jjtGetNumChildren (); @@ -1193,19 +1204,19 @@ private CSSLayerRule _createLayerRule (@NonNull final CSSNode aNode) final CSSNode aBodyChildNode = aBodyNode.jjtGetChild (nIndex); if (ECSSNodeType.STYLERULE.isNode (aBodyChildNode)) { - final CSSStyleRule aStyleRule = _createStyleRule (aBodyChildNode); + final CSSStyleRule aStyleRule = _createStyleRule (aBodyChildNode, nStyleRuleCount); if (aStyleRule != null) ret.addRule (aStyleRule); } else if (ECSSNodeType.LAYERRULE.isNode (aBodyChildNode)) - ret.addRule (_createLayerRule (aBodyChildNode)); + ret.addRule (_createLayerRule (aBodyChildNode, nStyleRuleCount)); else if (ECSSNodeType.MEDIARULE.isNode (aBodyChildNode)) - ret.addRule (_createMediaRule (aBodyChildNode)); + ret.addRule (_createMediaRule (aBodyChildNode, nStyleRuleCount)); else if (ECSSNodeType.SUPPORTSRULE.isNode (aBodyChildNode)) - ret.addRule (_createSupportsRule (aBodyChildNode)); + ret.addRule (_createSupportsRule (aBodyChildNode, nStyleRuleCount)); else if (ECSSNodeType.KEYFRAMESRULE.isNode (aBodyChildNode)) ret.addRule (_createKeyframesRule (aBodyChildNode)); @@ -1412,7 +1423,7 @@ private ICSSSupportsConditionMember _createSupportsConditionMemberRecursive (@No } @NonNull - private CSSSupportsRule _createSupportsRule (@NonNull final CSSNode aNode) + private CSSSupportsRule _createSupportsRule (@NonNull final CSSNode aNode, final int nStyleRuleCount) { _expectNodeType (aNode, ECSSNodeType.SUPPORTSRULE); final CSSSupportsRule ret = new CSSSupportsRule (); @@ -1432,13 +1443,13 @@ private CSSSupportsRule _createSupportsRule (@NonNull final CSSNode aNode) else if (ECSSNodeType.STYLERULE.isNode (aChildNode)) { - final CSSStyleRule aStyleRule = _createStyleRule (aChildNode); + final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, nStyleRuleCount); if (aStyleRule != null) ret.addRule (aStyleRule); } else if (ECSSNodeType.MEDIARULE.isNode (aChildNode)) - ret.addRule (_createMediaRule (aChildNode)); + ret.addRule (_createMediaRule (aChildNode, nStyleRuleCount)); else if (ECSSNodeType.PAGERULE.isNode (aChildNode)) ret.addRule (_createPageRule (aChildNode)); @@ -1453,10 +1464,10 @@ private CSSSupportsRule _createSupportsRule (@NonNull final CSSNode aNode) ret.addRule (_createViewportRule (aChildNode)); else if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode)) - ret.addRule (_createSupportsRule (aChildNode)); + ret.addRule (_createSupportsRule (aChildNode, nStyleRuleCount)); else if (ECSSNodeType.LAYERRULE.isNode (aChildNode)) - ret.addRule (_createLayerRule (aChildNode)); + ret.addRule (_createLayerRule (aChildNode, nStyleRuleCount)); else if (!ECSSNodeType.isErrorNode (aChildNode)) m_aErrorHandler.onCSSInterpretationError ("Unsupported supports-rule child: " + @@ -1512,7 +1523,7 @@ private void _recursiveFillCascadingStyleSheetFromNode (@NonNull final CSSNode a else if (ECSSNodeType.STYLERULE.isNode (aChildNode)) { - final CSSStyleRule aStyleRule = _createStyleRule (aChildNode); + final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, 0); if (aStyleRule != null) ret.addRule (aStyleRule); } @@ -1521,13 +1532,13 @@ private void _recursiveFillCascadingStyleSheetFromNode (@NonNull final CSSNode a ret.addRule (_createPageRule (aChildNode)); else if (ECSSNodeType.MEDIARULE.isNode (aChildNode)) - ret.addRule (_createMediaRule (aChildNode)); + ret.addRule (_createMediaRule (aChildNode, 0)); else if (ECSSNodeType.FONTFACERULE.isNode (aChildNode)) ret.addRule (_createFontFaceRule (aChildNode)); else if (ECSSNodeType.LAYERRULE.isNode (aChildNode)) - ret.addRule (_createLayerRule (aChildNode)); + ret.addRule (_createLayerRule (aChildNode, 0)); else if (ECSSNodeType.KEYFRAMESRULE.isNode (aChildNode)) ret.addRule (_createKeyframesRule (aChildNode)); @@ -1536,7 +1547,7 @@ private void _recursiveFillCascadingStyleSheetFromNode (@NonNull final CSSNode a ret.addRule (_createViewportRule (aChildNode)); else if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode)) - ret.addRule (_createSupportsRule (aChildNode)); + ret.addRule (_createSupportsRule (aChildNode, 0)); else if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode)) { diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index cc53973d..35a049da 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -1037,6 +1037,11 @@ void relativeSelector() : {} selector () } +void selectorOrRelativeSelector() #void : {} +{ + ( LOOKAHEAD( selectorCombinator() ) relativeSelector() | selector() ) +} + void relativeSelectorList() #void : {} { ( )* @@ -1326,11 +1331,11 @@ try { void styleRule() : {} { try{ - selector () + selectorOrRelativeSelector () ( )* ( ( )* - selector() + selectorOrRelativeSelector() ( )* )* styleDeclarationBlockWithNested() diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java index 06f4c460..2634be35 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java @@ -20,11 +20,15 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import com.helger.css.reader.CSSReaderSettings; +import com.helger.css.utils.CollectingCSSInterpretErrorHandler; import org.jspecify.annotations.NonNull; import org.junit.Test; import com.helger.css.reader.CSSReader; +import java.util.List; + /** * Test class for class {@link CSSSelector}. * @@ -32,10 +36,13 @@ */ public final class CSSSelectorTest { + private CollectingCSSInterpretErrorHandler m_aIEH = new CollectingCSSInterpretErrorHandler (); + @NonNull - private static CSSSelector _parse (@NonNull final String sCSS) + private CSSSelector _parse (@NonNull final String sCSS) { - final CascadingStyleSheet aCSS = CSSReader.readFromString (sCSS); + CSSReaderSettings aSettings = new CSSReaderSettings().setInterpretErrorHandler(m_aIEH); + final CascadingStyleSheet aCSS = CSSReader.readFromStringReader (sCSS, aSettings); assertNotNull (sCSS, aCSS); assertTrue (aCSS.hasStyleRules ()); assertEquals (1, aCSS.getStyleRuleCount ()); @@ -48,22 +55,44 @@ private static CSSSelector _parse (@NonNull final String sCSS) return aSel; } + @NonNull + private static CSSStyleRule _parseRule (@NonNull final String sCSS) + { + final CascadingStyleSheet aCSS = CSSReader.readFromString (sCSS); + assertNotNull (sCSS, aCSS); + assertTrue (aCSS.hasStyleRules ()); + assertEquals (1, aCSS.getStyleRuleCount ()); + final CSSStyleRule aStyle = aCSS.getAllStyleRules ().get (0); + assertNotNull (sCSS, aStyle); + return aStyle; + } + @Test - public void testRead () + public void testReadElementSelector () { - CSSSelector aSel; - aSel = _parse ("div { color:red }"); - assertEquals (1, aSel.getMemberCount ()); - assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); - assertEquals ("div", aSel.getMemberAtIndex (0).getAsCSSString ()); + CSSSelector aSel = _parse("div { color:red }"); + assertEquals(List.of(), m_aIEH.getErrors()); + assertEquals(1, aSel.getMemberCount()); + assertTrue(aSel.getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); + assertEquals("div", aSel.getMemberAtIndex(0).getAsCSSString()); + } - aSel = _parse ("#id { color:red }"); - assertEquals (1, aSel.getMemberCount ()); - assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); - assertEquals ("#id", aSel.getMemberAtIndex (0).getAsCSSString ()); - assertEquals ("#id", aSel.getAsCSSString ()); + @Test + public void testReadIdSelector () + { + CSSSelector aSel = _parse("#id { color:red }"); + assertEquals(List.of(), m_aIEH.getErrors()); + assertEquals(1, aSel.getMemberCount()); + assertTrue(aSel.getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); + assertEquals("#id", aSel.getMemberAtIndex(0).getAsCSSString()); + assertEquals("#id", aSel.getAsCSSString()); + } - aSel = _parse ("#id div { color:red }"); + @Test + public void testReadIdSpaceCombinator () + { + CSSSelector aSel = _parse ("#id div { color:red }"); + assertEquals(List.of(), m_aIEH.getErrors()); assertEquals (3, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals ("#id", aSel.getMemberAtIndex (0).getAsCSSString ()); @@ -72,8 +101,13 @@ public void testRead () assertTrue (aSel.getMemberAtIndex (2) instanceof CSSSelectorSimpleMember); assertEquals ("div", aSel.getMemberAtIndex (2).getAsCSSString ()); assertEquals ("#id div", aSel.getAsCSSString ()); + } - aSel = _parse ("#id ~ div { color:red }"); + @Test + public void testReadWaveDashCombinator () + { + CSSSelector aSel = _parse ("#id ~ div { color:red }"); + assertEquals(List.of(), m_aIEH.getErrors()); assertEquals (3, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals ("#id", aSel.getMemberAtIndex (0).getAsCSSString ()); @@ -82,15 +116,23 @@ public void testRead () assertTrue (aSel.getMemberAtIndex (2) instanceof CSSSelectorSimpleMember); assertEquals ("div", aSel.getMemberAtIndex (2).getAsCSSString ()); assertEquals ("#id~div", aSel.getAsCSSString ()); + } - aSel = _parse ("&.foo { color:red }"); + @Test + public void testReadNestingSelectorAtStart() { + CSSSelector aSel = _parse ("&.foo { color:red }"); + assertEquals(List.of(), m_aIEH.getErrors()); assertEquals (2, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals ("&", aSel.getMemberAtIndex (0).getAsCSSString ()); assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelectorSimpleMember); assertEquals (".foo", aSel.getMemberAtIndex (1).getAsCSSString ()); + } - aSel = _parse (".foo & { color:red }"); + @Test + public void testReadNestingSelectorAtEnd() { + CSSSelector aSel = _parse (".foo & { color:red }"); + assertEquals(List.of(), m_aIEH.getErrors()); assertEquals (3, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals (".foo", aSel.getMemberAtIndex (0).getAsCSSString ()); @@ -99,4 +141,76 @@ public void testRead () assertTrue (aSel.getMemberAtIndex (2) instanceof CSSSelectorSimpleMember); assertEquals ("&", aSel.getMemberAtIndex (2).getAsCSSString ()); } + + @Test + public void testReadRelativeSelectorWithinStyleRuleWithWaveDash() { + CSSStyleRule aRule = _parseRule (".foo { ~ .bar { color:red } }"); + + assertEquals(List.of(), m_aIEH.getErrors()); + + assertEquals(".foo", aRule.getSelectorAtIndex(0).getAsCSSString()); + assertTrue(aRule.getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals(1, ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorCount()); + CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorAtIndex(0); + assertEquals (2, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof ECSSSelectorCombinator); + assertEquals ("~", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName()); + + assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelector); + assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); + assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0)).getValue()); + } + + @Test + public void testReadRelativeSelectorWithinStyleRuleWithPlus() { + CSSStyleRule aRule = _parseRule (".foo { + .bar { color:red } }"); + + assertEquals(List.of(), m_aIEH.getErrors()); + + assertEquals(".foo", aRule.getSelectorAtIndex(0).getAsCSSString()); + assertTrue(aRule.getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals(1, ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorCount()); + CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorAtIndex(0); + assertEquals (2, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof ECSSSelectorCombinator); + assertEquals ("+", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName()); + + assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelector); + assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); + assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0)).getValue()); + } + + @Test + public void testReadRelativeSelectorWithinStyleRuleWithGreater() { + CSSStyleRule aRule = _parseRule (".foo { > .bar { color:red } }"); + + assertEquals(List.of(), m_aIEH.getErrors()); + + assertEquals(".foo", aRule.getSelectorAtIndex(0).getAsCSSString()); + assertTrue(aRule.getRuleAtIndex(0) instanceof CSSStyleRule); + assertEquals(1, ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorCount()); + CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorAtIndex(0); + assertEquals (2, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof ECSSSelectorCombinator); + assertEquals (">", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName()); + + assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelector); + assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); + assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0)).getValue()); + } + + @Test + public void testReadRelativeSelectorAtTopLevel() { + CSSSelector aSel = _parse ("> .bar { color:red }"); + + assertEquals(List.of("Relative selectors are not allowed at the top level!"), m_aIEH.getErrors()); + + assertEquals (2, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof ECSSSelectorCombinator); + assertEquals (">", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName()); + + assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelector); + assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); + assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0)).getValue()); + } } diff --git a/ph-css/src/test/java/com/helger/css/utils/CollectingCSSInterpretErrorHandler.java b/ph-css/src/test/java/com/helger/css/utils/CollectingCSSInterpretErrorHandler.java new file mode 100644 index 00000000..5105a3f8 --- /dev/null +++ b/ph-css/src/test/java/com/helger/css/utils/CollectingCSSInterpretErrorHandler.java @@ -0,0 +1,38 @@ +package com.helger.css.utils; + +import com.helger.css.reader.errorhandler.ICSSInterpretErrorHandler; +import org.jspecify.annotations.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public final class CollectingCSSInterpretErrorHandler implements ICSSInterpretErrorHandler { + private static final Logger LOGGER = LoggerFactory.getLogger (CollectingCSSInterpretErrorHandler.class); + + private final List m_aWarnings = new ArrayList<>(); + private final List m_aErrors = new ArrayList<>(); + + @Override + public void onCSSInterpretationWarning(@NonNull String sMessage) { + m_aWarnings.add(sMessage); + LOGGER.warn (sMessage); + } + + @Override + public void onCSSInterpretationError(@NonNull String sMessage) { + m_aErrors.add(sMessage); + LOGGER.error (sMessage); + } + + @NonNull + public List getWarnings() { + return List.copyOf(m_aWarnings); + } + + @NonNull + public List getErrors() { + return List.copyOf(m_aErrors); + } +} From 27cd08b6070ecbc8a2f8a7afc95cd51e5e7c11f7 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 3 Apr 2026 18:00:18 +0100 Subject: [PATCH 07/11] Fix testfiles/css30/good/issue-gc-13.css The file is not actually good -- it's missing an rbrace! --- ph-css/src/test/resources/testfiles/css30/good/issue-gc-13.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ph-css/src/test/resources/testfiles/css30/good/issue-gc-13.css b/ph-css/src/test/resources/testfiles/css30/good/issue-gc-13.css index 92fb50a7..0253ccba 100644 --- a/ph-css/src/test/resources/testfiles/css30/good/issue-gc-13.css +++ b/ph-css/src/test/resources/testfiles/css30/good/issue-gc-13.css @@ -407,4 +407,4 @@ solid #fff}#contactstable p{font-size:18px;padding:0px 0px 20px 0px;margin:0px}#contactstable h4{font-size:20px;font-weight:bold;padding:0px -0px 5px 0px;margin:0px} +0px 5px 0px;margin:0px}} From 2ce53432322141411c5082e31b238983b25a676b Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 3 Apr 2026 18:10:05 +0100 Subject: [PATCH 08/11] Adjust test for different interpretation of missing rbrace in nested rule With the browser compliant flag enabled, the behavior did not change (empty string). With the browser compliant flag disabled, .class{color:red;.class{color:green}.class{color:blue} is now parsed as nothing. Previously, an error occurred at the second ".class", causing the parser to skip to the next rbrace. Now, the second ".class" is the start of a nested rule. The parser only encounters an error at the very end, when it finds a missing rbrace. The error recovery tries looking for the next rbrace, but finds none, only an EOF, causing it to discard the entire rule. --- .../test/java/com/helger/css/reader/CSSReaderFuncTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ph-css/src/test/java/com/helger/css/reader/CSSReaderFuncTest.java b/ph-css/src/test/java/com/helger/css/reader/CSSReaderFuncTest.java index 18e8b0b3..c350960b 100644 --- a/ph-css/src/test/java/com/helger/css/reader/CSSReaderFuncTest.java +++ b/ph-css/src/test/java/com/helger/css/reader/CSSReaderFuncTest.java @@ -354,9 +354,10 @@ public void testSpecialCasesAsString () // Parsing problem String sCSS = ".class{color:red;.class{color:green}.class{color:blue}"; aCSS = CSSReader.readFromStringReader (sCSS, aReaderSettings); - assertNotNull (aCSS); - assertEquals (bBrowserCompliantMode ? "" : ".class{color:red}.class{color:blue}", - new CSSWriter (aWriterSettings).getCSSAsString (aCSS)); + if (bBrowserCompliantMode) + assertEquals ("", new CSSWriter (aWriterSettings).getCSSAsString (aCSS)); + else + assertNull (aCSS); sCSS = " \n/* comment */\n \n.class{color:red;}"; aCSS = CSSReader.readFromStringReader (sCSS, aReaderSettings); From 15bf1f70b6ca02db3d5327ab80aa26104531c167 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 3 Apr 2026 18:23:51 +0100 Subject: [PATCH 09/11] Add test for distinction between &.foo{} and & .foo{} --- .../java/com/helger/css/decl/CSSSelectorTest.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java index 2634be35..4780cfdd 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java @@ -119,7 +119,7 @@ public void testReadWaveDashCombinator () } @Test - public void testReadNestingSelectorAtStart() { + public void testReadNestingSelectorAtStartWithoutSpace() { CSSSelector aSel = _parse ("&.foo { color:red }"); assertEquals(List.of(), m_aIEH.getErrors()); assertEquals (2, aSel.getMemberCount ()); @@ -129,6 +129,19 @@ public void testReadNestingSelectorAtStart() { assertEquals (".foo", aSel.getMemberAtIndex (1).getAsCSSString ()); } + @Test + public void testReadNestingSelectorAtStartWithSpace() { + CSSSelector aSel = _parse ("& .foo { color:red }"); + assertEquals(List.of(), m_aIEH.getErrors()); + assertEquals (3, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals ("&", aSel.getMemberAtIndex (0).getAsCSSString ()); + assertTrue (aSel.getMemberAtIndex (1) instanceof ECSSSelectorCombinator); + assertEquals (" ", aSel.getMemberAtIndex (1).getAsCSSString ()); + assertTrue (aSel.getMemberAtIndex (2) instanceof CSSSelectorSimpleMember); + assertEquals (".foo", aSel.getMemberAtIndex (2).getAsCSSString ()); + } + @Test public void testReadNestingSelectorAtEnd() { CSSSelector aSel = _parse (".foo & { color:red }"); From a6c9d3f9cc92272c5e497ca99a9e3de6b16d3e11 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 3 Apr 2026 18:40:20 +0100 Subject: [PATCH 10/11] Some coding style fixes Use prefix "s" for string variables Use prefix "m_" for instance fields As per https://github.com/phax/meta/blob/master/CodingStyleguide.md --- .../decl/CSSExpressionMemberLineNames.java | 12 +++++----- .../com/helger/css/decl/CSSLayerRule.java | 24 +++++++++---------- .../css/property/CSSPropertyColors.java | 4 ++-- .../css/property/CSSPropertyEnumOrColors.java | 4 ++-- .../property/CSSPropertyEnumOrNumbers.java | 4 ++-- .../MockCountingDeclarationsVisitor.java | 6 ++--- ...MockCountingNestedDeclarationsVisitor.java | 6 ++--- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSExpressionMemberLineNames.java b/ph-css/src/main/java/com/helger/css/decl/CSSExpressionMemberLineNames.java index 879f7be1..8a3e3af9 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSExpressionMemberLineNames.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSExpressionMemberLineNames.java @@ -62,22 +62,22 @@ public CSSExpressionMemberLineNames addMember (@NonNull @Nonempty final String s } @NonNull - public CSSExpressionMemberLineNames addMember (@Nonnegative final int nIndex, @NonNull @Nonempty final String aMember) + public CSSExpressionMemberLineNames addMember (@Nonnegative final int nIndex, @NonNull @Nonempty final String sMember) { ValueEnforcer.isGE0 (nIndex, "Index"); - ValueEnforcer.notNull (aMember, "Member"); + ValueEnforcer.notNull (sMember, "Member"); if (nIndex >= getMemberCount ()) - m_aMembers.add (aMember); + m_aMembers.add (sMember); else - m_aMembers.add (nIndex, aMember); + m_aMembers.add (nIndex, sMember); return this; } @NonNull - public EChange removeMember (@NonNull final String aMember) + public EChange removeMember (@NonNull final String sMember) { - return m_aMembers.removeObject (aMember); + return m_aMembers.removeObject (sMember); } @NonNull diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java index e76ef713..263eea34 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java @@ -74,16 +74,16 @@ public int getSelectorCount () /** * Adds a selector to the end of the selector list. - * @param aSelector The selector to be added. Must not be null. + * @param sSelector The selector to be added. Must not be null. * @return This rule for chaining. Never null. * @since 8.2.0 */ @NonNull - public CSSLayerRule addSelector (@NonNull final String aSelector) + public CSSLayerRule addSelector (@NonNull final String sSelector) { - ValueEnforcer.notNull (aSelector, "Selector"); + ValueEnforcer.notNull (sSelector, "Selector"); - m_aSelectors.add (aSelector); + m_aSelectors.add (sSelector); return this; } @@ -91,35 +91,35 @@ public CSSLayerRule addSelector (@NonNull final String aSelector) * Adds a selector at the specified index. If the index is greater than the current number of selectors, the selector * is added at the end of the list. * @param nIndex The index at which the selector should be added. Must be ≥ 0. - * @param aSelector The selector to be added. Must not be null. + * @param sSelector The selector to be added. Must not be null. * @return This rule for chaining. Never null. * @since 8.2.0 */ @NonNull - public CSSLayerRule addSelector (@Nonnegative final int nIndex, @NonNull final String aSelector) + public CSSLayerRule addSelector (@Nonnegative final int nIndex, @NonNull final String sSelector) { ValueEnforcer.isGE0 (nIndex, "Index"); - ValueEnforcer.notNull (aSelector, "Selector"); + ValueEnforcer.notNull (sSelector, "Selector"); if (nIndex >= getSelectorCount ()) - m_aSelectors.add (aSelector); + m_aSelectors.add (sSelector); else - m_aSelectors.add (nIndex, aSelector); + m_aSelectors.add (nIndex, sSelector); return this; } /** * Remove the specified selector, if present. * - * @param aSelector The selector to be removed. Must not be null. + * @param sSelector The selector to be removed. Must not be null. * @return {@link EChange#CHANGED} if the selector was removed, {@link EChange#UNCHANGED} if the selector was not found. * Never null. * @since 8.2.0 */ @NonNull - public EChange removeSelector (@NonNull final String aSelector) + public EChange removeSelector (@NonNull final String sSelector) { - return m_aSelectors.removeObject (aSelector); + return m_aSelectors.removeObject (sSelector); } /** diff --git a/ph-css/src/main/java/com/helger/css/property/CSSPropertyColors.java b/ph-css/src/main/java/com/helger/css/property/CSSPropertyColors.java index c799bce8..205725dd 100644 --- a/ph-css/src/main/java/com/helger/css/property/CSSPropertyColors.java +++ b/ph-css/src/main/java/com/helger/css/property/CSSPropertyColors.java @@ -98,9 +98,9 @@ public boolean isValidValue (@Nullable final String sValue) return false; // Check each value - for (final String aPart : aParts) + for (final String sPart : aParts) { - final String sTrimmedPart = aPart.trim (); + final String sTrimmedPart = sPart.trim (); if (!super.isValidValue (sTrimmedPart) && !CSSColorHelper.isColorValue (sTrimmedPart)) return false; } diff --git a/ph-css/src/main/java/com/helger/css/property/CSSPropertyEnumOrColors.java b/ph-css/src/main/java/com/helger/css/property/CSSPropertyEnumOrColors.java index 893309af..b9f48852 100644 --- a/ph-css/src/main/java/com/helger/css/property/CSSPropertyEnumOrColors.java +++ b/ph-css/src/main/java/com/helger/css/property/CSSPropertyEnumOrColors.java @@ -104,9 +104,9 @@ public boolean isValidValue (@Nullable final String sValue) return false; // Check each value - for (final String aPart : aParts) + for (final String sPart : aParts) { - final String sTrimmedPart = aPart.trim (); + final String sTrimmedPart = sPart.trim (); if (!super.isValidValue (sTrimmedPart) && !CSSColorHelper.isColorValue (sTrimmedPart)) return false; } diff --git a/ph-css/src/main/java/com/helger/css/property/CSSPropertyEnumOrNumbers.java b/ph-css/src/main/java/com/helger/css/property/CSSPropertyEnumOrNumbers.java index 168a5fd4..c27e4fed 100644 --- a/ph-css/src/main/java/com/helger/css/property/CSSPropertyEnumOrNumbers.java +++ b/ph-css/src/main/java/com/helger/css/property/CSSPropertyEnumOrNumbers.java @@ -142,9 +142,9 @@ public boolean isValidValue (@Nullable final String sValue) if (aParts.length < m_nMinNumbers || aParts.length > m_nMaxNumbers) return false; - for (final String aPart : aParts) + for (final String sPart : aParts) { - final String sTrimmedPart = aPart.trim (); + final String sTrimmedPart = sPart.trim (); if (!super.isValidValue (sTrimmedPart) && !CSSNumberHelper.isValueWithUnit (sTrimmedPart, m_bWithPercentage)) return false; } diff --git a/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingDeclarationsVisitor.java b/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingDeclarationsVisitor.java index 67d52e56..c60675a8 100644 --- a/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingDeclarationsVisitor.java +++ b/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingDeclarationsVisitor.java @@ -8,12 +8,12 @@ class MockCountingDeclarationsVisitor extends DefaultCSSVisitor { private int m_nDeclaration = 0; - private final List declarations = new ArrayList<>(); + private final List m_sDeclarations = new ArrayList<>(); @Override public void onDeclaration(@NonNull CSSDeclaration aDeclaration) { m_nDeclaration++; - declarations.add(aDeclaration.getAsCSSString()); + m_sDeclarations.add(aDeclaration.getAsCSSString()); } public int getDeclarationCount() { @@ -21,6 +21,6 @@ public int getDeclarationCount() { } public List getDeclarations() { - return declarations; + return m_sDeclarations; } } diff --git a/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingNestedDeclarationsVisitor.java b/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingNestedDeclarationsVisitor.java index 53dafa0b..973724a1 100644 --- a/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingNestedDeclarationsVisitor.java +++ b/ph-css/src/test/java/com/helger/css/decl/visit/MockCountingNestedDeclarationsVisitor.java @@ -9,7 +9,7 @@ class MockCountingNestedDeclarationsVisitor extends DefaultCSSVisitor { private int m_nBeginNestedDeclarations = 0; private int m_nEndNestedDeclarations = 0; - private final List nestedDeclaration = new ArrayList<>(); + private final List m_sNestedDeclaration = new ArrayList<>(); @Override public void onBeginNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) { @@ -19,7 +19,7 @@ public void onBeginNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDecl @Override public void onEndNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) { m_nEndNestedDeclarations++; - nestedDeclaration.add(aNestedDeclarations.getAsCSSString()); + m_sNestedDeclaration.add(aNestedDeclarations.getAsCSSString()); } public int getBeginNestedDeclarationsCount() { @@ -31,6 +31,6 @@ public int getEndNestedDeclarationsCount() { } public List getNestedDeclarations() { - return nestedDeclaration; + return m_sNestedDeclaration; } } From 683de6afb49e1c86d2c145d24afa8c5a21ad9259 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Tue, 7 Apr 2026 19:14:09 +0100 Subject: [PATCH 11/11] Apply requested style changes #123 #94 --- .../com/helger/css/decl/CSSKeyframesRule.java | 4 +- .../css/decl/CSSNestedDeclarations.java | 19 +- .../java/com/helger/css/decl/CSSPageRule.java | 6 +- .../com/helger/css/decl/CSSStyleRule.java | 24 -- .../com/helger/css/decl/CSSWritableList.java | 2 +- .../css/decl/visit/CSSVisitorForUrl.java | 2 - .../css/decl/visit/DefaultCSSVisitor.java | 4 +- .../css/handler/CSSNodeToDomainObject.java | 54 ++--- .../helger/css/writer/CSSWriterSettings.java | 2 - .../com/helger/css/decl/CSSLayerRuleTest.java | 87 +++---- .../com/helger/css/decl/CSSMediaRuleTest.java | 92 ++++---- .../com/helger/css/decl/CSSSelectorTest.java | 104 ++++----- .../com/helger/css/decl/CSSStyleRuleTest.java | 212 +++++++++--------- .../helger/css/decl/CSSSupportsRuleTest.java | 59 ++--- .../helger/css/reader/CSSReaderFuncTest.java | 3 + 15 files changed, 322 insertions(+), 352 deletions(-) diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSKeyframesRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSKeyframesRule.java index a8cd9c6d..69ebccbb 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSKeyframesRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSKeyframesRule.java @@ -162,12 +162,12 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (!aSettings.isWriteKeyframesRules ()) return ""; - int nBlockCount = m_aBlocks.size (); boolean bFirst = true; - if (aSettings.isRemoveUnnecessaryCode () && nBlockCount == 0) + if (aSettings.isRemoveUnnecessaryCode () && m_aBlocks.isEmpty ()) return ""; + final int nBlockCount = m_aBlocks.size (); final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); final StringBuilder aSB = new StringBuilder (m_sDeclaration); diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSNestedDeclarations.java b/ph-css/src/main/java/com/helger/css/decl/CSSNestedDeclarations.java index adfb6cc8..9c073050 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSNestedDeclarations.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSNestedDeclarations.java @@ -70,10 +70,9 @@ public class CSSNestedDeclarations implements ICSSNestedRule, IHasCSSDeclaration /** * Creates a new, empty instance with no declarations. */ - public CSSNestedDeclarations() + public CSSNestedDeclarations () {} - @Override @NonNull public CSSNestedDeclarations addDeclaration (@NonNull final CSSDeclaration aDeclaration) { @@ -81,7 +80,6 @@ public CSSNestedDeclarations addDeclaration (@NonNull final CSSDeclaration aDecl return this; } - @Override @NonNull public CSSNestedDeclarations addDeclaration (@Nonnegative final int nIndex, @NonNull final CSSDeclaration aNewDeclaration) { @@ -89,28 +87,24 @@ public CSSNestedDeclarations addDeclaration (@Nonnegative final int nIndex, @Non return this; } - @Override @NonNull public EChange removeDeclaration (@NonNull final CSSDeclaration aDeclaration) { return m_aDeclarations.removeDeclaration (aDeclaration); } - @Override @NonNull public EChange removeDeclaration (@Nonnegative final int nDeclarationIndex) { return m_aDeclarations.removeDeclaration (nDeclarationIndex); } - @Override @NonNull public EChange removeAllDeclarations () { return m_aDeclarations.removeAllDeclarations (); } - @Override @NonNull @ReturnsMutableCopy public ICommonsList getAllDeclarations () @@ -118,14 +112,12 @@ public ICommonsList getAllDeclarations () return m_aDeclarations.getAllDeclarations (); } - @Override @Nullable public CSSDeclaration getDeclarationAtIndex (@Nonnegative final int nIndex) { return m_aDeclarations.getDeclarationAtIndex (nIndex); } - @Override @NonNull public CSSNestedDeclarations setDeclarationAtIndex (@Nonnegative final int nIndex, @NonNull final CSSDeclaration aNewDeclaration) { @@ -133,27 +125,23 @@ public CSSNestedDeclarations setDeclarationAtIndex (@Nonnegative final int nInde return this; } - @Override public boolean hasDeclarations () { return m_aDeclarations.hasDeclarations (); } - @Override @Nonnegative public int getDeclarationCount () { return m_aDeclarations.getDeclarationCount (); } - @Override @Nullable public CSSDeclaration getDeclarationOfPropertyName (@Nullable final String sPropertyName) { return m_aDeclarations.getDeclarationOfPropertyName (sPropertyName); } - @Override @NonNull @ReturnsMutableCopy public ICommonsList getAllDeclarationsOfPropertyName (@Nullable final String sPropertyName) @@ -161,7 +149,6 @@ public ICommonsList getAllDeclarationsOfPropertyName (@Nullable return m_aDeclarations.getAllDeclarationsOfPropertyName (sPropertyName); } - @Override @NonNull public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) { @@ -172,17 +159,15 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (aSettings.isRemoveUnnecessaryCode () && !hasDeclarations ()) return ""; - return m_aDeclarations.getDeclarationsAsCSSString(aSettings, nIndentLevel); + return m_aDeclarations.getDeclarationsAsCSSString (aSettings, nIndentLevel); } - @Override @Nullable public final CSSSourceLocation getSourceLocation () { return m_aSourceLocation; } - @Override public final void setSourceLocation (@Nullable final CSSSourceLocation aSourceLocation) { m_aSourceLocation = aSourceLocation; diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSPageRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSPageRule.java index 02b678f3..4912dcf2 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSPageRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSPageRule.java @@ -172,7 +172,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn { // A single declaration aSB.append (bOptimizedOutput ? "{" : " { "); - aSB.append (getPageRuleMemberAsCSS(aSettings, nIndentLevel + 1)); + aSB.append (_getPageRuleMemberAsCSS(aSettings, nIndentLevel + 1)); aSB.append (bOptimizedOutput ? "}" : " }"); } else @@ -182,7 +182,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn if (!bOptimizedOutput) { aSB.append (aSettings.getIndent(nIndentLevel + 1)); } - aSB.append (getPageRuleMemberAsCSS(aSettings, nIndentLevel + 1)); + aSB.append (_getPageRuleMemberAsCSS(aSettings, nIndentLevel + 1)); if (!bOptimizedOutput) aSB.append (aSettings.getNewLineString ()).append (aSettings.getIndent (nIndentLevel)); aSB.append ('}'); @@ -228,7 +228,7 @@ public String toString () .getToString (); } - private String getPageRuleMemberAsCSS(@NonNull ICSSWriterSettings aSettings, int nIndentLevel) { + private String _getPageRuleMemberAsCSS(@NonNull ICSSWriterSettings aSettings, int nIndentLevel) { final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); final int nDeclCount = m_aMembers.size (); diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java b/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java index e7406bdb..4d41c586 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSStyleRule.java @@ -150,20 +150,17 @@ public ICommonsList getAllSelectors () return m_aSelectors.getClone (); } - @Override public boolean hasRules() { return m_aRules.isNotEmpty (); } - @Override @Nonnegative public int getRuleCount() { return m_aRules.size (); } - @Override @NonNull public CSSStyleRule addRule(@NonNull final ICSSNestedRule aRule) { @@ -173,7 +170,6 @@ public CSSStyleRule addRule(@NonNull final ICSSNestedRule aRule) return this; } - @Override @NonNull public CSSStyleRule addRule(@Nonnegative final int nIndex, @NonNull final ICSSNestedRule aRule) { @@ -187,35 +183,30 @@ public CSSStyleRule addRule(@Nonnegative final int nIndex, @NonNull final ICSSNe return this; } - @Override @NonNull public EChange removeRule(@NonNull final ICSSNestedRule aRule) { return m_aRules.removeObject (aRule); } - @Override @NonNull public EChange removeRule(@Nonnegative final int nRuleIndex) { return m_aRules.removeAtIndex (nRuleIndex); } - @Override @NonNull public EChange removeAllRules() { return m_aRules.removeAll (); } - @Override @Nullable public ICSSNestedRule getRuleAtIndex(@Nonnegative final int nRuleIndex) { return m_aRules.getAtIndex (nRuleIndex); } - @Override @NonNull @ReturnsMutableCopy public ICommonsList getAllRules() @@ -223,7 +214,6 @@ public ICommonsList getAllRules() return m_aRules.getClone (); } - @Override @NonNull public CSSStyleRule addDeclaration (@NonNull final CSSDeclaration aDeclaration) { @@ -231,7 +221,6 @@ public CSSStyleRule addDeclaration (@NonNull final CSSDeclaration aDeclaration) return this; } - @Override @NonNull public CSSStyleRule addDeclaration (@Nonnegative final int nIndex, @NonNull final CSSDeclaration aNewDeclaration) { @@ -239,28 +228,24 @@ public CSSStyleRule addDeclaration (@Nonnegative final int nIndex, @NonNull fina return this; } - @Override @NonNull public EChange removeDeclaration (@NonNull final CSSDeclaration aDeclaration) { return m_aDeclarations.removeDeclaration (aDeclaration); } - @Override @NonNull public EChange removeDeclaration (@Nonnegative final int nDeclarationIndex) { return m_aDeclarations.removeDeclaration (nDeclarationIndex); } - @Override @NonNull public EChange removeAllDeclarations () { return m_aDeclarations.removeAllDeclarations (); } - @Override @NonNull @ReturnsMutableCopy public ICommonsList getAllDeclarations () @@ -268,14 +253,12 @@ public ICommonsList getAllDeclarations () return m_aDeclarations.getAllDeclarations (); } - @Override @Nullable public CSSDeclaration getDeclarationAtIndex (@Nonnegative final int nIndex) { return m_aDeclarations.getDeclarationAtIndex (nIndex); } - @Override @NonNull public CSSStyleRule setDeclarationAtIndex (@Nonnegative final int nIndex, @NonNull final CSSDeclaration aNewDeclaration) { @@ -283,27 +266,23 @@ public CSSStyleRule setDeclarationAtIndex (@Nonnegative final int nIndex, @NonNu return this; } - @Override public boolean hasDeclarations () { return m_aDeclarations.hasDeclarations (); } - @Override @Nonnegative public int getDeclarationCount () { return m_aDeclarations.getDeclarationCount (); } - @Override @Nullable public CSSDeclaration getDeclarationOfPropertyName (@Nullable final String sPropertyName) { return m_aDeclarations.getDeclarationOfPropertyName (sPropertyName); } - @Override @NonNull @ReturnsMutableCopy public ICommonsList getAllDeclarationsOfPropertyName (@Nullable final String sPropertyName) @@ -342,7 +321,6 @@ public String getSelectorsAsCSSString (@NonNull final ICSSWriterSettings aSettin return aSB.toString (); } - @Override @NonNull public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) { @@ -439,14 +417,12 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn return aSB.toString (); } - @Override @Nullable public final CSSSourceLocation getSourceLocation () { return m_aSourceLocation; } - @Override public final void setSourceLocation (@Nullable final CSSSourceLocation aSourceLocation) { m_aSourceLocation = aSourceLocation; diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSWritableList.java b/ph-css/src/main/java/com/helger/css/decl/CSSWritableList.java index cc026a75..f0339db7 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSWritableList.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSWritableList.java @@ -81,7 +81,7 @@ public String getAsCSSString (@NonNull final ICSSWriterSettings aSettings, @Nonn // No ';' at the last decl if (!bOptimizedOutput || nIndex < nDeclCount - 1) aSB.append (CCSS.DEFINITION_END); - if (!bOptimizedOutput && nIndex != size() - 1) + if (!bOptimizedOutput && nIndex != nDeclCount - 1) aSB.append (aSettings.getNewLineString ()); ++nIndex; } diff --git a/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitorForUrl.java b/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitorForUrl.java index 21830bfc..1feeb669 100644 --- a/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitorForUrl.java +++ b/ph-css/src/main/java/com/helger/css/decl/visit/CSSVisitorForUrl.java @@ -245,12 +245,10 @@ public void onEndLayerRule (@NonNull final CSSLayerRule aLayerRule) m_aTopLevelRule.pop(); } - @Override public void onBeginNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) { // no action } - @Override public void onEndNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) { // no action } diff --git a/ph-css/src/main/java/com/helger/css/decl/visit/DefaultCSSVisitor.java b/ph-css/src/main/java/com/helger/css/decl/visit/DefaultCSSVisitor.java index 6d0c7a9f..0380c78d 100644 --- a/ph-css/src/main/java/com/helger/css/decl/visit/DefaultCSSVisitor.java +++ b/ph-css/src/main/java/com/helger/css/decl/visit/DefaultCSSVisitor.java @@ -149,11 +149,11 @@ public void onBeginLayerRule (@NonNull final CSSLayerRule aLayerRule) public void onEndLayerRule (@NonNull final CSSLayerRule aLayerRule) {} - @Override + @OverrideOnDemand public void onBeginNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) {} - @Override + @OverrideOnDemand public void onEndNestedDeclarations(@NonNull CSSNestedDeclarations aNestedDeclarations) {} diff --git a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java index bf655ad1..a36c73fa 100644 --- a/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java +++ b/ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java @@ -780,7 +780,7 @@ private void _readStyleDeclarationList (@NonNull final CSSNode aNode, private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aNode, @NonNull final Consumer aDeclarationConsumer, @NonNull final Consumer aNestedRuleConsumer, - final int nStyleRuleCount) + final boolean bIsTopLevel) { _expectNodeType (aNode, ECSSNodeType.STYLEDECLARATIONLISTWITHNESTED); // Read all contained declarations and rules @@ -807,7 +807,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN { if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) aNestedRuleConsumer.accept (aNestedDeclarations); - final CSSStyleRule aRule = _createStyleRule (aChildNode, nStyleRuleCount + 1); + final CSSStyleRule aRule = _createStyleRule (aChildNode, false); if (aRule != null) aNestedRuleConsumer.accept (aRule); aNestedDeclarations = new CSSNestedDeclarations(); @@ -817,7 +817,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN { if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) aNestedRuleConsumer.accept (aNestedDeclarations); - final CSSMediaRule aRule = _createMediaRule (aChildNode, nStyleRuleCount); + final CSSMediaRule aRule = _createMediaRule (aChildNode, bIsTopLevel); if (aRule != null) aNestedRuleConsumer.accept (aRule); aNestedDeclarations = new CSSNestedDeclarations(); @@ -827,7 +827,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN { if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) aNestedRuleConsumer.accept (aNestedDeclarations); - final CSSSupportsRule aRule = _createSupportsRule (aChildNode, nStyleRuleCount); + final CSSSupportsRule aRule = _createSupportsRule (aChildNode, bIsTopLevel); if (aRule != null) aNestedRuleConsumer.accept (aRule); aNestedDeclarations = new CSSNestedDeclarations(); @@ -837,7 +837,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN { if (aNestedDeclarations != null && aNestedDeclarations.hasDeclarations()) aNestedRuleConsumer.accept (aNestedDeclarations); - final CSSLayerRule aRule = _createLayerRule (aChildNode, nStyleRuleCount); + final CSSLayerRule aRule = _createLayerRule (aChildNode, bIsTopLevel); if (aRule != null) aNestedRuleConsumer.accept (aRule); aNestedDeclarations = new CSSNestedDeclarations(); @@ -861,7 +861,7 @@ private void _readStyleDeclarationListWithNestedRules (@NonNull final CSSNode aN } @Nullable - private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode, final int nStyleRuleCount) + private CSSStyleRule _createStyleRule (@NonNull final CSSNode aNode, final boolean bIsTopLevel) { _expectNodeType (aNode, ECSSNodeType.STYLERULE); final CSSStyleRule ret = new CSSStyleRule (); @@ -882,7 +882,7 @@ else if (ECSSNodeType.RELATIVESELECTOR.isNode (aChildNode)) if (!bSelectors) m_aErrorHandler.onCSSInterpretationError ("Found a selector after a declaration!"); - if (nStyleRuleCount == 0) + if (bIsTopLevel) m_aErrorHandler.onCSSInterpretationError ("Relative selectors are not allowed at the top level!"); ret.addSelector (_createRelativeSelector (aChildNode)); @@ -894,7 +894,7 @@ else if (ECSSNodeType.RELATIVESELECTOR.isNode (aChildNode)) if (ECSSNodeType.STYLEDECLARATIONLISTWITHNESTED.isNode (aChildNode)) { // Read all contained declarations and nested rules - _readStyleDeclarationListWithNestedRules (aChildNode, ret::addDeclaration, ret::addRule, nStyleRuleCount); + _readStyleDeclarationListWithNestedRules (aChildNode, ret::addDeclaration, ret::addRule, bIsTopLevel); } else if (!ECSSNodeType.isErrorNode (aChildNode)) @@ -975,7 +975,7 @@ private CSSPageRule _createPageRule (@NonNull final CSSNode aNode) } @NonNull - private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode, final int nStyleRuleCount) + private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode, final boolean bIsTopLevel) { _expectNodeType (aNode, ECSSNodeType.MEDIARULE); final CSSMediaRule ret = new CSSMediaRule (); @@ -991,7 +991,7 @@ private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode, final int n else if (ECSSNodeType.STYLERULE.isNode (aChildNode)) { - final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, nStyleRuleCount); + final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, bIsTopLevel); if (aStyleRule != null) ret.addRule (aStyleRule); } @@ -999,7 +999,7 @@ private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode, final int n if (ECSSNodeType.MEDIARULE.isNode (aChildNode)) { // Nested media rules are OK! - ret.addRule (_createMediaRule (aChildNode, nStyleRuleCount)); + ret.addRule (_createMediaRule (aChildNode, bIsTopLevel)); } else if (ECSSNodeType.PAGERULE.isNode (aChildNode)) @@ -1015,10 +1015,10 @@ private CSSMediaRule _createMediaRule (@NonNull final CSSNode aNode, final int n ret.addRule (_createViewportRule (aChildNode)); else if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode)) - ret.addRule (_createSupportsRule (aChildNode, nStyleRuleCount)); + ret.addRule (_createSupportsRule (aChildNode, bIsTopLevel)); else if (ECSSNodeType.LAYERRULE.isNode (aChildNode)) - ret.addRule (_createLayerRule (aChildNode, nStyleRuleCount)); + ret.addRule (_createLayerRule (aChildNode, bIsTopLevel)); else if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode)) { @@ -1166,7 +1166,7 @@ private CSSFontFaceRule _createFontFaceRule (@NonNull final CSSNode aNode) } @NonNull - private CSSLayerRule _createLayerRule (@NonNull final CSSNode aNode, final int nStyleRuleCount) + private CSSLayerRule _createLayerRule (@NonNull final CSSNode aNode, final boolean bIsTopLevel) { _expectNodeType (aNode, ECSSNodeType.LAYERRULE); final int nChildCount = aNode.jjtGetNumChildren (); @@ -1204,19 +1204,19 @@ private CSSLayerRule _createLayerRule (@NonNull final CSSNode aNode, final int n final CSSNode aBodyChildNode = aBodyNode.jjtGetChild (nIndex); if (ECSSNodeType.STYLERULE.isNode (aBodyChildNode)) { - final CSSStyleRule aStyleRule = _createStyleRule (aBodyChildNode, nStyleRuleCount); + final CSSStyleRule aStyleRule = _createStyleRule (aBodyChildNode, bIsTopLevel); if (aStyleRule != null) ret.addRule (aStyleRule); } else if (ECSSNodeType.LAYERRULE.isNode (aBodyChildNode)) - ret.addRule (_createLayerRule (aBodyChildNode, nStyleRuleCount)); + ret.addRule (_createLayerRule (aBodyChildNode, bIsTopLevel)); else if (ECSSNodeType.MEDIARULE.isNode (aBodyChildNode)) - ret.addRule (_createMediaRule (aBodyChildNode, nStyleRuleCount)); + ret.addRule (_createMediaRule (aBodyChildNode, bIsTopLevel)); else if (ECSSNodeType.SUPPORTSRULE.isNode (aBodyChildNode)) - ret.addRule (_createSupportsRule (aBodyChildNode, nStyleRuleCount)); + ret.addRule (_createSupportsRule (aBodyChildNode, bIsTopLevel)); else if (ECSSNodeType.KEYFRAMESRULE.isNode (aBodyChildNode)) ret.addRule (_createKeyframesRule (aBodyChildNode)); @@ -1423,7 +1423,7 @@ private ICSSSupportsConditionMember _createSupportsConditionMemberRecursive (@No } @NonNull - private CSSSupportsRule _createSupportsRule (@NonNull final CSSNode aNode, final int nStyleRuleCount) + private CSSSupportsRule _createSupportsRule (@NonNull final CSSNode aNode, final boolean bIsTopLevel) { _expectNodeType (aNode, ECSSNodeType.SUPPORTSRULE); final CSSSupportsRule ret = new CSSSupportsRule (); @@ -1443,13 +1443,13 @@ private CSSSupportsRule _createSupportsRule (@NonNull final CSSNode aNode, final else if (ECSSNodeType.STYLERULE.isNode (aChildNode)) { - final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, nStyleRuleCount); + final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, bIsTopLevel); if (aStyleRule != null) ret.addRule (aStyleRule); } else if (ECSSNodeType.MEDIARULE.isNode (aChildNode)) - ret.addRule (_createMediaRule (aChildNode, nStyleRuleCount)); + ret.addRule (_createMediaRule (aChildNode, bIsTopLevel)); else if (ECSSNodeType.PAGERULE.isNode (aChildNode)) ret.addRule (_createPageRule (aChildNode)); @@ -1464,10 +1464,10 @@ private CSSSupportsRule _createSupportsRule (@NonNull final CSSNode aNode, final ret.addRule (_createViewportRule (aChildNode)); else if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode)) - ret.addRule (_createSupportsRule (aChildNode, nStyleRuleCount)); + ret.addRule (_createSupportsRule (aChildNode, bIsTopLevel)); else if (ECSSNodeType.LAYERRULE.isNode (aChildNode)) - ret.addRule (_createLayerRule (aChildNode, nStyleRuleCount)); + ret.addRule (_createLayerRule (aChildNode, bIsTopLevel)); else if (!ECSSNodeType.isErrorNode (aChildNode)) m_aErrorHandler.onCSSInterpretationError ("Unsupported supports-rule child: " + @@ -1523,7 +1523,7 @@ private void _recursiveFillCascadingStyleSheetFromNode (@NonNull final CSSNode a else if (ECSSNodeType.STYLERULE.isNode (aChildNode)) { - final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, 0); + final CSSStyleRule aStyleRule = _createStyleRule (aChildNode, true); if (aStyleRule != null) ret.addRule (aStyleRule); } @@ -1532,13 +1532,13 @@ private void _recursiveFillCascadingStyleSheetFromNode (@NonNull final CSSNode a ret.addRule (_createPageRule (aChildNode)); else if (ECSSNodeType.MEDIARULE.isNode (aChildNode)) - ret.addRule (_createMediaRule (aChildNode, 0)); + ret.addRule (_createMediaRule (aChildNode, true)); else if (ECSSNodeType.FONTFACERULE.isNode (aChildNode)) ret.addRule (_createFontFaceRule (aChildNode)); else if (ECSSNodeType.LAYERRULE.isNode (aChildNode)) - ret.addRule (_createLayerRule (aChildNode, 0)); + ret.addRule (_createLayerRule (aChildNode, true)); else if (ECSSNodeType.KEYFRAMESRULE.isNode (aChildNode)) ret.addRule (_createKeyframesRule (aChildNode)); @@ -1547,7 +1547,7 @@ private void _recursiveFillCascadingStyleSheetFromNode (@NonNull final CSSNode a ret.addRule (_createViewportRule (aChildNode)); else if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode)) - ret.addRule (_createSupportsRule (aChildNode, 0)); + ret.addRule (_createSupportsRule (aChildNode, true)); else if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode)) { diff --git a/ph-css/src/main/java/com/helger/css/writer/CSSWriterSettings.java b/ph-css/src/main/java/com/helger/css/writer/CSSWriterSettings.java index dcb0c4bb..0168e4e2 100644 --- a/ph-css/src/main/java/com/helger/css/writer/CSSWriterSettings.java +++ b/ph-css/src/main/java/com/helger/css/writer/CSSWriterSettings.java @@ -223,7 +223,6 @@ public final CSSWriterSettings setWriteNamespaceRules (final boolean bWriteNames return this; } - @Override public final boolean isWriteNestedDeclarations() { return m_bWriteNestedDeclarations; @@ -266,7 +265,6 @@ public final CSSWriterSettings setWriteKeyframesRules (final boolean bWriteKeyfr return this; } - @Override public final boolean isWriteLayerRules () { return m_bWriteLayerRules; diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSLayerRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSLayerRuleTest.java index f21d587e..f829d83f 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSLayerRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSLayerRuleTest.java @@ -16,53 +16,54 @@ * @author Philip Helger */ public class CSSLayerRuleTest { - @NonNull - private static CSSLayerRule _parse (@NonNull final String sCSS) - { - final CascadingStyleSheet aCSS = CSSReader.readFromString (sCSS); - assertNotNull (sCSS, aCSS); - assertTrue (aCSS.hasLayerRules ()); - assertEquals (1, aCSS.getLayerRuleCount ()); - final CSSLayerRule ret = aCSS.getAllLayerRules ().get (0); - assertNotNull (ret); - return ret; - } + @NonNull + private static CSSLayerRule _parse (@NonNull final String sCSS) + { + final CascadingStyleSheet aCSS = CSSReader.readFromString (sCSS); + assertNotNull (sCSS, aCSS); + assertTrue (aCSS.hasLayerRules ()); + assertEquals (1, aCSS.getLayerRuleCount ()); + final CSSLayerRule ret = aCSS.getAllLayerRules ().get (0); + assertNotNull (ret); + return ret; + } - @Test - public void testRead1 () - { - CSSLayerRule aSR = _parse (String.join("\n", List.of( - "@layer state {", - " .foo {", - " color: white;", - " .bar {", - " color: orange", - " }", - " color: black;", - " }", - "}"))); - assertEquals (1, aSR.getSelectorCount()); - assertEquals (1, aSR.getRuleCount ()); + @Test + public void testRead1 () + { + CSSLayerRule aSR = _parse (""" + @layer state { + .foo { + color: white; + .bar { + color: orange + } + color: black; + } + } + """); + assertEquals (1, aSR.getSelectorCount ()); + assertEquals (1, aSR.getRuleCount ()); - assertEquals ("state", aSR.getSelectorAtIndex(0)); + assertEquals ("state", aSR.getSelectorAtIndex (0)); - assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); - assertEquals (2, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + CSSStyleRule rule1 = (CSSStyleRule) aSR.getRuleAtIndex (0); + assertEquals (1, rule1.getSelectorCount ()); + assertEquals (1, rule1.getDeclarationCount ()); + assertEquals (2, rule1.getRuleCount ()); - assertEquals (".foo", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:white", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); + assertEquals (".foo", rule1.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:white", rule1.getDeclarationAtIndex (0).getAsCSSString ()); - assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getRuleCount()); - assertEquals (".bar", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:orange", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + CSSStyleRule rule11 = (CSSStyleRule) rule1.getRuleAtIndex (0); + assertEquals (1, rule11.getSelectorCount ()); + assertEquals (1, rule11.getDeclarationCount ()); + assertEquals (0, rule11.getRuleCount ()); + assertEquals (".bar", rule11.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:orange", rule11.getDeclarationAtIndex (0).getAsCSSString ()); - assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1) instanceof CSSNestedDeclarations); - assertEquals (1, ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationCount()); - assertEquals ("color:black", ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationAtIndex(0).getAsCSSString()); - } + CSSNestedDeclarations rule12 = (CSSNestedDeclarations) rule1.getRuleAtIndex (1); + assertEquals (1, rule12.getDeclarationCount ()); + assertEquals ("color:black", rule12.getDeclarationAtIndex (0).getAsCSSString ()); + } } diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSMediaRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSMediaRuleTest.java index 82118947..dea07b61 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSMediaRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSMediaRuleTest.java @@ -2,6 +2,7 @@ import com.helger.css.reader.CSSReader; import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.junit.Test; import java.util.List; @@ -16,53 +17,58 @@ * @author Philip Helger */ public class CSSMediaRuleTest { - @NonNull - private static CSSMediaRule _parse (@NonNull final String sCSS) - { - final CascadingStyleSheet aCSS = CSSReader.readFromString (sCSS); - assertNotNull (sCSS, aCSS); - assertTrue (aCSS.hasMediaRules ()); - assertEquals (1, aCSS.getMediaRuleCount ()); - final CSSMediaRule ret = aCSS.getAllMediaRules ().get (0); - assertNotNull (ret); - return ret; - } + @NonNull + private static CSSMediaRule _parse (@NonNull final String sCSS) + { + final CascadingStyleSheet aCSS = CSSReader.readFromString (sCSS); + assertNotNull (sCSS, aCSS); + assertTrue (aCSS.hasMediaRules ()); + assertEquals (1, aCSS.getMediaRuleCount ()); + final CSSMediaRule ret = aCSS.getAllMediaRules ().get (0); + assertNotNull (ret); + return ret; + } - @Test - public void testRead1 () - { - CSSMediaRule aSR = _parse (String.join("\n", List.of( - "@media print {", - " .foo {", - " color: white;", - " .bar {", - " color: orange", - " }", - " color: black;", - " }", - "}"))); - assertEquals(1, aSR.getMediaQueryCount()); - assertEquals (1, aSR.getRuleCount ()); + @Test + public void testRead1 () + { + CSSMediaRule aSR = _parse (""" + @media print { + .foo { + color: white; + .bar { + color: orange + } + color: black; + } + } + """); + assertEquals (1, aSR.getMediaQueryCount ()); + assertEquals (1, aSR.getRuleCount ()); - assertEquals("print", aSR.getMediaQueryAtIndex(0).getAsCSSString()); + CSSMediaQuery mediaQuery = aSR.getMediaQueryAtIndex (0); + assertEquals ("print", mediaQuery.getAsCSSString ()); - assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); - assertEquals (2, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); + CSSStyleRule rule1 = (CSSStyleRule) aSR.getRuleAtIndex (0); + assertEquals (1, rule1.getSelectorCount ()); + assertEquals (1, rule1.getDeclarationCount ()); + assertEquals (2, rule1.getRuleCount ()); - assertEquals (".foo", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:white", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); + CSSSelector selector1 = rule1.getSelectorAtIndex (0); + assertEquals (".foo", selector1.getAsCSSString ()); - assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getRuleCount()); - assertEquals (".bar", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:orange", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + CSSDeclaration declaration11 = rule1.getDeclarationAtIndex (0); + assertEquals ("color:white", declaration11.getAsCSSString ()); - assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1) instanceof CSSNestedDeclarations); - assertEquals (1, ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationCount()); - assertEquals ("color:black", ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationAtIndex(0).getAsCSSString()); - } + CSSStyleRule rule11 = (CSSStyleRule) rule1.getRuleAtIndex (0); + assertEquals (1, rule11.getSelectorCount ()); + assertEquals (1, rule11.getDeclarationCount ()); + assertEquals (0, rule11.getRuleCount ()); + assertEquals (".bar", rule11.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:orange", rule11.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSNestedDeclarations rule12 = (CSSNestedDeclarations) rule1.getRuleAtIndex (1); + assertEquals (1, rule12.getDeclarationCount ()); + assertEquals ("color:black", rule12.getDeclarationAtIndex (0).getAsCSSString ()); + } } diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java index 4780cfdd..90d2c658 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSSelectorTest.java @@ -41,7 +41,7 @@ public final class CSSSelectorTest @NonNull private CSSSelector _parse (@NonNull final String sCSS) { - CSSReaderSettings aSettings = new CSSReaderSettings().setInterpretErrorHandler(m_aIEH); + CSSReaderSettings aSettings = new CSSReaderSettings ().setInterpretErrorHandler (m_aIEH); final CascadingStyleSheet aCSS = CSSReader.readFromStringReader (sCSS, aSettings); assertNotNull (sCSS, aCSS); assertTrue (aCSS.hasStyleRules ()); @@ -70,29 +70,29 @@ private static CSSStyleRule _parseRule (@NonNull final String sCSS) @Test public void testReadElementSelector () { - CSSSelector aSel = _parse("div { color:red }"); - assertEquals(List.of(), m_aIEH.getErrors()); - assertEquals(1, aSel.getMemberCount()); - assertTrue(aSel.getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); - assertEquals("div", aSel.getMemberAtIndex(0).getAsCSSString()); + CSSSelector aSel = _parse ("div { color:red }"); + assertTrue (m_aIEH.getErrors ().isEmpty ()); + assertEquals (1, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals ("div", aSel.getMemberAtIndex (0).getAsCSSString ()); } @Test public void testReadIdSelector () { - CSSSelector aSel = _parse("#id { color:red }"); - assertEquals(List.of(), m_aIEH.getErrors()); - assertEquals(1, aSel.getMemberCount()); - assertTrue(aSel.getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); - assertEquals("#id", aSel.getMemberAtIndex(0).getAsCSSString()); - assertEquals("#id", aSel.getAsCSSString()); + CSSSelector aSel = _parse ("#id { color:red }"); + assertTrue (m_aIEH.getErrors ().isEmpty ()); + assertEquals (1, aSel.getMemberCount ()); + assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals ("#id", aSel.getMemberAtIndex (0).getAsCSSString ()); + assertEquals ("#id", aSel.getAsCSSString ()); } @Test public void testReadIdSpaceCombinator () { CSSSelector aSel = _parse ("#id div { color:red }"); - assertEquals(List.of(), m_aIEH.getErrors()); + assertTrue (m_aIEH.getErrors ().isEmpty ()); assertEquals (3, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals ("#id", aSel.getMemberAtIndex (0).getAsCSSString ()); @@ -107,7 +107,7 @@ public void testReadIdSpaceCombinator () public void testReadWaveDashCombinator () { CSSSelector aSel = _parse ("#id ~ div { color:red }"); - assertEquals(List.of(), m_aIEH.getErrors()); + assertTrue (m_aIEH.getErrors ().isEmpty ()); assertEquals (3, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals ("#id", aSel.getMemberAtIndex (0).getAsCSSString ()); @@ -119,9 +119,9 @@ public void testReadWaveDashCombinator () } @Test - public void testReadNestingSelectorAtStartWithoutSpace() { + public void testReadNestingSelectorAtStartWithoutSpace () { CSSSelector aSel = _parse ("&.foo { color:red }"); - assertEquals(List.of(), m_aIEH.getErrors()); + assertTrue (m_aIEH.getErrors ().isEmpty ()); assertEquals (2, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals ("&", aSel.getMemberAtIndex (0).getAsCSSString ()); @@ -130,9 +130,9 @@ public void testReadNestingSelectorAtStartWithoutSpace() { } @Test - public void testReadNestingSelectorAtStartWithSpace() { + public void testReadNestingSelectorAtStartWithSpace () { CSSSelector aSel = _parse ("& .foo { color:red }"); - assertEquals(List.of(), m_aIEH.getErrors()); + assertTrue (m_aIEH.getErrors ().isEmpty ()); assertEquals (3, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals ("&", aSel.getMemberAtIndex (0).getAsCSSString ()); @@ -143,9 +143,9 @@ public void testReadNestingSelectorAtStartWithSpace() { } @Test - public void testReadNestingSelectorAtEnd() { + public void testReadNestingSelectorAtEnd () { CSSSelector aSel = _parse (".foo & { color:red }"); - assertEquals(List.of(), m_aIEH.getErrors()); + assertTrue (m_aIEH.getErrors ().isEmpty ()); assertEquals (3, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); assertEquals (".foo", aSel.getMemberAtIndex (0).getAsCSSString ()); @@ -156,74 +156,74 @@ public void testReadNestingSelectorAtEnd() { } @Test - public void testReadRelativeSelectorWithinStyleRuleWithWaveDash() { + public void testReadRelativeSelectorWithinStyleRuleWithWaveDash () { CSSStyleRule aRule = _parseRule (".foo { ~ .bar { color:red } }"); - assertEquals(List.of(), m_aIEH.getErrors()); + assertTrue (m_aIEH.getErrors ().isEmpty ()); - assertEquals(".foo", aRule.getSelectorAtIndex(0).getAsCSSString()); - assertTrue(aRule.getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals(1, ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorCount()); - CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorAtIndex(0); + assertEquals (".foo", aRule.getSelectorAtIndex (0).getAsCSSString ()); + assertTrue (aRule.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aRule.getRuleAtIndex (0)).getSelectorCount ()); + CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex (0)).getSelectorAtIndex (0); assertEquals (2, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof ECSSSelectorCombinator); - assertEquals ("~", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName()); + assertEquals ("~", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName ()); assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelector); - assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); - assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0)).getValue()); + assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex (0)).getValue ()); } @Test - public void testReadRelativeSelectorWithinStyleRuleWithPlus() { + public void testReadRelativeSelectorWithinStyleRuleWithPlus () { CSSStyleRule aRule = _parseRule (".foo { + .bar { color:red } }"); - assertEquals(List.of(), m_aIEH.getErrors()); + assertTrue (m_aIEH.getErrors ().isEmpty ()); - assertEquals(".foo", aRule.getSelectorAtIndex(0).getAsCSSString()); - assertTrue(aRule.getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals(1, ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorCount()); - CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorAtIndex(0); + assertEquals (".foo", aRule.getSelectorAtIndex (0).getAsCSSString ()); + assertTrue (aRule.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aRule.getRuleAtIndex (0)).getSelectorCount ()); + CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex (0)).getSelectorAtIndex (0); assertEquals (2, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof ECSSSelectorCombinator); - assertEquals ("+", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName()); + assertEquals ("+", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName ()); assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelector); - assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); - assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0)).getValue()); + assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex (0)).getValue ()); } @Test - public void testReadRelativeSelectorWithinStyleRuleWithGreater() { + public void testReadRelativeSelectorWithinStyleRuleWithGreater () { CSSStyleRule aRule = _parseRule (".foo { > .bar { color:red } }"); - assertEquals(List.of(), m_aIEH.getErrors()); + assertTrue (m_aIEH.getErrors ().isEmpty ()); - assertEquals(".foo", aRule.getSelectorAtIndex(0).getAsCSSString()); - assertTrue(aRule.getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals(1, ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorCount()); - CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex(0)).getSelectorAtIndex(0); + assertEquals (".foo", aRule.getSelectorAtIndex (0).getAsCSSString ()); + assertTrue (aRule.getRuleAtIndex (0) instanceof CSSStyleRule); + assertEquals (1, ((CSSStyleRule)aRule.getRuleAtIndex (0)).getSelectorCount ()); + CSSSelector aSel = ((CSSStyleRule)aRule.getRuleAtIndex (0)).getSelectorAtIndex (0); assertEquals (2, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof ECSSSelectorCombinator); - assertEquals (">", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName()); + assertEquals (">", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName ()); assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelector); - assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); - assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0)).getValue()); + assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex (0)).getValue ()); } @Test - public void testReadRelativeSelectorAtTopLevel() { + public void testReadRelativeSelectorAtTopLevel () { CSSSelector aSel = _parse ("> .bar { color:red }"); - assertEquals(List.of("Relative selectors are not allowed at the top level!"), m_aIEH.getErrors()); + assertEquals (List.of ("Relative selectors are not allowed at the top level!"), m_aIEH.getErrors ()); assertEquals (2, aSel.getMemberCount ()); assertTrue (aSel.getMemberAtIndex (0) instanceof ECSSSelectorCombinator); - assertEquals (">", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName()); + assertEquals (">", ((ECSSSelectorCombinator)aSel.getMemberAtIndex (0)).getName ()); assertTrue (aSel.getMemberAtIndex (1) instanceof CSSSelector); - assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0) instanceof CSSSelectorSimpleMember); - assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex(0)).getValue()); + assertTrue (((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex (0) instanceof CSSSelectorSimpleMember); + assertEquals (".bar", ((CSSSelectorSimpleMember)((CSSSelector) aSel.getMemberAtIndex (1)).getMemberAtIndex (0)).getValue ()); } } diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java index 6d3839f0..a1eab503 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java @@ -120,18 +120,18 @@ public void testRead3 () assertEquals (0, aSR.getDeclarationCount ()); assertEquals (1, aSR.getRuleCount ()); - assertEquals("div", aSR.getSelectorAtIndex(0).getAsCSSString()); + assertEquals ("div", aSR.getSelectorAtIndex (0).getAsCSSString ()); assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); - - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); - assertEquals ("p", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:red", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount ()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount ()); + assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount ()); + + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount ()); + assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount ()); + assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount ()); + assertEquals ("p", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:red", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex (0).getAsCSSString ()); } @Test @@ -173,66 +173,68 @@ public void testRead4 () assertEquals (2, aSR.getDeclarationCount ()); assertEquals (5, aSR.getRuleCount ()); - assertEquals ("color:red", aSR.getDeclarationAtIndex(0).getAsCSSString()); - assertEquals ("p:dummy", aSR.getDeclarationAtIndex(1).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); - assertEquals ("p", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:dummy", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (1) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (1)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (1)).getDeclarationCount()); - assertEquals (2, ((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleCount()); - assertEquals (".foobar", ((CSSStyleRule)aSR.getRuleAtIndex (1)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:green", ((CSSStyleRule)aSR.getRuleAtIndex (1)).getDeclarationAtIndex(0).getAsCSSString()); - assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(0)).getRuleCount()); - assertEquals ("color:blue", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); - assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(1) instanceof CSSNestedDeclarations); - assertEquals (1, ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(1)).getDeclarationCount()); - assertEquals ("color:white", ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (1)).getRuleAtIndex(1)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (2) instanceof CSSNestedDeclarations); - assertEquals (1, ((CSSNestedDeclarations)aSR.getRuleAtIndex (2)).getDeclarationCount()); - assertEquals ("color:yellow", ((CSSNestedDeclarations)aSR.getRuleAtIndex (2)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (3) instanceof CSSMediaRule); - assertEquals (1, ((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleCount()); - assertTrue (((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getDeclarationCount()); - assertEquals (1, ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleCount()); - assertEquals (".print", ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:black", ((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); - assertTrue (((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getSelectorCount()); - assertEquals (2, ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getRuleCount()); - assertEquals ("&:hover", ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:orange", ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); - assertEquals ("font-size:20px", ((CSSStyleRule)((CSSStyleRule)((CSSMediaRule)aSR.getRuleAtIndex (3)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationAtIndex(1).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (4) instanceof CSSLayerRule); - assertEquals (1, ((CSSLayerRule)aSR.getRuleAtIndex (4)).getSelectorCount()); - assertEquals (1, ((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleCount()); - assertEquals ("state", ((CSSLayerRule)aSR.getRuleAtIndex (4)).getSelectorAtIndex(0)); - assertTrue (((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getDeclarationCount()); - assertEquals (1, ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleCount()); - assertEquals (".alert", ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("background-color:brown", ((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); - assertTrue (((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getRuleCount()); - assertEquals ("p", ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("border:medium solid limegreen", ((CSSStyleRule)((CSSStyleRule)((CSSLayerRule)aSR.getRuleAtIndex (4)).getRuleAtIndex(0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); + assertEquals ("color:red", aSR.getDeclarationAtIndex (0).getAsCSSString ()); + assertEquals ("p:dummy", aSR.getDeclarationAtIndex (1).getAsCSSString ()); + + CSSStyleRule rule1 = (CSSStyleRule) aSR.getRuleAtIndex (0); + assertEquals (1, rule1.getSelectorCount ()); + assertEquals (1, rule1.getDeclarationCount ()); + assertEquals (0, rule1.getRuleCount ()); + assertEquals ("p", rule1.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:dummy", rule1.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSStyleRule rule2 = (CSSStyleRule) aSR.getRuleAtIndex (1); + assertEquals (1, rule2.getSelectorCount ()); + assertEquals (1, rule2.getDeclarationCount ()); + assertEquals (2, rule2.getRuleCount ()); + assertEquals (".foobar", rule2.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:green", rule2.getDeclarationAtIndex (0).getAsCSSString ()); + CSSStyleRule rule21 = (CSSStyleRule) rule2.getRuleAtIndex (0); + assertEquals (1, rule21.getDeclarationCount ()); + assertEquals (0, rule21.getRuleCount ()); + assertEquals ("color:blue", rule21.getDeclarationAtIndex (0).getAsCSSString ()); + CSSNestedDeclarations rule22 = (CSSNestedDeclarations) rule2.getRuleAtIndex (1); + assertEquals (1, rule22.getDeclarationCount ()); + assertEquals ("color:white", rule22.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSNestedDeclarations rule3 = (CSSNestedDeclarations) aSR.getRuleAtIndex (2); + assertEquals (1, rule3.getDeclarationCount ()); + assertEquals ("color:yellow", rule3.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSMediaRule rule4 = (CSSMediaRule) aSR.getRuleAtIndex (3); + assertEquals (1, rule4.getRuleCount ()); + CSSStyleRule rule41 = (CSSStyleRule)rule4.getRuleAtIndex (0); + assertEquals (1, rule41.getSelectorCount ()); + assertEquals (1, rule41.getDeclarationCount ()); + assertEquals (1, rule41.getRuleCount ()); + assertEquals (".print", rule41.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:black", rule41.getDeclarationAtIndex (0).getAsCSSString ()); + CSSStyleRule rule411 = (CSSStyleRule)rule41.getRuleAtIndex (0); + assertEquals (1, rule411.getSelectorCount ()); + assertEquals (2, rule411.getDeclarationCount ()); + assertEquals (0, rule411.getRuleCount ()); + assertEquals ("&:hover", rule411.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:orange", rule411.getDeclarationAtIndex (0).getAsCSSString ()); + assertEquals ("font-size:20px", rule411.getDeclarationAtIndex (1).getAsCSSString ()); + + CSSLayerRule rule5 = (CSSLayerRule) aSR.getRuleAtIndex (4); + assertEquals (1, rule5.getSelectorCount ()); + assertEquals (1, rule5.getRuleCount ()); + assertEquals ("state", rule5.getSelectorAtIndex (0)); + assertTrue (rule5.getRuleAtIndex (0) instanceof CSSStyleRule); + CSSStyleRule rule51 = (CSSStyleRule)rule5.getRuleAtIndex (0); + assertEquals (1, rule51.getSelectorCount ()); + assertEquals (1, rule51.getDeclarationCount ()); + assertEquals (1, rule51.getRuleCount ()); + assertEquals (".alert", rule51.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("background-color:brown", rule51.getDeclarationAtIndex (0).getAsCSSString ()); + assertEquals (1, rule51.getRuleCount ()); + CSSStyleRule rule511 = (CSSStyleRule) rule51.getRuleAtIndex (0); + assertEquals (1, rule511.getSelectorCount ()); + assertEquals (1, rule511.getDeclarationCount ()); + assertEquals (0, rule511.getRuleCount ()); + assertEquals ("p", rule511.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("border:medium solid limegreen", rule511.getDeclarationAtIndex (0).getAsCSSString ()); } @Test @@ -253,40 +255,40 @@ public void testRead5 () assertEquals (1, aSR.getDeclarationCount ()); assertEquals (6, aSR.getRuleCount ()); - assertEquals ("div", aSR.getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:red", aSR.getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); - assertEquals (".a1", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:green", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (1) instanceof CSSNestedDeclarations); - assertEquals (1, ((CSSNestedDeclarations)aSR.getRuleAtIndex (1)).getDeclarationCount()); - assertEquals ("color:blue", ((CSSNestedDeclarations)aSR.getRuleAtIndex (1)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (2) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (2)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (2)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (2)).getRuleCount()); - assertEquals (".a2", ((CSSStyleRule)aSR.getRuleAtIndex (2)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:orange", ((CSSStyleRule)aSR.getRuleAtIndex (2)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (3) instanceof CSSNestedDeclarations); - assertEquals (1, ((CSSNestedDeclarations)aSR.getRuleAtIndex (3)).getDeclarationCount()); - assertEquals ("color:yellow", ((CSSNestedDeclarations)aSR.getRuleAtIndex (3)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (4) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (4)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (4)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)aSR.getRuleAtIndex (4)).getRuleCount()); - assertEquals (".a3", ((CSSStyleRule)aSR.getRuleAtIndex (4)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:white", ((CSSStyleRule)aSR.getRuleAtIndex (4)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (aSR.getRuleAtIndex (5) instanceof CSSNestedDeclarations); - assertEquals (1, ((CSSNestedDeclarations)aSR.getRuleAtIndex (5)).getDeclarationCount()); - assertEquals ("color:cyan", ((CSSNestedDeclarations)aSR.getRuleAtIndex (5)).getDeclarationAtIndex(0).getAsCSSString()); + assertEquals ("div", aSR.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:red", aSR.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSStyleRule rule1 = (CSSStyleRule) aSR.getRuleAtIndex (0); + assertEquals (1, rule1.getSelectorCount ()); + assertEquals (1, rule1.getDeclarationCount ()); + assertEquals (0, rule1.getRuleCount ()); + assertEquals (".a1", rule1.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:green", rule1.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSNestedDeclarations rule2 = (CSSNestedDeclarations) aSR.getRuleAtIndex (1); + assertEquals (1, rule2.getDeclarationCount ()); + assertEquals ("color:blue", rule2.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSStyleRule rule3 = (CSSStyleRule) aSR.getRuleAtIndex (2); + assertEquals (1, rule3.getSelectorCount ()); + assertEquals (1, rule3.getDeclarationCount ()); + assertEquals (0, rule3.getRuleCount ()); + assertEquals (".a2", rule3.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:orange", rule3.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSNestedDeclarations rule4 = (CSSNestedDeclarations) aSR.getRuleAtIndex (3); + assertEquals (1, rule4.getDeclarationCount ()); + assertEquals ("color:yellow", rule4.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSStyleRule rule5 = (CSSStyleRule) aSR.getRuleAtIndex (4); + assertEquals (1, rule5.getSelectorCount ()); + assertEquals (1, rule5.getDeclarationCount ()); + assertEquals (0, rule5.getRuleCount ()); + assertEquals (".a3", rule5.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:white", rule5.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSNestedDeclarations rule6 = (CSSNestedDeclarations) aSR.getRuleAtIndex (5); + assertEquals (1, rule6.getDeclarationCount ()); + assertEquals ("color:cyan", rule6.getDeclarationAtIndex (0).getAsCSSString ()); } } diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSSupportsRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSSupportsRuleTest.java index b66b510c..b9e168ca 100644 --- a/ph-css/src/test/java/com/helger/css/decl/CSSSupportsRuleTest.java +++ b/ph-css/src/test/java/com/helger/css/decl/CSSSupportsRuleTest.java @@ -134,39 +134,40 @@ public void testRead2 () @Test public void testRead3 () { - CSSSupportsRule aSR = _parse (String.join("\n", List.of( - "@supports(column-count: 1) {", - " .foo {", - " color: white;", - " .bar {", - " color: orange", - " }", - " color: black;", - " }", - "}"))); + CSSSupportsRule aSR = _parse (""" + @supports(column-count: 1) { + .foo { + color: white; + .bar { + color: orange + } + color: black; + } + } + """); assertEquals (1, aSR.getSupportsConditionMemberCount ()); assertEquals (1, aSR.getRuleCount ()); - assertEquals ("(column-count:1)", aSR.getSupportsConditionMemberAtIndex (0).getAsCSSString()); + assertEquals ("(column-count:1)", aSR.getSupportsConditionMemberAtIndex (0).getAsCSSString ()); - assertTrue (aSR.getRuleAtIndex (0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getDeclarationCount()); - assertEquals (2, ((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleCount()); - - assertEquals (".foo", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals (".foo", ((CSSStyleRule)aSR.getRuleAtIndex (0)).getSelectorAtIndex(0).getAsCSSString()); - - assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0) instanceof CSSStyleRule); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorCount()); - assertEquals (1, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationCount()); - assertEquals (0, ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getRuleCount()); - assertEquals (".bar", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getSelectorAtIndex(0).getAsCSSString()); - assertEquals ("color:orange", ((CSSStyleRule)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(0)).getDeclarationAtIndex(0).getAsCSSString()); - - assertTrue (((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1) instanceof CSSNestedDeclarations); - assertEquals (1, ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationCount()); - assertEquals ("color:black", ((CSSNestedDeclarations)((CSSStyleRule)aSR.getRuleAtIndex (0)).getRuleAtIndex(1)).getDeclarationAtIndex(0).getAsCSSString()); + CSSStyleRule rule1 = (CSSStyleRule) aSR.getRuleAtIndex (0); + assertEquals (1, rule1.getSelectorCount ()); + assertEquals (1, rule1.getDeclarationCount ()); + assertEquals (2, rule1.getRuleCount ()); + + assertEquals (".foo", rule1.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals (".foo", rule1.getSelectorAtIndex (0).getAsCSSString ()); + + CSSStyleRule rule11 = (CSSStyleRule) rule1.getRuleAtIndex (0); + assertEquals (1, rule11.getSelectorCount ()); + assertEquals (1, rule11.getDeclarationCount ()); + assertEquals (0, rule11.getRuleCount ()); + assertEquals (".bar", rule11.getSelectorAtIndex (0).getAsCSSString ()); + assertEquals ("color:orange", rule11.getDeclarationAtIndex (0).getAsCSSString ()); + + CSSNestedDeclarations rule12 = (CSSNestedDeclarations) rule1.getRuleAtIndex (1); + assertEquals (1, rule12.getDeclarationCount ()); + assertEquals ("color:black", rule12.getDeclarationAtIndex (0).getAsCSSString ()); } } diff --git a/ph-css/src/test/java/com/helger/css/reader/CSSReaderFuncTest.java b/ph-css/src/test/java/com/helger/css/reader/CSSReaderFuncTest.java index c350960b..0305b05e 100644 --- a/ph-css/src/test/java/com/helger/css/reader/CSSReaderFuncTest.java +++ b/ph-css/src/test/java/com/helger/css/reader/CSSReaderFuncTest.java @@ -355,7 +355,10 @@ public void testSpecialCasesAsString () String sCSS = ".class{color:red;.class{color:green}.class{color:blue}"; aCSS = CSSReader.readFromStringReader (sCSS, aReaderSettings); if (bBrowserCompliantMode) + { + assertNotNull (aCSS); assertEquals ("", new CSSWriter (aWriterSettings).getCSSAsString (aCSS)); + } else assertNull (aCSS);