blob: d8b595990dc4978f5b1b2d5042a1a843c0ace6d3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.transformation;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.environment.Context;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.Response;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.transformation.helpers.ParametersRecorder;
import org.apache.cocoon.transformation.helpers.TextRecorder;
import org.apache.cocoon.util.ClassUtils;
import org.apache.cocoon.util.TraxErrorHandler;
import org.apache.cocoon.xml.AttributesImpl;
import org.apache.cocoon.xml.ImmutableAttributesImpl;
import org.apache.cocoon.xml.IncludeXMLConsumer;
import org.apache.cocoon.xml.SaxBuffer;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.XMLUtils;
import org.apache.cocoon.xml.dom.DOMBuilder;
import org.apache.excalibur.source.SourceParameters;
import org.apache.excalibur.xml.sax.XMLizable;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
/**
* This class is the basis for all transformers. It provides various useful
* methods and hooks for implementing own custom transformers.
*
* <p>The basic behaviour of each transformer consists of the following four
* parts:</p>
* <ul>
* <li>Listen for specific events with a given namespace</li>
* <li>Collect information via these events</li>
* <li>Process the information</li>
* <li>Create new events from the processed information</li>
* </ul>
*
* <p>For all these four purposes the AbstractSAXTransformer offers some
* powerful methods and hooks:</p>
*
* <h3>Namespace handling</h3>
* By setting the instance variable namespaceURI to the namespace the
* events are filtered and only events with this namespace are send to
* the two hooks: <code>startTransformingElement</code> and
* <code>endTransformingElement</code>. It is possible to override the default
* namespace for the transformer by specifying the parameter "namespaceURI"
* in the pipeline. This avoids possible namespace collisions.
*
* <h3>Recording of information</h3>
* There are several methods for recording information, e.g. startRecording(),
* startTextRecording() etc. These methods collect information from the xml
* stream for further processing.
*
* <h3>Creating new events</h3>
* New events can be easily created with the <code>sendEvents()</code>
* method, the <code>sendStartElementEvent()</code> methods, the
* <code>sendEndElementEvent()</code> method or the
* <code>sendTextEvent()</code> method.
*
* <h3>Initialization</h3>
* Before the document is processed the <code>setupTransforming</code> hook
* is invoked.
*
* @author <a href="mailto:cziegeler@s-und-n.de">Carsten Ziegeler</a>
* @version $Id$
*/
public abstract class AbstractSAXTransformer extends AbstractTransformer
implements Serviceable, Configurable, Disposable {
/**
* Empty immutable attributes (for performance). Use them
* whenever creating an element with no attributes.
*/
protected static final Attributes EMPTY_ATTRIBUTES = XMLUtils.EMPTY_ATTRIBUTES;
/**
* The trax <code>TransformerFactory</code> used by this transformer.
*/
private SAXTransformerFactory tfactory;
/**
* Controlls SAX event handling.
* If set to true all whitespace events are ignored.
*/
protected boolean ignoreWhitespaces;
/**
* Controlls SAX event handling.
* If set to true all characters events containing only whitespaces
* are ignored.
*/
protected boolean ignoreEmptyCharacters;
/**
* Controlls SAX event handling.
* If this is incremented all events are not forwarded to the next
* pipeline component, but the hooks are still called.
*/
protected int ignoreEventsCount;
/**
* Controlls SAX event handling.
* If this is greater than zero, the hooks are not called. Attention,
* make sure, that you decrement this counter properly as your hooks are
* not called anymore!
*/
protected int ignoreHooksCount;
/**
* The namespace used by the transformer for the SAX events filtering.
* This either equals to the {@link #defaultNamespaceURI} or to the value
* set by the <code>namespaceURI</code> sitemap parameter for the pipeline.
* Must never be null.
*/
protected String namespaceURI;
/**
* This is the default namespace used by the transformer.
* Implementations should set its value in the constructor.
* Must never be null.
*/
protected String defaultNamespaceURI;
/**
* A stack for collecting information.
* The stack is important for collection information especially when
* the tags can be nested.
*/
protected final Stack stack = new Stack();
/**
* The stack of current used recorders
*/
protected final Stack recorderStack = new Stack();
/**
* The current Request object
*/
protected Request request;
/**
* The current Response object
*/
protected Response response;
/**
* The current Context object
*/
protected Context context;
/**
* The current objectModel of the environment
*/
protected Map objectModel;
/**
* The parameters specified in the sitemap
*/
protected Parameters parameters;
/**
* The source attribute specified in the sitemap
*/
protected String source;
/**
* The Avalon ServiceManager for getting Components
*/
protected ServiceManager manager;
/**
* The SourceResolver for this request
*/
protected SourceResolver resolver;
/**
* Are we already initialized for the current request?
*/
private boolean isInitialized;
/**
* Empty attributes (for performance). This can be used
* do create own attributes, but make sure to clean them
* afterwords.
* @deprecated Use {@link AbstractSAXTransformer#EMPTY_ATTRIBUTES}.
*/
protected Attributes emptyAttributes = EMPTY_ATTRIBUTES;
/**
* The namespaces and their prefixes
*/
private final List namespaces = new ArrayList(5);
/**
* The current prefix for our namespace
*/
private String ourPrefix;
//
// Lifecycle
//
/* (non-Javadoc)
* @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
*/
public void service(ServiceManager manager) throws ServiceException {
this.manager = manager;
}
/* (non-Javadoc)
* @see Configurable#configure(Configuration)
*/
public void configure(Configuration configuration) throws ConfigurationException {
String tFactoryClass = configuration.getChild("transformer-factory").getValue(null);
if (tFactoryClass != null) {
try {
this.tfactory = (SAXTransformerFactory) ClassUtils.newInstance(tFactoryClass);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Using transformer factory " + tFactoryClass);
}
} catch (Exception e) {
throw new ConfigurationException("Cannot load transformer factory " + tFactoryClass, e);
}
} else {
// Standard TrAX behaviour
this.tfactory = (SAXTransformerFactory) TransformerFactory.newInstance();
}
tfactory.setErrorListener(new TraxErrorHandler(getLogger()));
}
/* (non-Javadoc)
* @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(SourceResolver, Map, String, Parameters)
*/
public void setup(SourceResolver resolver,
Map objectModel,
String src,
Parameters params)
throws ProcessingException, SAXException, IOException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Setup resolver=" + resolver +
", objectModel=" + objectModel +
", src=" + src +
", parameters=" + params);
}
// defaultNamespaceURI should never be null
if (this.defaultNamespaceURI == null) {
this.defaultNamespaceURI = "";
}
this.objectModel = objectModel;
this.request = ObjectModelHelper.getRequest(objectModel);
this.response = ObjectModelHelper.getResponse(objectModel);
this.context = ObjectModelHelper.getContext(objectModel);
this.resolver = resolver;
this.parameters = params;
this.source = src;
this.isInitialized = false;
// get the current namespace
this.namespaceURI = params.getParameter("namespaceURI",
this.defaultNamespaceURI);
this.ignoreHooksCount = 0;
this.ignoreEventsCount = 0;
this.ignoreWhitespaces = true;
this.ignoreEmptyCharacters = false;
}
/* (non-Javadoc)
* @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
*/
public void recycle() {
this.namespaceURI = null;
this.objectModel = null;
this.request = null;
this.response = null;
this.context = null;
this.resolver = null;
this.stack.clear();
this.recorderStack.clear();
this.parameters = null;
this.source = null;
this.namespaces.clear();
this.ourPrefix = null;
super.recycle();
}
public void dispose() {
this.manager = null;
}
//
// SAX ContentHandler methods
//
/**
* Process the SAX event.
* @see ContentHandler#setDocumentLocator
*/
public void setDocumentLocator(Locator locator) {
if (this.ignoreEventsCount == 0) {
super.setDocumentLocator(locator);
}
}
/**
* Process the SAX event. A new document is processed. The hook method
* {@link #setupTransforming} is invoked.
* @see ContentHandler#startDocument
*/
public void startDocument()
throws SAXException {
if (!this.isInitialized) {
try {
setupTransforming();
} catch (ProcessingException e) {
throw new SAXException("ProcessingException: " + e, e);
} catch (IOException e) {
throw new SAXException("IOException: " + e, e);
}
this.isInitialized = true;
}
if (this.ignoreEventsCount == 0) {
super.startDocument();
}
}
/**
* Process the SAX event. The processing of the document is finished.
* @see org.xml.sax.ContentHandler#endDocument
*/
public void endDocument()
throws SAXException {
if (this.ignoreEventsCount == 0) {
super.endDocument();
}
}
/**
* Process the SAX event.
* @see org.xml.sax.ContentHandler#startPrefixMapping
*/
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
if (prefix != null) {
this.namespaces.add(new String[] {prefix, uri});
}
if (namespaceURI.equals(uri)) {
this.ourPrefix = prefix;
}
if (this.ignoreEventsCount == 0) {
super.startPrefixMapping(prefix, uri);
}
}
/**
* Process the SAX event.
* @see org.xml.sax.ContentHandler#endPrefixMapping
*/
public void endPrefixMapping(String prefix)
throws SAXException {
if (prefix != null) {
// Find and remove the namespace prefix
boolean found = false;
for (int i = this.namespaces.size() - 1; i >= 0; i--) {
final String[] prefixAndUri = (String[]) this.namespaces.get(i);
if (prefixAndUri[0].equals(prefix)) {
this.namespaces.remove(i);
found = true;
break;
}
}
if (!found) {
throw new SAXException("Namespace for prefix '" + prefix + "' not found.");
}
if (prefix.equals(this.ourPrefix)) {
// Reset our current prefix
this.ourPrefix = null;
// Now search if we have a different prefix for our namespace
for (int i = this.namespaces.size() - 1; i >= 0; i--) {
final String[] prefixAndUri = (String[]) this.namespaces.get(i);
if (namespaceURI.equals(prefixAndUri[1])) {
this.ourPrefix = prefixAndUri[0];
break;
}
}
}
}
if (this.ignoreEventsCount == 0) {
super.endPrefixMapping(prefix);
}
}
/**
* Process the SAX event. The namespace of the event is checked.
* If it is the defined namespace for this transformer,
* the {@link #startTransformingElement} hook is called.
* @see org.xml.sax.ContentHandler#startElement
*/
public void startElement(String uri,
String name,
String raw,
Attributes attr)
throws SAXException {
if (namespaceURI.equals(uri) && ignoreHooksCount == 0) {
// this is our namespace:
try {
startTransformingElement(uri, name, raw, attr);
} catch (ProcessingException e) {
throw new SAXException("ProcessingException: " + e, e);
} catch (IOException e) {
throw new SAXException("IOException occured during processing: " + e, e);
}
} else {
if (ignoreEventsCount == 0) {
super.startElement(uri, name, raw, attr);
}
}
}
/**
* Process the SAX event. The namespace of the event is checked.
* If it is the defined namespace for this transformer,
* the {@link #endTransformingElement} hook is called.
* @see org.xml.sax.ContentHandler#endElement
*/
public void endElement(String uri, String name, String raw)
throws SAXException {
if (namespaceURI.equals(uri) && this.ignoreHooksCount == 0) {
// this is our namespace:
try {
endTransformingElement(uri, name, raw);
} catch (ProcessingException e) {
throw new SAXException("ProcessingException: " + e, e);
} catch (IOException e) {
throw new SAXException("IOException occured during processing: " + e, e);
}
} else {
if (ignoreEventsCount == 0) {
super.endElement(uri, name, raw);
}
}
}
/**
* Process the SAX event.
* @see org.xml.sax.ContentHandler#characters
*/
public void characters(char[] p0, int p1, int p2)
throws SAXException {
if (this.ignoreEventsCount == 0) {
if (this.ignoreEmptyCharacters) {
String value = new String(p0, p1, p2);
if (value.trim().length() > 0) {
super.characters(p0, p1, p2);
}
} else {
super.characters(p0, p1, p2);
}
}
}
/**
* Process the SAX event.
* @see org.xml.sax.ContentHandler#ignorableWhitespace
*/
public void ignorableWhitespace(char[] p0, int p1, int p2)
throws SAXException {
if (ignoreWhitespaces == false && ignoreEventsCount == 0) {
super.ignorableWhitespace(p0, p1, p2);
}
}
/**
* Process the SAX event.
* @see ContentHandler#processingInstruction
*/
public void processingInstruction(String target, String data)
throws SAXException {
if (this.ignoreEventsCount == 0) {
super.processingInstruction(target, data);
}
}
/**
* Process the SAX event.
* @see ContentHandler#skippedEntity
*/
public void skippedEntity(String name)
throws SAXException {
if (this.ignoreEventsCount == 0) {
super.skippedEntity(name);
}
}
//
// SAX LexicalHandler methods
//
/**
* @see LexicalHandler#startDTD
*/
public void startDTD(String name, String public_id, String system_id)
throws SAXException {
if (this.ignoreEventsCount == 0) {
super.startDTD(name, public_id, system_id);
}
}
/**
* @see LexicalHandler#endDTD
*/
public void endDTD() throws SAXException {
if (this.ignoreEventsCount == 0) {
super.endDTD();
}
}
/**
* @see LexicalHandler#startEntity
*/
public void startEntity (String name)
throws SAXException {
if (this.ignoreEventsCount == 0) {
super.startEntity(name);
}
}
/**
* @see LexicalHandler#endEntity
*/
public void endEntity (String name)
throws SAXException {
if (this.ignoreEventsCount == 0) {
super.endEntity(name);
}
}
/**
* @see LexicalHandler#startCDATA
*/
public void startCDATA() throws SAXException {
if (this.ignoreEventsCount == 0) {
super.startCDATA();
}
}
/**
* @see LexicalHandler#endCDATA
*/
public void endCDATA() throws SAXException {
if (this.ignoreEventsCount == 0) {
super.endCDATA();
}
}
/**
* @see LexicalHandler#comment
*/
public void comment(char ary[], int start, int length)
throws SAXException {
if (this.ignoreEventsCount == 0) {
super.comment(ary, start, length);
}
}
/*
* Recording of events.
* With this method all events are not forwarded to the next component in the pipeline.
* They are recorded to create a document fragment.
*/
private LexicalHandler originalLexicalHandler;
private ContentHandler originalContentHandler;
/**
* Add a new recorder to the recording chain.
* Do not invoke this method directly.
*/
protected void addRecorder(XMLConsumer recorder) {
if (this.recorderStack.empty()) {
// redirect if first (top) recorder
this.originalLexicalHandler = this.lexicalHandler;
this.originalContentHandler = this.contentHandler;
}
setContentHandler(recorder);
setLexicalHandler(recorder);
this.recorderStack.push(recorder);
}
/**
* Remove a recorder from the recording chain.
* Do not invoke this method directly.
*/
protected Object removeRecorder() {
Object recorder = this.recorderStack.pop();
if (this.recorderStack.empty() == true) {
// undo redirect if no recorder any more
setContentHandler(originalContentHandler);
setLexicalHandler(originalLexicalHandler);
this.originalLexicalHandler = null;
this.originalContentHandler = null;
} else {
XMLConsumer next = (XMLConsumer) recorderStack.peek();
setContentHandler(next);
setLexicalHandler(next);
}
return recorder;
}
/**
* Start recording of SAX events.
* All incoming events are recorded and not forwarded. The resulting
* XMLizable can be obtained by the matching {@link #endSAXRecording} call.
* @since 2.1.5
*/
public void startSAXRecording()
throws SAXException {
addRecorder(new SaxBuffer());
sendStartPrefixMapping();
}
/**
* Stop recording of SAX events.
* This method returns the resulting XMLizable.
* @since 2.1.5
*/
public XMLizable endSAXRecording()
throws SAXException {
sendEndPrefixMapping();
return (XMLizable) removeRecorder();
}
/**
* Start recording of a text.
* No events forwarded, and all characters events
* are collected into a string.
*/
public void startTextRecording()
throws SAXException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Start text recording");
}
addRecorder(new TextRecorder());
sendStartPrefixMapping();
}
/**
* Stop recording of text and return the recorded information.
* @return The String, trimmed.
*/
public String endTextRecording()
throws SAXException {
sendEndPrefixMapping();
TextRecorder recorder = (TextRecorder) removeRecorder();
String text = recorder.getText();
if (getLogger().isDebugEnabled()) {
getLogger().debug("End text recording. Text=" + text);
}
return text;
}
/**
* Start recording of serialized xml
* All events are converted to an xml string which can be retrieved by
* endSerializedXMLRecording.
* @param format The format for the serialized output. If <CODE>null</CODE>
* is specified, the default format is used.
*/
public void startSerializedXMLRecording(Properties format)
throws SAXException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Start serialized XML recording. Format=" + format);
}
this.stack.push(format == null? XMLUtils.createPropertiesForXML(false): format);
startSAXRecording();
}
/**
* Return the serialized xml string.
* @return A string containing the recorded xml information, formatted by
* the properties passed to the corresponding startSerializedXMLRecording().
*/
public String endSerializedXMLRecording()
throws SAXException, ProcessingException {
XMLizable xml = endSAXRecording();
String text = XMLUtils.serialize(xml, (Properties) this.stack.pop());
if (getLogger().isDebugEnabled()) {
getLogger().debug("End serialized XML recording. XML=" + text);
}
return text;
}
/**
* Start recording of parameters.
* All events are not forwarded and the incoming xml is converted to
* parameters. Each toplevel node is a parameter and its text subnodes
* form the value.
* The Parameters can eiter be retrieved by endParametersRecording().
*/
public void startParametersRecording()
throws SAXException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Start parameters recording");
}
addRecorder(new ParametersRecorder());
sendStartPrefixMapping();
}
/**
* End recording of parameters
* If source is null a new parameters object is created, otherwise
* the parameters are added to this object.
* @param source An optional parameters object.
* @return The object containing all parameters.
*/
public SourceParameters endParametersRecording(Parameters source)
throws SAXException {
sendEndPrefixMapping();
ParametersRecorder recorder = (ParametersRecorder) this.removeRecorder();
SourceParameters parameters = recorder.getParameters(source);
if (getLogger().isDebugEnabled()) {
getLogger().debug("End parameters recording. Parameters=" + parameters);
}
return parameters;
}
/**
* End recording of parameters
* If source is null a new parameters object is created, otherwise
* the parameters are added to this object.
* @param source An optional parameters object.
* @return The object containing all parameters.
*/
public SourceParameters endParametersRecording(SourceParameters source)
throws SAXException {
sendEndPrefixMapping();
ParametersRecorder recorder = (ParametersRecorder) removeRecorder();
SourceParameters parameters = recorder.getParameters(source);
if (getLogger().isDebugEnabled()) {
getLogger().debug("End parameters recording. Parameters=" + parameters);
}
return parameters;
}
/**
* Start DOM DocumentFragment recording.
* All incoming events are recorded and not forwarded. The resulting
* DocumentFragment can be obtained by the matching {@link #endRecording} call.
*/
public void startRecording()
throws SAXException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Start recording");
}
DOMBuilder builder = new DOMBuilder(this.tfactory);
addRecorder(builder);
builder.startDocument();
builder.startElement("", "cocoon", "cocoon", EMPTY_ATTRIBUTES);
sendStartPrefixMapping();
}
/**
* Stop DOM DocumentFragment recording.
* This method returns the resulting DocumentFragment, normalized.
*/
public DocumentFragment endRecording()
throws SAXException {
sendEndPrefixMapping();
DOMBuilder builder = (DOMBuilder) removeRecorder();
builder.endElement("", "cocoon", "cocoon");
builder.endDocument();
// Create Document Fragment
final Document doc = builder.getDocument();
final DocumentFragment fragment = doc.createDocumentFragment();
final Node root = doc.getDocumentElement();
// Remove empty text nodes and collapse neighbouring text nodes
root.normalize();
// Move all nodes into the fragment
boolean space = true;
while (root.hasChildNodes()) {
Node child = root.getFirstChild();
root.removeChild(child);
// Leave out leading whitespace nodes
// FIXME: Why leading spaces are trimmed at all? Why not trailing spaces?
if (space && child.getNodeType() == Node.TEXT_NODE
&& child.getNodeValue().trim().length() == 0) {
continue;
}
space = false;
fragment.appendChild(child);
}
if (getLogger().isDebugEnabled()) {
Object serializedXML = null;
try {
serializedXML = fragment == null? "null": XMLUtils.serializeNode(fragment, XMLUtils.createPropertiesForXML(false));
} catch (ProcessingException ignore) {
serializedXML = fragment;
}
getLogger().debug("End recording. Fragment=" + serializedXML);
}
return fragment;
}
//
// Hooks
//
/**
* Setup the transformation of an xml document.
* This method is called just before the transformation (sending of sax events)
* starts. It should be used to initialize setup parameter depending on the
* object modell.
*/
public void setupTransforming()
throws IOException, ProcessingException, SAXException {
if (getLogger().isDebugEnabled()) {
getLogger().debug("setupTransforming");
}
this.stack.clear();
this.recorderStack.clear();
this.ignoreWhitespaces = true;
this.ignoreEmptyCharacters = false;
}
/**
* Start processing elements of our namespace.
* This hook is invoked for each sax event with our namespace.
* @param uri The namespace of the element.
* @param name The local name of the element.
* @param raw The qualified name of the element.
* @param attr The attributes of the element.
*/
public void startTransformingElement(String uri,
String name,
String raw,
Attributes attr)
throws ProcessingException, IOException, SAXException {
if (this.ignoreEventsCount == 0) {
super.startElement(uri, name, raw, attr);
}
}
/**
* Start processing elements of our namespace.
* This hook is invoked for each sax event with our namespace.
* @param uri The namespace of the element.
* @param name The local name of the element.
* @param raw The qualified name of the element.
*/
public void endTransformingElement(String uri,
String name,
String raw)
throws ProcessingException, IOException, SAXException {
if (this.ignoreEventsCount == 0) {
super.endElement(uri, name, raw);
}
}
/**
* Send SAX events to the next pipeline component.
* The characters event for the given text is send to the next
* component in the current pipeline.
* @param text The string containing the information.
*/
public void sendTextEvent(String text)
throws SAXException {
characters(text.toCharArray(), 0, text.length());
}
/**
* Send SAX events to the next pipeline component.
* The startElement event for the given element is send
* to the next component in the current pipeline.
* The element has no namespace and no attributes
* @param localname The name of the event.
*/
public void sendStartElementEvent(String localname)
throws SAXException {
startElement("", localname, localname, EMPTY_ATTRIBUTES);
}
/**
* Send SAX events to the next pipeline component.
* The startElement event for the given element is send
* to the next component in the current pipeline.
* The element has the namespace of the transformer,
* but not attributes
* @param localname The name of the event.
*/
public void sendStartElementEventNS(String localname)
throws SAXException {
startElement(this.namespaceURI,
localname, this.ourPrefix + ':' + localname, EMPTY_ATTRIBUTES);
}
/**
* Send SAX events to the next pipeline component.
* The startElement event for the given element is send
* to the next component in the current pipeline.
* The element has no namespace.
* @param localname The name of the event.
* @param attr The Attributes of the element
*/
public void sendStartElementEvent(String localname, Attributes attr)
throws SAXException {
startElement("", localname, localname, attr);
}
/**
* Send SAX events to the next pipeline component.
* The startElement event for the given element is send
* to the next component in the current pipeline.
* The element has the namespace of the transformer.
* @param localname The name of the event.
* @param attr The Attributes of the element
*/
public void sendStartElementEventNS(String localname, Attributes attr)
throws SAXException {
startElement(this.namespaceURI,
localname, this.ourPrefix + ':' + localname, attr);
}
/**
* Send SAX events to the next pipeline component.
* The endElement event for the given element is send
* to the next component in the current pipeline.
* The element has no namespace.
* @param localname The name of the event.
*/
public void sendEndElementEvent(String localname)
throws SAXException {
endElement("", localname, localname);
}
/**
* Send SAX events to the next pipeline component.
* The endElement event for the given element is send
* to the next component in the current pipeline.
* The element has the namespace of the transformer.
* @param localname The name of the event.
*/
public void sendEndElementEventNS(String localname)
throws SAXException {
endElement(this.namespaceURI,
localname, this.ourPrefix + ':' + localname);
}
/**
* Send SAX events to the next pipeline component.
* The node is parsed and the events are send to
* the next component in the pipeline.
* @param node The tree to be included.
*/
public void sendEvents(Node node)
throws SAXException {
IncludeXMLConsumer.includeNode(node, this, this);
}
/**
* Send SAX events for the <code>SourceParameters</code>.
* For each parametername/value pair an element is
* created with the name of the parameter and the content
* of this element is the value.
*/
public void sendParametersEvents(SourceParameters pars)
throws SAXException {
if (pars != null) {
Iterator names = pars.getParameterNames();
while (names.hasNext()) {
final String currentName = (String)names.next();
Iterator values = pars.getParameterValues(currentName);
while (values.hasNext()) {
final String currentValue = (String)values.next();
sendStartElementEvent(currentName);
sendTextEvent(currentValue);
sendEndElementEvent(currentName);
}
}
}
}
/**
* Send all start prefix mapping events to the current content handler
*/
protected void sendStartPrefixMapping()
throws SAXException {
final int l = this.namespaces.size();
for (int i = 0; i < l; i++) {
String[] prefixAndUri = (String[]) this.namespaces.get(i);
super.contentHandler.startPrefixMapping(prefixAndUri[0], prefixAndUri[1]);
}
}
/**
* Send all end prefix mapping events to the current content handler
*/
protected void sendEndPrefixMapping()
throws SAXException {
final int l = this.namespaces.size();
for (int i = 0; i < l; i++) {
String[] prefixAndUri = (String[]) this.namespaces.get(i);
super.contentHandler.endPrefixMapping(prefixAndUri[0]);
}
}
/**
* Find prefix mapping for the given namespace URI.
* @return Prefix mapping or null if no prefix defined
*/
protected String findPrefixMapping(String uri) {
final int l = this.namespaces.size();
for (int i = 0; i < l; i++) {
String[] prefixAndUri = (String[]) this.namespaces.get(i);
if (prefixAndUri[1].equals(uri)) {
return prefixAndUri[0];
}
}
return null;
}
/**
* Helper method to get a modifiable attribute set.
*/
protected AttributesImpl getMutableAttributes(Attributes a) {
if ( a instanceof AttributesImpl && !(a instanceof ImmutableAttributesImpl)) {
return (AttributesImpl)a;
}
return new AttributesImpl(a);
}
}