From ac894283fd57dedf1c60a5c07b61375a30d13261 Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Wed, 12 Jun 2024 16:08:23 +0200 Subject: [PATCH 01/15] issues88: add where and is css pseudo class selector --- .../helger/css/decl/CSSSelectorMemberIs.java | 201 ++++++++++++++++++ .../css/decl/CSSSelectorMemberWhere.java | 201 ++++++++++++++++++ .../css/handler/CSSNodeToDomainObject.java | 34 +++ .../com/helger/css/handler/ECSSNodeType.java | 2 + ph-css/src/main/jjtree/ParserCSS30.jjt | 36 ++++ .../css/supplementary/issues/Issue88Test.java | 39 ++++ 6 files changed, 513 insertions(+) create mode 100644 ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberIs.java create mode 100644 ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberWhere.java create mode 100644 ph-css/src/test/java/com/helger/css/supplementary/issues/Issue88Test.java diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberIs.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberIs.java new file mode 100644 index 00000000..807d48bd --- /dev/null +++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberIs.java @@ -0,0 +1,201 @@ +package com.helger.css.decl; + +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.NotThreadSafe; + +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.hashcode.HashCodeGenerator; +import com.helger.commons.state.EChange; +import com.helger.commons.string.ToStringGenerator; +import com.helger.css.CSSSourceLocation; +import com.helger.css.ECSSVersion; +import com.helger.css.ICSSSourceLocationAware; +import com.helger.css.ICSSVersionAware; +import com.helger.css.ICSSWriterSettings; + +/** + * Represents complex CSS selector as used for the ":is()" CSS pseudo + * class function. + * + * @author Mike Wiedenauer + * @author Philip Helger + * @since 7.0.3 + */ +@NotThreadSafe +public class CSSSelectorMemberIs implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware +{ + private final ICommonsList m_aNestedSelectors; + private CSSSourceLocation m_aSourceLocation; + + public CSSSelectorMemberIs (@Nonnull final CSSSelector aNestedSelector) + { + ValueEnforcer.notNull (aNestedSelector, "NestedSelector"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelector); + } + + public CSSSelectorMemberIs (@Nonnull final CSSSelector... aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public CSSSelectorMemberIs (@Nonnull final Iterable aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public boolean hasSelectors () + { + return m_aNestedSelectors.isNotEmpty (); + } + + @Nonnegative + public int getSelectorCount () + { + return m_aNestedSelectors.size (); + } + + @Nonnull + public CSSSelectorMemberIs addSelector (@Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberIs addSelector (@Nonnull final CSSSelector aSelector) + { + ValueEnforcer.notNull (aSelector, "Selector"); + + m_aNestedSelectors.add (aSelector); + return this; + } + + @Nonnull + public CSSSelectorMemberIs addSelector (@Nonnegative final int nIndex, @Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (nIndex, new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberIs 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 + @Nonempty + public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) + { + aSettings.checkVersionRequirements (this); + + final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); + final StringBuilder aSB = new StringBuilder (":is("); + 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 (); + } + + @Nonnull + public ECSSVersion getMinimumCSSVersion () + { + return ECSSVersion.CSS30; + } + + @Nullable + public final CSSSourceLocation getSourceLocation () + { + return m_aSourceLocation; + } + + 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 CSSSelectorMemberIs rhs = (CSSSelectorMemberIs) o; + return m_aNestedSelectors.equals (rhs.m_aNestedSelectors); + } + + @Override + public int hashCode () + { + return new HashCodeGenerator (this).append (m_aNestedSelectors).getHashCode (); + } + + @Override + public String toString () + { + return new ToStringGenerator (null).append ("nestedSelectors", m_aNestedSelectors) + .appendIfNotNull ("SourceLocation", m_aSourceLocation) + .getToString (); + } +} diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberWhere.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberWhere.java new file mode 100644 index 00000000..e58e86a0 --- /dev/null +++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberWhere.java @@ -0,0 +1,201 @@ +package com.helger.css.decl; + +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.NotThreadSafe; + +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.hashcode.HashCodeGenerator; +import com.helger.commons.state.EChange; +import com.helger.commons.string.ToStringGenerator; +import com.helger.css.CSSSourceLocation; +import com.helger.css.ECSSVersion; +import com.helger.css.ICSSSourceLocationAware; +import com.helger.css.ICSSVersionAware; +import com.helger.css.ICSSWriterSettings; + +/** + * Represents complex CSS selector as used for the ":where()" CSS pseudo + * class function. + * + * @author Mike Wiedenauer + * @author Philip Helger + * @since 7.0.3 + */ +@NotThreadSafe +public class CSSSelectorMemberWhere implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware +{ + private final ICommonsList m_aNestedSelectors; + private CSSSourceLocation m_aSourceLocation; + + public CSSSelectorMemberWhere (@Nonnull final CSSSelector aNestedSelector) + { + ValueEnforcer.notNull (aNestedSelector, "NestedSelector"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelector); + } + + public CSSSelectorMemberWhere (@Nonnull final CSSSelector... aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public CSSSelectorMemberWhere (@Nonnull final Iterable aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public boolean hasSelectors () + { + return m_aNestedSelectors.isNotEmpty (); + } + + @Nonnegative + public int getSelectorCount () + { + return m_aNestedSelectors.size (); + } + + @Nonnull + public CSSSelectorMemberWhere addSelector (@Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberWhere addSelector (@Nonnull final CSSSelector aSelector) + { + ValueEnforcer.notNull (aSelector, "Selector"); + + m_aNestedSelectors.add (aSelector); + return this; + } + + @Nonnull + public CSSSelectorMemberWhere addSelector (@Nonnegative final int nIndex, @Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (nIndex, new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberWhere 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 + @Nonempty + public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) + { + aSettings.checkVersionRequirements (this); + + final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); + final StringBuilder aSB = new StringBuilder (":where("); + 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 (); + } + + @Nonnull + public ECSSVersion getMinimumCSSVersion () + { + return ECSSVersion.CSS30; + } + + @Nullable + public final CSSSourceLocation getSourceLocation () + { + return m_aSourceLocation; + } + + 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 CSSSelectorMemberWhere rhs = (CSSSelectorMemberWhere) o; + return m_aNestedSelectors.equals (rhs.m_aNestedSelectors); + } + + @Override + public int hashCode () + { + return new HashCodeGenerator (this).append (m_aNestedSelectors).getHashCode (); + } + + @Override + public String toString () + { + return new ToStringGenerator (null).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 d5339f13..6ee25bde 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 @@ -244,6 +244,40 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) return ret; } + if (ECSSNodeType.WHERE.isNode (aNode, m_eVersion)) + { + // Note: no children don't make sense but are syntactically allowed! + final ICommonsList aNestedSelectors = new CommonsArrayList <> (); + for (int i = 0; i < nChildCount; ++i) + { + final CSSNode aChildNode = aNode.jjtGetChild (i); + final CSSSelector aSelector = _createSelector (aChildNode); + aNestedSelectors.add (aSelector); + } + + final CSSSelectorMemberWhere ret = new CSSSelectorMemberWhere (aNestedSelectors); + if (m_bUseSourceLocation) + ret.setSourceLocation (aNode.getSourceLocation ()); + return ret; + } + + if (ECSSNodeType.IS.isNode (aNode, m_eVersion)) + { + // Note: no children don't make sense but are syntactically allowed! + final ICommonsList aNestedSelectors = new CommonsArrayList <> (); + for (int i = 0; i < nChildCount; ++i) + { + final CSSNode aChildNode = aNode.jjtGetChild (i); + final CSSSelector aSelector = _createSelector (aChildNode); + aNestedSelectors.add (aSelector); + } + + final CSSSelectorMemberIs ret = new CSSSelectorMemberIs (aNestedSelectors); + if (m_bUseSourceLocation) + ret.setSourceLocation (aNode.getSourceLocation ()); + return ret; + } + if (ECSSNodeType.PSEUDO.isNode (aNode, m_eVersion)) { if (nChildCount == 0) 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 08189cfc..81455a1c 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 @@ -61,6 +61,8 @@ public enum ECSSNodeType HOSTCONTEXT (ParserCSS30TreeConstants.JJTHOSTCONTEXT), SLOTTED (ParserCSS30TreeConstants.JJTSLOTTED), NEGATION (ParserCSS30TreeConstants.JJTNEGATION), + WHERE (ParserCSS30TreeConstants.JJTWHERE), + IS (ParserCSS30TreeConstants.JJTIS), ATTRIB (ParserCSS30TreeConstants.JJTATTRIB), ATTRIBOPERATOR (ParserCSS30TreeConstants.JJTATTRIBOPERATOR), ATTRIBVALUE (ParserCSS30TreeConstants.JJTATTRIBVALUE), diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index a9aae592..e41eb785 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -301,6 +301,8 @@ TOKEN : | < FUNCTION_CALC: "calc(" | "-" "-calc(" > | < FUNCTION_NOT: ":not(" > +| < FUNCTION_WHERE: ":where(" > +| < FUNCTION_IS: ":is(" > | < FUNCTION_HOST: "host(" > | < FUNCTION_HOSTCONTEXT: "host-context(" > | < FUNCTION_SLOTTED: "slotted(" > @@ -1099,6 +1101,36 @@ void negation() : {} } +void where() : {} +{ + { jjtThis.setText (":where("); } + ( )* + ( selector () + ( )* + ( + ( )* + selector() + ( )* + )* + )? + +} + +void is() : {} +{ + { jjtThis.setText (":is("); } + ( )* + ( selector () + ( )* + ( + ( )* + selector() + ( )* + )* + )? + +} + void simpleSelectorSequence() #void : {} { LOOKAHEAD(2) @@ -1107,6 +1139,8 @@ void simpleSelectorSequence() #void : {} | _class() | attrib() | pseudo() + | where() + | is() | negation() )* ) @@ -1114,6 +1148,8 @@ void simpleSelectorSequence() #void : {} | _class() | attrib() | pseudo() + | where() + | is() | negation() )+ // Extension for CSS animations (e.g. 50%) diff --git a/ph-css/src/test/java/com/helger/css/supplementary/issues/Issue88Test.java b/ph-css/src/test/java/com/helger/css/supplementary/issues/Issue88Test.java new file mode 100644 index 00000000..daaa9ba7 --- /dev/null +++ b/ph-css/src/test/java/com/helger/css/supplementary/issues/Issue88Test.java @@ -0,0 +1,39 @@ +package com.helger.css.supplementary.issues; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.StandardCharsets; + +import org.junit.Test; + +import com.helger.commons.io.resource.ClassPathResource; +import com.helger.commons.io.resource.IReadableResource; +import com.helger.css.ECSSVersion; +import com.helger.css.decl.CascadingStyleSheet; +import com.helger.css.reader.CSSReader; +import com.helger.css.reader.CSSReaderSettings; +import com.helger.css.reader.errorhandler.LoggingCSSParseErrorHandler; +import com.helger.css.writer.CSSWriter; +import com.helger.css.writer.CSSWriterSettings; + +/** + * Test for issue 88: https://github.com/phax/ph-css/issues/88 + * + * @author Mike Wiedenbauer + */ +public final class Issue88Test +{ + @Test + public void testIssue () + { + final String sCSS = ":where(.some-tile:not(.preserve-color))>*{color:#161616}"; + final CascadingStyleSheet aCSS = CSSReader.readFromStringReader (sCSS, + new CSSReaderSettings ().setCSSVersion (ECSSVersion.LATEST)); + assertNotNull (aCSS); + assertEquals (":where(.some-tile:not(.preserve-color))>*{color:#161616}", + new CSSWriter (new CSSWriterSettings ().setOptimizedOutput (true)).setWriteHeaderText (false) + .getCSSAsString (aCSS)); + } +} From 0899b6a90d671ec53fb413b4f6f341c0b93b4334 Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Fri, 14 Jun 2024 14:37:12 +0200 Subject: [PATCH 02/15] issue-91: apply unescape logic --- .../com/helger/css/parser/CSSParseHelper.java | 127 +++++++++++++++++- .../helger/css/parser/CSSParseHelperTest.java | 1 + .../helger/css/utils/CSSURLHelperTest.java | 4 +- 3 files changed, 125 insertions(+), 7 deletions(-) diff --git a/ph-css/src/main/java/com/helger/css/parser/CSSParseHelper.java b/ph-css/src/main/java/com/helger/css/parser/CSSParseHelper.java index 4b92c9a1..1ede140c 100644 --- a/ph-css/src/main/java/com/helger/css/parser/CSSParseHelper.java +++ b/ph-css/src/main/java/com/helger/css/parser/CSSParseHelper.java @@ -49,6 +49,9 @@ public final class CSSParseHelper private static final String SPLIT_NUMBER_REGEX = "^([0-9]*\\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+([eE][+-]?[0-9]+)?).*$"; private static final Pattern SPLIT_NUMBER_PATTERN = RegExCache.getPattern (SPLIT_NUMBER_REGEX); + private static final char[] HEXA_CHARS_UPPER = "0123456789ABCDEF".toCharArray(); + private static final char[] HEXA_CHARS_LOWER = "0123456789abcdef".toCharArray(); + @PresentForCodeCoverage private static final CSSParseHelper INSTANCE = new CSSParseHelper (); @@ -61,6 +64,23 @@ private static String _trimBy (@Nonnull final CharSequence s, final int nLeftSki return s.toString ().substring (nLeftSkip, s.length () - nRightSkip); } + @Nonnull + private static int _parseIntFromReference(@Nonnull final String text, final int start, final int end, final int radix) { + int result = 0; + for (int i = start; i < end; i++) { + final char c = text.charAt(i); + int n = -1; + for (int j = 0; j < HEXA_CHARS_UPPER.length; j++) { + if (c == HEXA_CHARS_UPPER[j] || c == HEXA_CHARS_LOWER[j]) { + n = j; + break; + } + } + result = (radix * result) + n; + } + return result; + } + /** * Remove surrounding quotes (single or double) of a string (if present). If * the start and the end quote are not equal, nothing happens. @@ -87,6 +107,7 @@ public static String extractStringValue (@Nullable final String sStr) /** * Unescape all escaped characters in a CSS URL. All characters masked with a * '\\' character replaced. + * Implementation taken from: https://github.com/unbescape/unbescape * * @param sEscapedURL * The escaped URL. May not be null! @@ -105,14 +126,110 @@ public static String unescapeURL (@Nonnull final String sEscapedURL) final StringBuilder aSB = new StringBuilder (sEscapedURL.length ()); int nPrevIndex = 0; - do + int nReferenceOffset = 0; + while (nIndex >= 0) { + int nCodePoint = -1; + + final char c1 = sEscapedURL.charAt (nIndex + 1); + switch (c1) { + case '\n': + nCodePoint = -2; + nReferenceOffset = nIndex + 1; + break; + case ' ': + case '!': + case '"': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + // hyphen: will only be escaped when identifer starts with '--' or '-{digit}' + case '-': + case '.': + case '/': + // colon: will not be used for escaping: not recognized by IE < 8 + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case '\\': + case ']': + case '^': + // underscore: will only be escaped at the beginning of an identifier (in order to avoid issues in IE6) + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + nCodePoint = c1; + nReferenceOffset = nIndex + 1; + break; + default: + break; + } + + if (nCodePoint == -1) { + if ((c1 >= '0' && c1 <= '9') || (c1 >= 'A' && c1 <= 'F') || (c1 >= 'a' && c1 <= 'f')) { + // This is a hexa escape + + int f = nIndex + 2; + while (f < (nIndex + 7) && f < sEscapedURL.length()) { + final char cf = sEscapedURL.charAt (f); + if (!((cf >= '0' && cf <= '9') || (cf >= 'A' && cf <= 'F') || (cf >= 'a' && cf <= 'f'))) { + break; + } + f++; + } + + nCodePoint = _parseIntFromReference(sEscapedURL, nIndex + 1, f, 16); + + // Fast-forward to the first char after the parsed codepoint + nReferenceOffset = f - 1; + + // If there is a whitespace after the escape, just ignore it. + if (f < sEscapedURL.length() && sEscapedURL.charAt (f) == ' ') { + nReferenceOffset++; + } + + // Don't continue here, just let the unescape code below do its job + } else if (c1 == '\r' || c1 == '\f') { + // The only characters that cannot be escaped by means of a backslash are + // carriage return and form feed (besides hexadecimal digits). + nIndex = sEscapedURL.indexOf (URL_ESCAPE_CHAR, nIndex + 1); + continue; + } else { + // We weren't able to consume any valid escape chars, just consider it a normal char, + // which is allowed by the CSS escape syntax. + nCodePoint = c1; + nReferenceOffset = nIndex + 1; + } + } + // Append everything before the first quote char aSB.append (sEscapedURL, nPrevIndex, nIndex); - // Append the quoted char itself - aSB.append (sEscapedURL, nIndex + 1, nIndex + 2); - // The new position to start searching - nPrevIndex = nIndex + 2; + + nIndex = nReferenceOffset; + nPrevIndex = nIndex + 1; + + // Append the unescaped char itself + if (nCodePoint > '\uFFFF') { + aSB.append (Character.toChars (nCodePoint)); + } else if (nCodePoint != -2) { + aSB.append ((char) nCodePoint); + } + // Search the next escaped char nIndex = sEscapedURL.indexOf (URL_ESCAPE_CHAR, nPrevIndex); } while (nIndex >= 0); diff --git a/ph-css/src/test/java/com/helger/css/parser/CSSParseHelperTest.java b/ph-css/src/test/java/com/helger/css/parser/CSSParseHelperTest.java index c303b2b2..8dab9ce9 100644 --- a/ph-css/src/test/java/com/helger/css/parser/CSSParseHelperTest.java +++ b/ph-css/src/test/java/com/helger/css/parser/CSSParseHelperTest.java @@ -60,5 +60,6 @@ public void testUnescapeCSSURL () assertEquals ("/foo/bla.gif", CSSParseHelper.unescapeURL ("/foo/bla.gif")); assertEquals ("/foo/bla().gif", CSSParseHelper.unescapeURL ("/foo/bla\\(\\).gif")); assertEquals ("\\\\server\\foo\\bla.gif", CSSParseHelper.unescapeURL ("\\\\\\\\server\\\\foo\\\\bla.gif")); + assertEquals("/home/data/image.png", CSSParseHelper.unescapeURL("\\2f home\\2f data\\2f image.png")); } } diff --git a/ph-css/src/test/java/com/helger/css/utils/CSSURLHelperTest.java b/ph-css/src/test/java/com/helger/css/utils/CSSURLHelperTest.java index 21105891..16b643ee 100644 --- a/ph-css/src/test/java/com/helger/css/utils/CSSURLHelperTest.java +++ b/ph-css/src/test/java/com/helger/css/utils/CSSURLHelperTest.java @@ -39,8 +39,8 @@ public void testGetURLValue () assertEquals ("a.gif", CSSURLHelper.getURLValue ("url('a.gif')")); assertEquals ("a.gif", CSSURLHelper.getURLValue ("url(\"a.gif\")")); assertEquals ("a.gif?x=y", CSSURLHelper.getURLValue ("url(\"a.gif?x=y\")")); - // Test quote 'a' character - assertEquals ("a.gif", CSSURLHelper.getURLValue ("url(\"\\a.gif\")")); + // Test quoted & escaped 'a' character + assertEquals ("\n.gif", CSSURLHelper.getURLValue ("url(\"\\a.gif\")")); // different quote types assertEquals ("\"a.gif?x=y'", CSSURLHelper.getURLValue ("url(\"a.gif?x=y')")); // missing trailing ")" From 297a96c263d888ee5e65af2f93279cad3d2d273c Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Fri, 14 Jun 2024 16:23:24 +0200 Subject: [PATCH 03/15] Change groupId to "unblu.patched.com.helger" --- ph-css/pom.xml | 2 +- ph-csscompress-maven-plugin/pom.xml | 6 +++--- pom.xml | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index a5bd0352..782fa729 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -20,7 +20,7 @@ 4.0.0 - com.helger + unblu.patched.com.helger ph-css-parent-pom 7.0.3-SNAPSHOT diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index 1cce042c..31ee4dc1 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -20,11 +20,11 @@ 4.0.0 - com.helger + unblu.patched.com.helger ph-css-parent-pom 7.0.3-SNAPSHOT - com.helger.maven + unblu.patched.com.helger.maven ph-csscompress-maven-plugin maven-plugin ph-csscompress-maven-plugin @@ -74,7 +74,7 @@ - com.helger + unblu.patched.com.helger ph-css diff --git a/pom.xml b/pom.xml index 15ed519a..dd4d6bdf 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ parent-pom 2.0.5 + unblu.patched.com.helger ph-css-parent-pom 7.0.3-SNAPSHOT pom @@ -72,12 +73,12 @@ - com.helger + unblu.patched.com.helger ph-css ${project.version} - com.helger + unblu.patched.com.helger ph-csscompress-maven-plugin ${project.version} From 9775f6a6355f2614d4eb196d38a3f1305e98e6d9 Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Fri, 14 Jun 2024 16:23:52 +0200 Subject: [PATCH 04/15] [release] set version to "7.0.3-unblu-1" --- ph-css/pom.xml | 2 +- ph-csscompress-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index 782fa729..4bde12f0 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.3-SNAPSHOT + 7.0.3-unblu-1 ph-css bundle diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index 31ee4dc1..a3f6cb9e 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.3-SNAPSHOT + 7.0.3-unblu-1 unblu.patched.com.helger.maven ph-csscompress-maven-plugin diff --git a/pom.xml b/pom.xml index dd4d6bdf..8e571399 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.3-SNAPSHOT + 7.0.3-unblu-1 pom ph-css-parent-pom Base POM to build the ph-css projects From bff2bb30e095298d214e865291dd58ffab7108ae Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Fri, 14 Jun 2024 16:25:24 +0200 Subject: [PATCH 05/15] set version to "7.0.3-SNAPSHOT" --- ph-css/pom.xml | 2 +- ph-csscompress-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index 4bde12f0..782fa729 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.3-unblu-1 + 7.0.3-SNAPSHOT ph-css bundle diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index a3f6cb9e..31ee4dc1 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.3-unblu-1 + 7.0.3-SNAPSHOT unblu.patched.com.helger.maven ph-csscompress-maven-plugin diff --git a/pom.xml b/pom.xml index 8e571399..dd4d6bdf 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.3-unblu-1 + 7.0.3-SNAPSHOT pom ph-css-parent-pom Base POM to build the ph-css projects From 2ba2fb10b7a08d5aceab6e18d0368e5d59fa5ee7 Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Tue, 25 Feb 2025 15:51:31 +0100 Subject: [PATCH 06/15] - fix 'is' and 'where' --- ph-css/pom.xml | 2 +- ph-css/src/main/jjtree/ParserCSS30.jjt | 4 ++-- ph-csscompress-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index 956689e2..585941df 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-1 ph-css bundle diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index 730a5b8d..a3f7852c 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -1088,13 +1088,13 @@ void pseudoHas() #has : {} void pseudoIs() #is : {} { ( )* - selector() + relativeSelectorList() } void pseudoWhere() #where : {} { ( )* - selector() + relativeSelectorList() } void pseudoClassSelector() : {} diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index 9dbc6e3d..67230802 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-1 unblu.patched.com.helger.maven ph-csscompress-maven-plugin diff --git a/pom.xml b/pom.xml index 98d7d058..8324af9b 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-1 pom ph-css-parent-pom Base POM to build the ph-css projects From 52e40575907e5f59d9444e80762f5a58667f83ed Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Tue, 25 Feb 2025 16:51:05 +0100 Subject: [PATCH 07/15] fix java side of 'is' and 'where' --- .../helger/css/decl/CSSSelectorMemberIs.java | 201 ------------------ .../css/decl/CSSSelectorMemberPseudoIs.java | 128 ++++++++++- .../decl/CSSSelectorMemberPseudoWhere.java | 127 ++++++++++- .../css/decl/CSSSelectorMemberWhere.java | 201 ------------------ .../css/handler/CSSNodeToDomainObject.java | 54 ++--- 5 files changed, 251 insertions(+), 460 deletions(-) delete mode 100644 ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberIs.java delete mode 100644 ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberWhere.java diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberIs.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberIs.java deleted file mode 100644 index 807d48bd..00000000 --- a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberIs.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.helger.css.decl; - -import javax.annotation.Nonnegative; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.NotThreadSafe; - -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.hashcode.HashCodeGenerator; -import com.helger.commons.state.EChange; -import com.helger.commons.string.ToStringGenerator; -import com.helger.css.CSSSourceLocation; -import com.helger.css.ECSSVersion; -import com.helger.css.ICSSSourceLocationAware; -import com.helger.css.ICSSVersionAware; -import com.helger.css.ICSSWriterSettings; - -/** - * Represents complex CSS selector as used for the ":is()" CSS pseudo - * class function. - * - * @author Mike Wiedenauer - * @author Philip Helger - * @since 7.0.3 - */ -@NotThreadSafe -public class CSSSelectorMemberIs implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware -{ - private final ICommonsList m_aNestedSelectors; - private CSSSourceLocation m_aSourceLocation; - - public CSSSelectorMemberIs (@Nonnull final CSSSelector aNestedSelector) - { - ValueEnforcer.notNull (aNestedSelector, "NestedSelector"); - m_aNestedSelectors = new CommonsArrayList <> (aNestedSelector); - } - - public CSSSelectorMemberIs (@Nonnull final CSSSelector... aNestedSelectors) - { - ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); - m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); - } - - public CSSSelectorMemberIs (@Nonnull final Iterable aNestedSelectors) - { - ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); - m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); - } - - public boolean hasSelectors () - { - return m_aNestedSelectors.isNotEmpty (); - } - - @Nonnegative - public int getSelectorCount () - { - return m_aNestedSelectors.size (); - } - - @Nonnull - public CSSSelectorMemberIs addSelector (@Nonnull final ICSSSelectorMember aSingleSelectorMember) - { - ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); - - return addSelector (new CSSSelector ().addMember (aSingleSelectorMember)); - } - - @Nonnull - public CSSSelectorMemberIs addSelector (@Nonnull final CSSSelector aSelector) - { - ValueEnforcer.notNull (aSelector, "Selector"); - - m_aNestedSelectors.add (aSelector); - return this; - } - - @Nonnull - public CSSSelectorMemberIs addSelector (@Nonnegative final int nIndex, @Nonnull final ICSSSelectorMember aSingleSelectorMember) - { - ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); - - return addSelector (nIndex, new CSSSelector ().addMember (aSingleSelectorMember)); - } - - @Nonnull - public CSSSelectorMemberIs 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 - @Nonempty - public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) - { - aSettings.checkVersionRequirements (this); - - final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); - final StringBuilder aSB = new StringBuilder (":is("); - 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 (); - } - - @Nonnull - public ECSSVersion getMinimumCSSVersion () - { - return ECSSVersion.CSS30; - } - - @Nullable - public final CSSSourceLocation getSourceLocation () - { - return m_aSourceLocation; - } - - 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 CSSSelectorMemberIs rhs = (CSSSelectorMemberIs) o; - return m_aNestedSelectors.equals (rhs.m_aNestedSelectors); - } - - @Override - public int hashCode () - { - return new HashCodeGenerator (this).append (m_aNestedSelectors).getHashCode (); - } - - @Override - public String toString () - { - return new ToStringGenerator (null).append ("nestedSelectors", m_aNestedSelectors) - .appendIfNotNull ("SourceLocation", m_aSourceLocation) - .getToString (); - } -} diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoIs.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoIs.java index e5d9fda5..80caf2c8 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoIs.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoIs.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; @@ -41,19 +46,112 @@ @NotThreadSafe public class CSSSelectorMemberPseudoIs implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware { - private final CSSSelector m_aSelector; + private final ICommonsList m_aNestedSelectors; private CSSSourceLocation m_aSourceLocation; - public CSSSelectorMemberPseudoIs (@Nonnull final CSSSelector aSelector) + public CSSSelectorMemberPseudoIs (@Nonnull final CSSSelector aNestedSelector) { + ValueEnforcer.notNull (aNestedSelector, "NestedSelector"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelector); + } + + public CSSSelectorMemberPseudoIs (@Nonnull final CSSSelector... aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public CSSSelectorMemberPseudoIs (@Nonnull final Iterable aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public boolean hasSelectors () + { + return m_aNestedSelectors.isNotEmpty (); + } + + @Nonnegative + public int getSelectorCount () + { + return m_aNestedSelectors.size (); + } + + @Nonnull + public CSSSelectorMemberPseudoIs addSelector (@Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberPseudoIs addSelector (@Nonnull final CSSSelector aSelector) + { + ValueEnforcer.notNull (aSelector, "Selector"); + + m_aNestedSelectors.add (aSelector); + return this; + } + + @Nonnull + public CSSSelectorMemberPseudoIs addSelector (@Nonnegative final int nIndex, + @Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (nIndex, new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberPseudoIs addSelector (@Nonnegative final int nIndex, @Nonnull final CSSSelector aSelector) + { + ValueEnforcer.isGE0 (nIndex, "Index"); ValueEnforcer.notNull (aSelector, "Selector"); - m_aSelector = aSelector; + + 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 final CSSSelector getSelector () + public EChange removeAllSelectors () { - return m_aSelector; + 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 +160,20 @@ public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonn { aSettings.checkVersionRequirements (this); + aSettings.checkVersionRequirements (this); + + final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); final StringBuilder aSB = new StringBuilder (":is("); - aSB.append (m_aSelector.getAsCSSString (aSettings, 0)); + + 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 +202,19 @@ public boolean equals (final Object o) if (o == null || !getClass ().equals (o.getClass ())) return false; final CSSSelectorMemberPseudoIs rhs = (CSSSelectorMemberPseudoIs) o; - return m_aSelector.equals (rhs.m_aSelector); + return m_aNestedSelectors.equals (rhs.m_aNestedSelectors); } @Override public int hashCode () { - return new HashCodeGenerator (this).append (m_aSelector).getHashCode (); + return new HashCodeGenerator (this).append (m_aNestedSelectors).getHashCode (); } @Override public String toString () { - return new ToStringGenerator (null).append ("Selector", m_aSelector) + return new ToStringGenerator (null).append ("NestedSelectors", m_aNestedSelectors) .appendIfNotNull ("SourceLocation", m_aSourceLocation) .getToString (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoWhere.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoWhere.java index 2954b233..654fdfc9 100644 --- a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoWhere.java +++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberPseudoWhere.java @@ -23,7 +23,11 @@ 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.hashcode.HashCodeGenerator; +import com.helger.commons.state.EChange; import com.helger.commons.string.ToStringGenerator; import com.helger.css.CSSSourceLocation; import com.helger.css.ECSSVersion; @@ -41,19 +45,112 @@ @NotThreadSafe public class CSSSelectorMemberPseudoWhere implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware { - private final CSSSelector m_aSelector; + private final ICommonsList m_aNestedSelectors; private CSSSourceLocation m_aSourceLocation; - public CSSSelectorMemberPseudoWhere (@Nonnull final CSSSelector aSelector) + public CSSSelectorMemberPseudoWhere (@Nonnull final CSSSelector aNestedSelector) { + ValueEnforcer.notNull (aNestedSelector, "NestedSelector"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelector); + } + + public CSSSelectorMemberPseudoWhere (@Nonnull final CSSSelector... aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public CSSSelectorMemberPseudoWhere (@Nonnull final Iterable aNestedSelectors) + { + ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); + m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); + } + + public boolean hasSelectors () + { + return m_aNestedSelectors.isNotEmpty (); + } + + @Nonnegative + public int getSelectorCount () + { + return m_aNestedSelectors.size (); + } + + @Nonnull + public CSSSelectorMemberPseudoWhere addSelector (@Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberPseudoWhere addSelector (@Nonnull final CSSSelector aSelector) + { + ValueEnforcer.notNull (aSelector, "Selector"); + + m_aNestedSelectors.add (aSelector); + return this; + } + + @Nonnull + public CSSSelectorMemberPseudoWhere addSelector (@Nonnegative final int nIndex, + @Nonnull final ICSSSelectorMember aSingleSelectorMember) + { + ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); + + return addSelector (nIndex, new CSSSelector ().addMember (aSingleSelectorMember)); + } + + @Nonnull + public CSSSelectorMemberPseudoWhere addSelector (@Nonnegative final int nIndex, @Nonnull final CSSSelector aSelector) + { + ValueEnforcer.isGE0 (nIndex, "Index"); ValueEnforcer.notNull (aSelector, "Selector"); - m_aSelector = aSelector; + + 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 final CSSSelector getSelector () + public EChange removeAllSelectors () { - return m_aSelector; + 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 +159,20 @@ public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonn { aSettings.checkVersionRequirements (this); + aSettings.checkVersionRequirements (this); + + final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); final StringBuilder aSB = new StringBuilder (":where("); - aSB.append (m_aSelector.getAsCSSString (aSettings, 0)); + + 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 +201,19 @@ public boolean equals (final Object o) if (o == null || !getClass ().equals (o.getClass ())) return false; final CSSSelectorMemberPseudoWhere rhs = (CSSSelectorMemberPseudoWhere) o; - return m_aSelector.equals (rhs.m_aSelector); + return m_aNestedSelectors.equals (rhs.m_aNestedSelectors); } @Override public int hashCode () { - return new HashCodeGenerator (this).append (m_aSelector).getHashCode (); + return new HashCodeGenerator (this).append (m_aNestedSelectors).getHashCode (); } @Override public String toString () { - return new ToStringGenerator (null).append ("Selector", m_aSelector) + return new ToStringGenerator (null).append ("NestedSelectors", m_aNestedSelectors) .appendIfNotNull ("SourceLocation", m_aSourceLocation) .getToString (); } diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberWhere.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberWhere.java deleted file mode 100644 index e58e86a0..00000000 --- a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorMemberWhere.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.helger.css.decl; - -import javax.annotation.Nonnegative; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.NotThreadSafe; - -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.hashcode.HashCodeGenerator; -import com.helger.commons.state.EChange; -import com.helger.commons.string.ToStringGenerator; -import com.helger.css.CSSSourceLocation; -import com.helger.css.ECSSVersion; -import com.helger.css.ICSSSourceLocationAware; -import com.helger.css.ICSSVersionAware; -import com.helger.css.ICSSWriterSettings; - -/** - * Represents complex CSS selector as used for the ":where()" CSS pseudo - * class function. - * - * @author Mike Wiedenauer - * @author Philip Helger - * @since 7.0.3 - */ -@NotThreadSafe -public class CSSSelectorMemberWhere implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware -{ - private final ICommonsList m_aNestedSelectors; - private CSSSourceLocation m_aSourceLocation; - - public CSSSelectorMemberWhere (@Nonnull final CSSSelector aNestedSelector) - { - ValueEnforcer.notNull (aNestedSelector, "NestedSelector"); - m_aNestedSelectors = new CommonsArrayList <> (aNestedSelector); - } - - public CSSSelectorMemberWhere (@Nonnull final CSSSelector... aNestedSelectors) - { - ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); - m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); - } - - public CSSSelectorMemberWhere (@Nonnull final Iterable aNestedSelectors) - { - ValueEnforcer.notNull (aNestedSelectors, "NestedSelectors"); - m_aNestedSelectors = new CommonsArrayList <> (aNestedSelectors); - } - - public boolean hasSelectors () - { - return m_aNestedSelectors.isNotEmpty (); - } - - @Nonnegative - public int getSelectorCount () - { - return m_aNestedSelectors.size (); - } - - @Nonnull - public CSSSelectorMemberWhere addSelector (@Nonnull final ICSSSelectorMember aSingleSelectorMember) - { - ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); - - return addSelector (new CSSSelector ().addMember (aSingleSelectorMember)); - } - - @Nonnull - public CSSSelectorMemberWhere addSelector (@Nonnull final CSSSelector aSelector) - { - ValueEnforcer.notNull (aSelector, "Selector"); - - m_aNestedSelectors.add (aSelector); - return this; - } - - @Nonnull - public CSSSelectorMemberWhere addSelector (@Nonnegative final int nIndex, @Nonnull final ICSSSelectorMember aSingleSelectorMember) - { - ValueEnforcer.notNull (aSingleSelectorMember, "SingleSelectorMember"); - - return addSelector (nIndex, new CSSSelector ().addMember (aSingleSelectorMember)); - } - - @Nonnull - public CSSSelectorMemberWhere 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 - @Nonempty - public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) - { - aSettings.checkVersionRequirements (this); - - final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); - final StringBuilder aSB = new StringBuilder (":where("); - 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 (); - } - - @Nonnull - public ECSSVersion getMinimumCSSVersion () - { - return ECSSVersion.CSS30; - } - - @Nullable - public final CSSSourceLocation getSourceLocation () - { - return m_aSourceLocation; - } - - 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 CSSSelectorMemberWhere rhs = (CSSSelectorMemberWhere) o; - return m_aNestedSelectors.equals (rhs.m_aNestedSelectors); - } - - @Override - public int hashCode () - { - return new HashCodeGenerator (this).append (m_aNestedSelectors).getHashCode (); - } - - @Override - public String toString () - { - return new ToStringGenerator (null).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 d822575e..9f153900 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 @@ -252,40 +252,6 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) return ret; } - if (ECSSNodeType.WHERE.isNode (aNode, m_eVersion)) - { - // Note: no children don't make sense but are syntactically allowed! - final ICommonsList aNestedSelectors = new CommonsArrayList <> (); - for (int i = 0; i < nChildCount; ++i) - { - final CSSNode aChildNode = aNode.jjtGetChild (i); - final CSSSelector aSelector = _createSelector (aChildNode); - aNestedSelectors.add (aSelector); - } - - final CSSSelectorMemberWhere ret = new CSSSelectorMemberWhere (aNestedSelectors); - if (m_bUseSourceLocation) - ret.setSourceLocation (aNode.getSourceLocation ()); - return ret; - } - - if (ECSSNodeType.IS.isNode (aNode, m_eVersion)) - { - // Note: no children don't make sense but are syntactically allowed! - final ICommonsList aNestedSelectors = new CommonsArrayList <> (); - for (int i = 0; i < nChildCount; ++i) - { - final CSSNode aChildNode = aNode.jjtGetChild (i); - final CSSSelector aSelector = _createSelector (aChildNode); - aNestedSelectors.add (aSelector); - } - - final CSSSelectorMemberIs ret = new CSSSelectorMemberIs (aNestedSelectors); - if (m_bUseSourceLocation) - ret.setSourceLocation (aNode.getSourceLocation ()); - return ret; - } - if (ECSSNodeType.PSEUDO.isNode (aNode, m_eVersion)) { if (nChildCount == 0) @@ -386,11 +352,15 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) if (ECSSNodeType.PSEUDO_WHERE.isNode (aChildNode, m_eVersion)) { - final CSSSelector aSelector = new CSSSelector (); final int nChildChildCount = aChildNode.jjtGetNumChildren (); + final ICommonsList aNestedSelectors = new CommonsArrayList <> (); for (int j = 0; j < nChildChildCount; ++j) - aSelector.addMember (_createSelectorMember (aChildNode.jjtGetChild (j))); - final CSSSelectorMemberPseudoWhere ret = new CSSSelectorMemberPseudoWhere (aSelector); + { + final CSSSelector aSelector = _createSelector (aChildNode.jjtGetChild (j)); + aNestedSelectors.add (aSelector); + } + + final CSSSelectorMemberPseudoWhere ret = new CSSSelectorMemberPseudoWhere (aNestedSelectors); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); return ret; @@ -398,11 +368,15 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) if (ECSSNodeType.PSEUDO_IS.isNode (aChildNode, m_eVersion)) { - final CSSSelector aSelector = new CSSSelector (); final int nChildChildCount = aChildNode.jjtGetNumChildren (); + final ICommonsList aNestedSelectors = new CommonsArrayList <> (); for (int j = 0; j < nChildChildCount; ++j) - aSelector.addMember (_createSelectorMember (aChildNode.jjtGetChild (j))); - final CSSSelectorMemberPseudoIs ret = new CSSSelectorMemberPseudoIs (aSelector); + { + final CSSSelector aSelector = _createSelector (aChildNode.jjtGetChild (j)); + aNestedSelectors.add (aSelector); + } + + final CSSSelectorMemberPseudoIs ret = new CSSSelectorMemberPseudoIs (aNestedSelectors); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); return ret; From dbd33ae09d2c4fb8527ef1f4943a4d7783787a71 Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Tue, 25 Feb 2025 17:47:03 +0100 Subject: [PATCH 08/15] set version to "7.0.5-SNAPSHOT" --- ph-css/pom.xml | 2 +- ph-csscompress-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index 585941df..956689e2 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-1 + 7.0.5-SNAPSHOT ph-css bundle diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index 67230802..9dbc6e3d 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-1 + 7.0.5-SNAPSHOT unblu.patched.com.helger.maven ph-csscompress-maven-plugin diff --git a/pom.xml b/pom.xml index 8324af9b..98d7d058 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-1 + 7.0.5-SNAPSHOT pom ph-css-parent-pom Base POM to build the ph-css projects From 504f0bfbc0b1e05a9515672201e0a71ca692e772 Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Fri, 28 Feb 2025 09:16:38 +0100 Subject: [PATCH 09/15] implement proper list handling/parsing --- .../css/decl/CSSSelectorMemberPseudoHas.java | 31 +++-------- .../css/handler/CSSNodeToDomainObject.java | 53 ++++++++----------- .../com/helger/css/handler/ECSSNodeType.java | 1 + ph-css/src/main/jjtree/ParserCSS30.jjt | 36 ++++++++----- 4 files changed, 53 insertions(+), 68 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 79a3d6b5..a76ac902 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 @@ -26,7 +26,6 @@ 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; @@ -46,40 +45,27 @@ @NotThreadSafe public class CSSSelectorMemberPseudoHas implements ICSSSelectorMember, ICSSVersionAware, ICSSSourceLocationAware { - private final ECSSSelectorCombinator m_eCombinator; private final ICommonsList m_aNestedSelectors; private CSSSourceLocation m_aSourceLocation; - public CSSSelectorMemberPseudoHas (@Nullable final ECSSSelectorCombinator eCombinator, - @Nonnull final CSSSelector aNestedSelector) + public CSSSelectorMemberPseudoHas (@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) + public CSSSelectorMemberPseudoHas (@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) + public CSSSelectorMemberPseudoHas (@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 (); @@ -177,10 +163,6 @@ public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonn final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); final StringBuilder aSB = new StringBuilder (":has("); - - if (m_eCombinator != null) - aSB.append (m_eCombinator.getAsCSSString (aSettings)); - boolean bFirst = true; for (final CSSSelector aNestedSelector : m_aNestedSelectors) { @@ -218,20 +200,19 @@ public boolean equals (final Object o) if (o == null || !getClass ().equals (o.getClass ())) return false; final CSSSelectorMemberPseudoHas rhs = (CSSSelectorMemberPseudoHas) o; - return EqualsHelper.equals (m_eCombinator, rhs.m_eCombinator) && m_aNestedSelectors.equals (rhs.m_aNestedSelectors); + return m_aNestedSelectors.equals (rhs.m_aNestedSelectors); } @Override public int hashCode () { - return new HashCodeGenerator (this).append (m_eCombinator).append (m_aNestedSelectors).getHashCode (); + return new HashCodeGenerator (this).append (m_aNestedSelectors).getHashCode (); } @Override public String toString () { - return new ToStringGenerator (null).append ("Combinator", m_eCombinator) - .append ("NestedSelectors", m_aNestedSelectors) + return new ToStringGenerator (null).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 9f153900..8ee9f96f 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 @@ -321,30 +321,12 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) if (ECSSNodeType.PSEUDO_HAS.isNode (aChildNode, m_eVersion)) { - ECSSSelectorCombinator eSelectorCombinator = null; - final int nChildChildCount = aChildNode.jjtGetNumChildren (); - 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); - } + for (int j = 0; j < nChildChildCount; ++j) + aNestedSelectors.add (_createRelativeSelector(aChildNode.jjtGetChild (j))); - final CSSSelectorMemberPseudoHas ret = new CSSSelectorMemberPseudoHas (eSelectorCombinator, aNestedSelectors); + final CSSSelectorMemberPseudoHas ret = new CSSSelectorMemberPseudoHas (aNestedSelectors); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); return ret; @@ -355,11 +337,7 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) final int nChildChildCount = aChildNode.jjtGetNumChildren (); final ICommonsList aNestedSelectors = new CommonsArrayList <> (); for (int j = 0; j < nChildChildCount; ++j) - { - final CSSSelector aSelector = _createSelector (aChildNode.jjtGetChild (j)); - aNestedSelectors.add (aSelector); - } - + aNestedSelectors.add (_createSelector (aChildNode.jjtGetChild (j))); final CSSSelectorMemberPseudoWhere ret = new CSSSelectorMemberPseudoWhere (aNestedSelectors); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); @@ -371,11 +349,7 @@ private ICSSSelectorMember _createSelectorMember (final CSSNode aNode) final int nChildChildCount = aChildNode.jjtGetNumChildren (); final ICommonsList aNestedSelectors = new CommonsArrayList <> (); for (int j = 0; j < nChildChildCount; ++j) - { - final CSSSelector aSelector = _createSelector (aChildNode.jjtGetChild (j)); - aNestedSelectors.add (aSelector); - } - + aNestedSelectors.add (_createSelector (aChildNode.jjtGetChild (j))); final CSSSelectorMemberPseudoIs ret = new CSSSelectorMemberPseudoIs (aNestedSelectors); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); @@ -423,6 +397,23 @@ private CSSSelector _createSelector (@Nonnull final CSSNode aNode) return ret; } + @Nonnull + private CSSSelector _createRelativeSelector (@Nonnull final CSSNode aNode) + { + _expectNodeType (aNode, ECSSNodeType.RELATIVESELECTOR); + + final CSSSelector ret = new CSSSelector (); + if (m_bUseSourceLocation) + ret.setSourceLocation (aNode.getSourceLocation ()); + for (final CSSNode aChildNode : aNode) + { + final ICSSSelectorMember aMember = _createSelectorMember (aChildNode); + if (aMember != null) + ret.addMember (aMember); + } + return ret; + } + @Nonnull private CSSExpressionMemberMathProduct _createExpressionCalcProduct (@Nonnull final CSSNode aNode) { 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 0619171b..66e60b71 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 @@ -49,6 +49,7 @@ public enum ECSSNodeType FONTFACERULE (ParserCSS30TreeConstants.JJTFONTFACERULE), // top level -- style rule SELECTOR (ParserCSS30TreeConstants.JJTSELECTOR), + RELATIVESELECTOR (ParserCSS30TreeConstants.JJTRELATIVESELECTOR), STYLEDECLARATIONLIST (ParserCSS30TreeConstants.JJTSTYLEDECLARATIONLIST), STYLEDECLARATION (ParserCSS30TreeConstants.JJTSTYLEDECLARATION), // style rule -- selector diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index a3f7852c..41246a1c 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -1060,23 +1060,25 @@ void pseudoSlotted () #slotted : {} )* } -void relativeSelector() #void : {} +void relativeSelector() : {} { ( selectorCombinator() ( )* )? selector () - ( )* - ( - ( )* - selector() - ( )* - )* } void relativeSelectorList() #void : {} { - ( relativeSelector() )* + ( )* + ( relativeSelector() + ( )* + ( + ( )* + relativeSelector () + ( )* + )* + ) } void pseudoHas() #has : {} @@ -1085,16 +1087,26 @@ void pseudoHas() #has : {} relativeSelectorList() } -void pseudoIs() #is : {} +void simpleSelectorList() #void : {} { ( )* - relativeSelectorList() + ( selector () + ( )* + ( + ( )* + selector () + ( )* + )* + ) +} +void pseudoIs() #is : {} +{ + simpleSelectorList () } void pseudoWhere() #where : {} { - ( )* - relativeSelectorList() + simpleSelectorList () } void pseudoClassSelector() : {} From e2a47d0ab0c2b60351478516e53e7c623ade408c Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Tue, 4 Mar 2025 09:45:48 +0100 Subject: [PATCH 10/15] small changes to the selector list handling --- ph-css/src/main/jjtree/ParserCSS30.jjt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index 41246a1c..1c306c67 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -1083,8 +1083,7 @@ void relativeSelectorList() #void : {} void pseudoHas() #has : {} { - ( )* - relativeSelectorList() + ( relativeSelectorList() )* } void simpleSelectorList() #void : {} @@ -1099,14 +1098,15 @@ void simpleSelectorList() #void : {} )* ) } + void pseudoIs() #is : {} { - simpleSelectorList () + ( simpleSelectorList () )* } void pseudoWhere() #where : {} { - simpleSelectorList () + ( simpleSelectorList () )* } void pseudoClassSelector() : {} From ef533d7c2377a9bdf42e27fa186a6ed7a7259195 Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Fri, 14 Jun 2024 16:23:52 +0200 Subject: [PATCH 11/15] [release] set version to "7.0.5-unblu-2" --- ph-css/pom.xml | 2 +- ph-csscompress-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index 956689e2..b3d02c50 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-2 ph-css bundle diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index 9dbc6e3d..39b49f20 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-2 unblu.patched.com.helger.maven ph-csscompress-maven-plugin diff --git a/pom.xml b/pom.xml index 98d7d058..aa8411db 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-2 pom ph-css-parent-pom Base POM to build the ph-css projects From 6f0d90e8dac1cc24499bd146d54473abc00e7740 Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Tue, 4 Mar 2025 10:07:58 +0100 Subject: [PATCH 12/15] Set version to "7.0.5-SNAPSHOT" --- ph-css/pom.xml | 2 +- ph-csscompress-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index b3d02c50..956689e2 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-2 + 7.0.5-SNAPSHOT ph-css bundle diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index 39b49f20..9dbc6e3d 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-2 + 7.0.5-SNAPSHOT unblu.patched.com.helger.maven ph-csscompress-maven-plugin diff --git a/pom.xml b/pom.xml index aa8411db..98d7d058 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-2 + 7.0.5-SNAPSHOT pom ph-css-parent-pom Base POM to build the ph-css projects From e20c99eb60be0d070555f232910dce322b6133a8 Mon Sep 17 00:00:00 2001 From: Mike Wiedenbauer Date: Fri, 27 Jun 2025 14:35:42 +0200 Subject: [PATCH 13/15] add support for @layer rule --- .../com/helger/css/decl/CSSLayerRule.java | 156 +++++++++++++++++ .../com/helger/css/decl/visit/CSSVisitor.java | 35 +++- .../css/decl/visit/CSSVisitorForUrl.java | 10 ++ .../css/decl/visit/DefaultCSSVisitor.java | 9 + .../helger/css/decl/visit/ICSSVisitor.java | 17 ++ .../css/handler/CSSNodeToDomainObject.java | 164 +++++++++++++----- .../com/helger/css/handler/ECSSNodeType.java | 5 + ph-css/src/main/jjtree/ParserCSS30.jjt | 66 +++++++ 8 files changed, 417 insertions(+), 45 deletions(-) create mode 100644 ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java 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 new file mode 100644 index 00000000..fd38a51d --- /dev/null +++ b/ph-css/src/main/java/com/helger/css/decl/CSSLayerRule.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014-2025 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 javax.annotation.Nonnegative; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.NotThreadSafe; + +import com.helger.commons.ValueEnforcer; +import com.helger.commons.collection.impl.CommonsArrayList; +import com.helger.commons.collection.impl.ICommonsList; +import com.helger.commons.hashcode.HashCodeGenerator; +import com.helger.commons.string.StringHelper; +import com.helger.commons.string.ToStringGenerator; +import com.helger.css.CSSSourceLocation; +import com.helger.css.ECSSVersion; +import com.helger.css.ICSSSourceLocationAware; +import com.helger.css.ICSSVersionAware; +import com.helger.css.ICSSWriterSettings; + +@NotThreadSafe +public class CSSLayerRule extends AbstractHasTopLevelRules implements ICSSTopLevelRule, ICSSVersionAware, ICSSSourceLocationAware +{ + private final ICommonsList m_aSelectors; + private CSSSourceLocation m_aSourceLocation; + + public CSSLayerRule (@Nullable final String sLayerSelector) + { + m_aSelectors = StringHelper.hasText (sLayerSelector) ? new CommonsArrayList <> (sLayerSelector) : new CommonsArrayList <> (); + } + + public CSSLayerRule (@Nonnull final Iterable aSelectors) + { + ValueEnforcer.notNullNoNullValue (aSelectors, "Selectors"); + m_aSelectors = new CommonsArrayList <> (aSelectors); + } + + @Nonnull + public ICommonsList getAllSelectors () + { + return m_aSelectors.getClone (); + } + + @Nonnull + public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) + { + aSettings.checkVersionRequirements (this); + + final boolean bOptimizedOutput = aSettings.isOptimizedOutput (); + + final StringBuilder aSB = new StringBuilder ("@layer "); + boolean bFirst = true; + if (m_aSelectors.isNotEmpty ()) + { + for (final String sSelector : m_aSelectors) + { + if (bFirst) + bFirst = false; + else + aSB.append (bOptimizedOutput ? "," : ", "); + aSB.append (sSelector); + } + } + + final int nRuleCount = m_aRules.size (); + if (nRuleCount == 0) + { + aSB.append(";"); + } + else + { + // At least one rule present + aSB.append (bOptimizedOutput ? "{" : " {" + aSettings.getNewLineString ()); + bFirst = true; + for (final ICSSTopLevelRule aRule : m_aRules) + { + final String sRuleCSS = aRule.getAsCSSString (aSettings, nIndentLevel + 1); + if (StringHelper.hasText (sRuleCSS)) + { + if (bFirst) + bFirst = false; + else + if (!bOptimizedOutput) + aSB.append (aSettings.getNewLineString ()); + + if (!bOptimizedOutput) + aSB.append (aSettings.getIndent (nIndentLevel + 1)); + aSB.append (sRuleCSS); + } + } + if (!bOptimizedOutput) + aSB.append (aSettings.getIndent (nIndentLevel)); + aSB.append ('}'); + } + + if (!bOptimizedOutput) + aSB.append (aSettings.getNewLineString ()); + + return aSB.toString (); + } + + @Nonnull + public ECSSVersion getMinimumCSSVersion () + { + return ECSSVersion.CSS30; + } + + @Nullable + public final CSSSourceLocation getSourceLocation () + { + return m_aSourceLocation; + } + + 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; + return true; + } + + @Override + public int hashCode () + { + return new HashCodeGenerator (this).getHashCode (); + } + + @Override + public String toString () + { + return new ToStringGenerator (this).appendIfNotNull ("SourceLocation", m_aSourceLocation) + .getToString (); + } +} 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 52e64dd7..4caf782c 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 @@ -26,6 +26,7 @@ import com.helger.css.decl.CSSImportRule; import com.helger.css.decl.CSSKeyframesBlock; 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.CSSPageMarginBlock; @@ -287,6 +288,29 @@ public static void visitSupportsRule (@Nonnull final CSSSupportsRule aSupportsRu } } + /** + * Visit all elements of a single layer rule. + * + * @param aLayerRule + * The layer rule to visit. May not be null. + * @param aVisitor + * The visitor to use. May not be null. + */ + public static void visitLayerRule (@Nonnull final CSSLayerRule aLayerRule, @Nonnull final ICSSVisitor aVisitor) + { + aVisitor.onBeginLayerRule (aLayerRule); + try + { + // for all nested rules + for (final ICSSTopLevelRule aRule : aLayerRule.getAllRules ()) + visitTopLevelRule (aRule, aVisitor); + } + finally + { + aVisitor.onEndLayerRule (aLayerRule); + } + } + /** * Visit all elements of a single unknown @ rule. * @@ -346,12 +370,17 @@ public static void visitTopLevelRule (@Nonnull final ICSSTopLevelRule aTopLevelR visitSupportsRule ((CSSSupportsRule) aTopLevelRule, aVisitor); } else - if (aTopLevelRule instanceof CSSUnknownRule) + if (aTopLevelRule instanceof CSSLayerRule) { - visitUnknownRule ((CSSUnknownRule) aTopLevelRule, aVisitor); + visitLayerRule ((CSSLayerRule) aTopLevelRule, aVisitor); } else - throw new IllegalStateException ("Top level rule " + aTopLevelRule + " is unsupported!"); + if (aTopLevelRule instanceof CSSUnknownRule) + { + visitUnknownRule ((CSSUnknownRule) aTopLevelRule, aVisitor); + } + else + throw new IllegalStateException ("Top level rule " + aTopLevelRule + " is unsupported!"); } /** 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 95e2ba98..12aff4d1 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 @@ -235,6 +235,16 @@ public void onEndSupportsRule (@Nonnull final CSSSupportsRule aSupportsRule) m_aTopLevelRule.pop (); } + public void onBeginLayerRule (@Nonnull final CSSLayerRule aLayerRule) + { + m_aTopLevelRule.push(aLayerRule); + } + + public void onEndLayerRule (@Nonnull final CSSLayerRule aLayerRule) + { + m_aTopLevelRule.pop(); + } + 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 4c8c74af..f43cb494 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 @@ -25,6 +25,7 @@ import com.helger.css.decl.CSSImportRule; import com.helger.css.decl.CSSKeyframesBlock; 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.CSSPageMarginBlock; @@ -139,6 +140,14 @@ public void onBeginSupportsRule (@Nonnull final CSSSupportsRule aSupportsRule) public void onEndSupportsRule (@Nonnull final CSSSupportsRule aSupportsRule) {} + @OverrideOnDemand + public void onBeginLayerRule (@Nonnull final CSSLayerRule aLayerRule) + {} + + @OverrideOnDemand + public void onEndLayerRule (@Nonnull final CSSLayerRule aLayerRule) + {} + @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 62581a19..b339a9e4 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 @@ -23,6 +23,7 @@ import com.helger.css.decl.CSSImportRule; import com.helger.css.decl.CSSKeyframesBlock; 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.CSSPageMarginBlock; @@ -245,6 +246,22 @@ public interface ICSSVisitor */ void onEndSupportsRule (@Nonnull CSSSupportsRule aSupportsRule); + /** + * Called when a layer rule start. + * + * @param aLayerRule + * The layer rule. Never null. + */ + void onBeginLayerRule (@Nonnull CSSLayerRule aLayerRule); + + /** + * Called when a layer rule ends. + * + * @param aLayerRule + * The layer rule. Never null. + */ + void onEndLayerRule (@Nonnull CSSLayerRule aLayerRule); + // unknown rules /** * Called when an unknown rule is encountered. 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 8ee9f96f..4b1d29fd 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 @@ -36,6 +36,7 @@ import com.helger.css.parser.CSSParseHelper; import com.helger.css.reader.errorhandler.ICSSInterpretErrorHandler; +import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** @@ -946,17 +947,20 @@ private CSSMediaRule _createMediaRule (@Nonnull final CSSNode aNode) if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode, m_eVersion)) ret.addRule (_createSupportsRule (aChildNode)); else - if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode, m_eVersion)) - { - // Unknown rule indicates either - // 1. a parsing error - // 2. a non-standard rule - ret.addRule (_createUnknownRule (aChildNode)); - } + if (ECSSNodeType.LAYERRULE.isNode (aChildNode, m_eVersion)) + ret.addRule (_createLayerRule (aChildNode)); else - if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion)) - m_aErrorHandler.onCSSInterpretationError ("Unsupported media-rule child: " + - ECSSNodeType.getNodeName (aChildNode, m_eVersion)); + if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode, m_eVersion)) + { + // Unknown rule indicates either + // 1. a parsing error + // 2. a non-standard rule + ret.addRule (_createUnknownRule (aChildNode)); + } + else + if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion)) + m_aErrorHandler.onCSSInterpretationError ("Unsupported media-rule child: " + + ECSSNodeType.getNodeName (aChildNode, m_eVersion)); } return ret; } @@ -1101,6 +1105,76 @@ private CSSFontFaceRule _createFontFaceRule (@Nonnull final CSSNode aNode) return ret; } + @NonNull + private CSSLayerRule _createLayerRule (@Nonnull final CSSNode aNode) + { + _expectNodeType (aNode, ECSSNodeType.LAYERRULE); + final int nChildCount = aNode.jjtGetNumChildren (); + if (nChildCount < 1 || nChildCount > 2) + _throwUnexpectedChildrenCount (aNode, + "Expected at least 1 child and at last 2 children but got " + nChildCount + "!"); + + final CSSLayerRule ret; + final ICommonsList aLayerSelectors = new CommonsArrayList <> (); + if (ECSSNodeType.LAYERSELECTORLIST.isNode(aNode.jjtGetChild(0), m_eVersion)) + { + for (CSSNode aSelectorChild : aNode.jjtGetChild (0)) + { + _expectNodeType (aSelectorChild, ECSSNodeType.LAYERSELECTOR); + aLayerSelectors.add (aSelectorChild.getText ()); + } + + ret = new CSSLayerRule (aLayerSelectors); + } + else + { + if (ECSSNodeType.LAYERSELECTOR.isNode(aNode.jjtGetChild (0), m_eVersion)) + { + aLayerSelectors.add (aNode.jjtGetChild (0).getText ()); + } + + ret = new CSSLayerRule (aLayerSelectors); + + final CSSNode aBodyNode = aNode.jjtGetChild (nChildCount - 1); + _expectNodeType (aBodyNode, ECSSNodeType.LAYERRULEBLOCK); + + final int nBodyChildren = aBodyNode.jjtGetNumChildren (); + for (int nIndex = 0; nIndex < nBodyChildren; ++nIndex) + { + final CSSNode aBodyChildNode = aBodyNode.jjtGetChild (nIndex); + if (ECSSNodeType.STYLERULE.isNode (aBodyChildNode, m_eVersion)) + { + final CSSStyleRule aStyleRule = _createStyleRule (aBodyChildNode); + if (aStyleRule != null) + ret.addRule (aStyleRule); + } + else + if (ECSSNodeType.LAYERRULE.isNode (aBodyChildNode, m_eVersion)) + ret.addRule (_createLayerRule (aBodyChildNode)); + else + if (ECSSNodeType.MEDIARULE.isNode (aBodyChildNode, m_eVersion)) + ret.addRule (_createMediaRule (aBodyChildNode)); + else + if (ECSSNodeType.SUPPORTSRULE.isNode (aBodyChildNode, m_eVersion)) + ret.addRule (_createSupportsRule (aBodyChildNode)); + else + if (ECSSNodeType.KEYFRAMESRULE.isNode (aBodyChildNode, m_eVersion)) + ret.addRule (_createKeyframesRule (aBodyChildNode)); + else + if (ECSSNodeType.FONTFACERULE.isNode (aBodyChildNode, m_eVersion)) + ret.addRule (_createFontFaceRule (aBodyChildNode)); + else + if (!ECSSNodeType.isErrorNode (aBodyChildNode, m_eVersion)) + m_aErrorHandler.onCSSInterpretationError ("Unsupported layer-rule child: " + + ECSSNodeType.getNodeName (aBodyChildNode, m_eVersion)); + } + } + + if (m_bUseSourceLocation) + ret.setSourceLocation (aNode.getSourceLocation()); + return ret; + } + @Nonnull private CSSKeyframesRule _createKeyframesRule (@Nonnull final CSSNode aNode) { @@ -1332,9 +1406,12 @@ private CSSSupportsRule _createSupportsRule (@Nonnull final CSSNode aNode) if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode, m_eVersion)) ret.addRule (_createSupportsRule (aChildNode)); else - if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion)) - m_aErrorHandler.onCSSInterpretationError ("Unsupported supports-rule child: " + - ECSSNodeType.getNodeName (aChildNode, m_eVersion)); + if (ECSSNodeType.LAYERRULE.isNode (aChildNode, m_eVersion)) + ret.addRule (_createLayerRule (aChildNode)); + else + if (!ECSSNodeType.isErrorNode (aChildNode, m_eVersion)) + m_aErrorHandler.onCSSInterpretationError ("Unsupported supports-rule child: " + + ECSSNodeType.getNodeName (aChildNode, m_eVersion)); } return ret; } @@ -1400,42 +1477,45 @@ private void _recursiveFillCascadingStyleSheetFromNode (@Nonnull final CSSNode a if (ECSSNodeType.FONTFACERULE.isNode (aChildNode, m_eVersion)) ret.addRule (_createFontFaceRule (aChildNode)); else - if (ECSSNodeType.KEYFRAMESRULE.isNode (aChildNode, m_eVersion)) - ret.addRule (_createKeyframesRule (aChildNode)); + if (ECSSNodeType.LAYERRULE.isNode(aChildNode, m_eVersion)) + ret.addRule (_createLayerRule(aChildNode)); else - if (ECSSNodeType.VIEWPORTRULE.isNode (aChildNode, m_eVersion)) - ret.addRule (_createViewportRule (aChildNode)); + if (ECSSNodeType.KEYFRAMESRULE.isNode (aChildNode, m_eVersion)) + ret.addRule (_createKeyframesRule (aChildNode)); else - if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode, m_eVersion)) - ret.addRule (_createSupportsRule (aChildNode)); + if (ECSSNodeType.VIEWPORTRULE.isNode (aChildNode, m_eVersion)) + ret.addRule (_createViewportRule (aChildNode)); else - if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode, m_eVersion)) - { - // Unknown rule indicates either - // 1. a parsing error - // 2. a non-standard rule - ret.addRule (_createUnknownRule (aChildNode)); - } + if (ECSSNodeType.SUPPORTSRULE.isNode (aChildNode, m_eVersion)) + ret.addRule (_createSupportsRule (aChildNode)); else - if (ECSSNodeType.ROOT.isNode (aChildNode, m_eVersion)) + if (ECSSNodeType.UNKNOWNRULE.isNode (aChildNode, m_eVersion)) { - /* - * In case a parsing error occurs (as e.g. - * happening in issue #41) and browser compliant - * mode is enabled, some CSS code is skipped and a - * retry happens. This retry will be a recursive - * stylesheet object that is a child of the - * previous stylesheet but "flattened" for the - * result object. - */ - _recursiveFillCascadingStyleSheetFromNode (aChildNode, ret); + // Unknown rule indicates either + // 1. a parsing error + // 2. a non-standard rule + ret.addRule (_createUnknownRule (aChildNode)); } else - m_aErrorHandler.onCSSInterpretationError ("Unsupported child of " + - ECSSNodeType.getNodeName (aNode, m_eVersion) + - ": " + - ECSSNodeType.getNodeName (aChildNode, - m_eVersion)); + if (ECSSNodeType.ROOT.isNode (aChildNode, m_eVersion)) + { + /* + * In case a parsing error occurs (as e.g. + * happening in issue #41) and browser compliant + * mode is enabled, some CSS code is skipped and a + * retry happens. This retry will be a recursive + * stylesheet object that is a child of the + * previous stylesheet but "flattened" for the + * result object. + */ + _recursiveFillCascadingStyleSheetFromNode (aChildNode, ret); + } + else + m_aErrorHandler.onCSSInterpretationError ("Unsupported child of " + + ECSSNodeType.getNodeName (aNode, m_eVersion) + + ": " + + ECSSNodeType.getNodeName (aChildNode, + m_eVersion)); } } 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 66e60b71..a6d7f4ba 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 @@ -46,6 +46,7 @@ public enum ECSSNodeType IMPORTRULE (ParserCSS30TreeConstants.JJTIMPORTRULE), PAGERULE (ParserCSS30TreeConstants.JJTPAGERULE), MEDIARULE (ParserCSS30TreeConstants.JJTMEDIARULE), + LAYERRULE (ParserCSS30TreeConstants.JJTLAYERRULE), FONTFACERULE (ParserCSS30TreeConstants.JJTFONTFACERULE), // top level -- style rule SELECTOR (ParserCSS30TreeConstants.JJTSELECTOR), @@ -94,6 +95,10 @@ public enum ECSSNodeType MEDIAMODIFIER (ParserCSS30TreeConstants.JJTMEDIAMODIFIER), MEDIAEXPR (ParserCSS30TreeConstants.JJTMEDIAEXPR), MEDIAFEATURE (ParserCSS30TreeConstants.JJTMEDIAFEATURE), + // layer stuff + LAYERSELECTOR (ParserCSS30TreeConstants.JJTLAYERSELECTOR), + LAYERSELECTORLIST (ParserCSS30TreeConstants.JJTLAYERSELECTORLIST), + LAYERRULEBLOCK (ParserCSS30TreeConstants.JJTLAYERRULEBLOCK), // page stuff PSEUDOPAGE (CGlobal.ILLEGAL_UINT), PAGESELECTOR (ParserCSS30TreeConstants.JJTPAGESELECTOR), diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index 05852163..475b3bba 100644 --- a/ph-css/src/main/jjtree/ParserCSS30.jjt +++ b/ph-css/src/main/jjtree/ParserCSS30.jjt @@ -232,6 +232,7 @@ TOKEN : | < RIGHTBOTTOM_SYM: "@right-bottom" > | < FOOTNOTE_SYM: "@footnote" > | < MEDIA_SYM: "@media" > +| < LAYER_SYM: "@layer" > | < FONTFACE_SYM: "@-" "-font-face" | "@font-face" > | < KEYFRAMES_SYM: "@-" "-keyframes" @@ -595,6 +596,7 @@ try{ ( ( styleRule() | mediaRule() | pageRule() + | layerRule() | fontfaceRule() | keyframesRule() | viewportRule() @@ -1261,6 +1263,7 @@ void styleDeclarationOrRule() #void : {} | charsetRule() { errorUnexpectedRule ("@charset", "charset rule in the middle of a rule-set is not allowed!"); } | importRule() { errorUnexpectedRule ("@import", "import rule in the middle of a rule-set is not allowed!"); } | namespaceRule() { errorUnexpectedRule ("@namespace", "namespace rule in the middle of a rule-set is not allowed!"); } + | layerRule() { errorUnexpectedRule ("@layer", "layer rule in the middle of a rule-set is not allowed!"); } ) ( | | )* ) @@ -1391,6 +1394,7 @@ void mediaRuleList() #void : {} | keyframesRule() | viewportRule() | supportsRule() + | layerRule() | unknownRule() | charsetRule() { errorUnexpectedRule ("@charset", "charset rule in the middle of a @media rule is not allowed!"); } | importRule() { errorUnexpectedRule ("@import", "import rule in the middle of a @media rule is not allowed!"); } @@ -1515,6 +1519,67 @@ void pageRule() : {} pageRuleBlock() } +void layerSelector() : {} +{ + { jjtThis.setText (token.image); } +} + +void layerSelectorList() : {} +{ + layerSelector() + ( )* + ( + ( )* + layerSelector() + ( )* + )* +} + +void layerBody() #void : {} +{ + ( ( styleRule() + | layerRule() + | mediaRule() + | supportsRule() + | keyframesRule() + | fontfaceRule() + | charsetRule() { errorUnexpectedRule ("@charset", "charset rule in the middle of a @layer rule is not allowed!"); } + | importRule() { errorUnexpectedRule ("@import", "import rule in the middle of a @layer rule is not allowed!"); } + | namespaceRule() { errorUnexpectedRule ("@namespace", "namespace rule in the middle of a @layer rule is not allowed!"); } + ) + ( )* + )+ +} + +void layerRuleBlock() : {} +{ + +try{ + ( )* + layerBody() + +} catch (/*final*/ ParseException ex) { + if (m_bBrowserCompliantMode) + browserCompliantSkipInRule (ex); + else + errorSkipTo (ex, RBRACE); +} +} + +void layerRule() : {} +{ + + ( )* + ( LOOKAHEAD(10, ( )* | ( )* ) + layerSelectorList() + ( )* + + | ( layerSelector() )? + ( )* + layerRuleBlock() + ) +} + // // Font face rule // @@ -1643,6 +1708,7 @@ void supportsRuleBodyRule() #void : {} | fontfaceRule() | keyframesRule() | supportsRule() + | layerRule() | unknownRule() | charsetRule() { errorUnexpectedRule ("@charset", "charset rule in the middle of a @supports rule is not allowed!"); } | importRule() { errorUnexpectedRule ("@import", "import rule in the middle of a @supports rule is not allowed!"); } From 5685179b643acb63ecf6dd6b70d4e0af06ab8f22 Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Fri, 27 Jun 2025 14:43:43 +0200 Subject: [PATCH 14/15] [release] set version to "7.0.5-unblu-3" --- ph-css/pom.xml | 2 +- ph-csscompress-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index 766b7be6..9cb8c784 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-3 ph-css bundle diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index 30b873a3..f6cac7e0 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-3 unblu.patched.com.helger.maven ph-csscompress-maven-plugin diff --git a/pom.xml b/pom.xml index 29cbdda1..35d911e8 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-SNAPSHOT + 7.0.5-unblu-3 pom ph-css-parent-pom Base POM to build the ph-css projects From ff72a0627778da63be59a8e423aa0c6b4dc72afd Mon Sep 17 00:00:00 2001 From: Jeremie Bresson Date: Fri, 27 Jun 2025 14:52:14 +0200 Subject: [PATCH 15/15] Set version to "7.0.5-SNAPSHOT" --- ph-css/pom.xml | 2 +- ph-csscompress-maven-plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ph-css/pom.xml b/ph-css/pom.xml index 9cb8c784..766b7be6 100644 --- a/ph-css/pom.xml +++ b/ph-css/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-3 + 7.0.5-SNAPSHOT ph-css bundle diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index f6cac7e0..30b873a3 100644 --- a/ph-csscompress-maven-plugin/pom.xml +++ b/ph-csscompress-maven-plugin/pom.xml @@ -22,7 +22,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-3 + 7.0.5-SNAPSHOT unblu.patched.com.helger.maven ph-csscompress-maven-plugin diff --git a/pom.xml b/pom.xml index 35d911e8..29cbdda1 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ unblu.patched.com.helger ph-css-parent-pom - 7.0.5-unblu-3 + 7.0.5-SNAPSHOT pom ph-css-parent-pom Base POM to build the ph-css projects