Skip to content

Commit 234afda

Browse files
authored
Merge pull request #116 from Aditi-1400/case-flag
Add support for case sensitivity in attribute selector
2 parents 39323f2 + c7d3610 commit 234afda

File tree

6 files changed

+130
-8
lines changed

6 files changed

+130
-8
lines changed

ph-css/src/main/java/com/helger/css/decl/CSSSelectorAttribute.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
* A single CSS selector attribute.
3636
*
3737
* @see ECSSAttributeOperator
38+
* @see ECSSAttributeCase
3839
* @author Philip Helger
3940
*/
4041
@NotThreadSafe
@@ -44,6 +45,7 @@ public class CSSSelectorAttribute implements ICSSSelectorMember, ICSSSourceLocat
4445
private final String m_sAttrName;
4546
private final ECSSAttributeOperator m_eOperator;
4647
private final String m_sAttrValue;
48+
private final ECSSAttributeCase m_eAttrCase;
4749
private CSSSourceLocation m_aSourceLocation;
4850

4951
private static boolean _isValidNamespacePrefix (@Nullable final String sNamespacePrefix)
@@ -61,12 +63,22 @@ public CSSSelectorAttribute (@Nullable final String sNamespacePrefix, @Nonnull @
6163
m_sAttrName = sAttrName;
6264
m_eOperator = null;
6365
m_sAttrValue = null;
66+
m_eAttrCase = null;
6467
}
6568

6669
public CSSSelectorAttribute (@Nullable final String sNamespacePrefix,
6770
@Nonnull @Nonempty final String sAttrName,
6871
@Nonnull final ECSSAttributeOperator eOperator,
6972
@Nonnull final String sAttrValue)
73+
{
74+
this (sNamespacePrefix, sAttrName, eOperator, sAttrValue, null);
75+
}
76+
77+
public CSSSelectorAttribute (@Nullable final String sNamespacePrefix,
78+
@Nonnull @Nonempty final String sAttrName,
79+
@Nullable final ECSSAttributeOperator eOperator,
80+
@Nullable final String sAttrValue,
81+
@Nullable final ECSSAttributeCase eCaseFlag)
7082
{
7183
if (!_isValidNamespacePrefix (sNamespacePrefix))
7284
throw new IllegalArgumentException ("namespacePrefix is illegal!");
@@ -78,6 +90,7 @@ public CSSSelectorAttribute (@Nullable final String sNamespacePrefix,
7890
m_sAttrName = sAttrName;
7991
m_eOperator = eOperator;
8092
m_sAttrValue = sAttrValue;
93+
m_eAttrCase = eCaseFlag;
8194
}
8295

8396
@Nullable
@@ -105,6 +118,12 @@ public String getAttrValue ()
105118
return m_sAttrValue;
106119
}
107120

121+
@Nullable
122+
public ECSSAttributeCase getCaseSensitivityFlag ()
123+
{
124+
return m_eAttrCase;
125+
}
126+
108127
@Nonnull
109128
@Nonempty
110129
public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel)
@@ -115,7 +134,11 @@ public String getAsCSSString (@Nonnull final ICSSWriterSettings aSettings, @Nonn
115134
aSB.append (m_sNamespacePrefix);
116135
aSB.append (m_sAttrName);
117136
if (m_eOperator != null)
137+
{
118138
aSB.append (m_eOperator.getAsCSSString (aSettings, nIndentLevel)).append (m_sAttrValue);
139+
if (m_eAttrCase != null)
140+
aSB.append (' ').append (m_eAttrCase.getName ());
141+
}
119142
return aSB.append (']').toString ();
120143
}
121144

@@ -141,7 +164,8 @@ public boolean equals (final Object o)
141164
return EqualsHelper.equals (m_sNamespacePrefix, rhs.m_sNamespacePrefix) &&
142165
m_sAttrName.equals (rhs.m_sAttrName) &&
143166
EqualsHelper.equals (m_eOperator, rhs.m_eOperator) &&
144-
EqualsHelper.equals (m_sAttrValue, rhs.m_sAttrValue);
167+
EqualsHelper.equals (m_sAttrValue, rhs.m_sAttrValue) &&
168+
EqualsHelper.equals (m_eAttrCase, rhs.m_eAttrCase);
145169
}
146170

