package com.adobe.dp.css; import java.io.FileInputStream; import java.io.InputStream; import java.io.PrintWriter; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.Vector; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import com.adobe.dp.xml.util.SMap; import com.adobe.dp.xml.util.SMapAttributesAdapter; import com.adobe.dp.xml.util.SMapImpl; import com.adobe.dp.xml.util.XMLSerializer; public class CascadeEngine { CascadeResult result; Vector matcherLists = new Vector(); Hashtable classMap = new Hashtable(); Hashtable tagMap = new Hashtable(); int depth; int order; static class CascadeValue extends CSSValue { int specificity; int importance; int order; CSSValue value; public CascadeValue(CSSValue value, int specificity, int importance, int order) { this.value = value; this.specificity = specificity; this.importance = importance; this.order = order; } int compareSpecificity(CascadeValue other) { if (specificity != other.specificity) return specificity - other.specificity; if (importance != other.importance) return importance - other.importance; return order - other.order; } public void serialize(PrintWriter out) { // CascadeValue is internal and should never be left in public rules throw new RuntimeException("unexpected call"); } } static class MatcherList { Vector matchers = new Vector(); Set mediaList; CSSStylesheet stylesheet; MatcherList(CSSStylesheet stylesheet, Set mediaList) { this.stylesheet = stylesheet; this.mediaList = mediaList; } void addSelectorRule(SelectorRule rule, int depth, int order, boolean addSimpleSelectors) { for (int i = 0; i < rule.selectors.length; i++) { Selector s = rule.selectors[i]; if (addSimpleSelectors || !(isClassSelector(s) || isTagSelector(s))) { ElementMatcher matcher = s.getElementMatcher(); while (depth > 0) { matcher.pushElement(null, "*", null); depth--; } matchers.add(new MatcherRule(matcher, rule, order)); } } } } static class MatcherRule { ElementMatcher matcher; SelectorRule rule; int order; public MatcherRule(ElementMatcher matcher, SelectorRule rule, int order) { super(); this.matcher = matcher; this.rule = rule; this.order = order; } } public CascadeEngine() { } private static boolean isClassSelector(Selector s) { return s instanceof ClassSelector; } private static boolean isTagSelector(Selector s) { return s instanceof NamedElementSelector; } private void collectSimpleSelectors(SelectorRule rule, int order) { for (int i = 0; i < rule.selectors.length; i++) { Selector s = rule.selectors[i]; if (isClassSelector(s)) { ClassSelector cs = (ClassSelector) s; Vector list = (Vector) classMap.get(cs.className); if (list == null) { list = new Vector(); classMap.put(cs.className, list); } list.add(new MatcherRule(cs.getElementMatcher(), rule, order)); } else if (isTagSelector(s)) { NamedElementSelector ns = (NamedElementSelector) s; Vector list = (Vector) tagMap.get(ns.getElementName()); if (list == null) { list = new Vector(); tagMap.put(ns.getElementName(), list); } list.add(new MatcherRule(ns.getElementMatcher(), rule, order)); } } } public void add(CSSStylesheet stylesheet, Set mediaList) { MatcherList ml = new MatcherList(stylesheet, mediaList); Iterator statements = stylesheet.statements.iterator(); while (statements.hasNext()) { Object statement = statements.next(); if (statement instanceof SelectorRule) { SelectorRule sr = (SelectorRule) statement; if (mediaList == null) { collectSimpleSelectors(sr, order); } ml.addSelectorRule(sr, depth, order++, mediaList != null); } } matcherLists.add(ml); } private void applyRule(Selector selector, int order, BaseRule rule, String pseudoElement, Set mediaList) { int specificity = selector.getSpecificity(); applyRule(specificity, order, rule, pseudoElement, mediaList); } public void applyInlineRule(InlineRule rule) { applyRule(0x7F000000, order, rule, null, null); } private void applyRule(int specificity, int order, BaseRule rule, String pseudoElement, Set mediaList) { if (rule == null || rule.properties == null) return; Iterator entries = rule.properties.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); String prop = (String) entry.getKey(); int importance = 0; CSSValue value = (CSSValue) entry.getValue(); if (value instanceof CSSImportant) importance = 1; Iterator it = (mediaList == null ? null : mediaList.iterator()); do { ElementProperties props; if (it == null) props = result.getProperties(); else props = result.getPropertiesForMedia((String) it.next()); InlineRule style; if (pseudoElement == null) style = props.getPropertySet(); else style = props.getPropertySetForPseudoElement(pseudoElement); CascadeValue existing = (CascadeValue) style.get(prop); CascadeValue curr = new CascadeValue(value, specificity, importance, order); if (existing == null || curr.compareSpecificity(existing) > 0) style.set(prop, curr); } while (it != null && it.hasNext()); } } private void applyClassRules(String ns, String name, SMap attrs) { if (attrs != null) { String classAttr = ClassElementMatcher.getClassAttribute(ns, name); if (classAttr != null) { Object classStr = attrs.get(null, classAttr); if (classStr != null) { StringTokenizer tok = new StringTokenizer(classStr.toString(), " "); while (tok.hasMoreTokens()) { String className = tok.nextToken(); Vector list = (Vector) classMap.get(className); if (list != null) { int len = list.size(); for (int i = 0; i < len; i++) { MatcherRule mr = (MatcherRule) list.get(i); applyRule(mr.matcher.getSelector(), mr.order, mr.rule, null, null); } } } } } } } private void applyTagRules(String ns, String name) { Vector list = (Vector) tagMap.get(name); if (list != null) { int len = list.size(); for (int i = 0; i < len; i++) { MatcherRule mr = (MatcherRule) list.get(i); NamedElementSelector s = (NamedElementSelector) mr.matcher.getSelector(); if (!s.hasElementNamespace() || (ns != null && s.getElementNamespace().equals(ns))) applyRule(s, mr.order, mr.rule, null, null); } } } /** * Styles an element with a given namespace, name and attributes * * @param ns * element's namespace * @param name * element's local name * @param attrs * element's attributes */ public void pushElement(String ns, String name, SMap attrs) { depth++; result = new CascadeResult(); applyTagRules(ns, name); applyClassRules(ns, name, attrs); Iterator mli = matcherLists.iterator(); while (mli.hasNext()) { MatcherList ml = (MatcherList) mli.next(); Iterator matchers = ml.matchers.iterator(); while (matchers.hasNext()) { MatcherRule mr = (MatcherRule) matchers.next(); MatchResult res = mr.matcher.pushElement(ns, name, attrs); if (res != null) applyRule(mr.matcher.getSelector(), mr.order, mr.rule, res.pseudoElement, ml.mediaList); } } } /** * Finish element's processing. Note that pushElement/popElement calls * should correspond to the elements nesting. */ public void popElement() { depth--; Iterator mli = matcherLists.iterator(); while (mli.hasNext()) { MatcherList ml = (MatcherList) mli.next(); Iterator matchers = ml.matchers.iterator(); while (matchers.hasNext()) { MatcherRule mr = (MatcherRule) matchers.next(); mr.matcher.popElement(); } } } BaseRule makeRule(HashMap map) { if (map.isEmpty()) return null; BaseRule rule = new InlineRule(); Iterator entries = map.entrySet().iterator(); while (entries.hasNext()) { Map.Entry entry = (Map.Entry) entries.next(); String prop = (String) entry.getKey(); CascadeValue cv = (CascadeValue) entry.getValue(); rule.set(prop, cv.value); } return rule; } public CascadeResult getCascadeResult() { CascadeResult r = new CascadeResult(); Iterator mediaList = result.media(); ElementProperties elementProperties = result.getProperties(); if (mediaList != null) { while (mediaList.hasNext()) { String media = (String) mediaList.next(); ElementProperties mediaProps = result.getPropertiesForMedia(media); Iterator pel = mediaProps.pseudoElements(); if (pel != null) { while (pel.hasNext()) { String pseudoElement = (String) pel.next(); InlineRule mps = mediaProps.getPropertySetForPseudoElement(pseudoElement); InlineRule ps = elementProperties.getPropertySetForPseudoElement(pseudoElement); Iterator properties = mps.properties(); while (properties.hasNext()) { String property = (String) properties.next(); CascadeValue mediaSpecific = (CascadeValue) mps.get(property); CascadeValue generic = (CascadeValue) ps.get(property); if (generic == null || mediaSpecific.compareSpecificity(generic) > 0) { CSSValue value = mediaSpecific.value; ElementProperties rm = r.getPropertiesForMedia(media); rm.getPropertySetForPseudoElement(pseudoElement).set(property, value); } } } } InlineRule mps = mediaProps.getPropertySet(); InlineRule ps = elementProperties.getPropertySet(); Iterator properties = mps.properties(); while (properties.hasNext()) { String property = (String) properties.next(); CascadeValue mediaSpecific = (CascadeValue) mps.get(property); CascadeValue generic = (CascadeValue) ps.get(property); if (generic == null || mediaSpecific.compareSpecificity(generic) > 0) { CSSValue value = mediaSpecific.value; ElementProperties rm = r.getPropertiesForMedia(media); rm.getPropertySet().set(property, value); } } } } Iterator pel = elementProperties.pseudoElements(); if (pel != null) { while (pel.hasNext()) { String pseudoElement = (String) pel.next(); InlineRule ps = elementProperties.getPropertySetForPseudoElement(pseudoElement); Iterator properties = ps.properties(); while (properties.hasNext()) { String property = (String) properties.next(); CascadeValue cv = (CascadeValue) ps.get(property); ElementProperties rp = r.getProperties(); rp.getPropertySetForPseudoElement(pseudoElement).set(property, cv.value); } } } InlineRule ps = elementProperties.getPropertySet(); Iterator properties = ps.properties(); while (properties.hasNext()) { String property = (String) properties.next(); CascadeValue cv = (CascadeValue) ps.get(property); ElementProperties rp = r.getProperties(); rp.getPropertySet().set(property, cv.value); } return r; } public static void main(String[] args) { try { InputStream in = new FileInputStream(args[0]); InputStream cssin = new FileInputStream(args[1]); CSSStylesheet stylesheet = (new CSSParser()).readStylesheet(cssin); SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); final CascadeEngine styler = new CascadeEngine(); styler.add(stylesheet, null); final XMLSerializer ser = new XMLSerializer(System.out); ser.startDocument("1.0", "UTF-8"); SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); reader.setContentHandler(new ContentHandler() { public void characters(char[] text, int offset, int len) throws SAXException { ser.text(text, offset, len); } public void endDocument() throws SAXException { } public void endElement(String ns, String local, String qname) throws SAXException { styler.popElement(); ser.endElement(ns, local); } public void endPrefixMapping(String arg0) throws SAXException { } public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException { } public void processingInstruction(String arg0, String arg1) throws SAXException { } public void setDocumentLocator(Locator arg0) { } public void skippedEntity(String arg0) throws SAXException { } public void startDocument() throws SAXException { } public void startElement(String ns, String local, String qname, Attributes attrs) throws SAXException { SMapImpl smap = new SMapImpl(new SMapAttributesAdapter(attrs)); styler.pushElement(ns, local, smap); BaseRule es = styler.getCascadeResult().getProperties().getPropertySet(); smap.put(null, "style", (es.isEmpty() ? null : es.toString())); ser.startElement(ns, local, smap, qname.indexOf(':') < 0); } public void startPrefixMapping(String arg0, String arg1) throws SAXException { } }); reader.parse(new InputSource(in)); ser.endDocument(); } catch (Exception e) { e.printStackTrace(); } } }