| /* |
| * Copyright 1999-2005 The Apache Software Foundation. |
| * |
| * Licensed 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.excalibur.pool.Recyclable; |
| 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. |
| * |
| * @version $Id$ |
| */ |
| public abstract class AbstractSAXTransformer extends AbstractTransformer |
| implements Serviceable, Configurable, Recyclable, 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; |
| |
| /** |
| * The namespaces and their prefixes |
| */ |
| private final List namespaces = new ArrayList(5); |
| |
| /** |
| * The current prefix for our namespace |
| */ |
| private String ourPrefix; |
| |
| /** |
| * Remove namespace prefixes for our namespace? |
| * @since 2.2 |
| */ |
| protected boolean removeOurNamespacePrefixes = false; |
| |
| /** |
| * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager) |
| */ |
| public void service(ServiceManager manager) throws ServiceException { |
| this.manager = manager; |
| } |
| |
| /** |
| * @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())); |
| } |
| |
| /** |
| * @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; |
| } |
| |
| /** |
| * @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(); |
| } |
| |
| /** |
| * @see org.apache.avalon.framework.activity.Disposable#dispose() |
| */ |
| 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 { |
| boolean isOurPrefix = false; |
| if (prefix != null) { |
| this.namespaces.add(new String[] {prefix, uri}); |
| } |
| if (namespaceURI.equals(uri)) { |
| this.ourPrefix = prefix; |
| isOurPrefix = true; |
| } |
| if (this.ignoreEventsCount == 0) { |
| if ( !removeOurNamespacePrefixes || !isOurPrefix) { |
| super.startPrefixMapping(prefix, uri); |
| } |
| } |
| } |
| |
| /** |
| * Process the SAX event. |
| * @see org.xml.sax.ContentHandler#endPrefixMapping |
| */ |
| public void endPrefixMapping(String prefix) |
| throws SAXException { |
| boolean isOurPrefix = false; |
| 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)) { |
| isOurPrefix = true; |
| // 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) { |
| if ( !removeOurNamespacePrefixes || !isOurPrefix) { |
| 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); |
| } 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); |
| } |
| } |