diff --git a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorAttribute.java b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorAttribute.java
index f82af397..cae959a2 100644
--- a/ph-css/src/main/java/com/helger/css/decl/CSSSelectorAttribute.java
+++ b/ph-css/src/main/java/com/helger/css/decl/CSSSelectorAttribute.java
@@ -35,6 +35,7 @@
* A single CSS selector attribute.
*
* @see ECSSAttributeOperator
+ * @see ECSSAttributeCase
* @author Philip Helger
*/
@NotThreadSafe
@@ -44,6 +45,7 @@ public class CSSSelectorAttribute implements ICSSSelectorMember, ICSSSourceLocat
private final String m_sAttrName;
private final ECSSAttributeOperator m_eOperator;
private final String m_sAttrValue;
+ private final ECSSAttributeCase m_eAttrCase;
private CSSSourceLocation m_aSourceLocation;
private static boolean _isValidNamespacePrefix (@Nullable final String sNamespacePrefix)
@@ -61,12 +63,22 @@ public CSSSelectorAttribute (@Nullable final String sNamespacePrefix, @Nonnull @
m_sAttrName = sAttrName;
m_eOperator = null;
m_sAttrValue = null;
+ m_eAttrCase = null;
}
public CSSSelectorAttribute (@Nullable final String sNamespacePrefix,
@Nonnull @Nonempty final String sAttrName,
@Nonnull final ECSSAttributeOperator eOperator,
@Nonnull final String sAttrValue)
+ {
+ this (sNamespacePrefix, sAttrName, eOperator, sAttrValue, null);
+ }
+
+ public CSSSelectorAttribute (@Nullable final String sNamespacePrefix,
+ @Nonnull @Nonempty final String sAttrName,
+ @Nullable final ECSSAttributeOperator eOperator,
+ @Nullable final String sAttrValue,
+ @Nullable final ECSSAttributeCase eCaseFlag)
{
if (!_isValidNamespacePrefix (sNamespacePrefix))
throw new IllegalArgumentException ("namespacePrefix is illegal!");
@@ -78,6 +90,7 @@ public CSSSelectorAttribute (@Nullable final String sNamespacePrefix,
m_sAttrName = sAttrName;
m_eOperator = eOperator;
m_sAttrValue = sAttrValue;
+ m_eAttrCase = eCaseFlag;
}
@Nullable
@@ -105,6 +118,12 @@ public String getAttrValue ()
return m_sAttrValue;
}
+ @Nullable
+ public ECSSAttributeCase getCaseSensitivityFlag ()
+ {
+ return m_eAttrCase;
+ }
+
@Nonnull
@Nonempty
public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel)
@@ -115,7 +134,11 @@ public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonn
aSB.append (m_sNamespacePrefix);
aSB.append (m_sAttrName);
if (m_eOperator != null)
+ {
aSB.append (m_eOperator.getAsCSSString (aSettings, nIndentLevel)).append (m_sAttrValue);
+ if (m_eAttrCase != null)
+ aSB.append (' ').append (m_eAttrCase.getName ());
+ }
return aSB.append (']').toString ();
}
@@ -141,7 +164,8 @@ public boolean equals (final Object o)
return EqualsHelper.equals (m_sNamespacePrefix, rhs.m_sNamespacePrefix) &&
m_sAttrName.equals (rhs.m_sAttrName) &&
EqualsHelper.equals (m_eOperator, rhs.m_eOperator) &&
- EqualsHelper.equals (m_sAttrValue, rhs.m_sAttrValue);
+ EqualsHelper.equals (m_sAttrValue, rhs.m_sAttrValue) &&
+ EqualsHelper.equals (m_eAttrCase, rhs.m_eAttrCase);
}
@Override
@@ -151,6 +175,7 @@ public int hashCode ()
.append (m_sAttrName)
.append (m_eOperator)
.append (m_sAttrValue)
+ .append (m_eAttrCase)
.getHashCode ();
}
@@ -161,6 +186,7 @@ public String toString ()
.append ("attrName", m_sAttrName)
.appendIfNotNull ("operator", m_eOperator)
.appendIfNotNull ("attrValue", m_sAttrValue)
+ .appendIfNotNull ("caseFlag", m_eAttrCase)
.appendIfNotNull ("SourceLocation", m_aSourceLocation)
.getToString ();
}
diff --git a/ph-css/src/main/java/com/helger/css/decl/ECSSAttributeCase.java b/ph-css/src/main/java/com/helger/css/decl/ECSSAttributeCase.java
new file mode 100644
index 00000000..a9620155
--- /dev/null
+++ b/ph-css/src/main/java/com/helger/css/decl/ECSSAttributeCase.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.helger.annotation.Nonempty;
+import com.helger.annotation.Nonnegative;
+import com.helger.base.lang.EnumHelper;
+import com.helger.base.name.IHasName;
+import com.helger.css.ICSSWriteable;
+import com.helger.css.ICSSWriterSettings;
+
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+
+/**
+ * Enumeration with case sensitivity flags as in
+ * [foo="bar" i] or [foo="bar" s]
+ *
+ * @author Aditi Singh
+ */
+
+public enum ECSSAttributeCase implements ICSSWriteable, IHasName {
+ CASE_SENSITIVE("s"),
+ CASE_INSENSITIVE("i");
+
+ private final String m_sName;
+
+ ECSSAttributeCase(@Nonnull @Nonempty final String sName) {
+ m_sName = sName;
+ }
+
+ @Nonnull
+ @Nonempty
+ public String getName() {
+ return m_sName;
+ }
+
+ @Nonnull
+ @Nonempty
+ public String getAsCSSString(@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) {
+ return m_sName;
+ }
+
+ @Nullable
+ public static ECSSAttributeCase getFromNameOrNull(@Nullable final String sName) {
+ return EnumHelper.getFromNameOrNull(ECSSAttributeCase.class, sName);
+ }
+}
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 f09301fe..3d9a008e 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
@@ -166,13 +166,17 @@ private CSSSelectorAttribute _createSelectorAttribute (@Nonnull final CSSNode aN
}
else
{
- final int nExpectedChildCount = nOperatorIndex + 2;
- if (nChildren != nExpectedChildCount)
+ // With operator and value
+ int nMin = nOperatorIndex + 2;
+ // With operator, value and case sensitivity flag
+ int nMax = nOperatorIndex + 3;
+
+ if (nChildren < nMin || nChildren > nMax)
_throwUnexpectedChildrenCount (aNode,
"Illegal number of children present (" +
nChildren +
- ") - expected " +
- nExpectedChildCount);
+ ") - expected it to be more than " +
+ nMin + " and less than " + nMax);
// With operator...
final CSSNode aOperator = aNode.jjtGetChild (nOperatorIndex);
@@ -182,10 +186,20 @@ private CSSSelectorAttribute _createSelectorAttribute (@Nonnull final CSSNode aN
final CSSNode aAttrValue = aNode.jjtGetChild (nOperatorIndex + 1);
_expectNodeType (aAttrValue, ECSSNodeType.ATTRIBVALUE);
+ ECSSAttributeCase eCaseFlag = null;
+ // Optional case sensitivity flag
+ if (nChildren == nMax)
+ {
+ final CSSNode aFlag = aNode.jjtGetChild (nOperatorIndex + 2);
+ _expectNodeType (aFlag, ECSSNodeType.ATTRIBCASE);
+ eCaseFlag = ECSSAttributeCase.getFromNameOrNull (aFlag.getText ());
+ }
+
ret = new CSSSelectorAttribute (sNamespacePrefix,
sAttrName,
ECSSAttributeOperator.getFromNameOrNull (aOperator.getText ()),
- aAttrValue.getText ());
+ aAttrValue.getText (),
+ eCaseFlag);
}
if (m_bUseSourceLocation)
ret.setSourceLocation (aNode.getSourceLocation ());
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 1a213391..bda7c1a5 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
@@ -68,6 +68,7 @@ public enum ECSSNodeType
ATTRIB (ParserCSS30TreeConstants.JJTATTRIBUTESELECTOR),
ATTRIBOPERATOR (ParserCSS30TreeConstants.JJTATTRIBOPERATOR),
ATTRIBVALUE (ParserCSS30TreeConstants.JJTATTRIBVALUE),
+ ATTRIBCASE (ParserCSS30TreeConstants.JJTATTRIBCASE),
// RELATIVE_SELECTOR (ParserCSS30TreeConstants.JJTRELATIVESELECTOR),
SELECTORCOMBINATOR (ParserCSS30TreeConstants.JJTSELECTORCOMBINATOR),
NTH (ParserCSS30TreeConstants.JJTNTH),
diff --git a/ph-css/src/main/jjtree/ParserCSS30.jjt b/ph-css/src/main/jjtree/ParserCSS30.jjt
index e9608f3c..6b2117fb 100644
--- a/ph-css/src/main/jjtree/ParserCSS30.jjt
+++ b/ph-css/src/main/jjtree/ParserCSS30.jjt
@@ -942,6 +942,21 @@ void attribValue() :
)
}
+void attribCase() : {}
+{
+ ( )*
+ (
+ {
+ final String v = token.image;
+ if ("i".equalsIgnoreCase(v) || "s".equalsIgnoreCase(v))
+ jjtThis.setText(v.toLowerCase(java.util.Locale.ROOT));
+ else
+ throw new ParseException("Invalid case-sensitivity flag '" + v +
+ "'. Only 'i' or 's' are allowed.");
+ }
+ )?
+}
+
void attributeSelector() : {}
{
@@ -957,7 +972,9 @@ void attributeSelector() : {}
( )*
attribValue()
( )*
+ attribCase()
)?
+ ( )*
}
diff --git a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java
index eb65baef..f7baae90 100644
--- a/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java
+++ b/ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java
@@ -74,7 +74,7 @@ public void testRead1 ()
@Test
public void testRead2 ()
{
- CSSStyleRule aSR = _parse ("div, .colored, #my-red, #menu > .active, a[href^=red] { }");
+ CSSStyleRule aSR = _parse ("div, .colored, #my-red, #menu > .active, a[href^=red i] { }");
assertEquals (5, aSR.getSelectorCount ());
assertEquals (1, aSR.getSelectorAtIndex (0).getMemberCount ());
@@ -94,6 +94,7 @@ public void testRead2 ()
assertEquals (2, aSR.getSelectorAtIndex (4).getMemberCount ());
assertTrue (aSR.getSelectorAtIndex (4).getMemberAtIndex (0) instanceof CSSSelectorSimpleMember);
assertTrue (aSR.getSelectorAtIndex (4).getMemberAtIndex (1) instanceof CSSSelectorAttribute);
+ assertEquals (ECSSAttributeCase.CASE_INSENSITIVE, ((CSSSelectorAttribute) aSR.getSelectorAtIndex (4).getMemberAtIndex (1)).getCaseSensitivityFlag ());
// Create the same rule by application
final CSSStyleRule aCreated = new CSSStyleRule ();
@@ -107,7 +108,8 @@ public void testRead2 ()
.addMember (new CSSSelectorAttribute (null,
"href",
ECSSAttributeOperator.BEGINMATCH,
- "red")));
+ "red",
+ ECSSAttributeCase.CASE_INSENSITIVE)));
TestHelper.testDefaultImplementationWithEqualContentObject (aSR, aCreated);
}
}