/******************************************************************************* * Copyright (c) 2004, 2008 John Krasnay and others. * 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: * John Krasnay - initial API and implementation *******************************************************************************/ package net.sf.vex.css; import java.io.CharArrayReader; import java.io.IOException; import java.io.Reader; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.w3c.css.sac.CSSException; import org.w3c.css.sac.DocumentHandler; import org.w3c.css.sac.InputSource; import org.w3c.css.sac.LexicalUnit; import org.w3c.css.sac.Parser; import org.w3c.css.sac.SACMediaList; import org.w3c.css.sac.Selector; import org.w3c.css.sac.SelectorList; /** * Driver for the creation of StyleSheet objects. */ public class StyleSheetReader { /** * Class constructor. */ public StyleSheetReader() { } protected static Parser createParser() { //return new org.apache.batik.css.parser.Parser(); return new org.w3c.flute.parser.Parser(); } /** * Creates a new StyleSheet object from a URL. * @param url URL from which to read the style sheet. */ public StyleSheet read(URL url) throws IOException { return this.read(new InputSource(url.toString()), url); } /** * Creates a style sheet from a string. This is mainly used for small * style sheets within unit tests. * @param s String containing the style sheet. */ public StyleSheet read(String s) throws CSSException, IOException { Reader reader = new CharArrayReader(s.toCharArray()); return this.read(new InputSource(reader), null); } /** * Creates a new Stylesheet from an input source. * @param inputSource InputSource from which to read the stylesheet. * @param url URL representing the input source, used to resolve @import * rules with relative URIs. May be null, in which case @import rules * are ignored. */ public StyleSheet read(InputSource inputSource, URL url) throws CSSException, IOException { // FIXME hardcoded cp Parser parser = createParser(); List rules = new ArrayList(); StyleSheetBuilder ssBuilder = new StyleSheetBuilder(this.source, rules, url); parser.setDocumentHandler(ssBuilder); parser.parseStyleSheet(new InputSource( url.toString())); Rule[] ruleArray = (Rule[]) rules.toArray(new Rule[rules.size()]); return new StyleSheet(ruleArray); } /** * Returns the source of the stylesheet. */ public byte getSource() { return source; } /** * Sets the source of the stylesheet. Must be one of StyleSheet.SOURCE_USER, * StyleSheet.SOURCE_AUTHOR, or StyleSheet.SOURCE_DEFAULT. * @param source The source to set. */ public void setSource(byte source) { this.source = source; } //======================================================== PRIVATE private byte source = StyleSheet.SOURCE_DEFAULT; private static class StyleSheetBuilder implements DocumentHandler { private byte source; // The rules that will be added to the stylesheet private List rules; // The rules to which decls are currently being added private List currentRules; // URL from which @import rules relative URIs are resolved. // May be null! private URL url; // Factory for creating serializable clones of SAC objects SacFactory factory = new SacFactory(); public StyleSheetBuilder(byte source, List rules, URL url) { this.source = source; this.rules = rules; this.url = url; } //-------------------------------------------- DocumentHandler methods public void comment(java.lang.String text) { } public void endDocument(InputSource source) { } public void endFontFace() { } public void endMedia(SACMediaList media) { } public void endPage(String name, String pseudo_page) { } public void endSelector(SelectorList selectors) { this.rules.addAll(this.currentRules); this.currentRules = null; } public void ignorableAtRule(String atRule) { } public void importStyle( String uri, SACMediaList media, String defaultNamespaceURI) { if (this.url == null) { return; } try { Parser parser = createParser(); URL importUrl = new URL(this.url, uri); StyleSheetBuilder ssBuilder = new StyleSheetBuilder(this.source, rules, importUrl); parser.setDocumentHandler(ssBuilder); parser.parseStyleSheet(new InputSource(importUrl.toString())); } catch (CSSException e) { } catch (IOException e) { } } public void namespaceDeclaration(String prefix, String uri) { } public void property( String name, LexicalUnit value, boolean important) { // Create a serializable clone of the value for storage in our // stylesheet. LexicalUnit val = factory.cloneLexicalUnit(value); if (name.equals(CSS.BORDER)) { this.expandBorder(val, important); } else if (name.equals(CSS.BORDER_BOTTOM)) { this.expandBorder(val, CSS.BORDER_BOTTOM, important); } else if (name.equals(CSS.BORDER_LEFT)) { this.expandBorder(val, CSS.BORDER_LEFT, important); } else if (name.equals(CSS.BORDER_RIGHT)) { this.expandBorder(val, CSS.BORDER_RIGHT, important); } else if (name.equals(CSS.BORDER_TOP)) { this.expandBorder(val, CSS.BORDER_TOP, important); } else if (name.equals(CSS.BORDER_COLOR)) { this.expandBorderColor(val, important); } else if (name.equals(CSS.BORDER_STYLE)) { this.expandBorderStyle(val, important); } else if (name.equals(CSS.BORDER_WIDTH)) { this.expandBorderWidth(val, important); } else if (name.equals(CSS.FONT)) { this.expandFont(val, important); } else if (name.equals(CSS.MARGIN)) { this.expandMargin(val, important); } else if (name.equals(CSS.PADDING)) { this.expandPadding(val, important); } else { this.addDecl(name, val, important); } } public void startDocument(InputSource source) { } public void startFontFace() { } public void startMedia(SACMediaList media) { } public void startPage(String name, String pseudo_page) { } public void startSelector(SelectorList selectors) { this.currentRules = new ArrayList(); for (int i = 0; i < selectors.getLength(); i++) { Selector selector = factory.cloneSelector(selectors.item(i)); Rule rule = new Rule(this.source, selector); this.currentRules.add(rule); } } //----------------------------------------- DocumentHandler methods end //======================================================= PRIVATE /** * Adds a PropertyDecl to the current set of rules. */ private void addDecl(String name, LexicalUnit value, boolean important) { Iterator iter = this.currentRules.iterator(); while (iter.hasNext()) { Rule rule = (Rule) iter.next(); rule.add(new PropertyDecl(rule, name, value, important)); } } /** * Expand the "border" shorthand property. */ private void expandBorder(LexicalUnit value, boolean important) { this.expandBorder(value, CSS.BORDER_BOTTOM, important); this.expandBorder(value, CSS.BORDER_LEFT, important); this.expandBorder(value, CSS.BORDER_RIGHT, important); this.expandBorder(value, CSS.BORDER_TOP, important); } /** * Expand one of the the "border-xxx" shorthand * properties. whichBorder must be one of CSS.BORDER_BOTTOM, * CSS.BORDER_LEFT, CSS.BORDER_RIGHT, CSS.BORDER_TOP. */ private void expandBorder( LexicalUnit value, String whichBorder, boolean important) { if (AbstractProperty.isInherit(value)) { this.addDecl(whichBorder + CSS.COLOR_SUFFIX, value, important); this.addDecl(whichBorder + CSS.STYLE_SUFFIX, value, important); this.addDecl(whichBorder + CSS.WIDTH_SUFFIX, value, important); return; } LexicalUnit[] lus = getLexicalUnitList(value); int i = 0; if (BorderWidthProperty.isBorderWidth(lus[i])) { this.addDecl(whichBorder + CSS.WIDTH_SUFFIX, lus[i], important); i++; } if (i < lus.length && BorderStyleProperty.isBorderStyle(lus[i])) { this.addDecl(whichBorder + CSS.STYLE_SUFFIX, lus[i], important); i++; } if (i < lus.length && ColorProperty.isColor(lus[i])) { this.addDecl(whichBorder + CSS.COLOR_SUFFIX, lus[i], important); i++; } } /** * Expand the "border-color" shorthand property. */ private void expandBorderColor(LexicalUnit value, boolean important) { if (AbstractProperty.isInherit(value)) { this.addDecl(CSS.BORDER_TOP_COLOR, value, important); this.addDecl(CSS.BORDER_LEFT_COLOR, value, important); this.addDecl(CSS.BORDER_RIGHT_COLOR, value, important); this.addDecl(CSS.BORDER_BOTTOM_COLOR, value, important); return; } LexicalUnit[] lus = getLexicalUnitList(value); if (lus.length >= 4) { this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important); this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[2], important); this.addDecl(CSS.BORDER_LEFT_COLOR, lus[3], important); } else if (lus.length == 3) { this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important); this.addDecl(CSS.BORDER_LEFT_COLOR, lus[1], important); this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[2], important); } else if (lus.length == 2) { this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important); this.addDecl(CSS.BORDER_LEFT_COLOR, lus[1], important); this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[0], important); } else if (lus.length == 1) { this.addDecl(CSS.BORDER_TOP_COLOR, lus[0], important); this.addDecl(CSS.BORDER_LEFT_COLOR, lus[0], important); this.addDecl(CSS.BORDER_RIGHT_COLOR, lus[0], important); this.addDecl(CSS.BORDER_BOTTOM_COLOR, lus[0], important); } } /** * Expand the "border-style" shorthand property. */ private void expandBorderStyle(LexicalUnit value, boolean important) { if (AbstractProperty.isInherit(value)) { this.addDecl(CSS.BORDER_TOP_STYLE, value, important); this.addDecl(CSS.BORDER_LEFT_STYLE, value, important); this.addDecl(CSS.BORDER_RIGHT_STYLE, value, important); this.addDecl(CSS.BORDER_BOTTOM_STYLE, value, important); return; } LexicalUnit[] lus = getLexicalUnitList(value); if (lus.length >= 4) { this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important); this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[2], important); this.addDecl(CSS.BORDER_LEFT_STYLE, lus[3], important); } else if (lus.length == 3) { this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important); this.addDecl(CSS.BORDER_LEFT_STYLE, lus[1], important); this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[2], important); } else if (lus.length == 2) { this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important); this.addDecl(CSS.BORDER_LEFT_STYLE, lus[1], important); this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[0], important); } else if (lus.length == 1) { this.addDecl(CSS.BORDER_TOP_STYLE, lus[0], important); this.addDecl(CSS.BORDER_LEFT_STYLE, lus[0], important); this.addDecl(CSS.BORDER_RIGHT_STYLE, lus[0], important); this.addDecl(CSS.BORDER_BOTTOM_STYLE, lus[0], important); } } /** * Expand the "border-width" shorthand property. */ private void expandBorderWidth(LexicalUnit value, boolean important) { if (AbstractProperty.isInherit(value)) { this.addDecl(CSS.BORDER_TOP_WIDTH, value, important); this.addDecl(CSS.BORDER_LEFT_WIDTH, value, important); this.addDecl(CSS.BORDER_RIGHT_WIDTH, value, important); this.addDecl(CSS.BORDER_BOTTOM_WIDTH, value, important); return; } LexicalUnit[] lus = getLexicalUnitList(value); if (lus.length >= 4) { this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important); this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[2], important); this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[3], important); } else if (lus.length == 3) { this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important); this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[1], important); this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[2], important); } else if (lus.length == 2) { this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important); this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[1], important); this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[1], important); this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[0], important); } else if (lus.length == 1) { this.addDecl(CSS.BORDER_TOP_WIDTH, lus[0], important); this.addDecl(CSS.BORDER_LEFT_WIDTH, lus[0], important); this.addDecl(CSS.BORDER_RIGHT_WIDTH, lus[0], important); this.addDecl(CSS.BORDER_BOTTOM_WIDTH, lus[0], important); } } /** * Expand the "font" shorthand property. */ private void expandFont(LexicalUnit value, boolean important) { if (AbstractProperty.isInherit(value)) { this.addDecl(CSS.FONT_STYLE, value, important); this.addDecl(CSS.FONT_VARIANT, value, important); this.addDecl(CSS.FONT_WEIGHT, value, important); this.addDecl(CSS.FONT_SIZE, value, important); this.addDecl(CSS.FONT_FAMILY, value, important); return; } LexicalUnit[] lus = getLexicalUnitList(value); int n = lus.length; int i = 0; if (i < n && FontStyleProperty.isFontStyle(lus[i])) { this.addDecl(CSS.FONT_STYLE, lus[i], important); i++; } if (i < n && FontVariantProperty.isFontVariant(lus[i])) { this.addDecl(CSS.FONT_VARIANT, lus[i], important); i++; } if (i < n && FontWeightProperty.isFontWeight(lus[i])) { this.addDecl(CSS.FONT_WEIGHT, lus[i], important); i++; } if (i < n && FontSizeProperty.isFontSize(lus[i])) { this.addDecl(CSS.FONT_SIZE, lus[i], important); i++; } if (i < n && lus[i].getLexicalUnitType() == LexicalUnit.SAC_OPERATOR_SLASH) { i++; // gobble slash if (i < n) { this.addDecl(CSS.LINE_HEIGHT, lus[i], important); } i++; } if (i < n) { this.addDecl(CSS.FONT_FAMILY, lus[i], important); } } /** * Expand the "margin" shorthand property. */ private void expandMargin(LexicalUnit value, boolean important) { if (AbstractProperty.isInherit(value)) { this.addDecl(CSS.MARGIN_TOP, value, important); this.addDecl(CSS.MARGIN_RIGHT, value, important); this.addDecl(CSS.MARGIN_BOTTOM, value, important); this.addDecl(CSS.MARGIN_LEFT, value, important); return; } LexicalUnit[] lus = getLexicalUnitList(value); if (lus.length >= 4) { this.addDecl(CSS.MARGIN_TOP, lus[0], important); this.addDecl(CSS.MARGIN_RIGHT, lus[1], important); this.addDecl(CSS.MARGIN_BOTTOM, lus[2], important); this.addDecl(CSS.MARGIN_LEFT, lus[3], important); } else if (lus.length == 3) { this.addDecl(CSS.MARGIN_TOP, lus[0], important); this.addDecl(CSS.MARGIN_LEFT, lus[1], important); this.addDecl(CSS.MARGIN_RIGHT, lus[1], important); this.addDecl(CSS.MARGIN_BOTTOM, lus[2], important); } else if (lus.length == 2) { this.addDecl(CSS.MARGIN_TOP, lus[0], important); this.addDecl(CSS.MARGIN_LEFT, lus[1], important); this.addDecl(CSS.MARGIN_RIGHT, lus[1], important); this.addDecl(CSS.MARGIN_BOTTOM, lus[0], important); } else if (lus.length == 1) { this.addDecl(CSS.MARGIN_TOP, lus[0], important); this.addDecl(CSS.MARGIN_LEFT, lus[0], important); this.addDecl(CSS.MARGIN_RIGHT, lus[0], important); this.addDecl(CSS.MARGIN_BOTTOM, lus[0], important); } } /** * Expand the "padding" shorthand property. */ private void expandPadding(LexicalUnit value, boolean important) { if (AbstractProperty.isInherit(value)) { this.addDecl(CSS.PADDING_TOP, value, important); this.addDecl(CSS.PADDING_LEFT, value, important); this.addDecl(CSS.PADDING_RIGHT, value, important); this.addDecl(CSS.PADDING_BOTTOM, value, important); return; } LexicalUnit[] lus = getLexicalUnitList(value); if (lus.length >= 4) { this.addDecl(CSS.PADDING_TOP, lus[0], important); this.addDecl(CSS.PADDING_RIGHT, lus[1], important); this.addDecl(CSS.PADDING_BOTTOM, lus[2], important); this.addDecl(CSS.PADDING_LEFT, lus[3], important); } else if (lus.length == 3) { this.addDecl(CSS.PADDING_TOP, lus[0], important); this.addDecl(CSS.PADDING_LEFT, lus[1], important); this.addDecl(CSS.PADDING_RIGHT, lus[1], important); this.addDecl(CSS.PADDING_BOTTOM, lus[2], important); } else if (lus.length == 2) { this.addDecl(CSS.PADDING_TOP, lus[0], important); this.addDecl(CSS.PADDING_LEFT, lus[1], important); this.addDecl(CSS.PADDING_RIGHT, lus[1], important); this.addDecl(CSS.PADDING_BOTTOM, lus[0], important); } else if (lus.length == 1) { this.addDecl(CSS.PADDING_TOP, lus[0], important); this.addDecl(CSS.PADDING_LEFT, lus[0], important); this.addDecl(CSS.PADDING_RIGHT, lus[0], important); this.addDecl(CSS.PADDING_BOTTOM, lus[0], important); } } /** * Returns an array of <code>LexicalUnit</code> objects, the first * of which is given. */ private static LexicalUnit[] getLexicalUnitList(LexicalUnit lu) { List lus = new ArrayList(); while (lu != null) { lus.add(lu); lu = lu.getNextLexicalUnit(); } return (LexicalUnit[]) lus.toArray(new LexicalUnit[lus.size()]); } } }