001: /* *****************************************************************************
002: * StyleSheetCompiler.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.compiler;
011:
012: import org.openlaszlo.css.*;
013: import org.openlaszlo.sc.ScriptCompiler;
014: import org.openlaszlo.utils.FileUtils;
015: import org.w3c.css.sac.*;
016: import java.io.*;
017: import java.util.*;
018: import java.text.MessageFormat;
019:
020: import org.jdom.Element;
021: import org.apache.log4j.*;
022:
023: /** Compiler for <code>stylesheet</code> elements.
024: *
025: * @author Benjamin Shine
026: */
027: class StyleSheetCompiler extends LibraryCompiler {
028: /** Logger */
029: private static Logger mLogger = Logger
030: .getLogger(StyleSheetCompiler.class);
031:
032: private static final String SRC_ATTR_NAME = "src";
033:
034: StyleSheetCompiler(CompilationEnvironment env) {
035: super (env);
036: }
037:
038: /** Returns true iff this class applies to this element.
039: * @param element an element
040: * @return see doc
041: */
042: static boolean isElement(Element element) {
043: return element.getName().intern() == "stylesheet";
044: }
045:
046: public void compile(Element element) {
047: try {
048: mLogger.info("StyleSheetCompiler.compile called!");
049:
050: if (!element.getChildren().isEmpty()) {
051: throw new CompilationError(
052: "<stylesheet> elements can't have children",
053: element);
054: }
055:
056: String pathname = null;
057: String stylesheetText = element.getText();
058: String src = element.getAttributeValue(SRC_ATTR_NAME);
059:
060: if (src != null) {
061: mLogger.info("reading in stylesheet from src=" + src);
062: // Find the css file
063: // Using the FileResolver accomplishes two nice things:
064: // 1, it searches the standard directory include paths
065: // including the application directory for the css file.
066: // 2, it adds the css file to the dependencies for the
067: // current application. This makes the application be
068: // recompiled if the css file changes.
069: // This fixes LPP-2733 [bshine 10.20.06]
070:
071: String base = mEnv.getApplicationFile().getParent();
072:
073: // [bshine 12.29.06] For LPP-2974, we also have to
074: // check for the css file relative to the file which is including it.
075: // First try to find the css file as a sibling of this source file
076: String sourceDir = new File(Parser
077: .getSourcePathname(element)).getParent();
078: File resolvedFile = mEnv.resolve(src, sourceDir);
079:
080: // If our first try at finding the css file doesn't find it as a sibling,
081: // try to resolve relative to the application source file.
082: if (!resolvedFile.exists()) {
083: resolvedFile = mEnv.resolve(src, base);
084: if (resolvedFile.exists()) {
085: mLogger
086: .info("Resolved css file to a file that exists!");
087: } else {
088: mLogger
089: .error("Could not resolve css file to a file that exists.");
090: throw new CompilationError(
091: "Could not find css file " + src);
092: }
093: }
094:
095: // Actually parse and compile the stylesheet! W00t!
096: CSSHandler fileHandler = CSSHandler.parse(resolvedFile);
097: this .compile(fileHandler, element);
098:
099: } else if (stylesheetText != null
100: && (!"".equals(stylesheetText))) {
101: mLogger.info("inline stylesheet");
102: CSSHandler inlineHandler = CSSHandler
103: .parse(stylesheetText);
104: this .compile(inlineHandler, element);
105: //
106: } else {
107: // TODO: i18n errors
108: throw new CompilationError(
109: "<stylesheet> element must have either src attribute or inline text. This has neither.",
110: element);
111: }
112:
113: } catch (CompilationError e) {
114: // If there was an error compiling a stylesheet, we report
115: // it as a compilation error, and fail the compile.
116: // Fixes LPP-2734 [bshine 10.20.06]
117: mLogger.error("Error compiling StyleSheet element: "
118: + element);
119: throw e;
120: } catch (IOException e) {
121: // This exception indicates there was a problem reading the
122: // CSS from the file
123: mLogger.error("IO error compiling StyleSheet: " + element);
124: throw new CompilationError(
125: "IO error, can't find source file for <stylesheet> element.",
126: element);
127: } catch (CSSParseException e) {
128: // CSSParseExceptions provide a line number and URI,
129: // as well as a helpful message
130: // Fixes LPP-2734 [bshine 10.20.06]
131: String message = "Error parsing css file at line "
132: + e.getLineNumber() + ", " + e.getMessage();
133:
134: mLogger.error(message);
135: throw new CompilationError(message);
136: } catch (CSSException e) {
137: // CSSExceptions don't provide a line number, just a message
138: // Fixes LPP-2734 [bshine 10.20.06]
139: mLogger.error("Error compiling css: " + element);
140: throw new CompilationError(
141: "Error compiling css, no line number available: "
142: + e.getMessage());
143: } catch (Exception e) {
144: // This catch clause will catch disastrous errors; normal expected
145: // css-related errors are handled with the more specific catch clauses
146: // above.
147: mLogger.error("Exception compiling css: " + element + ", "
148: + e.getMessage());
149: throw new CompilationError("Error compiling css. "
150: + e.getMessage());
151: }
152:
153: }
154:
155: void compile(CSSHandler handler, Element element)
156: throws CompilationError {
157: mLogger.debug("compiling CSSHandler using new unique names");
158: String script = "";
159: for (int i = 0; i < handler.mRuleList.size(); i++) {
160: Rule rule = (Rule) handler.mRuleList.get(i);
161: // TODO: move this out out global scope [bshine 9.17.06]
162: String curRuleName = "__cssRule" + mEnv.uniqueName();
163: script += "var " + curRuleName
164: + " = new LzCSSStyleRule(); \n";
165:
166: String curRuleSelector = buildSelector(rule.getSelector());
167: script += curRuleName + ".selector = " + curRuleSelector
168: + ";\n";
169: String curRuleProperties = buildPropertiesJavascript(rule);
170: script += curRuleName + ".properties = "
171: + curRuleProperties + ";\n";
172: script += " LzCSSStyle._addRule( " + curRuleName + " ); \n";
173: mLogger.debug("created rule " + curRuleName);
174: }
175: mLogger.debug("whole stylesheet as css " + script + "\n\n");
176: mEnv.compileScript(CompilerUtils.sourceLocationDirective(
177: element, true)
178: +
179: // NOTE [2007-06-02 bshine] This semicolon is needed
180: // to work around bug LPP-4083, javascript compiler
181: // doesn't emit a semicolon somewhere
182: ";" +
183: // NOTE: [2007-02-11 ptw] It is crucial
184: // that this be terminated with a `;` so
185: // that it is a statement, not an
186: // expression.
187: " (function() { " + script + "})();", element);
188: }
189:
190: String buildSelector(Selector sel) {
191: String selectorString = "\"selector_not_handled\"";
192:
193: switch (sel.getSelectorType()) {
194: case Selector.SAC_ELEMENT_NODE_SELECTOR:
195: // This selector matches only tag type
196: ElementSelector es = (ElementSelector) sel;
197: selectorString = buildElementSelectorJS(es.getLocalName());
198: break;
199: case Selector.SAC_CONDITIONAL_SELECTOR:
200: // This selector matches all the interesting things:
201: // #myId
202: // [someattr="someval"]
203: // simple[role="private"]
204: ConditionalSelector cs = (ConditionalSelector) sel;
205: // Take care of the simple selector part of this
206: selectorString = buildConditionalSelectorJS(cs
207: .getCondition(), cs.getSimpleSelector());
208: break;
209: case Selector.SAC_DESCENDANT_SELECTOR:
210: DescendantSelector ds = (DescendantSelector) sel;
211: selectorString = buildDescendantSelector(ds);
212: break;
213: default:
214: selectorString = "unknown_selector"
215: + Integer.toString(sel.getSelectorType());
216: }
217:
218: return selectorString;
219: }
220:
221: String buildElementSelectorJS(String localName) {
222: return "\"" + localName + "\"";
223: }
224:
225: String buildConditionalSelectorJS(Condition cond,
226: SimpleSelector simpleSelector) {
227: mLogger.debug("Conditional selector: " + cond.toString());
228: String condString = "no_match";
229: switch (cond.getConditionType()) {
230: case Condition.SAC_ID_CONDITION: /* #id */
231: AttributeCondition idCond = (AttributeCondition) cond;
232: condString = "\"#" + idCond.getValue() + "\""; // should be the id specified
233: break;
234:
235: case Condition.SAC_ATTRIBUTE_CONDITION: // [attr] or [attr="val"] or elem[attr="val"]
236: mLogger.debug("Attribute condition");
237: AttributeCondition attrCond = (AttributeCondition) cond;
238: String name = attrCond.getLocalName();
239: String value = attrCond.getValue();
240: condString = "{ attrname: \"" + name + "\", attrvalue: \""
241: + value + "\"";
242: // The simple selector is the element part of the selector, ie,
243: // foo in foo[bar="baz"]. If there is no element part of the selector, ie
244: // [bar="lum"] then batik gives us a non-null SimpleSelector with a
245: // localName of the null string. We don't write out the simple selector if
246: // it's not specified.
247: if (simpleSelector != null) {
248: mLogger.debug("simple selector:"
249: + simpleSelector.toString());
250: if (simpleSelector.getSelectorType() == Selector.SAC_ELEMENT_NODE_SELECTOR) {
251:
252: ElementSelector es = (ElementSelector) simpleSelector;
253: String simpleSelectorString = es.getLocalName();
254: // Discard the simple selector if it isn't specified
255: if (simpleSelectorString != null)
256: condString += ", simpleselector: \""
257: + simpleSelectorString + "\"";
258: } else {
259: mLogger.error("Can't handle CSS selector "
260: + simpleSelector.toString());
261: }
262: }
263:
264: condString += "}";
265: mLogger.debug("Cond string: " + condString);
266: break;
267: default:
268: }
269: return condString;
270: }
271:
272: /**
273: * Build a string holding the javascript to create the selector at runtime, where
274: the selector is a descendant selector, ie
275: E F
276: would be
277: descendantrule.selector = [
278: "E",
279: "F" ];
280: The selector is specified as an array of selectors, ancestor first.
281: */
282: String buildDescendantSelector(DescendantSelector ds) {
283: // We need the simple selector and the ancestor selector
284: SimpleSelector ss = ds.getSimpleSelector();
285: Selector ancestorsel = ds.getAncestorSelector();
286: String str = "[";
287:
288: // If this is complicated, it will be [ "something", "complicated" ]
289: // Strip excessive square brackets. This lets us pretend to unroll
290: // recursive selectors into a list of selectors.
291: // This is a cheap way to get deep selectors.
292: String ancestorselstr = buildSelector(ancestorsel);
293: ancestorselstr = ancestorselstr.replace('[', ' ');
294: ancestorselstr = ancestorselstr.replace(']', ' ');
295: str += ancestorselstr;
296: str += ", ";
297: str += buildSelector(ss);
298:
299: str += "]";
300: // mLogger.error("Here's the whole descendant selector:" + str);
301: return str;
302: }
303:
304: /**
305: * Build a string holding the javascript to create the rule's properties attribute.
306: * This should just be a standard javascript object composed of attributes and values,
307: * wrapped in curly quotes. Escape the quotes for attributes' values.
308: * for example "{ width: 500, occupation: \"pet groomer and holistic veterinarian\",
309: miscdata: \"spends most days indoors\"}""
310: */
311:
312: String buildPropertiesJavascript(Rule rule) {
313: /*
314: String props = "{ width: 500, occupation: \"pet groomer and holistic veterinarian\"," +
315: " miscdata: \"spends most days indoors\"} ";
316: */
317:
318: StringWriter result = new StringWriter();
319: try {
320: Map properties = rule.getStyleMap();
321: ScriptCompiler.writeObject(properties, result);
322: } catch (IOException e) {
323: throw new CompilationError("IO error writing property map");
324: }
325: return result.toString();
326: }
327: }
|