103928aee4356845252ac6b662d5c72c29903813eJake Slack// 203928aee4356845252ac6b662d5c72c29903813eJake Slack// ======================================================================== 303928aee4356845252ac6b662d5c72c29903813eJake Slack// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. 403928aee4356845252ac6b662d5c72c29903813eJake Slack// ------------------------------------------------------------------------ 503928aee4356845252ac6b662d5c72c29903813eJake Slack// All rights reserved. This program and the accompanying materials 603928aee4356845252ac6b662d5c72c29903813eJake Slack// are made available under the terms of the Eclipse Public License v1.0 703928aee4356845252ac6b662d5c72c29903813eJake Slack// and Apache License v2.0 which accompanies this distribution. 803928aee4356845252ac6b662d5c72c29903813eJake Slack// 903928aee4356845252ac6b662d5c72c29903813eJake Slack// The Eclipse Public License is available at 1003928aee4356845252ac6b662d5c72c29903813eJake Slack// http://www.eclipse.org/legal/epl-v10.html 1103928aee4356845252ac6b662d5c72c29903813eJake Slack// 1203928aee4356845252ac6b662d5c72c29903813eJake Slack// The Apache License v2.0 is available at 1303928aee4356845252ac6b662d5c72c29903813eJake Slack// http://www.opensource.org/licenses/apache2.0.php 1403928aee4356845252ac6b662d5c72c29903813eJake Slack// 1503928aee4356845252ac6b662d5c72c29903813eJake Slack// You may elect to redistribute this code under either of these licenses. 1603928aee4356845252ac6b662d5c72c29903813eJake Slack// ======================================================================== 1703928aee4356845252ac6b662d5c72c29903813eJake Slack// 1803928aee4356845252ac6b662d5c72c29903813eJake Slack 1903928aee4356845252ac6b662d5c72c29903813eJake Slackpackage org.eclipse.jetty.xml; 2003928aee4356845252ac6b662d5c72c29903813eJake Slack 2103928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.io.File; 2203928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.io.IOException; 2303928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.io.InputStream; 2403928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.net.URL; 2503928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.util.AbstractList; 2603928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.util.ArrayList; 2703928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.util.HashMap; 2803928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.util.Iterator; 2903928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.util.Map; 3003928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.util.NoSuchElementException; 3103928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.util.Stack; 3203928aee4356845252ac6b662d5c72c29903813eJake Slackimport java.util.StringTokenizer; 3303928aee4356845252ac6b662d5c72c29903813eJake Slack 3403928aee4356845252ac6b662d5c72c29903813eJake Slackimport javax.xml.parsers.SAXParser; 3503928aee4356845252ac6b662d5c72c29903813eJake Slackimport javax.xml.parsers.SAXParserFactory; 3603928aee4356845252ac6b662d5c72c29903813eJake Slack 3703928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.eclipse.jetty.util.LazyList; 3803928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.eclipse.jetty.util.log.Log; 3903928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.eclipse.jetty.util.log.Logger; 4003928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.eclipse.jetty.util.resource.Resource; 4103928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.xml.sax.Attributes; 4203928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.xml.sax.ContentHandler; 4303928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.xml.sax.InputSource; 4403928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.xml.sax.SAXException; 4503928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.xml.sax.SAXParseException; 4603928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.xml.sax.XMLReader; 4703928aee4356845252ac6b662d5c72c29903813eJake Slackimport org.xml.sax.helpers.DefaultHandler; 4803928aee4356845252ac6b662d5c72c29903813eJake Slack 4903928aee4356845252ac6b662d5c72c29903813eJake Slack/*--------------------------------------------------------------*/ 5003928aee4356845252ac6b662d5c72c29903813eJake Slack/** 5103928aee4356845252ac6b662d5c72c29903813eJake Slack * XML Parser wrapper. This class wraps any standard JAXP1.1 parser with convieniant error and 5203928aee4356845252ac6b662d5c72c29903813eJake Slack * entity handlers and a mini dom-like document tree. 5303928aee4356845252ac6b662d5c72c29903813eJake Slack * <P> 5403928aee4356845252ac6b662d5c72c29903813eJake Slack * By default, the parser is created as a validating parser only if xerces is present. This can be 5503928aee4356845252ac6b662d5c72c29903813eJake Slack * configured by setting the "org.eclipse.jetty.xml.XmlParser.Validating" system property. 5603928aee4356845252ac6b662d5c72c29903813eJake Slack * 5703928aee4356845252ac6b662d5c72c29903813eJake Slack * 5803928aee4356845252ac6b662d5c72c29903813eJake Slack */ 5903928aee4356845252ac6b662d5c72c29903813eJake Slackpublic class XmlParser 6003928aee4356845252ac6b662d5c72c29903813eJake Slack{ 6103928aee4356845252ac6b662d5c72c29903813eJake Slack private static final Logger LOG = Log.getLogger(XmlParser.class); 6203928aee4356845252ac6b662d5c72c29903813eJake Slack 6303928aee4356845252ac6b662d5c72c29903813eJake Slack private Map<String,URL> _redirectMap = new HashMap<String,URL>(); 6403928aee4356845252ac6b662d5c72c29903813eJake Slack private SAXParser _parser; 6503928aee4356845252ac6b662d5c72c29903813eJake Slack private Map<String,ContentHandler> _observerMap; 6603928aee4356845252ac6b662d5c72c29903813eJake Slack private Stack<ContentHandler> _observers = new Stack<ContentHandler>(); 6703928aee4356845252ac6b662d5c72c29903813eJake Slack private String _xpath; 6803928aee4356845252ac6b662d5c72c29903813eJake Slack private Object _xpaths; 6903928aee4356845252ac6b662d5c72c29903813eJake Slack private String _dtd; 7003928aee4356845252ac6b662d5c72c29903813eJake Slack 7103928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 7203928aee4356845252ac6b662d5c72c29903813eJake Slack /** 7303928aee4356845252ac6b662d5c72c29903813eJake Slack * Construct 7403928aee4356845252ac6b662d5c72c29903813eJake Slack */ 7503928aee4356845252ac6b662d5c72c29903813eJake Slack public XmlParser() 7603928aee4356845252ac6b662d5c72c29903813eJake Slack { 7703928aee4356845252ac6b662d5c72c29903813eJake Slack SAXParserFactory factory = SAXParserFactory.newInstance(); 7803928aee4356845252ac6b662d5c72c29903813eJake Slack boolean validating_dft = factory.getClass().toString().startsWith("org.apache.xerces."); 7903928aee4356845252ac6b662d5c72c29903813eJake Slack String validating_prop = System.getProperty("org.eclipse.jetty.xml.XmlParser.Validating", validating_dft ? "true" : "false"); 8003928aee4356845252ac6b662d5c72c29903813eJake Slack boolean validating = Boolean.valueOf(validating_prop).booleanValue(); 8103928aee4356845252ac6b662d5c72c29903813eJake Slack setValidating(validating); 8203928aee4356845252ac6b662d5c72c29903813eJake Slack } 8303928aee4356845252ac6b662d5c72c29903813eJake Slack 8403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 8503928aee4356845252ac6b662d5c72c29903813eJake Slack /** 8603928aee4356845252ac6b662d5c72c29903813eJake Slack * Constructor. 8703928aee4356845252ac6b662d5c72c29903813eJake Slack */ 8803928aee4356845252ac6b662d5c72c29903813eJake Slack public XmlParser(boolean validating) 8903928aee4356845252ac6b662d5c72c29903813eJake Slack { 9003928aee4356845252ac6b662d5c72c29903813eJake Slack setValidating(validating); 9103928aee4356845252ac6b662d5c72c29903813eJake Slack } 9203928aee4356845252ac6b662d5c72c29903813eJake Slack 9303928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 9403928aee4356845252ac6b662d5c72c29903813eJake Slack public void setValidating(boolean validating) 9503928aee4356845252ac6b662d5c72c29903813eJake Slack { 9603928aee4356845252ac6b662d5c72c29903813eJake Slack try 9703928aee4356845252ac6b662d5c72c29903813eJake Slack { 9803928aee4356845252ac6b662d5c72c29903813eJake Slack SAXParserFactory factory = SAXParserFactory.newInstance(); 9903928aee4356845252ac6b662d5c72c29903813eJake Slack factory.setValidating(validating); 10003928aee4356845252ac6b662d5c72c29903813eJake Slack _parser = factory.newSAXParser(); 10103928aee4356845252ac6b662d5c72c29903813eJake Slack 10203928aee4356845252ac6b662d5c72c29903813eJake Slack try 10303928aee4356845252ac6b662d5c72c29903813eJake Slack { 10403928aee4356845252ac6b662d5c72c29903813eJake Slack if (validating) 10503928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/schema", validating); 10603928aee4356845252ac6b662d5c72c29903813eJake Slack } 10703928aee4356845252ac6b662d5c72c29903813eJake Slack catch (Exception e) 10803928aee4356845252ac6b662d5c72c29903813eJake Slack { 10903928aee4356845252ac6b662d5c72c29903813eJake Slack if (validating) 11003928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.warn("Schema validation may not be supported: ", e); 11103928aee4356845252ac6b662d5c72c29903813eJake Slack else 11203928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.ignore(e); 11303928aee4356845252ac6b662d5c72c29903813eJake Slack } 11403928aee4356845252ac6b662d5c72c29903813eJake Slack 11503928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.getXMLReader().setFeature("http://xml.org/sax/features/validation", validating); 11603928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", true); 11703928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", false); 11803928aee4356845252ac6b662d5c72c29903813eJake Slack try 11903928aee4356845252ac6b662d5c72c29903813eJake Slack { 12003928aee4356845252ac6b662d5c72c29903813eJake Slack if (validating) 12103928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.getXMLReader().setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", validating); 12203928aee4356845252ac6b662d5c72c29903813eJake Slack } 12303928aee4356845252ac6b662d5c72c29903813eJake Slack catch (Exception e) 12403928aee4356845252ac6b662d5c72c29903813eJake Slack { 12503928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.warn(e.getMessage()); 12603928aee4356845252ac6b662d5c72c29903813eJake Slack } 12703928aee4356845252ac6b662d5c72c29903813eJake Slack } 12803928aee4356845252ac6b662d5c72c29903813eJake Slack catch (Exception e) 12903928aee4356845252ac6b662d5c72c29903813eJake Slack { 13003928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.warn(Log.EXCEPTION, e); 13103928aee4356845252ac6b662d5c72c29903813eJake Slack throw new Error(e.toString()); 13203928aee4356845252ac6b662d5c72c29903813eJake Slack } 13303928aee4356845252ac6b662d5c72c29903813eJake Slack } 13403928aee4356845252ac6b662d5c72c29903813eJake Slack 13503928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 13603928aee4356845252ac6b662d5c72c29903813eJake Slack /** 13703928aee4356845252ac6b662d5c72c29903813eJake Slack * @param name 13803928aee4356845252ac6b662d5c72c29903813eJake Slack * @param entity 13903928aee4356845252ac6b662d5c72c29903813eJake Slack */ 14003928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized void redirectEntity(String name, URL entity) 14103928aee4356845252ac6b662d5c72c29903813eJake Slack { 14203928aee4356845252ac6b662d5c72c29903813eJake Slack if (entity != null) 14303928aee4356845252ac6b662d5c72c29903813eJake Slack _redirectMap.put(name, entity); 14403928aee4356845252ac6b662d5c72c29903813eJake Slack } 14503928aee4356845252ac6b662d5c72c29903813eJake Slack 14603928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 14703928aee4356845252ac6b662d5c72c29903813eJake Slack /** 14803928aee4356845252ac6b662d5c72c29903813eJake Slack * 14903928aee4356845252ac6b662d5c72c29903813eJake Slack * @return Returns the xpath. 15003928aee4356845252ac6b662d5c72c29903813eJake Slack */ 15103928aee4356845252ac6b662d5c72c29903813eJake Slack public String getXpath() 15203928aee4356845252ac6b662d5c72c29903813eJake Slack { 15303928aee4356845252ac6b662d5c72c29903813eJake Slack return _xpath; 15403928aee4356845252ac6b662d5c72c29903813eJake Slack } 15503928aee4356845252ac6b662d5c72c29903813eJake Slack 15603928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 15703928aee4356845252ac6b662d5c72c29903813eJake Slack /** 15803928aee4356845252ac6b662d5c72c29903813eJake Slack * Set an XPath A very simple subset of xpath is supported to select a partial tree. Currently 15903928aee4356845252ac6b662d5c72c29903813eJake Slack * only path like "/node1/nodeA | /node1/nodeB" are supported. 16003928aee4356845252ac6b662d5c72c29903813eJake Slack * 16103928aee4356845252ac6b662d5c72c29903813eJake Slack * @param xpath The xpath to set. 16203928aee4356845252ac6b662d5c72c29903813eJake Slack */ 16303928aee4356845252ac6b662d5c72c29903813eJake Slack public void setXpath(String xpath) 16403928aee4356845252ac6b662d5c72c29903813eJake Slack { 16503928aee4356845252ac6b662d5c72c29903813eJake Slack _xpath = xpath; 16603928aee4356845252ac6b662d5c72c29903813eJake Slack StringTokenizer tok = new StringTokenizer(xpath, "| "); 16703928aee4356845252ac6b662d5c72c29903813eJake Slack while (tok.hasMoreTokens()) 16803928aee4356845252ac6b662d5c72c29903813eJake Slack _xpaths = LazyList.add(_xpaths, tok.nextToken()); 16903928aee4356845252ac6b662d5c72c29903813eJake Slack } 17003928aee4356845252ac6b662d5c72c29903813eJake Slack 17103928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 17203928aee4356845252ac6b662d5c72c29903813eJake Slack public String getDTD() 17303928aee4356845252ac6b662d5c72c29903813eJake Slack { 17403928aee4356845252ac6b662d5c72c29903813eJake Slack return _dtd; 17503928aee4356845252ac6b662d5c72c29903813eJake Slack } 17603928aee4356845252ac6b662d5c72c29903813eJake Slack 17703928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 17803928aee4356845252ac6b662d5c72c29903813eJake Slack /** 17903928aee4356845252ac6b662d5c72c29903813eJake Slack * Add a ContentHandler. Add an additional _content handler that is triggered on a tag name. SAX 18003928aee4356845252ac6b662d5c72c29903813eJake Slack * events are passed to the ContentHandler provided from a matching start element to the 18103928aee4356845252ac6b662d5c72c29903813eJake Slack * corresponding end element. Only a single _content handler can be registered against each tag. 18203928aee4356845252ac6b662d5c72c29903813eJake Slack * 18303928aee4356845252ac6b662d5c72c29903813eJake Slack * @param trigger Tag local or q name. 18403928aee4356845252ac6b662d5c72c29903813eJake Slack * @param observer SAX ContentHandler 18503928aee4356845252ac6b662d5c72c29903813eJake Slack */ 18603928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized void addContentHandler(String trigger, ContentHandler observer) 18703928aee4356845252ac6b662d5c72c29903813eJake Slack { 18803928aee4356845252ac6b662d5c72c29903813eJake Slack if (_observerMap == null) 18903928aee4356845252ac6b662d5c72c29903813eJake Slack _observerMap = new HashMap(); 19003928aee4356845252ac6b662d5c72c29903813eJake Slack _observerMap.put(trigger, observer); 19103928aee4356845252ac6b662d5c72c29903813eJake Slack } 19203928aee4356845252ac6b662d5c72c29903813eJake Slack 19303928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 19403928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized Node parse(InputSource source) throws IOException, SAXException 19503928aee4356845252ac6b662d5c72c29903813eJake Slack { 19603928aee4356845252ac6b662d5c72c29903813eJake Slack _dtd=null; 19703928aee4356845252ac6b662d5c72c29903813eJake Slack Handler handler = new Handler(); 19803928aee4356845252ac6b662d5c72c29903813eJake Slack XMLReader reader = _parser.getXMLReader(); 19903928aee4356845252ac6b662d5c72c29903813eJake Slack reader.setContentHandler(handler); 20003928aee4356845252ac6b662d5c72c29903813eJake Slack reader.setErrorHandler(handler); 20103928aee4356845252ac6b662d5c72c29903813eJake Slack reader.setEntityResolver(handler); 20203928aee4356845252ac6b662d5c72c29903813eJake Slack if (LOG.isDebugEnabled()) 20303928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug("parsing: sid=" + source.getSystemId() + ",pid=" + source.getPublicId()); 20403928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.parse(source, handler); 20503928aee4356845252ac6b662d5c72c29903813eJake Slack if (handler._error != null) 20603928aee4356845252ac6b662d5c72c29903813eJake Slack throw handler._error; 20703928aee4356845252ac6b662d5c72c29903813eJake Slack Node doc = (Node) handler._top.get(0); 20803928aee4356845252ac6b662d5c72c29903813eJake Slack handler.clear(); 20903928aee4356845252ac6b662d5c72c29903813eJake Slack return doc; 21003928aee4356845252ac6b662d5c72c29903813eJake Slack } 21103928aee4356845252ac6b662d5c72c29903813eJake Slack 21203928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 21303928aee4356845252ac6b662d5c72c29903813eJake Slack /** 21403928aee4356845252ac6b662d5c72c29903813eJake Slack * Parse String URL. 21503928aee4356845252ac6b662d5c72c29903813eJake Slack */ 21603928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized Node parse(String url) throws IOException, SAXException 21703928aee4356845252ac6b662d5c72c29903813eJake Slack { 21803928aee4356845252ac6b662d5c72c29903813eJake Slack if (LOG.isDebugEnabled()) 21903928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug("parse: " + url); 22003928aee4356845252ac6b662d5c72c29903813eJake Slack return parse(new InputSource(url)); 22103928aee4356845252ac6b662d5c72c29903813eJake Slack } 22203928aee4356845252ac6b662d5c72c29903813eJake Slack 22303928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 22403928aee4356845252ac6b662d5c72c29903813eJake Slack /** 22503928aee4356845252ac6b662d5c72c29903813eJake Slack * Parse File. 22603928aee4356845252ac6b662d5c72c29903813eJake Slack */ 22703928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized Node parse(File file) throws IOException, SAXException 22803928aee4356845252ac6b662d5c72c29903813eJake Slack { 22903928aee4356845252ac6b662d5c72c29903813eJake Slack if (LOG.isDebugEnabled()) 23003928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug("parse: " + file); 23103928aee4356845252ac6b662d5c72c29903813eJake Slack return parse(new InputSource(Resource.toURL(file).toString())); 23203928aee4356845252ac6b662d5c72c29903813eJake Slack } 23303928aee4356845252ac6b662d5c72c29903813eJake Slack 23403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 23503928aee4356845252ac6b662d5c72c29903813eJake Slack /** 23603928aee4356845252ac6b662d5c72c29903813eJake Slack * Parse InputStream. 23703928aee4356845252ac6b662d5c72c29903813eJake Slack */ 23803928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized Node parse(InputStream in) throws IOException, SAXException 23903928aee4356845252ac6b662d5c72c29903813eJake Slack { 24003928aee4356845252ac6b662d5c72c29903813eJake Slack _dtd=null; 24103928aee4356845252ac6b662d5c72c29903813eJake Slack Handler handler = new Handler(); 24203928aee4356845252ac6b662d5c72c29903813eJake Slack XMLReader reader = _parser.getXMLReader(); 24303928aee4356845252ac6b662d5c72c29903813eJake Slack reader.setContentHandler(handler); 24403928aee4356845252ac6b662d5c72c29903813eJake Slack reader.setErrorHandler(handler); 24503928aee4356845252ac6b662d5c72c29903813eJake Slack reader.setEntityResolver(handler); 24603928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.parse(new InputSource(in), handler); 24703928aee4356845252ac6b662d5c72c29903813eJake Slack if (handler._error != null) 24803928aee4356845252ac6b662d5c72c29903813eJake Slack throw handler._error; 24903928aee4356845252ac6b662d5c72c29903813eJake Slack Node doc = (Node) handler._top.get(0); 25003928aee4356845252ac6b662d5c72c29903813eJake Slack handler.clear(); 25103928aee4356845252ac6b662d5c72c29903813eJake Slack return doc; 25203928aee4356845252ac6b662d5c72c29903813eJake Slack } 25303928aee4356845252ac6b662d5c72c29903813eJake Slack 25403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 25503928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 25603928aee4356845252ac6b662d5c72c29903813eJake Slack private class NoopHandler extends DefaultHandler 25703928aee4356845252ac6b662d5c72c29903813eJake Slack { 25803928aee4356845252ac6b662d5c72c29903813eJake Slack Handler _next; 25903928aee4356845252ac6b662d5c72c29903813eJake Slack int _depth; 26003928aee4356845252ac6b662d5c72c29903813eJake Slack 26103928aee4356845252ac6b662d5c72c29903813eJake Slack NoopHandler(Handler next) 26203928aee4356845252ac6b662d5c72c29903813eJake Slack { 26303928aee4356845252ac6b662d5c72c29903813eJake Slack this._next = next; 26403928aee4356845252ac6b662d5c72c29903813eJake Slack } 26503928aee4356845252ac6b662d5c72c29903813eJake Slack 26603928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 26703928aee4356845252ac6b662d5c72c29903813eJake Slack public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException 26803928aee4356845252ac6b662d5c72c29903813eJake Slack { 26903928aee4356845252ac6b662d5c72c29903813eJake Slack _depth++; 27003928aee4356845252ac6b662d5c72c29903813eJake Slack } 27103928aee4356845252ac6b662d5c72c29903813eJake Slack 27203928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 27303928aee4356845252ac6b662d5c72c29903813eJake Slack public void endElement(String uri, String localName, String qName) throws SAXException 27403928aee4356845252ac6b662d5c72c29903813eJake Slack { 27503928aee4356845252ac6b662d5c72c29903813eJake Slack if (_depth == 0) 27603928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.getXMLReader().setContentHandler(_next); 27703928aee4356845252ac6b662d5c72c29903813eJake Slack else 27803928aee4356845252ac6b662d5c72c29903813eJake Slack _depth--; 27903928aee4356845252ac6b662d5c72c29903813eJake Slack } 28003928aee4356845252ac6b662d5c72c29903813eJake Slack } 28103928aee4356845252ac6b662d5c72c29903813eJake Slack 28203928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 28303928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 28403928aee4356845252ac6b662d5c72c29903813eJake Slack private class Handler extends DefaultHandler 28503928aee4356845252ac6b662d5c72c29903813eJake Slack { 28603928aee4356845252ac6b662d5c72c29903813eJake Slack Node _top = new Node(null, null, null); 28703928aee4356845252ac6b662d5c72c29903813eJake Slack SAXParseException _error; 28803928aee4356845252ac6b662d5c72c29903813eJake Slack private Node _context = _top; 28903928aee4356845252ac6b662d5c72c29903813eJake Slack private NoopHandler _noop; 29003928aee4356845252ac6b662d5c72c29903813eJake Slack 29103928aee4356845252ac6b662d5c72c29903813eJake Slack Handler() 29203928aee4356845252ac6b662d5c72c29903813eJake Slack { 29303928aee4356845252ac6b662d5c72c29903813eJake Slack _noop = new NoopHandler(this); 29403928aee4356845252ac6b662d5c72c29903813eJake Slack } 29503928aee4356845252ac6b662d5c72c29903813eJake Slack 29603928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 29703928aee4356845252ac6b662d5c72c29903813eJake Slack void clear() 29803928aee4356845252ac6b662d5c72c29903813eJake Slack { 29903928aee4356845252ac6b662d5c72c29903813eJake Slack _top = null; 30003928aee4356845252ac6b662d5c72c29903813eJake Slack _error = null; 30103928aee4356845252ac6b662d5c72c29903813eJake Slack _context = null; 30203928aee4356845252ac6b662d5c72c29903813eJake Slack } 30303928aee4356845252ac6b662d5c72c29903813eJake Slack 30403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 30503928aee4356845252ac6b662d5c72c29903813eJake Slack public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException 30603928aee4356845252ac6b662d5c72c29903813eJake Slack { 30703928aee4356845252ac6b662d5c72c29903813eJake Slack String name = null; 30803928aee4356845252ac6b662d5c72c29903813eJake Slack if (_parser.isNamespaceAware()) 30903928aee4356845252ac6b662d5c72c29903813eJake Slack name = localName; 31003928aee4356845252ac6b662d5c72c29903813eJake Slack 31103928aee4356845252ac6b662d5c72c29903813eJake Slack if (name == null || "".equals(name)) 31203928aee4356845252ac6b662d5c72c29903813eJake Slack name = qName; 31303928aee4356845252ac6b662d5c72c29903813eJake Slack 31403928aee4356845252ac6b662d5c72c29903813eJake Slack Node node = new Node(_context, name, attrs); 31503928aee4356845252ac6b662d5c72c29903813eJake Slack 31603928aee4356845252ac6b662d5c72c29903813eJake Slack 31703928aee4356845252ac6b662d5c72c29903813eJake Slack // check if the node matches any xpaths set? 31803928aee4356845252ac6b662d5c72c29903813eJake Slack if (_xpaths != null) 31903928aee4356845252ac6b662d5c72c29903813eJake Slack { 32003928aee4356845252ac6b662d5c72c29903813eJake Slack String path = node.getPath(); 32103928aee4356845252ac6b662d5c72c29903813eJake Slack boolean match = false; 32203928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = LazyList.size(_xpaths); !match && i-- > 0;) 32303928aee4356845252ac6b662d5c72c29903813eJake Slack { 32403928aee4356845252ac6b662d5c72c29903813eJake Slack String xpath = (String) LazyList.get(_xpaths, i); 32503928aee4356845252ac6b662d5c72c29903813eJake Slack 32603928aee4356845252ac6b662d5c72c29903813eJake Slack match = path.equals(xpath) || xpath.startsWith(path) && xpath.length() > path.length() && xpath.charAt(path.length()) == '/'; 32703928aee4356845252ac6b662d5c72c29903813eJake Slack } 32803928aee4356845252ac6b662d5c72c29903813eJake Slack 32903928aee4356845252ac6b662d5c72c29903813eJake Slack if (match) 33003928aee4356845252ac6b662d5c72c29903813eJake Slack { 33103928aee4356845252ac6b662d5c72c29903813eJake Slack _context.add(node); 33203928aee4356845252ac6b662d5c72c29903813eJake Slack _context = node; 33303928aee4356845252ac6b662d5c72c29903813eJake Slack } 33403928aee4356845252ac6b662d5c72c29903813eJake Slack else 33503928aee4356845252ac6b662d5c72c29903813eJake Slack { 33603928aee4356845252ac6b662d5c72c29903813eJake Slack _parser.getXMLReader().setContentHandler(_noop); 33703928aee4356845252ac6b662d5c72c29903813eJake Slack } 33803928aee4356845252ac6b662d5c72c29903813eJake Slack } 33903928aee4356845252ac6b662d5c72c29903813eJake Slack else 34003928aee4356845252ac6b662d5c72c29903813eJake Slack { 34103928aee4356845252ac6b662d5c72c29903813eJake Slack _context.add(node); 34203928aee4356845252ac6b662d5c72c29903813eJake Slack _context = node; 34303928aee4356845252ac6b662d5c72c29903813eJake Slack } 34403928aee4356845252ac6b662d5c72c29903813eJake Slack 34503928aee4356845252ac6b662d5c72c29903813eJake Slack ContentHandler observer = null; 34603928aee4356845252ac6b662d5c72c29903813eJake Slack if (_observerMap != null) 34703928aee4356845252ac6b662d5c72c29903813eJake Slack observer = (ContentHandler) _observerMap.get(name); 34803928aee4356845252ac6b662d5c72c29903813eJake Slack _observers.push(observer); 34903928aee4356845252ac6b662d5c72c29903813eJake Slack 35003928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < _observers.size(); i++) 35103928aee4356845252ac6b662d5c72c29903813eJake Slack if (_observers.get(i) != null) 35203928aee4356845252ac6b662d5c72c29903813eJake Slack ((ContentHandler) _observers.get(i)).startElement(uri, localName, qName, attrs); 35303928aee4356845252ac6b662d5c72c29903813eJake Slack } 35403928aee4356845252ac6b662d5c72c29903813eJake Slack 35503928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 35603928aee4356845252ac6b662d5c72c29903813eJake Slack public void endElement(String uri, String localName, String qName) throws SAXException 35703928aee4356845252ac6b662d5c72c29903813eJake Slack { 35803928aee4356845252ac6b662d5c72c29903813eJake Slack _context = _context._parent; 35903928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < _observers.size(); i++) 36003928aee4356845252ac6b662d5c72c29903813eJake Slack if (_observers.get(i) != null) 36103928aee4356845252ac6b662d5c72c29903813eJake Slack ((ContentHandler) _observers.get(i)).endElement(uri, localName, qName); 36203928aee4356845252ac6b662d5c72c29903813eJake Slack _observers.pop(); 36303928aee4356845252ac6b662d5c72c29903813eJake Slack } 36403928aee4356845252ac6b662d5c72c29903813eJake Slack 36503928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 36603928aee4356845252ac6b662d5c72c29903813eJake Slack public void ignorableWhitespace(char buf[], int offset, int len) throws SAXException 36703928aee4356845252ac6b662d5c72c29903813eJake Slack { 36803928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < _observers.size(); i++) 36903928aee4356845252ac6b662d5c72c29903813eJake Slack if (_observers.get(i) != null) 37003928aee4356845252ac6b662d5c72c29903813eJake Slack ((ContentHandler) _observers.get(i)).ignorableWhitespace(buf, offset, len); 37103928aee4356845252ac6b662d5c72c29903813eJake Slack } 37203928aee4356845252ac6b662d5c72c29903813eJake Slack 37303928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 37403928aee4356845252ac6b662d5c72c29903813eJake Slack public void characters(char buf[], int offset, int len) throws SAXException 37503928aee4356845252ac6b662d5c72c29903813eJake Slack { 37603928aee4356845252ac6b662d5c72c29903813eJake Slack _context.add(new String(buf, offset, len)); 37703928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < _observers.size(); i++) 37803928aee4356845252ac6b662d5c72c29903813eJake Slack if (_observers.get(i) != null) 37903928aee4356845252ac6b662d5c72c29903813eJake Slack ((ContentHandler) _observers.get(i)).characters(buf, offset, len); 38003928aee4356845252ac6b662d5c72c29903813eJake Slack } 38103928aee4356845252ac6b662d5c72c29903813eJake Slack 38203928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 38303928aee4356845252ac6b662d5c72c29903813eJake Slack public void warning(SAXParseException ex) 38403928aee4356845252ac6b662d5c72c29903813eJake Slack { 38503928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug(Log.EXCEPTION, ex); 38603928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.warn("WARNING@" + getLocationString(ex) + " : " + ex.toString()); 38703928aee4356845252ac6b662d5c72c29903813eJake Slack } 38803928aee4356845252ac6b662d5c72c29903813eJake Slack 38903928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 39003928aee4356845252ac6b662d5c72c29903813eJake Slack public void error(SAXParseException ex) throws SAXException 39103928aee4356845252ac6b662d5c72c29903813eJake Slack { 39203928aee4356845252ac6b662d5c72c29903813eJake Slack // Save error and continue to report other errors 39303928aee4356845252ac6b662d5c72c29903813eJake Slack if (_error == null) 39403928aee4356845252ac6b662d5c72c29903813eJake Slack _error = ex; 39503928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug(Log.EXCEPTION, ex); 39603928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.warn("ERROR@" + getLocationString(ex) + " : " + ex.toString()); 39703928aee4356845252ac6b662d5c72c29903813eJake Slack } 39803928aee4356845252ac6b662d5c72c29903813eJake Slack 39903928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 40003928aee4356845252ac6b662d5c72c29903813eJake Slack public void fatalError(SAXParseException ex) throws SAXException 40103928aee4356845252ac6b662d5c72c29903813eJake Slack { 40203928aee4356845252ac6b662d5c72c29903813eJake Slack _error = ex; 40303928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug(Log.EXCEPTION, ex); 40403928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.warn("FATAL@" + getLocationString(ex) + " : " + ex.toString()); 40503928aee4356845252ac6b662d5c72c29903813eJake Slack throw ex; 40603928aee4356845252ac6b662d5c72c29903813eJake Slack } 40703928aee4356845252ac6b662d5c72c29903813eJake Slack 40803928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 40903928aee4356845252ac6b662d5c72c29903813eJake Slack private String getLocationString(SAXParseException ex) 41003928aee4356845252ac6b662d5c72c29903813eJake Slack { 41103928aee4356845252ac6b662d5c72c29903813eJake Slack return ex.getSystemId() + " line:" + ex.getLineNumber() + " col:" + ex.getColumnNumber(); 41203928aee4356845252ac6b662d5c72c29903813eJake Slack } 41303928aee4356845252ac6b662d5c72c29903813eJake Slack 41403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 41503928aee4356845252ac6b662d5c72c29903813eJake Slack public InputSource resolveEntity(String pid, String sid) 41603928aee4356845252ac6b662d5c72c29903813eJake Slack { 41703928aee4356845252ac6b662d5c72c29903813eJake Slack if (LOG.isDebugEnabled()) 41803928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug("resolveEntity(" + pid + ", " + sid + ")"); 41903928aee4356845252ac6b662d5c72c29903813eJake Slack 42003928aee4356845252ac6b662d5c72c29903813eJake Slack if (sid!=null && sid.endsWith(".dtd")) 42103928aee4356845252ac6b662d5c72c29903813eJake Slack _dtd=sid; 42203928aee4356845252ac6b662d5c72c29903813eJake Slack 42303928aee4356845252ac6b662d5c72c29903813eJake Slack URL entity = null; 42403928aee4356845252ac6b662d5c72c29903813eJake Slack if (pid != null) 42503928aee4356845252ac6b662d5c72c29903813eJake Slack entity = (URL) _redirectMap.get(pid); 42603928aee4356845252ac6b662d5c72c29903813eJake Slack if (entity == null) 42703928aee4356845252ac6b662d5c72c29903813eJake Slack entity = (URL) _redirectMap.get(sid); 42803928aee4356845252ac6b662d5c72c29903813eJake Slack if (entity == null) 42903928aee4356845252ac6b662d5c72c29903813eJake Slack { 43003928aee4356845252ac6b662d5c72c29903813eJake Slack String dtd = sid; 43103928aee4356845252ac6b662d5c72c29903813eJake Slack if (dtd.lastIndexOf('/') >= 0) 43203928aee4356845252ac6b662d5c72c29903813eJake Slack dtd = dtd.substring(dtd.lastIndexOf('/') + 1); 43303928aee4356845252ac6b662d5c72c29903813eJake Slack 43403928aee4356845252ac6b662d5c72c29903813eJake Slack if (LOG.isDebugEnabled()) 43503928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug("Can't exact match entity in redirect map, trying " + dtd); 43603928aee4356845252ac6b662d5c72c29903813eJake Slack entity = (URL) _redirectMap.get(dtd); 43703928aee4356845252ac6b662d5c72c29903813eJake Slack } 43803928aee4356845252ac6b662d5c72c29903813eJake Slack 43903928aee4356845252ac6b662d5c72c29903813eJake Slack if (entity != null) 44003928aee4356845252ac6b662d5c72c29903813eJake Slack { 44103928aee4356845252ac6b662d5c72c29903813eJake Slack try 44203928aee4356845252ac6b662d5c72c29903813eJake Slack { 44303928aee4356845252ac6b662d5c72c29903813eJake Slack InputStream in = entity.openStream(); 44403928aee4356845252ac6b662d5c72c29903813eJake Slack if (LOG.isDebugEnabled()) 44503928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.debug("Redirected entity " + sid + " --> " + entity); 44603928aee4356845252ac6b662d5c72c29903813eJake Slack InputSource is = new InputSource(in); 44703928aee4356845252ac6b662d5c72c29903813eJake Slack is.setSystemId(sid); 44803928aee4356845252ac6b662d5c72c29903813eJake Slack return is; 44903928aee4356845252ac6b662d5c72c29903813eJake Slack } 45003928aee4356845252ac6b662d5c72c29903813eJake Slack catch (IOException e) 45103928aee4356845252ac6b662d5c72c29903813eJake Slack { 45203928aee4356845252ac6b662d5c72c29903813eJake Slack LOG.ignore(e); 45303928aee4356845252ac6b662d5c72c29903813eJake Slack } 45403928aee4356845252ac6b662d5c72c29903813eJake Slack } 45503928aee4356845252ac6b662d5c72c29903813eJake Slack return null; 45603928aee4356845252ac6b662d5c72c29903813eJake Slack } 45703928aee4356845252ac6b662d5c72c29903813eJake Slack } 45803928aee4356845252ac6b662d5c72c29903813eJake Slack 45903928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 46003928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 46103928aee4356845252ac6b662d5c72c29903813eJake Slack /** 46203928aee4356845252ac6b662d5c72c29903813eJake Slack * XML Attribute. 46303928aee4356845252ac6b662d5c72c29903813eJake Slack */ 46403928aee4356845252ac6b662d5c72c29903813eJake Slack public static class Attribute 46503928aee4356845252ac6b662d5c72c29903813eJake Slack { 46603928aee4356845252ac6b662d5c72c29903813eJake Slack private String _name; 46703928aee4356845252ac6b662d5c72c29903813eJake Slack private String _value; 46803928aee4356845252ac6b662d5c72c29903813eJake Slack 46903928aee4356845252ac6b662d5c72c29903813eJake Slack Attribute(String n, String v) 47003928aee4356845252ac6b662d5c72c29903813eJake Slack { 47103928aee4356845252ac6b662d5c72c29903813eJake Slack _name = n; 47203928aee4356845252ac6b662d5c72c29903813eJake Slack _value = v; 47303928aee4356845252ac6b662d5c72c29903813eJake Slack } 47403928aee4356845252ac6b662d5c72c29903813eJake Slack 47503928aee4356845252ac6b662d5c72c29903813eJake Slack public String getName() 47603928aee4356845252ac6b662d5c72c29903813eJake Slack { 47703928aee4356845252ac6b662d5c72c29903813eJake Slack return _name; 47803928aee4356845252ac6b662d5c72c29903813eJake Slack } 47903928aee4356845252ac6b662d5c72c29903813eJake Slack 48003928aee4356845252ac6b662d5c72c29903813eJake Slack public String getValue() 48103928aee4356845252ac6b662d5c72c29903813eJake Slack { 48203928aee4356845252ac6b662d5c72c29903813eJake Slack return _value; 48303928aee4356845252ac6b662d5c72c29903813eJake Slack } 48403928aee4356845252ac6b662d5c72c29903813eJake Slack } 48503928aee4356845252ac6b662d5c72c29903813eJake Slack 48603928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 48703928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 48803928aee4356845252ac6b662d5c72c29903813eJake Slack /** 48903928aee4356845252ac6b662d5c72c29903813eJake Slack * XML Node. Represents an XML element with optional attributes and ordered content. 49003928aee4356845252ac6b662d5c72c29903813eJake Slack */ 49103928aee4356845252ac6b662d5c72c29903813eJake Slack public static class Node extends AbstractList<Object> 49203928aee4356845252ac6b662d5c72c29903813eJake Slack { 49303928aee4356845252ac6b662d5c72c29903813eJake Slack Node _parent; 49403928aee4356845252ac6b662d5c72c29903813eJake Slack private ArrayList<Object> _list; 49503928aee4356845252ac6b662d5c72c29903813eJake Slack private String _tag; 49603928aee4356845252ac6b662d5c72c29903813eJake Slack private Attribute[] _attrs; 49703928aee4356845252ac6b662d5c72c29903813eJake Slack private boolean _lastString = false; 49803928aee4356845252ac6b662d5c72c29903813eJake Slack private String _path; 49903928aee4356845252ac6b662d5c72c29903813eJake Slack 50003928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 50103928aee4356845252ac6b662d5c72c29903813eJake Slack Node(Node parent, String tag, Attributes attrs) 50203928aee4356845252ac6b662d5c72c29903813eJake Slack { 50303928aee4356845252ac6b662d5c72c29903813eJake Slack _parent = parent; 50403928aee4356845252ac6b662d5c72c29903813eJake Slack _tag = tag; 50503928aee4356845252ac6b662d5c72c29903813eJake Slack 50603928aee4356845252ac6b662d5c72c29903813eJake Slack if (attrs != null) 50703928aee4356845252ac6b662d5c72c29903813eJake Slack { 50803928aee4356845252ac6b662d5c72c29903813eJake Slack _attrs = new Attribute[attrs.getLength()]; 50903928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < attrs.getLength(); i++) 51003928aee4356845252ac6b662d5c72c29903813eJake Slack { 51103928aee4356845252ac6b662d5c72c29903813eJake Slack String name = attrs.getLocalName(i); 51203928aee4356845252ac6b662d5c72c29903813eJake Slack if (name == null || name.equals("")) 51303928aee4356845252ac6b662d5c72c29903813eJake Slack name = attrs.getQName(i); 51403928aee4356845252ac6b662d5c72c29903813eJake Slack _attrs[i] = new Attribute(name, attrs.getValue(i)); 51503928aee4356845252ac6b662d5c72c29903813eJake Slack } 51603928aee4356845252ac6b662d5c72c29903813eJake Slack } 51703928aee4356845252ac6b662d5c72c29903813eJake Slack } 51803928aee4356845252ac6b662d5c72c29903813eJake Slack 51903928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 52003928aee4356845252ac6b662d5c72c29903813eJake Slack public Node getParent() 52103928aee4356845252ac6b662d5c72c29903813eJake Slack { 52203928aee4356845252ac6b662d5c72c29903813eJake Slack return _parent; 52303928aee4356845252ac6b662d5c72c29903813eJake Slack } 52403928aee4356845252ac6b662d5c72c29903813eJake Slack 52503928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 52603928aee4356845252ac6b662d5c72c29903813eJake Slack public String getTag() 52703928aee4356845252ac6b662d5c72c29903813eJake Slack { 52803928aee4356845252ac6b662d5c72c29903813eJake Slack return _tag; 52903928aee4356845252ac6b662d5c72c29903813eJake Slack } 53003928aee4356845252ac6b662d5c72c29903813eJake Slack 53103928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 53203928aee4356845252ac6b662d5c72c29903813eJake Slack public String getPath() 53303928aee4356845252ac6b662d5c72c29903813eJake Slack { 53403928aee4356845252ac6b662d5c72c29903813eJake Slack if (_path == null) 53503928aee4356845252ac6b662d5c72c29903813eJake Slack { 53603928aee4356845252ac6b662d5c72c29903813eJake Slack if (getParent() != null && getParent().getTag() != null) 53703928aee4356845252ac6b662d5c72c29903813eJake Slack _path = getParent().getPath() + "/" + _tag; 53803928aee4356845252ac6b662d5c72c29903813eJake Slack else 53903928aee4356845252ac6b662d5c72c29903813eJake Slack _path = "/" + _tag; 54003928aee4356845252ac6b662d5c72c29903813eJake Slack } 54103928aee4356845252ac6b662d5c72c29903813eJake Slack return _path; 54203928aee4356845252ac6b662d5c72c29903813eJake Slack } 54303928aee4356845252ac6b662d5c72c29903813eJake Slack 54403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 54503928aee4356845252ac6b662d5c72c29903813eJake Slack /** 54603928aee4356845252ac6b662d5c72c29903813eJake Slack * Get an array of element attributes. 54703928aee4356845252ac6b662d5c72c29903813eJake Slack */ 54803928aee4356845252ac6b662d5c72c29903813eJake Slack public Attribute[] getAttributes() 54903928aee4356845252ac6b662d5c72c29903813eJake Slack { 55003928aee4356845252ac6b662d5c72c29903813eJake Slack return _attrs; 55103928aee4356845252ac6b662d5c72c29903813eJake Slack } 55203928aee4356845252ac6b662d5c72c29903813eJake Slack 55303928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 55403928aee4356845252ac6b662d5c72c29903813eJake Slack /** 55503928aee4356845252ac6b662d5c72c29903813eJake Slack * Get an element attribute. 55603928aee4356845252ac6b662d5c72c29903813eJake Slack * 55703928aee4356845252ac6b662d5c72c29903813eJake Slack * @return attribute or null. 55803928aee4356845252ac6b662d5c72c29903813eJake Slack */ 55903928aee4356845252ac6b662d5c72c29903813eJake Slack public String getAttribute(String name) 56003928aee4356845252ac6b662d5c72c29903813eJake Slack { 56103928aee4356845252ac6b662d5c72c29903813eJake Slack return getAttribute(name, null); 56203928aee4356845252ac6b662d5c72c29903813eJake Slack } 56303928aee4356845252ac6b662d5c72c29903813eJake Slack 56403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 56503928aee4356845252ac6b662d5c72c29903813eJake Slack /** 56603928aee4356845252ac6b662d5c72c29903813eJake Slack * Get an element attribute. 56703928aee4356845252ac6b662d5c72c29903813eJake Slack * 56803928aee4356845252ac6b662d5c72c29903813eJake Slack * @return attribute or null. 56903928aee4356845252ac6b662d5c72c29903813eJake Slack */ 57003928aee4356845252ac6b662d5c72c29903813eJake Slack public String getAttribute(String name, String dft) 57103928aee4356845252ac6b662d5c72c29903813eJake Slack { 57203928aee4356845252ac6b662d5c72c29903813eJake Slack if (_attrs == null || name == null) 57303928aee4356845252ac6b662d5c72c29903813eJake Slack return dft; 57403928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < _attrs.length; i++) 57503928aee4356845252ac6b662d5c72c29903813eJake Slack if (name.equals(_attrs[i].getName())) 57603928aee4356845252ac6b662d5c72c29903813eJake Slack return _attrs[i].getValue(); 57703928aee4356845252ac6b662d5c72c29903813eJake Slack return dft; 57803928aee4356845252ac6b662d5c72c29903813eJake Slack } 57903928aee4356845252ac6b662d5c72c29903813eJake Slack 58003928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 58103928aee4356845252ac6b662d5c72c29903813eJake Slack /** 58203928aee4356845252ac6b662d5c72c29903813eJake Slack * Get the number of children nodes. 58303928aee4356845252ac6b662d5c72c29903813eJake Slack */ 58403928aee4356845252ac6b662d5c72c29903813eJake Slack public int size() 58503928aee4356845252ac6b662d5c72c29903813eJake Slack { 58603928aee4356845252ac6b662d5c72c29903813eJake Slack if (_list != null) 58703928aee4356845252ac6b662d5c72c29903813eJake Slack return _list.size(); 58803928aee4356845252ac6b662d5c72c29903813eJake Slack return 0; 58903928aee4356845252ac6b662d5c72c29903813eJake Slack } 59003928aee4356845252ac6b662d5c72c29903813eJake Slack 59103928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 59203928aee4356845252ac6b662d5c72c29903813eJake Slack /** 59303928aee4356845252ac6b662d5c72c29903813eJake Slack * Get the ith child node or content. 59403928aee4356845252ac6b662d5c72c29903813eJake Slack * 59503928aee4356845252ac6b662d5c72c29903813eJake Slack * @return Node or String. 59603928aee4356845252ac6b662d5c72c29903813eJake Slack */ 59703928aee4356845252ac6b662d5c72c29903813eJake Slack public Object get(int i) 59803928aee4356845252ac6b662d5c72c29903813eJake Slack { 59903928aee4356845252ac6b662d5c72c29903813eJake Slack if (_list != null) 60003928aee4356845252ac6b662d5c72c29903813eJake Slack return _list.get(i); 60103928aee4356845252ac6b662d5c72c29903813eJake Slack return null; 60203928aee4356845252ac6b662d5c72c29903813eJake Slack } 60303928aee4356845252ac6b662d5c72c29903813eJake Slack 60403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 60503928aee4356845252ac6b662d5c72c29903813eJake Slack /** 60603928aee4356845252ac6b662d5c72c29903813eJake Slack * Get the first child node with the tag. 60703928aee4356845252ac6b662d5c72c29903813eJake Slack * 60803928aee4356845252ac6b662d5c72c29903813eJake Slack * @param tag 60903928aee4356845252ac6b662d5c72c29903813eJake Slack * @return Node or null. 61003928aee4356845252ac6b662d5c72c29903813eJake Slack */ 61103928aee4356845252ac6b662d5c72c29903813eJake Slack public Node get(String tag) 61203928aee4356845252ac6b662d5c72c29903813eJake Slack { 61303928aee4356845252ac6b662d5c72c29903813eJake Slack if (_list != null) 61403928aee4356845252ac6b662d5c72c29903813eJake Slack { 61503928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < _list.size(); i++) 61603928aee4356845252ac6b662d5c72c29903813eJake Slack { 61703928aee4356845252ac6b662d5c72c29903813eJake Slack Object o = _list.get(i); 61803928aee4356845252ac6b662d5c72c29903813eJake Slack if (o instanceof Node) 61903928aee4356845252ac6b662d5c72c29903813eJake Slack { 62003928aee4356845252ac6b662d5c72c29903813eJake Slack Node n = (Node) o; 62103928aee4356845252ac6b662d5c72c29903813eJake Slack if (tag.equals(n._tag)) 62203928aee4356845252ac6b662d5c72c29903813eJake Slack return n; 62303928aee4356845252ac6b662d5c72c29903813eJake Slack } 62403928aee4356845252ac6b662d5c72c29903813eJake Slack } 62503928aee4356845252ac6b662d5c72c29903813eJake Slack } 62603928aee4356845252ac6b662d5c72c29903813eJake Slack return null; 62703928aee4356845252ac6b662d5c72c29903813eJake Slack } 62803928aee4356845252ac6b662d5c72c29903813eJake Slack 62903928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 63003928aee4356845252ac6b662d5c72c29903813eJake Slack @Override 63103928aee4356845252ac6b662d5c72c29903813eJake Slack public void add(int i, Object o) 63203928aee4356845252ac6b662d5c72c29903813eJake Slack { 63303928aee4356845252ac6b662d5c72c29903813eJake Slack if (_list == null) 63403928aee4356845252ac6b662d5c72c29903813eJake Slack _list = new ArrayList<Object>(); 63503928aee4356845252ac6b662d5c72c29903813eJake Slack if (o instanceof String) 63603928aee4356845252ac6b662d5c72c29903813eJake Slack { 63703928aee4356845252ac6b662d5c72c29903813eJake Slack if (_lastString) 63803928aee4356845252ac6b662d5c72c29903813eJake Slack { 63903928aee4356845252ac6b662d5c72c29903813eJake Slack int last = _list.size() - 1; 64003928aee4356845252ac6b662d5c72c29903813eJake Slack _list.set(last, (String) _list.get(last) + o); 64103928aee4356845252ac6b662d5c72c29903813eJake Slack } 64203928aee4356845252ac6b662d5c72c29903813eJake Slack else 64303928aee4356845252ac6b662d5c72c29903813eJake Slack _list.add(i, o); 64403928aee4356845252ac6b662d5c72c29903813eJake Slack _lastString = true; 64503928aee4356845252ac6b662d5c72c29903813eJake Slack } 64603928aee4356845252ac6b662d5c72c29903813eJake Slack else 64703928aee4356845252ac6b662d5c72c29903813eJake Slack { 64803928aee4356845252ac6b662d5c72c29903813eJake Slack _lastString = false; 64903928aee4356845252ac6b662d5c72c29903813eJake Slack _list.add(i, o); 65003928aee4356845252ac6b662d5c72c29903813eJake Slack } 65103928aee4356845252ac6b662d5c72c29903813eJake Slack } 65203928aee4356845252ac6b662d5c72c29903813eJake Slack 65303928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 65403928aee4356845252ac6b662d5c72c29903813eJake Slack public void clear() 65503928aee4356845252ac6b662d5c72c29903813eJake Slack { 65603928aee4356845252ac6b662d5c72c29903813eJake Slack if (_list != null) 65703928aee4356845252ac6b662d5c72c29903813eJake Slack _list.clear(); 65803928aee4356845252ac6b662d5c72c29903813eJake Slack _list = null; 65903928aee4356845252ac6b662d5c72c29903813eJake Slack } 66003928aee4356845252ac6b662d5c72c29903813eJake Slack 66103928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 66203928aee4356845252ac6b662d5c72c29903813eJake Slack /** 66303928aee4356845252ac6b662d5c72c29903813eJake Slack * Get a tag as a string. 66403928aee4356845252ac6b662d5c72c29903813eJake Slack * 66503928aee4356845252ac6b662d5c72c29903813eJake Slack * @param tag The tag to get 66603928aee4356845252ac6b662d5c72c29903813eJake Slack * @param tags IF true, tags are included in the value. 66703928aee4356845252ac6b662d5c72c29903813eJake Slack * @param trim If true, trim the value. 66803928aee4356845252ac6b662d5c72c29903813eJake Slack * @return results of get(tag).toString(tags). 66903928aee4356845252ac6b662d5c72c29903813eJake Slack */ 67003928aee4356845252ac6b662d5c72c29903813eJake Slack public String getString(String tag, boolean tags, boolean trim) 67103928aee4356845252ac6b662d5c72c29903813eJake Slack { 67203928aee4356845252ac6b662d5c72c29903813eJake Slack Node node = get(tag); 67303928aee4356845252ac6b662d5c72c29903813eJake Slack if (node == null) 67403928aee4356845252ac6b662d5c72c29903813eJake Slack return null; 67503928aee4356845252ac6b662d5c72c29903813eJake Slack String s = node.toString(tags); 67603928aee4356845252ac6b662d5c72c29903813eJake Slack if (s != null && trim) 67703928aee4356845252ac6b662d5c72c29903813eJake Slack s = s.trim(); 67803928aee4356845252ac6b662d5c72c29903813eJake Slack return s; 67903928aee4356845252ac6b662d5c72c29903813eJake Slack } 68003928aee4356845252ac6b662d5c72c29903813eJake Slack 68103928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 68203928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized String toString() 68303928aee4356845252ac6b662d5c72c29903813eJake Slack { 68403928aee4356845252ac6b662d5c72c29903813eJake Slack return toString(true); 68503928aee4356845252ac6b662d5c72c29903813eJake Slack } 68603928aee4356845252ac6b662d5c72c29903813eJake Slack 68703928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 68803928aee4356845252ac6b662d5c72c29903813eJake Slack /** 68903928aee4356845252ac6b662d5c72c29903813eJake Slack * Convert to a string. 69003928aee4356845252ac6b662d5c72c29903813eJake Slack * 69103928aee4356845252ac6b662d5c72c29903813eJake Slack * @param tag If false, only _content is shown. 69203928aee4356845252ac6b662d5c72c29903813eJake Slack */ 69303928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized String toString(boolean tag) 69403928aee4356845252ac6b662d5c72c29903813eJake Slack { 69503928aee4356845252ac6b662d5c72c29903813eJake Slack StringBuilder buf = new StringBuilder(); 69603928aee4356845252ac6b662d5c72c29903813eJake Slack toString(buf, tag); 69703928aee4356845252ac6b662d5c72c29903813eJake Slack return buf.toString(); 69803928aee4356845252ac6b662d5c72c29903813eJake Slack } 69903928aee4356845252ac6b662d5c72c29903813eJake Slack 70003928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 70103928aee4356845252ac6b662d5c72c29903813eJake Slack /** 70203928aee4356845252ac6b662d5c72c29903813eJake Slack * Convert to a string. 70303928aee4356845252ac6b662d5c72c29903813eJake Slack * 70403928aee4356845252ac6b662d5c72c29903813eJake Slack * @param tag If false, only _content is shown. 70503928aee4356845252ac6b662d5c72c29903813eJake Slack */ 70603928aee4356845252ac6b662d5c72c29903813eJake Slack public synchronized String toString(boolean tag, boolean trim) 70703928aee4356845252ac6b662d5c72c29903813eJake Slack { 70803928aee4356845252ac6b662d5c72c29903813eJake Slack String s = toString(tag); 70903928aee4356845252ac6b662d5c72c29903813eJake Slack if (s != null && trim) 71003928aee4356845252ac6b662d5c72c29903813eJake Slack s = s.trim(); 71103928aee4356845252ac6b662d5c72c29903813eJake Slack return s; 71203928aee4356845252ac6b662d5c72c29903813eJake Slack } 71303928aee4356845252ac6b662d5c72c29903813eJake Slack 71403928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 71503928aee4356845252ac6b662d5c72c29903813eJake Slack private synchronized void toString(StringBuilder buf, boolean tag) 71603928aee4356845252ac6b662d5c72c29903813eJake Slack { 71703928aee4356845252ac6b662d5c72c29903813eJake Slack if (tag) 71803928aee4356845252ac6b662d5c72c29903813eJake Slack { 71903928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append("<"); 72003928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append(_tag); 72103928aee4356845252ac6b662d5c72c29903813eJake Slack 72203928aee4356845252ac6b662d5c72c29903813eJake Slack if (_attrs != null) 72303928aee4356845252ac6b662d5c72c29903813eJake Slack { 72403928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < _attrs.length; i++) 72503928aee4356845252ac6b662d5c72c29903813eJake Slack { 72603928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append(' '); 72703928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append(_attrs[i].getName()); 72803928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append("=\""); 72903928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append(_attrs[i].getValue()); 73003928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append("\""); 73103928aee4356845252ac6b662d5c72c29903813eJake Slack } 73203928aee4356845252ac6b662d5c72c29903813eJake Slack } 73303928aee4356845252ac6b662d5c72c29903813eJake Slack } 73403928aee4356845252ac6b662d5c72c29903813eJake Slack 73503928aee4356845252ac6b662d5c72c29903813eJake Slack if (_list != null) 73603928aee4356845252ac6b662d5c72c29903813eJake Slack { 73703928aee4356845252ac6b662d5c72c29903813eJake Slack if (tag) 73803928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append(">"); 73903928aee4356845252ac6b662d5c72c29903813eJake Slack for (int i = 0; i < _list.size(); i++) 74003928aee4356845252ac6b662d5c72c29903813eJake Slack { 74103928aee4356845252ac6b662d5c72c29903813eJake Slack Object o = _list.get(i); 74203928aee4356845252ac6b662d5c72c29903813eJake Slack if (o == null) 74303928aee4356845252ac6b662d5c72c29903813eJake Slack continue; 74403928aee4356845252ac6b662d5c72c29903813eJake Slack if (o instanceof Node) 74503928aee4356845252ac6b662d5c72c29903813eJake Slack ((Node) o).toString(buf, tag); 74603928aee4356845252ac6b662d5c72c29903813eJake Slack else 74703928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append(o.toString()); 74803928aee4356845252ac6b662d5c72c29903813eJake Slack } 74903928aee4356845252ac6b662d5c72c29903813eJake Slack if (tag) 75003928aee4356845252ac6b662d5c72c29903813eJake Slack { 75103928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append("</"); 75203928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append(_tag); 75303928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append(">"); 75403928aee4356845252ac6b662d5c72c29903813eJake Slack } 75503928aee4356845252ac6b662d5c72c29903813eJake Slack } 75603928aee4356845252ac6b662d5c72c29903813eJake Slack else if (tag) 75703928aee4356845252ac6b662d5c72c29903813eJake Slack buf.append("/>"); 75803928aee4356845252ac6b662d5c72c29903813eJake Slack } 75903928aee4356845252ac6b662d5c72c29903813eJake Slack 76003928aee4356845252ac6b662d5c72c29903813eJake Slack /* ------------------------------------------------------------ */ 76103928aee4356845252ac6b662d5c72c29903813eJake Slack /** 76203928aee4356845252ac6b662d5c72c29903813eJake Slack * Iterator over named child nodes. 76303928aee4356845252ac6b662d5c72c29903813eJake Slack * 76403928aee4356845252ac6b662d5c72c29903813eJake Slack * @param tag The tag of the nodes. 76503928aee4356845252ac6b662d5c72c29903813eJake Slack * @return Iterator over all child nodes with the specified tag. 76603928aee4356845252ac6b662d5c72c29903813eJake Slack */ 76703928aee4356845252ac6b662d5c72c29903813eJake Slack public Iterator<Node> iterator(final String tag) 76803928aee4356845252ac6b662d5c72c29903813eJake Slack { 76903928aee4356845252ac6b662d5c72c29903813eJake Slack return new Iterator<Node>() 77003928aee4356845252ac6b662d5c72c29903813eJake Slack { 77103928aee4356845252ac6b662d5c72c29903813eJake Slack int c = 0; 77203928aee4356845252ac6b662d5c72c29903813eJake Slack Node _node; 77303928aee4356845252ac6b662d5c72c29903813eJake Slack 77403928aee4356845252ac6b662d5c72c29903813eJake Slack /* -------------------------------------------------- */ 77503928aee4356845252ac6b662d5c72c29903813eJake Slack public boolean hasNext() 77603928aee4356845252ac6b662d5c72c29903813eJake Slack { 77703928aee4356845252ac6b662d5c72c29903813eJake Slack if (_node != null) 77803928aee4356845252ac6b662d5c72c29903813eJake Slack return true; 77903928aee4356845252ac6b662d5c72c29903813eJake Slack while (_list != null && c < _list.size()) 78003928aee4356845252ac6b662d5c72c29903813eJake Slack { 78103928aee4356845252ac6b662d5c72c29903813eJake Slack Object o = _list.get(c); 78203928aee4356845252ac6b662d5c72c29903813eJake Slack if (o instanceof Node) 78303928aee4356845252ac6b662d5c72c29903813eJake Slack { 78403928aee4356845252ac6b662d5c72c29903813eJake Slack Node n = (Node) o; 78503928aee4356845252ac6b662d5c72c29903813eJake Slack if (tag.equals(n._tag)) 78603928aee4356845252ac6b662d5c72c29903813eJake Slack { 78703928aee4356845252ac6b662d5c72c29903813eJake Slack _node = n; 78803928aee4356845252ac6b662d5c72c29903813eJake Slack return true; 78903928aee4356845252ac6b662d5c72c29903813eJake Slack } 79003928aee4356845252ac6b662d5c72c29903813eJake Slack } 79103928aee4356845252ac6b662d5c72c29903813eJake Slack c++; 79203928aee4356845252ac6b662d5c72c29903813eJake Slack } 79303928aee4356845252ac6b662d5c72c29903813eJake Slack return false; 79403928aee4356845252ac6b662d5c72c29903813eJake Slack } 79503928aee4356845252ac6b662d5c72c29903813eJake Slack 79603928aee4356845252ac6b662d5c72c29903813eJake Slack /* -------------------------------------------------- */ 79703928aee4356845252ac6b662d5c72c29903813eJake Slack public Node next() 79803928aee4356845252ac6b662d5c72c29903813eJake Slack { 79903928aee4356845252ac6b662d5c72c29903813eJake Slack try 80003928aee4356845252ac6b662d5c72c29903813eJake Slack { 80103928aee4356845252ac6b662d5c72c29903813eJake Slack if (hasNext()) 80203928aee4356845252ac6b662d5c72c29903813eJake Slack return _node; 80303928aee4356845252ac6b662d5c72c29903813eJake Slack throw new NoSuchElementException(); 80403928aee4356845252ac6b662d5c72c29903813eJake Slack } 80503928aee4356845252ac6b662d5c72c29903813eJake Slack finally 80603928aee4356845252ac6b662d5c72c29903813eJake Slack { 80703928aee4356845252ac6b662d5c72c29903813eJake Slack _node = null; 80803928aee4356845252ac6b662d5c72c29903813eJake Slack c++; 80903928aee4356845252ac6b662d5c72c29903813eJake Slack } 81003928aee4356845252ac6b662d5c72c29903813eJake Slack } 81103928aee4356845252ac6b662d5c72c29903813eJake Slack 81203928aee4356845252ac6b662d5c72c29903813eJake Slack /* -------------------------------------------------- */ 81303928aee4356845252ac6b662d5c72c29903813eJake Slack public void remove() 81403928aee4356845252ac6b662d5c72c29903813eJake Slack { 81503928aee4356845252ac6b662d5c72c29903813eJake Slack throw new UnsupportedOperationException("Not supported"); 81603928aee4356845252ac6b662d5c72c29903813eJake Slack } 81703928aee4356845252ac6b662d5c72c29903813eJake Slack }; 81803928aee4356845252ac6b662d5c72c29903813eJake Slack } 81903928aee4356845252ac6b662d5c72c29903813eJake Slack } 82003928aee4356845252ac6b662d5c72c29903813eJake Slack} 821