147171
@Override
@@ -151,6 +175,7 @@ public int hashCode ()
151175
.append (m_sAttrName)
152176
.append (m_eOperator)
153177
.append (m_sAttrValue)
178+
.append (m_eAttrCase)
154179
.getHashCode ();
155180
}
156181

@@ -161,6 +186,7 @@ public String toString ()
161186
.append ("attrName", m_sAttrName)
162187
.appendIfNotNull ("operator", m_eOperator)
163188
.appendIfNotNull ("attrValue", m_sAttrValue)
189+
.appendIfNotNull ("caseFlag", m_eAttrCase)
164190
.appendIfNotNull ("SourceLocation", m_aSourceLocation)
165191
.getToString ();
166192
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (C) 2014-2025 Philip Helger (www.helger.com)
3+
* philip[at]helger[dot]com
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.helger.css.decl;
18+
19+
import com.helger.annotation.Nonempty;
20+
import com.helger.annotation.Nonnegative;
21+
import com.helger.base.lang.EnumHelper;
22+
import com.helger.base.name.IHasName;
23+
import com.helger.css.ICSSWriteable;
24+
import com.helger.css.ICSSWriterSettings;
25+
26+
import jakarta.annotation.Nonnull;
27+
import jakarta.annotation.Nullable;
28+
29+
/**
30+
* Enumeration with case sensitivity flags as in
31+
* <code>[foo="bar" i]</code> or <code>[foo="bar" s]</code>
32+
*
33+
* @author Aditi Singh
34+
*/
35+
36+
public enum ECSSAttributeCase implements ICSSWriteable, IHasName {
37+
CASE_SENSITIVE("s"),
38+
CASE_INSENSITIVE("i");
39+
40+
private final String m_sName;
41+
42+
ECSSAttributeCase(@Nonnull @Nonempty final String sName) {
43+
m_sName = sName;
44+
}
45+
46+
@Nonnull
47+
@Nonempty
48+
public String getName() {
49+
return m_sName;
50+
}
51+
52+
@Nonnull
53+
@Nonempty
54+
public String getAsCSSString(@Nonnull final ICSSWriterSettings aSettings, @Nonnegative final int nIndentLevel) {
55+
return m_sName;
56+
}
57+
58+
@Nullable
59+
public static ECSSAttributeCase getFromNameOrNull(@Nullable final String sName) {
60+
return EnumHelper.getFromNameOrNull(ECSSAttributeCase.class, sName);
61+
}
62+
}

ph-css/src/main/java/com/helger/css/handler/CSSNodeToDomainObject.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,17 @@ private CSSSelectorAttribute _createSelectorAttribute (@Nonnull final CSSNode aN
166166
}
167167
else
168168
{
169-
final int nExpectedChildCount = nOperatorIndex + 2;
170-
if (nChildren != nExpectedChildCount)
169+
// With operator and value
170+
int nMin = nOperatorIndex + 2;
171+
// With operator, value and case sensitivity flag
172+
int nMax = nOperatorIndex + 3;
173+
174+
if (nChildren < nMin || nChildren > nMax)
171175
_throwUnexpectedChildrenCount (aNode,
172176
"Illegal number of children present (" +
173177
nChildren +
174-
") - expected " +
175-
nExpectedChildCount);
178+
") - expected it to be more than " +
179+
nMin + " and less than " + nMax);
176180

177181
// With operator...
178182
final CSSNode aOperator = aNode.jjtGetChild (nOperatorIndex);
@@ -182,10 +186,20 @@ private CSSSelectorAttribute _createSelectorAttribute (@Nonnull final CSSNode aN
182186
final CSSNode aAttrValue = aNode.jjtGetChild (nOperatorIndex + 1);
183187
_expectNodeType (aAttrValue, ECSSNodeType.ATTRIBVALUE);
184188

189+
ECSSAttributeCase eCaseFlag = null;
190+
// Optional case sensitivity flag
191+
if (nChildren == nMax)
192+
{
193+
final CSSNode aFlag = aNode.jjtGetChild (nOperatorIndex + 2);
194+
_expectNodeType (aFlag, ECSSNodeType.ATTRIBCASE);
195+
eCaseFlag = ECSSAttributeCase.getFromNameOrNull (aFlag.getText ());
196+
}
197+
185198
ret = new CSSSelectorAttribute (sNamespacePrefix,
186199
sAttrName,
187200
ECSSAttributeOperator.getFromNameOrNull (aOperator.getText ()),
188-
aAttrValue.getText ());
201+
aAttrValue.getText (),
202+
eCaseFlag);
189203
}
190204
if (m_bUseSourceLocation)
191205
ret.setSourceLocation (aNode.getSourceLocation ());

