diff --git a/ph-css/pom.xml b/ph-css/pom.xml index 2d74fc59..766b7be6 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.5-SNAPSHOT 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/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/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/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 9e896dec..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; /** @@ -321,30 +322,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; @@ -352,11 +335,11 @@ 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); + aNestedSelectors.add (_createSelector (aChildNode.jjtGetChild (j))); + final CSSSelectorMemberPseudoWhere ret = new CSSSelectorMemberPseudoWhere (aNestedSelectors); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); return ret; @@ -364,11 +347,11 @@ 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); + aNestedSelectors.add (_createSelector (aChildNode.jjtGetChild (j))); + final CSSSelectorMemberPseudoIs ret = new CSSSelectorMemberPseudoIs (aNestedSelectors); if (m_bUseSourceLocation) ret.setSourceLocation (aNode.getSourceLocation ()); return ret; @@ -415,6 +398,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) { @@ -947,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; } @@ -1102,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) { @@ -1333,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; } @@ -1401,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 0619171b..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,9 +46,11 @@ 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), + RELATIVESELECTOR (ParserCSS30TreeConstants.JJTRELATIVESELECTOR), STYLEDECLARATIONLIST (ParserCSS30TreeConstants.JJTSTYLEDECLARATIONLIST), STYLEDECLARATION (ParserCSS30TreeConstants.JJTSTYLEDECLARATION), // style rule -- selector @@ -93,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/java/com/helger/css/parser/CSSParseHelper.java b/ph-css/src/main/java/com/helger/css/parser/CSSParseHelper.java index adae1f33..01c972cd 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/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt index 4e9153fc..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() @@ -1060,41 +1062,53 @@ void pseudoSlotted () #slotted : {} )* } -void relativeSelector() #void : {} +void relativeSelector() : {} { ( selectorCombinator() ( )* )? selector () - ( )* - ( - ( )* - selector() - ( )* - )* } void relativeSelectorList() #void : {} { - ( relativeSelector() )* + ( )* + ( relativeSelector() + ( )* + ( + ( )* + relativeSelector () + ( )* + )* + ) } void pseudoHas() #has : {} +{ + ( relativeSelectorList() )* +} + +void simpleSelectorList() #void : {} { ( )* - relativeSelectorList() + ( selector () + ( )* + ( + ( )* + selector () + ( )* + )* + ) } void pseudoIs() #is : {} { - ( )* - selector() + ( simpleSelectorList () )* } void pseudoWhere() #where : {} { - ( )* - selector() + ( simpleSelectorList () )* } void pseudoClassSelector() : {} @@ -1249,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!"); } ) ( | | )* ) @@ -1379,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!"); } @@ -1503,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 // @@ -1631,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!"); } 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 0ca0995d..03fdc88a 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 446ca6b3..f45cd33b 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 ")" diff --git a/ph-csscompress-maven-plugin/pom.xml b/ph-csscompress-maven-plugin/pom.xml index 7205876e..30b873a3 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.5-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 c7d9ff33..29cbdda1 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ parent-pom 2.1.4 + unblu.patched.com.helger ph-css-parent-pom 7.0.5-SNAPSHOT pom @@ -76,12 +77,12 @@ - com.helger + unblu.patched.com.helger ph-css ${project.version} - com.helger + unblu.patched.com.helger ph-csscompress-maven-plugin ${project.version}