org.openqa.jetty.xml.XmlParser.java Source code

Java tutorial

Introduction

Here is the source code for org.openqa.jetty.xml.XmlParser.java

Source

// ========================================================================
// $Id: XmlParser.java,v 1.21 2005/10/25 07:53:22 gregwilkins Exp $
// Copyright 1999-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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.openqa.jetty.xml;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.commons.logging.Log;
import org.openqa.jetty.log.LogFactory;
import org.openqa.jetty.util.LazyList;
import org.openqa.jetty.util.LogSupport;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/*--------------------------------------------------------------*/
/**
 * XML Parser wrapper. This class wraps any standard JAXP1.1 parser with convieniant error and
 * entity handlers and a mini dom-like document tree.
 * <P>
 * By default, the parser is created as a validating parser. This can be changed by setting the
 * "org.openqa.jetty.xml.XmlParser.NotValidating" system property to true.
 * 
 * @version $Id: XmlParser.java,v 1.21 2005/10/25 07:53:22 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */
public class XmlParser {
    private static Log log = LogFactory.getLog(XmlParser.class);
    private Map _redirectMap = new HashMap();
    private SAXParser _parser;
    private String _xpath;
    private Object _xpaths;

    /* ------------------------------------------------------------ */
    /**
     * Construct
     */
    public XmlParser() {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            boolean notValidating = Boolean.getBoolean("org.openqa.jetty.xml.XmlParser.NotValidating");
            factory.setValidating(!notValidating);
            _parser = factory.newSAXParser();
            try {
                if (!notValidating)
                    _parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/schema", true);
            } catch (Exception e) {
                log.warn("Schema validation may not be supported");
                log.debug("", e);
                notValidating = true;
            }
            _parser.getXMLReader().setFeature("http://xml.org/sax/features/validation", !notValidating);
            _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", !notValidating);
            _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", !notValidating);
        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new Error(e.toString());
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Constructor.
     */
    public XmlParser(boolean validating) {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setValidating(validating);
            _parser = factory.newSAXParser();
            try {
                if (validating)
                    _parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/schema",
                            validating);
            } catch (Exception e) {
                if (validating)
                    log.warn("Schema validation may not be supported: ", e);
                else
                    LogSupport.ignore(log, e);
            }
            _parser.getXMLReader().setFeature("http://xml.org/sax/features/validation", validating);
            _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", validating);
            _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", validating);
        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new Error(e.toString());
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * @param name
     * @param entity
     */
    public synchronized void redirectEntity(String name, URL entity) {
        if (entity != null)
            _redirectMap.put(name, entity);
    }

    /* ------------------------------------------------------------ */
    /** 
     *
     * @return Returns the xpath.
     */
    public String getXpath() {
        return _xpath;
    }

    /* ------------------------------------------------------------ */
    /** Set an XPath
     * A very simple subset of xpath is supported to select a partial
     * tree.  Currently only path like "/node1/nodeA | /node1/nodeB" 
     * are supported.
     * @param xpath The xpath to set.
     */
    public void setXpath(String xpath) {
        _xpath = xpath;
        StringTokenizer tok = new StringTokenizer(xpath, "| ");
        while (tok.hasMoreTokens())
            _xpaths = LazyList.add(_xpaths, tok.nextToken());
    }

    /* ------------------------------------------------------------ */
    public synchronized Node parse(InputSource source) throws IOException, SAXException {
        Handler handler = new Handler();
        XMLReader reader = _parser.getXMLReader();
        reader.setContentHandler(handler);
        reader.setErrorHandler(handler);
        reader.setEntityResolver(handler);
        if (log.isDebugEnabled())
            log.debug("parsing: sid=" + source.getSystemId() + ",pid=" + source.getPublicId());
        _parser.parse(source, handler);
        if (handler._error != null)
            throw handler._error;
        Node doc = (Node) handler._top.get(0);
        handler.clear();
        return doc;
    }

    /* ------------------------------------------------------------ */
    /**
     * Parse string URL.
     */
    public synchronized Node parse(String url) throws IOException, SAXException {
        if (log.isDebugEnabled())
            log.debug("parse: " + url);
        return parse(new InputSource(url));
    }

    /* ------------------------------------------------------------ */
    /**
     * Parse File.
     */
    public synchronized Node parse(File file) throws IOException, SAXException {
        if (log.isDebugEnabled())
            log.debug("parse: " + file);
        return parse(new InputSource(file.toURL().toString()));
    }

    /* ------------------------------------------------------------ */
    /**
     * Parse InputStream.
     */
    public synchronized Node parse(InputStream in) throws IOException, SAXException {
        Handler handler = new Handler();
        XMLReader reader = _parser.getXMLReader();
        reader.setContentHandler(handler);
        reader.setErrorHandler(handler);
        reader.setEntityResolver(handler);
        _parser.parse(new InputSource(in), handler);
        if (handler._error != null)
            throw handler._error;
        Node doc = (Node) handler._top.get(0);
        handler.clear();
        return doc;
    }

    /* ------------------------------------------------------------ */
    /**
     * Parse URL.
     */
    public synchronized Node parse(URL url) throws IOException, SAXException {
        Node n = null;
        InputStream is = url.openStream();
        try {
            n = parse(is);
        } finally {
            try {
                is.close();
            } catch (Exception e) {
                // xerces closes streams you give it to parse, so this close() will throw an
                // exception.
                // This behavior is stupid, so we should not assume it.
            }
        }
        return n;
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private class NoopHandler extends DefaultHandler {
        Handler _next;
        int _depth;

        NoopHandler(Handler next) {
            this._next = next;
        }

        /* ------------------------------------------------------------ */
        public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
            _depth++;
        }

        /* ------------------------------------------------------------ */
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (_depth == 0)
                _parser.getXMLReader().setContentHandler(_next);
            else
                _depth--;
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    private class Handler extends DefaultHandler {
        Node _top = new Node(null, null, null);
        SAXParseException _error;
        private Node _context = _top;
        private NoopHandler _noop;

        Handler() {
            _noop = new NoopHandler(this);
        }

        /* ------------------------------------------------------------ */
        void clear() {
            _top = null;
            _error = null;
            _context = null;
        }

        /* ------------------------------------------------------------ */
        public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
            String name = (uri == null || uri.equals("")) ? qName : localName;
            Node node = new Node(_context, name, attrs);

            // check if the node matches any xpaths set?
            if (_xpaths != null) {
                String path = node.getPath();
                boolean match = false;
                for (int i = LazyList.size(_xpaths); !match && i-- > 0;) {
                    String xpath = (String) LazyList.get(_xpaths, i);

                    match = path.equals(xpath) || xpath.startsWith(path) && xpath.length() > path.length()
                            && xpath.charAt(path.length()) == '/';
                }

                if (match) {
                    _context.add(node);
                    _context = node;
                } else {
                    _parser.getXMLReader().setContentHandler(_noop);
                }
            } else {
                _context.add(node);
                _context = node;
            }
        }

        /* ------------------------------------------------------------ */
        public void endElement(String uri, String localName, String qName) throws SAXException {
            _context = _context._parent;
        }

        /* ------------------------------------------------------------ */
        public void ignorableWhitespace(char buf[], int offset, int len) throws SAXException {
        }

        /* ------------------------------------------------------------ */
        public void characters(char buf[], int offset, int len) throws SAXException {
            _context.add(new String(buf, offset, len));
        }

        /* ------------------------------------------------------------ */
        public void warning(SAXParseException ex) {
            log.debug(LogSupport.EXCEPTION, ex);
            log.warn("WARNING@" + getLocationString(ex) + " : " + ex.toString());
        }

        /* ------------------------------------------------------------ */
        public void error(SAXParseException ex) throws SAXException {
            // Save error and continue to report other errors
            if (_error == null)
                _error = ex;
            log.debug(LogSupport.EXCEPTION, ex);
            log.warn("ERROR@" + getLocationString(ex) + " : " + ex.toString());
        }

        /* ------------------------------------------------------------ */
        public void fatalError(SAXParseException ex) throws SAXException {
            _error = ex;
            log.debug(LogSupport.EXCEPTION, ex);
            log.warn("FATAL@" + getLocationString(ex) + " : " + ex.toString());
            throw ex;
        }

        /* ------------------------------------------------------------ */
        private String getLocationString(SAXParseException ex) {
            return ex.getSystemId() + " line:" + ex.getLineNumber() + " col:" + ex.getColumnNumber();
        }

        /* ------------------------------------------------------------ */
        public InputSource resolveEntity(String pid, String sid) {
            if (log.isDebugEnabled())
                log.debug("resolveEntity(" + pid + ", " + sid + ")");
            URL entity = null;
            if (pid != null)
                entity = (URL) _redirectMap.get(pid);
            if (entity == null)
                entity = (URL) _redirectMap.get(sid);
            if (entity == null) {
                String dtd = sid;
                if (dtd.lastIndexOf('/') >= 0)
                    dtd = dtd.substring(dtd.lastIndexOf('/') + 1);
                if (log.isDebugEnabled())
                    log.debug("Can't exact match entity in redirect map, trying " + dtd);
                entity = (URL) _redirectMap.get(dtd);
            }
            if (entity != null) {
                try {
                    InputStream in = entity.openStream();
                    if (log.isDebugEnabled())
                        log.debug("Redirected entity " + sid + " --> " + entity);
                    InputSource is = new InputSource(in);
                    is.setSystemId(sid);
                    return is;
                } catch (IOException e) {
                    LogSupport.ignore(log, e);
                }
            }
            return null;
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * XML Attribute.
     */
    public static class Attribute {
        private String _name;
        private String _value;

        Attribute(String n, String v) {
            _name = n;
            _value = v;
        }

        public String getName() {
            return _name;
        }

        public String getValue() {
            return _value;
        }
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /**
     * XML Node. Represents an XML element with optional attributes and ordered content.
     */
    public static class Node extends AbstractList {
        Node _parent;
        private ArrayList _list;
        private String _tag;
        private Attribute[] _attrs;
        private boolean _lastString = false;
        private String _path;

        /* ------------------------------------------------------------ */
        Node(Node parent, String tag, Attributes attrs) {
            _parent = parent;
            _tag = tag;
            if (attrs != null) {
                _attrs = new Attribute[attrs.getLength()];
                for (int i = 0; i < attrs.getLength(); i++) {
                    String name = attrs.getLocalName(i);
                    if (name == null || name.equals(""))
                        name = attrs.getQName(i);
                    _attrs[i] = new Attribute(name, attrs.getValue(i));
                }
            }
        }

        /* ------------------------------------------------------------ */
        public Node getParent() {
            return _parent;
        }

        /* ------------------------------------------------------------ */
        public String getTag() {
            return _tag;
        }

        /* ------------------------------------------------------------ */
        public String getPath() {
            if (_path == null) {
                if (getParent() != null && getParent().getTag() != null)
                    _path = getParent().getPath() + "/" + _tag;
                else
                    _path = "/" + _tag;
            }
            return _path;
        }

        /* ------------------------------------------------------------ */
        /**
         * Get an array of element attributes.
         */
        public Attribute[] getAttributes() {
            return _attrs;
        }

        /* ------------------------------------------------------------ */
        /**
         * Get an element attribute.
         * 
         * @return attribute or null.
         */
        public String getAttribute(String name) {
            return getAttribute(name, null);
        }

        /* ------------------------------------------------------------ */
        /**
         * Get an element attribute.
         * 
         * @return attribute or null.
         */
        public String getAttribute(String name, String dft) {
            if (_attrs == null || name == null)
                return dft;
            for (int i = 0; i < _attrs.length; i++)
                if (name.equals(_attrs[i].getName()))
                    return _attrs[i].getValue();
            return dft;
        }

        /* ------------------------------------------------------------ */
        /**
         * Get the number of children nodes.
         */
        public int size() {
            if (_list != null)
                return _list.size();
            return 0;
        }

        /* ------------------------------------------------------------ */
        /**
         * Get the ith child node or content.
         * 
         * @return Node or String.
         */
        public Object get(int i) {
            if (_list != null)
                return _list.get(i);
            return null;
        }

        /* ------------------------------------------------------------ */
        /**
         * Get the first child node with the tag.
         * 
         * @param tag
         * @return Node or null.
         */
        public Node get(String tag) {
            if (_list != null) {
                for (int i = 0; i < _list.size(); i++) {
                    Object o = _list.get(i);
                    if (o instanceof Node) {
                        Node n = (Node) o;
                        if (tag.equals(n._tag))
                            return n;
                    }
                }
            }
            return null;
        }

        /* ------------------------------------------------------------ */
        public void add(int i, Object o) {
            if (_list == null)
                _list = new ArrayList();
            if (o instanceof String) {
                if (_lastString) {
                    int last = _list.size() - 1;
                    _list.set(last, (String) _list.get(last) + o);
                } else
                    _list.add(i, o);
                _lastString = true;
            } else {
                _lastString = false;
                _list.add(i, o);
            }
        }

        /* ------------------------------------------------------------ */
        public void clear() {
            if (_list != null)
                _list.clear();
            _list = null;
        }

        /* ------------------------------------------------------------ */
        /**
         * Get a tag as a string.
         * 
         * @param tag The tag to get
         * @param tags IF true, tags are included in the value.
         * @param trim If true, trim the value.
         * @return results of get(tag).toString(tags).
         */
        public String getString(String tag, boolean tags, boolean trim) {
            Node node = get(tag);
            if (node == null)
                return null;
            String s = node.toString(tags);
            if (s != null && trim)
                s = s.trim();
            return s;
        }

        /* ------------------------------------------------------------ */
        public synchronized String toString() {
            return toString(true);
        }

        /* ------------------------------------------------------------ */
        /**
         * Convert to a string.
         * 
         * @param tag If false, only content is shown.
         */
        public synchronized String toString(boolean tag) {
            StringBuffer buf = new StringBuffer();
            synchronized (buf) {
                toString(buf, tag);
                return buf.toString();
            }
        }

        /* ------------------------------------------------------------ */
        /**
         * Convert to a string.
         * 
         * @param tag If false, only content is shown.
         */
        public synchronized String toString(boolean tag, boolean trim) {
            String s = toString(tag);
            if (s != null && trim)
                s = s.trim();
            return s;
        }

        /* ------------------------------------------------------------ */
        private synchronized void toString(StringBuffer buf, boolean tag) {
            if (tag) {
                buf.append("<");
                buf.append(_tag);
                if (_attrs != null) {
                    for (int i = 0; i < _attrs.length; i++) {
                        buf.append(' ');
                        buf.append(_attrs[i].getName());
                        buf.append("=\"");
                        buf.append(_attrs[i].getValue());
                        buf.append("\"");
                    }
                }
            }
            if (_list != null) {
                if (tag)
                    buf.append(">");
                for (int i = 0; i < _list.size(); i++) {
                    Object o = _list.get(i);
                    if (o == null)
                        continue;
                    if (o instanceof Node)
                        ((Node) o).toString(buf, tag);
                    else
                        buf.append(o.toString());
                }
                if (tag) {
                    buf.append("</");
                    buf.append(_tag);
                    buf.append(">");
                }
            } else if (tag)
                buf.append("/>");
        }

        /* ------------------------------------------------------------ */
        /**
         * Iterator over named child nodes.
         * 
         * @param tag The tag of the nodes.
         * @return Iterator over all child nodes with the specified tag.
         */
        public Iterator iterator(final String tag) {
            return new Iterator() {
                int c = 0;
                Node _node;

                /* -------------------------------------------------- */
                public boolean hasNext() {
                    if (_node != null)
                        return true;
                    while (_list != null && c < _list.size()) {
                        Object o = _list.get(c);
                        if (o instanceof Node) {
                            Node n = (Node) o;
                            if (tag.equals(n._tag)) {
                                _node = n;
                                return true;
                            }
                        }
                        c++;
                    }
                    return false;
                }

                /* -------------------------------------------------- */
                public Object next() {
                    try {
                        if (hasNext())
                            return _node;
                        throw new NoSuchElementException();
                    } finally {
                        _node = null;
                        c++;
                    }
                }

                /* -------------------------------------------------- */
                public void remove() {
                    throw new UnsupportedOperationException("Not supported");
                }
            };
        }
    }
}