/** * Copyright (c) 2006-2012 Cloudsmith Inc. and other contributors, as listed below. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cloudsmith * */ package org.cloudsmith.xtext.dommodel.formatter.css; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.cloudsmith.xtext.dommodel.IDomNode; /** * A DOM CSS consists of a set of {@link Rule} describing the styling of an {@link IDomNode}. * */ public class DomCSS { ArrayList<Rule> cssRules; /** * Comparator that compares specificity of two rules, and if specificity is equal, the rule with * lower index is considered to be 'before'. */ public static final Comparator<Rule> RULE_COMPARATOR = new Comparator<Rule>() { public int compare(Rule r1, Rule r2) { int r1s = r1.getSpecificity(); int r2s = r2.getSpecificity(); if(r1s < r2s) return -1; if(r1s > r2s) return 1; // if they are equal - they should be ordered on their index in the ruleset // the one with the lower index r1s = r1.getDomCSS().indexOf(r1); r2s = r2.getDomCSS().indexOf(r2); if(r1s < r2s) return -1; if(r1s > r2s) return 1; throw new IllegalStateException("Comparator MUST order rules"); } }; public DomCSS() { cssRules = new ArrayList<Rule>(); } /** * Adds all rules from another style sheet. * * @param ruleSet */ public void addAll(DomCSS ruleSet) { // can't just add them using collection routines as the parent ruleSet must be set, // and rule cloned. // for(Rule r : ruleSet.cssRules) addRule(r); } public void addAll(Iterable<Rule> rules) { for(Rule r : rules) addRule(r); } /** * Adds a rule to this rule set. If the rule is already in another style sheet, the rule is cloned before * being added. The added rule's domCSS property is set to this style sheet. * * @param rule */ public void addRule(Rule rule) { if(rule == Rule.NULL_RULE) return; rule = rule.getDomCSS() != null ? (Rule) rule.clone() : rule; rule.setDomCSS(this); cssRules.add(rule); } /** * @see #addRule(Rule) * @param rule * @param rules */ public void addRules(Rule rule, Rule... rules) { addRule(rule); for(Rule r : rules) addRule(r); } /** * Adds rules to this style sheet if they have a selector that is not equal to a selector already * in the style sheet. * * @param rules */ public void addUnique(Collection<Rule> rules) { boolean add; for(Rule r : rules) { add = true; // assume it is not there for(Rule q : cssRules) if(r.equalSelectorMatches(q)) { add = false; break; } // we have stopped iterating over the rule set so it is safe to add it here. // could be improved by collecting all to add first, and then add all of them // as this algorithm will rescan the just added rules - but this is perhaps wanted - the // ruleSet being copied may contain duplicate matching rules. if(add) addRule(r); } } /** * Adds all unique rules from the given style sheet to this style sheet. A rule is considered unique if it has a * different selector pattern than existing rules * * @param domCSS */ public void addUnique(DomCSS domCSS) { addUnique(domCSS.cssRules); } /** * Collects an (ordered) list of rules in order of specificity (lowest first) that matches the given node. * The node's instance style is taken into consideration with 'instance' specificity. * If two rules have the same specificity, the one added first to the rule set will have a lower index. * * @return - a list of matching Rules for the given node */ public List<Rule> collectRules(IDomNode node) { ArrayList<Rule> matches = new ArrayList<Rule>(5); // guessing on size // if element has a style map, add a (matched) rule for it if(node.getStyles() != null) matches.add(new Rule(new Select.Instance(node), node.getStyles()).withRuleName("StyleInNode")); for(Rule r : cssRules) if(r.matches(node)) matches.add(r); Collections.sort(matches, RULE_COMPARATOR); return matches; } /** * Collects the style applicable to the given node. (The styles from all matching rules are reduced to a * resulting style set). * * @param context * @param element * @return a style set with all collected styles */ public StyleSet collectStyles(IDomNode element) { StyleSetWithTracking result = new StyleSetWithTracking(); for(Rule r : collectRules(element)) { result.setSource(r); r.collectStyles(result); } result.setSource(null); // just in case something else manipulates this set return result; } /** * Collects the style applicable to the element by calling a visitor. * * @param node * @param collector */ public void collectStyles(IDomNode node, IStyleVisitor collector) { StyleSet collected = collectStyles(node); for(IStyle<? extends Object> s : collected.getStyles()) s.visit(node, collector); } /** * Returns the style sheet index of the given rule. * * @param rule * @return */ public int indexOf(Rule rule) { return cssRules.indexOf(rule); } }