blob: 89f78a99d675d6dfb274451d3c22490347b0683a [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xalan" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, Lotus
* Development Corporation., http://www.lotus.com. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.xalan.transformer;
import java.util.Enumeration;
import org.apache.xalan.processor.StylesheetHandler;
import org.apache.xalan.res.XSLTErrorResources;
import org.apache.xalan.templates.Stylesheet;
import org.apache.xalan.templates.StylesheetRoot;
import org.apache.xalan.templates.ElemTemplate;
import org.apache.xalan.templates.ElemTemplateElement;
import org.apache.xalan.trace.TraceManager;
import org.apache.xalan.trace.GenerateEvent;
import org.apache.xml.utils.MutableAttrListImpl;
import org.apache.xml.utils.QName;
import org.apache.xml.utils.TreeWalker;
import org.apache.xml.utils.ObjectPool;
import org.apache.xml.utils.XMLCharacterRecognizer;
import org.apache.xpath.objects.XObject;
import org.apache.xpath.XPathContext;
import org.apache.xml.dtm.DTM;
import org.apache.xml.dtm.DTMIterator;
import org.apache.xml.dtm.DTMFilter;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXException;
import org.xml.sax.ErrorHandler;
import org.xml.sax.helpers.NamespaceSupport;
import org.apache.xml.utils.NamespaceSupport2;
import org.xml.sax.Locator;
import javax.xml.transform.TransformerException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Transformer;
/**
* This class is a layer between the direct calls to the result
* tree content handler, and the transformer. For one thing,
* we have to delay the call to
* getContentHandler().startElement(name, atts) because of the
* xsl:attribute and xsl:copy calls. In other words,
* the attributes have to be fully collected before you
* can call startElement.
*/
public class ResultTreeHandler extends QueuedEvents
implements ContentHandler, LexicalHandler, TransformState,
org.apache.xml.dtm.ref.dom2dtm.DOM2DTM.CharacterNodeHandler,
ErrorHandler
{
/** Indicate whether running in Debug mode */
private static final boolean DEBUG = false;
/**
* Null constructor for object pooling.
*/
public ResultTreeHandler(){}
/**
* Create a new result tree handler. The real content
* handler will be the ContentHandler passed as an argument.
*
* @param transformer non-null transformer instance
* @param realHandler Content Handler instance
*/
public ResultTreeHandler(TransformerImpl transformer,
ContentHandler realHandler)
{
init(transformer, realHandler);
}
/**
* Initializer method.
*
* @param transformer non-null transformer instance
* @param realHandler Content Handler instance
*/
public void init(TransformerImpl transformer, ContentHandler realHandler)
{
m_transformer = transformer;
// m_startDoc.setTransformer(m_transformer);
TraceManager tracer = transformer.getTraceManager();
if ((null != tracer) && tracer.hasTraceListeners())
m_tracer = tracer;
else
m_tracer = null;
// m_startDoc.setTraceManager(m_tracer);
m_contentHandler = realHandler;
// m_startDoc.setContentHandler(m_contentHandler);
if (m_contentHandler instanceof LexicalHandler)
m_lexicalHandler = (LexicalHandler) m_contentHandler;
else
m_lexicalHandler = null;
m_isTransformClient = (m_contentHandler instanceof TransformerClient);
m_cloner = new ClonerToResultTree(transformer, this);
// The stylesheet is set at a rather late stage, so I do
// this here, though it would probably be better done elsewhere.
if (null != m_transformer)
m_stylesheetRoot = m_transformer.getStylesheet();
pushDocumentEvent(); // not pending yet.
}
/**
* Bottleneck the startDocument event.
*
* @throws org.xml.sax.SAXException
*/
public void startDocument() throws org.xml.sax.SAXException{}
/**
* Bottleneck the endDocument event. This may be called
* more than once in order to make sure the pending start
* document is called.
*
* @throws org.xml.sax.SAXException
*/
public void endDocument() throws org.xml.sax.SAXException
{
flushPending(true);
if (!m_docEnded)
{
m_contentHandler.endDocument();
if (null != m_tracer)
{
GenerateEvent ge =
new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_ENDDOCUMENT, null);
m_tracer.fireGenerateEvent(ge);
}
m_docEnded = true;
m_docPending = false;
}
}
/**
* Bottleneck the startElement event. This is used to "pend" an
* element, so that attributes can still be added to it before
* the real "startElement" is called on the result tree listener.
*
* @param ns Namespace URI of element
* @param localName Local part of qname of element
* @param name Name of element
* @param atts List of attributes for the element
*
* @throws org.xml.sax.SAXException
*/
public void startElement(
String ns, String localName, String name, Attributes atts)
throws org.xml.sax.SAXException
{
if (DEBUG)
{
if (m_elemIsPending)
System.out.println("(ResultTreeHandler#startElement - pended: "
+ m_url + "#" + m_localName);
System.out.println("ResultTreeHandler#startElement: " + ns + "#"
+ localName);
// if(null == ns)
// {
// (new RuntimeException(localName+" has a null namespace!")).printStackTrace();
// }
}
if(m_docPending)
checkForSerializerSwitch(ns, localName);
flushPending(true);
if (!m_nsContextPushed)
{
if (DEBUG)
System.out.println(
"ResultTreeHandler#startElement - push(startElement)");
m_nsSupport.pushContext();
m_nsContextPushed = true;
}
if (ns != null)
ensurePrefixIsDeclared(ns, name);
m_name = name;
m_url = ns;
m_localName = localName;
if (null != atts)
m_attributes.addAttributes(atts);
m_elemIsPending = true;
m_elemIsEnded = false;
if(m_isTransformClient && (null != m_transformer))
{
m_snapshot.m_currentElement = m_transformer.getCurrentElement();
m_snapshot.m_currentTemplate = m_transformer.getCurrentTemplate();
m_snapshot.m_matchedTemplate = m_transformer.getMatchedTemplate();
int currentNodeHandle = m_transformer.getCurrentNode();
DTM dtm = m_transformer.getXPathContext().getDTM(currentNodeHandle);
m_snapshot.m_currentNode = dtm.getNode(currentNodeHandle);
m_snapshot.m_matchedNode = m_transformer.getMatchedNode();
m_snapshot.m_contextNodeList = m_transformer.getContextNodeList(); // TODO: Need to clone
}
// initQSE(m_startElement);
m_eventCount++;
}
/**
* Bottleneck the endElement event.
*
* @param ns Namespace URI of element
* @param localName Local part of qname of element
* @param name Name of element
*
* @throws org.xml.sax.SAXException
*/
public void endElement(String ns, String localName, String name)
throws org.xml.sax.SAXException
{
if (DEBUG)
{
if (m_elemIsPending)
System.out.println("(ResultTreeHandler#endElement - pended: "
+ m_url + "#" + m_localName);
System.out.println("ResultTreeHandler#endElement: " + ns + "#"
+ localName);
}
flushPending(true);
m_contentHandler.endElement(ns, localName, name);
if (null != m_tracer)
{
GenerateEvent ge = new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_ENDELEMENT,
name, (Attributes)null);
m_tracer.fireGenerateEvent(ge);
}
sendEndPrefixMappings();
popEvent();
if (DEBUG)
System.out.println("ResultTreeHandler#startElement pop: " + localName);
m_nsSupport.popContext();
}
/** Indicate whether a namespace context was pushed */
boolean m_nsContextPushed = false;
/**
* Begin the scope of a prefix-URI Namespace mapping.
*
* <p>The information from this event is not necessary for
* normal Namespace processing: the SAX XML reader will
* automatically replace prefixes for element and attribute
* names when the http://xml.org/sax/features/namespaces
* feature is true (the default).</p>
*
* <p>There are cases, however, when applications need to
* use prefixes in character data or in attribute values,
* where they cannot safely be expanded automatically; the
* start/endPrefixMapping event supplies the information
* to the application to expand prefixes in those contexts
* itself, if necessary.</p>
*
* <p>Note that start/endPrefixMapping events are not
* guaranteed to be properly nested relative to each-other:
* all startPrefixMapping events will occur before the
* corresponding startElement event, and all endPrefixMapping
* events will occur after the corresponding endElement event,
* but their order is not guaranteed.</p>
*
* @param prefix The Namespace prefix being declared.
* @param uri The Namespace URI the prefix is mapped to.
* @throws org.xml.sax.SAXException The client may throw
* an exception during processing.
* @see #endPrefixMapping
* @see #startElement
*/
public void startPrefixMapping(String prefix, String uri)
throws org.xml.sax.SAXException
{
startPrefixMapping(prefix, uri, true);
}
/**
* Begin the scope of a prefix-URI Namespace mapping.
*
*
* @param prefix The Namespace prefix being declared.
* @param uri The Namespace URI the prefix is mapped to.
* @param shouldFlush Indicate whether pending events needs
* to be flushed first
*
* @throws org.xml.sax.SAXException The client may throw
* an exception during processing.
*/
public void startPrefixMapping(
String prefix, String uri, boolean shouldFlush)
throws org.xml.sax.SAXException
{
if (shouldFlush)
flushPending(false);
if (!m_nsContextPushed)
{
if (DEBUG)
System.out.println(
"ResultTreeHandler#startPrefixMapping push(startPrefixMapping: "
+ prefix + ")");
m_nsSupport.pushContext();
m_nsContextPushed = true;
}
if (null == prefix)
prefix = ""; // bit-o-hack, that that's OK
String existingURI = m_nsSupport.getURI(prefix);
if (null == existingURI)
existingURI = "";
if (null == uri)
uri = "";
if (!existingURI.equals(uri))
{
if (DEBUG)
{
System.out.println("ResultTreeHandler#startPrefixMapping Prefix: "
+ prefix);
System.out.println("ResultTreeHandler#startPrefixMapping uri: "
+ uri);
}
m_nsSupport.declarePrefix(prefix, uri);
}
}
/**
* End the scope of a prefix-URI mapping.
*
* <p>See startPrefixMapping for details. This event will
* always occur after the corresponding endElement event,
* but the order of endPrefixMapping events is not otherwise
* guaranteed.</p>
*
* @param prefix The prefix that was being mapping.
* @throws org.xml.sax.SAXException The client may throw
* an exception during processing.
* @see #startPrefixMapping
* @see #endElement
*/
public void endPrefixMapping(String prefix)
throws org.xml.sax.SAXException{}
/**
* Bottleneck the characters event.
*
* @param ch Array of characters to process
* @param start start of characters in the array
* @param length Number of characters in the array
*
* @throws org.xml.sax.SAXException
*/
public void characters(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
// It would be nice to suppress all whitespace before the
// first element, but this is going to cause potential problems with
// text serialization and with text entities (right term?).
// So this really needs to be done at the serializer level.
/*if (m_startDoc.isPending
&& XMLCharacterRecognizer.isWhiteSpace(ch, start, length))
return;*/
if (DEBUG)
{
System.out.print("ResultTreeHandler#characters: ");
int n = start + length;
for (int i = start; i < n; i++)
{
if (Character.isWhitespace(ch[i]))
System.out.print("\\" + ((int) ch[i]));
else
System.out.print(ch[i]);
}
System.out.println("");
}
flushPending(true);
m_contentHandler.characters(ch, start, length);
if (null != m_tracer)
{
GenerateEvent ge = new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_CHARACTERS,
ch, start, length);
m_tracer.fireGenerateEvent(ge);
}
}
public void characters(org.w3c.dom.Node node)
throws org.xml.sax.SAXException
{
flushPending(true);
if(m_isTransformClient)
m_snapshot.m_currentNode = node;
String data = node.getNodeValue();
char [] ch = null;
int length = 0;
if (data != null)
{
ch = data.toCharArray();
length = data.length();
m_contentHandler.characters(ch, 0, length);
}
if (null != m_tracer)
{
GenerateEvent ge = new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_CHARACTERS,
ch, 0, length);
m_tracer.fireGenerateEvent(ge);
}
if(m_isTransformClient)
m_snapshot.m_currentNode = null;
}
/**
* Bottleneck the ignorableWhitespace event.
*
* @param ch Array of characters to process
* @param start start of characters in the array
* @param length Number of characters in the array
*
* @throws org.xml.sax.SAXException
*/
public void ignorableWhitespace(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
if (m_docPending
&& XMLCharacterRecognizer.isWhiteSpace(ch, start, length))
return;
flushPending(true);
m_contentHandler.ignorableWhitespace(ch, start, length);
if (null != m_tracer)
{
GenerateEvent ge =
new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_IGNORABLEWHITESPACE, ch,
start, length);
m_tracer.fireGenerateEvent(ge);
}
}
/**
* Bottleneck the processingInstruction event.
*
* @param target Processing instruction target name
* @param data Processing instruction data
*
* @throws org.xml.sax.SAXException
*/
public void processingInstruction(String target, String data)
throws org.xml.sax.SAXException
{
flushPending(true);
m_contentHandler.processingInstruction(target, data);
if (null != m_tracer)
{
GenerateEvent ge = new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_PI,
target, data);
m_tracer.fireGenerateEvent(ge);
}
}
/**
* Bottleneck the comment event.
*
* @param data Comment data
*
* @throws org.xml.sax.SAXException
*/
public void comment(String data) throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.comment(data.toCharArray(), 0, data.length());
}
if (null != m_tracer)
{
GenerateEvent ge = new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_COMMENT,
data);
m_tracer.fireGenerateEvent(ge);
}
}
/**
* Bottleneck the comment event.
*
* @param ch Character array with comment data
* @param start start of characters in the array
* @param length number of characters in the array
*
* @throws org.xml.sax.SAXException
*/
public void comment(char ch[], int start, int length)
throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.comment(ch, start, length);
}
if (null != m_tracer)
{
GenerateEvent ge = new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_COMMENT,
new String(ch, start, length));
m_tracer.fireGenerateEvent(ge);
}
}
/**
* Entity reference event.
*
* @param name Name of entity
*
* @throws org.xml.sax.SAXException
*/
public void entityReference(String name) throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.startEntity(name);
m_lexicalHandler.endEntity(name);
}
if (null != m_tracer)
{
GenerateEvent ge = new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_ENTITYREF,
name);
m_tracer.fireGenerateEvent(ge);
}
}
/**
* Start an entity.
*
* @param name Name of the entity
*
* @throws org.xml.sax.SAXException
*/
public void startEntity(String name) throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.startEntity(name);
}
}
/**
* End an entity.
*
* @param name Name of the entity
*
* @throws org.xml.sax.SAXException
*/
public void endEntity(String name) throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.endEntity(name);
}
if (null != m_tracer)
{
GenerateEvent ge = new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_ENTITYREF,
name);
m_tracer.fireGenerateEvent(ge);
}
}
/**
* Start the DTD.
*
* @param s1 The document type name.
* @param s2 The declared public identifier for the
* external DTD subset, or null if none was declared.
* @param s3 The declared system identifier for the
* external DTD subset, or null if none was declared.
*
* @throws org.xml.sax.SAXException
*/
public void startDTD(String s1, String s2, String s3)
throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.startDTD(s1, s2, s3);
}
}
/**
* End the DTD.
*
* @throws org.xml.sax.SAXException
*/
public void endDTD() throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.endDTD();
}
}
/**
* Start the CDATACharacters.
*
* @throws org.xml.sax.SAXException
*/
public void startCDATA() throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.startCDATA();
}
}
/**
* End the CDATA characters.
*
* @throws org.xml.sax.SAXException
*/
public void endCDATA() throws org.xml.sax.SAXException
{
flushPending(true);
if (null != m_lexicalHandler)
{
m_lexicalHandler.endCDATA();
}
}
/**
* Receive notification of a skipped entity.
*
* <p>The Parser will invoke this method once for each entity
* skipped. Non-validating processors may skip entities if they
* have not seen the declarations (because, for example, the
* entity was declared in an external DTD subset). All processors
* may skip external entities, depending on the values of the
* http://xml.org/sax/features/external-general-entities and the
* http://xml.org/sax/features/external-parameter-entities
* properties.</p>
*
* @param name The name of the skipped entity. If it is a
* parameter entity, the name will begin with '%'.
* @throws org.xml.sax.SAXException Any SAX exception, possibly
* wrapping another exception.
*/
public void skippedEntity(String name) throws org.xml.sax.SAXException{}
/**
* Set whether Namespace declarations have been added to
* this element
*
*
* @param b Flag indicating whether Namespace declarations
* have been added to this element
*/
public void setNSDeclsHaveBeenAdded(boolean b)
{
m_nsDeclsHaveBeenAdded = b;
}
/**
* Flush the event.
*
* @throws TransformerException
*
* @throws org.xml.sax.SAXException
*/
void flushDocEvent() throws org.xml.sax.SAXException
{
if (m_docPending)
{
m_contentHandler.startDocument();
if (null != m_tracer)
{
GenerateEvent ge =
new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_STARTDOCUMENT);
m_tracer.fireGenerateEvent(ge);
}
if (m_contentHandler instanceof TransformerClient)
{
((TransformerClient) m_contentHandler).setTransformState(this);
}
m_docPending = false;
}
}
/**
* Flush the event.
*
* @throws SAXException
*/
void flushElem() throws org.xml.sax.SAXException
{
if (m_elemIsPending)
{
if (null != m_name)
{
try
{
m_contentHandler.startElement(m_url, m_localName, m_name,
m_attributes);
}
catch(Exception re)
{
// If we don't do this, and the exception is a RuntimeException,
// good line numbers of where the exception occured in the stylesheet
// won't get reported. I tried just catching RuntimeException, but
// for whatever reason it didn't seem to catch.
// Fix for Christina's DOMException error problem.
throw new SAXParseException(re.getMessage(),
m_transformer.getCurrentElement().getPublicId(),
m_transformer.getCurrentElement().getSystemId(),
m_transformer.getCurrentElement().getLineNumber(),
m_transformer.getCurrentElement().getColumnNumber(),
re);
}
if(null != m_tracer)
{
GenerateEvent ge =
new GenerateEvent(m_transformer,
GenerateEvent.EVENTTYPE_STARTELEMENT, m_name,
m_attributes);
m_tracer.fireGenerateEvent(ge);
}
if(m_isTransformClient)
m_snapshot.m_currentNode = null;
}
m_elemIsPending = false;
m_attributes.clear();
m_nsDeclsHaveBeenAdded = false;
m_name = null;
m_url = null;
m_localName = null;
m_namespaces = null;
// super.flush();
}
}
/**
* Flush the pending element.
*
* @throws org.xml.sax.SAXException
*/
public final void flushPending() throws org.xml.sax.SAXException
{
flushPending(true);
}
/**
* Flush the pending element, and any attributes associated with it.
*
* NOTE: If there are attributes but _no_ pending element (which can
* happen if the user's stylesheet is doing something inappropriate),
* we still want to make sure they are flushed.
*
* @param type Event type
*
* NEEDSDOC @param flushPrefixes
*
* @throws org.xml.sax.SAXException
*/
public final void flushPending(boolean flushPrefixes)
throws org.xml.sax.SAXException
{
if (flushPrefixes && m_docPending)
{
flushDocEvent();
}
if (m_elemIsPending)
{
// Combined loop shoud be much more efficient.
// %REVIEW% %OPT% Will the "else" case ever arise?
if (!m_nsDeclsHaveBeenAdded)
// addNSDeclsToAttrs();
startAndAddPrefixMappings(); // new
else // new
sendStartPrefixMappings();
if (DEBUG)
{
System.out.println("ResultTreeHandler#flushPending - start flush: "
+ m_name);
}
flushElem();
if (DEBUG)
{
System.out.println(
"ResultTreeHandler#flushPending - after flush, isPending: "
+ m_elemIsPending);
}
m_nsContextPushed = false;
}
}
/**
* Given a result tree fragment, walk the tree and
* output it to the result stream.
*
* @param obj Result tree fragment object
* @param support XPath context for the result tree fragment
*
* @throws org.xml.sax.SAXException
*/
public void outputResultTreeFragment(XObject obj, XPathContext support)
throws org.xml.sax.SAXException
{
int doc = obj.rtf();
DTM dtm = support.getDTM(doc);
if(null != dtm)
{
for (int n = dtm.getFirstChild(doc); DTM.NULL != n;
n = dtm.getNextSibling(n))
{
flushPending(true); // I think.
if (dtm.getNamespaceURI(n) == null)
startPrefixMapping("","");
dtm.dispatchToEvents(n, this);
}
}
}
/**
* To fullfill the FormatterListener interface... no action
* for the moment.
*
* @param locator Document locator
*/
public void setDocumentLocator(Locator locator){}
/**
* This function checks to make sure a given prefix is really
* declared. It might not be, because it may be an excluded prefix.
* If it's not, it still needs to be declared at this point.
* TODO: This needs to be done at an earlier stage in the game... -sb
*
* @param ns Namespace URI of the element
* @param rawName Raw name of element (with prefix)
*
* @throws org.xml.sax.SAXException
*/
public void ensurePrefixIsDeclared(String ns, String rawName)
throws org.xml.sax.SAXException
{
if (ns != null && ns.length() > 0)
{
int index;
String prefix = (index = rawName.indexOf(":")) < 0
? "" : rawName.substring(0, index);
if (null != prefix)
{
String foundURI = m_nsSupport.getURI(prefix);
if ((null == foundURI) ||!foundURI.equals(ns))
{
startPrefixMapping(prefix, ns, false);
// Bugzilla1133: Generate attribute as well as namespace event.
// SAX does expect both.
m_attributes.addAttribute("http://www.w3.org/2000/xmlns/",
prefix,
"xmlns"+(prefix.length()==0 ? "" : ":")+prefix,
"CDATA", ns);
}
}
}
}
/**
* This function checks to make sure a given prefix is really
* declared. It might not be, because it may be an excluded prefix.
* If it's not, it still needs to be declared at this point.
* TODO: This needs to be done at an earlier stage in the game... -sb
*
* @param ns Namespace URI of the element
* @param rawName Raw name of element (with prefix)
*
* NEEDSDOC @param dtm
* NEEDSDOC @param namespace
*
* @throws org.xml.sax.SAXException
*/
public void ensureNamespaceDeclDeclared(DTM dtm, int namespace)
throws org.xml.sax.SAXException
{
String uri = dtm.getNodeValue(namespace);
String prefix = dtm.getNodeNameX(namespace);
if ((uri != null && uri.length() > 0) && (null != prefix))
{
String foundURI = m_nsSupport.getURI(prefix);
if ((null == foundURI) ||!foundURI.equals(uri))
{
startPrefixMapping(prefix, uri, false);
}
}
}
/**
* Add the attributes that have been declared to the attribute list.
* (Seems like I shouldn't have to do this...)
* Internally deprecated in favor of combined startAndAddPrefixMappings();
*
*
* @throws org.xml.sax.SAXException
*/
protected void sendStartPrefixMappings() throws org.xml.sax.SAXException
{
Enumeration prefixes = m_nsSupport.getDeclaredPrefixes();
ContentHandler handler = m_contentHandler;
while (prefixes.hasMoreElements())
{
String prefix = (String) prefixes.nextElement();
handler.startPrefixMapping(prefix, m_nsSupport.getURI(prefix));
}
}
/**
* Combination of sendStartPrefixMappings and
* addNSDeclsToAttrs() (which it mostly replaces). Merging the two
* loops is significantly more efficient.
*
* @throws org.xml.sax.SAXException */
protected void startAndAddPrefixMappings() throws org.xml.sax.SAXException
{
Enumeration prefixes = m_nsSupport.getDeclaredPrefixes();
ContentHandler handler = m_contentHandler;
while (prefixes.hasMoreElements())
{
String prefix = (String) prefixes.nextElement();
String uri=m_nsSupport.getURI(prefix);
if (null == uri)
uri = "";
// Send event
handler.startPrefixMapping(prefix, uri);
// Set attribute
boolean isDefault = (prefix.length() == 0);
String name;
if (isDefault)
{
//prefix = "xml";
name = "xmlns";
}
else
name = "xmlns:" + prefix;
m_attributes.addAttribute("http://www.w3.org/2000/xmlns/",
prefix, name, "CDATA", uri);
}
m_nsDeclsHaveBeenAdded=true;
}
/**
* Add the attributes that have been declared to the attribute list.
* (Seems like I shouldn't have to do this...)
*
* @throws org.xml.sax.SAXException
*/
protected void sendEndPrefixMappings() throws org.xml.sax.SAXException
{
Enumeration prefixes = m_nsSupport.getDeclaredPrefixes();
ContentHandler handler = m_contentHandler;
while (prefixes.hasMoreElements())
{
String prefix = (String) prefixes.nextElement();
handler.endPrefixMapping(prefix);
}
}
/**
* Check to see if we should switch serializers based on the
* first output element being an HTML element.
*
* @param ns Namespace URI of the element
* @param localName Local part of name of the element
*
* @throws org.xml.sax.SAXException
*/
private void checkForSerializerSwitch(String ns, String localName)
throws org.xml.sax.SAXException
{
try
{
if (m_docPending)
{
SerializerSwitcher.switchSerializerIfHTML(m_transformer, ns,
localName);
}
}
catch (TransformerException te)
{
throw new org.xml.sax.SAXException(te);
}
}
/**
* Add the attributes that have been declared to the attribute list.
*
* %REVIEW% This should have been done automatically during
* flushPending(boolean); is it ever explicitly reinvoked?
*/
public void addNSDeclsToAttrs()
{
Enumeration prefixes = m_nsSupport.getDeclaredPrefixes();
while (prefixes.hasMoreElements())
{
String prefix = (String) prefixes.nextElement();
boolean isDefault = (prefix.length() == 0);
String name;
if (isDefault)
{
//prefix = "xml";
name = "xmlns";
}
else
name = "xmlns:" + prefix;
String uri = m_nsSupport.getURI(prefix);
if (null == uri)
uri = "";
m_attributes.addAttribute("http://www.w3.org/2000/xmlns/",
prefix, name, "CDATA", uri);
m_nsDeclsHaveBeenAdded = true;
}
}
/**
* Copy <KBD>xmlns:</KBD> attributes in if not already in scope.
*
* As a quick hack to support ClonerToResultTree, this can also be used
* to copy an individual namespace node.
*
* @param src Source Node
* NEEDSDOC @param type
* NEEDSDOC @param dtm
*
* @throws TransformerException
*/
public void processNSDecls(int src, int type, DTM dtm)
throws TransformerException
{
try
{
if (type == DTM.ELEMENT_NODE)
{
for (int namespace = dtm.getFirstNamespaceNode(src, true);
DTM.NULL != namespace;
namespace = dtm.getNextNamespaceNode(src, namespace, true))
{
// String prefix = dtm.getPrefix(namespace);
String prefix = dtm.getNodeNameX(namespace);
String desturi = getURI(prefix);
String srcURI = dtm.getNodeValue(namespace);
if (!srcURI.equalsIgnoreCase(desturi))
{
this.startPrefixMapping(prefix, srcURI, false);
}
}
}
else if (type == DTM.NAMESPACE_NODE)
{
String prefix = dtm.getNodeNameX(src);
String desturi = getURI(prefix);
String srcURI = dtm.getNodeValue(src);
if (!srcURI.equalsIgnoreCase(desturi))
{
this.startPrefixMapping(prefix, srcURI, false);
}
}
}
catch (org.xml.sax.SAXException se)
{
throw new TransformerException(se);
}
}
/**
* Given a prefix, return the namespace,
*
* @param prefix Given prefix name
*
* @return Namespace associated with the given prefix, or null
*/
public String getURI(String prefix)
{
return m_nsSupport.getURI(prefix);
}
/**
* Given a namespace, try and find a prefix.
*
* @param namespace Given namespace URI
*
* @return Prefix name associated with namespace URI
*/
public String getPrefix(String namespace)
{
// This Enumeration business may be too slow for our purposes...
Enumeration enum = m_nsSupport.getPrefixes();
while (enum.hasMoreElements())
{
String prefix = (String) enum.nextElement();
if (m_nsSupport.getURI(prefix).equals(namespace))
return prefix;
}
return null;
}
/**
* Get the NamespaceSupport object.
*
* @return NamespaceSupport object.
*/
public NamespaceSupport getNamespaceSupport()
{
return m_nsSupport;
}
// /**
// * Override QueuedEvents#initQSE.
// *
// * @param qse Give queued Sax event
// */
// protected void initQSE(QueuedSAXEvent qse)
// {
//
// // qse.setContentHandler(m_contentHandler);
// // qse.setTransformer(m_transformer);
// // qse.setTraceManager(m_tracer);
// }
/**
* Return the current content handler.
*
* @return The current content handler, or null if none
* has been registered.
* @see #setContentHandler
*/
public final ContentHandler getContentHandler()
{
return m_contentHandler;
}
/**
* Set the current content handler.
*
*
* @param ch Content Handler to be set
* @return The current content handler, or null if none
* has been registered.
* @see #getContentHandler
*/
public void setContentHandler(ContentHandler ch)
{
m_contentHandler = ch;
m_isTransformClient = (m_contentHandler instanceof TransformerClient);
if (m_contentHandler instanceof LexicalHandler)
m_lexicalHandler = (LexicalHandler) m_contentHandler;
else
m_lexicalHandler = null;
reInitEvents();
}
/**
* Get a unique namespace value.
*
* @return a unique namespace value to be used with a
* fabricated prefix
*/
public int getUniqueNSValue()
{
return m_uniqueNSValue++;
}
/**
* Get new unique namespace prefix.
*
* @return Unique fabricated prefix.
*/
public String getNewUniqueNSPrefix()
{
return S_NAMESPACEPREFIX + String.valueOf(getUniqueNSValue());
}
/**
* Get the pending attributes. We have to delay the call to
* m_flistener.startElement(name, atts) because of the
* xsl:attribute and xsl:copy calls. In other words,
* the attributes have to be fully collected before you
* can call startElement.
*
* @return the pending attributes.
*/
public MutableAttrListImpl getPendingAttributes()
{
return m_attributes;
}
/**
* Add an attribute to the end of the list.
*
* <p>Do not pass in xmlns decls to this function!
*
* <p>For the sake of speed, this method does no checking
* to see if the attribute is already in the list: that is
* the responsibility of the application.</p>
*
* @param uri The Namespace URI, or the empty string if
* none is available or Namespace processing is not
* being performed.
* @param localName The local name, or the empty string if
* Namespace processing is not being performed.
* @param rawName The raw XML 1.0 name, or the empty string
* if raw names are not available.
* @param type The attribute type as a string.
* @param value The attribute value.
*
* @throws TransformerException
*/
public void addAttribute(
String uri, String localName, String rawName, String type, String value)
throws TransformerException
{
// %REVIEW% See Bugzilla 4344. Do we need an "else" that announces
// an error? Technically, this can't happen unless the stylesheet
// is unreasonable... but it's unclear whether silent or noisy
// failure is called for.
// Will add an "else" and emit a warning message. This should
// cover testcases such as copyerr04-07, attribset19,34,35,
// attribseterr08...(is)
if (m_elemIsPending)
{
// %REVIEW% %OPT% Is this ever needed?????
// The check is not needed. See Bugzilla 10306.
// if (!m_nsDeclsHaveBeenAdded)
addNSDeclsToAttrs();
if (null == uri) { // defensive, should not really need this.
uri = "";
}
try {
if (!rawName.equals("xmlns")) { // don't handle xmlns default namespace.
ensurePrefixIsDeclared(uri, rawName);
}
} catch (org.xml.sax.SAXException se) {
throw new TransformerException(se);
}
if (DEBUG) {
System.out.println("ResultTreeHandler#addAttribute Adding attr: "
+ localName + ", " + uri);
}
if (!isDefinedNSDecl(rawName, value)) {
m_attributes.addAttribute(uri, localName, rawName, type, value);
}
} else {
m_transformer.getMsgMgr().warn(m_stylesheetRoot,
XSLTErrorResources.WG_ILLEGAL_ATTRIBUTE_POSITION,
new Object[]{ localName });
}
}
/**
* Return whether or not a namespace declaration is defined
*
*
* @param rawName Raw name of namespace element
* @param value URI of given namespace
*
* @return True if the namespace is already defined in list of
* namespaces
*/
public boolean isDefinedNSDecl(String rawName, String value)
{
if (rawName.equals("xmlns") || rawName.startsWith("xmlns:"))
{
int index;
String prefix = (index = rawName.indexOf(":")) < 0
? "" : rawName.substring(0, index);
String definedURI = m_nsSupport.getURI(prefix);
if (null != definedURI)
{
if (definedURI.equals(value))
{
return true;
}
else
return false;
}
else
return false;
}
else
return false;
}
/**
* Returns whether a namespace is defined
*
*
* @param attr Namespace attribute node
*
* @return True if the namespace is already defined in
* list of namespaces
*/
public boolean isDefinedNSDecl(int attr)
{
DTM dtm = m_transformer.getXPathContext().getDTM(attr);
if (DTM.NAMESPACE_NODE == dtm.getNodeType(attr))
{
// String prefix = dtm.getPrefix(attr);
String prefix = dtm.getNodeNameX(attr);
String uri = getURI(prefix);
if ((null != uri) && uri.equals(dtm.getStringValue(attr)))
return true;
}
return false;
}
/**
* Returns whether a namespace is defined
*
*
* @param attr Namespace attribute node
* @param dtm The DTM that owns attr.
*
* @return True if the namespace is already defined in
* list of namespaces
*/
public boolean isDefinedNSDecl(int attr, DTM dtm)
{
if (DTM.NAMESPACE_NODE == dtm.getNodeType(attr))
{
// String prefix = dtm.getPrefix(attr);
String prefix = dtm.getNodeNameX(attr);
String uri = getURI(prefix);
if ((null != uri) && uri.equals(dtm.getStringValue(attr)))
return true;
}
return false;
}
/**
* Copy an DOM attribute to the created output element, executing
* attribute templates as need be, and processing the xsl:use
* attribute.
*
* @param attr Attribute node to add to result tree
*
* @throws TransformerException
*/
public void addAttribute(int attr) throws TransformerException
{
DTM dtm = m_transformer.getXPathContext().getDTM(attr);
if (isDefinedNSDecl(attr, dtm))
return;
String ns = dtm.getNamespaceURI(attr);
if (ns == null)
ns = "";
// %OPT% ...can I just store the node handle?
addAttribute(ns, dtm.getLocalName(attr), dtm.getNodeName(attr), "CDATA",
dtm.getNodeValue(attr));
} // end copyAttributeToTarget method
/**
* Copy DOM attributes to the result element.
*
* @param src Source node with the attributes
*
* @throws TransformerException
*/
public void addAttributes(int src) throws TransformerException
{
DTM dtm = m_transformer.getXPathContext().getDTM(src);
for (int node = dtm.getFirstAttribute(src); DTM.NULL != node;
node = dtm.getNextAttribute(node))
{
addAttribute(node);
}
}
/**
* Tell if an element is pending, to be output to the result tree.
*
* @return True if an element is pending
*/
public final boolean isElementPending()
{
return m_elemIsPending;
}
/**
* Retrieves the stylesheet element that produced
* the SAX event.
*
* <p>Please note that the ElemTemplateElement returned may
* be in a default template, and thus may not be
* defined in the stylesheet.</p>
*
* @return the stylesheet element that produced the SAX event.
*/
public ElemTemplateElement getCurrentElement()
{
if (m_elemIsPending)
return m_snapshot.m_currentElement;
else
return m_transformer.getCurrentElement();
}
/**
* This method retrieves the current context node
* in the source tree.
*
* @return the current context node in the source tree.
*/
public org.w3c.dom.Node getCurrentNode()
{
if (m_snapshot.m_currentNode != null)
{
return m_snapshot.m_currentNode;
}
else
{
DTM dtm = m_transformer.getXPathContext().getDTM(m_transformer.getCurrentNode());
return dtm.getNode(m_transformer.getCurrentNode());
}
}
/**
* This method retrieves the xsl:template
* that is in effect, which may be a matched template
* or a named template.
*
* <p>Please note that the ElemTemplate returned may
* be a default template, and thus may not have a template
* defined in the stylesheet.</p>
*
* @return the xsl:template that is in effect
*/
public ElemTemplate getCurrentTemplate()
{
if (m_elemIsPending)
return m_snapshot.m_currentTemplate;
else
return m_transformer.getCurrentTemplate();
}
/**
* This method retrieves the xsl:template
* that was matched. Note that this may not be
* the same thing as the current template (which
* may be from getCurrentElement()), since a named
* template may be in effect.
*
* <p>Please note that the ElemTemplate returned may
* be a default template, and thus may not have a template
* defined in the stylesheet.</p>
*
* @return the xsl:template that was matched.
*/
public ElemTemplate getMatchedTemplate()
{
if (m_elemIsPending)
return m_snapshot.m_matchedTemplate;
else
return m_transformer.getMatchedTemplate();
}
/**
* Retrieves the node in the source tree that matched
* the template obtained via getMatchedTemplate().
*
* @return the node in the source tree that matched
* the template obtained via getMatchedTemplate().
*/
public org.w3c.dom.Node getMatchedNode()
{
if (m_elemIsPending)
{
DTM dtm = m_transformer.getXPathContext().getDTM(m_snapshot.m_matchedNode);
return dtm.getNode(m_snapshot.m_matchedNode);
}
else
{
DTM dtm = m_transformer.getXPathContext().getDTM(m_transformer.getMatchedNode());
return dtm.getNode(m_transformer.getMatchedNode());
}
}
/**
* Get the current context node list.
*
* @return the current context node list.
*/
public org.w3c.dom.traversal.NodeIterator getContextNodeList()
{
if (m_elemIsPending)
{
return new org.apache.xml.dtm.ref.DTMNodeIterator(m_snapshot.m_contextNodeList);
}
else
return new org.apache.xml.dtm.ref.DTMNodeIterator(m_transformer.getContextNodeList());
}
/**
* Get the TrAX Transformer object in effect.
*
* @return the TrAX Transformer object in effect.
*/
public Transformer getTransformer()
{
return m_transformer;
}
// Implement ErrorHandler
/**
* Receive notification of a warning.
*
* <p>SAX parsers will use this method to report conditions that
* are not errors or fatal errors as defined by the XML 1.0
* recommendation. The default behaviour is to take no action.</p>
*
* <p>The SAX parser must continue to provide normal parsing events
* after invoking this method: it should still be possible for the
* application to process the document through to the end.</p>
*
* <p>Filters may use this method to report other, non-XML warnings
* as well.</p>
*
* @param exception The warning information encapsulated in a
* SAX parse exception.
* @exception org.xml.sax.SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.SAXParseException
*/
public void warning (SAXParseException exception)
throws SAXException
{
if (m_contentHandler instanceof ErrorHandler)
((ErrorHandler)m_contentHandler).warning(exception);
}
/**
* Receive notification of a recoverable error.
*
* <p>This corresponds to the definition of "error" in section 1.2
* of the W3C XML 1.0 Recommendation. For example, a validating
* parser would use this callback to report the violation of a
* validity constraint. The default behaviour is to take no
* action.</p>
*
* <p>The SAX parser must continue to provide normal parsing events
* after invoking this method: it should still be possible for the
* application to process the document through to the end. If the
* application cannot do so, then the parser should report a fatal
* error even if the XML 1.0 recommendation does not require it to
* do so.</p>
*
* <p>Filters may use this method to report other, non-XML errors
* as well.</p>
*
* @param exception The error information encapsulated in a
* SAX parse exception.
* @exception org.xml.sax.SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.SAXParseException
*/
public void error (SAXParseException exception)
throws SAXException
{
if (m_contentHandler instanceof ErrorHandler)
((ErrorHandler)m_contentHandler).error(exception);
}
/**
* Receive notification of a non-recoverable error.
*
* <p>This corresponds to the definition of "fatal error" in
* section 1.2 of the W3C XML 1.0 Recommendation. For example, a
* parser would use this callback to report the violation of a
* well-formedness constraint.</p>
*
* <p>The application must assume that the document is unusable
* after the parser has invoked this method, and should continue
* (if at all) only for the sake of collecting addition error
* messages: in fact, SAX parsers are free to stop reporting any
* other events once this method has been invoked.</p>
*
* @param exception The error information encapsulated in a
* SAX parse exception.
* @exception org.xml.sax.SAXException Any SAX exception, possibly
* wrapping another exception.
* @see org.xml.sax.SAXParseException
*/
public void fatalError (SAXParseException exception)
throws SAXException
{
m_elemIsPending = false;
m_docEnded = true;
m_docPending = false;
if (m_contentHandler instanceof ErrorHandler)
((ErrorHandler)m_contentHandler).fatalError(exception);
}
boolean m_isTransformClient = false;
/**
* Use the SAX2 helper class to track result namespaces.
*/
NamespaceSupport m_nsSupport = new NamespaceSupport2();
/**
* The transformer object.
*/
private TransformerImpl m_transformer;
/**
* The content handler. May be null, in which
* case, we'll defer to the content handler in the
* transformer.
*/
private ContentHandler m_contentHandler;
/** The LexicalHandler */
private LexicalHandler m_lexicalHandler;
/**
* The root of a linked set of stylesheets.
*/
private StylesheetRoot m_stylesheetRoot = null;
/**
* This is used whenever a unique namespace is needed.
*/
private int m_uniqueNSValue = 0;
/** Prefix used to create unique prefix names */
private static final String S_NAMESPACEPREFIX = "ns";
/**
* This class clones nodes to the result tree.
*/
public ClonerToResultTree m_cloner;
/**
* Trace manager for debug support.
*/
private TraceManager m_tracer;
private QueuedStateSnapshot m_snapshot = new QueuedStateSnapshot();
// These are passed to flushPending, to help it decide if it
// should really flush.
class QueuedStateSnapshot
{
/**
* The stylesheet element that produced the SAX event.
*/
ElemTemplateElement m_currentElement;
/**
* The current context node in the source tree.
*/
org.w3c.dom.Node m_currentNode;
/**
* The xsl:template that is in effect, which may be a matched template
* or a named template.
*/
ElemTemplate m_currentTemplate;
/**
* The xsl:template that was matched.
*/
ElemTemplate m_matchedTemplate;
/**
* The node in the source tree that matched
* the template obtained via getMatchedTemplate().
*/
int m_matchedNode;
/**
* The current context node list.
*/
DTMIterator m_contextNodeList;
}
}