All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javanet.staxutils.XMLStreamUtils Maven / Gradle / Ivy

There is a newer version: 4.0.4
Show newest version
/*
 * $Id: XMLStreamUtils.java,v 1.8 2004/07/09 17:30:50 cniles Exp $
 * 
 * Copyright (c) 2004, Christian Niles, Unit12
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *		*   Redistributions of source code must retain the above copyright
 *          notice, this list of conditions and the following disclaimer.
 * 
 *	    *	Redistributions in binary form must reproduce the above copyright
 *          notice, this list of conditions and the following disclaimer in the
 *          documentation and/or other materials provided with the distribution.
 * 
 *      *   Neither the name of Christian Niles, Unit12, nor the names of its
 *          contributors may be used to endorse or promote products derived from
 *          this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 */
package javanet.staxutils;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.XMLEventConsumer;
import javax.xml.transform.Result;
import javax.xml.transform.Source;

/**
 * Static utility methods useful when handling XML Streams.
 * 
 * @author Christian Niles
 * @version $Revision: 1.8 $
 */
public class XMLStreamUtils {

    private static XMLInputFactory inputFactory = XMLInputFactory.newInstance();

    private static XMLOutputFactory outputFactory = XMLOutputFactory.newInstance();

    private static final String[] EVENT_NAMES = new String[16];
    static {

        EVENT_NAMES[0] = ""; // no event has 0 index
        EVENT_NAMES[XMLStreamConstants.ATTRIBUTE] = "ATTRIBUTE";
        EVENT_NAMES[XMLStreamConstants.CDATA] = "CDATA";
        EVENT_NAMES[XMLStreamConstants.CHARACTERS] = "CHARACTERS";
        EVENT_NAMES[XMLStreamConstants.COMMENT] = "COMMENT";
        EVENT_NAMES[XMLStreamConstants.DTD] = "DTD";
        EVENT_NAMES[XMLStreamConstants.END_DOCUMENT] = "END_DOCUMENT";
        EVENT_NAMES[XMLStreamConstants.END_ELEMENT] = "END_ELEMENT";
        EVENT_NAMES[XMLStreamConstants.ENTITY_DECLARATION] = "ENTITY_DECLARATION";
        EVENT_NAMES[XMLStreamConstants.ENTITY_REFERENCE] = "ENTITY_REFERENCE";
        EVENT_NAMES[XMLStreamConstants.NAMESPACE] = "NAMESPACE";
        EVENT_NAMES[XMLStreamConstants.NOTATION_DECLARATION] = "NOTATION_DECLARATION";
        EVENT_NAMES[XMLStreamConstants.PROCESSING_INSTRUCTION] = "PROCESSING_INSTRUCTION";
        EVENT_NAMES[XMLStreamConstants.SPACE] = "SPACE";
        EVENT_NAMES[XMLStreamConstants.START_DOCUMENT] = "START_DOCUMENT";
        EVENT_NAMES[XMLStreamConstants.START_ELEMENT] = "START_ELEMENT";

    }

    /**
     * Returns the name of the specified stream event constant.
     * 
     * @param eventType The event constant, such as
     *     {@link XMLStreamConstants#START_DOCUMENT}.
     * @return The name of the specified event, or "UNKNOWN" if the
     *     constant isn't valid.
     */
    public static final String getEventTypeName(int eventType) {

        if (eventType > 0 || eventType < EVENT_NAMES.length) {

            return EVENT_NAMES[eventType];

        } else {

            return "UNKNOWN";

        }

    }

    /**
     * Returns the value of the attribute with the given non-qualified name.
     * 
     * @param reader The xml stream reader
     * @param name The name of the attribute.
     * @return The value of the unqualified attribute, or null
     *         if the attribute wasn't present.
     */
    public static final String attributeValue(XMLStreamReader reader,
            String name) {

        return reader.getAttributeValue("", name);

    }

    /**
     * Returns the value of the attribute with the given name.
     * 
     * @param reader The xml stream reader
     * @param name The name of the attribute.
     * @return The value of the attribute, or null if the
     *         attribute wasn't present.
     */
    public static final String attributeValue(XMLStreamReader reader, QName name) {

        return reader.getAttributeValue(name.getNamespaceURI(),
                name.getLocalPart());

    }

    /**
     * Skips all events within a single element, including its start and end
     * tags. The provided reader must be positioned directly in front of a
     * StartElement event or it will have no effect. After this
     * method completes, the reader will be positioned before the event
     * following the end tag (the end tag will have been read).
     * 
     * @param reader The event stream to read.
     * @throws XMLStreamException If an error occurs reading events.
     */
    public static final void skipElement(XMLEventReader reader)
            throws XMLStreamException {

        copyElement(reader, null);

    }

    /**
     * Copies an element and all its content from the provided event reader, to
     * the provided event consumer. The event reader must be positioned before a
     * start element event, or this method has no effect.
     * 
     * @param reader The reader from which to read the events.
     * @param consumer The destination for read events, or null to
     * 		ignore all events.
     * @throws XMLStreamException If an error occurs reading or writing the
     *             events.
     */
    public static final void copyElement(XMLEventReader reader,
            XMLEventConsumer consumer) throws XMLStreamException {

        if (!reader.hasNext())
            return;

        XMLEvent event = reader.peek();
        if (!event.isStartElement())
            return;

        int depth = 0;
        do {

            XMLEvent currEvt = reader.nextEvent();
            if (currEvt.isStartElement()) {

                depth++;

            } else if (currEvt.isEndElement()) {

                depth--;

            }

            if (consumer != null) {

                consumer.add(currEvt);

            }

        } while (depth > 0 && reader.hasNext());

    }

    /**
     * Skips all events within a StartElement until the matching
     * EndElement is reached. This method assumes that the reader
     * is positioned after the StartElement event, and when the
     * method completes, the stream will be positioned before the
     * EndElement event, but it will not consume the end tag.
     * 
     * @param reader The event stream to read, positioned after the
     * 		StartElement
     * @throws XMLStreamException If an error occurs reading events.
     */
    public static final void skipElementContent(XMLEventReader reader)
            throws XMLStreamException {

        copyElementContent(reader, null);

    }

    /**
     * Copies all events within a StartElement until the matching
     * EndElement is reached. This method assumes that the reader
     * is positioned after the StartElement event, and when the
     * method completes, the stream will be positioned before the 
     * EndElement event, but it will not consume the end tag.
     * 
     * @param reader The event stream to read, positioned after the
     * 		StartElement
     * @param consumer The destination for events read from teh stream, or
     * 		null to ignore the events completely.
     * @throws XMLStreamException If an error occurs reading events.
     */
    public static final void copyElementContent(XMLEventReader reader,
            XMLEventConsumer consumer) throws XMLStreamException {

        if (!reader.hasNext())
            return;

        for (int depth = 1; true;) {

            // peek and see if we're at the end element
            XMLEvent currEvt = reader.peek();
            if (currEvt.isEndElement()) {

                depth--;
                if (depth == 0) {

                    break;

                }

            } else if (currEvt.isStartElement()) {

                depth++;

            }

            // consume the event
            currEvt = reader.nextEvent();

            if (consumer != null) {

                consumer.add(currEvt);

            }

        }

    }

    /**
     * Skips the complete content of the element at the specified reader's
     * cursor. The reader's current event type must be START_ELEMENT, otherwise
     * this method will have no effect. Upon completion, the reader's cursor
     * will be at the END_ELEMENT event for the skipped element.
     * 
     * @param reader An XML stream reader currently in the START_ELEMENT event.
     */
    public static final void skipElement(XMLStreamReader reader)
            throws XMLStreamException {

        if (reader.isStartElement()) {

            skipElementContent(reader);

        }

    }

    /**
     * Skips an element's complete content. This method assumes that the
     * START_ELEMENT has already be passed, and when it terminates,
     * the stream will be positioned at the END_ELEMENT.
     * 
     * @param reader The stream reader to read.
     * @throws XMLStreamException If an error occurs reading the stream.
     */
    public static final void skipElementContent(XMLStreamReader reader)
            throws XMLStreamException {

        int depth = 0;
        while (depth >= 0) {

            reader.next();
            if (reader.isStartElement()) {

                depth++;

            } else if (reader.isEndElement()) {

                depth--;

            }

        }

    }

    /**
     * Static utility method that throws an exception if the supplied reader's
     * cursor doesn't point to a START_ELEMENT with the given name.
     * 
     * @param reader The reader to test.
     * @param name The name of the element to require.
     * @throws XMLStreamException If the reader state is an element with the
     *             specified name.
     */
    public static final void requireElement(XMLStreamReader reader, QName name)
            throws XMLStreamException {

        reader.require(XMLStreamReader.START_ELEMENT, name.getNamespaceURI(),
                name.getLocalPart());

    }

    /**
     * Copies the content read from the specified source stream to the provided
     * result stream. This method is exactly the same as calling
     * {@link XMLEventWriter#add(XMLEventReader)}, and is provided only for
     * completeness.
     * 
     * @param reader The source stream.
     * @param consumer The destination stream.
     * @throws XMLStreamException If an error occurs copying the stream
     *             contents.
     */
    public static final void copy(XMLEventReader reader,
            XMLEventConsumer consumer) throws XMLStreamException {

        if (consumer instanceof XMLEventWriter) {

            copy(reader, (XMLEventWriter) consumer);

        } else {

            while (reader.hasNext()) {

                consumer.add(reader.nextEvent());

            }

        }

    }

