All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hfg.css.CSSSelector Maven / Gradle / Ivy

There is a newer version: 20240423
Show newest version
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.svg.SvgAttr;
import com.hfg.svg.SvgNode;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.StringUtil;
import com.hfg.xml.XMLContainer;
import com.hfg.xml.XMLTag;

//------------------------------------------------------------------------------
/**
 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 static final 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; } //-------------------------------------------------------------------------- /** Currently supports simple type selectors, id selectors, class selectors, attribute selectors, ancestor selectors, parent selectors, and adjacent sibling selectors. @param inSvgNode the SVG 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(SvgNode inSvgNode) { boolean applies = false; if (null == mBaseTag || inSvgNode.getTagName().equalsIgnoreCase(mBaseTag)) { applies = true; if (mId != null) { applies = mId.equals(inSvgNode.getId()); } if (CollectionUtil.hasValues(mClassNames)) { applies = false; String tagClassString = inSvgNode.getAttributeValue(SvgAttr.CLASS); 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 (inSvgNode instanceof XMLTag && ! attrCriteria.appliesTo((XMLTag) inSvgNode)) { applies = false; break; } } } if (applies) { if (mAncestorSelector != null) { applies = false; XMLContainer parentTag = inSvgNode.getParentNode(); while (! applies && parentTag != null) { if (parentTag instanceof SvgNode && mAncestorSelector.appliesTo((SvgNode)parentTag)) { applies = true; } parentTag = parentTag.getParentNode(); } } else if (mParentSelector != null) { XMLContainer parentTag = inSvgNode.getParentNode(); applies = (parentTag instanceof SvgNode && mParentSelector.appliesTo((SvgNode) parentTag)); } else if (mPrecedingSiblingSelector != null) { XMLContainer previousSibling = inSvgNode.getPreviousSibling(); applies = (previousSibling instanceof SvgNode && mPrecedingSiblingSelector.appliesTo((SvgNode) 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(XMLTag inTag) { boolean applies = false; String attrValue = inTag.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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy