From 268da6f7b61865cf5da1b455166e66ef42c18818 Mon Sep 17 00:00:00 2001 From: Philip Helger Date: Mon, 23 Sep 2024 13:18:04 +0200 Subject: [PATCH] Test fixes --- .../css/decl/CSSSelectorMemberPseudoHas.java | 147 ++++++++++++++++-- .../css/handler/CSSNodeToDomainObject.java | 45 ++++-- ph-css/src/main/jjtree/ParserCSS30.jjt | 22 +-- .../css/reader/AbstractFuncTestCSSReader.java | 27 ++-- .../css/reader/CSSReader30FuncTest.java | 13 +- .../helger/css/writer/CSSWriterFuncTest.java | 4 +- .../testfiles/css30/good/issue101.css | 2 +- 7 files changed, 204 insertions(+), 56 deletions(-) diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoHas.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoHas.java index 1e9177d7..7bf3f945 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoHas.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoHas.java @@ -23,7 +23,12 @@ import com.helger.commons.ValueEnforcer; import com.helger.commons.annotation.Nonempty; +import com.helger.commons.annotation.ReturnsMutableCopy; +import com.helger.commons.collection.impl.CommonsArrayList; +import com.helger.commons.collection.impl.ICommonsList; +import com.helger.commons.equals.EqualsHelper; import com.helger.commons.hashcode.HashCodeGenerator; +import com.helger.commons.state.EChange; import com.helger.commons.string.ToStringGenerator; import com.helger.css.CSSSourceLocation; import com.helger.css.ECSSVersion; @@ -33,7 +38,7 @@ /** * Represents a single, simple CSS selector as used for the ":has()" CSS pseudo - * element.
+ * element. * * @author Philip Helger * @since 7.0.3 @@ -41,19 +46,125 @@ @NotThreadSafe public class CSSSelectorMemberPseudoHas implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware { - private final CSSSelector m_aSelector; + private final ECSSSelectorCombinator m_eCombinator; + private final ICommonsList m_aNestedSelectors; private CSSSourceLocation m_aSourceLocation; - public CSSSelectorMemberPseudoHas (@Nonnull final CSSSelector aSelector) + public CSSSelectorMemberPseudoHas (@Nullable final ECSSSelectorCombinator eCombinator, + @Nonnull final CSSSelector aNestedSelector) + { + ValueEnforcer.notNull (aNestedSelector, "NestedSelector"); + m_eCombinator = eCombinator; + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelector); + } + + public CSSSelectorMemberPseudoHas (@Nullable final ECSSSelectorCombinator eCombinator, + @Nonnull final CSSSelector... aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_eCombinator = eCombinator; + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public CSSSelectorMemberPseudoHas (@Nullable final ECSSSelectorCombinator eCombinator, + @Nonnull final Iterable aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_eCombinator = eCombinator; + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + @Nullable + public ECSSSelectorCombinator getCombinator () + { + return m_eCombinator; + } + + public boolean hasSelectors () + { + return m_aNestedSelectors.isNotEmpty (); + } + + @Nonnegative + public int getSelectorCount () + { + return m_aNestedSelectors.size (); + } + + @Nonnull + public CSSSelectorMemberPseudoHas addSelector (@Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberPseudoHas addSelector (@Nonnull final CSSSelector aSelector) { ValueEnforcer.notNull (aSelector, "Selector"); - m_aSelector = aSelector; + + m_aNestedSelectors.add (aSelector); + return this; } @Nonnull - public final CSSSelector getSelector () + public CSSSelectorMemberPseudoHas addSelector (@Nonnegative final int nIndex, + @Nonnull final ICSSSelectorMember aSingleSelectorMember) { - return m_aSelector; + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (nIndex, new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberPseudoHas addSelector (@Nonnegative final int nIndex, @Nonnull final CSSSelector aSelector) + { + ValueEnforcer.isGE0 (nIndex, "Index"); + ValueEnforcer.notNull (aSelector, "Selector"); + + if (nIndex >= getSelectorCount ()) + m_aNestedSelectors.add (aSelector); + else + m_aNestedSelectors.add (nIndex, aSelector); + return this; + } + + @Nonnull + public EChange removeSelector (@Nonnull final CSSSelector aSelector) + { + return m_aNestedSelectors.removeObject (aSelector); + } + + @Nonnull + public EChange removeSelector (@Nonnegative final int nSelectorIndex) + { + return m_aNestedSelectors.removeAtIndex (nSelectorIndex); + } + + /** + * Remove all selectors. + * + * @return {@link EChange#CHANGED} if any selector was removed, + * {@link EChange#UNCHANGED} otherwise. Never null. + */ + @Nonnull + public EChange removeAllSelectors () + { + return m_aNestedSelectors.removeAll (); + } + + @Nullable + public CSSSelector getSelectorAtIndex (@Nonnegative final int nSelectorIndex) + { + return m_aNestedSelectors.getAtIndex (nSelectorIndex); + } + + @Nonnull + @ReturnsMutableCopy + public ICommonsList getAllSelectors () + { + return m_aNestedSelectors.getClone (); } @Nonnull @@ -62,8 +173,23 @@ public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonn { aSettings.checkVersionRequirements (this); + aSettings.checkVersionRequirements (this); + + final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); final StringBuilder aSB = new StringBuilder (":has("); - aSB.append (m_aSelector.getAsCSSString (aSettings, 0)); + + if (m_eCombinator != null) + aSB.append (m_eCombinator.getAsCSSString (aSettings)); + + boolean bFirst = true; + for (final CSSSelector aNestedSelector : m_aNestedSelectors) + { + if (bFirst) + bFirst = false; + else + aSB.append (bOptimizedOutput ? "," : ", "); + aSB.append (aNestedSelector.getAsCSSString (aSettings, 0)); + } return aSB.append (')').toString (); } @@ -92,19 +218,20 @@ public boolean equals (final Object o) if (o == null || !getClass ().equals (o.getClass ())) return false; final CSSSelectorMemberPseudoHas rhs = (CSSSelectorMemberPseudoHas) o; - return m_aSelector.equals (rhs.m_aSelector); + return EqualsHelper.equals (m_eCombinator, rhs.m_eCombinator) && m_aNestedSelectors.equals (rhs.m_aNestedSelectors); } @Override public int hashCode () { - return new HashCodeGenerator (this).append (m_aSelector).getHashCode (); + return new HashCodeGenerator (this).append (m_eCombinator).append (m_aNestedSelectors).getHashCode (); } @Override public String toString () { - return new ToStringGenerator (null).append ("Selector", m_aSelector) + return new ToStringGenerator (null).append ("Combinator", m_eCombinator) + .append ("NestedSelectors", m_aNestedSelectors) .appendIfNotNull ("SourceLocation", m_aSourceLocation) .getToString (); } 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 d4688d6e..e67868bb 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 @@ -202,6 +202,15 @@ private CSSSelectorAttribute _createSelectorAttribute (@Nonnull final CSSNode aN return ret; } + @Nullable + private ECSSSelectorCombinator _createSelectorCombinator (final String sText) + { + final ECSSSelectorCombinator eCombinator = ECSSSelectorCombinator.getFromNameOrNull (sText); + if (eCombinator == null) + m_aErrorHandler.onCSSInterpretationError ("Failed to parse CSS selector combinator '" + sText + "'"); + return eCombinator; + } + @Nullable private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) { @@ -224,13 +233,7 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) return _createSelectorAttribute (aNode); if (ECSSNodeType.SELECTORCOMBINATOR.isNode (aNode, m_eVersion)) - { - final String sText = aNode.getText (); - final ECSSSelectorCombinator eCombinator = ECSSSelectorCombinator.getFromNameOrNull (sText); - if (eCombinator == null) - m_aErrorHandler.onCSSInterpretationError ("Failed to parse CSS selector combinator '" + sText + "'"); - return eCombinator; - } + return _createSelectorCombinator (aNode.getText ()); if (ECSSNodeType.NEGATION.isNode (aNode, m_eVersion)) { @@ -318,11 +321,30 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) if (ECSSNodeType.PSEUDO_HAS.isNode (aChildNode, m_eVersion)) { - final CSSSelector aSelector = new CSSSelector (); + ECSSSelectorCombinator eSelectorCombinator = null; + final int nChildChildCount = aChildNode.jjtGetNumChildren (); - for (int j = 0; j < nChildChildCount; ++j) - aSelector.addMember (_createSelectorMember (aChildNode.jjtGetChild (j))); - final CSSSelectorMemberPseudoHas ret = new CSSSelectorMemberPseudoHas (aSelector); + int i = 0; + CSSNode aChildChildNode = aChildNode.jjtGetChild (i); + if (ECSSNodeType.SELECTORCOMBINATOR.isNode (aChildChildNode, m_eVersion)) + { + eSelectorCombinator = _createSelectorCombinator (aChildChildNode.getText ()); + if (eSelectorCombinator != null) + { + // Skip the first elements as selector + i++; + } + } + + final ICommonsList aNestedSelectors = new CommonsArrayList <> (); + for (; i < nChildChildCount; ++i) + { + aChildChildNode = aChildNode.jjtGetChild (i); + final CSSSelector aSelector = _createSelector (aChildChildNode); + aNestedSelectors.add (aSelector); + } + + final CSSSelectorMemberPseudoHas ret = new CSSSelectorMemberPseudoHas (eSelectorCombinator, aNestedSelectors); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); return ret; @@ -380,6 +402,7 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) private CSSSelector _createSelector (@Nonnull final CSSNode aNode) { _expectNodeType (aNode, ECSSNodeType.SELECTOR); + final CSSSelector ret = new CSSSelector (); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index dd3de4fc..0dfcd4f6 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -1098,15 +1098,11 @@ void pseudoWhere() #where : {} selector() } -void pseudoElementSelector() : {} -{ - { jjtThis.setText (":"); } - pseudoClassSelector() -} - void pseudoClassSelector() : {} { { jjtThis.setText (":"); } + // For pseudo elements + ( { jjtThis.appendText (":"); } )? ( { jjtThis.appendText (token.image); } pseudoNth() // do not append because of expression! @@ -1154,7 +1150,7 @@ void funcNot() : {} void simpleSelectorSequence() #void : {} { - LOOKAHEAD(2) + LOOKAHEAD(4) ( typeSelector() ( idSelector() | classSelector() @@ -1162,10 +1158,6 @@ void simpleSelectorSequence() #void : {} | pseudoClassSelector() | funcNot() )* - ( LOOKAHEAD(2) - pseudoElementSelector() - ( pseudoClassSelector() )* - )* ) | ( idSelector() | classSelector() @@ -1173,14 +1165,6 @@ void simpleSelectorSequence() #void : {} | pseudoClassSelector() | funcNot() )+ - ( LOOKAHEAD(2) - pseudoElementSelector() - ( pseudoClassSelector() )* - )* - | LOOKAHEAD(2) - ( pseudoElementSelector() - ( pseudoClassSelector() )* - )+ // Extension for CSS animations (e.g. 50%) | } diff --git a/ph-css/src/test/java/com/helger/css/reader/AbstractFuncTestCSSReader.java b/ph-css/src/test/java/com/helger/css/reader/AbstractFuncTestCSSReader.java index 9f34d072..4bc45e7e 100644 --- a/ph-css/src/test/java/com/helger/css/reader/AbstractFuncTestCSSReader.java +++ b/ph-css/src/test/java/com/helger/css/reader/AbstractFuncTestCSSReader.java @@ -50,7 +50,9 @@ public abstract class AbstractFuncTestCSSReader private final CSSReaderSettings m_aReaderSettings; private final CSSWriterSettings m_aWriterSettings; - protected AbstractFuncTestCSSReader (@Nonnull final Charset aCharset, final boolean bDebug, final boolean bBrowserCompliantMode) + protected AbstractFuncTestCSSReader (@Nonnull final Charset aCharset, + final boolean bDebug, + final boolean bBrowserCompliantMode) { m_bDebug = bDebug; m_aReaderSettings = new CSSReaderSettings ().setFallbackCharset (aCharset) @@ -76,13 +78,13 @@ protected final void testReadGood (@Nonnull final String sBaseDir) for (final File aFile : new FileSystemRecursiveIterator (aBaseDir).withFilter (IFileFilter.filenameEndsWith (".css"))) { - final String sKey = aFile.getAbsolutePath (); + final String sFilename = aFile.getAbsolutePath (); if (m_bDebug) - m_aLogger.info ("Filename: " + sKey); + m_aLogger.info ("Filename: " + sFilename); final CollectingCSSParseErrorHandler aErrorHdl = new CollectingCSSParseErrorHandler (); m_aReaderSettings.setCustomErrorHandler (aErrorHdl.and (new LoggingCSSParseErrorHandler ())); final CascadingStyleSheet aCSS = CSSReader.readFromFile (aFile, m_aReaderSettings); - assertNotNull (sKey, aCSS); + assertNotNull (sFilename, aCSS); // May have errors or not if (m_bDebug) @@ -90,26 +92,27 @@ protected final void testReadGood (@Nonnull final String sBaseDir) // Write optimized version and compare it String sCSS = new CSSWriter (m_aWriterSettings.setOptimizedOutput (true)).getCSSAsString (aCSS); - assertNotNull (sKey, sCSS); + assertNotNull (sFilename, sCSS); if (m_bDebug) m_aLogger.info ("Created CSS: " + sCSS); final CascadingStyleSheet aCSSReRead = CSSReader.readFromStringReader (sCSS, m_aReaderSettings); - assertNotNull ("Failed to parse " + sKey + ":\n" + sCSS, aCSSReRead); - assertEquals (sKey + "\n" + sCSS, aCSS, aCSSReRead); + assertNotNull ("Failed to parse " + sFilename + ":\n" + sCSS, aCSSReRead); + assertEquals ("Differences in " + sFilename + "\n" + sCSS + "\n" + aCSS + "\n" + aCSSReRead, aCSS, aCSSReRead); // Write non-optimized version and compare it sCSS = new CSSWriter (m_aWriterSettings.setOptimizedOutput (false)).getCSSAsString (aCSS); - assertNotNull (sKey, sCSS); + assertNotNull (sFilename, sCSS); if (m_bDebug) m_aLogger.info ("Read and re-created CSS: " + sCSS); - assertEquals (sKey, aCSS, CSSReader.readFromStringReader (sCSS, m_aReaderSettings)); + assertEquals (sFilename, aCSS, CSSReader.readFromStringReader (sCSS, m_aReaderSettings)); // Write non-optimized and code-removed version and ensure it is not // null - sCSS = new CSSWriter (m_aWriterSettings.setOptimizedOutput (false).setRemoveUnnecessaryCode (true)).getCSSAsString (aCSS); - assertNotNull (sKey, sCSS); - assertNotNull (sKey, CSSReader.readFromStringReader (sCSS, m_aReaderSettings)); + sCSS = new CSSWriter (m_aWriterSettings.setOptimizedOutput (false) + .setRemoveUnnecessaryCode (true)).getCSSAsString (aCSS); + assertNotNull (sFilename, sCSS); + assertNotNull (sFilename, CSSReader.readFromStringReader (sCSS, m_aReaderSettings)); // Restore value :) m_aWriterSettings.setRemoveUnnecessaryCode (false); diff --git a/ph-css/src/test/java/com/helger/css/reader/CSSReader30FuncTest.java b/ph-css/src/test/java/com/helger/css/reader/CSSReader30FuncTest.java index 1ff7a982..dbe8528b 100644 --- a/ph-css/src/test/java/com/helger/css/reader/CSSReader30FuncTest.java +++ b/ph-css/src/test/java/com/helger/css/reader/CSSReader30FuncTest.java @@ -348,7 +348,8 @@ public void testSpecialCasesAsString () final CSSReaderSettings aReaderSettings = new CSSReaderSettings ().setCSSVersion (ECSSVersion.CSS30) .setCustomErrorHandler (new LoggingCSSParseErrorHandler ()) .setBrowserCompliantMode (bBrowserCompliantMode); - final CSSWriterSettings aWriterSettings = new CSSWriterSettings ().setCSSVersion (ECSSVersion.CSS30).setOptimizedOutput (true); + final CSSWriterSettings aWriterSettings = new CSSWriterSettings ().setCSSVersion (ECSSVersion.CSS30) + .setOptimizedOutput (true); CascadingStyleSheet aCSS; CascadingStyleSheet aCSS2; @@ -483,6 +484,14 @@ public void testSpecialCasesAsString () sCSS = ".x{left: calc(50% - (600px / 2 + var(--page-column-padding-x)));}"; aCSS = CSSReader.readFromStringReader (sCSS, aReaderSettings); assertNotNull (aCSS); - assertEquals (".x{left:calc(50% - (600px/2 + var(--page-column-padding-x)))}", new CSSWriter (aWriterSettings).getCSSAsString (aCSS)); + assertEquals (".x{left:calc(50% - (600px/2 + var(--page-column-padding-x)))}", + new CSSWriter (aWriterSettings).getCSSAsString (aCSS)); + + // Issue 101 + sCSS = "section:not(:has(h1, h2, h3, h4, h5, h6)) { color:red; }"; + aCSS = CSSReader.readFromStringReader (sCSS, aReaderSettings); + assertNotNull (aCSS); + assertEquals ("section:not(:has(h1,h2,h3,h4,h5,h6)){color:red}", + new CSSWriter (aWriterSettings).getCSSAsString (aCSS)); } } diff --git a/ph-css/src/test/java/com/helger/css/writer/CSSWriterFuncTest.java b/ph-css/src/test/java/com/helger/css/writer/CSSWriterFuncTest.java index cf371910..a20c414a 100644 --- a/ph-css/src/test/java/com/helger/css/writer/CSSWriterFuncTest.java +++ b/ph-css/src/test/java/com/helger/css/writer/CSSWriterFuncTest.java @@ -60,7 +60,9 @@ private void _testMe (@Nonnull final File aFile, @Nonnull final ECSSVersion eVer System.out.println ("--" + i + "--\n" + sCSS); // read again from buffer - assertEquals (aFile.getAbsolutePath () + (i == 0 ? " unoptimized" : " optimized"), aCSS, CSSReader.readFromString (sCSS, eVersion)); + assertEquals (aFile.getAbsolutePath () + (i == 0 ? " unoptimized" : " optimized"), + aCSS, + CSSReader.readFromString (sCSS, eVersion)); } } diff --git a/ph-css/src/test/resources/testfiles/css30/good/issue101.css b/ph-css/src/test/resources/testfiles/css30/good/issue101.css index 4956b2fe..37b3140b 100644 --- a/ph-css/src/test/resources/testfiles/css30/good/issue101.css +++ b/ph-css/src/test/resources/testfiles/css30/good/issue101.css @@ -71,7 +71,7 @@ section:not(:has(h1, h2, h3, h4, h5, h6)) { } section:has(:not(h1, h2, h3, h4, h5, h6)) { - color:red; + color:blue; } foo|h1 { color: blue } /* first rule */