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

cz.vutbr.web.domassign.AnalyzerUtil Maven / Gradle / Ivy

Go to download

jStyleParser is a CSS parser written in Java. It has its own application interface that is designed to allow an efficient CSS processing in Java and mapping the values to the Java data types. It parses CSS 2.1 style sheets into structures that can be efficiently assigned to DOM elements. It is intended be the primary CSS parser for the CSSBox library. While handling errors, it is user agent conforming according to the CSS specification.

There is a newer version: 4.0.1
Show newest version
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.ElementMatcher;
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.PseudoClassType;
import cz.vutbr.web.css.Selector.PseudoElementType;
import cz.vutbr.web.css.StyleSheet;
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 = getClassifiedRules(sheets, mediaspec);
	    return getApplicableRules(element, rules, null);
    }

    public static Holder getClassifiedRules(final List sheets, final MediaSpec mediaspec) {
	    final Holder rules = new Holder();
	    AnalyzerUtil.classifyAllSheets(sheets, rules, mediaspec);
	    return rules;
    }

    public static NodeData getElementStyle(Element el, PseudoElementType pseudo, final ElementMatcher matcher, MatchCondition matchCond, OrderedRule[] applicableRules)
    {
    	return makeNodeData(computeDeclarations(el, pseudo, applicableRules, matcher, matchCond));
    }

	public static OrderedRule[] getApplicableRules(final Element e, final Holder holder, final RuleSet[] elementRuleSets)
	{
        // 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 : CSSFactory.getElementMatcher().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 = CSSFactory.getElementMatcher().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 = CSSFactory.getElementMatcher().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();
        final int netCandidates = elementRuleSets == null ? totalCandidates : totalCandidates + elementRuleSets.length;

        // 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[netCandidates]);
        Arrays.sort(clist, 0, totalCandidates);

        // Append the element rules
        if (elementRuleSets != null) {
          final int lastOrder = totalCandidates > 0 ? clist[totalCandidates-1].getOrder() : 0;
          for (int i = 0; i < elementRuleSets.length; i++) {
            clist[totalCandidates + i] = new OrderedRule(elementRuleSets[i], lastOrder + i);
          }
        }

        // NOTE: The following trace statement creates a lot of memory pressure
        // 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)
	{
		final Counter orderCounter = new Counter();
	    for (final StyleSheet sheet : sheets)
	        classifyRules(sheet, mediaspec, rules, orderCounter);
	}
	
	static boolean elementSelectorMatches(final Selector s, final Element e, final ElementMatcher matcher, final MatchCondition matchCond) {
		return s.matches(e, matcher, matchCond);
	}

    private static boolean nodeSelectorMatches(final Selector s, final Node n, final ElementMatcher matcher, final MatchCondition matchCond) {
        if (n.getNodeType() == Node.ELEMENT_NODE) {
            return s.matches((Element) n, matcher, matchCond);
        } else {
            return false;
        }
    }
    
	static List computeDeclarations(final Element e, final PseudoElementType pseudo, final OrderedRule[] clist, final ElementMatcher matcher, 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, matcher, matchCond)) {
                    log.trace("CombinedSelector \"{}\" NOT matched!", s);
                    continue;
                }

                log.trace("CombinedSelector \"{}\" matched", s);
                
                final PseudoElementType ptype = s.getPseudoElementType();
                if (ptype == pseudo)
                {
                    // add to the resulting list
                    final CombinedSelector.Specificity spec = s.computeSpecificity();
                    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, PseudoClassType pd)
    {
		for (final OrderedRule rule : rules) {
			for (final CombinedSelector cs : rule.getRule().getSelectors()) {
				final Selector lastSelector = cs.get(cs.size() - 1);
				if (lastSelector.hasPseudoClass(pd)) {
					return true;
				}
			}
		}
		return false;
	}

    public static boolean hasPseudoSelectorForAncestor(final OrderedRule[] rules, final Element e, final Element targetAncestor, final ElementMatcher matcher, final MatchCondition matchCond, PseudoClassType pd)
    {
		for (final OrderedRule rule : rules) {
			for (final CombinedSelector cs : rule.getRule().getSelectors()) {
				if (hasPseudoSelectorForAncestor(cs, e, targetAncestor, matcher, matchCond, pd)) {
					return true;
				}
			}
		}
		return false;
    }

    private static boolean hasPseudoSelectorForAncestor(final CombinedSelector sel, final Element e, final Element targetAncestor, final ElementMatcher matcher, final MatchCondition matchCond, PseudoClassType 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, matcher, 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, matcher, matchCond);
                }
            } else if (combinator == Selector.Combinator.PRECEDING) {
                Node preceding = current.getPreviousSibling();
                retval = false;
                do
                {
                    if (preceding != null)
                    {
                        if (nodeSelectorMatches(s, preceding, matcher, 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, matcher, 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, matcher, matchCond);
                }
            }

            // set combinator for next loop
            combinator = s.getCombinator();

            // leave loop if not matched
            if (!retval) {
                break;
            } else if (current == targetAncestor) {
                return s.hasPseudoClass(pd);
            }
        }
        return false;
    }

    protected static boolean matchSelector(final CombinedSelector sel, final Element e, final ElementMatcher matcher, 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, matcher, 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, matcher, matchCond);
                }
            } else if (combinator == Selector.Combinator.PRECEDING) {
                Node preceding = current.getPreviousSibling();
                retval = false;
                do
                {
                    if (preceding != null)
                    {
                        if (nodeSelectorMatches(s, preceding, matcher, 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, matcher, 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, matcher, 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.
	 * @param orderCounter 
	 */
	private static void classifyRules(final StyleSheet sheet, final MediaSpec mediaspec, final Holder rules, final Counter orderCounter) {

		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);
			}
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy