Package org.apache.cocoon.transformation

Source Code of org.apache.cocoon.transformation.AbstractSAXTransformer

/*
* 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: AbstractSAXTransformer.java 433543 2006-08-22 06:22:54Z crossley $
*/
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);
    }
}
TOP

Related Classes of org.apache.cocoon.transformation.AbstractSAXTransformer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.