| // ======================================================================== |
| // $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"); |
| } |
| }; |
| } |
| } |
| } |