    /**
     * Copies the content read from the specified source stream to the provided
     * result stream. This method is exactly the same as calling
     * {@link XMLEventWriter#add(XMLEventReader)}, and is provided only for
     * completeness.
     * 
     * @param reader The source stream.
     * @param writer The destination stream.
     * @throws XMLStreamException If an error occurs copying the stream
     *             contents.
     */
    public static final void copy(XMLEventReader reader, XMLEventWriter writer)
            throws XMLStreamException {

        writer.add(reader);

    }

    /**
     * Copies the content read from the specified source stream to the provided
     * result stream.
     * 
     * @param reader The source stream.
     * @param writer The destination stream.
     * @throws XMLStreamException If an error occurs copying the stream
     *             contents.
     */
    public static final void copy(XMLStreamReader reader, XMLStreamWriter writer)
            throws XMLStreamException {

        XMLEventReader r = inputFactory.createXMLEventReader(reader);
        XMLEventWriter w = new XMLStreamEventWriter(writer);

        try {

            w.add(r);

        } finally {

            // force any cached events to the underlying writer
            w.flush();

        }

    }

    /**
     * Copies the content read from the specified source stream to the provided
     * result stream.
     * 
     * @param reader The source stream.
     * @param writer The destination stream.
     * @param factory An optional input factory used to create any intermediate
     *            streams.
     * @throws XMLStreamException If an error occurs copying the stream
     *             contents.
     */
    public static final void copy(XMLStreamReader reader,
            XMLStreamWriter writer, XMLInputFactory factory)
            throws XMLStreamException {

        if (factory == null) {

            factory = inputFactory;

        }

        XMLEventReader r = factory.createXMLEventReader(reader);
        XMLEventWriter w = new XMLStreamEventWriter(writer);

        try {

            w.add(r);

        } finally {

            // force any cached events to the underlying writer
            w.flush();

        }

    }

    /**
     * Copies the content read from a TrAX {@link Source} to a StAX
     * {@link XMLStreamWriter}.
     * 
     * @param source The content source.
     * @param writer The destination stream.
     * @throws XMLStreamException If an error occurs copying the content to the
     * 		stream.
     */
    public static final void copy(Source source, XMLStreamWriter writer)
            throws XMLStreamException {

        XMLStreamReader reader = inputFactory.createXMLStreamReader(source);
        copy(reader, writer);

    }

    /**
     * Copies the content read from a TrAX {@link Source} to a StAX
     * {@link XMLEventWriter}.
     * 
     * @param source The content source.
     * @param writer The destination event stream.
     * @throws XMLStreamException If an error occurs copying the content to the
     * 		event stream.
     */
    public static final void copy(Source source, XMLEventWriter writer)
            throws XMLStreamException {

        XMLEventReader reader = inputFactory.createXMLEventReader(source);
        copy(reader, writer);

    }

    /**
     * Copies the content read from a StAX {@link XMLEventReader} to a TrAX
     * {@link Result}.
     * 
     * @param reader The source event stream.
     * @param result The destination {@link Result}.
     * @throws XMLStreamException If an error occurs copying the content to the
     * 		result.
     */
    public static final void copy(XMLEventReader reader, Result result)
            throws XMLStreamException {

        XMLEventWriter writer = outputFactory.createXMLEventWriter(result);

        copy(reader, writer);

        // force any cached events to the result
        writer.flush();

    }

    /**
     * Copies the content read from a StAX {@link XMLStreamReader} to a TrAX
     * {@link Result}.
     * 
     * @param reader The source stream.
     * @param result The destination {@link Result}.
     * @throws XMLStreamException If an error occurs copying the content to the
     * 		result.
     */
    public static final void copy(XMLStreamReader reader, Result result)
            throws XMLStreamException {

        XMLStreamWriter writer = outputFactory.createXMLStreamWriter(result);

        copy(reader, writer);

        // force any cached content to the result
        writer.flush();

    }

    /**
     * Utility method that throws an exception if the provided reader is not
     * positioned before a StartElement event with the specified tag name.
     * 
     * @param reader The reader to test.
     * @param qname The required name of the start-tag. If null,
     *            any start tag is accepted.
     * @throws XMLStreamException If an error occurs reading from the stream.
     */
    public static final void requireStartElement(XMLEventReader reader,
            QName qname) throws XMLStreamException {

        if (reader.hasNext()) {

            XMLEvent nextEvent = reader.peek();
            if (nextEvent.isStartElement()) {

                if (qname != null) {

                    StartElement start = nextEvent.asStartElement();
                    QName name = start.getName();
                    if (!name.equals(qname)) {

                        throw new XMLStreamException(
                                "Encountered unexpected element; expected "
                                        + qname + ", but found " + name);

                    }

                }

            } else {

                throw new XMLStreamException(
                        "Encountered unexpected event; expected " + qname
                                + " start-tag, but found event " + nextEvent);

            }

        } else {

            throw new XMLStreamException(
                    "Encountered unexpected end of stream; expected element "
                            + qname);

        }

    }

