blob: 32b9c09b7470a8d225cc47b3f5b0e5da6ed1e8fd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.generation;
import java.beans.PropertyDescriptor;
import java.io.CharArrayReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.TimeZone;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.caching.CacheableProcessingComponent;
import org.apache.cocoon.components.flow.FlowHelper;
import org.apache.cocoon.components.flow.WebContinuation;
import org.apache.cocoon.components.flow.javascript.fom.FOM_JavaScriptFlowHelper;
import org.apache.cocoon.components.source.SourceUtil;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.transformation.ServiceableTransformer;
import org.apache.cocoon.util.jxpath.NamespacesTablePointer;
import org.apache.cocoon.util.location.LocatedRuntimeException;
import org.apache.cocoon.util.location.Location;
import org.apache.cocoon.util.location.LocationUtils;
import org.apache.cocoon.xml.IncludeXMLConsumer;
import org.apache.cocoon.xml.NamespacesTable;
import org.apache.cocoon.xml.RedundantNamespacesFilter;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.XMLUtils;
import org.apache.cocoon.xml.dom.DOMBuilder;
import org.apache.cocoon.xml.dom.DOMStreamer;
import org.apache.commons.jexl.Expression;
import org.apache.commons.jexl.ExpressionFactory;
import org.apache.commons.jexl.JexlContext;
import org.apache.commons.jexl.util.Introspector;
import org.apache.commons.jexl.util.introspection.Info;
import org.apache.commons.jexl.util.introspection.UberspectImpl;
import org.apache.commons.jexl.util.introspection.VelMethod;
import org.apache.commons.jexl.util.introspection.VelPropertyGet;
import org.apache.commons.jexl.util.introspection.VelPropertySet;
import org.apache.commons.jxpath.*;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceException;
import org.apache.excalibur.source.SourceValidity;
import org.apache.excalibur.xml.sax.XMLizable;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeJavaClass;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.Wrapper;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
/**
* @cocoon.sitemap.component.documentation
* Provides a generic page template with embedded JSTL and XPath
* expression substitution to access data sent by Cocoon Flowscripts.
*
* @cocoon.sitemap.component.name jx
* @cocoon.sitemap.component.label content
* @cocoon.sitemap.component.logger sitemap.generator.jx
*
* @cocoon.sitemap.component.pooling.max 16
*
* @deprecated Replaced with the template block: {@link org.apache.cocoon.template.JXTemplateGenerator}.
*
* @version $Id$
*/
public class JXTemplateGenerator extends ServiceableGenerator implements CacheableProcessingComponent {
// Quick'n dirty hack to replace all SAXParseException by a located runtime exception
private static final class JXTException extends LocatedRuntimeException {
JXTException(String message, Location loc, Throwable thr) {
super(message, thr, loc);
}
}
private static final JXPathContextFactory jxpathContextFactory = JXPathContextFactory.newInstance();
private static final Attributes EMPTY_ATTRS = XMLUtils.EMPTY_ATTRIBUTES;
private final NamespacesTable namespaces = new NamespacesTable();
private static final Iterator EMPTY_ITER = new Iterator() {
public boolean hasNext() {
return false;
}
public Object next() {
return null;
}
public void remove() {
// EMPTY
}
};
private static final Iterator NULL_ITER = new Iterator() {
public boolean hasNext() {
return true;
}
public Object next() {
return null;
}
public void remove() {
// EMPTY
}
};
private XMLConsumer getConsumer() {
return this.xmlConsumer;
}
public static class ErrorHolder extends Exception {
private Error err;
public ErrorHolder(Error err) {
super(err.getMessage());
this.err = err;
}
public void printStackTrace(PrintStream ps) {
err.printStackTrace(ps);
}
public void printStackTrace(PrintWriter pw) {
err.printStackTrace(pw);
}
public void printStackTrace() {
err.printStackTrace();
}
public Error getError() {
return err;
}
}
/**
* Facade to the Location to be set on the consumer prior to
* sending other events, location member changeable
*/
public static class LocationFacade implements Locator {
private Location locator;
public LocationFacade(Location initialLocation) {
this.locator = initialLocation;
}
public void setDocumentLocation(Location newLocation) {
this.locator = newLocation;
}
public int getColumnNumber() {
return this.locator.getColumnNumber();
}
public int getLineNumber() {
return this.locator.getLineNumber();
}
public String getPublicId() {
return null;
}
public String getSystemId() {
return this.locator.getURI();
}
}
/**
* Jexl Introspector that supports Rhino JavaScript objects
* as well as Java Objects
*/
static class JSIntrospector extends UberspectImpl {
static class JSMethod implements VelMethod {
Scriptable scope;
String name;
public JSMethod(Scriptable scope, String name) {
this.scope = scope;
this.name = name;
}
public Object invoke(Object thisArg, Object[] args) throws Exception {
Context cx = Context.enter();
try {
Object result;
Scriptable thisObj = !(thisArg instanceof Scriptable) ?
Context.toObject(thisArg, scope) : (Scriptable)thisArg;
result = ScriptableObject.getProperty(thisObj, name);
Object[] newArgs = null;
if (args != null) {
newArgs = new Object[args.length];
int len = args.length;
for (int i = 0; i < len; i++) {
newArgs[i] = args[i];
if (args[i] != null &&
!(args[i] instanceof Number) &&
!(args[i] instanceof Boolean) &&
!(args[i] instanceof String) &&
!(args[i] instanceof Scriptable)) {
newArgs[i] = Context.toObject(args[i], scope);
}
}
}
result = ScriptRuntime.call(cx, result, thisObj, newArgs, scope);
if (result == Undefined.instance || result == Scriptable.NOT_FOUND) {
result = null;
} else if (!(result instanceof NativeJavaClass)) {
while (result instanceof Wrapper) {
result = ((Wrapper)result).unwrap();
}
}
return result;
} catch (JavaScriptException e) {
throw new java.lang.reflect.InvocationTargetException(e);
} finally {
Context.exit();
}
}
public boolean isCacheable() {
return false;
}
public String getMethodName() {
return name;
}
public Class getReturnType() {
return Object.class;
}
}
static class JSPropertyGet implements VelPropertyGet {
Scriptable scope;
String name;
public JSPropertyGet(Scriptable scope, String name) {
this.scope = scope;
this.name = name;
}
public Object invoke(Object thisArg) throws Exception {
Context cx = Context.enter();
try {
Scriptable thisObj = !(thisArg instanceof Scriptable) ?
Context.toObject(thisArg, scope) : (Scriptable)thisArg;
Object result = ScriptableObject.getProperty(thisObj, name);
if (result == Scriptable.NOT_FOUND) {
result = ScriptableObject.getProperty(thisObj, "get" + StringUtils.capitalize(name));
if (result != Scriptable.NOT_FOUND && result instanceof Function) {
try {
result = ((Function)result).call(
cx, ScriptableObject.getTopLevelScope(thisObj), thisObj, new Object[] {});
} catch (JavaScriptException exc) {
exc.printStackTrace();
result = null;
}
}
}
if (result == Scriptable.NOT_FOUND || result == Undefined.instance) {
result = null;
} else if (result instanceof Wrapper && !(result instanceof NativeJavaClass)) {
result = ((Wrapper)result).unwrap();
}
return result;
} finally {
Context.exit();
}
}
public boolean isCacheable() {
return false;
}
public String getMethodName() {
return name;
}
}
static class JSPropertySet implements VelPropertySet {
Scriptable scope;
String name;
public JSPropertySet(Scriptable scope, String name) {
this.scope = scope;
this.name = name;
}
public Object invoke(Object thisArg, Object rhs) throws Exception {
Context.enter();
try {
Scriptable thisObj;
Object arg = rhs;
if (!(thisArg instanceof Scriptable)) {
thisObj = Context.toObject(thisArg, scope);
} else {
thisObj = (Scriptable)thisArg;
}
if (arg != null &&
!(arg instanceof Number) &&
!(arg instanceof Boolean) &&
!(arg instanceof String) &&
!(arg instanceof Scriptable)) {
arg = Context.toObject(arg, scope);
}
ScriptableObject.putProperty(thisObj, name, arg);
return rhs;
} finally {
Context.exit();
}
}
public boolean isCacheable() {
return false;
}
public String getMethodName() {
return name;
}
}
static class NativeArrayIterator implements Iterator {
NativeArray arr;
int index;
public NativeArrayIterator(NativeArray arr) {
this.arr = arr;
this.index = 0;
}
public boolean hasNext() {
return index < (int)arr.jsGet_length();
}
public Object next() {
Context.enter();
try {
Object result = arr.get(index++, arr);
if (result == Undefined.instance ||
result == Scriptable.NOT_FOUND) {
result = null;
} else {
if (!(result instanceof NativeJavaClass)) {
while (result instanceof Wrapper) {
result = ((Wrapper)result).unwrap();
}
}
}
return result;
} finally {
Context.exit();
}
}
public void remove() {
arr.delete(index);
}
}
static class ScriptableIterator implements Iterator {
Scriptable scope;
Object[] ids;
int index;
public ScriptableIterator(Scriptable scope) {
this.scope = scope;
this.ids = scope.getIds();
this.index = 0;
}
public boolean hasNext() {
return index < ids.length;
}
public Object next() {
Context.enter();
try {
Object result = ScriptableObject.getProperty(scope, ids[index++].toString());
if (result == Undefined.instance || result == Scriptable.NOT_FOUND) {
result = null;
} else if (!(result instanceof NativeJavaClass)) {
while (result instanceof Wrapper) {
result = ((Wrapper)result).unwrap();
}
}
return result;
} finally {
Context.exit();
}
}
public void remove() {
Context.enter();
try {
scope.delete(ids[index].toString());
} finally {
Context.exit();
}
}
}
public Iterator getIterator(Object obj, Info i) throws Exception {
if (!(obj instanceof Scriptable)) {
// support Enumeration
if (obj instanceof Enumeration) {
final Enumeration e = (Enumeration)obj;
return new Iterator() {
public boolean hasNext() {
return e.hasMoreElements();
}
public Object next() {
return e.nextElement();
}
public void remove() {
// no action
}
};
}
if (obj instanceof Iterator) {
// support Iterator
return (Iterator)obj;
}
return super.getIterator(obj, i);
}
if (obj instanceof NativeArray) {
return new NativeArrayIterator((NativeArray)obj);
}
return new ScriptableIterator((Scriptable)obj);
}
public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception {
return !(obj instanceof Scriptable) ?
super.getMethod(obj, methodName, args, i) : new JSMethod((Scriptable)obj, methodName);
}
public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i) throws Exception {
return !(obj instanceof Scriptable) ?
super.getPropertyGet(obj, identifier, i) : new JSPropertyGet((Scriptable)obj, identifier);
}
public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i) throws Exception {
return !(obj instanceof Scriptable) ?
super.getPropertySet(obj, identifier, arg, i) : new JSPropertySet((Scriptable)obj, identifier);
}
}
static class MyJexlContext extends HashMap implements JexlContext {
private MyJexlContext closure;
MyJexlContext() {
this(null);
}
MyJexlContext(MyJexlContext closure) {
this.closure = closure;
}
public Map getVars() {
return this;
}
public void setVars(Map map) {
putAll(map);
}
public boolean containsKey(Object key) {
return this.get(key) !=null;
}
public Object get(Object key) {
if (key.equals("this")) {
return this;
}
Object result = super.get(key);
if (result == null && closure != null) {
result = closure.get(key);
}
return result;
}
}
static class MyVariables implements Variables {
MyVariables closure;
Map localVariables = new HashMap();
static final String[] VARIABLES = new String[] {
"cocoon",
"continuation",
"flowContext",
"request",
"response",
"context",
"session",
"parameters"
};
Object cocoon;
// backward compatibility
Object bean, kont, request, response,
session, context, parameters;
MyVariables(Object cocoon,
Object bean,
WebContinuation kont,
Object request,
Object session,
Object context,
Object parameters) {
this.cocoon = cocoon;
this.bean = bean;
this.kont = kont;
this.request = request;
this.session = session;
this.context = context;
this.parameters = parameters;
}
public MyVariables(MyVariables parent) {
this.closure = parent;
}
public boolean isDeclaredVariable(String varName) {
int len = VARIABLES.length;
for (int i = 0; i < len; i++) {
if (varName.equals(VARIABLES[i])) {
return true;
}
}
if (localVariables.containsKey(varName)) {
return true;
}
if (closure != null) {
return closure.isDeclaredVariable(varName);
}
return false;
}
public Object getVariable(String varName) {
Object result = localVariables.get(varName);
if (result != null) {
return result;
}
if (closure != null) {
return closure.getVariable(varName);
}
if (varName.equals("cocoon")) {
return cocoon;
}
// backward compatibility
if (varName.equals("continuation")) {
return kont;
} else if (varName.equals("flowContext")) {
return bean;
} else if (varName.equals("request")) {
return request;
} else if (varName.equals("session")) {
return session;
} else if (varName.equals("context")) {
return context;
} else if (varName.equals("parameters")) {
return parameters;
}
return null;
}
public void declareVariable(String varName, Object value) {
localVariables.put(varName, value);
}
public void undeclareVariable(String varName) {
localVariables.remove(varName);
}
}
static {
// Hack: there's no _nice_ way to add my introspector to Jexl right now
try {
Field field = Introspector.class.getDeclaredField("uberSpect");
field.setAccessible(true);
field.set(null, new JSIntrospector());
} catch (Exception e) {
e.printStackTrace();
}
}
/** The namespace used by this generator */
public final static String NS = "http://apache.org/cocoon/templates/jx/1.0";
final static String TEMPLATE = "template";
final static String FOR_EACH = "forEach";
final static String IF = "if";
final static String CHOOSE = "choose";
final static String WHEN = "when";
final static String OTHERWISE = "otherwise";
final static String OUT = "out";
final static String IMPORT = "import";
final static String SET = "set";
final static String MACRO = "macro";
final static String EVALBODY = "evalBody";
final static String EVAL = "eval";
final static String PARAMETER = "parameter";
final static String FORMAT_NUMBER = "formatNumber";
final static String FORMAT_DATE = "formatDate";
final static String COMMENT = "comment";
final static String CACHE_KEY = "cache-key";
final static String VALIDITY = "cache-validity";
/**
* Compile a single Jexl expr (contained in ${}) or XPath expression
* (contained in #{})
*/
private static JXTExpression compileExpr(String expr, String errorPrefix, Location location) throws JXTException {
try {
return compileExpr(expr);
} catch (Exception exc) {
throw new JXTException(errorPrefix + exc.getMessage(), location, exc);
}
}
private static JXTExpression compileExpr(String inStr) throws Exception {
try {
if (inStr == null) {
return null;
}
StringReader in = new StringReader(inStr.trim());
int ch;
boolean xpath = false;
boolean inExpr = false;
StringBuffer expr = new StringBuffer();
while ((ch = in.read()) != -1) {
char c = (char)ch;
if (inExpr) {
if (c == '\\') {
ch = in.read();
expr.append((ch == -1) ? '\\' : (char)ch);
} else if (c == '}') {
return compile(expr.toString(), xpath);
} else {
expr.append(c);
}
} else {
if (c == '$' || c == '#') {
ch = in.read();
if (ch == '{') {
inExpr = true;
xpath = c == '#';
continue;
}
}
// hack: invalid expression?
// just return the original and swallow exception
return new JXTExpression(inStr, null);
}
}
if (inExpr) {
// unclosed #{} or ${}
throw new Exception("Unterminated " + (xpath ? "#" : "$") + "{");
}
} catch (IOException ignored) {
ignored.printStackTrace();
}
return new JXTExpression(inStr, null);
}
/*
* Compile an integer expression (returns either a Compiled Expression
* or an Integer literal)
*/
private static JXTExpression compileInt(String val, String msg, Location location) throws SAXException {
JXTExpression res = compileExpr(val, msg, location);
if (res != null) {
if (res.compiledExpression == null) {
res.compiledExpression = Integer.valueOf(res.raw);
}
return res;
}
return null;
}
private static JXTExpression compileBoolean(String val, String msg, Location location) throws SAXException {
JXTExpression res = compileExpr(val, msg, location);
if (res != null) {
if (res.compiledExpression == null) {
res.compiledExpression = Boolean.valueOf(res.raw);
}
return res;
}
return null;
}
private static JXTExpression compile(final String variable, boolean xpath) throws Exception {
Object compiled;
if (xpath) {
compiled = JXPathContext.compile(variable);
} else {
compiled = ExpressionFactory.createExpression(variable);
}
return new JXTExpression(variable, compiled);
}
static private Object getValue(JXTExpression expr, JexlContext jexlContext,
JXPathContext jxpathContext, Boolean lenient) throws Exception {
if (expr != null) {
Object compiled = expr.compiledExpression;
try {
if (compiled instanceof CompiledExpression) {
CompiledExpression e = (CompiledExpression)compiled;
boolean oldLenient = jxpathContext.isLenient();
if (lenient != null) {
jxpathContext.setLenient(lenient.booleanValue());
}
try {
return e.getValue(jxpathContext);
} finally {
jxpathContext.setLenient(oldLenient);
}
} else if (compiled instanceof Expression) {
Expression e = (Expression)compiled;
return e.evaluate(jexlContext);
}
return compiled;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Exception) {
throw (Exception)t;
}
throw (Error)t;
}
} else {
return null;
}
}
static private Object getValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception {
return getValue(expr, jexlContext, jxpathContext, null);
}
static private int getIntValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception {
Object res = getValue(expr, jexlContext, jxpathContext);
return res instanceof Number ? ((Number)res).intValue() : 0;
}
static private Number getNumberValue(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception {
Object res = getValue(expr, jexlContext, jxpathContext);
if (res instanceof Number) {
return (Number)res;
}
if (res != null) {
return Double.valueOf(res.toString());
}
return null;
}
static private String getStringValue(JXTExpression expr, JexlContext jexlContext,
JXPathContext jxpathContext) throws Exception {
Object res = getValue(expr, jexlContext, jxpathContext);
if (res != null) {
return res.toString();
}
if (expr != null && expr.compiledExpression == null) {
return expr.raw;
}
return null;
}
static private Boolean getBooleanValue(JXTExpression expr, JexlContext jexlContext,
JXPathContext jxpathContext) throws Exception {
Object res = getValue(expr, jexlContext, jxpathContext);
return res instanceof Boolean ? (Boolean)res : null;
}
private Object getNode(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception {
return getNode(expr, jexlContext, jxpathContext, null);
}
// Hack: try to prevent JXPath from converting result to a String
private Object getNode(JXTExpression expr, JexlContext jexlContext, JXPathContext jxpathContext, Boolean lenient)
throws Exception {
try {
Object compiled = expr.compiledExpression;
if (compiled instanceof CompiledExpression) {
CompiledExpression e = (CompiledExpression)compiled;
boolean oldLenient = jxpathContext.isLenient();
if (lenient != null) jxpathContext.setLenient(lenient.booleanValue());
try {
Iterator iter = e.iteratePointers(jxpathContext);
if (iter.hasNext()) {
Pointer first = (Pointer)iter.next();
if (iter.hasNext()) {
List result = new LinkedList();
result.add(first.getNode());
boolean dom = (first.getNode() instanceof Node);
while (iter.hasNext()) {
Object obj = ((Pointer)iter.next()).getNode();
dom = dom && (obj instanceof Node);
result.add(obj);
}
Object[] arr;
if (dom) {
arr = new Node[result.size()];
} else {
arr = new Object[result.size()];
}
result.toArray(arr);
return arr;
}
return first.getNode();
}
return null;
} finally {
jxpathContext.setLenient(oldLenient);
}
} else if (compiled instanceof Expression) {
Expression e = (Expression)compiled;
return e.evaluate(jexlContext);
}
return expr.raw;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Exception) {
throw (Exception)t;
}
throw (Error)t;
}
}
static class Event {
final Location location;
Event next; // in document order
Event(Location locator) {
this.location = locator != null ? locator : Location.UNKNOWN;
}
public String locationString() {
return location.toString();
}
}
static class TextEvent extends Event {
TextEvent(Location location, char[] chars, int start, int length)
throws SAXException {
super(location);
StringBuffer buf = new StringBuffer();
this.raw = new char[length];
System.arraycopy(chars, start, this.raw, 0, length);
CharArrayReader in = new CharArrayReader(chars, start, length);
int ch;
boolean inExpr = false;
boolean xpath = false;
try {
top: while ((ch = in.read()) != -1) {
// column++;
char c = (char)ch;
processChar: while (true) {
if (inExpr) {
if (c == '\\') {
ch = in.read();
buf.append(ch == -1 ? '\\' : (char)ch);
} else if (c == '}') {
String str = buf.toString();
Object compiledExpression;
try {
if (xpath) {
compiledExpression = JXPathContext.compile(str);
} else {
compiledExpression = ExpressionFactory.createExpression(str);
}
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), this.location, exc);
}
substitutions.add(new JXTExpression(str, compiledExpression));
buf.setLength(0);
inExpr = false;
} else {
buf.append(c);
}
} else if (c == '$' || c == '#') {
ch = in.read();
if (ch == '{') {
xpath = c == '#';
inExpr = true;
if (buf.length() > 0) {
char[] charArray = new char[buf.length()];
buf.getChars(0, buf.length(), charArray, 0);
substitutions.add(charArray);
buf.setLength(0);
}
continue top;
}
buf.append(c);
if (ch != -1) {
c = (char)ch;
continue processChar;
}
} else {
buf.append(c);
}
break;
}
}
} catch (IOException ignored) {
// won't happen
ignored.printStackTrace();
}
if (inExpr) {
// unclosed #{} or ${}
buf.insert(0, (xpath ? "#" : "$") + "{");
}
if (buf.length() > 0) {
char[] charArray = new char[buf.length()];
buf.getChars(0, buf.length(), charArray, 0);
substitutions.add(charArray);
} else if (substitutions.isEmpty()) {
substitutions.add(ArrayUtils.EMPTY_CHAR_ARRAY);
}
}
final List substitutions = new LinkedList();
final char[] raw;
}
static class Characters extends TextEvent {
Characters(Location location, char[] chars, int start, int length) throws SAXException {
super(location, chars, start, length);
}
}
static class StartDocument extends Event {
StartDocument(Location location) {
super(location);
templateProperties = new HashMap();
}
SourceValidity compileTime;
EndDocument endDocument; // null if document fragment
Map templateProperties;
}
static class EndDocument extends Event {
EndDocument(Location location) {
super(location);
}
}
static class EndElement extends Event {
EndElement(Location location, StartElement startElement) {
super(location);
this.startElement = startElement;
}
final StartElement startElement;
}
static class EndPrefixMapping extends Event {
EndPrefixMapping(Location location, String prefix) {
super(location);
this.prefix = prefix;
}
final String prefix;
}
static class IgnorableWhitespace extends TextEvent {
IgnorableWhitespace(Location location, char[] chars, int start, int length) throws SAXException {
super(location, chars, start, length);
}
}
static class ProcessingInstruction extends Event {
ProcessingInstruction(Location location, String target, String data) {
super(location);
this.target = target;
this.data = data;
}
final String target;
final String data;
}
static class SkippedEntity extends Event {
SkippedEntity(Location location, String name) {
super(location);
this.name = name;
}
final String name;
}
abstract static class AttributeEvent {
AttributeEvent(String namespaceURI, String localName, String raw, String type) {
this.namespaceURI = namespaceURI;
this.localName = localName;
this.raw = raw;
this.type = type;
}
final String namespaceURI;
final String localName;
final String raw;
final String type;
}
static class CopyAttribute extends AttributeEvent {
CopyAttribute(String namespaceURI, String localName, String raw, String type, String value) {
super(namespaceURI, localName, raw, type);
this.value = value;
}
final String value;
}
static class Subst {
// VOID
}
static class Literal extends Subst {
Literal(String val) {
this.value = val;
}
final String value;
}
static class JXTExpression extends Subst {
JXTExpression(String raw, Object expr) {
this.raw = raw;
this.compiledExpression = expr;
}
String raw;
Object compiledExpression;
}
static class SubstituteAttribute extends AttributeEvent {
SubstituteAttribute(String namespaceURI, String localName, String raw, String type, List substs) {
super(namespaceURI, localName, raw, type);
this.substitutions = substs;
}
final List substitutions;
}
static class StartElement extends Event {
StartElement(Location location, String namespaceURI, String localName,
String raw, Attributes attrs)
throws SAXException {
super(location);
this.namespaceURI = namespaceURI;
this.localName = localName;
this.raw = raw;
this.qname = "{" + namespaceURI + "}" + localName;
StringBuffer buf = new StringBuffer();
int len = attrs.getLength();
for (int i = 0; i < len; i++) {
String uri = attrs.getURI(i);
String local = attrs.getLocalName(i);
String qname = attrs.getQName(i);
String type = attrs.getType(i);
String value = attrs.getValue(i);
StringReader in = new StringReader(value);
int ch;
buf.setLength(0);
boolean inExpr = false;
List substEvents = new LinkedList();
boolean xpath = false;
try {
top: while ((ch = in.read()) != -1) {
char c = (char)ch;
processChar: while (true) {
if (inExpr) {
if (c == '\\') {
ch = in.read();
buf.append(ch == -1 ? '\\' : (char)ch);
} else if (c == '}') {
String str = buf.toString();
JXTExpression compiledExpression;
try {
compiledExpression = compile(str, xpath);
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), location, exc);
}
substEvents.add(compiledExpression);
buf.setLength(0);
inExpr = false;
} else {
buf.append(c);
}
} else if (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;
}
buf.append(c);
if (ch != -1) {
c = (char)ch;
continue processChar;
}
} else {
buf.append(c);
}
break;
}
}
} catch (IOException ignored) {
ignored.printStackTrace();
}
if (inExpr) {
// unclosed #{} or ${}
String msg = "Unterminated " + (xpath ? "#" : "$") + "{";
throw new JXTException(msg, location, null);
}
if (buf.length() > 0) {
if (substEvents.size() == 0) {
attributeEvents.add(new CopyAttribute(uri, local, qname, type, buf.toString()));
} else {
substEvents.add(new Literal(buf.toString()));
attributeEvents.add(new SubstituteAttribute(uri, local, qname, type, substEvents));
}
} else {
if (substEvents.size() > 0) {
attributeEvents.add(new SubstituteAttribute(uri, local, qname, type, substEvents));
} else {
attributeEvents.add(new CopyAttribute(uri, local, qname, type, ""));
}
}
}
this.attributes = new AttributesImpl(attrs);
}
final String namespaceURI;
final String localName;
final String raw;
final String qname;
final List attributeEvents = new LinkedList();
final Attributes attributes;
EndElement endElement;
}
static class StartPrefixMapping extends Event {
StartPrefixMapping(Location location, String prefix, String uri) {
super(location);
this.prefix = prefix;
this.uri = uri;
}
final String prefix;
final String uri;
}
static class EndCDATA extends Event {
EndCDATA(Location location) {
super(location);
}
}
static class EndDTD extends Event {
EndDTD(Location location) {
super(location);
}
}
static class EndEntity extends Event {
EndEntity(Location location, String name) {
super(location);
this.name = name;
}
final String name;
}
static class StartCDATA extends Event {
StartCDATA(Location location) {
super(location);
}
}
static class StartDTD extends Event {
StartDTD(Location location, String name, String publicId, String systemId) {
super(location);
this.name = name;
this.publicId = publicId;
this.systemId = systemId;
}
final String name;
final String publicId;
final String systemId;
}
static class StartEntity extends Event {
public StartEntity(Location location, String name) {
super(location);
this.name = name;
}
final String name;
}
static class StartInstruction extends Event {
StartInstruction(StartElement startElement) {
super(startElement.location);
this.startElement = startElement;
}
final StartElement startElement;
EndInstruction endInstruction;
}
static class EndInstruction extends Event {
EndInstruction(Location locator, StartInstruction startInstruction) {
super(locator);
this.startInstruction = startInstruction;
startInstruction.endInstruction = this;
}
final StartInstruction startInstruction;
}
static class StartForEach extends StartInstruction {
StartForEach(StartElement raw, JXTExpression items, JXTExpression var,
JXTExpression varStatus, JXTExpression begin, JXTExpression end,
JXTExpression step, Boolean lenient) {
super(raw);
this.items = items;
this.var = var;
this.varStatus = varStatus;
this.begin = begin;
this.end = end;
this.step = step;
this.lenient = lenient;
}
final JXTExpression items;
final JXTExpression var;
final JXTExpression varStatus;
final JXTExpression begin;
final JXTExpression end;
final JXTExpression step;
final Boolean lenient;
}
static class StartIf extends StartInstruction {
StartIf(StartElement raw, JXTExpression test) {
super(raw);
this.test = test;
}
final JXTExpression test;
}
static class StartChoose extends StartInstruction {
StartChoose(StartElement raw) {
super(raw);
}
StartWhen firstChoice;
StartOtherwise otherwise;
}
static class StartWhen extends StartInstruction {
StartWhen(StartElement raw, JXTExpression test) {
super(raw);
this.test = test;
}
final JXTExpression test;
StartWhen nextChoice;
}
static class StartOtherwise extends StartInstruction {
StartOtherwise(StartElement raw) {
super(raw);
}
}
static class StartOut extends StartInstruction {
StartOut(StartElement raw, JXTExpression expr, Boolean lenient) {
super(raw);
this.compiledExpression = expr;
this.lenient = lenient;
}
final JXTExpression compiledExpression;
final Boolean lenient;
}
static class StartImport extends StartInstruction {
StartImport(StartElement raw, AttributeEvent uri, JXTExpression select) {
super(raw);
this.uri = uri;
this.select = select;
}
final AttributeEvent uri;
final JXTExpression select;
}
static class StartTemplate extends StartInstruction {
StartTemplate(StartElement raw) {
super(raw);
}
}
static class StartEvalBody extends StartInstruction {
StartEvalBody(StartElement raw) {
super(raw);
}
}
static class StartEval extends StartInstruction {
StartEval(StartElement raw, JXTExpression value) {
super(raw);
this.value = value;
}
final JXTExpression value;
}
static class StartDefine extends StartInstruction {
StartDefine(StartElement raw, String namespace, String name) {
super(raw);
this.namespace = namespace;
this.name = name;
this.qname = "{" + namespace + "}" + name;
this.parameters = new HashMap();
}
final String name;
final String namespace;
final String qname;
final Map parameters;
Event body;
void finish() throws SAXException {
Event e = next;
boolean params = true;
while (e != this.endInstruction) {
if (e instanceof StartParameter) {
StartParameter startParam = (StartParameter)e;
if (!params) {
throw new JXTException("<parameter> not allowed here: \"" + startParam.name + "\"",
startParam.location, null);
}
Object prev = parameters.put(startParam.name, startParam);
if (prev != null) {
throw new JXTException("duplicate parameter: \"" + startParam.name + "\"", location, null);
}
e = startParam.endInstruction;
} else if (e instanceof IgnorableWhitespace) {
// EMPTY
} else if (e instanceof Characters) {
// check for whitespace
char[] ch = ((TextEvent)e).raw;
int len = ch.length;
for (int i = 0; i < len; i++) {
if (!Character.isWhitespace(ch[i])) {
if (params) {
params = false;
body = e;
}
break;
}
}
} else {
if (params) {
params = false;
body = e;
}
}
e = e.next;
}
if (this.body == null) {
this.body = this.endInstruction;
}
}
}
static class StartParameter extends StartInstruction {
StartParameter(StartElement raw, String name, String optional, String default_) {
super(raw);
this.name = name;
this.optional = optional;
this.default_ = default_;
}
final String name;
final String optional;
final String default_;
}
static class StartSet extends StartInstruction {
StartSet(StartElement raw, JXTExpression var, JXTExpression value) {
super(raw);
this.var = var;
this.value = value;
}
final JXTExpression var;
final JXTExpression value;
}
static class StartComment extends StartInstruction {
StartComment(StartElement raw) {
super(raw);
}
}
// formatNumber tag (borrows from Jakarta taglibs JSTL)
private static Locale parseLocale(String locale, String variant) {
Locale ret = null;
String language = locale;
String country = null;
int index = StringUtils.indexOfAny(locale, "-_");
if (index > -1) {
language = locale.substring(0, index);
country = locale.substring(index + 1);
}
if (StringUtils.isEmpty(language)) {
throw new IllegalArgumentException("No language in locale");
}
if (country == null) {
ret = variant != null ? new Locale(language, "", variant) : new Locale(language, "");
} else if (country.length() > 0) {
ret = variant != null ? new Locale(language, country, variant) : new Locale(language, country);
} else {
throw new IllegalArgumentException("Empty country in locale");
}
return ret;
}
private static final String NUMBER = "number";
private static final String CURRENCY = "currency";
private static final String PERCENT = "percent";
static class LocaleAwareInstruction extends StartInstruction {
private JXTExpression locale;
LocaleAwareInstruction(StartElement startElement, JXTExpression locale) {
super(startElement);
this.locale = locale;
}
protected Locale getLocale(JexlContext jexl, JXPathContext jxp) throws Exception {
Object locVal = getValue(this.locale, jexl, jxp);
if (locVal == null)
locVal = getStringValue(this.locale, jexl, jxp);
if (locVal != null) {
return locVal instanceof Locale ? (Locale) locVal : parseLocale(locVal.toString(), null);
} else {
return Locale.getDefault();
}
}
}
static class StartFormatNumber extends LocaleAwareInstruction {
JXTExpression value;
JXTExpression type;
JXTExpression pattern;
JXTExpression currencyCode;
JXTExpression currencySymbol;
JXTExpression isGroupingUsed;
JXTExpression maxIntegerDigits;
JXTExpression minIntegerDigits;
JXTExpression maxFractionDigits;
JXTExpression minFractionDigits;
JXTExpression var;
private static Class currencyClass;
static {
try {
currencyClass = Class.forName("java.util.Currency");
// container's runtime is J2SE 1.4 or greater
} catch (Exception cnfe) {
// EMPTY
}
}
public StartFormatNumber(StartElement raw,
JXTExpression var,
JXTExpression value,
JXTExpression type,
JXTExpression pattern,
JXTExpression currencyCode,
JXTExpression currencySymbol,
JXTExpression isGroupingUsed,
JXTExpression maxIntegerDigits,
JXTExpression minIntegerDigits,
JXTExpression maxFractionDigits,
JXTExpression minFractionDigits,
JXTExpression locale) {
super(raw, locale);
this.var = var;
this.value = value;
this.type = type;
this.pattern = pattern;
this.currencyCode = currencyCode;
this.currencySymbol = currencySymbol;
this.isGroupingUsed = isGroupingUsed;
this.maxIntegerDigits = maxIntegerDigits;
this.minIntegerDigits = minIntegerDigits;
this.maxFractionDigits = maxFractionDigits;
this.minFractionDigits = minFractionDigits;
}
String format(JexlContext jexl, JXPathContext jxp) throws Exception {
// Determine formatting locale
String var = getStringValue(this.var, jexl, jxp);
Number input = getNumberValue(this.value, jexl, jxp);
String type = getStringValue(this.type, jexl, jxp);
String pattern = getStringValue(this.pattern, jexl, jxp);
String currencyCode = getStringValue(this.currencyCode, jexl, jxp);
String currencySymbol = getStringValue(this.currencySymbol, jexl, jxp);
Boolean isGroupingUsed = getBooleanValue(this.isGroupingUsed, jexl, jxp);
Number maxIntegerDigits = getNumberValue(this.maxIntegerDigits, jexl, jxp);
Number minIntegerDigits = getNumberValue(this.minIntegerDigits, jexl, jxp);
Number maxFractionDigits = getNumberValue(this.maxFractionDigits, jexl, jxp);
Number minFractionDigits = getNumberValue(this.minFractionDigits, jexl, jxp);
Locale loc = getLocale(jexl,jxp);
String formatted;
if (loc != null) {
// Create formatter
NumberFormat formatter = null;
if (StringUtils.isNotEmpty(pattern)) {
// if 'pattern' is specified, 'type' is ignored
DecimalFormatSymbols symbols = new DecimalFormatSymbols(loc);
formatter = new DecimalFormat(pattern, symbols);
} else {
formatter = createFormatter(loc, type);
}
if (StringUtils.isNotEmpty(pattern) || CURRENCY.equalsIgnoreCase(type)) {
setCurrency(formatter, currencyCode, currencySymbol);
}
configureFormatter(formatter,
isGroupingUsed,
maxIntegerDigits,
minIntegerDigits,
maxFractionDigits,
minFractionDigits);
formatted = formatter.format(input);
} else {
// no formatting locale available, use toString()
formatted = input.toString();
}
if (var != null) {
jexl.getVars().put(var, formatted);
jxp.getVariables().declareVariable(var, formatted);
return null;
}
return formatted;
}
private NumberFormat createFormatter(Locale loc, String type) throws Exception {
NumberFormat formatter = null;
if ((type == null) || NUMBER.equalsIgnoreCase(type)) {
formatter = NumberFormat.getNumberInstance(loc);
} else if (CURRENCY.equalsIgnoreCase(type)) {
formatter = NumberFormat.getCurrencyInstance(loc);
} else if (PERCENT.equalsIgnoreCase(type)) {
formatter = NumberFormat.getPercentInstance(loc);
} else {
throw new IllegalArgumentException("Invalid type: \"" + type + "\": should be \"number\" or \"currency\" or \"percent\"");
}
return formatter;
}
/*
* Applies the 'groupingUsed', 'maxIntegerDigits', 'minIntegerDigits',
* 'maxFractionDigits', and 'minFractionDigits' attributes to the given
* formatter.
*/
private void configureFormatter(NumberFormat formatter,
Boolean isGroupingUsed,
Number maxIntegerDigits,
Number minIntegerDigits,
Number maxFractionDigits,
Number minFractionDigits) {
if (isGroupingUsed != null)
formatter.setGroupingUsed(isGroupingUsed.booleanValue());
if (maxIntegerDigits != null)
formatter.setMaximumIntegerDigits(maxIntegerDigits.intValue());
if (minIntegerDigits != null)
formatter.setMinimumIntegerDigits(minIntegerDigits.intValue());
if (maxFractionDigits != null)
formatter.setMaximumFractionDigits(maxFractionDigits.intValue());
if (minFractionDigits != null)
formatter.setMinimumFractionDigits(minFractionDigits.intValue());
}
/*
* Override the formatting locale's default currency symbol with the
* specified currency code (specified via the "currencyCode" attribute) or
* currency symbol (specified via the "currencySymbol" attribute).
*
* If both "currencyCode" and "currencySymbol" are present,
* "currencyCode" takes precedence over "currencySymbol" if the
* java.util.Currency class is defined in the container's runtime (that
* is, if the container's runtime is J2SE 1.4 or greater), and
* "currencySymbol" takes precendence over "currencyCode" otherwise.
*
* If only "currencyCode" is given, it is used as a currency symbol if
* java.util.Currency is not defined.
*
* Example:
*
* JDK "currencyCode" "currencySymbol" Currency symbol being displayed
* -----------------------------------------------------------------------
* all --- --- Locale's default currency symbol
*
* <1.4 EUR --- EUR
* >=1.4 EUR --- Locale's currency symbol for Euro
*
* all --- \u20AC \u20AC
*
* <1.4 EUR \u20AC \u20AC
* >=1.4 EUR \u20AC Locale's currency symbol for Euro
*/
private void setCurrency(NumberFormat formatter, String currencyCode, String currencySymbol) throws Exception {
String code = null;
String symbol = null;
if (currencyCode == null) {
if (currencySymbol == null) {
return;
}
symbol = currencySymbol;
} else if (currencySymbol != null) {
if (currencyClass != null) {
code = currencyCode;
} else {
symbol = currencySymbol;
}
} else if (currencyClass != null) {
code = currencyCode;
} else {
symbol = currencyCode;
}
if (code != null) {
Object[] methodArgs = new Object[1];
/*
* java.util.Currency.getInstance()
*/
Method m = currencyClass.getMethod("getInstance", new Class[] {String.class});
methodArgs[0] = code;
Object currency = m.invoke(null, methodArgs);
/*
* java.text.NumberFormat.setCurrency()
*/
Class[] paramTypes = new Class[1];
paramTypes[0] = currencyClass;
Class numberFormatClass = Class.forName("java.text.NumberFormat");
m = numberFormatClass.getMethod("setCurrency", paramTypes);
methodArgs[0] = currency;
m.invoke(formatter, methodArgs);
} else {
/*
* Let potential ClassCastException propagate up (will almost
* never happen)
*/
DecimalFormat df = (DecimalFormat) formatter;
DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
dfs.setCurrencySymbol(symbol);
df.setDecimalFormatSymbols(dfs);
}
}
}
// formatDate tag (borrows from Jakarta taglibs JSTL)
static class StartFormatDate extends LocaleAwareInstruction {
private static final String DATE = "date";
private static final String TIME = "time";
private static final String DATETIME = "both";
JXTExpression var;
JXTExpression value;
JXTExpression type;
JXTExpression pattern;
JXTExpression timeZone;
JXTExpression dateStyle;
JXTExpression timeStyle;
JXTExpression locale;
StartFormatDate(StartElement raw,
JXTExpression var,
JXTExpression value,
JXTExpression type,
JXTExpression pattern,
JXTExpression timeZone,
JXTExpression dateStyle,
JXTExpression timeStyle,
JXTExpression locale) {
super(raw, locale);
this.var = var;
this.value = value;
this.type = type;
this.pattern = pattern;
this.timeZone = timeZone;
this.dateStyle = dateStyle;
this.timeStyle = timeStyle;
}
String format(JexlContext jexl, JXPathContext jxp)
throws Exception {
String var = getStringValue(this.var, jexl, jxp);
Object value = getValue(this.value, jexl, jxp);
String pattern = getStringValue(this.pattern, jexl, jxp);
Object timeZone = getValue(this.timeZone, jexl, jxp);
String type = getStringValue(this.type, jexl, jxp);
String timeStyle = getStringValue(this.timeStyle, jexl, jxp);
String dateStyle = getStringValue(this.dateStyle, jexl, jxp);
String formatted = null;
// Create formatter
Locale locale = getLocale(jexl, jxp);
DateFormat formatter = createFormatter(locale, type, dateStyle, timeStyle);
// Apply pattern, if present
if (pattern != null) {
try {
((SimpleDateFormat) formatter).applyPattern(pattern);
} catch (ClassCastException cce) {
formatter = new SimpleDateFormat(pattern, locale);
}
}
// Set time zone
TimeZone tz = null;
if ((timeZone instanceof String) && timeZone.equals("")) {
timeZone = null;
}
if (timeZone != null) {
if (timeZone instanceof String) {
tz = TimeZone.getTimeZone((String) timeZone);
} else if (timeZone instanceof TimeZone) {
tz = (TimeZone) timeZone;
} else {
throw new IllegalArgumentException("Illegal timeZone value: \""+timeZone+"\"");
}
}
if (tz != null) {
formatter.setTimeZone(tz);
}
formatted = formatter.format(value);
if (var != null) {
jexl.getVars().put(var, formatted);
jxp.getVariables().declareVariable(var, formatted);
return null;
}
return formatted;
}
private DateFormat createFormatter(Locale loc, String type, String dateStyle, String timeStyle)
throws Exception {
DateFormat formatter = null;
if ((type == null) || DATE.equalsIgnoreCase(type)) {
formatter = DateFormat.getDateInstance(getStyle(dateStyle), loc);
} else if (TIME.equalsIgnoreCase(type)) {
formatter = DateFormat.getTimeInstance(getStyle(timeStyle), loc);
} else if (DATETIME.equalsIgnoreCase(type)) {
formatter = DateFormat.getDateTimeInstance(getStyle(dateStyle), getStyle(timeStyle), loc);
} else {
throw new IllegalArgumentException("Invalid type: \""+ type+"\"");
}
return formatter;
}
private static final String DEFAULT = "default";
private static final String SHORT = "short";
private static final String MEDIUM = "medium";
private static final String LONG = "long";
private static final String FULL = "full";
private int getStyle(String style) {
int ret = DateFormat.DEFAULT;
if (style != null) {
if (DEFAULT.equalsIgnoreCase(style)) {
ret = DateFormat.DEFAULT;
} else if (SHORT.equalsIgnoreCase(style)) {
ret = DateFormat.SHORT;
} else if (MEDIUM.equalsIgnoreCase(style)) {
ret = DateFormat.MEDIUM;
} else if (LONG.equalsIgnoreCase(style)) {
ret = DateFormat.LONG;
} else if (FULL.equalsIgnoreCase(style)) {
ret = DateFormat.FULL;
} else {
throw new IllegalArgumentException("Invalid style: \"" + style + "\": should be \"default\" or \"short\" or \"medium\" or \"long\" or \"full\"");
}
}
return ret;
}
}
static class Parser implements ContentHandler, LexicalHandler {
StartDocument startEvent;
Event lastEvent;
Stack stack = new Stack();
Locator locator;
Location charLocation;
StringBuffer charBuf;
public Parser() {
// EMPTY
}
StartDocument getStartEvent() {
return this.startEvent;
}
void recycle() {
startEvent = null;
lastEvent = null;
stack.clear();
locator = null;
charLocation = null;
charBuf = null;
}
private void addEvent(Event ev) throws SAXException {
if (ev != null) {
if (lastEvent == null) {
lastEvent = startEvent = new StartDocument(LocationUtils.getLocation(locator, "template"));
} else {
flushChars();
}
lastEvent.next = ev;
lastEvent = ev;
} else {
throw new NullPointerException("null event");
}
}
void flushChars() throws SAXException {
if (charBuf != null) {
char[] chars = new char[charBuf.length()];
charBuf.getChars(0, charBuf.length(), chars, 0);
Characters ev = new Characters(charLocation, chars, 0, chars.length);
lastEvent.next = ev;
lastEvent = ev;
charLocation = null;
charBuf = null;
}
}
public void characters(char[] ch, int start, int length)
throws SAXException {
if (charBuf == null) {
charBuf = new StringBuffer(length);
charLocation = LocationUtils.getLocation(locator, "[text]");
}
charBuf.append(ch, start, length);
}
public void endDocument() throws SAXException {
StartDocument startDoc = (StartDocument)stack.pop();
EndDocument endDoc = new EndDocument(LocationUtils.getLocation(locator, "template"));
startDoc.endDocument = endDoc;
addEvent(endDoc);
}
public void endElement(String namespaceURI, String localName, String raw) throws SAXException {
Event start = (Event)stack.pop();
Event newEvent = null;
if (NS.equals(namespaceURI)) {
StartInstruction startInstruction = (StartInstruction)start;
EndInstruction endInstruction = new EndInstruction(LocationUtils.getLocation(locator, "<"+raw+">"), startInstruction);
newEvent = endInstruction;
if (start instanceof StartWhen) {
StartWhen startWhen = (StartWhen)start;
StartChoose startChoose = (StartChoose)stack.peek();
if (startChoose.firstChoice != null) {
StartWhen w = startChoose.firstChoice;
while (w.nextChoice != null) {
w = w.nextChoice;
}
w.nextChoice = startWhen;
} else {
startChoose.firstChoice = startWhen;
}
} else if (start instanceof StartOtherwise) {
StartOtherwise startOtherwise = (StartOtherwise)start;
StartChoose startChoose = (StartChoose)stack.peek();
startChoose.otherwise = startOtherwise;
}
} else {
StartElement startElement = (StartElement)start;
newEvent = startElement.endElement = new EndElement(LocationUtils.getLocation(locator, "<"+raw+">"), startElement);
}
addEvent(newEvent);
if (start instanceof StartDefine) {
StartDefine startDefine = (StartDefine)start;
startDefine.finish();
}
}
public void endPrefixMapping(String prefix) throws SAXException {
EndPrefixMapping endPrefixMapping = new EndPrefixMapping(LocationUtils.getLocation(locator, null), prefix);
addEvent(endPrefixMapping);
}
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
Event ev = new IgnorableWhitespace(LocationUtils.getLocation(locator, null), ch, start, length);
addEvent(ev);
}
public void processingInstruction(String target, String data) throws SAXException {
Event pi = new ProcessingInstruction(LocationUtils.getLocation(locator, null), target, data);
addEvent(pi);
}
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
public void skippedEntity(String name) throws SAXException {
addEvent(new SkippedEntity(LocationUtils.getLocation(locator, null), name));
}
public void startDocument() {
startEvent = new StartDocument(LocationUtils.getLocation(locator, null));
lastEvent = startEvent;
stack.push(lastEvent);
}
public void startElement(String namespaceURI, String localName, String qname, Attributes attrs) throws SAXException {
Event newEvent = null;
Location locator = LocationUtils.getLocation(this.locator, "<"+qname+">");
AttributesImpl elementAttributes = new AttributesImpl(attrs);
int attributeCount = elementAttributes.getLength();
for (int i = 0; i < attributeCount; i++) {
String attributeURI = elementAttributes.getURI(i);
if (StringUtils.equals(attributeURI, NS)) {
getStartEvent().templateProperties.put(elementAttributes.getLocalName(i),
compileExpr(elementAttributes.getValue(i), null, locator));
elementAttributes.removeAttribute(i--);
}
}
StartElement startElement = new StartElement(locator, namespaceURI,
localName, qname, elementAttributes);
if (NS.equals(namespaceURI)) {
if (localName.equals(FOR_EACH)) {
String items = attrs.getValue("items");
String select = attrs.getValue("select");
JXTExpression begin = compileInt(attrs.getValue("begin"), FOR_EACH, locator);
JXTExpression end = compileInt(attrs.getValue("end"), FOR_EACH, locator);
JXTExpression step = compileInt(attrs.getValue("step"), FOR_EACH, locator);
JXTExpression var = compileExpr(attrs.getValue("var"), null, locator);
JXTExpression varStatus = compileExpr(attrs.getValue("varStatus"), null, locator);
if (items == null) {
if (select == null && (begin == null || end == null)) {
throw new JXTException("forEach: \"select\", \"items\", or both \"begin\" and \"end\" must be specified", locator, null);
}
} else if (select != null) {
throw new JXTException("forEach: only one of \"select\" or \"items\" may be specified", locator, null);
}
JXTExpression expr = compileExpr(items == null ? select : items, null, locator);
String lenientValue = attrs.getValue("lenient");
Boolean lenient = (lenientValue == null) ? null : Boolean.valueOf(lenientValue);
StartForEach startForEach = new StartForEach(startElement, expr, var, varStatus, begin, end, step,lenient);
newEvent = startForEach;
} else if (localName.equals(FORMAT_NUMBER)) {
JXTExpression value = compileExpr(attrs.getValue("value"), null, locator);
JXTExpression type = compileExpr(attrs.getValue("type"), null, locator);
JXTExpression pattern = compileExpr(attrs.getValue("pattern"), null, locator);
JXTExpression currencyCode = compileExpr(attrs.getValue("currencyCode"), null, locator);
JXTExpression currencySymbol = compileExpr(attrs.getValue("currencySymbol"), null, locator);
JXTExpression isGroupingUsed = compileBoolean(attrs.getValue("isGroupingUsed"), null, locator);
JXTExpression maxIntegerDigits = compileInt(attrs.getValue("maxIntegerDigits"), null, locator);
JXTExpression minIntegerDigits = compileInt(attrs.getValue("minIntegerDigits"), null, locator);
JXTExpression maxFractionDigits = compileInt(attrs.getValue("maxFractionDigits"), null, locator);
JXTExpression minFractionDigits = compileInt(attrs.getValue("minFractionDigits"), null, locator);
JXTExpression var = compileExpr(attrs.getValue("var"), null, locator);
JXTExpression locale = compileExpr(attrs.getValue("locale"), null, locator);
StartFormatNumber startFormatNumber =
new StartFormatNumber(startElement,
var,
value,
type,
pattern,
currencyCode,
currencySymbol,
isGroupingUsed,
maxIntegerDigits,
minIntegerDigits,
maxFractionDigits,
minFractionDigits,
locale);
newEvent = startFormatNumber;
} else if (localName.equals(FORMAT_DATE)) {
JXTExpression var = compileExpr(attrs.getValue("var"), null, locator);
JXTExpression value = compileExpr(attrs.getValue("value"), null, locator);
JXTExpression type = compileExpr(attrs.getValue("type"), null, locator);
JXTExpression pattern = compileExpr(attrs.getValue("pattern"), null, locator);
JXTExpression timeZone = compileExpr(attrs.getValue("timeZone"), null, locator);
JXTExpression dateStyle = compileExpr(attrs.getValue("dateStyle"), null, locator);
JXTExpression timeStyle = compileExpr(attrs.getValue("timeStyle"), null, locator);
JXTExpression locale = compileExpr(attrs.getValue("locale"), null, locator);
StartFormatDate startFormatDate =
new StartFormatDate(startElement,
var,
value,
type,
pattern,
timeZone,
dateStyle,
timeStyle,
locale);
newEvent = startFormatDate;
} else if (localName.equals(CHOOSE)) {
StartChoose startChoose = new StartChoose(startElement);
newEvent = startChoose;
} else if (localName.equals(WHEN)) {
if (stack.size() == 0 || !(stack.peek() instanceof StartChoose)) {
throw new JXTException("<when> must be within <choose>", locator, null);
}
String test = attrs.getValue("test");
if (test != null) {
JXTExpression expr = compileExpr(test, "when: \"test\": ", locator);
StartWhen startWhen = new StartWhen(startElement, expr);
newEvent = startWhen;
} else {
throw new JXTException("when: \"test\" is required", locator, null);
}
} else if (localName.equals(OUT)) {
String value = attrs.getValue("value");
if (value != null) {
JXTExpression expr = compileExpr(value, "out: \"value\": ", locator);
String lenientValue = attrs.getValue("lenient");
Boolean lenient = lenientValue == null ? null : Boolean.valueOf(lenientValue);
newEvent = new StartOut(startElement, expr, lenient);
} else {
throw new JXTException("out: \"value\" is required", locator, null);
}
} else if (localName.equals(OTHERWISE)) {
if (stack.size() != 0 && (stack.peek() instanceof StartChoose)) {
StartOtherwise startOtherwise = new StartOtherwise(startElement);
newEvent = startOtherwise;
} else {
throw new JXTException( "<otherwise> must be within <choose>", locator, null);
}
} else if (localName.equals(IF)) {
String test = attrs.getValue("test");
if (test != null) {
JXTExpression expr = compileExpr(test, "if: \"test\": ", locator);
StartIf startIf = new StartIf(startElement, expr);
newEvent = startIf;
} else {
throw new JXTException("if: \"test\" is required", locator, null);
}
} else if (localName.equals(MACRO)) {
// <macro name="myTag" targetNamespace="myNamespace">
// <parameter name="paramName" required="Boolean" default="value"/>
// body
// </macro>
String namespace = StringUtils.defaultString(attrs.getValue("targetNamespace"));
String name = attrs.getValue("name");
if (name != null) {
StartDefine startDefine = new StartDefine(startElement, namespace, name);
newEvent = startDefine;
} else {
throw new JXTException("macro: \"name\" is required", locator, null);
}
} else if (localName.equals(PARAMETER)) {
if (stack.size() == 0 || !(stack.peek() instanceof StartDefine)) {
throw new JXTException("<parameter> not allowed here", locator, null);
} else {
String name = attrs.getValue("name");
String optional = attrs.getValue("optional");
String default_ = attrs.getValue("default");
if (name != null) {
StartParameter startParameter = new StartParameter(startElement, name, optional, default_);
newEvent = startParameter;
} else {
throw new JXTException("parameter: \"name\" is required", locator, null);
}
}
} else if (localName.equals(EVALBODY)) {
newEvent = new StartEvalBody(startElement);
} else if (localName.equals(EVAL)) {
String value = attrs.getValue("select");
JXTExpression valueExpr = compileExpr(value, "eval: \"select\":", locator);
newEvent = new StartEval(startElement, valueExpr);
} else if (localName.equals(SET)) {
String var = attrs.getValue("var");
String value = attrs.getValue("value");
JXTExpression varExpr = null;
JXTExpression valueExpr = null;
if (var != null) {
varExpr = compileExpr(var, "set: \"var\":", locator);
}
if (value != null) {
valueExpr = compileExpr(value, "set: \"value\":", locator);
}
StartSet startSet = new StartSet(startElement, varExpr, valueExpr);
newEvent = startSet;
} else if (localName.equals(IMPORT)) {
// <import uri="${root}/foo/bar.xml" context="${foo}"/>
AttributeEvent uri = null;
Iterator iter = startElement.attributeEvents.iterator();
while (iter.hasNext()) {
AttributeEvent e = (AttributeEvent)iter.next();
if (e.localName.equals("uri")) {
uri = e;
break;
}
}
if (uri != null) {
// If "context" is present then its value will be used
// as the context object in the imported template
String select = attrs.getValue("context");
JXTExpression expr = null;
if (select != null) {
expr = compileExpr(select, "import: \"context\": ", locator);
}
StartImport startImport = new StartImport(startElement, uri, expr);
newEvent = startImport;
} else {
throw new JXTException("import: \"uri\" is required", locator, null);
}
} else if (localName.equals(TEMPLATE)) {
StartTemplate startTemplate = new StartTemplate(startElement);
newEvent = startTemplate;
} else if (localName.equals(COMMENT)) {
// <jx:comment>This will be parsed</jx:comment>
StartComment startJXComment = new StartComment(startElement);
newEvent = startJXComment;
} else {
throw new JXTException("unrecognized tag: " + localName, locator, null);
}
} else {
newEvent = startElement;
}
stack.push(newEvent);
addEvent(newEvent);
}
public void startPrefixMapping(String prefix, String uri) throws SAXException {
addEvent(new StartPrefixMapping(LocationUtils.getLocation(locator, null), prefix, uri));
}
public void comment(char ch[], int start, int length) throws SAXException {
// DO NOTHING
}
public void endCDATA() throws SAXException {
addEvent(new EndCDATA(LocationUtils.getLocation(locator, null)));
}
public void endDTD() throws SAXException {
addEvent(new EndDTD(LocationUtils.getLocation(locator, null)));
}
public void endEntity(String name) throws SAXException {
addEvent(new EndEntity(LocationUtils.getLocation(locator, null), name));
}
public void startCDATA() throws SAXException {
addEvent(new StartCDATA(LocationUtils.getLocation(locator, null)));
}
public void startDTD(String name, String publicId, String systemId) throws SAXException {
addEvent(new StartDTD(LocationUtils.getLocation(locator, null), name, publicId, systemId));
}
public void startEntity(String name) throws SAXException {
addEvent(new StartEntity(LocationUtils.getLocation(locator, null), name));
}
}
/**
* Adapter that makes this generator usable as a transformer
* (Note there is a performance penalty for this however:
* you effectively recompile the template for every instance document)
*/
public static class TransformerAdapter extends ServiceableTransformer {
static class TemplateConsumer extends Parser implements XMLConsumer {
public TemplateConsumer() {
this.gen = new JXTemplateGenerator();
}
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters)
throws ProcessingException, SAXException, IOException {
this.gen.setup(resolver, objectModel, null, parameters);
}
public void service(ServiceManager manager)
throws ServiceException {
this.gen.service(manager);
}
public void endDocument() throws SAXException {
super.endDocument();
gen.performGeneration(gen.getConsumer(), gen.getJexlContext(), gen.getJXPathContext(), null, getStartEvent(), null);
}
void setConsumer(XMLConsumer consumer) {
gen.setConsumer(consumer);
}
void recycle() {
super.recycle();
gen.recycle();
}
JXTemplateGenerator gen;
}
TemplateConsumer templateConsumer = new TemplateConsumer();
public void recycle() {
super.recycle();
templateConsumer.recycle();
}
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters)
throws ProcessingException, SAXException, IOException {
super.setup(resolver, objectModel, src, parameters);
templateConsumer.setup(resolver, objectModel, src, parameters);
}
public void service(ServiceManager manager)
throws ServiceException {
super.service(manager);
templateConsumer.service(manager);
}
public void setConsumer(XMLConsumer xmlConsumer) {
super.setConsumer(templateConsumer);
templateConsumer.setConsumer(xmlConsumer);
}
}
private JXPathContext jxpathContext;
private MyJexlContext globalJexlContext;
private Variables variables;
private static Map cache = new HashMap();
private Source inputSource;
private Map definitions;
private Map cocoon;
private JXPathContext getJXPathContext() {
return jxpathContext;
}
private MyJexlContext getJexlContext() {
return globalJexlContext;
}
/* (non-Javadoc)
* @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
*/
public void recycle() {
if (this.resolver != null) {
this.resolver.release(this.inputSource);
}
this.inputSource = null;
this.jxpathContext = null;
this.globalJexlContext = null;
this.variables = null;
this.definitions = null;
this.cocoon = null;
this.namespaces.clear();
super.recycle();
}
/* (non-Javadoc)
* @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters)
*/
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters parameters)
throws ProcessingException, SAXException, IOException {
super.setup(resolver, objectModel, src, parameters);
if (src != null) {
try {
this.inputSource = resolver.resolveURI(src);
} catch (SourceException se) {
throw SourceUtil.handle("Error during resolving of '" + src + "'.", se);
}
final String uri = inputSource.getURI();
boolean regenerate = false;
StartDocument startEvent = null;
synchronized (cache) {
startEvent = (StartDocument)cache.get(uri);
if (startEvent != null) {
int valid = SourceValidity.UNKNOWN;
if (startEvent.compileTime != null) {
valid = startEvent.compileTime.isValid();
}
if (valid == SourceValidity.UNKNOWN && startEvent.compileTime != null) {
SourceValidity validity = inputSource.getValidity();
valid = startEvent.compileTime.isValid(validity);
}
if (valid != SourceValidity.VALID) {
regenerate = true;
}
} else {
regenerate = true;
}
}
if (regenerate) {
Parser parser = new Parser();
SourceUtil.parse(this.manager, this.inputSource, parser);
startEvent = parser.getStartEvent();
startEvent.compileTime = this.inputSource.getValidity();
synchronized (cache) {
cache.put(uri, startEvent);
}
}
}
Object bean = FlowHelper.getContextObject(objectModel);
WebContinuation kont = FlowHelper.getWebContinuation(objectModel);
setContexts(bean, kont, parameters, objectModel);
this.definitions = new HashMap();
}
private void fillContext(Object contextObject, Map map) {
if (contextObject != null) {
// Hack: I use jxpath to populate the context object's properties
// in the jexl context
final JXPathBeanInfo bi = JXPathIntrospector.getBeanInfo(contextObject.getClass());
if (bi.isDynamic()) {
Class cl = bi.getDynamicPropertyHandlerClass();
try {
DynamicPropertyHandler h = (DynamicPropertyHandler)cl.newInstance();
String[] result = h.getPropertyNames(contextObject);
int len = result.length;
for (int i = 0; i < len; i++) {
try {
map.put(result[i], h.getProperty(contextObject, result[i]));
} catch (Exception exc) {
exc.printStackTrace();
}
}
} catch (Exception ignored) {
ignored.printStackTrace();
}
} else {
PropertyDescriptor[] props = bi.getPropertyDescriptors();
int len = props.length;
for (int i = 0; i < len; i++) {
try {
Method read = props[i].getReadMethod();
if (read != null) {
map.put(props[i].getName(), read.invoke(contextObject, null));
}
} catch (Exception ignored) {
ignored.printStackTrace();
}
}
}
}
}
private void setContexts(Object contextObject, WebContinuation kont, Parameters parameters, Map objectModel) {
final Request request = ObjectModelHelper.getRequest(objectModel);
final Object session = request.getSession(false);
final Object app = ObjectModelHelper.getContext(objectModel);
cocoon = new HashMap();
Object fomRequest = FOM_JavaScriptFlowHelper.getFOM_Request(objectModel);
cocoon.put("request", fomRequest != null ? fomRequest : request);
if (session != null) {
cocoon.put("session", FOM_JavaScriptFlowHelper.getFOM_Session(objectModel));
}
cocoon.put("context", FOM_JavaScriptFlowHelper.getFOM_Context(objectModel));
cocoon.put("continuation", FOM_JavaScriptFlowHelper.getFOM_WebContinuation(objectModel));
cocoon.put("parameters", parameters);
this.variables = new MyVariables(cocoon, contextObject, kont, request, session, app, parameters);
Map map;
if (contextObject instanceof Map) {
map = (Map)contextObject;
} else {
map = new HashMap();
fillContext(contextObject, map);
}
jxpathContext = jxpathContextFactory.newContext(null, contextObject);
jxpathContext.setNamespaceContextPointer(new NamespacesTablePointer(namespaces));
jxpathContext.setVariables(variables);
jxpathContext.setLenient(parameters.getParameterAsBoolean("lenient-xpath", false));
globalJexlContext = new MyJexlContext();
globalJexlContext.setVars(map);
map = globalJexlContext.getVars();
map.put("cocoon", cocoon);
if (contextObject != null) {
map.put("flowContext", contextObject);
// FIXME (VG): Is this required (what it's used for - examples)?
// Here I use Rhino's live-connect objects to allow Jexl to call
// java constructors
Object javaPkg = FOM_JavaScriptFlowHelper.getJavaPackage(objectModel);
Object pkgs = FOM_JavaScriptFlowHelper.getPackages(objectModel);
map.put("java", javaPkg);
map.put("Packages", pkgs);
}
if (kont != null) {
map.put("continuation", kont);
}
map.put("request", request);
map.put("context", app);
map.put("parameters", parameters);
if (session != null) {
map.put("session", session);
}
}
/* (non-Javadoc)
* @see org.apache.cocoon.generation.Generator#generate()
*/
public void generate()
throws IOException, SAXException, ProcessingException {
final String cacheKey = this.inputSource.getURI();
StartDocument startEvent;
synchronized (cache) {
startEvent = (StartDocument)cache.get(cacheKey);
}
performGeneration(this.xmlConsumer, globalJexlContext, jxpathContext, null, startEvent, null);
}
private void performGeneration(final XMLConsumer consumer, MyJexlContext jexlContext, JXPathContext jxpathContext,
StartElement macroCall, Event startEvent, Event endEvent) throws SAXException {
cocoon.put("consumer", consumer);
RedundantNamespacesFilter filter = new RedundantNamespacesFilter(this.xmlConsumer);
// EventPrinterPipe log = new EventPrinterPipe();
// log.setConsumer(filter);
execute(filter, globalJexlContext, jxpathContext, null, startEvent, null);
}
interface CharHandler {
public void characters(char[] ch, int offset, int length)
throws SAXException;
}
private void characters(JexlContext jexlContext, JXPathContext jxpathContext, TextEvent event, CharHandler handler)
throws SAXException {
Iterator iter = event.substitutions.iterator();
while (iter.hasNext()) {
Object subst = iter.next();
char[] chars;
if (subst instanceof char[]) {
chars = (char[])subst;
} else {
JXTExpression expr = (JXTExpression)subst;
try {
Object val = getValue(expr, jexlContext, jxpathContext);
chars = val != null ? val.toString().toCharArray() : ArrayUtils.EMPTY_CHAR_ARRAY;
} catch (Exception e) {
throw new JXTException(e.getMessage(), event.location, e);
}
}
handler.characters(chars, 0, chars.length);
}
}
/** dump a DOM document, using an IncludeXMLConsumer to filter out start/end document events */
private void executeDOM(final XMLConsumer consumer, MyJexlContext jexlContext, JXPathContext jxpathContext, Node node)
throws SAXException {
IncludeXMLConsumer includer = new IncludeXMLConsumer(consumer);
DOMStreamer streamer = new DOMStreamer(includer);
streamer.stream(node);
}
private void call(Location location, StartElement macroCall, final XMLConsumer consumer, MyJexlContext jexlContext,
JXPathContext jxpathContext, Event startEvent, Event endEvent) throws SAXException {
try {
execute(consumer, jexlContext, jxpathContext, macroCall, startEvent, endEvent);
} catch (Exception exc) {
throw new JXTException(macroCall.localName + ": " + exc.getMessage(), location, exc);
}
}
public static class LoopTagStatus {
Object current;
int index;
int count;
boolean first;
boolean last;
int begin;
int end;
int step;
public Object getCurrent() {
return current;
}
public int getIndex() {
return index;
}
public int getCount() {
return count;
}
public boolean isFirst() {
return first;
}
public boolean isLast() {
return last;
}
public int getBegin() {
return begin;
}
public int getEnd() {
return end;
}
public int getStep() {
return step;
}
}
private void execute(final XMLConsumer consumer, MyJexlContext jexlContext, JXPathContext jxpathContext,
StartElement macroCall, Event startEvent, Event endEvent) throws SAXException {
Event ev = startEvent;
LocationFacade loc = new LocationFacade(ev.location);
consumer.setDocumentLocator(loc);
while (ev != endEvent) {
loc.setDocumentLocation(ev.location);
if (ev instanceof Characters) {
TextEvent text = (TextEvent)ev;
Iterator iter = text.substitutions.iterator();
while (iter.hasNext()) {
Object subst = iter.next();
char[] chars;
if (subst instanceof char[]) {
chars = (char[])subst;
} else {
JXTExpression expr = (JXTExpression)subst;
try {
Object val = getNode(expr, jexlContext, jxpathContext);
if (val instanceof Node) {
executeDOM(consumer, jexlContext, jxpathContext, (Node)val);
continue;
} else if (val instanceof NodeList) {
NodeList nodeList = (NodeList)val;
int len = nodeList.getLength();
for (int i = 0; i < len; i++) {
Node n = nodeList.item(i);
executeDOM(consumer, jexlContext, jxpathContext, n);
}
continue;
} else if (val instanceof Node[]) {
Node[] nodeList = (Node[])val;
int len = nodeList.length;
for (int i = 0; i < len; i++) {
Node n = nodeList[i];
executeDOM(consumer, jexlContext, jxpathContext, n);
}
continue;
} else if (val instanceof XMLizable) {
((XMLizable)val).toSAX(new IncludeXMLConsumer(consumer));
continue;
}
chars = val != null ? val.toString().toCharArray() : ArrayUtils.EMPTY_CHAR_ARRAY;
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
}
consumer.characters(chars, 0, chars.length);
}
} else if (ev instanceof EndElement) {
EndElement endElement = (EndElement)ev;
StartElement startElement = endElement.startElement;
StartDefine def =
(StartDefine)definitions.get(startElement.qname);
if (def == null) {
consumer.endElement(startElement.namespaceURI, startElement.localName, startElement.raw);
namespaces.leaveScope(consumer);
}
} else if (ev instanceof EndPrefixMapping) {
EndPrefixMapping endPrefixMapping = (EndPrefixMapping)ev;
namespaces.removeDeclaration(endPrefixMapping.prefix);
} else if (ev instanceof IgnorableWhitespace) {
TextEvent text = (TextEvent)ev;
characters(jexlContext, jxpathContext, text, new CharHandler() {
public void characters(char[] ch, int offset, int len) throws SAXException {
consumer.ignorableWhitespace(ch, offset, len);
}
});
} else if (ev instanceof SkippedEntity) {
SkippedEntity skippedEntity = (SkippedEntity)ev;
consumer.skippedEntity(skippedEntity.name);
} else if (ev instanceof StartIf) {
StartIf startIf = (StartIf)ev;
Object val;
try {
val = getValue(startIf.test, jexlContext, jxpathContext, Boolean.TRUE);
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
boolean result = false;
if (val instanceof Boolean) {
result = ((Boolean)val).booleanValue();
} else {
result = (val != null);
}
if (!result) {
ev = startIf.endInstruction.next;
continue;
}
} else if (ev instanceof StartForEach) {
StartForEach startForEach = (StartForEach)ev;
final Object items = startForEach.items;
Iterator iter = null;
int begin, end, step;
String var, varStatus;
try {
if (items != null) {
JXTExpression expr = (JXTExpression)items;
if (expr.compiledExpression instanceof CompiledExpression) {
CompiledExpression compiledExpression = (CompiledExpression)expr.compiledExpression;
Object val =
compiledExpression.getPointer(jxpathContext, expr.raw).getNode();
// FIXME: workaround for JXPath bug
iter = val instanceof NativeArray ? new JSIntrospector.NativeArrayIterator((NativeArray)val) :
compiledExpression.iteratePointers(jxpathContext);
} else if (expr.compiledExpression instanceof Expression) {
Expression e = (Expression)expr.compiledExpression;
Object result = e.evaluate(jexlContext);
if (result != null) {
iter = Introspector.getUberspect().getIterator(result,
new Info(ev.location.getURI(), ev.location.getLineNumber(), ev.location.getColumnNumber()));
}
if (iter == null) {
iter = EMPTY_ITER;
}
} else {
// literal value
iter = new Iterator() {
Object val = items;
public boolean hasNext() {
return val != null;
}
public Object next() {
Object res = val;
val = null;
return res;
}
public void remove() {
// EMPTY
}
};
}
} else {
iter = NULL_ITER;
}
begin = startForEach.begin == null ? 0 :
getIntValue(startForEach.begin, jexlContext, jxpathContext);
end = startForEach.end == null ? Integer.MAX_VALUE :
getIntValue(startForEach.end, jexlContext, jxpathContext);
step = startForEach.step == null ? 1 :
getIntValue(startForEach.step, jexlContext, jxpathContext);
var = getStringValue(startForEach.var, jexlContext, jxpathContext);
varStatus = getStringValue(startForEach.varStatus, jexlContext, jxpathContext);
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), ev.location, exc);
}
MyJexlContext localJexlContext = new MyJexlContext(jexlContext);
MyVariables localJXPathVariables = new MyVariables((MyVariables)jxpathContext.getVariables());
int i = 0;
// Move to the begin row
while (i < begin && iter.hasNext()) {
iter.next();
i++;
}
LoopTagStatus status = null;
if (varStatus != null) {
status = new LoopTagStatus();
status.begin = begin;
status.end = end;
status.step = step;
status.first = true;
localJexlContext.put(varStatus, status);
localJXPathVariables.declareVariable(varStatus, status);
}
int skipCounter, count = 1;
JXPathContext localJXPathContext = null;
while (i <= end && iter.hasNext()) {
Object value = iter.next();
if (value instanceof Pointer) {
Pointer ptr = (Pointer)value;
localJXPathContext = jxpathContext.getRelativeContext(ptr);
localJXPathContext.setNamespaceContextPointer(new NamespacesTablePointer(namespaces));
try {
value = ptr.getNode();
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), ev.location, null);
}
} else {
localJXPathContext = jxpathContextFactory.newContext(jxpathContext, value);
localJXPathContext.setNamespaceContextPointer(new NamespacesTablePointer(namespaces));
}
localJXPathContext.setVariables(localJXPathVariables);
if (var != null) {
localJexlContext.put(var, value);
}
if (status != null) {
status.index = i;
status.count = count;
status.first = i == begin;
status.current = value;
status.last = (i == end || !iter.hasNext());
}
execute(consumer, localJexlContext, localJXPathContext, macroCall, startForEach.next, startForEach.endInstruction);
// Skip rows
skipCounter = step;
while (--skipCounter > 0 && iter.hasNext()) {
iter.next();
}
// Increase index
i += step;
count++;
}
ev = startForEach.endInstruction.next;
continue;
} else if (ev instanceof StartChoose) {
StartChoose startChoose = (StartChoose)ev;
StartWhen startWhen = startChoose.firstChoice;
while (startWhen != null) {
Object val;
try {
val = getValue(startWhen.test, jexlContext, jxpathContext, Boolean.TRUE);
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
boolean result;
if (val instanceof Boolean) {
result = ((Boolean)val).booleanValue();
} else {
result = (val != null);
}
if (result) {
execute(consumer, jexlContext, jxpathContext, macroCall, startWhen.next, startWhen.endInstruction);
break;
}
startWhen = startWhen.nextChoice;
}
if (startWhen == null && startChoose.otherwise != null) {
execute(consumer, jexlContext, jxpathContext, macroCall, startChoose.otherwise.next, startChoose.otherwise.endInstruction);
}
ev = startChoose.endInstruction.next;
continue;
} else if (ev instanceof StartSet) {
StartSet startSet = (StartSet)ev;
Object value = null;
String var = null;
try {
if (startSet.var != null) {
var = getStringValue(startSet.var, jexlContext, jxpathContext);
}
if (startSet.value != null) {
value = getNode(startSet.value, jexlContext, jxpathContext);
}
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), ev.location, exc);
}
if (value == null) {
NodeList nodeList = toDOMNodeList("set", startSet, jexlContext, macroCall);
// JXPath doesn't handle NodeList, so convert it to an array
int len = nodeList.getLength();
Node[] nodeArr = new Node[len];
for (int i = 0; i < len; i++) {
nodeArr[i] = nodeList.item(i);
}
value = nodeArr;
}
if (var != null) {
jxpathContext.getVariables().declareVariable(var, value);
jexlContext.put(var, value);
}
ev = startSet.endInstruction.next;
continue;
} else if (ev instanceof StartElement) {
StartElement startElement = (StartElement)ev;
StartDefine def =
(StartDefine)definitions.get(startElement.qname);
if (def != null) {
Map attributeMap = new HashMap();
Iterator i = startElement.attributeEvents.iterator();
while (i.hasNext()) {
String attributeName;
Object attributeValue;
AttributeEvent attrEvent = (AttributeEvent) i.next();
attributeName = attrEvent.localName;
if (attrEvent instanceof CopyAttribute) {
CopyAttribute copy = (CopyAttribute)attrEvent;
attributeValue = copy.value;
} else if (attrEvent instanceof SubstituteAttribute) {
SubstituteAttribute substEvent = (SubstituteAttribute)attrEvent;
if (substEvent.substitutions.size() == 1 && substEvent.substitutions.get(0) instanceof JXTExpression) {
JXTExpression expr = (JXTExpression)substEvent.substitutions.get(0);
Object val;
try {
val = getNode(expr, jexlContext, jxpathContext);
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
attributeValue = val != null ? val : "";
} else {
StringBuffer buf = new StringBuffer();
Iterator iterSubst = substEvent.substitutions.iterator();
while (iterSubst.hasNext()) {
Subst subst = (Subst)iterSubst.next();
if (subst instanceof Literal) {
Literal lit = (Literal)subst;
buf.append(lit.value);
} else if (subst instanceof JXTExpression) {
JXTExpression expr = (JXTExpression)subst;
Object val;
try {
val = getValue(expr, jexlContext, jxpathContext);
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
buf.append(val != null ? val.toString() : "");
}
}
attributeValue = buf.toString();
}
} else {
throw new Error("this shouldn't have happened");
}
attributeMap.put(attributeName, attributeValue);
}
MyVariables parent =(MyVariables)jxpathContext.getVariables();
MyVariables vars = new MyVariables(parent);
MyJexlContext localJexlContext = new MyJexlContext(jexlContext);
HashMap macro = new HashMap();
macro.put("body", startElement);
macro.put("arguments", attributeMap);
localJexlContext.put("macro", macro);
vars.declareVariable("macro", macro);
Iterator iter = def.parameters.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry e = (Map.Entry)iter.next();
String key = (String)e.getKey();
StartParameter startParam = (StartParameter)e.getValue();
Object default_ = startParam.default_;
Object val = attributeMap.get(key);
if (val == null) {
val = default_;
}
localJexlContext.put(key, val);
vars.declareVariable(key, val);
}
JXPathContext localJXPathContext = jxpathContextFactory.newContext(null, jxpathContext.getContextBean());
localJXPathContext.setNamespaceContextPointer(new NamespacesTablePointer(namespaces));
localJXPathContext.setVariables(vars);
call(ev.location, startElement, consumer, localJexlContext, localJXPathContext, def.body, def.endInstruction);
ev = startElement.endElement.next;
continue;
}
Iterator i = startElement.attributeEvents.iterator();
AttributesImpl attrs = new AttributesImpl();
while (i.hasNext()) {
AttributeEvent attrEvent = (AttributeEvent)i.next();
if (attrEvent instanceof CopyAttribute) {
CopyAttribute copy = (CopyAttribute)attrEvent;
attrs.addAttribute(copy.namespaceURI, copy.localName, copy.raw, copy.type, copy.value);
} else if (attrEvent instanceof SubstituteAttribute) {
StringBuffer buf = new StringBuffer();
SubstituteAttribute substEvent = (SubstituteAttribute)attrEvent;
Iterator iterSubst = substEvent.substitutions.iterator();
while (iterSubst.hasNext()) {
Subst subst = (Subst)iterSubst.next();
if (subst instanceof Literal) {
Literal lit = (Literal)subst;
buf.append(lit.value);
} else if (subst instanceof JXTExpression) {
JXTExpression expr = (JXTExpression)subst;
Object val;
try {
val = getValue(expr, jexlContext, jxpathContext);
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
buf.append(val != null ? val.toString() : "");
}
}
attrs.addAttribute(attrEvent.namespaceURI, attrEvent.localName, attrEvent.raw, attrEvent.type, buf.toString());
}
}
namespaces.enterScope(consumer);
consumer.startElement(startElement.namespaceURI, startElement.localName, startElement.raw, attrs);
} else if (ev instanceof StartFormatNumber) {
StartFormatNumber startFormatNumber = (StartFormatNumber)ev;
try {
String result = startFormatNumber.format(jexlContext, jxpathContext);
if (result != null) {
char[] chars = result.toCharArray();
consumer.characters(chars, 0, chars.length);
}
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
} else if (ev instanceof StartFormatDate) {
StartFormatDate startFormatDate = (StartFormatDate)ev;
try {
String result = startFormatDate.format(jexlContext, jxpathContext);
if (result != null) {
char[] chars = result.toCharArray();
consumer.characters(chars, 0, chars.length);
}
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
} else if (ev instanceof StartPrefixMapping) {
StartPrefixMapping startPrefixMapping = (StartPrefixMapping)ev;
namespaces.addDeclaration(startPrefixMapping.prefix, startPrefixMapping.uri);
} else if (ev instanceof StartComment) {
StartComment startJXComment = (StartComment)ev;
// Parse the body of the comment
NodeList nodeList = toDOMNodeList("comment", startJXComment, jexlContext, macroCall);
// JXPath doesn't handle NodeList, so convert it to an array
int len = nodeList.getLength();
final StringBuffer buf = new StringBuffer();
Properties omit = XMLUtils.createPropertiesForXML(true);
for (int i = 0; i < len; i++) {
try {
buf.append(XMLUtils.serializeNode(nodeList.item(i), omit));
} catch (Exception e) {
throw new JXTException(e.getMessage(), startJXComment.location, e);
}
}
char[] chars = new char[buf.length()];
buf.getChars(0, chars.length, chars, 0);
consumer.comment(chars, 0, chars.length);
ev = startJXComment.endInstruction.next;
continue;
} else if (ev instanceof EndCDATA) {
consumer.endCDATA();
} else if (ev instanceof EndDTD) {
consumer.endDTD();
} else if (ev instanceof EndEntity) {
consumer.endEntity(((EndEntity)ev).name);
} else if (ev instanceof StartCDATA) {
consumer.startCDATA();
} else if (ev instanceof StartDTD) {
StartDTD startDTD = (StartDTD)ev;
consumer.startDTD(startDTD.name, startDTD.publicId, startDTD.systemId);
} else if (ev instanceof StartEntity) {
consumer.startEntity(((StartEntity)ev).name);
} else if (ev instanceof StartOut) {
StartOut startOut = (StartOut)ev;
Object val;
try {
val = getNode(startOut.compiledExpression, jexlContext, jxpathContext, startOut.lenient);
if (val instanceof Node) {
executeDOM(consumer, jexlContext, jxpathContext, (Node)val);
} else if (val instanceof NodeList) {
NodeList nodeList = (NodeList)val;
int len = nodeList.getLength();
for (int i = 0; i < len; i++) {
Node n = nodeList.item(i);
executeDOM(consumer, jexlContext, jxpathContext, n);
}
} else if (val instanceof Node[]) {
Node[] nodeList = (Node[])val;
int len = nodeList.length;
for (int i = 0;i < len; i++) {
Node n = nodeList[i];
executeDOM(consumer, jexlContext, jxpathContext, n);
}
} else if (val instanceof XMLizable) {
((XMLizable)val).toSAX(new IncludeXMLConsumer(consumer));
} else {
char[] ch = val == null ? ArrayUtils.EMPTY_CHAR_ARRAY : val.toString().toCharArray();
consumer.characters(ch, 0, ch.length);
}
} catch (Exception e) {
throw new JXTException(e.getMessage(), ev.location, e);
}
} else if (ev instanceof StartTemplate) {
// EMPTY
} else if (ev instanceof StartEval) {
StartEval startEval = (StartEval)ev;
JXTExpression expr = startEval.value;
try {
Object val = getNode(expr, jexlContext, jxpathContext);
if (!(val instanceof StartElement)) {
throw new Exception("macro invocation required instead of: " + val);
}
StartElement call = (StartElement)val;
execute(consumer, jexlContext, jxpathContext, call, call.next, call.endElement);
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), ev.location, exc);
}
ev = startEval.endInstruction.next;
continue;
} else if (ev instanceof StartEvalBody) {
StartEvalBody startEval = (StartEvalBody)ev;
try {
execute(consumer, jexlContext, jxpathContext, null, macroCall.next, macroCall.endElement);
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), ev.location, exc);
}
ev = startEval.endInstruction.next;
continue;
} else if (ev instanceof StartDefine) {
StartDefine startDefine = (StartDefine)ev;
definitions.put(startDefine.qname, startDefine);
ev = startDefine.endInstruction.next;
continue;
} else if (ev instanceof StartImport) {
StartImport startImport = (StartImport)ev;
String uri;
AttributeEvent e = startImport.uri;
if (e instanceof CopyAttribute) {
CopyAttribute copy = (CopyAttribute)e;
uri = copy.value;
} else {
StringBuffer buf = new StringBuffer();
SubstituteAttribute substAttr = (SubstituteAttribute)e;
Iterator i = substAttr.substitutions.iterator();
while (i.hasNext()) {
Subst subst = (Subst)i.next();
if (subst instanceof Literal) {
Literal lit = (Literal)subst;
buf.append(lit.value);
} else if (subst instanceof JXTExpression) {
JXTExpression expr = (JXTExpression)subst;
Object val;
try {
val = getValue(expr, jexlContext, jxpathContext);
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), ev.location, exc);
}
buf.append(val != null ? val.toString() : "");
}
}
uri = buf.toString();
}
Source input = null;
StartDocument doc;
try {
input = resolver.resolveURI(uri);
SourceValidity validity = null;
synchronized (cache) {
doc = (StartDocument)cache.get(input.getURI());
if (doc != null) {
boolean recompile = false;
if (doc.compileTime == null) {
recompile = true;
} else {
int valid = doc.compileTime.isValid();
if (valid == SourceValidity.UNKNOWN) {
validity = input.getValidity();
valid = doc.compileTime.isValid(validity);
}
if (valid != SourceValidity.VALID) {
recompile = true;
}
}
if (recompile) {
doc = null; // recompile
}
}
}
if (doc == null) {
Parser parser = new Parser();
// call getValidity before using the stream is faster if the source is a SitemapSource
if (validity == null) {
validity = input.getValidity();
}
SourceUtil.parse(this.manager, input, parser);
doc = parser.getStartEvent();
doc.compileTime = validity;
synchronized (cache) {
cache.put(input.getURI(), doc);
}
}
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), ev.location, exc);
}
finally {
resolver.release(input);
}
JXPathContext selectJXPath = jxpathContext;
MyJexlContext selectJexl = jexlContext;
if (startImport.select != null) {
try {
Object obj = getValue(startImport.select, jexlContext, jxpathContext);
selectJXPath = jxpathContextFactory.newContext(null, obj);
selectJXPath.setNamespaceContextPointer(new NamespacesTablePointer(namespaces));
selectJXPath.setVariables(variables);
selectJexl = new MyJexlContext(jexlContext);
fillContext(obj, selectJexl);
} catch (Exception exc) {
throw new JXTException(exc.getMessage(), ev.location, exc);
}
}
try {
execute(consumer, selectJexl, selectJXPath, macroCall, doc.next, doc.endDocument);
} catch (Exception exc) {
throw new JXTException("Exception occurred in imported template " + uri + ": "+ exc.getMessage(), ev.location, exc);
}
ev = startImport.endInstruction.next;
continue;
} else if (ev instanceof StartDocument) {
if (((StartDocument)ev).endDocument != null) {
// if this isn't a document fragment
consumer.startDocument();
}
} else if (ev instanceof EndDocument) {
consumer.endDocument();
} else if (ev instanceof ProcessingInstruction) {
ProcessingInstruction pi = (ProcessingInstruction)ev;
consumer.processingInstruction(pi.target, pi.data);
}
ev = ev.next;
}
}
/* (non-Javadoc)
* @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey()
*/
public Serializable getKey() {
JXTExpression cacheKeyExpr = (JXTExpression)getCurrentTemplateProperty(CACHE_KEY);
try {
final Serializable templateKey = (Serializable) getValue(cacheKeyExpr, globalJexlContext, jxpathContext);
if (templateKey != null) {
return new JXCacheKey(this.inputSource.getURI(), templateKey);
}
} catch (Exception e) {
getLogger().error("error evaluating cache key", e);
}
return null;
}
/* (non-Javadoc)
* @see org.apache.cocoon.caching.CacheableProcessingComponent#getValidity()
*/
public SourceValidity getValidity() {
JXTExpression validityExpr = (JXTExpression)getCurrentTemplateProperty(VALIDITY);
try {
final SourceValidity sourceValidity = this.inputSource.getValidity();
final SourceValidity templateValidity = (SourceValidity) getValue(validityExpr, globalJexlContext, jxpathContext);
if (sourceValidity != null && templateValidity != null) {
return new JXSourceValidity(sourceValidity, templateValidity);
}
} catch (Exception e) {
getLogger().error("error evaluating cache validity", e);
}
return null;
}
private Object getCurrentTemplateProperty(String propertyName) {
final String uri = this.inputSource.getURI();
StartDocument startEvent;
synchronized (cache) {
startEvent = (StartDocument)cache.get(uri);
}
return (startEvent != null) ? startEvent.templateProperties.get(propertyName) : null;
}
private NodeList toDOMNodeList(String elementName, StartInstruction si,
MyJexlContext jexlContext, StartElement macroCall) throws SAXException{
DOMBuilder builder = new DOMBuilder();
builder.startDocument();
builder.startElement(NS, elementName, elementName, EMPTY_ATTRS);
execute(builder, jexlContext, jxpathContext, macroCall, si.next, si.endInstruction);
builder.endElement(NS, elementName, elementName);
builder.endDocument();
Node node = builder.getDocument().getDocumentElement();
return node.getChildNodes();
}
static final class JXCacheKey implements Serializable {
private final String templateUri;
private final Serializable templateKey;
private JXCacheKey(String templateUri, Serializable templateKey) {
this.templateUri = templateUri;
this.templateKey = templateKey;
}
public int hashCode() {
return templateUri.hashCode() + templateKey.hashCode();
}
public String toString() {
return "TK:" + templateUri + "_" + templateKey;
}
public boolean equals(Object o) {
if (o instanceof JXCacheKey) {
JXCacheKey jxck = (JXCacheKey)o;
return this.templateUri.equals(jxck.templateUri)
&& this.templateKey.equals(jxck.templateKey);
}
return false;
}
}
static final class JXSourceValidity implements SourceValidity, Serializable {
private final SourceValidity sourceValidity;
private final SourceValidity templateValidity;
private JXSourceValidity(SourceValidity sourceValidity, SourceValidity templateValidity) {
this.sourceValidity = sourceValidity;
this.templateValidity = templateValidity;
}
public int isValid() {
switch (sourceValidity.isValid()) {
case SourceValidity.INVALID:
return SourceValidity.INVALID;
case SourceValidity.UNKNOWN:
if (templateValidity.isValid() == SourceValidity.INVALID) {
return SourceValidity.INVALID;
}
return SourceValidity.UNKNOWN;
case SourceValidity.VALID:
return templateValidity.isValid();
}
return SourceValidity.UNKNOWN;
}
public int isValid(SourceValidity otherValidity) {
if (otherValidity instanceof JXSourceValidity) {
JXSourceValidity otherJXValidity = (JXSourceValidity) otherValidity;
switch (sourceValidity.isValid(otherJXValidity.sourceValidity)) {
case SourceValidity.INVALID:
return SourceValidity.INVALID;
case SourceValidity.UNKNOWN:
if (templateValidity.isValid(otherJXValidity.templateValidity) == SourceValidity.INVALID) {
return SourceValidity.INVALID;
}
return SourceValidity.UNKNOWN;
case SourceValidity.VALID:
return templateValidity.isValid(otherJXValidity.templateValidity);
}
}
return SourceValidity.UNKNOWN;
}
}
}