From 136b435f28fe4135e4027ddc26cdddd63fd963da Mon Sep 17 00:00:00 2001 From: hrj Date: Tue, 7 Jul 2015 16:43:14 +0530 Subject: [PATCH] for #17: Implemented API to check for pseudo selectors * Refactored some of the previous implementations into a pure static class, keeping the existing API intact. s* mall optimisation: use array instead of array list --- src/main/java/cz/vutbr/web/css/Selector.java | 6 + .../cz/vutbr/web/csskit/SelectorImpl.java | 13 + .../java/cz/vutbr/web/domassign/Analyzer.java | 146 +----- .../cz/vutbr/web/domassign/AnalyzerUtil.java | 487 ++++++++++++++++++ .../vutbr/web/domassign/DirectAnalyzer.java | 202 +------- 5 files changed, 519 insertions(+), 335 deletions(-) create mode 100644 src/main/java/cz/vutbr/web/domassign/AnalyzerUtil.java diff --git a/src/main/java/cz/vutbr/web/css/Selector.java b/src/main/java/cz/vutbr/web/css/Selector.java index 276b7b27..702547ab 100644 --- a/src/main/java/cz/vutbr/web/css/Selector.java +++ b/src/main/java/cz/vutbr/web/css/Selector.java @@ -142,6 +142,12 @@ private PseudoDeclaration(String value, boolean isElement) { */ public PseudoDeclaration getPseudoElement(); + /** + * Checks where the specified pseudo declaration is in this selector + * @return true if the selector has the specified pseudo declaration + */ + public boolean hasPseudoDeclaration(final PseudoDeclaration pd); + /** * Modifies specificity according to CSS standard * @param spec Specificity to be modified diff --git a/src/main/java/cz/vutbr/web/csskit/SelectorImpl.java b/src/main/java/cz/vutbr/web/csskit/SelectorImpl.java index 1a59c568..d5d03e5c 100644 --- a/src/main/java/cz/vutbr/web/csskit/SelectorImpl.java +++ b/src/main/java/cz/vutbr/web/csskit/SelectorImpl.java @@ -96,6 +96,19 @@ public PseudoDeclaration getPseudoElement() { return ret; } + public boolean hasPseudoDeclaration(final PseudoDeclaration pd) { + for(SelectorPart item : list) { + if(item instanceof PseudoPage) + { + final PseudoDeclaration ret = ((PseudoPage)item).getDeclaration(); + if (ret == pd) { + return true; + } + } + } + return false; + } + public boolean matches(Element e) { // check other items of simple selector diff --git a/src/main/java/cz/vutbr/web/domassign/Analyzer.java b/src/main/java/cz/vutbr/web/domassign/Analyzer.java index 24e3f6bf..c08711f4 100644 --- a/src/main/java/cz/vutbr/web/domassign/Analyzer.java +++ b/src/main/java/cz/vutbr/web/domassign/Analyzer.java @@ -20,11 +20,8 @@ import cz.vutbr.web.css.CombinedSelector; import cz.vutbr.web.css.Declaration; import cz.vutbr.web.css.MatchCondition; -import cz.vutbr.web.css.MediaQuery; import cz.vutbr.web.css.MediaSpec; import cz.vutbr.web.css.NodeData; -import cz.vutbr.web.css.Rule; -import cz.vutbr.web.css.RuleMedia; import cz.vutbr.web.css.RuleSet; import cz.vutbr.web.css.Selector; import cz.vutbr.web.css.Selector.PseudoDeclaration; @@ -388,139 +385,10 @@ protected boolean matchSelector(CombinedSelector sel, Element e, TreeWalker w) { protected void classifyAllSheets(MediaSpec mediaspec) { rules = new Holder(); - for (StyleSheet sheet : sheets) - classifyRules(sheet, mediaspec); - } - - /** - * Divides rules in sheet into different categories to be easily and more - * quickly parsed afterward - * - * @param sheet The style sheet to be classified - * @param mediaspec The specification of the media for evaluating the media queries. - */ - protected void classifyRules(StyleSheet sheet, MediaSpec mediaspec) { - - // create a new holder if it does not exist - if (rules == null) { - rules = new Holder(); - } - - for (Rule rule : sheet) { - // this rule conforms to all media - if (rule instanceof RuleSet) { - RuleSet ruleset = (RuleSet) rule; - for (CombinedSelector s : ruleset.getSelectors()) { - insertClassified(rules, classifySelector(s), ruleset); - } - } - // this rule conforms to different media - else if (rule instanceof RuleMedia) { - RuleMedia rulemedia = (RuleMedia) rule; - - boolean mediaValid = false; - if(rulemedia.getMediaQueries()==null || rulemedia.getMediaQueries().isEmpty()) { - //no media queries actually - mediaValid = mediaspec.matchesEmpty(); - } else { - //find a matching query - for (MediaQuery media : rulemedia.getMediaQueries()) { - if (mediaspec.matches(media)) { - mediaValid = true; - break; - } - } - } - - if (mediaValid) - { - // for all rules in media set - for (RuleSet ruleset : rulemedia) { - // for all selectors in there - for (CombinedSelector s : ruleset.getSelectors()) { - insertClassified(rules, classifySelector(s), ruleset); - } - } - } - } - } - - // logging - if (log.isDebugEnabled()) { - log.debug("For media \"{}\" we have {} rules", mediaspec, rules.contentCount()); - if(log.isTraceEnabled()) { - log.trace("Detailed view: \n{}", rules); - } - } - - } - - /** - * Classify CSS rule according its selector for to be of specified item(s) - * - * @param selector - * CombinedSelector of rules - * @return List of HolderSelectors to which selectors conforms - */ - private List classifySelector(CombinedSelector selector) { - - List hs = new ArrayList(); - - try { - // last simple selector decided about all selector - Selector last = selector.getLastSelector(); - - // is element or other (wildcard) - String element = last.getElementName(); - if (element != null) { - // wildcard - if (Selector.ElementName.WILDCARD.equals(element)) - hs.add(new HolderSelector(HolderItem.OTHER, null)); - // element - else - hs.add(new HolderSelector(HolderItem.ELEMENT, element - .toLowerCase())); - } - - // is class name - String className = last.getClassName(); - if (className != null) - hs.add(new HolderSelector(HolderItem.CLASS, className - .toLowerCase())); - - // is id - String id = last.getIDName(); - if (id != null) - hs.add(new HolderSelector(HolderItem.ID, id.toLowerCase())); - - // is in others - if (hs.size() == 0) - hs.add(new HolderSelector(HolderItem.OTHER, null)); - - return hs; - } catch (UnsupportedOperationException e) { - log - .error("CombinedSelector does not include any selector, this should not happen!"); - return Collections.emptyList(); - } + AnalyzerUtil.classifyAllSheets(sheets, rules, mediaspec); } - - /** - * Inserts rules into holder - * - * @param holder - * Wrap to be inserted - * @param hs - * Wrap's selector and key - * @param value - * Value to be inserted - */ - private void insertClassified(Holder holder, List hs, RuleSet value) { - for (HolderSelector h : hs) - holder.insert(h.item, h.key, new OrderedRule(value, currentOrder++)); - } - + /** * Decides about holder item * @@ -547,7 +415,7 @@ public int type() { * @author kapy * */ - protected class HolderSelector { + protected static class HolderSelector { public HolderItem item; public String key; @@ -562,7 +430,7 @@ public HolderSelector(HolderItem item, String key) { * * @author burgetr */ - protected final class OrderedRule implements Comparable { + public static final class OrderedRule implements Comparable { private final RuleSet rule; private final int order; @@ -582,6 +450,12 @@ public int getOrder() { public int compareTo(OrderedRule o) { return getOrder() - o.getOrder(); } + + @Override + public String toString() { + return "OR" + order + ", " + rule; + } + } /** diff --git a/src/main/java/cz/vutbr/web/domassign/AnalyzerUtil.java b/src/main/java/cz/vutbr/web/domassign/AnalyzerUtil.java new file mode 100644 index 00000000..856be572 --- /dev/null +++ b/src/main/java/cz/vutbr/web/domassign/AnalyzerUtil.java @@ -0,0 +1,487 @@ +package cz.vutbr.web.domassign; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import cz.vutbr.web.css.CSSFactory; +import cz.vutbr.web.css.CombinedSelector; +import cz.vutbr.web.css.Declaration; +import cz.vutbr.web.css.MatchCondition; +import cz.vutbr.web.css.MediaQuery; +import cz.vutbr.web.css.MediaSpec; +import cz.vutbr.web.css.NodeData; +import cz.vutbr.web.css.Rule; +import cz.vutbr.web.css.RuleMedia; +import cz.vutbr.web.css.RuleSet; +import cz.vutbr.web.css.Selector; +import cz.vutbr.web.css.Selector.PseudoDeclaration; +import cz.vutbr.web.css.StyleSheet; +import cz.vutbr.web.csskit.ElementUtil; +import cz.vutbr.web.domassign.Analyzer.Holder; +import cz.vutbr.web.domassign.Analyzer.HolderItem; +import cz.vutbr.web.domassign.Analyzer.HolderSelector; +import cz.vutbr.web.domassign.Analyzer.OrderedRule; + +/** + * A pure (state-less) Analyser. + * + * Can be used by clients that need more control over what computation is cached. + * + */ +public final class AnalyzerUtil { + + private static final Logger log = LoggerFactory.getLogger(AnalyzerUtil.class); + + /** + * Returns all applicable rules for an element + * + * @param sheets + * @param element + * @param mediaspec + * @return + */ + public static OrderedRule[] getApplicableRules(final List sheets, final Element element, final MediaSpec mediaspec) { + final Holder rules = new Holder(); + AnalyzerUtil.classifyAllSheets(sheets, rules, mediaspec); + return getApplicableRules(element, rules); + } + + public static NodeData getElementStyle(Element el, PseudoDeclaration pseudo, MatchCondition matchCond, OrderedRule[] applicableRules) + { + return makeNodeData(computeDeclarations(el, pseudo, applicableRules, matchCond)); + } + + private static OrderedRule[] getApplicableRules(final Element e, final Holder holder) + { + // create set of possible candidates applicable to given element + // set is automatically filtered to not contain duplicates + final Set candidates = new HashSet(); + + // match element classes + for (final String cname : ElementUtil.elementClasses(e)) { + // holder contains rule with given class + final List classRules = holder.get(HolderItem.CLASS, cname.toLowerCase()); + if (classRules != null) + candidates.addAll(classRules); + } + log.trace("After CLASSes {} total candidates.", candidates.size()); + + // match IDs + final String id = ElementUtil.elementID(e); + if (id != null && id.length() != 0) { + final List idRules = holder.get(HolderItem.ID, id.toLowerCase()); + if (idRules != null) + candidates.addAll(idRules); + } + log.trace("After IDs {} total candidates.", candidates.size()); + + // match elements + final String name = ElementUtil.elementName(e); + if (name != null) { + final List nameRules = holder.get(HolderItem.ELEMENT, name.toLowerCase()); + if (nameRules != null) + candidates.addAll(nameRules); + } + log.trace("After ELEMENTs {} total candidates.", candidates.size()); + + // others + candidates.addAll(holder.get(HolderItem.OTHER, null)); + + final int totalCandidates = candidates.size(); + log.debug("Totally {} candidates.", totalCandidates); + + // transform to array to speed up traversal + // and sort rules in order as they were found in CSS definition + final OrderedRule[] clist = (OrderedRule[]) candidates.toArray(new OrderedRule[totalCandidates]); + Arrays.sort(clist); + + log.trace("With values: {}", Arrays.toString(clist)); + + return clist; + } + + static NodeData makeNodeData(final List decls) + { + final NodeData main = CSSFactory.createNodeData(); + for (final Declaration d : decls) + main.push(d); + + return main; + } + + /** + * Classifies the rules in all the style sheets. + * @param mediaspec The specification of the media for evaluating the media queries. + */ + static void classifyAllSheets(final List sheets, final Holder rules, final MediaSpec mediaspec) + { + for (final StyleSheet sheet : sheets) + classifyRules(sheet, mediaspec, rules); + } + + static boolean elementSelectorMatches(final Selector s, final Element e, final MatchCondition matchCond) { + return matchCond == null ? s.matches(e) : s.matches(e, matchCond); + } + + private static boolean nodeSelectorMatches(final Selector s, final Node n, final MatchCondition matchCond) { + if (n.getNodeType() == Node.ELEMENT_NODE) { + final Element e = (Element) n; + return matchCond == null ? s.matches(e) : s.matches(e, matchCond); + } else { + return false; + } + } + + static List computeDeclarations(final Element e, final PseudoDeclaration pseudo, final OrderedRule[] clist, final MatchCondition matchCond) { + // resulting list of declaration for this element with no pseudo-selectors (main list)(local cache) + final List eldecl = new ArrayList(); + + // for all candidates + for (final OrderedRule orule : clist) { + + final RuleSet rule = orule.getRule(); + final StyleSheet sheet = rule.getStyleSheet(); + final StyleSheet.Origin origin = (sheet == null) ? StyleSheet.Origin.AGENT : sheet.getOrigin(); + + // for all selectors inside + for (final CombinedSelector s : rule.getSelectors()) { + + if (!AnalyzerUtil.matchSelector(s, e, matchCond)) { + log.trace("CombinedSelector \"{}\" NOT matched!", s); + continue; + } + + log.trace("CombinedSelector \"{}\" matched", s); + + final PseudoDeclaration psel = s.getPseudoElement(); + final CombinedSelector.Specificity spec = s.computeSpecificity(); + if (psel == pseudo) + { + // add to the resulting list + for (final Declaration d : rule) + eldecl.add(new AssignedDeclaration(d, spec, origin)); + } + } + } + + // sort declarations + Collections.sort(eldecl); //sort the main list + log.debug("Sorted {} declarations.", eldecl.size()); + log.trace("With values: {}", eldecl); + + return eldecl; + } + + public static boolean hasPseudoSelector(final OrderedRule[] rules, final Element e, final MatchCondition matchCond, PseudoDeclaration pd) + { + for (final OrderedRule rule : rules) { + for (final CombinedSelector cs : rule.getRule().getSelectors()) { + final Selector lastSelector = cs.get(cs.size() - 1); + if (lastSelector.hasPseudoDeclaration(pd)) { + return true; + } + } + } + return false; + } + + public static boolean hasPseudoSelectorForAncestor(final OrderedRule[] rules, final Element e, final Element targetAncestor, final MatchCondition matchCond, PseudoDeclaration pd) + { + for (final OrderedRule rule : rules) { + for (final CombinedSelector cs : rule.getRule().getSelectors()) { + if (hasPseudoSelectorForAncestor(cs, e, targetAncestor, matchCond, pd)) { + return true; + } + } + } + return false; + } + + private static boolean hasPseudoSelectorForAncestor(final CombinedSelector sel, final Element e, final Element targetAncestor, final MatchCondition matchCond, PseudoDeclaration pd) + { + boolean retval = false; + Selector.Combinator combinator = null; + Element current = e; + // traverse simple selector backwards + for (int i = sel.size() - 1; i >= 0; i--) { + // last simple selector + final Selector s = sel.get(i); + + // decide according to combinator anti-pattern + if (combinator == null) { + retval = elementSelectorMatches(s, current, matchCond); + } else if (combinator == Selector.Combinator.ADJACENT) { + Node adjacent = current; + do { + adjacent = adjacent.getPreviousSibling(); + } while (adjacent != null && adjacent.getNodeType() != Node.ELEMENT_NODE); + retval = false; + if (adjacent != null && adjacent.getNodeType() == Node.ELEMENT_NODE) + { + current = (Element) adjacent; + retval = elementSelectorMatches(s, current, matchCond); + } + } else if (combinator == Selector.Combinator.PRECEDING) { + Node preceding = current.getPreviousSibling(); + retval = false; + do + { + if (preceding != null) + { + if (nodeSelectorMatches(s, preceding, matchCond)) + { + current = (Element) preceding; + retval = true; + } + else + preceding = preceding.getPreviousSibling(); + } + } while (!retval && preceding != null); + } else if (combinator == Selector.Combinator.DESCENDANT) { + Node ancestor = current.getParentNode(); + retval = false; + do + { + if (ancestor != null) + { + if (nodeSelectorMatches(s, ancestor, matchCond)) + { + current = (Element) ancestor; + retval = true; + } + else + ancestor = ancestor.getParentNode(); + } + } while (!retval && ancestor != null); + } else if (combinator == Selector.Combinator.CHILD) { + final Node parent = current.getParentNode(); + retval = false; + if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) + { + current = (Element) parent; + retval = elementSelectorMatches(s, current, matchCond); + } + } + + // set combinator for next loop + combinator = s.getCombinator(); + + // leave loop if not matched + if (!retval) { + break; + } else if (current == targetAncestor) { + return s.hasPseudoDeclaration(pd); + } + } + return false; + } + + protected static boolean matchSelector(final CombinedSelector sel, final Element e, final MatchCondition matchCond) + { + boolean retval = false; + Selector.Combinator combinator = null; + Element current = e; + // traverse simple selector backwards + for (int i = sel.size() - 1; i >= 0; i--) { + // last simple selector + final Selector s = sel.get(i); + log.trace("Iterating loop with selector {}, combinator {}", + s, combinator); + + // decide according to combinator anti-pattern + if (combinator == null) { + retval = elementSelectorMatches(s, current, matchCond); + } else if (combinator == Selector.Combinator.ADJACENT) { + Node adjacent = current; + do { + adjacent = adjacent.getPreviousSibling(); + } while (adjacent != null && adjacent.getNodeType() != Node.ELEMENT_NODE); + retval = false; + if (adjacent != null && adjacent.getNodeType() == Node.ELEMENT_NODE) + { + current = (Element) adjacent; + retval = elementSelectorMatches(s, current, matchCond); + } + } else if (combinator == Selector.Combinator.PRECEDING) { + Node preceding = current.getPreviousSibling(); + retval = false; + do + { + if (preceding != null) + { + if (nodeSelectorMatches(s, preceding, matchCond)) + { + current = (Element) preceding; + retval = true; + } + else + preceding = preceding.getPreviousSibling(); + } + } while (!retval && preceding != null); + } else if (combinator == Selector.Combinator.DESCENDANT) { + Node ancestor = current.getParentNode(); + retval = false; + do + { + if (ancestor != null) + { + if (nodeSelectorMatches(s, ancestor, matchCond)) + { + current = (Element) ancestor; + retval = true; + } + else + ancestor = ancestor.getParentNode(); + } + } while (!retval && ancestor != null); + } else if (combinator == Selector.Combinator.CHILD) { + final Node parent = current.getParentNode(); + retval = false; + if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) + { + current = (Element) parent; + retval = elementSelectorMatches(s, current, matchCond); + } + } + + // set combinator for next loop + combinator = s.getCombinator(); + + // leave loop if not matched + if (!retval) + break; + } + return retval; + } + + /** + * Classify CSS rule according its selector for to be of specified item(s) + * + * @param selector + * CombinedSelector of rules + * @return List of HolderSelectors to which selectors conforms + */ + private static List classifySelector(final CombinedSelector selector) { + + final List hs = new ArrayList(); + + try { + // last simple selector decided about all selector + final Selector last = selector.getLastSelector(); + + // is element or other (wildcard) + final String element = last.getElementName(); + if (element != null) { + // wildcard + if (Selector.ElementName.WILDCARD.equals(element)) + hs.add(new HolderSelector(HolderItem.OTHER, null)); + // element + else + hs.add(new HolderSelector(HolderItem.ELEMENT, element + .toLowerCase())); + } + + // is class name + final String className = last.getClassName(); + if (className != null) + hs.add(new HolderSelector(HolderItem.CLASS, className + .toLowerCase())); + + // is id + final String id = last.getIDName(); + if (id != null) + hs.add(new HolderSelector(HolderItem.ID, id.toLowerCase())); + + // is in others + if (hs.size() == 0) + hs.add(new HolderSelector(HolderItem.OTHER, null)); + + return hs; + + } catch (final UnsupportedOperationException e) { + log + .error("CombinedSelector does not include any selector, this should not happen!"); + return Collections.emptyList(); + } + } + + private static class Counter { + private int count = 0; + public int getAndIncrement() { + return count++; + } + } + + private static void insertClassified(final Holder holder, final List hs, final RuleSet value, final Counter orderCounter) { + for (final HolderSelector h : hs) + holder.insert(h.item, h.key, new OrderedRule(value, orderCounter.getAndIncrement())); + } + + /** + * Divides rules in sheet into different categories to be easily and more + * quickly parsed afterward + * + * @param sheet The style sheet to be classified + * @param mediaspec The specification of the media for evaluating the media queries. + */ + private static void classifyRules(final StyleSheet sheet, final MediaSpec mediaspec, final Holder rules) { + final Counter orderCounter = new Counter(); + + for (final Rule rule : sheet) { + // this rule conforms to all media + if (rule instanceof RuleSet) { + final RuleSet ruleset = (RuleSet) rule; + for (final CombinedSelector s : ruleset.getSelectors()) { + insertClassified(rules, classifySelector(s), ruleset, orderCounter); + } + } + // this rule conforms to different media + else if (rule instanceof RuleMedia) { + final RuleMedia rulemedia = (RuleMedia) rule; + + boolean mediaValid = false; + if(rulemedia.getMediaQueries()==null || rulemedia.getMediaQueries().isEmpty()) { + //no media queries actually + mediaValid = mediaspec.matchesEmpty(); + } else { + //find a matching query + for (final MediaQuery media : rulemedia.getMediaQueries()) { + if (mediaspec.matches(media)) { + mediaValid = true; + break; + } + } + } + + if (mediaValid) + { + // for all rules in media set + for (final RuleSet ruleset : rulemedia) { + // for all selectors in there + for (final CombinedSelector s : ruleset.getSelectors()) { + insertClassified(rules, classifySelector(s), ruleset, orderCounter); + } + } + } + } + } + + // logging + if (log.isDebugEnabled()) { + log.debug("For media \"{}\" we have {} rules", mediaspec, rules.contentCount()); + if(log.isTraceEnabled()) { + log.trace("Detailed view: \n{}", rules); + } + } + } + +} diff --git a/src/main/java/cz/vutbr/web/domassign/DirectAnalyzer.java b/src/main/java/cz/vutbr/web/domassign/DirectAnalyzer.java index e5ab9665..568707bd 100644 --- a/src/main/java/cz/vutbr/web/domassign/DirectAnalyzer.java +++ b/src/main/java/cz/vutbr/web/domassign/DirectAnalyzer.java @@ -5,28 +5,16 @@ */ package cz.vutbr.web.domassign; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; -import org.w3c.dom.Node; -import cz.vutbr.web.css.CSSFactory; -import cz.vutbr.web.css.CombinedSelector; -import cz.vutbr.web.css.Declaration; -import cz.vutbr.web.css.MatchCondition; import cz.vutbr.web.css.MediaSpec; import cz.vutbr.web.css.NodeData; -import cz.vutbr.web.css.RuleSet; -import cz.vutbr.web.css.Selector; import cz.vutbr.web.css.Selector.PseudoDeclaration; import cz.vutbr.web.css.StyleSheet; -import cz.vutbr.web.csskit.ElementUtil; /** * A simple ananalyzer that computes a style for the individual DOM nodes with no mapping and caching. @@ -67,18 +55,10 @@ public DirectAnalyzer(List sheets) */ public NodeData getElementStyle(Element el, PseudoDeclaration pseudo, MediaSpec media) { - if (rules == null) - classifyAllSheets(media); - - List decls = getDeclarationsForElement(el, pseudo, rules); - - NodeData main = CSSFactory.createNodeData(); - for (Declaration d : decls) - main.push(d); - - return main; + final OrderedRule[] applicableRules = AnalyzerUtil.getApplicableRules(sheets, el, media); + return AnalyzerUtil.getElementStyle(el, pseudo, getMatchCondition(), applicableRules); } - + /** * Computes the style of an element with an eventual pseudo element for the given media. * @param el The DOM element. @@ -92,181 +72,5 @@ public NodeData getElementStyle(Element el, PseudoDeclaration pseudo, String med } //========================================================================================== - - protected List getDeclarationsForElement(Element e, PseudoDeclaration pseudo, Holder holder) - { - if(log.isDebugEnabled()) { - log.debug("Traversal of {} {}.", e.getNodeName(), e.getNodeValue()); - } - - // create set of possible candidates applicable to given element - // set is automatically filtered to not contain duplicates - Set candidates = new HashSet(); - - // match element classes - for (String cname : ElementUtil.elementClasses(e)) { - // holder contains rule with given class - List rules = holder.get(HolderItem.CLASS, cname.toLowerCase()); - if (rules != null) - candidates.addAll(rules); - } - log.trace("After CLASSes {} total candidates.", candidates.size()); - - // match IDs - String id = ElementUtil.elementID(e); - if (id != null && id.length() != 0) { - List rules = holder.get(HolderItem.ID, id.toLowerCase()); - if (rules != null) - candidates.addAll(rules); - } - log.trace("After IDs {} total candidates.", candidates.size()); - - // match elements - String name = ElementUtil.elementName(e); - if (name != null) { - List rules = holder.get(HolderItem.ELEMENT, name.toLowerCase()); - if (rules != null) - candidates.addAll(rules); - } - log.trace("After ELEMENTs {} total candidates.", candidates.size()); - - // others - candidates.addAll(holder.get(HolderItem.OTHER, null)); - - // transform to list to speed up traversal - // and sort rules in order as they were found in CSS definition - List clist = new ArrayList(candidates); - Collections.sort(clist); - - log.debug("Totally {} candidates.", candidates.size()); - log.trace("With values: {}", clist); - - // resulting list of declaration for this element with no pseudo-selectors (main list)(local cache) - List eldecl = new ArrayList(); - - // for all candidates - for (OrderedRule orule : clist) { - - final RuleSet rule = orule.getRule(); - StyleSheet sheet = rule.getStyleSheet(); - StyleSheet.Origin origin = (sheet == null) ? StyleSheet.Origin.AGENT : sheet.getOrigin(); - - // for all selectors inside - for (CombinedSelector s : rule.getSelectors()) { - - if (!matchSelector(s, e)) { - log.trace("CombinedSelector \"{}\" NOT matched!", s); - continue; - } - - log.trace("CombinedSelector \"{}\" matched", s); - - PseudoDeclaration psel = s.getPseudoElement(); - CombinedSelector.Specificity spec = s.computeSpecificity(); - if (psel == pseudo) - { - // add to the resulting list - for (Declaration d : rule) - eldecl.add(new AssignedDeclaration(d, spec, origin)); - } - } - } - // sort declarations - Collections.sort(eldecl); //sort the main list - log.debug("Sorted {} declarations.", eldecl.size()); - log.trace("With values: {}", eldecl); - - return eldecl; - } - - private boolean nodeSelectorMatches(final Selector s, final Node n) { - if (n.getNodeType() == Node.ELEMENT_NODE) { - final Element e = (Element) n; - final MatchCondition matchCond = this.getMatchCondition(); - return matchCond == null ? s.matches(e) : s.matches(e, matchCond); - } else { - return false; - } - } - - protected boolean matchSelector(CombinedSelector sel, Element e) - { - boolean retval = false; - Selector.Combinator combinator = null; - Element current = e; - // traverse simple selector backwards - for (int i = sel.size() - 1; i >= 0; i--) { - // last simple selector - Selector s = sel.get(i); - log.trace("Iterating loop with selector {}, combinator {}", - s, combinator); - - // decide according to combinator anti-pattern - if (combinator == null) { - retval = this.elementSelectorMatches(s, current); - } else if (combinator == Selector.Combinator.ADJACENT) { - Node adjacent = current; - do { - adjacent = adjacent.getPreviousSibling(); - } while (adjacent != null && adjacent.getNodeType() != Node.ELEMENT_NODE); - retval = false; - if (adjacent != null && adjacent.getNodeType() == Node.ELEMENT_NODE) - { - current = (Element) adjacent; - retval = this.elementSelectorMatches(s, current); - } - } else if (combinator == Selector.Combinator.PRECEDING) { - Node preceding = current.getPreviousSibling(); - retval = false; - do - { - if (preceding != null) - { - if (this.nodeSelectorMatches(s, preceding)) - { - current = (Element) preceding; - retval = true; - } - else - preceding = preceding.getPreviousSibling(); - } - } while (!retval && preceding != null); - } else if (combinator == Selector.Combinator.DESCENDANT) { - Node ancestor = current.getParentNode(); - retval = false; - do - { - if (ancestor != null) - { - if (this.nodeSelectorMatches(s, ancestor)) - { - current = (Element) ancestor; - retval = true; - } - else - ancestor = ancestor.getParentNode(); - } - } while (!retval && ancestor != null); - } else if (combinator == Selector.Combinator.CHILD) { - Node parent = current.getParentNode(); - retval = false; - if (parent != null && parent.getNodeType() == Node.ELEMENT_NODE) - { - current = (Element) parent; - retval = this.elementSelectorMatches(s, current); - } - } - - // set combinator for next loop - combinator = s.getCombinator(); - - // leave loop if not matched - if (!retval) - break; - } - return retval; - } - - }