Java tutorial
/* * $Id: Processor.java,v 2.62 2010/01/24 15:32:51 obecker Exp $ * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is: this file * * The Initial Developer of the Original Code is Oliver Becker. * * Portions created by ______________________ * are Copyright (C) ______ _______________________. * All Rights Reserved. * * Contributor(s): Anatolij Zubow, Thomas Behrends. */ package net.sf.joost.stx; import net.sf.joost.Constants; import net.sf.joost.OptionalLog; import net.sf.joost.OutputURIResolver; import net.sf.joost.TransformerHandlerResolver; import net.sf.joost.emitter.StxEmitter; import net.sf.joost.grammar.EvalException; import net.sf.joost.instruction.AbstractInstruction; import net.sf.joost.instruction.GroupBase; import net.sf.joost.instruction.NodeBase; import net.sf.joost.instruction.PSiblingsFactory; import net.sf.joost.instruction.ProcessBase; import net.sf.joost.instruction.TemplateFactory; import net.sf.joost.instruction.TransformFactory; import java.io.IOException; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.Properties; import java.util.Stack; import java.util.Vector; import javax.xml.transform.ErrorListener; import javax.xml.transform.OutputKeys; import javax.xml.transform.URIResolver; import org.apache.commons.logging.Log; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotSupportedException; import org.xml.sax.XMLReader; import org.xml.sax.ext.DeclHandler; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.NamespaceSupport; import org.xml.sax.helpers.XMLFilterImpl; import org.xml.sax.helpers.XMLReaderFactory; /** * Processes an XML document as SAX XMLFilter. Actions are contained * within an array of templates, received from a transform node. * @version $Revision: 2.62 $ $Date: 2010/01/24 15:32:51 $ * @author Oliver Becker */ public class Processor extends XMLFilterImpl implements Constants, LexicalHandler /*, DeclHandler */ { /** * Possible actions when no matching template was found. * Set by <code>stx:options' no-match-events</code> */ public static final byte PASS_THROUGH_NONE = 0x0, // default, see Context PASS_THROUGH_ELEMENT = 0x1, PASS_THROUGH_TEXT = 0x2, PASS_THROUGH_COMMENT = 0x4, PASS_THROUGH_PI = 0x8, PASS_THROUGH_ATTRIBUTE = 0x10, PASS_THROUGH_ALL = ~PASS_THROUGH_NONE; // all bits set /** The node representing the transformation sheet */ private TransformFactory.Instance transformNode; /** * Array of global visible templates (templates with an attribute * <code>visibility="global"</code>). */ private TemplateFactory.Instance[] globalTemplates; /** The Context object */ private Context context; /** * Depth in the subtree to be skipped; increased by startElement * and decreased by endElement. */ private int skipDepth = 0; /** * Set to true between {@link #startCDATA} and {@link #endCDATA}, * needed for CDATA processing */ private boolean insideCDATA = false; /** * Set to true between {@link #startDTD} and {@link #endDTD}, * needed for ignoring comments */ private boolean insideDTD = false; /** Buffer for collecting character data into single text nodes */ private StringBuffer collectedCharacters = new StringBuffer(); /** Last event (this Processor uses one look-ahead) */ private SAXEvent lastElement = null; /** The namespaces of the current scope */ private Hashtable inScopeNamespaces; /** The namespace context as a stack */ private Stack namespaceContext = new Stack(); /** Flag that controls namespace contexts */ private boolean nsContextActive = false; /** * Stack for input events (of type {@link SAXEvent}). Every * <code>startElement</code> event pushes itself on this stack, every * <code>endElement</code> event pops its event from the stack. * Character events (text()), comments, PIs will be put on the stack * before processing and removed immediately afterwards. This stack is * needed for matching and for position counting within the parent of * each event. */ private Stack eventStack; /** * Stack needed for inner processing (buffers, documents). * This stack stores the event stack for <code>stx:process-document</code> * and character data that has been already read as look-ahead * ({@link #collectedCharacters}). */ private Stack innerProcStack = new Stack(); public Properties outputProperties; private final boolean isProcessorClass = getClass().equals(Processor.class); // ********************************************************************** /** * Inner class for data which is processing/template specific. * Objects of this class will be put on the instance stack * {@link Processor#dataStack}. */ public final class Data { /** * Last process status while processing this template. * The values used are defined in {@link Constants} as "process state * values". */ private short lastProcStatus; /** The last instantiated template */ public TemplateFactory.Instance template; /** The next instruction to be executed */ private AbstractInstruction instruction; /** The current group */ public GroupBase currentGroup; /** The context position of the current node (from {@link Context}) */ private long contextPosition; /** Next group in the processing, contains the visible templates */ private GroupBase targetGroup; /** current table of local variables in {@link #template} */ private Hashtable localVars; /** passed parameters to {@link #template} (only for the debugging) */ private Hashtable passedParams; /** * <code>stx:process-siblings</code> instruction * (for stx:process-siblings) */ private PSiblingsFactory.Instance psiblings; /** current event (for stx:process-siblings) */ private SAXEvent sibEvent; /** * Constructor for the initialization of all fields, needed for * <code>stx:process-siblings</code> */ Data(short lps, TemplateFactory.Instance t, AbstractInstruction i, Hashtable pp, Context c, SAXEvent se) { lastProcStatus = lps; template = t; instruction = i; currentGroup = c.currentGroup; contextPosition = c.position; targetGroup = c.targetGroup; localVars = (Hashtable) c.localVars.clone(); passedParams = pp; psiblings = c.psiblings; sibEvent = se; } /** Constructor for "descendant or self" processing */ Data(short lps, TemplateFactory.Instance t, AbstractInstruction i, Hashtable pp, Context c) { lastProcStatus = lps; template = t; instruction = i; currentGroup = c.currentGroup; contextPosition = c.position; targetGroup = c.targetGroup; localVars = (Hashtable) c.localVars.clone(); passedParams = pp; } /** * Initial constructor for the first element of the data stack. * @param c the initial context */ Data(Context c) { targetGroup = c.targetGroup; // other fields are default initialized with 0 or null resp. } /** * Constructor used when processing a built-in template. * @param data a {@link Processor.Data} element that will be copied * partially */ Data(Data data) { targetGroup = data.targetGroup; currentGroup = data.currentGroup; // other fields are default initialized with 0 or null resp. } // methods /** returns the value of {@link #passedParams} */ public Hashtable getPassedParams() { return passedParams; } /** returns the value of {@link #localVars} */ public Hashtable getLocalVars() { return localVars; } /** returns the value of {@link #targetGroup} */ public GroupBase getTargetGroup() { return targetGroup; } /** just for debugging */ public String toString() { return "Data{" + template + "," + contextPosition + "," + java.util.Arrays.asList(targetGroup.visibleTemplates) + "," + lastProcStatus + "}"; } } // inner class Data // ********************************************************************** /** Stack for {@link Processor.Data} objects */ private DataStack dataStack = new DataStack(); /** * Inner class that implements a stack for {@link Processor.Data} objects. * <p> * I've implemented my own (typed) stack to circumvent the costs of * type casts for the Data objects. However, I've noticed no notable * performance gain. */ public final class DataStack { private Data[] stack = new Data[32]; private int objCount = 0; void push(Data d) { if (objCount == stack.length) { Data[] tmp = new Data[objCount << 1]; System.arraycopy(stack, 0, tmp, 0, objCount); stack = tmp; } stack[objCount++] = d; } Data peek() { return stack[objCount - 1]; } Data pop() { return stack[--objCount]; } public int size() { return objCount; } Data elementAt(int pos) { return stack[pos]; } public Data[] getStack() { return stack; } // for debugging public String toString() { StringBuffer sb = new StringBuffer('['); for (int i = 0; i < objCount; i++) { if (i > 0) sb.append(','); sb.append(stack[i].toString()); } sb.append(']'); return sb.toString(); } } // inner class DataStack // ********************************************************************** private static Log log = OptionalLog.getLog(Processor.class); // // Constructors // /** * Constructs a new <code>Processor</code> instance by parsing an * STX transformation sheet. This constructor attempts to create * its own {@link XMLReader} object. * @param src the source for the STX transformation sheet * @param pContext the parse context * @throws IOException if <code>src</code> couldn't be retrieved * @throws SAXException if a SAX parser couldn't be created */ public Processor(InputSource src, ParseContext pContext) throws IOException, SAXException { this(null, src, pContext); } /** * Constructs a new <code>Processor</code> instance by parsing an * STX transformation sheet. * @param reader the parser that is used for reading the transformation * sheet * @param src the source for the STX transformation sheet * @param pContext a parse context * @throws IOException if <code>src</code> couldn't be retrieved * @throws SAXException if a SAX parser couldn't be created */ public Processor(XMLReader reader, InputSource src, ParseContext pContext) throws IOException, SAXException { if (reader == null) reader = createXMLReader(); // create a Parser for parsing the STX transformation sheet Parser stxParser = new Parser(pContext); reader.setContentHandler(stxParser); reader.setErrorHandler(pContext.getErrorHandler()); // parse the transformation sheet reader.parse(src); init(stxParser.getTransformNode()); // re-use this XMLReader for processing setParent(reader); } /** * Constructs a new Processor instance from an existing Parser * (Joost representation of an STX transformation sheet) * @param stxParser the Joost representation of a transformation sheet * @throws SAXException if {@link #createXMLReader} fails */ public Processor(Parser stxParser) throws SAXException { init(stxParser.getTransformNode()); setParent(createXMLReader()); } /** * Constructs a copy of the given Processor. * @param proc the original Processor object * @throws SAXException if the construction of a new XML parser fails */ public Processor(Processor proc) throws SAXException { HashMap copies = new HashMap(); globalTemplates = AbstractInstruction.deepTemplateArrayCopy(proc.globalTemplates, copies); init((TransformFactory.Instance) proc.transformNode.deepCopy(copies)); setParent(createXMLReader()); setTransformerHandlerResolver(proc.context.defaultTransformerHandlerResolver.customResolver); setOutputURIResolver(proc.context.outputUriResolver); } /** * Constructs a copy of this Processor. * @throws SAXException if the construction of a new XML parser fails */ public Processor copy() throws SAXException { return new Processor(this); } // // Methods // /** * Create an <code>XMLReader</code> object (a SAX Parser) * @throws SAXException if a SAX Parser couldn't be created */ public static XMLReader createXMLReader() throws SAXException { // Using pure SAX2, not JAXP XMLReader reader = null; try { // try default parser implementation reader = XMLReaderFactory.createXMLReader(); } catch (SAXException e) { String prop = System.getProperty("org.xml.sax.driver"); if (prop != null) { // property set, but still failed throw new SAXException("Can't create XMLReader for class " + prop); // leave the method here } // try another SAX implementation String PARSER_IMPLS[] = { "org.apache.xerces.parsers.SAXParser", // Xerces "org.apache.crimson.parser.XMLReaderImpl", // Crimson "gnu.xml.aelfred2.SAXDriver" // Aelfred nonvalidating }; for (int i = 0; i < PARSER_IMPLS.length; i++) { try { reader = XMLReaderFactory.createXMLReader(PARSER_IMPLS[i]); break; // for (...) } catch (SAXException e1) { } // continuing } if (reader == null) { throw new SAXException("Can't find SAX parser implementation.\n" + "Please specify a parser class via the system property " + "'org.xml.sax.driver'"); } } // set features and properties that have been put // into the system properties (e.g. via command line) Properties sysProps = System.getProperties(); for (Enumeration e = sysProps.propertyNames(); e.hasMoreElements();) { String propKey = (String) e.nextElement(); if (propKey.startsWith(EXTERN_SAX_FEATURE_PREFIX)) { reader.setFeature(propKey.substring(EXTERN_SAX_FEATURE_PREFIX.length()), Boolean.parseBoolean(sysProps.getProperty(propKey))); continue; } if (propKey.startsWith(EXTERN_SAX_PROPERTY_PREFIX)) { reader.setProperty(propKey.substring(EXTERN_SAX_PROPERTY_PREFIX.length()), sysProps.getProperty(propKey)); continue; } } if (DEBUG) log.debug("Using " + reader.getClass().getName()); return reader; } /** * Initialize a <code>Processor</code> object */ private void init(TransformFactory.Instance pTransformNode) { context = new Context(); context.emitter = initializeEmitter(context); eventStack = context.ancestorStack; setErrorHandler(context.errorHandler); // register error handler context.currentProcessor = this; context.currentGroup = context.targetGroup = transformNode = pTransformNode; // first Data frame; needed for the first target group dataStack.push(new Data(context)); // initialize namespaces initNamespaces(); // array of global templates if (globalTemplates == null) { // note: getGlobalTemplates() returns null at the second invocation! Vector tempVec = transformNode.getGlobalTemplates(); globalTemplates = new TemplateFactory.Instance[tempVec.size()]; tempVec.toArray(globalTemplates); Arrays.sort(globalTemplates); } initOutputProperties(); } /** * Create a fresh namespace hashtable */ private void initNamespaces() { inScopeNamespaces = new Hashtable(); inScopeNamespaces.put("xml", NamespaceSupport.XMLNS); } /** * The initialization of the emitter could be overriden * for debug purpose. * @param ctx The current context * @return an emitter-instance */ protected Emitter initializeEmitter(Context ctx) { return new Emitter(ctx.errorHandler); } /** * Initialize the output properties to the values specified in the * transformation sheet or to their default values, resp. */ public void initOutputProperties() { outputProperties = new Properties(); outputProperties.setProperty(OutputKeys.ENCODING, transformNode.outputEncoding); outputProperties.setProperty(OutputKeys.MEDIA_TYPE, "text/xml"); outputProperties.setProperty(OutputKeys.METHOD, transformNode.outputMethod); outputProperties.setProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); outputProperties.setProperty(OutputKeys.STANDALONE, "no"); outputProperties.setProperty(OutputKeys.VERSION, "1.0"); } /** * Assigns a parent to this filter instance. Attempts to register itself * as a lexical handler on this parent. */ public void setParent(XMLReader parent) { super.setParent(parent); parent.setContentHandler(this); // necessary?? try { parent.setProperty("http://xml.org/sax/properties/lexical-handler", this); } catch (SAXException ex) { if (log != null) log.warn("Accessing " + parent + ": " + ex); else System.err.println("Warning - Accessing " + parent + ": " + ex); } } /** * Registers a content handler. */ public void setContentHandler(ContentHandler handler) { context.emitter.setContentHandler(handler); } /** * Registers a lexical handler. */ public void setLexicalHandler(LexicalHandler handler) { context.emitter.setLexicalHandler(handler); } /** * Registers a declaration handler. Does nothing at the moment. */ public void setDeclHandler(DeclHandler handler) { } /** Standard prefix for SAX2 properties */ private static String PROP_PREFIX = "http://xml.org/sax/properties/"; /** * Set the property of a value on the underlying XMLReader. */ public void setProperty(String prop, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { if ((PROP_PREFIX + "lexical-handler").equals(prop)) setLexicalHandler((LexicalHandler) value); else if ((PROP_PREFIX + "declaration-handler").equals(prop)) setDeclHandler((DeclHandler) value); else super.setProperty(prop, value); } /** * Registers a <code>ErrorListener</code> object for reporting * errors while processing (transforming) the XML input */ public void setErrorListener(ErrorListener listener) { context.errorHandler.errorListener = listener; } /** * @return the output encoding specified in the STX transformation sheet */ public String getOutputEncoding() { return transformNode.outputEncoding; } /** * Sets a global parameter of the STX transformation sheet * @param name the (expanded) parameter name * @param value the parameter value */ public void setParameter(String name, Object value) { if (!name.startsWith("{")) name = "{}" + name; context.globalParameters.put(name, new Value(value)); } /** * Returns a global parameter of the STX transformation sheet * @param name the (expanded) parameter name * @return the parameter value or <code>null</code> if this parameter * isn't present */ public Object getParameter(String name) { if (!name.startsWith("{")) name = "{}" + name; Value param = (Value) context.globalParameters.get(name); try { if (param != null) return param.toJavaObject(Object.class); } catch (EvalException ex) { // shouldn't happen here if (log != null) log.fatal(ex); else System.err.println("Fatal error - " + ex); } return null; } /** * Clear all preset parameters */ public void clearParameters() { context.globalParameters.clear(); } /** * Registers a custom {@link TransformerHandlerResolver} object. * @param resolver the resolver to be registered */ public void setTransformerHandlerResolver(TransformerHandlerResolver resolver) { context.defaultTransformerHandlerResolver.customResolver = resolver; } /** * Registers a URIResolver for <code>stx:process-document</code> * @param resolver the resolver to be registered */ public void setURIResolver(URIResolver resolver) { context.uriResolver = resolver; } /** * Registers an {@link OutputURIResolver} for <code>stx:result-document</code> * @param resolver the resolver to be registered */ public void setOutputURIResolver(OutputURIResolver resolver) { context.outputUriResolver = resolver; } /** * Registers a message emitter for <code>stx:message</code> * @param emitter the emitter object to be registered */ public void setMessageEmitter(StxEmitter emitter) { context.messageEmitter = emitter; } /** * Starts the inner processing of a new buffer or another document * by saving the text data already read and jumping to the targetted * group (if specified). */ public void startInnerProcessing() { // there might be characters already read innerProcStack.push(collectedCharacters.toString()); collectedCharacters.setLength(0); innerProcStack.push(inScopeNamespaces); initNamespaces(); // possible jump to another group (changed visibleTemplates) dataStack.push(new Data(PR_BUFFER, null, null, null, context)); } /** * Ends the inner processing by restoring the collected text data. */ public void endInnerProcessing() throws SAXException { // look-ahead mechanism if (lastElement != null) processLastElement(true); if (collectedCharacters.length() != 0) processCharacters(); // Clean up dataStack: terminate pending stx:process-siblings clearProcessSiblings(); // remove Data object from startInnerProcessing() context.localVars = dataStack.pop().localVars; inScopeNamespaces = (Hashtable) innerProcStack.pop(); collectedCharacters.append(innerProcStack.pop()); } /** * Check for the next best matching template after * <code>stx:process-self</code> * @param temp a template matching the current node * @return <code>true</code> if this template hasn't been processed before */ private boolean foundUnprocessedTemplate(TemplateFactory.Instance temp) { for (int top = dataStack.size() - 1; top >= 0; top--) { Data d = dataStack.elementAt(top); if (d.lastProcStatus == PR_SELF) { // stx:process-self if (d.template == temp) return false; // no, this template was already in use // else continue } else return true; // yes, no process-self on top of the stack } return true; // yes, reached bottom of the stack } /** * @return the matching template for the current event stack. */ private TemplateFactory.Instance findMatchingTemplate() throws SAXException { TemplateFactory.Instance found = null; TemplateFactory.Instance[] category = null; int tempIndex = -1; Data top = dataStack.peek(); // Is the previous instruction not an stx:process-self? // used for performance (to prevent calling foundUnprocessedTemplate()) boolean notSelf = (top.lastProcStatus != PR_SELF); // the three precedence categories TemplateFactory.Instance precCats[][] = { top.targetGroup.visibleTemplates, top.targetGroup.groupTemplates, globalTemplates }; // look up for a matching template in the categories for (int i = 0; i < precCats.length && category == null; i++) for (int j = 0; j < precCats[i].length; j++) if (precCats[i][j].matches(context, true) && (notSelf || foundUnprocessedTemplate(precCats[i][j]))) { // bingo! category = precCats[i]; tempIndex = j; break; } if (category != null) { // means, we found a template found = category[tempIndex]; double priority = found.getPriority(); // look for more templates with the same priority in the same // category if (++tempIndex < category.length && priority == category[tempIndex].getPriority()) { for (; tempIndex < category.length && priority == category[tempIndex].getPriority(); tempIndex++) { if (category[tempIndex].matches(context, false)) context.errorHandler.error( "Ambigous template rule with priority " + priority + ", found matching template rule already in line " + found.lineNo, category[tempIndex].publicId, category[tempIndex].systemId, category[tempIndex].lineNo, category[tempIndex].colNo); } } } return found; } /** contains the last return value after processing STX instructions */ private int processStatus; /** * Performs the processing of the linked instruction chain until * an end condition was met. This method stores the last return * value in the class member variable {@link #processStatus}. * @param inst the first instruction in the chain * @param event the current event * @param skipProcessBase set if ProcessBase instructions shouldn't be * reported * @return the last processed instruction */ private AbstractInstruction doProcessLoop(AbstractInstruction inst, SAXEvent event, boolean skipProcessBase) throws SAXException { processStatus = PR_CONTINUE; while (inst != null && processStatus == PR_CONTINUE) { // check, if this is the original class: call process() directly if (isProcessorClass) { while (inst != null && processStatus == PR_CONTINUE) { if (DEBUG) if (log.isDebugEnabled()) log.debug(inst.lineNo + ": " + inst); processStatus = inst.process(context); inst = inst.next; } } // otherwise: this is a derived class else { while (inst != null && processStatus == PR_CONTINUE) { // skip ProcessBase if requested if (skipProcessBase && inst.getNode() instanceof ProcessBase) processStatus = inst.process(context); else processStatus = processInstruction(inst, event); inst = inst.next; } } if (processStatus == PR_ATTRIBUTES) { // stx:process-attributes encountered // (i.e. the current node must be an element with attributes) processAttributes(event.attrs); processStatus = PR_CONTINUE; } } return inst; } /** * Process an instruction. * This method should be overridden for debug purposes. * @param inst The instruction which should be processed * @param event The current event * @return see {@link AbstractInstruction#process} * @throws SAXException in case of parse-errors */ protected int processInstruction(AbstractInstruction inst, SAXEvent event) throws SAXException { // process instruction return inst.process(context); } /** * Processes the upper most event on the event stack. */ private void processEvent() throws SAXException { SAXEvent event = (SAXEvent) eventStack.peek(); if (DEBUG) if (log.isDebugEnabled()) { log.debug(event); log.debug(context.localVars); } if (dataStack.peek().lastProcStatus == PR_SIBLINGS) processSiblings(); TemplateFactory.Instance temp = findMatchingTemplate(); if (temp != null) { AbstractInstruction inst = temp; context.localVars.clear(); Hashtable currentParams = context.passedParameters; inst = doProcessLoop(inst, event, false); if (DEBUG) if (log.isDebugEnabled()) { log.debug("stop " + processStatus); log.debug(context.localVars); } switch (processStatus) { case PR_CONTINUE: // templated finished if (event.type == SAXEvent.ELEMENT || event.type == SAXEvent.ROOT) { skipDepth = 1; collectedCharacters.setLength(0); // clear text insideCDATA = false; // reset if there was a CDATA section } break; case PR_CHILDREN: // stx:process-children encountered dataStack.push(new Data(PR_CHILDREN, temp, inst, currentParams, context)); if (context.targetHandler != null) { // instruction had a filter attribute startExternDocument(); if (collectedCharacters.length() > 0) { context.targetHandler.characters(collectedCharacters.toString().toCharArray(), 0, collectedCharacters.length()); collectedCharacters.setLength(0); } skipDepth = 1; } break; case PR_SELF: // stx:process-self encountered dataStack.push(new Data(PR_SELF, temp, inst, currentParams, context)); if (context.targetHandler != null) { // instruction had a filter attribute switch (event.type) { case SAXEvent.ELEMENT: startExternDocument(); context.targetHandler.startElement(event.uri, event.lName, event.qName, event.attrs); skipDepth = 1; break; case SAXEvent.TEXT: startExternDocument(); context.targetHandler.characters(event.value.toCharArray(), 0, event.value.length()); endExternDocument(); break; case SAXEvent.CDATA: startExternDocument(); context.targetHandler.startCDATA(); context.targetHandler.characters(event.value.toCharArray(), 0, event.value.length()); context.targetHandler.endCDATA(); endExternDocument(); break; case SAXEvent.PI: startExternDocument(); context.targetHandler.processingInstruction(event.qName, event.value); endExternDocument(); break; case SAXEvent.COMMENT: startExternDocument(); context.targetHandler.comment(event.value.toCharArray(), 0, event.value.length()); endExternDocument(); break; case SAXEvent.ROOT: context.targetHandler.startDocument(); skipDepth = 1; break; case SAXEvent.ATTRIBUTE: // nothing to do break; default: if (log != null) log.error("Unexpected event: " + event); else System.err.println("Error - Unexpected event: " + event); } } else processEvent(); // recurse if (event.type == SAXEvent.TEXT || event.type == SAXEvent.CDATA || event.type == SAXEvent.COMMENT || event.type == SAXEvent.PI || event.type == SAXEvent.ATTRIBUTE) { // no children present, continue processing dataStack.pop(); inst = doProcessLoop(inst, event, false); if (DEBUG) if (log.isDebugEnabled()) log.debug("stop " + processStatus); switch (processStatus) { case PR_CHILDREN: case PR_SELF: NodeBase start = inst.getNode(); context.errorHandler.error("Encountered '" + start.qName + "' after stx:process-self", start.publicId, start.systemId, start.lineNo, start.colNo); // falls through, if the error handler returns case PR_ERROR: throw new SAXException("Non-recoverable error"); case PR_SIBLINGS: dataStack.push(new Data(PR_SIBLINGS, temp, inst, currentParams, context, event)); break; // case PR_ATTRIBUTES: won't happen // case PR_CONTINUE: nothing to do } } break; case PR_SIBLINGS: // stx:process-siblings encountered if (event.type == SAXEvent.ELEMENT || event.type == SAXEvent.ROOT) { // end of template reached, skip contents skipDepth = 1; collectedCharacters.setLength(0); // clear text } dataStack.push(new Data(PR_SIBLINGS, temp, inst, currentParams, context, event)); break; // case PR_ATTRIBUTES: won't happen case PR_ERROR: // errorHandler returned after a fatal error throw new SAXException("Non-recoverable error"); default: // Mustn't happen String msg = "Unexpected return value from process() " + processStatus; if (log != null) log.error(msg); throw new SAXException(msg); } } else { // no template found, default action GroupBase tg = context.targetGroup; Emitter emitter = context.emitter; switch (event.type) { case SAXEvent.ROOT: dataStack.push(new Data(dataStack.peek())); break; case SAXEvent.ELEMENT: if ((tg.passThrough & PASS_THROUGH_ELEMENT) != 0) emitter.startElement(event.uri, event.lName, event.qName, event.attrs, event.namespaces, tg); dataStack.push(new Data(dataStack.peek())); break; case SAXEvent.TEXT: if ((tg.passThrough & PASS_THROUGH_TEXT) != 0) { emitter.characters(event.value.toCharArray(), 0, event.value.length(), tg); } break; case SAXEvent.CDATA: if ((tg.passThrough & PASS_THROUGH_TEXT) != 0) { emitter.startCDATA(tg); emitter.characters(event.value.toCharArray(), 0, event.value.length(), tg); emitter.endCDATA(); } break; case SAXEvent.COMMENT: if ((tg.passThrough & PASS_THROUGH_COMMENT) != 0) emitter.comment(event.value.toCharArray(), 0, event.value.length(), tg); break; case SAXEvent.PI: if ((tg.passThrough & PASS_THROUGH_PI) != 0) emitter.processingInstruction(event.qName, event.value, tg); break; case SAXEvent.ATTRIBUTE: if ((tg.passThrough & PASS_THROUGH_ATTRIBUTE) != 0) emitter.addAttribute(event.uri, event.qName, event.lName, event.value, tg); break; default: if (log != null) log.error("no default action for " + event); else System.err.println("Error - no default action for " + event); } } } /** * Process last element start (stored as {@link #lastElement} in * {@link #startElement startElement}) */ private void processLastElement(boolean hasChildren) throws SAXException { if (DEBUG) if (log.isDebugEnabled()) log.debug(lastElement); // determine if the look-ahead is a text node String s = collectedCharacters.toString(); if (s.length() == 0 || (context.targetGroup.stripSpace && s.trim().length() == 0)) { if (hasChildren) lastElement.enableChildNodes(true); } else { // set string value of the last element lastElement.value = s; lastElement.enableChildNodes(true); } // put last element on the event stack ((SAXEvent) eventStack.peek()).countElement(lastElement.uri, lastElement.lName); eventStack.push(lastElement); lastElement = null; processEvent(); } /** * Process a text node (from several consecutive <code>characters</code> * events) */ private void processCharacters() throws SAXException { String s = collectedCharacters.toString(); if (DEBUG) if (log.isDebugEnabled()) log.debug("'" + s + "'"); if (skipDepth > 0 && context.targetHandler != null) { if (insideCDATA) { context.targetHandler.startCDATA(); context.targetHandler.characters(s.toCharArray(), 0, s.length()); context.targetHandler.endCDATA(); } else context.targetHandler.characters(s.toCharArray(), 0, s.length()); collectedCharacters.setLength(0); return; } if (context.targetGroup.stripSpace && s.trim().length() == 0) { collectedCharacters.setLength(0); return; // white-space only characters found, do nothing } SAXEvent ev; if (insideCDATA) { ((SAXEvent) eventStack.peek()).countCDATA(); ev = SAXEvent.newCDATA(s); } else { ((SAXEvent) eventStack.peek()).countText(); ev = SAXEvent.newText(s); } eventStack.push(ev); processEvent(); eventStack.pop(); collectedCharacters.setLength(0); } /** * Simulate events for each of the attributes of the current element. * This method will be called due to an <code>stx:process-attributes</code> * instruction. * @param attrs the attributes to be processed */ private void processAttributes(Attributes attrs) throws SAXException { // actually only the target group need to be put on this stack .. // (for findMatchingTemplate) dataStack.push(new Data(PR_ATTRIBUTES, null, null, null, context)); for (int i = 0; i < attrs.getLength(); i++) { if (DEBUG) if (log.isDebugEnabled()) log.debug(attrs.getQName(i)); SAXEvent ev = SAXEvent.newAttribute(attrs, i); eventStack.push(ev); processEvent(); eventStack.pop(); if (DEBUG) if (log.isDebugEnabled()) log.debug("done " + attrs.getQName(i)); } Data d = dataStack.pop(); // restore position, current group and variables context.position = d.contextPosition; context.currentGroup = d.currentGroup; context.localVars = d.localVars; } /** * Check and process pending templates whose processing was suspended * by an stx:process-siblings instruction */ private void processSiblings() throws SAXException { Data stopData; int stopPos = 0; do { // check, if one of the last consecutive stx:process-siblings // terminates int stackPos = dataStack.size() - 1; Data data = dataStack.peek(); Hashtable storedVars = context.localVars; stopData = null; do { context.localVars = data.localVars; if (!data.psiblings.matches(context)) { stopData = data; stopPos = stackPos; } data = dataStack.elementAt(--stackPos); } while (data.lastProcStatus == PR_SIBLINGS); context.localVars = storedVars; if (stopData != null) // the first of the non-matching process-sibs clearProcessSiblings(stopData, false); // If after clearing the process siblings instructions there is // a new PR_SIBLINGS on the stack, its match conditions must // be checked here, too. } while (stopData != null && dataStack.size() == stopPos + 1 && dataStack.peek().lastProcStatus == PR_SIBLINGS); } /** * Clear all consecutive pending <code>stx:process-siblings</code> * instructions on the top of {@link #dataStack}. Does nothing * if there's no <code>stx:process-siblings</code> pending. */ private void clearProcessSiblings() throws SAXException { // find last of these consecutive stx:process-siblings instructions Data data, stopData = null; for (int i = dataStack.size() - 1; (data = dataStack.elementAt(i)).lastProcStatus == PR_SIBLINGS; i--) { stopData = data; } if (stopData != null) // yep, found at least one clearProcessSiblings(stopData, true); } /** * Clear consecutive pending <code>stx:process-siblings</code> * instructions on the top of {@link #dataStack} until * the passed object is encountered. * @param stopData data for the last <code>stx:process-siblings</code> * instruction * @param clearLast <code>true</code> if the template in * <code>stopData</code> itself must be cleared */ private void clearProcessSiblings(Data stopData, boolean clearLast) throws SAXException { // replace top-most event and local variables Object topEvent = null; // if clearLast==true then there's no event to remove, // because the end of of the parent has been encountered if (clearLast) topEvent = eventStack.peek(); else topEvent = eventStack.pop(); Hashtable storedVars = context.localVars; Data data; do { data = dataStack.pop(); // put back stored event eventStack.push(data.sibEvent); context.position = data.contextPosition; // restore position context.localVars = data.localVars; // restore variables AbstractInstruction inst = data.instruction; do { inst = doProcessLoop(inst, (SAXEvent) topEvent, false); if (DEBUG) if (log.isDebugEnabled()) { log.debug("stop " + processStatus); log.debug(context.localVars); } switch (processStatus) { case PR_CHILDREN: case PR_SELF: NodeBase start = inst.getNode(); context.errorHandler.error("Encountered '" + start.qName + "' after stx:process-siblings", start.publicId, start.systemId, start.lineNo, start.colNo); // falls through, if the error handler returns case PR_ERROR: throw new SAXException("Non-recoverable error"); // case PR_ATTRIBUTES: won't happen // case PR_CONTINUE or PR_SIBLINGS: ok, nothing to do } // ignore further stx:process-siblings instructions in this // template if the processing was stopped by another // stx:process-siblings or clearLast==true } while (processStatus == PR_SIBLINGS && (clearLast || data != stopData)); if (processStatus == PR_SIBLINGS) { // put back the last stx:process-siblings instruction stopData.instruction = inst; // there might have been a group attribute stopData.targetGroup = context.targetGroup; stopData.psiblings = context.psiblings; stopData.localVars = context.localVars; context.localVars = storedVars; dataStack.push(stopData); } // remove this event eventStack.pop(); } while (data != stopData); // last object // If the instruction before the last cleared process-siblings is a // process-self, we have to clear it too if (dataStack.peek().lastProcStatus == PR_SELF) { SAXEvent selfEvent = data.sibEvent; // prepare the event stack eventStack.push(selfEvent); // put another namespace context on the stack because endElement() // will remove it namespaceContext.push(namespaceContext.peek()); // postpone the processing of character data StringBuffer postponedCharacters = collectedCharacters; collectedCharacters = new StringBuffer(); endElement(selfEvent.uri, selfEvent.lName, selfEvent.qName); collectedCharacters = postponedCharacters; } // restore old event stack if (!clearLast) eventStack.push(topEvent); } /** * Emits a <code>startDocument</code> event to an external handler * (in {@link Context#targetHandler}), followed by all necessary * namespace declarations (<code>startPrefixMapping</code> events). */ private void startExternDocument() throws SAXException { try { context.targetHandler.startDocument(); // declare current namespaces for (Enumeration e = inScopeNamespaces.keys(); e.hasMoreElements();) { String prefix = (String) e.nextElement(); if (!prefix.equals("xml")) context.targetHandler.startPrefixMapping(prefix, (String) inScopeNamespaces.get(prefix)); } // If the Map interface would be used: // // Map.Entry[] nsEntries = new Map.Entry[inScopeNamespaces.size()]; // inScopeNamespaces.entrySet().toArray(nsEntries); // for (int i=0; i<nsEntries.length; i++) { // String prefix = (String)nsEntries[i].getKey(); // if (!prefix.equals("xml")) // context.targetHandler.startPrefixMapping( // prefix, (String)nsEntries[i].getValue()); // } } catch (RuntimeException e) { // wrap exception java.io.StringWriter sw = null; sw = new java.io.StringWriter(); e.printStackTrace(new java.io.PrintWriter(sw)); NodeBase nb = context.currentInstruction; context.errorHandler.fatalError("External processing failed: " + sw, nb.publicId, nb.systemId, nb.lineNo, nb.colNo, e); } } /** * Emits an <code>endDocument</code> event to an external handler * (in {@link Context#targetHandler}), preceded by all necessary * namespace undeclarations (<code>endPrefixMapping</code> events). */ private void endExternDocument() throws SAXException { try { // undeclare current namespaces for (Enumeration e = inScopeNamespaces.keys(); e.hasMoreElements();) { String prefix = (String) e.nextElement(); if (!prefix.equals("xml")) context.targetHandler.endPrefixMapping(prefix); } // If the Map interface would be used // // Map.Entry[] nsEntries = new Map.Entry[inScopeNamespaces.size()]; // inScopeNamespaces.entrySet().toArray(nsEntries); // for (int i=0; i<nsEntries.length; i++) { // String prefix = (String)nsEntries[i].getKey(); // if (!prefix.equals("xml")) // context.targetHandler.endPrefixMapping(prefix); // } context.targetHandler.endDocument(); context.targetHandler = null; } catch (RuntimeException e) { // wrap exception java.io.StringWriter sw = null; sw = new java.io.StringWriter(); e.printStackTrace(new java.io.PrintWriter(sw)); NodeBase nb = context.currentInstruction; context.errorHandler.fatalError("External processing failed: " + sw, nb.publicId, nb.systemId, nb.lineNo, nb.colNo, e); } } // ********************************************************************** // // from interface ContentHandler // public void startDocument() throws SAXException { // perform this only at the begin of a transformation, // not at the begin of processing another document if (innerProcStack.empty()) { // initialize all group stx:variables transformNode.initGroupVariables(context); context.emitter.startDocument(); } else { // stx:process-document innerProcStack.push(eventStack); context.ancestorStack = eventStack = new Stack(); } eventStack.push(SAXEvent.newRoot()); processEvent(); } public void endDocument() throws SAXException { if (collectedCharacters.length() != 0) processCharacters(); if (skipDepth == 1 && context.targetHandler != null && dataStack.peek().lastProcStatus == PR_CHILDREN) { // provisional fix for bug #765301 // (see comment in endElement below) skipDepth = 0; endExternDocument(); } if (skipDepth == 0) { clearProcessSiblings(); Data data = dataStack.pop(); context.currentGroup = data.currentGroup; context.targetGroup = data.targetGroup; short prStatus = data.lastProcStatus; if (data.template == null) { // default action: nothing to do } else if (prStatus == PR_CHILDREN || prStatus == PR_SELF) { context.position = data.contextPosition; // restore position context.localVars = data.localVars; AbstractInstruction inst = data.instruction; inst = doProcessLoop(inst, (SAXEvent) eventStack.peek(), true); switch (processStatus) { case PR_CHILDREN: case PR_SELF: NodeBase start = inst.getNode(); context.errorHandler.error("Encountered '" + start.qName + "' after stx:process-" + // prStatus must be either PR_CHILDREN or PR_SELF, see above (prStatus == PR_CHILDREN ? "children" : "self"), start.publicId, start.systemId, start.lineNo, start.colNo); // falls through if the error handler returns case PR_ERROR: throw new SAXException("Non-recoverable error"); // case PR_ATTRIBUTE: // case PR_SIBLINGS: // not possible because the context node is the document node } } else { if (log != null) log.error("encountered 'else' " + prStatus); else System.err.println("Error - encountered 'else' " + prStatus); } } else { // no stx:process-children in match="/" skipDepth--; if (skipDepth == 0 && context.targetHandler != null) endExternDocument(); } if (skipDepth == 0) { // look at the previous process status on the stack if (dataStack.peek().lastProcStatus == PR_SELF) endDocument(); // recurse (process-self) else { eventStack.pop(); if (innerProcStack.empty()) { transformNode.exitRecursionLevel(context); context.emitter.endDocument(transformNode); } else eventStack = context.ancestorStack = (Stack) innerProcStack.pop(); } } else if (log != null) log.error("skipDepth at document end: " + skipDepth); else System.err.println("Error - skipDepth at document end: " + skipDepth); } public void startElement(String uri, String lName, String qName, Attributes attrs) throws SAXException { if (DEBUG) if (log.isDebugEnabled()) { log.debug(qName); log.debug("eventStack: " + eventStack); log.debug("dataStack: " + dataStack); } // look-ahead mechanism if (lastElement != null) processLastElement(true); if (collectedCharacters.length() != 0) processCharacters(); if (skipDepth > 0) { skipDepth++; if (context.targetHandler != null) context.targetHandler.startElement(uri, lName, qName, attrs); return; } lastElement = SAXEvent.newElement(uri, lName, qName, attrs, false, inScopeNamespaces); if (!nsContextActive) { namespaceContext.push(inScopeNamespaces); inScopeNamespaces = (Hashtable) inScopeNamespaces.clone(); } nsContextActive = false; } public void endElement(String uri, String lName, String qName) throws SAXException { if (DEBUG) if (log.isDebugEnabled()) { log.debug(qName + " (skipDepth: " + skipDepth + ")"); // log.debug("eventStack: " + eventStack.toString()); // log.debug("dataStack: " + dataStack.toString()); } if (lastElement != null) processLastElement(false); if (collectedCharacters.length() != 0) processCharacters(); if (skipDepth == 1 && context.targetHandler != null && dataStack.peek().lastProcStatus == PR_CHILDREN) { // provisional fix for bug #765301 // (This whole external filter stuff needs to be rewritten to // enable the functionality for stx:process-siblings. Using // skipDepth isn't really a good idea ...) skipDepth = 0; endExternDocument(); } if (skipDepth == 0) { clearProcessSiblings(); Data data = dataStack.pop(); short prStatus = data.lastProcStatus; context.currentGroup = data.currentGroup; context.targetGroup = dataStack.peek().targetGroup; if (data.template == null) { // perform default action? if ((data.targetGroup.passThrough & PASS_THROUGH_ELEMENT) != 0) context.emitter.endElement(uri, lName, qName, data.targetGroup); } else if (prStatus == PR_CHILDREN || prStatus == PR_SELF) { context.position = data.contextPosition; // restore position context.localVars = data.localVars; AbstractInstruction inst = data.instruction; inst = doProcessLoop(inst, (SAXEvent) eventStack.peek(), true); if (DEBUG) if (log.isDebugEnabled()) log.debug("stop " + processStatus); switch (processStatus) { case PR_CHILDREN: case PR_SELF: { NodeBase start = inst.getNode(); context.errorHandler.error("Encountered '" + start.qName + "' after stx:process-" + // prStatus must be either PR_CHILDREN or PR_SELF, see above (prStatus == PR_CHILDREN ? "children" : "self"), start.publicId, start.systemId, start.lineNo, start.colNo); throw new SAXException("Non-recoverable error"); } case PR_SIBLINGS: dataStack.push(new Data(PR_SIBLINGS, data.template, inst, data.passedParams, context, (SAXEvent) eventStack.peek())); break; // case PR_ATTRIBUTES: won't happen case PR_ERROR: throw new SAXException("Non-recoverable error"); } } else { if (log != null) log.error("encountered 'else' " + prStatus); else System.err.println("Error - encountered 'else' " + prStatus); } } else { skipDepth--; if (context.targetHandler != null) { context.targetHandler.endElement(uri, lName, qName); if (skipDepth == 0) endExternDocument(); } } if (skipDepth == 0) { // look at the previous process status on the data stack if (dataStack.peek().lastProcStatus == PR_SELF) { endElement(uri, lName, qName); // recurse (process-self) } else { eventStack.pop(); inScopeNamespaces = (Hashtable) namespaceContext.pop(); } } } public void characters(char[] ch, int start, int length) throws SAXException { if (skipDepth > 0) { if (context.targetHandler != null) context.targetHandler.characters(ch, start, length); return; } collectedCharacters.append(ch, start, length); } public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { characters(ch, start, length); } public void processingInstruction(String target, String data) throws SAXException { if (insideDTD) return; if (lastElement != null) processLastElement(true); if (collectedCharacters.length() != 0) processCharacters(); if (skipDepth > 0) { if (context.targetHandler != null) context.targetHandler.processingInstruction(target, data); return; } // don't modify the event stack after process-self ((SAXEvent) eventStack.peek()).countPI(target); eventStack.push(SAXEvent.newPI(target, data)); processEvent(); eventStack.pop(); } public void startPrefixMapping(String prefix, String uri) throws SAXException { if (lastElement != null) processLastElement(true); if (skipDepth > 0) { if (context.targetHandler != null) context.targetHandler.startPrefixMapping(prefix, uri); return; } if (!nsContextActive) { namespaceContext.push(inScopeNamespaces); inScopeNamespaces = (Hashtable) inScopeNamespaces.clone(); nsContextActive = true; } if (uri.equals("")) // undeclare namespace inScopeNamespaces.remove(prefix); else inScopeNamespaces.put(prefix, uri); } public void endPrefixMapping(String prefix) throws SAXException { if (context.targetHandler != null) context.targetHandler.endPrefixMapping(prefix); } // public void skippedEntity(String name) // { // } /** * Store the locator in the context object */ public void setDocumentLocator(Locator locator) { context.locator = locator; } // // from interface LexicalHandler // public void startDTD(String name, String publicId, String systemId) { insideDTD = true; } public void endDTD() { insideDTD = false; } public void startEntity(java.lang.String name) throws SAXException { } public void endEntity(java.lang.String name) throws SAXException { } public void startCDATA() throws SAXException { if (!context.targetGroup.recognizeCdata) return; if (DEBUG) log.debug(""); if (skipDepth > 0) { if (context.targetHandler != null) context.targetHandler.startCDATA(); return; } if (collectedCharacters.length() != 0) { if (lastElement != null) processLastElement(true); processCharacters(); if (skipDepth > 0) { if (context.targetHandler != null) context.targetHandler.startCDATA(); return; } } insideCDATA = true; } public void endCDATA() throws SAXException { if (!context.targetGroup.recognizeCdata) return; if (skipDepth > 0) { if (context.targetHandler != null) context.targetHandler.endCDATA(); return; } if (lastElement != null) processLastElement(true); processCharacters(); // test for emptiness occurs there insideCDATA = false; } public void comment(char[] ch, int start, int length) throws SAXException { if (DEBUG) if (log.isDebugEnabled()) log.debug(new String(ch, start, length)); if (insideDTD) return; if (lastElement != null) processLastElement(true); if (collectedCharacters.length() != 0) processCharacters(); if (skipDepth > 0) { if (context.targetHandler != null) context.targetHandler.comment(ch, start, length); return; } // don't modify the event stack after process-self ((SAXEvent) eventStack.peek()).countComment(); eventStack.push(SAXEvent.newComment(new String(ch, start, length))); processEvent(); eventStack.pop(); } // //----------------------------new methods----------------------------- // /** * Returns a reference to the event stack. * @return the event stack */ public Stack getEventStack() { return this.eventStack; } /** * Returns a reference to the data stack. * @return the data stack */ protected DataStack getDataStack() { return this.dataStack; } /** * Returns a ref to the current context of the processing. * @return the current context */ public Context getContext() { return this.context; } /** * Returns a ref to the registered emitter * @return the emitter */ public Emitter getEmitter() { return context.emitter; } /** * Returns a ref to the last element (look ahead) * @return the last element */ protected SAXEvent getLastElement() { return this.lastElement; } // ********************************************************************** // private static long maxUsed = 0; // private static int initWait = 0; // private void traceMemory() // { // System.gc(); // if (initWait < 20) { // initWait++; // return; // } // long total = Runtime.getRuntime().totalMemory(); // long free = Runtime.getRuntime().freeMemory(); // long used = total-free; // maxUsed = (used>maxUsed) ? used : maxUsed; // log.debug((total - free) + " = " + total + " - " + free + // " [" + maxUsed + "]"); // /* // log.debug("templateStack: " + templateStack.size()); // log.debug("templateProcStack: " + templateProcStack.size()); // log.debug("categoryStack: " + categoryStack.size()); // log.debug("eventStack: " + eventStack.size()); // log.debug("newNs: " + newNs.size()); // log.debug("collectedCharacters: " + collectedCharacters.capacity()); // */ // } }