com.hfg.css.CSSSelector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.css;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.hfg.html.HTMLTag;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.StringUtil;
import com.hfg.xml.XMLContainer;
//------------------------------------------------------------------------------
/**
CSS selector encapsulation.
See http://www.w3.org/TR/CSS2/selector.html
@author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
public class CSSSelector
{
private String mStringValue;
private String mBaseTag;
private String mId;
private CSSSelector mAncestorSelector;
private CSSSelector mParentSelector;
private CSSSelector mPrecedingSiblingSelector;
private Set mClassNames;
private Set mAttributeCriteria;
private Pattern sAttrPattern = Pattern.compile("(\\w+)(?:([\\~\\|]?\\=)(.+))?");
private static enum AttributeCriteriaFlag { CONTAINS, PREFIX }
//##########################################################################
// CONSTRUCTORS
//##########################################################################
//--------------------------------------------------------------------------
public CSSSelector(String inValue)
{
mStringValue = inValue;
init();
}
//##########################################################################
// PUBLIC METHODS
//##########################################################################
//--------------------------------------------------------------------------
/**
Currently supports simple type selectors, id selectors, class selectors,
attribute selectors, ancestor selectors, parent selectors, and adjacent sibling selectors.
@param inHTMLTag the HTML tag to be checked
@return boolean whether or not the CSS selector applies to the specified HTML tag
*/
// TODO: Support for pseudo-classes, pseudo-elements
public boolean appliesTo(HTMLTag inHTMLTag)
{
boolean applies = false;
if (null == mBaseTag
|| inHTMLTag.getTagName().equalsIgnoreCase(mBaseTag))
{
applies = true;
if (mId != null)
{
applies = mId.equals(inHTMLTag.getId());
}
if (CollectionUtil.hasValues(mClassNames))
{
applies = false;
String tagClassString = inHTMLTag.getClassAttribute();
if (StringUtil.isSet(tagClassString))
{
List tagClassNames = Arrays.asList(tagClassString.split("\\s+"));
applies = true;
for (String className : mClassNames)
{
if (! tagClassNames.contains(className))
{
applies = false;
break;
}
}
}
}
if (applies
&& CollectionUtil.hasValues(mAttributeCriteria))
{
for (AttributeCriteria attrCriteria : mAttributeCriteria)
{
if (! attrCriteria.appliesTo(inHTMLTag))
{
applies = false;
break;
}
}
}
if (applies)
{
if (mAncestorSelector != null)
{
applies = false;
XMLContainer parentTag = inHTMLTag.getParentNode();
while (! applies
&& parentTag != null)
{
if (parentTag instanceof HTMLTag
&& mAncestorSelector.appliesTo((HTMLTag)parentTag))
{
applies = true;
}
parentTag = parentTag.getParentNode();
}
}
else if (mParentSelector != null)
{
XMLContainer parentTag = inHTMLTag.getParentNode();
applies = (parentTag instanceof HTMLTag
&& mParentSelector.appliesTo((HTMLTag) parentTag));
}
else if (mPrecedingSiblingSelector != null)
{
XMLContainer previousSibling = inHTMLTag.getPreviousSibling();
applies = (previousSibling instanceof HTMLTag
&& mPrecedingSiblingSelector.appliesTo((HTMLTag) previousSibling));
}
}
}
return applies;
}
//--------------------------------------------------------------------------
@Override
public String toString()
{
return mStringValue;
}
//--------------------------------------------------------------------------
@Override
public int hashCode()
{
return mStringValue.hashCode();
}
//--------------------------------------------------------------------------
@Override
public boolean equals(Object inObj)
{
return (inObj != null
&& inObj instanceof CSSSelector
&& inObj.toString().equals(toString()));
}
//##########################################################################
// PRIVATE METHODS
//##########################################################################
//--------------------------------------------------------------------------
private void init()
{
String[] pieces = mStringValue.split("\\s+");
String finalPiece = pieces[pieces.length - 1];
if (2 == pieces.length)
{
mAncestorSelector = new CSSSelector(pieces[0]);
}
else if (3 == pieces.length
&& pieces[1].equals(">"))
{
mParentSelector = new CSSSelector(pieces[0]);
}
else if (3 == pieces.length
&& pieces[1].equals("+"))
{
mPrecedingSiblingSelector = new CSSSelector(pieces[0]);
}
parse(finalPiece);
}
//--------------------------------------------------------------------------
private void parse(String inSelectorString)
{
char[] chars = inSelectorString.toCharArray();
boolean parsingBaseTag = true;
boolean parsingClassName = false;
boolean parsingId = false;
boolean parsingAttribute = false;
int startIndex = 0;
for (int i = 0; i < chars.length; i++)
{
char currentChar = chars[i];
if (parsingBaseTag)
{
if (currentChar == '.')
{
parsingBaseTag = false;
parsingClassName = true;
}
else if (currentChar == '#')
{
parsingBaseTag = false;
parsingId = true;
}
else if (currentChar == '[')
{
parsingBaseTag = false;
parsingAttribute = true;
}
if (! parsingBaseTag)
{
if (i > 0)
{
mBaseTag = inSelectorString.substring(0, i);
}
startIndex = i + 1;
}
}
else if (parsingClassName)
{
if (currentChar == '.')
{
addClassName(inSelectorString.substring(startIndex, i));
startIndex = i + 1;
}
else if (currentChar == '#')
{
parsingClassName = false;
parsingId = true;
}
else if (currentChar == '[')
{
parsingClassName = false;
parsingAttribute = true;
}
if (! parsingClassName)
{
addClassName(inSelectorString.substring(startIndex, i));
startIndex = i + 1;
}
}
else if (parsingId)
{
if (currentChar == '.')
{
parsingId = false;
parsingClassName = true;
}
else if (currentChar == '[')
{
parsingId = false;
parsingAttribute = true;
}
if (! parsingId)
{
mId = inSelectorString.substring(startIndex, i);
startIndex = i + 1;
}
}
else if (parsingAttribute)
{
if (currentChar == ']')
{
addAttributeCriteria(inSelectorString.substring(startIndex, i));
parsingAttribute = false;
}
}
else if (currentChar == '[')
{
parsingAttribute = true;
startIndex = i + 1;
}
}
// Anything left unparsed when we hit the end of the string?
if (parsingBaseTag)
{
mBaseTag = inSelectorString;
}
else if (parsingClassName)
{
addClassName(inSelectorString.substring(startIndex));
}
else if (parsingId)
{
mId = inSelectorString.substring(startIndex);
}
else if (parsingAttribute)
{
throw new CSSException("Problem parsing CSS selector " + StringUtil.singleQuote(inSelectorString) + "!");
}
if ("*".equals(mBaseTag))
{
mBaseTag = null;
}
}
//--------------------------------------------------------------------------
private void addClassName(String inValue)
{
if (null == mClassNames)
{
mClassNames = new HashSet(4);
}
mClassNames.add(inValue);
}
//--------------------------------------------------------------------------
private void addAttributeCriteria(String inValue)
{
if (null == mAttributeCriteria)
{
mAttributeCriteria = new HashSet(2);
}
Matcher m = sAttrPattern.matcher(inValue);
if (m.matches())
{
AttributeCriteria criteria = new AttributeCriteria(m.group(1), m.group(3));
if (m.group(2) != null)
{
if (m.group(2).equals("~="))
{
criteria.setFlag(AttributeCriteriaFlag.CONTAINS);
}
else if (m.group(2).equals("|="))
{
criteria.setFlag(AttributeCriteriaFlag.PREFIX);
}
}
mAttributeCriteria.add(criteria);
}
else
{
throw new CSSException("Problem parsing attribute selector " + StringUtil.singleQuote(inValue) + "!");
}
}
//##########################################################################
// INNER CLASS
//##########################################################################
private class AttributeCriteria
{
private String mAttr;
private String mValue;
private AttributeCriteriaFlag mFlag;
//-----------------------------------------------------------------------
public AttributeCriteria(String inName, String inValue)
{
mAttr = inName;
mValue = StringUtil.isSet(inValue) ? StringUtil.unquote(inValue) : null;
}
//-----------------------------------------------------------------------
public AttributeCriteria setFlag(AttributeCriteriaFlag inValue)
{
mFlag = inValue;
return this;
}
//-----------------------------------------------------------------------
public boolean appliesTo(HTMLTag inHTMLTag)
{
boolean applies = false;
String attrValue = inHTMLTag.getAttributeValue(mAttr);
if (attrValue != null)
{
applies = true;
if (mValue != null)
{
if (AttributeCriteriaFlag.CONTAINS == mFlag)
{
applies = false;
String[] pieces = attrValue.split("\\s+");
for (String piece : pieces)
{
if (piece.equals(mValue))
{
applies = true;
break;
}
}
}
else if (AttributeCriteriaFlag.PREFIX == mFlag)
{
applies = mValue.equals(attrValue) || attrValue.startsWith(mValue + "-");
}
else
{
applies = mValue.equals(attrValue);
}
}
}
return applies;
}
}
}