coliver 2003/04/05 22:41:40
Modified: src/scratchpad/src/org/apache/cocoon/transformation JexlTransformer.java Log: Improved error reporting and added XPath support. Unlike in JSTL, XPath expressions can be used everywhere Jexl expressions can. To make this possible XPath expressions are enclosed in {} rather than ${} Revision Changes Path 1.3 +202 -116 cocoon-2.1/src/scratchpad/src/org/apache/cocoon/transformation/JexlTransformer.java Index: JexlTransformer.java =================================================================== RCS file: /home/cvs/cocoon-2.1/src/scratchpad/src/org/apache/cocoon/transformation/JexlTransformer.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- JexlTransformer.java 2 Apr 2003 21:38:38 -0000 1.2 +++ JexlTransformer.java 6 Apr 2003 06:41:40 -0000 1.3 @@ -85,6 +85,8 @@ import org.apache.commons.jxpath.DynamicPropertyHandler; import org.apache.commons.jxpath.JXPathBeanInfo; import org.apache.commons.jxpath.JXPathContext; +import org.apache.commons.jxpath.Pointer; +import org.apache.commons.jxpath.JXPathContextFactory; import org.apache.commons.jxpath.JXPathIntrospector; import org.apache.commons.jxpath.Variables; import org.apache.excalibur.source.Source; @@ -99,13 +101,16 @@ import org.mozilla.javascript.Wrapper; import org.w3c.dom.DocumentFragment; import org.xml.sax.Attributes; +import org.xml.sax.Locator; import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; import org.xml.sax.helpers.AttributesImpl; /** * Jexl Transformer. * * <p> - * Transformer implementation using Apache Commons Jexl. + * Transformer implementation using Apache Commons Jexl and + * Apache Commons JXPath. * Provides a tag library and expression language similar * to a read-only subset of core JSTL. * </p> @@ -118,6 +123,8 @@ public class JexlTransformer extends AbstractSAXTransformer implements Initializable, Generator { + private static final JXPathContextFactory + jxpathContextFactory = JXPathContextFactory.newInstance(); /** * Jexl Introspector that supports Rhino JavaScript objects * as well as Java Objects @@ -397,7 +404,7 @@ public static final String JEXL_NAMESPACE_URI = "http://cocoon.apache.org/transformation/jexl/1.0"; - public static final String JEXL_FOR_EACH = "forEach"; + public static final String JEXL_CHOOSE = "choose"; public static final String JEXL_WHEN = "when"; public static final String JEXL_WHEN_TEST = "test"; @@ -418,6 +425,7 @@ public static final String JEXL_FOREACH_STEP = "step"; public static final String JEXL_FOREACH_VAR = "var"; public static final String JEXL_FOREACH_VAR_STATUS = "varStatus"; + public static final String JEXL_FOREACH_SELECT = "select"; static { // Hack: there's no _nice_ way to add my introspector to Jexl right now @@ -436,9 +444,10 @@ private WebContinuation kont; JexlContext jexlContext; - JXPathContext jxpathContext; + Stack jxpathContextStack; Iterator foreachIter; + boolean foreachXPath; String foreachVar; int foreachBegin; int foreachEnd; @@ -471,12 +480,20 @@ // Run as a generator for debugging: to get line numbers in error messages private Source inputSource; + Locator locator = null; + + public void setDocumentLocator(Locator loc) { + this.locator = loc; + } public void generate() throws IOException, SAXException, ProcessingException { try { this.resolver.toSAX(this.inputSource, this); } catch (SAXException e) { + if (e instanceof SAXParseException) { + throw e; // keep line number info + } final Exception cause = e.getException(); if( cause != null ) { if ( cause instanceof ProcessingException ) @@ -485,7 +502,7 @@ throw (IOException)cause; if ( cause instanceof SAXException ) throw (SAXException)cause; - throw new ProcessingException("Could not read resource " + throw new ProcessingException("Error reading resource " + this.inputSource.getURI(), cause); } throw e; @@ -529,6 +546,8 @@ kont = (WebContinuation)((Environment)resolver).getAttribute("kont"); chooseStack = new Stack(); ifStack = new Stack(); + jxpathContextStack = new Stack(); + jexlContext = JexlHelper.createContext(); setContexts(bean); getJexlContext().getVars().put("this", bean); getJexlContext().getVars().put("continuation", kont); @@ -538,19 +557,24 @@ * Evaluate a Jexl expr contained in ${} but don't do substitution: * just return its value */ + + private Object eval(String inStr) throws SAXException { + return eval(inStr, false); + } - Object eval(String inStr) /* throws Exception */ { + private Object eval(String inStr, boolean iterate) throws SAXException { try { StringReader in = new StringReader(inStr.trim()); int ch; StringBuffer expr = new StringBuffer(); + boolean xpath = false; boolean inExpr = false; while ((ch = in.read()) != -1) { char c = (char)ch; if (inExpr) { if (c == '}') { String str = expr.toString(); - return getValue(str); + return getValue(str, xpath, iterate); } else if (c == '\\') { ch = in.read(); if (ch == -1) { @@ -568,67 +592,91 @@ inExpr = true; continue; } + } else if (c == '{') { + ch = in.read(); + if (ch != -1) { + inExpr = true; + xpath = true; + expr.append((char)ch); + continue; + } } // hack: invalid expression? // just return the original and swallow exception return inStr; } } - } catch (Exception ignored) { + } catch (IOException ignored) { ignored.printStackTrace(); } return inStr; } /** - * Substitute the values of JEXL expr's (contained in ${}) within attribute values + * Substitute the values of Jexl expr's (contained in ${}) and + * XPath expr's (contained in {}) within attribute values */ - private void substitute(Reader in, Writer out) throws Exception { - int ch; - StringBuffer expr = new StringBuffer(); - boolean inExpr = false; - while ((ch = in.read()) != -1) { - char c = (char)ch; - if (inExpr) { - if (c == '}') { - String str = expr.toString(); - expr.setLength(0); - str = String.valueOf(getValue(str)); - out.write(str); - inExpr = false; - } else if (c == '\\') { - ch = in.read(); - if (ch == -1) { - expr.append('\\'); - } else { - expr.append((char)ch); - } - } else { - expr.append(c); - } - } else { - if (c == '\\') { - ch = in.read(); - if (ch == -1) { - out.write('\\'); + private void substitute(Reader in, Writer out) + throws SAXException { + try { + int ch; + StringBuffer expr = new StringBuffer(); + boolean inExpr = false; + boolean xpath = false; + while ((ch = in.read()) != -1) { + char c = (char)ch; + if (inExpr) { + if (c == '}') { + String str = expr.toString(); + expr.setLength(0); + str = String.valueOf(getValue(str, xpath, false)); + out.write(str); + inExpr = false; + } else if (c == '\\') { + ch = in.read(); + if (ch == -1) { + expr.append('\\'); + } else { + expr.append((char)ch); + } } else { - out.write((char)ch); + expr.append(c); } } else { - if (c == '$') { + if (c == '\\') { ch = in.read(); - if (ch == '{') { - inExpr = true; - continue; + if (ch == -1) { + out.write('\\'); + } else { + out.write((char)ch); + } + } else { + if (c == '$') { + ch = in.read(); + if (ch == '{') { + inExpr = true; + continue; + } + out.write('$'); + } else if (c == '{') { + ch = in.read(); + if (ch != -1) { + expr.append((char)ch); + inExpr = true; + xpath = true; + continue; + } + } + if (ch != -1) { + out.write((char)ch); } - out.write('$'); - } - if (ch != -1) { - out.write((char)ch); } } } + } catch (IOException e) { + throw new CascadingRuntimeException(e.getMessage(), + e); } } @@ -640,15 +688,11 @@ // substitute EL values AttributesImpl impl = new AttributesImpl(attr); for (int i = 0, len = impl.getLength(); i < len; i++) { - try { - String value = impl.getValue(i); - StringReader reader = new StringReader(value); - StringWriter writer = new StringWriter(); - substitute(reader, writer); - impl.setValue(i, writer.toString()); - } catch (Exception exc) { - exc.printStackTrace(); - } + String value = impl.getValue(i); + StringReader reader = new StringReader(value); + StringWriter writer = new StringWriter(); + substitute(reader, writer); + impl.setValue(i, writer.toString()); } attr = impl; } @@ -690,16 +734,16 @@ return; } else if (JEXL_WHEN.equals(name)) { if (!inChoose) { - throw new ProcessingException("<when> must be contained in <choose>"); + throw new SAXParseException("<when> must be contained in <choose>", locator, null); } doWhen(attr); } else if (JEXL_OTHERWISE.equals(name)) { if (!inChoose) { - throw new ProcessingException("<otherwise> must be contained in <choose>"); + throw new SAXParseException("<otherwise> must be contained in <choose>", locator, null); } doOtherwise(attr); } else { - throw new ProcessingException("unknown jexl-transformer element: " + name); + throw new SAXParseException("unknown jexl-transformer element: " + name, locator, null); } inChoose = false; } @@ -744,11 +788,40 @@ return jexlContext; } -/* + private JXPathContext getJXPathContext() { - return jxpathContext; + return (JXPathContext)jxpathContextStack.peek(); + } + + private void pushJXPathContext(Object contextObject) { + JXPathContext jxpathContext = + jxpathContextFactory.newContext(null, contextObject); + jxpathContext.setVariables(new Variables() { + + public boolean isDeclaredVariable(String varName) { + return varName.equals("continuation"); + } + + public Object getVariable(String varName) { + if (varName.equals("continuation")) { + return kont; + } + return null; + } + + public void declareVariable(String varName, Object value) { + } + + public void undeclareVariable(String varName) { + } + }); + jxpathContextStack.push(jxpathContext); + } + + private void popJXPathContext() { + jxpathContextStack.pop(); } -*/ + private void setContexts(Object contextObject) { Map map; @@ -790,27 +863,7 @@ } } } - jxpathContext = JXPathContext.newContext(contextObject); - jxpathContext.setVariables(new Variables() { - - public boolean isDeclaredVariable(String varName) { - return varName.equals("continuation"); - } - - public Object getVariable(String varName) { - if (varName.equals("continuation")) { - return kont; - } - return null; - } - - public void declareVariable(String varName, Object value) { - } - - public void undeclareVariable(String varName) { - } - }); - jexlContext = JexlHelper.createContext(); + pushJXPathContext(contextObject); jexlContext.setVars(map); } @@ -818,19 +871,40 @@ * Helper method for obtaining the value of a particular variable. * * @param variable variable name + * @param xpath if true, treat variable as xpath expression + * @param iterate if true, treat result as a collection and return its iterator * @return variable value as an <code>Object</code> */ - private Object getValue(final String variable) { - JexlContext context = getJexlContext(); - try { - Expression e = ExpressionFactory.createExpression(variable); - return e.evaluate(context); - } catch (Exception e) { - throw new CascadingRuntimeException(e.getMessage(), e); + private Object getValue(final String variable, boolean xpath, + boolean iterate) throws SAXException { + if (xpath) { + JXPathContext context = getJXPathContext(); + if (iterate) { + return context.iteratePointers(variable); + } else { + return context.getValue(variable); + } + } else { + JexlContext context = getJexlContext(); + try { + Expression e = ExpressionFactory.createExpression(variable); + Object result = e.evaluate(context); + if (iterate) { + return Introspector.getUberspect().getIterator(result, + null); + } + return result; + } catch (Exception e) { + throw new SAXParseException("Error evaluating expression: " + + variable + ": "+ e.getMessage(), + locator, + e); + } } } + /** * Helper method to process a <jexl-transformer:value-of select="."> tag * @@ -841,22 +915,22 @@ private void doOut(final Attributes a) throws SAXException, ProcessingException { - final String select = a.getValue(JEXL_OUT_VALUE); + final String value = a.getValue(JEXL_OUT_VALUE); final String def = a.getValue(JEXL_OUT_DEFAULT); - if (null != select) { - Object value = eval(select); - if (value == null) { - value = def; - if (value == null) { - value = ""; + if (value != null) { + Object result = eval(value); + if (result == null) { + result = def; + if (result == null) { + result = ""; } } - sendTextEvent(value.toString()); + sendTextEvent(result.toString()); } else { - throw new ProcessingException( - "jexl-transformer:" + JEXL_OUT + " specified without a "+JEXL_OUT_VALUE+" attribute" - ); + throw new SAXParseException("out: \"value\" is required", + locator, + null); } } @@ -878,7 +952,7 @@ // get the test variable String expr = a.getValue(JEXL_IF_TEST); if (expr == null) { - throw new SAXException("if: \"test\" is required"); + throw new SAXParseException("if: \"test\" is required", locator, null); } final Object value = eval(expr); final boolean isTrueBoolean = @@ -909,6 +983,7 @@ throws SAXException { if (ignoreEventsCount == 0) { String items = a.getValue(JEXL_FOREACH_ITEMS); + String select = a.getValue(JEXL_FOREACH_SELECT); String s = a.getValue(JEXL_FOREACH_BEGIN); int begin = s == null ? -1 : Integer.parseInt(s); s = a.getValue(JEXL_FOREACH_END); @@ -916,25 +991,24 @@ s = a.getValue(JEXL_FOREACH_STEP); foreachStep = s == null ? 1 : Integer.parseInt(s); if (foreachStep < 1) { - throw new SAXException("forEach: \"step\" must be a positive integer"); + throw new SAXParseException("forEach: \"step\" must be a positive integer", locator, null); } String var = a.getValue(JEXL_FOREACH_VAR); - if (items == null && (begin == -1 || end == -1)) { - throw new SAXException("forEach: \"items\" or both \"begin\" and \"end\" must be specified"); + if (items == null) { + if (select == null && (begin == -1 || end == -1)) { + throw new SAXParseException("forEach: \"select\", \"items\", or both \"begin\" and \"end\" must be specified", locator, null); + } + } else if (select != null) { + throw new SAXParseException("forEach: only one of \"select\" or \"items\" may be specified", locator, null); } foreachBegin = begin == -1 ? 0 : begin; foreachEnd = end == -1 ? Integer.MAX_VALUE: end; + foreachXPath = false; if (items != null) { - Object value = eval(items); - if (value instanceof Collection) { - foreachIter = ((Collection)value).iterator(); - } else { - try { - foreachIter = Introspector.getUberspect().getIterator(value, null); - } catch (Exception exc) { - throw new SAXException(exc.getMessage(), exc); - } - } + foreachIter = (Iterator)eval(items, true); + } else if (select != null) { + foreachIter = (Iterator)eval(select, true); + foreachXPath = true; } else { foreachIter = new Iterator() { public boolean hasNext() { @@ -963,16 +1037,27 @@ int step = foreachStep; foreachIter = null; foreachVar = null; + boolean xpath = foreachXPath; int i; for (i = 0; i < begin && iter.hasNext(); i++) { iter.next(); } for (; i < end && iter.hasNext(); i++) { - Object value = iter.next(); + Object value; + if (xpath) { + Pointer ptr = (Pointer)iter.next(); + value = ptr.getNode(); + pushJXPathContext(value); + } else { + value = iter.next(); + } if (varName != null) { - jexlContext.getVars().put(varName, value); + getJexlContext().getVars().put(varName, value); } sendEvents(frag); + if (xpath) { + popJXPathContext(); + } for (int skip = step-1; skip > 0 && iter.hasNext(); --skip) { iter.next(); } @@ -1057,6 +1142,7 @@ super.recycle(); kont = null; jexlContext = null; + jxpathContextStack = null; foreachIter = null; foreachVar = null; chooseStack = null;