    /**
     * Constructs a new StartElement that merges the attributes and namespaces
     * found in the specified StartElement, with the provided attributes. The
     * returned StartElement will contain all the attributes and namespaces of
     * the original, plus those defined in the map.
     * 
     * @param tag The original StartElement
     * @param attrs An iterator of Atributes to add to the element.
     * @return A new StartElement that contains all the original attributes and
     *         namespaces, plus the provided attributes.
     */
    public static StartElement mergeAttributes(StartElement tag,
            Iterator attrs, XMLEventFactory factory) {

        // create Attribute map
        Map attributes = new HashMap();

        // iterate through start tag's attributes
        for (Iterator i = tag.getAttributes(); i.hasNext();) {

            Attribute attr = (Attribute) i.next();
            attributes.put(attr.getName(), attr);

        }

        // iterate through new attributes
        while (attrs.hasNext()) {

            Attribute attr = (Attribute) attrs.next();
            attributes.put(attr.getName(), attr);

        }

        factory.setLocation(tag.getLocation());

        QName tagName = tag.getName();
        return factory.createStartElement(tagName.getPrefix(),
                tagName.getNamespaceURI(), tagName.getLocalPart(),
                attributes.values().iterator(), tag.getNamespaces(),
                tag.getNamespaceContext());

    }

    /**
     * Reads the text content of an element. The reader should be positioned in
     * front of a StartElement event, and will be read up to and including the
     * end element tag.
     * 
     * @param reader The event stream from which to read the element text.
     * @param elemName The optional name of the element being read. If this
     *            paramter is non- null then an exception will
     *            be thrown if the element read doesn't have the same name.
     * @return The text read from the element.
     * @throws XMLStreamException If an error occurs reading the stream, or if
     *             the read element doesn't match the provided QName.
     */
    public static final String readTextElement(XMLEventReader reader,
            QName elemName) throws XMLStreamException {

        if (elemName != null) {

            requireStartElement(reader, elemName);

        }

        // read text
        String text = reader.getElementText();

        // consume the end tag
        reader.nextEvent();

        return text;

    }

    /**
     * Advances the event stream until it encounters a start or end tag, but
     * does not actaully read the event.
     * 
     * @param reader The reader to peek.
     * @return The next StartElement or EndElement event, retrieved using
     *         peek(), or null if the end of the
     *         stream was encountered before any tag event.
     * @throws XMLStreamException If an error occurs reading the stream.
     */
    public static final XMLEvent nextTag(XMLEventReader reader)
            throws XMLStreamException {

        while (reader.hasNext()) {

            XMLEvent nextEvent = reader.peek();
            if (nextEvent.isStartElement() || nextEvent.isEndElement()) {

                return nextEvent;

            } else {

                // eat the event.
                reader.nextEvent();

            }

        }

        return null;

    }

    /**
     * Reads the events from the provided event stream until either a start or
     * end tag is encountered. In the former case, the start tag will be
     * returned, but if an end tag is encountered, null will be
     * returned. After returning, the stream will be positioned just before the
     * returned start element. The start element will not be consumed by this
     * method.
     * 
     * @param reader The event stream from which to read.
     * @return The StartElement read from the stream, or null if
     *         an end tag was found first, or the stream ended before a start
     *         element was found.
     * @throws XMLStreamException If an error occurs reading the stream.
     */
    public static final StartElement nextElement(XMLEventReader reader)
            throws XMLStreamException {

        return nextElement(reader, null);

    }

    /**
     * Reads the events from the provided event stream until either a start or
     * end tag is encountered. In the former case, the start tag will be
     * returned if it matches the specified QName, but if it doesn't match, an
     * end tag is encountered, or the stream ends, null will be
     * returned. After returning, the stream will be positioned just before the
     * start element. The start element will not be consumed by this method.
     * 
     * @param reader The event stream from which to read.
     * @param name The name of the element to read, or null to
     *            read any start tag.
     * @return The StartElement read from the stream, or null if
     *         the encountered start tag didn't match the specified QName, an
     *         end tag was found first, or the stream ended before a start
     *         element was found.
     * @throws XMLStreamException If an error occurs reading the stream.
     */
    public static final StartElement nextElement(XMLEventReader reader,
            QName name) throws XMLStreamException {

        while (reader.hasNext()) {

            XMLEvent nextEvent = reader.peek();
            if (nextEvent.isStartElement()) {

                StartElement start = nextEvent.asStartElement();
                if (name == null || start.getName().equals(name)) {

                    return start;

                } else {

                    break;

                }

            } else if (nextEvent.isEndElement()) {

                break;

            } else {

                // consume the event.
                reader.nextEvent();

            }

        }

        return null;

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy