/* * @(#)CSSLoader.java * * Copyright (c) 1996-2010 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. * * Original code taken from article "Swing and CSS" by Joshua Marinacci 10/14/2003 * http://today.java.net/pub/a/today/2003/10/14/swingcss.html */ package org.jhotdraw.xml.css; import java.io.*; import java.util.*; /** * Parsers a Cascading Style Sheet (CSS). * <pre> * IDENT {ident} * ATKEYWORD @{ident} * STRING {string} * INVALID {invalid} * HASH #{name} * NUMBER {num} * PERCENTAGE {num}% * DIMENSION {num}{ident} * URI url\({w}{string}{w}\) * |url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w}\) * UNICODE-RANGE U\+[0-9A-F?]{1,6}(-[0-9A-F]{1,6})? * CDO <!-- * CDC --> * ; ; * { \{ * } \} * ( \( * ) \) * [ \[ * ] \] * S [ \t\r\n\f]+ * COMMENT \/\*[^*]*\*+([^/*][^*]*\*+)*\/ * FUNCTION {ident}\( * INCLUDES ~= * DASHMATCH |= * DELIM any other character not matched by the above rules, and neither a single nor a double quote * * * stylesheet : [ CDO | CDC | S | statement ]*; * statement : ruleset | at-rule; * at-rule : ATKEYWORD S* any* [ block | ';' S* ]; * block : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*; * ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*; * selector : any+; * declaration : DELIM? property S* ':' S* value; * property : IDENT; * value : [ any | block | ATKEYWORD S* ]+; * any : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING * | DELIM | URI | HASH | UNICODE-RANGE | INCLUDES * | DASHMATCH | FUNCTION S* any* ')' * | '(' S* any* ')' | '[' S* any* ']' ] S*; * </pre> * * @author Werner Randelshofer * @version $Id$ */ public class CSSParser { public void parse(String css, StyleManager rm) throws IOException { parse(new StringReader(css), rm); } public void parse(Reader css, StyleManager rm) throws IOException { StreamTokenizer tt = new StreamTokenizer(css); tt.resetSyntax(); tt.wordChars('a', 'z'); tt.wordChars('A', 'Z'); tt.wordChars('0', '9'); tt.wordChars(128 + 32, 255); tt.whitespaceChars(0, ' '); tt.commentChar('/'); tt.slashStarComments(true); parseStylesheet(tt, rm); } private void parseStylesheet(StreamTokenizer tt, StyleManager rm) throws IOException { while (tt.nextToken() != StreamTokenizer.TT_EOF) { tt.pushBack(); parseRuleset(tt, rm); } } private void parseRuleset(StreamTokenizer tt, StyleManager rm) throws IOException { // parse selector list List<String> selectors = parseSelectorList(tt); if (tt.nextToken() != '{') throw new IOException("Ruleset '{' missing for "+selectors); Map<String,String> declarations = parseDeclarationMap(tt); if (tt.nextToken() != '}') throw new IOException("Ruleset '}' missing for "+selectors); for (String selector : selectors) { rm.add(new CSSRule(selector, declarations)); // System.out.println("CSSParser.add("+selector+","+declarations); /* for (Map.Entry<String,String> entry : declarations.entrySet()) { rm.add(new CSSRule(selector, entry.getKey(), entry.getValue())); }*/ } } private List<String> parseSelectorList(StreamTokenizer tt) throws IOException { LinkedList<String> list = new LinkedList<String>(); StringBuilder selector = new StringBuilder(); boolean needsWhitespace = false; while (tt.nextToken() != StreamTokenizer.TT_EOF && tt.ttype != '{') { switch (tt.ttype) { case StreamTokenizer.TT_WORD : if (needsWhitespace) selector.append(' '); selector.append(tt.sval); needsWhitespace = true; break; case ',' : list.add(selector.toString()); selector.setLength(0); needsWhitespace = false; break; default : if (needsWhitespace) selector.append(' '); selector.append((char) tt.ttype); needsWhitespace = false; break; } } if (selector.length() != 0) { list.add(selector.toString()); } tt.pushBack(); //System.out.println("selectors:"+list); return list; } private Map<String,String> parseDeclarationMap(StreamTokenizer tt) throws IOException { HashMap<String,String> map = new HashMap<String, String>(); do { // Parse key StringBuilder key = new StringBuilder(); while (tt.nextToken() != StreamTokenizer.TT_EOF && tt.ttype != '}' && tt.ttype != ':' && tt.ttype != ';') { switch (tt.ttype) { case StreamTokenizer.TT_WORD : key.append(tt.sval); break; default : key.append((char) tt.ttype); break; } } if (tt.ttype == '}' && key.length() == 0) { break; } if (tt.ttype != ':') throw new IOException("Declaration ':' missing for "+key); // Parse value StringBuilder value = new StringBuilder(); boolean needsWhitespace = false; while (tt.nextToken() != StreamTokenizer.TT_EOF && tt.ttype != ';' && tt.ttype != '}') { switch (tt.ttype) { case StreamTokenizer.TT_WORD : if (needsWhitespace) value.append(' '); value.append(tt.sval); needsWhitespace = true; break; default : value.append((char) tt.ttype); needsWhitespace = false; break; } } map.put(key.toString(), value.toString()); //System.out.println(" declaration: "+key+":"+value); } while (tt.ttype != '}' && tt.ttype != StreamTokenizer.TT_EOF); tt.pushBack(); return map; } }