Return-Path:
- *
* The immediate properties of the bean object are also available in the context
- * flowContext
- which represents the bean that was from the Flowscript continuation
- which represents the current continuation - an instance of {@link org.apache.cocoon.components.flow.WebContinuation}
Cocoon {@link Generator} that produces dynamic XML SAX events * fom an XML template file.
* *Provides a tag library with embedded JSTL and XPath expression substitution * to access data sent by Cocoon flowscripts.
* The embedded expression language allows a page author to access an * object using a simplified syntax such as ** <site signOn="${accountForm.signOn}"> **
Embedded Jexl expressions are contained in ${}.
*Embedded XPath expressions are contained in {}.
*Note that since this generator uses Apache JXPath and Apache Jexl, the referenced
* objects may be Java Beans, DOM, JDOM, or JavaScript objects from a
* Flowscript. The current Web Continuation from the Flowscript
* is also available as an variable named continuation
. You would
* typically access its id:
*
* <form action="${continuation.id}">
*
You can also reach previous continuations by using the getContinuation()
function:
* <form action="${continuation.getContinuation(1).id}" > **
The if
tag allows the conditional execution of its body
* according to value of a test
attribute:
* <if test="Expression"> * body * </if> **
The choose
tag performs conditional block execution by the
* embedded when
sub tags. It renders the body of the first
* when
tag whose test
condition evaluates to true.
* If none of the test
conditions of nested when
tags
* evaluate to true
, then the body of an otherwise
* tag is evaluated, if present:
* <choose> * <when test="Expression"> * body * </when> * <otherwise> * body * </otherwise> * </choose> **
The value-of
tag evaluates an XPath expression and outputs
* the result of the evaluation:
* <out value="Expression"/> **
The forEach
tag allows you to iterate over a collection
* of objects:
*
* <forEach var="name" items="Expression" begin="n" end="n" step="n"> * body * </for-each> ** * */ public class JexlTemplate extends AbstractGenerator { private static final JXPathContextFactory jxpathContextFactory = JXPathContextFactory.newInstance(); /** * Jexl Introspector that supports Rhino JavaScript objects * as well as Java Objects */ static public class JSIntrospector extends UberspectImpl { public 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; if (!(thisArg instanceof Scriptable)) { thisObj = Context.toObject(thisArg, scope); } else { thisObj = (Scriptable)thisArg; } result = ScriptableObject.getProperty(thisObj, name); Object[] newArgs = null; if (args != null) { newArgs = new Object[args.length]; for (int i = 0; i < args.length; 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 == ScriptableObject.NOT_FOUND) { result = null; } else 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; } } public 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.enter(); try { Scriptable thisObj; if (!(thisArg instanceof Scriptable)) { thisObj = Context.toObject(thisArg, scope); } else { thisObj = (Scriptable)thisArg; } Object result = ScriptableObject.getProperty(thisObj, name); if (result == Undefined.instance || result == ScriptableObject.NOT_FOUND) { result = null; } else while (result instanceof Wrapper) { result = ((Wrapper)result).unwrap(); } return result; } finally { Context.exit(); } } public boolean isCacheable() { return false; } public String getMethodName() { return name; } } public 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; } } public 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 == ScriptableObject.NOT_FOUND) { result = null; } else while (result instanceof Wrapper) { result = ((Wrapper)result).unwrap(); } return result; } finally { Context.exit(); } } public void remove() { arr.delete(index); } } public 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 == ScriptableObject.NOT_FOUND) { result = null; } else 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)) { 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 { if (!(obj instanceof Scriptable)) { return super.getMethod(obj, methodName, args, i); } return new JSMethod((Scriptable)obj, methodName); } public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i) throws Exception { if (!(obj instanceof Scriptable)) { return super.getPropertyGet(obj, identifier, i); } return new JSPropertyGet((Scriptable)obj, identifier); } public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i) throws Exception { if (!(obj instanceof Scriptable)) { return super.getPropertySet(obj, identifier, arg, i); } return new JSPropertySet((Scriptable)obj, identifier); } } static class MyVariables implements Variables { Map myVariables = new HashMap(); static final String[] VARIABLES = new String[] { "continuation", "flowContext", "request", "response", "context", "session", "parameters" }; Object bean, kont, request, response, session, context, parameters; MyVariables(Object bean, WebContinuation kont, Request request, Response response, org.apache.cocoon.environment.Context context, Parameters parameters) { this.bean = bean; this.kont = kont; this.request = request; this.session = request.getSession(false); this.response = response; this.context = context; this.parameters = parameters; } public boolean isDeclaredVariable(String varName) { for (int i = 0; i < VARIABLES.length; i++) { if (varName.equals(VARIABLES[i])) { return true; } } return myVariables.containsKey(varName); } public Object getVariable(String varName) { if (varName.equals("continuation")) { return kont; } else if (varName.equals("flowContext")) { return bean; } else if (varName.equals("request")) { return request; } else if (varName.equals("response")) { return response; } else if (varName.equals("session")) { return session; } else if (varName.equals("context")) { return context; } else if (varName.equals("parameters")) { return parameters; } return myVariables.get(varName); } public void declareVariable(String varName, Object value) { myVariables.put(varName, value); } public void undeclareVariable(String varName) { myVariables.remove(varName); } } static { // Hack: there's no _nice_ way to add my introspector to Jexl right now try { Field field = org.apache.commons.jexl.util.Introspector.class.getDeclaredField("uberSpect"); field.setAccessible(true); field.set(null, new JSIntrospector()); } catch (Exception e) { e.printStackTrace(); } } final static String JEXL_NS = "http://cocoon.apache.org/transformation/jexl/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"; /** * Compile a single Jexl expr (contained in ${}) or XPath expression * (contained in {}) */ private Object compileExpr(String inStr) throws Exception { try { if (inStr == null) return null; StringReader in = new StringReader(inStr.trim()); int ch; StringBuffer expr = new StringBuffer(); boolean xpath = false; boolean inExpr = false; while ((ch = in.read()) != -1) { char c = (char)ch; if (inExpr) { if (c == '}') { String str = expr.toString(); return compile(str, xpath); } else if (c == '\\') { ch = in.read(); if (ch == -1) { expr.append('\\'); } else { expr.append((char)ch); } } else { expr.append(c); } } else { if (c == '$') { ch = in.read(); if (ch == '{') { inExpr = true; xpath = false; continue; } } else if (c == '{') { ch = in.read(); if (ch != -1) { inExpr = true; xpath = true; expr.append((char)ch); continue; } } // hack: invalid expression? // just return the original and swallow exception return inStr; } } } catch (IOException ignored) { ignored.printStackTrace(); } return inStr; } private Object compile(final String variable, boolean xpath) throws Exception { if (xpath) { return JXPathContext.compile(variable); } else { return ExpressionFactory.createExpression(variable); } } private Object getValue(Object expr, JexlContext jexlContext, JXPathContext jxpathContext) throws Exception { if (expr instanceof CompiledExpression) { CompiledExpression e = (CompiledExpression)expr; return e.getValue(jxpathContext); } else { org.apache.commons.jexl.Expression e = (org.apache.commons.jexl.Expression)expr; return e.evaluate(jexlContext); } } class Event { final Locator location; Event next; Event(Locator location) { this.location = new LocatorImpl(location); } public String locationString() { String result = ""; String systemId = location.getSystemId(); if (systemId != null) { result += systemId + ", "; } result += "Line " + location.getLineNumber(); int col = location.getColumnNumber(); if (col > 0) { result += "." + col; } return result; } } class TextEvent extends Event { TextEvent(Locator location, char[] chars, int start, int length) throws SAXException { super(location); StringBuffer buf = new StringBuffer(); CharArrayReader in = new CharArrayReader(chars, start, length); int ch; boolean inExpr = false; boolean xpath = false; try { while ((ch = in.read()) != -1) { char c = (char)ch; if (inExpr) { 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 SAXParseException(exc.getMessage(), location, exc); } substitutions.add(compiledExpression); buf.setLength(0); inExpr = false; } else if (c == '\\') { ch = in.read(); if (ch == -1) { buf.append('\\'); } else { buf.append((char)ch); } } else { buf.append(c); } } else { if (c == '\\') { ch = in.read(); if (ch == -1) { buf.append('\\'); } else { buf.append((char)ch); } } else { if (c == '$') { ch = in.read(); if (ch == '{') { xpath = false; 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; } } else if (c == '{') { ch = in.read(); if (ch != -1) { if (buf.length() > 0) { char[] charArray = new char[buf.length()]; buf.getChars(0, buf.length(), charArray, 0); substitutions.add(charArray); buf.setLength(0); } buf.append((char)ch); inExpr = true; xpath = true; continue; } } } if (ch != -1) { buf.append((char)ch); } } } } catch (IOException ignored) { ignored.printStackTrace(); } if (buf.length() > 0) { char[] charArray = new char[buf.length()]; buf.getChars(0, buf.length(), charArray, 0); substitutions.add(charArray); } else if (substitutions.size() == 0) { substitutions.add(EMPTY_CHARS); } } final List substitutions = new LinkedList(); } class Characters extends TextEvent { Characters(Locator location, char[] chars, int start, int length) throws SAXException { super(location, chars, start, length); } } class StartDocument extends Event { StartDocument(Locator location) { super(location); } long compileTime; EndDocument endDocument; // null if document fragment } class EndDocument extends Event { EndDocument(Locator location) { super(location); } } class EndElement extends Event { EndElement(Locator location, StartElement startElement) { super(location); this.startElement = startElement; } final StartElement startElement; } class EndPrefixMapping extends Event { EndPrefixMapping(Locator location, String prefix) { super(location); this.prefix = prefix; } final String prefix; } class IgnorableWhitespace extends TextEvent { IgnorableWhitespace(Locator location, char[] chars, int start, int length) throws SAXException { super(location, chars, start, length); } } class ProcessingInstruction extends Event { ProcessingInstruction(Locator location, String target, String data) { super(location); this.target = target; this.data = data; } final String target; final String data; } class SkippedEntity extends Event { SkippedEntity(Locator location, String name) { super(location); this.name = name; } final String name; } abstract 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; } 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; } class Subst { } class Literal extends Subst { Literal(String val) { this.value = val; } final String value; } class Expression extends Subst { Expression(Object expr) { this.compiledExpression = expr; } final Object compiledExpression; } 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; } class StartElement extends Event { StartElement(Locator location, String namespaceURI, String localName, String raw, Attributes attrs) throws SAXException { super(location); this.namespaceURI = namespaceURI; this.localName = localName; this.raw = raw; StringBuffer buf = new StringBuffer(); for (int i = 0, len = attrs.getLength(); 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 { while ((ch = in.read()) != -1) { char c = (char)ch; if (inExpr) { if (c == '}') { String str = buf.toString(); Object compiledExpression; try { compiledExpression = compile(str, xpath); } catch (Exception exc) { throw new SAXParseException(exc.getMessage(), location, exc); } substEvents.add(new Expression(compiledExpression)); buf.setLength(0); inExpr = false; } else if (c == '\\') { ch = in.read(); if (ch == -1) { buf.append('\\'); } else { buf.append((char)ch); } } else { buf.append(c); } } else { if (c == '\\') { ch = in.read(); if (ch == -1) { buf.append('\\'); } else { buf.append((char)ch); } } else { if (c == '$') { ch = in.read(); if (ch == '{') { if (buf.length() > 0) { substEvents.add(new Literal(buf.toString())); buf.setLength(0); } inExpr = true; xpath = false; continue; } } else if (c == '{') { ch = in.read(); if (ch != -1) { if (buf.length() > 0) { substEvents.add(new Literal(buf.toString())); buf.setLength(0); } buf.append((char)ch); inExpr = true; xpath = true; continue; } buf.append('{'); } if (ch != -1) { buf.append((char)ch); } } } } } catch (IOException ignored) { ignored.printStackTrace(); } if (buf.length() > 0) { if (substEvents.size() == 0) { attributeEvents.add(new CopyAttribute(uri, local, qname, type, value)); } 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, "")); } } } } final String namespaceURI; final String localName; final String raw; final List attributeEvents = new LinkedList(); } class StartForEach extends Event { StartForEach(Locator location, Object items, String var, int begin, int end, int step) { super(location); this.items = items; this.var = var; this.begin = begin; this.end = end; this.step = step; } final Object items; final String var; final int begin; final int end; final int step; EndForEach endForEach; } class EndForEach extends Event { EndForEach(Locator location) { super(location); } } class StartIf extends Event { StartIf(Locator location, Object test) { super(location); this.test = test; } final Object test; EndIf endIf; } class EndIf extends Event { EndIf(Locator location) { super(location); } } class StartChoose extends Event { StartChoose(Locator location) { super(location); } StartWhen firstChoice; StartOtherwise otherwise; EndChoose endChoose; } class EndChoose extends Event { EndChoose(Locator location) { super(location); } } class StartWhen extends Event { StartWhen(Locator location, Object test) { super(location); this.test = test; } final Object test; StartWhen nextChoice; EndWhen endWhen; } class EndWhen extends Event { EndWhen(Locator location) { super(location); } } class StartOtherwise extends Event { StartOtherwise(Locator location) { super(location); } EndOtherwise endOtherwise; } class EndOtherwise extends Event { EndOtherwise(Locator location) { super(location); } } class StartPrefixMapping extends Event { StartPrefixMapping(Locator location, String prefix, String uri) { super(location); this.prefix = prefix; this.uri = uri; } final String prefix; final String uri; } class Comment extends TextEvent { Comment(Locator location, char[] chars, int start, int length) throws SAXException { super(location, chars, start, length); } } class EndCDATA extends Event { EndCDATA(Locator location) { super(location); } } class EndDTD extends Event { EndDTD(Locator location) { super(location); } } class EndEntity extends Event { EndEntity(Locator location, String name) { super(location); this.name = name; } final String name; } class StartCDATA extends Event { StartCDATA(Locator location) { super(location); } } class StartDTD extends Event { StartDTD(Locator 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; } class StartEntity extends Event { public StartEntity(Locator location, String name) { super(location); this.name = name; } final String name; } class StartOut extends Event { StartOut(Locator location, Object expr) { super(location); this.compiledExpression = expr; } final Object compiledExpression; } class EndOut extends Event { EndOut(Locator location) { super(location); } } class StartImport extends Event { StartImport(Locator location, AttributeEvent uri, Object select) { super(location); this.uri = uri; this.select = select; } final AttributeEvent uri; final Object select; EndImport endImport; } class EndImport extends Event { EndImport(Locator location) { super(location); } } class StartTemplate extends Event { StartTemplate(Locator location) { super(location); } EndTemplate endTemplate; } class EndTemplate extends Event { EndTemplate(Locator location) { super(location); } } class Parser implements LexicalHandler, ContentHandler { StartDocument startEvent; Event lastEvent; Stack stack = new Stack(); Locator locator; StartDocument getStartEvent() { return startEvent; } private void addEvent(Event ev) { if (ev == null) { throw new NullPointerException("null event"); } if (lastEvent == null) { lastEvent = startEvent = new StartDocument(locator); } lastEvent.next = ev; lastEvent = ev; } public void characters(char[] ch, int start, int length) throws SAXException { Characters chars = new Characters(locator, ch, start, length); addEvent(chars); } public void endDocument() { StartDocument startDoc = (StartDocument)stack.pop(); EndDocument endDoc = new EndDocument(locator); 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 (JEXL_NS.equals(namespaceURI)) { if (start instanceof StartForEach) { StartForEach startForEach = (StartForEach)start; newEvent = startForEach.endForEach = new EndForEach(locator); } else if (start instanceof StartIf) { StartIf startIf = (StartIf)start; newEvent = startIf.endIf = new EndIf(locator); } else 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; } newEvent = startWhen.endWhen = new EndWhen(locator); } else if (start instanceof StartOtherwise) { StartOtherwise startOtherwise = (StartOtherwise)start; StartChoose startChoose = (StartChoose)stack.peek(); newEvent = startOtherwise.endOtherwise = new EndOtherwise(locator); startChoose.otherwise = startOtherwise; } else if (start instanceof StartOut) { newEvent = new EndOut(locator); } else if (start instanceof StartChoose) { StartChoose startChoose = (StartChoose)start; newEvent = startChoose.endChoose = new EndChoose(locator); } else if (start instanceof StartImport) { StartImport startImport = (StartImport)start; newEvent = startImport.endImport = new EndImport(locator); } else if (start instanceof StartTemplate) { StartTemplate startTemplate = (StartTemplate)start; newEvent = startTemplate.endTemplate = new EndTemplate(locator); } else { throw new SAXParseException("unrecognized tag: " + localName, locator, null); } } else { StartElement startElement = (StartElement)start; newEvent = new EndElement(locator, startElement); } addEvent(newEvent); } public void endPrefixMapping(String prefix) { EndPrefixMapping endPrefixMapping = new EndPrefixMapping(locator, prefix); addEvent(endPrefixMapping); } public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { Event ev = new IgnorableWhitespace(locator, ch, start, length); addEvent(ev); } public void processingInstruction(String target, String data) { Event pi = new ProcessingInstruction(locator, target, data); addEvent(pi); } public void setDocumentLocator(Locator locator) { this.locator = locator; } public void skippedEntity(String name) { addEvent(new SkippedEntity(locator, name)); } public void startDocument() { startEvent = new StartDocument(locator); lastEvent = startEvent; stack.push(lastEvent); } public void startElement(String namespaceURI, String localName, String raw, Attributes attrs) throws SAXException { Event newEvent = null; if (JEXL_NS.equals(namespaceURI)) { if (localName.equals(FOR_EACH)) { String items = attrs.getValue("items"); String select = attrs.getValue("select"); String s = attrs.getValue("begin"); int begin = s == null ? -1 : Integer.parseInt(s); s = attrs.getValue("end"); int end = s == null ? -1 : Integer.parseInt(s); s = attrs.getValue("step"); int step = s == null ? 1 : Integer.parseInt(s); if (step < 1) { throw new SAXParseException("forEach: \"step\" must be a positive integer", locator, null); } String var = attrs.getValue("var"); if (items == null) { if (select == null && (begin == -1 || end == -1)) { throw new SAXParseException("forEach: \"select\", \"items\", or both \"begin\" and \"end\" must be specified", locator, null); } } else if (select != null) { throw new SAXParseException("forEach: only one of \"select\" or \"items\" may be specified", locator, null); } begin = begin == -1 ? 0 : begin; end = end == -1 ? Integer.MAX_VALUE: end; Object expr; try { expr = compileExpr(items == null ? select : items); } catch (Exception exc) { throw new SAXParseException(exc.getMessage(), locator, exc); } StartForEach startForEach = new StartForEach(locator, expr, var, begin, end, step); newEvent = startForEach; } else if (localName.equals(CHOOSE)) { StartChoose startChoose = new StartChoose(locator); newEvent = startChoose; } else if (localName.equals(WHEN)) { if (!(stack.peek() instanceof StartChoose)) { throw new SAXParseException("