ph-css/src/main/java/com/helger/css/handler/ECSSNodeType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public enum ECSSNodeType
6868
ATTRIB (ParserCSS30TreeConstants.JJTATTRIBUTESELECTOR),
6969
ATTRIBOPERATOR (ParserCSS30TreeConstants.JJTATTRIBOPERATOR),
7070
ATTRIBVALUE (ParserCSS30TreeConstants.JJTATTRIBVALUE),
71+
ATTRIBCASE (ParserCSS30TreeConstants.JJTATTRIBCASE),
7172
// RELATIVE_SELECTOR (ParserCSS30TreeConstants.JJTRELATIVESELECTOR),
7273
SELECTORCOMBINATOR (ParserCSS30TreeConstants.JJTSELECTORCOMBINATOR),
7374
NTH (ParserCSS30TreeConstants.JJTNTH),

ph-css/src/main/jjtree/ParserCSS30.jjt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,21 @@ void attribValue() :
942942
)
943943
}
944944

945+
void attribCase() : {}
946+
{
947+
( <S> )*
948+
( <IDENT>
949+
{
950+
final String v = token.image;
951+
if ("i".equalsIgnoreCase(v) || "s".equalsIgnoreCase(v))
952+
jjtThis.setText(v.toLowerCase(java.util.Locale.ROOT));
953+
else
954+
throw new ParseException("Invalid case-sensitivity flag '" + v +
955+
"'. Only 'i' or 's' are allowed.");
956+
}
957+
)?
958+
}
959+
945960
void attributeSelector() : {}
946961
{
947962
<LSQUARE>
@@ -957,7 +972,9 @@ void attributeSelector() : {}
957972
( <S> )*
958973
attribValue()
959974
( <S> )*
975+
attribCase()
960976
)?
977+
( <S> )*
961978
<RSQUARE>
962979
}
963980

ph-css/src/test/java/com/helger/css/decl/CSSStyleRuleTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public void testRead1 ()
7474
@Test
7575
public void testRead2 ()
7676
{
77-
CSSStyleRule aSR = _parse ("div, .colored, #my-red, #menu > .active, a[href^=red] { }");
77+
CSSStyleRule aSR = _parse ("div, .colored, #my-red, #menu > .active, a[href^=red i] { }");
7878
assertEquals (5, aSR.getSelectorCount ());
7979

8080
assertEquals (1, aSR.getSelectorAtIndex (0).getMemberCount ());
@@ -94,6 +94,7 @@ public void testRead2 ()
9494
assertEquals (2, aSR.getSelectorAtIndex (4).getMemberCount ());
9595
assertTrue (aSR.getSelectorAtIndex (4).getMemberAtIndex (0) instanceof CSSSelectorSimpleMember);
9696
assertTrue (aSR.getSelectorAtIndex (4).getMemberAtIndex (1) instanceof CSSSelectorAttribute);
97+
assertEquals (ECSSAttributeCase.CASE_INSENSITIVE, ((CSSSelectorAttribute) aSR.getSelectorAtIndex (4).getMemberAtIndex (1)).getCaseSensitivityFlag ());
9798

9899
// Create the same rule by application
99100
final CSSStyleRule aCreated = new CSSStyleRule ();
@@ -107,7 +108,8 @@ public void testRead2 ()
107108
.addMember (new CSSSelectorAttribute (null,
108109
"href",
109110
ECSSAttributeOperator.BEGINMATCH,
110-
"red")));
111+
"red",
112+
ECSSAttributeCase.CASE_INSENSITIVE)));
111113
TestHelper.testDefaultImplementationWithEqualContentObject (aSR, aCreated);
112114
}
113115
}

0 commit comments

Comments
 (0)