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;