/* * 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. */ /* * $Id: StylesheetHandler.java 468640 2006-10-28 06:53:53Z minchau $ */ package org.apache.xalan.processor; import java.util.Stack; import javax.xml.transform.ErrorListener; import javax.xml.transform.Source; import javax.xml.transform.SourceLocator; import javax.xml.transform.Templates; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.sax.TemplatesHandler; import org.apache.xalan.extensions.ExpressionVisitor; import org.apache.xalan.res.XSLMessages; import org.apache.xalan.res.XSLTErrorResources; import org.apache.xalan.templates.Constants; import org.apache.xalan.templates.ElemForEach; import org.apache.xalan.templates.ElemTemplateElement; import org.apache.xalan.templates.Stylesheet; import org.apache.xalan.templates.StylesheetRoot; import org.apache.xml.utils.BoolStack; import org.apache.xml.utils.NamespaceSupport2; import org.apache.xml.utils.NodeConsumer; import org.apache.xml.utils.PrefixResolver; import org.apache.xml.utils.SAXSourceLocator; import org.apache.xml.utils.XMLCharacterRecognizer; import org.apache.xpath.XPath; import org.apache.xpath.compiler.FunctionTable; import org.apache.xpath.functions.Function; import org.w3c.dom.Node; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.NamespaceSupport; /** * Initializes and processes a stylesheet via SAX events. * This class acts as essentially a state machine, maintaining * a ContentHandler stack, and pushing appropriate content * handlers as parse events occur. * @xsl.usage advanced */ public class StylesheetHandler extends DefaultHandler implements TemplatesHandler, PrefixResolver, NodeConsumer { /** * The function table of XPath and XSLT; */ private FunctionTable m_funcTable = new FunctionTable(); /** * The flag for the setting of the optimize feature; */ private boolean m_optimize = true; /** * The flag for the setting of the incremental feature; */ private boolean m_incremental = false; /** * The flag for the setting of the source_location feature; */ private boolean m_source_location = false; /** * Create a StylesheetHandler object, creating a root stylesheet * as the target. * * @param processor non-null reference to the transformer factory that owns this handler. * * @throws TransformerConfigurationException if a StylesheetRoot * can not be constructed for some reason. */ public StylesheetHandler(TransformerFactoryImpl processor) throws TransformerConfigurationException { Class func = org.apache.xalan.templates.FuncDocument.class; m_funcTable.installFunction("document", func); // func = new org.apache.xalan.templates.FuncKey(); // FunctionTable.installFunction("key", func); func = org.apache.xalan.templates.FuncFormatNumb.class; m_funcTable.installFunction("format-number", func); m_optimize =((Boolean) processor.getAttribute( TransformerFactoryImpl.FEATURE_OPTIMIZE)).booleanValue(); m_incremental = ((Boolean) processor.getAttribute( TransformerFactoryImpl.FEATURE_INCREMENTAL)).booleanValue(); m_source_location = ((Boolean) processor.getAttribute( TransformerFactoryImpl.FEATURE_SOURCE_LOCATION)).booleanValue(); // m_schema = new XSLTSchema(); init(processor); } /** * Do common initialization. * * @param processor non-null reference to the transformer factory that owns this handler. */ void init(TransformerFactoryImpl processor) { m_stylesheetProcessor = processor; // Set the initial content handler. m_processors.push(m_schema.getElementProcessor()); this.pushNewNamespaceSupport(); // m_includeStack.push(SystemIDResolver.getAbsoluteURI(this.getBaseIdentifier(), null)); // initXPath(processor, null); } /** * Process an expression string into an XPath. * Must be public for access by the AVT class. * * @param str A non-null reference to a valid or invalid XPath expression string. * * @return A non-null reference to an XPath object that represents the string argument. * * @throws javax.xml.transform.TransformerException if the expression can not be processed. * @see Section 4 Expressions in XSLT Specification */ public XPath createXPath(String str, ElemTemplateElement owningTemplate) throws javax.xml.transform.TransformerException { ErrorListener handler = m_stylesheetProcessor.getErrorListener(); XPath xpath = new XPath(str, owningTemplate, this, XPath.SELECT, handler, m_funcTable); // Visit the expression, registering namespaces for any extension functions it includes. xpath.callVisitors(xpath, new ExpressionVisitor(getStylesheetRoot())); return xpath; } /** * Process an expression string into an XPath. * * @param str A non-null reference to a valid or invalid match pattern string. * * @return A non-null reference to an XPath object that represents the string argument. * * @throws javax.xml.transform.TransformerException if the pattern can not be processed. * @see Section 5.2 Patterns in XSLT Specification */ XPath createMatchPatternXPath(String str, ElemTemplateElement owningTemplate) throws javax.xml.transform.TransformerException { ErrorListener handler = m_stylesheetProcessor.getErrorListener(); XPath xpath = new XPath(str, owningTemplate, this, XPath.MATCH, handler, m_funcTable); // Visit the expression, registering namespaces for any extension functions it includes. xpath.callVisitors(xpath, new ExpressionVisitor(getStylesheetRoot())); return xpath; } /** * Given a namespace, get the corrisponding prefix from the current * namespace support context. * * @param prefix The prefix to look up, which may be an empty string ("") for the default Namespace. * * @return The associated Namespace URI, or null if the prefix * is undeclared in this context. */ public String getNamespaceForPrefix(String prefix) { return this.getNamespaceSupport().getURI(prefix); } /** * Given a namespace, get the corrisponding prefix. This is here only * to support the {@link org.apache.xml.utils.PrefixResolver} interface, * and will throw an error if invoked on this object. * * @param prefix The prefix to look up, which may be an empty string ("") for the default Namespace. * @param context The node context from which to look up the URI. * * @return The associated Namespace URI, or null if the prefix * is undeclared in this context. */ public String getNamespaceForPrefix(String prefix, org.w3c.dom.Node context) { // Don't need to support this here. Return the current URI for the prefix, // ignoring the context. assertion(true, "can't process a context node in StylesheetHandler!"); return null; } /** * Utility function to see if the stack contains the given URL. * * @param stack non-null reference to a Stack. * @param url URL string on which an equality test will be performed. * * @return true if the stack contains the url argument. */ private boolean stackContains(Stack stack, String url) { int n = stack.size(); boolean contains = false; for (int i = 0; i < n; i++) { String url2 = (String) stack.elementAt(i); if (url2.equals(url)) { contains = true; break; } } return contains; } //////////////////////////////////////////////////////////////////// // Implementation of the TRAX TemplatesBuilder interface. //////////////////////////////////////////////////////////////////// /** * When this object is used as a ContentHandler or ContentHandler, it will * create a Templates object, which the caller can get once * the SAX events have been completed. * @return The stylesheet object that was created during * the SAX event process, or null if no stylesheet has * been created. * * Author Scott Boag * * */ public Templates getTemplates() { return getStylesheetRoot(); } /** * Set the base ID (URL or system ID) for the stylesheet * created by this builder. This must be set in order to * resolve relative URLs in the stylesheet. * * @param baseID Base URL for this stylesheet. */ public void setSystemId(String baseID) { pushBaseIndentifier(baseID); } /** * Get the base ID (URI or system ID) from where relative * URLs will be resolved. * * @return The systemID that was set with {@link #setSystemId}. */ public String getSystemId() { return this.getBaseIdentifier(); } //////////////////////////////////////////////////////////////////// // Implementation of the EntityResolver interface. //////////////////////////////////////////////////////////////////// /** * Resolve an external entity. * * @param publicId The public identifer, or null if none is * available. * @param systemId The system identifier provided in the XML * document. * @return The new input source, or null to require the * default behaviour. * * @throws org.xml.sax.SAXException if the entity can not be resolved. */ public InputSource resolveEntity(String publicId, String systemId) throws org.xml.sax.SAXException { return getCurrentProcessor().resolveEntity(this, publicId, systemId); } //////////////////////////////////////////////////////////////////// // Implementation of DTDHandler interface. //////////////////////////////////////////////////////////////////// /** * Receive notification of a notation declaration. * *

By default, do nothing. Application writers may override this * method in a subclass if they wish to keep track of the notations * declared in a document.

* * @param name The notation name. * @param publicId The notation public identifier, or null if not * available. * @param systemId The notation system identifier. * @see org.xml.sax.DTDHandler#notationDecl */ public void notationDecl(String name, String publicId, String systemId) { getCurrentProcessor().notationDecl(this, name, publicId, systemId); } /** * Receive notification of an unparsed entity declaration. * * @param name The entity name. * @param publicId The entity public identifier, or null if not * available. * @param systemId The entity system identifier. * @param notationName The name of the associated notation. * @see org.xml.sax.DTDHandler#unparsedEntityDecl */ public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) { getCurrentProcessor().unparsedEntityDecl(this, name, publicId, systemId, notationName); } /** * Given a namespace URI, and a local name or a node type, get the processor * for the element, or return null if not allowed. * * @param uri The Namespace URI, or an empty string. * @param localName The local name (without prefix), or empty string if not namespace processing. * @param rawName The qualified name (with prefix). * * @return A non-null reference to a element processor. * * @throws org.xml.sax.SAXException if the element is not allowed in the * found position in the stylesheet. */ XSLTElementProcessor getProcessorFor( String uri, String localName, String rawName) throws org.xml.sax.SAXException { XSLTElementProcessor currentProcessor = getCurrentProcessor(); XSLTElementDef def = currentProcessor.getElemDef(); XSLTElementProcessor elemProcessor = def.getProcessorFor(uri, localName); if (null == elemProcessor && !(currentProcessor instanceof ProcessorStylesheetDoc) && ((null == getStylesheet() || Double.valueOf(getStylesheet().getVersion()).doubleValue() > Constants.XSLTVERSUPPORTED) ||(!uri.equals(Constants.S_XSLNAMESPACEURL) && currentProcessor instanceof ProcessorStylesheetElement) || getElemVersion() > Constants.XSLTVERSUPPORTED )) { elemProcessor = def.getProcessorForUnknown(uri, localName); } if (null == elemProcessor) error(XSLMessages.createMessage(XSLTErrorResources.ER_NOT_ALLOWED_IN_POSITION, new Object[]{rawName}),null);//rawName + " is not allowed in this position in the stylesheet!", return elemProcessor; } //////////////////////////////////////////////////////////////////// // Implementation of ContentHandler interface. //////////////////////////////////////////////////////////////////// /** * Receive a Locator object for document events. * This is called by the parser to push a locator for the * stylesheet being parsed. The stack needs to be popped * after the stylesheet has been parsed. We pop in * popStylesheet. * * @param locator A locator for all SAX document events. * @see org.xml.sax.ContentHandler#setDocumentLocator * @see org.xml.sax.Locator */ public void setDocumentLocator(Locator locator) { // System.out.println("pushing locator for: "+locator.getSystemId()); m_stylesheetLocatorStack.push(new SAXSourceLocator(locator)); } /** * The level of the stylesheet we are at. */ private int m_stylesheetLevel = -1; /** * Receive notification of the beginning of the document. * * @see org.xml.sax.ContentHandler#startDocument * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void startDocument() throws org.xml.sax.SAXException { m_stylesheetLevel++; pushSpaceHandling(false); } /** m_parsingComplete becomes true when the top-level stylesheet and all * its included/imported stylesheets have been been fully parsed, as an * indication that composition/optimization/compilation can begin. * @see isStylesheetParsingComplete */ private boolean m_parsingComplete = false; /** * Test whether the _last_ endDocument() has been processed. * This is needed as guidance for stylesheet optimization * and compilation engines, which generally don't want to start * until all included and imported stylesheets have been fully * parsed. * * @return true iff the complete stylesheet tree has been built. */ public boolean isStylesheetParsingComplete() { return m_parsingComplete; } /** * Receive notification of the end of the document. * * @see org.xml.sax.ContentHandler#endDocument * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void endDocument() throws org.xml.sax.SAXException { try { if (null != getStylesheetRoot()) { if (0 == m_stylesheetLevel) getStylesheetRoot().recompose(); } else throw new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_NO_STYLESHEETROOT, null)); //"Did not find the stylesheet root!"); XSLTElementProcessor elemProcessor = getCurrentProcessor(); if (null != elemProcessor) elemProcessor.startNonText(this); m_stylesheetLevel--; popSpaceHandling(); // WARNING: This test works only as long as stylesheets are parsed // more or less recursively. If we switch to an iterative "work-list" // model, this will become true prematurely. In that case, // isStylesheetParsingComplete() will have to be adjusted to be aware // of the worklist. m_parsingComplete = (m_stylesheetLevel < 0); } catch (TransformerException te) { throw new org.xml.sax.SAXException(te); } } private java.util.Vector m_prefixMappings = new java.util.Vector(); /** * Receive notification of the start of a Namespace mapping. * *

By default, do nothing. Application writers may override this * method in a subclass to take specific actions at the start of * each element (such as allocating a new tree node or writing * output to a file).

* * @param prefix The Namespace prefix being declared. * @param uri The Namespace URI mapped to the prefix. * @see org.xml.sax.ContentHandler#startPrefixMapping * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void startPrefixMapping(String prefix, String uri) throws org.xml.sax.SAXException { // m_nsSupport.pushContext(); // this.getNamespaceSupport().declarePrefix(prefix, uri); //m_prefixMappings.add(prefix); // JDK 1.2+ only -sc //m_prefixMappings.add(uri); // JDK 1.2+ only -sc m_prefixMappings.addElement(prefix); // JDK 1.1.x compat -sc m_prefixMappings.addElement(uri); // JDK 1.1.x compat -sc } /** * Receive notification of the end of a Namespace mapping. * *

By default, do nothing. Application writers may override this * method in a subclass to take specific actions at the start of * each element (such as allocating a new tree node or writing * output to a file).

* * @param prefix The Namespace prefix being declared. * @see org.xml.sax.ContentHandler#endPrefixMapping * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException { // m_nsSupport.popContext(); } /** * Flush the characters buffer. * * @throws org.xml.sax.SAXException */ private void flushCharacters() throws org.xml.sax.SAXException { XSLTElementProcessor elemProcessor = getCurrentProcessor(); if (null != elemProcessor) elemProcessor.startNonText(this); } /** * Receive notification of the start of an element. * * @param uri The Namespace URI, or an empty string. * @param localName The local name (without prefix), or empty string if not namespace processing. * @param rawName The qualified name (with prefix). * @param attributes The specified or defaulted attributes. * * @throws org.xml.sax.SAXException */ public void startElement( String uri, String localName, String rawName, Attributes attributes) throws org.xml.sax.SAXException { NamespaceSupport nssupport = this.getNamespaceSupport(); nssupport.pushContext(); int n = m_prefixMappings.size(); for (int i = 0; i < n; i++) { String prefix = (String)m_prefixMappings.elementAt(i++); String nsURI = (String)m_prefixMappings.elementAt(i); nssupport.declarePrefix(prefix, nsURI); } //m_prefixMappings.clear(); // JDK 1.2+ only -sc m_prefixMappings.removeAllElements(); // JDK 1.1.x compat -sc m_elementID++; // This check is currently done for all elements. We should possibly consider // limiting this check to xsl:stylesheet elements only since that is all it really // applies to. Also, it could be bypassed if m_shouldProcess is already true. // In other words, the next two statements could instead look something like this: // if (!m_shouldProcess) // { // if (localName.equals(Constants.ELEMNAME_STYLESHEET_STRING) && // url.equals(Constants.S_XSLNAMESPACEURL)) // { // checkForFragmentID(attributes); // if (!m_shouldProcess) // return; // } // else // return; // } // I didn't include this code statement at this time because in practice // it is a small performance hit and I was waiting to see if its absence // caused a problem. - GLP checkForFragmentID(attributes); if (!m_shouldProcess) return; flushCharacters(); pushSpaceHandling(attributes); XSLTElementProcessor elemProcessor = getProcessorFor(uri, localName, rawName); if(null != elemProcessor) // defensive, for better multiple error reporting. -sb { this.pushProcessor(elemProcessor); elemProcessor.startElement(this, uri, localName, rawName, attributes); } else { m_shouldProcess = false; popSpaceHandling(); } } /** * Receive notification of the end of an element. * * @param uri The Namespace URI, or an empty string. * @param localName The local name (without prefix), or empty string if not namespace processing. * @param rawName The qualified name (with prefix). * @see org.xml.sax.ContentHandler#endElement * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void endElement(String uri, String localName, String rawName) throws org.xml.sax.SAXException { m_elementID--; if (!m_shouldProcess) return; if ((m_elementID + 1) == m_fragmentID) m_shouldProcess = false; flushCharacters(); popSpaceHandling(); XSLTElementProcessor p = getCurrentProcessor(); p.endElement(this, uri, localName, rawName); this.popProcessor(); this.getNamespaceSupport().popContext(); } /** * Receive notification of character data inside an element. * * @param ch The characters. * @param start The start position in the character array. * @param length The number of characters to use from the * character array. * @see org.xml.sax.ContentHandler#characters * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void characters(char ch[], int start, int length) throws org.xml.sax.SAXException { if (!m_shouldProcess) return; XSLTElementProcessor elemProcessor = getCurrentProcessor(); XSLTElementDef def = elemProcessor.getElemDef(); if (def.getType() != XSLTElementDef.T_PCDATA) elemProcessor = def.getProcessorFor(null, "text()"); if (null == elemProcessor) { // If it's whitespace, just ignore it, otherwise flag an error. if (!XMLCharacterRecognizer.isWhiteSpace(ch, start, length)) error( XSLMessages.createMessage(XSLTErrorResources.ER_NONWHITESPACE_NOT_ALLOWED_IN_POSITION, null),null);//"Non-whitespace text is not allowed in this position in the stylesheet!", } else elemProcessor.characters(this, ch, start, length); } /** * Receive notification of ignorable whitespace in element content. * * @param ch The whitespace characters. * @param start The start position in the character array. * @param length The number of characters to use from the * character array. * @see org.xml.sax.ContentHandler#ignorableWhitespace * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void ignorableWhitespace(char ch[], int start, int length) throws org.xml.sax.SAXException { if (!m_shouldProcess) return; getCurrentProcessor().ignorableWhitespace(this, ch, start, length); } /** * Receive notification of a processing instruction. * *

The Parser will invoke this method once for each processing * instruction found: note that processing instructions may occur * before or after the main document element.

* *

A SAX parser should never report an XML declaration (XML 1.0, * section 2.8) or a text declaration (XML 1.0, section 4.3.1) * using this method.

* *

By default, do nothing. Application writers may override this * method in a subclass to take specific actions for each * processing instruction, such as setting status variables or * invoking other methods.

* * @param target The processing instruction target. * @param data The processing instruction data, or null if * none is supplied. * @see org.xml.sax.ContentHandler#processingInstruction * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void processingInstruction(String target, String data) throws org.xml.sax.SAXException { if (!m_shouldProcess) return; // Recreating Scott's kluge: // A xsl:for-each or xsl:apply-templates may have a special // PI that tells us not to cache the document. This PI // should really be namespaced. // String localName = getLocalName(target); // String ns = m_stylesheet.getNamespaceFromStack(target); // // %REVIEW%: We need a better PI architecture String prefix="",ns="", localName=target; int colon=target.indexOf(':'); if(colon>=0) { ns=getNamespaceForPrefix(prefix=target.substring(0,colon)); localName=target.substring(colon+1); } try { // A xsl:for-each or xsl:apply-templates may have a special // PI that tells us not to cache the document. This PI // should really be namespaced... but since the XML Namespaces // spec never defined namespaces as applying to PI's, and since // the testcase we're trying to support is inconsistant in whether // it binds the prefix, I'm going to make this sloppy for // testing purposes. if( "xalan-doc-cache-off".equals(target) || "xalan:doc-cache-off".equals(target) || ("doc-cache-off".equals(localName) && ns.equals("org.apache.xalan.xslt.extensions.Redirect") ) ) { if(!(m_elems.peek() instanceof ElemForEach)) throw new TransformerException ("xalan:doc-cache-off not allowed here!", getLocator()); ElemForEach elem = (ElemForEach)m_elems.peek(); elem.m_doc_cache_off = true; //System.out.println("JJK***** Recognized "); } } catch(Exception e) { // JJK: Officially, unknown PIs can just be ignored. // Do we want to issue a warning? } flushCharacters(); getCurrentProcessor().processingInstruction(this, target, data); } /** * Receive notification of a skipped entity. * *

By default, do nothing. Application writers may override this * method in a subclass to take specific actions for each * processing instruction, such as setting status variables or * invoking other methods.

* * @param name The name of the skipped entity. * @see org.xml.sax.ContentHandler#processingInstruction * * @throws org.xml.sax.SAXException Any SAX exception, possibly * wrapping another exception. */ public void skippedEntity(String name) throws org.xml.sax.SAXException { if (!m_shouldProcess) return; getCurrentProcessor().skippedEntity(this, name); } /** * Warn the user of an problem. * * @param msg An key into the {@link org.apache.xalan.res.XSLTErrorResources} * table, that is one of the WG_ prefixed definitions. * @param args An array of arguments for the given warning. * * @throws org.xml.sax.SAXException that wraps a * {@link javax.xml.transform.TransformerException} if the current * {@link javax.xml.transform.ErrorListener#warning} * method chooses to flag this condition as an error. * @xsl.usage internal */ public void warn(String msg, Object args[]) throws org.xml.sax.SAXException { String formattedMsg = XSLMessages.createWarning(msg, args); SAXSourceLocator locator = getLocator(); ErrorListener handler = m_stylesheetProcessor.getErrorListener(); try { if (null != handler) handler.warning(new TransformerException(formattedMsg, locator)); } catch (TransformerException te) { throw new org.xml.sax.SAXException(te); } } /** * Assert that a condition is true. If it is not true, throw an error. * * @param condition false if an error should not be thrown, otherwise true. * @param msg Error message to be passed to the RuntimeException as an * argument. * @throws RuntimeException if the condition is not true. * @xsl.usage internal */ private void assertion(boolean condition, String msg) throws RuntimeException { if (!condition) throw new RuntimeException(msg); } /** * Tell the user of an error, and probably throw an * exception. * * @param msg An error message. * @param e An error which the SAXException should wrap. * * @throws org.xml.sax.SAXException that wraps a * {@link javax.xml.transform.TransformerException} if the current * {@link javax.xml.transform.ErrorListener#error} * method chooses to flag this condition as an error. * @xsl.usage internal */ protected void error(String msg, Exception e) throws org.xml.sax.SAXException { SAXSourceLocator locator = getLocator(); ErrorListener handler = m_stylesheetProcessor.getErrorListener(); TransformerException pe; if (!(e instanceof TransformerException)) { pe = (null == e) ? new TransformerException(msg, locator) : new TransformerException(msg, locator, e); } else pe = (TransformerException) e; if (null != handler) { try { handler.error(pe); } catch (TransformerException te) { throw new org.xml.sax.SAXException(te); } } else throw new org.xml.sax.SAXException(pe); } /** * Tell the user of an error, and probably throw an * exception. * * @param msg A key into the {@link org.apache.xalan.res.XSLTErrorResources} * table, that is one of the WG_ prefixed definitions. * @param args An array of arguments for the given warning. * @param e An error which the SAXException should wrap. * * @throws org.xml.sax.SAXException that wraps a * {@link javax.xml.transform.TransformerException} if the current * {@link javax.xml.transform.ErrorListener#error} * method chooses to flag this condition as an error. * @xsl.usage internal */ protected void error(String msg, Object args[], Exception e) throws org.xml.sax.SAXException { String formattedMsg = XSLMessages.createMessage(msg, args); error(formattedMsg, e); } /** * Receive notification of a XSLT processing warning. * * @param e The warning information encoded as an exception. * * @throws org.xml.sax.SAXException that wraps a * {@link javax.xml.transform.TransformerException} if the current * {@link javax.xml.transform.ErrorListener#warning} * method chooses to flag this condition as an error. */ public void warning(org.xml.sax.SAXParseException e) throws org.xml.sax.SAXException { String formattedMsg = e.getMessage(); SAXSourceLocator locator = getLocator(); ErrorListener handler = m_stylesheetProcessor.getErrorListener(); try { handler.warning(new TransformerException(formattedMsg, locator)); } catch (TransformerException te) { throw new org.xml.sax.SAXException(te); } } /** * Receive notification of a recoverable XSLT processing error. * * @param e The error information encoded as an exception. * * @throws org.xml.sax.SAXException that wraps a * {@link javax.xml.transform.TransformerException} if the current * {@link javax.xml.transform.ErrorListener#error} * method chooses to flag this condition as an error. */ public void error(org.xml.sax.SAXParseException e) throws org.xml.sax.SAXException { String formattedMsg = e.getMessage(); SAXSourceLocator locator = getLocator(); ErrorListener handler = m_stylesheetProcessor.getErrorListener(); try { handler.error(new TransformerException(formattedMsg, locator)); } catch (TransformerException te) { throw new org.xml.sax.SAXException(te); } } /** * Report a fatal XSLT processing error. * * @param e The error information encoded as an exception. * * @throws org.xml.sax.SAXException that wraps a * {@link javax.xml.transform.TransformerException} if the current * {@link javax.xml.transform.ErrorListener#fatalError} * method chooses to flag this condition as an error. */ public void fatalError(org.xml.sax.SAXParseException e) throws org.xml.sax.SAXException { String formattedMsg = e.getMessage(); SAXSourceLocator locator = getLocator(); ErrorListener handler = m_stylesheetProcessor.getErrorListener(); try { handler.fatalError(new TransformerException(formattedMsg, locator)); } catch (TransformerException te) { throw new org.xml.sax.SAXException(te); } } /** * If we have a URL to a XML fragment, this is set * to false until the ID is found. * (warning: I worry that this should be in a stack). */ private boolean m_shouldProcess = true; /** * If we have a URL to a XML fragment, the value is stored * in this string, and the m_shouldProcess flag is set to * false until we match an ID with this string. * (warning: I worry that this should be in a stack). */ private String m_fragmentIDString; /** * Keep track of the elementID, so we can tell when * is has completed. This isn't a real ID, but rather * a nesting level. However, it's good enough for * our purposes. * (warning: I worry that this should be in a stack). */ private int m_elementID = 0; /** * The ID of the fragment that has been found * (warning: I worry that this should be in a stack). */ private int m_fragmentID = 0; /** * Check to see if an ID attribute matched the #id, called * from startElement. * * @param attributes The specified or defaulted attributes. */ private void checkForFragmentID(Attributes attributes) { if (!m_shouldProcess) { if ((null != attributes) && (null != m_fragmentIDString)) { int n = attributes.getLength(); for (int i = 0; i < n; i++) { String name = attributes.getQName(i); if (name.equals(Constants.ATTRNAME_ID)) { String val = attributes.getValue(i); if (val.equalsIgnoreCase(m_fragmentIDString)) { m_shouldProcess = true; m_fragmentID = m_elementID; } } } } } } /** * The XSLT TransformerFactory for needed services. */ private TransformerFactoryImpl m_stylesheetProcessor; /** * Get the XSLT TransformerFactoryImpl for needed services. * TODO: This method should be renamed. * * @return The TransformerFactoryImpl that owns this handler. */ public TransformerFactoryImpl getStylesheetProcessor() { return m_stylesheetProcessor; } /** * If getStylesheetType returns this value, the current stylesheet * is a root stylesheet. * @xsl.usage internal */ public static final int STYPE_ROOT = 1; /** * If getStylesheetType returns this value, the current stylesheet * is an included stylesheet. * @xsl.usage internal */ public static final int STYPE_INCLUDE = 2; /** * If getStylesheetType returns this value, the current stylesheet * is an imported stylesheet. * @xsl.usage internal */ public static final int STYPE_IMPORT = 3; /** The current stylesheet type. */ private int m_stylesheetType = STYPE_ROOT; /** * Get the type of stylesheet that should be built * or is being processed. * * @return one of STYPE_ROOT, STYPE_INCLUDE, or STYPE_IMPORT. */ int getStylesheetType() { return m_stylesheetType; } /** * Set the type of stylesheet that should be built * or is being processed. * * @param type Must be one of STYPE_ROOT, STYPE_INCLUDE, or STYPE_IMPORT. */ void setStylesheetType(int type) { m_stylesheetType = type; } /** * The stack of stylesheets being processed. */ private Stack m_stylesheets = new Stack(); /** * Return the stylesheet that this handler is constructing. * * @return The current stylesheet that is on top of the stylesheets stack, * or null if no stylesheet is on the stylesheets stack. */ Stylesheet getStylesheet() { return (m_stylesheets.size() == 0) ? null : (Stylesheet) m_stylesheets.peek(); } /** * Return the last stylesheet that was popped off the stylesheets stack. * * @return The last popped stylesheet, or null. */ Stylesheet getLastPoppedStylesheet() { return m_lastPoppedStylesheet; } /** * Return the stylesheet root that this handler is constructing. * * @return The root stylesheet of the stylesheets tree. */ public StylesheetRoot getStylesheetRoot() { if (m_stylesheetRoot != null){ m_stylesheetRoot.setOptimizer(m_optimize); m_stylesheetRoot.setIncremental(m_incremental); m_stylesheetRoot.setSource_location(m_source_location); } return m_stylesheetRoot; } /** The root stylesheet of the stylesheets tree. */ StylesheetRoot m_stylesheetRoot; /** The last stylesheet that was popped off the stylesheets stack. */ Stylesheet m_lastPoppedStylesheet; /** * Push the current stylesheet being constructed. If no other stylesheets * have been pushed onto the stack, assume the argument is a stylesheet * root, and also set the stylesheet root member. * * @param s non-null reference to a stylesheet. */ public void pushStylesheet(Stylesheet s) { if (m_stylesheets.size() == 0) m_stylesheetRoot = (StylesheetRoot) s; m_stylesheets.push(s); } /** * Pop the last stylesheet pushed, and return the stylesheet that this * handler is constructing, and set the last popped stylesheet member. * Also pop the stylesheet locator stack. * * @return The stylesheet popped off the stack, or the last popped stylesheet. */ Stylesheet popStylesheet() { // The stylesheetLocatorStack needs to be popped because // a locator was pushed in for this stylesheet by the SAXparser by calling // setDocumentLocator(). if (!m_stylesheetLocatorStack.isEmpty()) m_stylesheetLocatorStack.pop(); if (!m_stylesheets.isEmpty()) m_lastPoppedStylesheet = (Stylesheet) m_stylesheets.pop(); // Shouldn't this be null if stylesheets is empty? -sb return m_lastPoppedStylesheet; } /** * The stack of current processors. */ private Stack m_processors = new Stack(); /** * Get the current XSLTElementProcessor at the top of the stack. * * @return Valid XSLTElementProcessor, which should never be null. */ XSLTElementProcessor getCurrentProcessor() { return (XSLTElementProcessor) m_processors.peek(); } /** * Push the current XSLTElementProcessor onto the top of the stack. * * @param processor non-null reference to the current element processor. */ void pushProcessor(XSLTElementProcessor processor) { m_processors.push(processor); } /** * Pop the current XSLTElementProcessor from the top of the stack. * @return the XSLTElementProcessor which was popped. */ XSLTElementProcessor popProcessor() { return (XSLTElementProcessor) m_processors.pop(); } /** * The root of the XSLT Schema, which tells us how to * transition content handlers, create elements, etc. * For the moment at least, this can't be static, since * the processors store state. */ private XSLTSchema m_schema = new XSLTSchema(); /** * Get the root of the XSLT Schema, which tells us how to * transition content handlers, create elements, etc. * * @return The root XSLT Schema, which should never be null. * @xsl.usage internal */ public XSLTSchema getSchema() { return m_schema; } /** * The stack of elements, pushed and popped as events occur. */ private Stack m_elems = new Stack(); /** * Get the current ElemTemplateElement at the top of the stack. * @return Valid ElemTemplateElement, which may be null. */ ElemTemplateElement getElemTemplateElement() { try { return (ElemTemplateElement) m_elems.peek(); } catch (java.util.EmptyStackException ese) { return null; } } /** An increasing number that is used to indicate the order in which this element * was encountered during the parse of the XSLT tree. */ private int m_docOrderCount = 0; /** * Returns the next m_docOrderCount number and increments the number for future use. */ int nextUid() { return m_docOrderCount++; } /** * Push the current XSLTElementProcessor to the top of the stack. As a * side-effect, set the document order index (simply because this is a * convenient place to set it). * * @param elem Should be a non-null reference to the intended current * template element. */ void pushElemTemplateElement(ElemTemplateElement elem) { if (elem.getUid() == -1) elem.setUid(nextUid()); m_elems.push(elem); } /** * Get the current XSLTElementProcessor from the top of the stack. * @return the ElemTemplateElement which was popped. */ ElemTemplateElement popElemTemplateElement() { return (ElemTemplateElement) m_elems.pop(); } /** * This will act as a stack to keep track of the * current include base. */ Stack m_baseIdentifiers = new Stack(); /** * Push a base identifier onto the base URI stack. * * @param baseID The current base identifier for this position in the * stylesheet, which may be a fragment identifier, or which may be null. * @see * Section 3.2 Base URI of XSLT specification. */ void pushBaseIndentifier(String baseID) { if (null != baseID) { int posOfHash = baseID.indexOf('#'); if (posOfHash > -1) { m_fragmentIDString = baseID.substring(posOfHash + 1); m_shouldProcess = false; } else m_shouldProcess = true; } else m_shouldProcess = true; m_baseIdentifiers.push(baseID); } /** * Pop a base URI from the stack. * @return baseIdentifier. */ String popBaseIndentifier() { return (String) m_baseIdentifiers.pop(); } /** * Return the base identifier. * * @return The base identifier of the current stylesheet. */ public String getBaseIdentifier() { // Try to get the baseIdentifier from the baseIdentifier's stack, // which may not be the same thing as the value found in the // SourceLocators stack. String base = (String) (m_baseIdentifiers.isEmpty() ? null : m_baseIdentifiers.peek()); // Otherwise try the stylesheet. if (null == base) { SourceLocator locator = getLocator(); base = (null == locator) ? "" : locator.getSystemId(); } return base; } /** * The top of this stack should contain the currently processed * stylesheet SAX locator object. */ private Stack m_stylesheetLocatorStack = new Stack(); /** * Get the current stylesheet Locator object. * * @return non-null reference to the current locator object. */ public SAXSourceLocator getLocator() { if (m_stylesheetLocatorStack.isEmpty()) { SAXSourceLocator locator = new SAXSourceLocator(); locator.setSystemId(this.getStylesheetProcessor().getDOMsystemID()); return locator; // m_stylesheetLocatorStack.push(locator); } return ((SAXSourceLocator) m_stylesheetLocatorStack.peek()); } /** * A stack of URL hrefs for imported stylesheets. This is * used to diagnose circular imports. */ private Stack m_importStack = new Stack(); /** * A stack of Source objects obtained from a URIResolver, * for each element in this stack there is a 1-1 correspondence * with an element in the m_importStack. */ private Stack m_importSourceStack = new Stack(); /** * Push an import href onto the stylesheet stack. * * @param hrefUrl non-null reference to the URL for the current imported * stylesheet. */ void pushImportURL(String hrefUrl) { m_importStack.push(hrefUrl); } /** * Push the Source of an import href onto the stylesheet stack, * obtained from a URIResolver, null if there is no URIResolver, * or if that resolver returned null. */ void pushImportSource(Source sourceFromURIResolver) { m_importSourceStack.push(sourceFromURIResolver); } /** * See if the imported stylesheet stack already contains * the given URL. Used to test for recursive imports. * * @param hrefUrl non-null reference to a URL string. * * @return true if the URL is on the import stack. */ boolean importStackContains(String hrefUrl) { return stackContains(m_importStack, hrefUrl); } /** * Pop an import href from the stylesheet stack. * * @return non-null reference to the import URL that was popped. */ String popImportURL() { return (String) m_importStack.pop(); } String peekImportURL() { return (String) m_importStack.peek(); } Source peekSourceFromURIResolver() { return (Source) m_importSourceStack.peek(); } /** * Pop a Source from a user provided URIResolver, corresponding * to the URL popped from the m_importStack. */ Source popImportSource() { return (Source) m_importSourceStack.pop(); } /** * If this is set to true, we've already warned about using the * older XSLT namespace URL. */ private boolean warnedAboutOldXSLTNamespace = false; /** Stack of NamespaceSupport objects. */ Stack m_nsSupportStack = new Stack(); /** * Push a new NamespaceSupport instance. */ void pushNewNamespaceSupport() { m_nsSupportStack.push(new NamespaceSupport2()); } /** * Pop the current NamespaceSupport object. * */ void popNamespaceSupport() { m_nsSupportStack.pop(); } /** * Get the current NamespaceSupport object. * * @return a non-null reference to the current NamespaceSupport object, * which is the top of the namespace support stack. */ NamespaceSupport getNamespaceSupport() { return (NamespaceSupport) m_nsSupportStack.peek(); } /** * The originating node if the current stylesheet is being created * from a DOM. * @see org.apache.xml.utils.NodeConsumer */ private Node m_originatingNode; /** * Set the node that is originating the SAX event. * * @param n Reference to node that originated the current event. * @see org.apache.xml.utils.NodeConsumer */ public void setOriginatingNode(Node n) { m_originatingNode = n; } /** * Set the node that is originating the SAX event. * * @return Reference to node that originated the current event. * @see org.apache.xml.utils.NodeConsumer */ public Node getOriginatingNode() { return m_originatingNode; } /** * Stack of booleans that are pushed and popped in start/endElement depending * on the value of xml:space=default/preserve. */ private BoolStack m_spacePreserveStack = new BoolStack(); /** * Return boolean value from the spacePreserve stack depending on the value * of xml:space=default/preserve. * * @return true if space should be preserved, false otherwise. */ boolean isSpacePreserve() { return m_spacePreserveStack.peek(); } /** * Pop boolean value from the spacePreserve stack. */ void popSpaceHandling() { m_spacePreserveStack.pop(); } /** * Push boolean value on to the spacePreserve stack. * * @param b true if space should be preserved, false otherwise. */ void pushSpaceHandling(boolean b) throws org.xml.sax.SAXParseException { m_spacePreserveStack.push(b); } /** * Push boolean value on to the spacePreserve stack depending on the value * of xml:space=default/preserve. * * @param attrs list of attributes that were passed to startElement. */ void pushSpaceHandling(Attributes attrs) throws org.xml.sax.SAXParseException { String value = attrs.getValue("xml:space"); if(null == value) { m_spacePreserveStack.push(m_spacePreserveStack.peekOrFalse()); } else if(value.equals("preserve")) { m_spacePreserveStack.push(true); } else if(value.equals("default")) { m_spacePreserveStack.push(false); } else { SAXSourceLocator locator = getLocator(); ErrorListener handler = m_stylesheetProcessor.getErrorListener(); try { handler.error(new TransformerException(XSLMessages.createMessage(XSLTErrorResources.ER_ILLEGAL_XMLSPACE_VALUE, null), locator)); //"Illegal value for xml:space", locator)); } catch (TransformerException te) { throw new org.xml.sax.SAXParseException(te.getMessage(), locator, te); } m_spacePreserveStack.push(m_spacePreserveStack.peek()); } } private double getElemVersion() { ElemTemplateElement elem = getElemTemplateElement(); double version = -1; while ((version == -1 || version == Constants.XSLTVERSUPPORTED) && elem != null) { try{ version = Double.valueOf(elem.getXmlVersion()).doubleValue(); } catch (Exception ex) { version = -1; } elem = elem.getParentElem(); } return (version == -1)? Constants.XSLTVERSUPPORTED : version; } /** * @see PrefixResolver#handlesNullPrefixes() */ public boolean handlesNullPrefixes() { return false; } /** * @return Optimization flag */ public boolean getOptimize() { return m_optimize; } /** * @return Incremental flag */ public boolean getIncremental() { return m_incremental; } /** * @return Source Location flag */ public boolean getSource_location() { return m_source_location; } }