coliver 2003/04/15 13:31:19
Modified: src/scratchpad/src/org/apache/cocoon/generation JexlTemplate.java Log: added <define> tag (macro facility), and changed to enclose XPath EL in #{} rather than {} (which is too big a pain to escape) Revision Changes Path 1.2 +255 -101 cocoon-2.1/src/scratchpad/src/org/apache/cocoon/generation/JexlTemplate.java Index: JexlTemplate.java =================================================================== RCS file: /home/cvs/cocoon-2.1/src/scratchpad/src/org/apache/cocoon/generation/JexlTemplate.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- JexlTemplate.java 15 Apr 2003 06:52:28 -0000 1.1 +++ JexlTemplate.java 15 Apr 2003 20:31:19 -0000 1.2 @@ -78,9 +78,6 @@ import java.beans.PropertyDescriptor; /** * - * <p>Cocoon [EMAIL PROTECTED] Generator} that produces dynamic XML SAX events - * fom an XML template file.</p> - * * <p>Provides a tag library with embedded JSTL and XPath expression substitution * to access data sent by Cocoon flowscripts.</p> * The embedded expression language allows a page author to access an @@ -88,13 +85,13 @@ * <p><pre> * <site signOn="${accountForm.signOn}"> * </pre></p> - * <p>Embedded Jexl expressions are contained in ${}.</p> - * <p>Embedded XPath expressions are contained in {}.</p> - * <p>Note that since this generator uses <a href="http://jakarta.apache.org/commons/jxpath">Apache JXPath</a> and <a href="http://jakarta.apache.org/commons/jexl">Apache Jexl, the referenced + * <p>Embedded Jexl expressions are contained in <code>${}</code>.</p> + * <p>Embedded XPath expressions are contained in <code>#{}</code>.</p> + * <p>Note that since this generator uses <a href="http://jakarta.apache.org/commons/jxpath">Apache JXPath</a> and <a href="http://jakarta.apache.org/commons/jexl">Apache Jexl</a>, the referenced * objects may be Java Beans, DOM, JDOM, or JavaScript objects from a * Flowscript. The current Web Continuation from the Flowscript * is also available as an variable named <code>continuation</code>. You would - * typically access its id: + * typically access its <code>id</code>: * <p><pre> * <form action="${continuation.id}"> * </pre></p> @@ -125,7 +122,7 @@ * </otherwise> * </choose> * </pre></p> - * <p>The <code>value-of</code> tag evaluates an XPath expression and outputs + * <p>The <code>out</code> tag evaluates an expression and outputs * the result of the evaluation:</p> * <p><pre> * <out value="Expression"/> @@ -135,7 +132,10 @@ * <p><pre> * <forEach var="name" items="Expression" begin="n" end="n" step="n"> * body - * </for-each> + * </forEach> + * <forEach select="XPathExpression" begin="n" end="n" step="n"> + * body + * </forEach> * </pre></p> * * @@ -421,9 +421,38 @@ } } - static class MyVariables implements Variables { + static class MyJexlContext + extends HashMap implements JexlContext { + public Map getVars() { + return this; + } + public void setVars(Map map) { + putAll(map); + } + public Object get(Object key) { + Object result = super.get(key); + if (result != null) { + return result; + } + MyJexlContext c = closure; + for (; c != null; c = c.closure) { + result = c.get(key); + if (result != null) { + return result; + } + } + return result; + } + MyJexlContext closure; + MyJexlContext() { + } + MyJexlContext(MyJexlContext closure) { + this.closure = closure; + } + } - Map myVariables = new HashMap(); + static class MyVariables implements Variables { + Map localVariables = new HashMap(); static final String[] VARIABLES = new String[] { "continuation", @@ -457,7 +486,7 @@ return true; } } - return myVariables.containsKey(varName); + return localVariables.containsKey(varName); } public Object getVariable(String varName) { @@ -476,15 +505,15 @@ } else if (varName.equals("parameters")) { return parameters; } - return myVariables.get(varName); + return localVariables.get(varName); } public void declareVariable(String varName, Object value) { - myVariables.put(varName, value); + localVariables.put(varName, value); } public void undeclareVariable(String varName) { - myVariables.remove(varName); + localVariables.remove(varName); } } @@ -512,11 +541,12 @@ final static String OTHERWISE = "otherwise"; final static String OUT = "out"; final static String IMPORT = "import"; + final static String DEFINE = "define"; /** * Compile a single Jexl expr (contained in ${}) or XPath expression - * (contained in {}) + * (contained in #{}) */ private Object compileExpr(String inStr) throws Exception { @@ -544,22 +574,14 @@ expr.append(c); } } else { - if (c == '$') { + if (c == '$' || c == '#') { ch = in.read(); if (ch == '{') { inExpr = true; - xpath = false; - continue; - } - } else if (c == '{') { - ch = in.read(); - if (ch != -1) { - inExpr = true; - xpath = true; - expr.append((char)ch); + xpath = c == '#'; continue; } - } + } // hack: invalid expression? // just return the original and swallow exception return inStr; @@ -626,7 +648,7 @@ boolean inExpr = false; boolean xpath = false; try { - while ((ch = in.read()) != -1) { + top: while ((ch = in.read()) != -1) { char c = (char)ch; if (inExpr) { if (c == '}') { @@ -666,44 +688,30 @@ } else { buf.append((char)ch); } - } else { - if (c == '$') { + } else if (c == '$' || c == '#') { + while (c == '$' || c == '#') { ch = in.read(); if (ch == '{') { - xpath = false; + xpath = c == '#'; inExpr = true; if (buf.length() > 0) { char[] charArray = - new char[buf.length()]; + new char[buf.length()]; buf.getChars(0, buf.length(), charArray, 0); substitutions.add(charArray); buf.setLength(0); } - continue; - } - } else if (c == '{') { - ch = in.read(); - if (ch != -1) { - if (buf.length() > 0) { - char[] charArray = - new char[buf.length()]; - - buf.getChars(0, buf.length(), - charArray, 0); - substitutions.add(charArray); - buf.setLength(0); - } - buf.append((char)ch); - inExpr = true; - xpath = true; - continue; + continue top; + } else if (ch == -1) { + break; } + buf.append(c); + c = (char)ch; } - } - if (ch != -1) { - buf.append((char)ch); + } else { + buf.append(c); } } } @@ -852,6 +860,7 @@ this.namespaceURI = namespaceURI; this.localName = localName; this.raw = raw; + this.qname = "{"+namespaceURI+"}"+localName; StringBuffer buf = new StringBuffer(); for (int i = 0, len = attrs.getLength(); i < len; i++) { String uri = attrs.getURI(i); @@ -866,7 +875,7 @@ List substEvents = new LinkedList(); boolean xpath = false; try { - while ((ch = in.read()) != -1) { + top: while ((ch = in.read()) != -1) { char c = (char)ch; if (inExpr) { if (c == '}') { @@ -903,33 +912,25 @@ buf.append((char)ch); } } else { - if (c == '$') { - ch = in.read(); - if (ch == '{') { - if (buf.length() > 0) { - substEvents.add(new Literal(buf.toString())); - buf.setLength(0); + if (c == '$' || c == '#') { + while (c == '$' || c == '#') { + ch = in.read(); + if (ch == '{') { + if (buf.length() > 0) { + substEvents.add(new Literal(buf.toString())); + buf.setLength(0); + } + inExpr = true; + xpath = c == '#'; + continue top; + } else if (ch == -1) { + break; } - inExpr = true; - xpath = false; - continue; + buf.append(c); + c = (char)ch; } - } else if (c == '{') { - ch = in.read(); - if (ch != -1) { - if (buf.length() > 0) { - substEvents.add(new Literal(buf.toString())); - buf.setLength(0); - } - buf.append((char)ch); - inExpr = true; - xpath = true; - continue; - } - buf.append('{'); - } - if (ch != -1) { - buf.append((char)ch); + } else { + buf.append(c); } } } @@ -970,7 +971,9 @@ final String namespaceURI; final String localName; final String raw; + final String qname; final List attributeEvents = new LinkedList(); + EndElement endElement; } class StartForEach extends Event { @@ -1167,6 +1170,29 @@ } } + class StartDefine extends Event { + StartDefine(Locator location, String namespace, String name, + Map formalParameters) { + super(location); + this.namespace = namespace; + this.name = name; + this.qname = "{"+namespace+"}"+name; + this.parameters = formalParameters; + } + final String namespace; + final String name; + final String qname; + final Map parameters; + EndDefine endDefine; + } + + class EndDefine extends Event { + EndDefine(Locator location) { + super(location); + } + } + + class Parser implements LexicalHandler, ContentHandler { StartDocument startEvent; @@ -1255,13 +1281,17 @@ StartTemplate startTemplate = (StartTemplate)start; newEvent = startTemplate.endTemplate = new EndTemplate(locator); + } else if (start instanceof StartDefine) { + StartDefine startDefine = (StartDefine)start; + newEvent = + startDefine.endDefine = new EndDefine(locator); } else { throw new SAXParseException("unrecognized tag: " + localName, locator, null); } } else { StartElement startElement = (StartElement)start; - newEvent = new EndElement(locator, - startElement); + newEvent = startElement.endElement = + new EndElement(locator, startElement); } addEvent(newEvent); } @@ -1341,7 +1371,8 @@ StartChoose startChoose = new StartChoose(locator); newEvent = startChoose; } else if (localName.equals(WHEN)) { - if (!(stack.peek() instanceof StartChoose)) { + if (stack.size() == 0 || + !(stack.peek() instanceof StartChoose)) { throw new SAXParseException("<when> must be within <choose>", locator, null); } String test = attrs.getValue("test"); @@ -1357,19 +1388,20 @@ StartWhen startWhen = new StartWhen(locator, expr); newEvent = startWhen; } else if (localName.equals(OUT)) { - String select = attrs.getValue("value"); - if (select == null) { + String value = attrs.getValue("value"); + if (value == null) { throw new SAXParseException("out: \"value\" is required", locator, null); } Object expr; try { - expr = compileExpr(select); + expr = compileExpr(value); } catch (Exception e) { throw new SAXParseException("out: \"value\": " + e.getMessage(), locator, null); } newEvent = new StartOut(locator, expr); } else if (localName.equals(OTHERWISE)) { - if (!(stack.peek() instanceof StartChoose)) { + if (stack.size() == 0 || + !(stack.peek() instanceof StartChoose)) { throw new SAXParseException("<otherwise> must be within <choose>", locator, null); } StartOtherwise startOtherwise = @@ -1389,6 +1421,32 @@ StartIf startIf = new StartIf(locator, expr); newEvent = startIf; + } else if (localName.equals(DEFINE)) { + // <define namespace="a" name="b"> + // body + // </define> + String namespace = attrs.getValue(JEXL_NS, "namespace"); + if (namespace == null) { + namespace = ""; + } + String name = attrs.getValue(JEXL_NS, "name"); + if (name == null) { + throw new SAXParseException("define: template \"name\" is required", locator, null); + } + Map formalParams = new HashMap(); + for (int i = 0, len = attrs.getLength(); i < len; i++) { + String uri = attrs.getURI(i); + if (uri.equals("")) { + String n = attrs.getLocalName(i); + String v = attrs.getValue(i); + if (v == null) v = ""; + formalParams.put(n, v); + } + } + StartDefine startDefine = + new StartDefine(locator, namespace, name, + formalParams); + newEvent = startDefine; } else if (localName.equals(IMPORT)) { // <import uri="${root}/foo/bar.xml" select="{.}"/> // Allow expression substituion in "uri" attribute @@ -1474,18 +1532,20 @@ private XMLConsumer consumer; private JXPathContext jxpathContext; - private JexlContext jexlContext; + private MyJexlContext globalJexlContext; private Variables variables; private static Map cache = new HashMap(); private Source inputSource; + private Map definitions; public void recycle() { super.recycle(); consumer = null; jxpathContext = null; - jexlContext = null; + globalJexlContext = null; variables = null; inputSource = null; + definitions = null; } public void setup(SourceResolver resolver, Map objectModel, @@ -1518,6 +1578,7 @@ ObjectModelHelper.getResponse(objectModel), ObjectModelHelper.getContext(objectModel), parameters); + definitions = new HashMap(); } private void setContexts(Object contextObject, @@ -1574,9 +1635,10 @@ } } jxpathContext = jxpathContextFactory.newContext(null, contextObject); - jexlContext = JexlHelper.createContext(); - jexlContext.setVars(map); - map = jexlContext.getVars(); + jxpathContext.setVariables(variables); + globalJexlContext = new MyJexlContext(); + globalJexlContext.setVars(map); + map = globalJexlContext.getVars(); map.put("flowContext", contextObject); map.put("continuation", kont); map.put("request", request); @@ -1605,7 +1667,7 @@ cache.put(inputSource.getURI(), startEvent); } } - execute(jexlContext, jxpathContext, startEvent, null); + execute(globalJexlContext, jxpathContext, startEvent, null); } final static char[] EMPTY_CHARS = "".toCharArray(); @@ -1644,7 +1706,7 @@ } } - private void execute(JexlContext jexlContext, + private void execute(MyJexlContext jexlContext, JXPathContext jxpathContext, Event startEvent, Event endEvent) throws SAXException { @@ -1704,7 +1766,7 @@ StartIf startIf = (StartIf)ev; Object val; try { - val = getValue(startIf.test,jexlContext, jxpathContext); + val = getValue(startIf.test, jexlContext, jxpathContext); } catch (Exception e) { throw new SAXParseException(e.getMessage(), ev.location, @@ -1757,6 +1819,8 @@ int begin = startForEach.begin; int end = startForEach.end; int step = startForEach.step; + MyJexlContext localJexlContext = + new MyJexlContext(jexlContext); for (i = 0; i < begin && iter.hasNext(); i++) { iter.next(); } @@ -1776,15 +1840,14 @@ } else { value = iter.next(); } - JXPathContext newJXPathContext = - jxpathContextFactory.newContext(null, - jxpathContext); - newJXPathContext.setVariables(variables); + JXPathContext localJXPathContext = + jxpathContextFactory.newContext(null, value); + localJXPathContext.setVariables(variables); if (startForEach.var != null) { - jexlContext.getVars().put(startForEach.var, value); + localJexlContext.put(startForEach.var, value); } - execute(jexlContext, - newJXPathContext, + execute(localJexlContext, + localJXPathContext, startForEach.next, startForEach.endForEach); for (int skip = step-1; @@ -1828,6 +1891,93 @@ continue; } else if (ev instanceof StartElement) { StartElement startElement = (StartElement)ev; + StartDefine def = + (StartDefine)definitions.get(startElement.qname); + if (def != null) { + MyVariables vars = + (MyVariables)jxpathContext.getVariables(); + MyJexlContext local = new MyJexlContext(globalJexlContext); + final Map localVariables = vars.localVariables; + vars.localVariables = new HashMap(); + Iterator i = startElement.attributeEvents.iterator(); + while (i.hasNext()) { + AttributeEvent attrEvent = (AttributeEvent)i.next(); + Object defVal; + if (attrEvent.namespaceURI.length() == 0) { + defVal = def.parameters.get(attrEvent.localName); + if (defVal == null) continue; + } + if (attrEvent instanceof CopyAttribute) { + CopyAttribute copy = + (CopyAttribute)attrEvent; + vars.declareVariable(attrEvent.localName, + copy.value); + local.put(attrEvent.localName, + copy.value); + } else if (attrEvent instanceof + SubstituteAttribute) { + SubstituteAttribute substEvent = + (SubstituteAttribute)attrEvent; + if (substEvent.substitutions.size() == 1) { + Subst subst = + (Subst)substEvent.substitutions.get(0); + if (subst instanceof Expression) { + Object val; + Expression expr = (Expression)subst; + try { + val = + getValue(expr.compiledExpression, + jexlContext, + jxpathContext); + } catch (Exception e) { + throw new SAXParseException(e.getMessage(), + ev.location, + e); + } + vars.declareVariable(attrEvent.localName, + val); + local.put(attrEvent.localName, val); + continue; + } + } + StringBuffer buf = new StringBuffer(); + Iterator ii = substEvent.substitutions.iterator(); + while (ii.hasNext()) { + Subst subst = (Subst)ii.next(); + if (subst instanceof Literal) { + Literal lit = (Literal)subst; + buf.append(lit.value); + } else if (subst instanceof Expression) { + Expression expr = (Expression)subst; + Object val; + try { + val = + getValue(expr.compiledExpression, + jexlContext, + jxpathContext); + } catch (Exception e) { + throw new SAXParseException(e.getMessage(), + ev.location, + e); + } + if (val == null) { + val = ""; + } + buf.append(val.toString()); + } + } + vars.declareVariable(attrEvent.localName, + buf.toString()); + local.put(attrEvent.localName, + buf.toString()); + } + } + execute(local, jxpathContext, + def.next, def.endDefine); + vars.localVariables = localVariables; + ev = startElement.endElement.next; + continue; + } Iterator i = startElement.attributeEvents.iterator(); AttributesImpl attrs = new AttributesImpl(); while (i.hasNext()) { @@ -1937,6 +2087,10 @@ consumer.characters(ch, 0, ch.length); } else if (ev instanceof StartTemplate) { // no action + } else if (ev instanceof StartDefine) { + StartDefine startDefine = (StartDefine)ev; + definitions.put(startDefine.qname, startDefine); + ev = startDefine.endDefine.next; } else if (ev instanceof StartImport) { String uri; StartImport startImport = (StartImport)ev;