001: /* *****************************************************************************
002: * CSSHandler.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2007 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.css;
011:
012: import java.io.*;
013: import java.util.*;
014: import java.util.regex.*;
015: import org.w3c.css.sac.*;
016: import org.apache.log4j.*;
017: import org.jdom.*;
018:
019: /**
020: * Handler used to parse CSS file and process style rules on a document element.
021: *
022: * @author <a href="mailto:pkang@laszlosystems.com">Pablo Kang</a>
023: */
024: public class CSSHandler implements DocumentHandler, Serializable,
025: ErrorHandler {
026:
027: //==========================================================================
028: // class static
029: //==========================================================================
030:
031: /** Logger. */
032: private static Logger mLogger = Logger.getLogger(CSSHandler.class);
033:
034: /** CSS parser factory. */
035: private static org.w3c.css.sac.helpers.ParserFactory mCSSParserFactory = null;
036:
037: static {
038: // This system property is required for the SAC ParserFactory.
039: if (System.getProperty("org.w3c.css.sac.parser") == null) {
040: System.setProperty("org.w3c.css.sac.parser",
041: "org.apache.batik.css.parser.Parser");
042: }
043: mCSSParserFactory = new org.w3c.css.sac.helpers.ParserFactory();
044: }
045:
046: /**
047: * Entry point to creating a CSSHandler to read from an external
048: * stylesheet file
049: * @param rootDir the directory where cssFile exists.
050: * @param cssFile the css file to read.
051: */
052: public static CSSHandler parse(File file) throws CSSException {
053: try {
054: mLogger.info("creating CSSHandler");
055: CSSHandler handler = new CSSHandler(file);
056: Parser parser = mCSSParserFactory.makeParser();
057: parser.setDocumentHandler(handler);
058: parser.setErrorHandler(handler);
059: parser.parseStyleSheet(handler.getInputSource());
060: return handler;
061: } catch (CSSParseException e) {
062: mLogger.error("got css parse exception");
063: throw e;
064: } catch (CSSException e) {
065: mLogger.error("got css exception");
066: throw e;
067: } catch (Exception e) {
068: mLogger.error("exception while parsing CSS");
069: throw new CSSException(e.getMessage());
070: }
071: }
072:
073: /**
074: * Entry point to creating a CSSHandler from just a string of inlined css
075: * @param cssString a string containing the entire CSS to parse
076: */
077: public static CSSHandler parse(String cssString)
078: throws CSSException {
079: try {
080: mLogger
081: .debug("entering CSSHandler.parse with inline string");
082: CSSHandler handler = new CSSHandler(cssString);
083: Parser parser = mCSSParserFactory.makeParser();
084: parser.setDocumentHandler(handler);
085: parser.setErrorHandler(handler);
086: java.io.Reader cssReader = new java.io.StringReader(
087: cssString);
088: InputSource inputSource = new InputSource(cssReader);
089: parser.parseStyleSheet(inputSource);
090: return handler;
091: } catch (CSSParseException e) {
092: mLogger.error("got css parse exception on inline css, "
093: + e.getMessage());
094: throw e;
095: } catch (CSSException e) {
096: mLogger.error("got css exception on inline css"
097: + e.getMessage());
098: throw e;
099: } catch (Exception e) {
100: mLogger.error("exception while parsing inline css");
101: throw new CSSException(e.getMessage());
102: }
103: }
104:
105: //==========================================================================
106: // instance
107: //==========================================================================
108:
109: /** The css file itself */
110: File mFile;
111:
112: /** List of Rule instances. */
113: public List mRuleList;
114:
115: /** Used as a map of style properties for selector group being parsed. */
116: Map mStyleMap;
117:
118: /** A list of CSS files separated by two file separators characters. */
119: String mFileDependencies;
120:
121: /** protected constructor */
122: CSSHandler(File file) {
123: mFile = file;
124: mRuleList = new Vector();
125: mFileDependencies = getFullPath();
126: }
127:
128: /** protected constructor */
129: CSSHandler(String cssString) {
130: mFile = null; // No file associated with inline css
131: mRuleList = new Vector();
132: mFileDependencies = ""; // inline css doesn't add any file dependencies
133: }
134:
135: /** Helper function to log and throw an error. */
136: void throwCSSException(String errMsg) throws CSSException {
137: mLogger.error(errMsg);
138: throw new CSSException(errMsg);
139: }
140:
141: /** @return full path to CSS file */
142: String getFullPath() {
143: try {
144: return mFile.getCanonicalPath();
145: } catch (IOException e) {
146: mLogger.error("Exception getting canonical path of: "
147: + mFile + ", " + e.getMessage());
148: return "";
149: }
150: }
151:
152: /** @return InputSource object pointing to the CSS file. */
153: InputSource getInputSource() throws FileNotFoundException {
154: InputSource is = new InputSource(new FileReader(mFile));
155: // is.setEncoding("ISO-8859-1");
156: return is;
157: }
158:
159: /**
160: * Get a string containing a list CSS files required by the parse. Includes
161: * imported CSS files.
162: * @return a list of CSS files separated by two file separators characters.
163: */
164: public String getFileDependencies() {
165: return mFileDependencies;
166: }
167:
168: //--------------------------------------------------------------------------
169: // Implement DocumentHandler interface.
170: //--------------------------------------------------------------------------
171:
172: public void startSelector(SelectorList selectors)
173: throws CSSException {
174: mStyleMap = new HashMap();
175: }
176:
177: public void endSelector(SelectorList selectors) throws CSSException {
178: if (mStyleMap.size() != 0) {
179: for (int i = 0; i < selectors.getLength(); i++) {
180: mRuleList.add(new Rule(selectors.item(i), mStyleMap));
181: }
182: }
183: }
184:
185: // Unicode characters
186: Pattern hexEscapePattern = Pattern
187: .compile("\\\\([0-9a-fA-F]{1,6})(\r\n|[ \t\r\n\f])?");
188: // The only characters that need to be escaped in javascript
189: Pattern charEscapePattern = Pattern
190: .compile("\\\\([^\r\n\f0-9a-fA-F])");
191:
192: // Convert CSS escapes to Javascript
193: protected String processEscapes(String input) {
194: // Convert unicode escapes to Javascript equivalent (which
195: // means parse the hex escape and insert the corresponding
196: // unicode character in it's place).
197: Matcher m = hexEscapePattern.matcher(input);
198: StringBuffer sb = new StringBuffer();
199: while (m.find()) {
200: // Have I told you how much Java sucks lately?
201: CharArrayWriter u = new CharArrayWriter();
202: u.write(Integer.parseInt(m.group(1), 16));
203: m.appendReplacement(sb, u.toString());
204: }
205: m.appendTail(sb);
206: input = sb.toString();
207: // Convert character escapes to Javascript (which means just
208: // remove the escape, since none of those chars need escaping
209: // in Javascript).
210: input = charEscapePattern.matcher(input).replaceAll("$1");
211: return input;
212: }
213:
214: public void property(String name, LexicalUnit value,
215: boolean important) throws CSSException {
216: mStyleMap.put(processEscapes(name), new StyleProperty(
217: luToString(value), important));
218: }
219:
220: public void importStyle(String uri, SACMediaList media,
221: String defaultNamespaceURI) throws CSSException {
222: try {
223: CSSHandler handler = new CSSHandler(new File(uri));
224: Parser parser = mCSSParserFactory.makeParser();
225: parser.setDocumentHandler(handler);
226: parser.parseStyleSheet(handler.getInputSource());
227: } catch (Exception e) {
228: mLogger.error("Exception", e);
229: throw new CSSException(e.getMessage());
230: }
231: }
232:
233: public void startDocument(InputSource source) throws CSSException {
234: /* ignore */
235: }
236:
237: public void endDocument(InputSource source) throws CSSException {
238: /* ignore */
239: }
240:
241: public void comment(String text) {
242: /* ignore */
243: }
244:
245: public void startFontFace() throws CSSException {
246: throwCSSException("start font face unsupported");
247: }
248:
249: public void endFontFace() throws CSSException {
250: throwCSSException("end font face unsupported");
251: }
252:
253: public void startMedia(SACMediaList media) throws CSSException {
254: throwCSSException("start media unsupported");
255: }
256:
257: public void endMedia(SACMediaList media) throws CSSException {
258: throwCSSException("start media unsupported");
259: }
260:
261: public void startPage(String name, String pseudoPage)
262: throws CSSException {
263: throwCSSException("start page unsupported: " + name + ", "
264: + pseudoPage);
265: }
266:
267: public void endPage(String name, String pseudoPage)
268: throws CSSException {
269: throwCSSException("end page unsupported: " + name + ", "
270: + pseudoPage);
271: }
272:
273: public void namespaceDeclaration(String prefix, String uri)
274: throws CSSException {
275: throwCSSException("namespace declaration unsupported: "
276: + prefix + ":" + uri);
277: }
278:
279: public void ignorableAtRule(String atRule) throws CSSException {
280: throwCSSException("ignorable at rule: " + atRule);
281: }
282:
283: //--------------------------------------------------------------------------
284: // helper methods
285: //--------------------------------------------------------------------------
286:
287: /** @return an RGB formatted hex string like #FFFFFF. */
288: String getRGBString(LexicalUnit lu) {
289: int rr = lu.getLexicalUnitType() == LexicalUnit.SAC_PERCENTAGE ? (int) Math
290: .round(Math.min(lu.getFloatValue() * 255.0 / 100.0,
291: 255.0))
292: : lu.getIntegerValue();
293: lu = lu.getNextLexicalUnit().getNextLexicalUnit(); // skip comma
294: int gg = lu.getLexicalUnitType() == LexicalUnit.SAC_PERCENTAGE ? (int) Math
295: .round(Math.min(lu.getFloatValue() * 255.0 / 100.0,
296: 255.0))
297: : lu.getIntegerValue();
298: lu = lu.getNextLexicalUnit().getNextLexicalUnit(); // skip comma
299: int bb = lu.getLexicalUnitType() == LexicalUnit.SAC_PERCENTAGE ? (int) Math
300: .round(Math.min(lu.getFloatValue() * 255.0 / 100.0,
301: 255.0))
302: : lu.getIntegerValue();
303:
304: return "0x" + (rr < 16 ? "0" : "")
305: + Integer.toHexString(rr).toUpperCase()
306: + (gg < 16 ? "0" : "")
307: + Integer.toHexString(gg).toUpperCase()
308: + (bb < 16 ? "0" : "")
309: + Integer.toHexString(bb).toUpperCase();
310: }
311:
312: /**
313: * Convert LexicalUnit to a Javascript value (represented
314: * as a String).
315: */
316: String luToString(LexicalUnit lu) {
317: String str = "";
318: switch (lu.getLexicalUnitType()) {
319:
320: case LexicalUnit.SAC_ATTR:
321: str = "function () { return this['"
322: + processEscapes(lu.getStringValue()) + "']; }";
323: break;
324:
325: case LexicalUnit.SAC_IDENT:
326: str = "function () { return global['"
327: + processEscapes(lu.getStringValue()) + "']; }";
328: break;
329:
330: case LexicalUnit.SAC_STRING_VALUE:
331: str = "\"" + processEscapes(lu.getStringValue()) + "\"";
332: break;
333:
334: case LexicalUnit.SAC_URI:
335: // url needs to be defined when this is evaluated
336: str = "url(\"" + lu.getStringValue() + "\")";
337: break;
338:
339: case LexicalUnit.SAC_CENTIMETER:
340: case LexicalUnit.SAC_DEGREE:
341: case LexicalUnit.SAC_DIMENSION:
342: case LexicalUnit.SAC_EM:
343: case LexicalUnit.SAC_EX:
344: case LexicalUnit.SAC_GRADIAN:
345: case LexicalUnit.SAC_HERTZ:
346: case LexicalUnit.SAC_INCH:
347: case LexicalUnit.SAC_KILOHERTZ:
348: case LexicalUnit.SAC_MILLIMETER:
349: case LexicalUnit.SAC_MILLISECOND:
350: case LexicalUnit.SAC_PICA:
351: case LexicalUnit.SAC_PIXEL:
352: case LexicalUnit.SAC_POINT:
353: case LexicalUnit.SAC_RADIAN:
354: case LexicalUnit.SAC_SECOND:
355: // Dimensioned values are stored as strings for now, but
356: // perhaps they should be converted to
357: // length/frequency/angle in the appropriate base unit?
358: float val = lu.getFloatValue();
359: str = "\"" + (val == 0 ? "0" : Float.toString(val))
360: + lu.getDimensionUnitText() + "\"";
361: break;
362:
363: case LexicalUnit.SAC_PERCENTAGE:
364: str = Float.toString(lu.getFloatValue() / 100);
365: break;
366:
367: case LexicalUnit.SAC_REAL:
368: str = Float.toString(lu.getFloatValue());
369: break;
370:
371: case LexicalUnit.SAC_INTEGER:
372: str = Integer.toString(lu.getIntegerValue());
373: break;
374:
375: case LexicalUnit.SAC_RGBCOLOR:
376: str = getRGBString(lu.getParameters());
377: break;
378:
379: //----------------------------------------------------------------------
380: // TODO [2005-10-07 pkang]: don't think I'm handling these literal
381: // lexical units correctly. Not worrying for now since we only
382: // support basic values
383: //----------------------------------------------------------------------
384:
385: case LexicalUnit.SAC_INHERIT:
386: str = "inherit";
387: break;
388: case LexicalUnit.SAC_OPERATOR_COMMA:
389: str = ",";
390: break;
391: case LexicalUnit.SAC_OPERATOR_EXP:
392: str = "^";
393: break;
394: case LexicalUnit.SAC_OPERATOR_GE:
395: str = ">=";
396: break;
397: case LexicalUnit.SAC_OPERATOR_GT:
398: str = ">";
399: break;
400: case LexicalUnit.SAC_OPERATOR_LE:
401: str = "<=";
402: break;
403: case LexicalUnit.SAC_OPERATOR_LT:
404: str = "<";
405: break;
406: case LexicalUnit.SAC_OPERATOR_MINUS:
407: str = "-";
408: break;
409: case LexicalUnit.SAC_OPERATOR_MOD:
410: str = "%";
411: break;
412: case LexicalUnit.SAC_OPERATOR_MULTIPLY:
413: str = "*";
414: break;
415: case LexicalUnit.SAC_OPERATOR_PLUS:
416: str = "+";
417: break;
418: case LexicalUnit.SAC_OPERATOR_SLASH:
419: str = "/";
420: break;
421: case LexicalUnit.SAC_OPERATOR_TILDE:
422: str = "~";
423: break;
424:
425: // Can we support these as functions?
426: case LexicalUnit.SAC_COUNTER_FUNCTION:
427: throwCSSException("SAC_COUNTER_FUNCTION unsupported");
428: case LexicalUnit.SAC_COUNTERS_FUNCTION:
429: throwCSSException("SAC_COUNTERS_FUNCTION unsupported");
430: case LexicalUnit.SAC_RECT_FUNCTION:
431: case LexicalUnit.SAC_FUNCTION:
432: // Function needs to be defined when this is evaluated
433: str = lu.getFunctionName() + "("
434: + luToString(lu.getParameters()) + ")";
435: break;
436: case LexicalUnit.SAC_SUB_EXPRESSION:
437: throwCSSException("SAC_SUB_EXPRESSION unsupported");
438: case LexicalUnit.SAC_UNICODERANGE:
439: throwCSSException("SAC_UNICODERANGE unsupported");
440: default:
441: throwCSSException("unsupported lexical unit type: "
442: + lu.getLexicalUnitType());
443: }
444: return str;
445: }
446:
447: public void warning(CSSParseException e) throws CSSException {
448: mLogger.error("warning ", e);
449: throw e;
450: }
451:
452: public void error(CSSParseException e) throws CSSException {
453: mLogger.error("error ", e);
454: throw e;
455: }
456:
457: public void fatalError(CSSParseException e) throws CSSException {
458: mLogger.error("fatal error while parsing css", e);
459: throw e;
460: }
461: }
|