package org.jdom.input; import java.io.*; import java.lang.reflect.*; import java.net.*; import java.util.*; import org.jdom.*; import org.xml.sax.*; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.ext.DeclHandler; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.XMLReaderFactory; /** *

SAXHandler supports SAXBuilder

* * @author Brett McLaughlin * @author Jason Hunter */ public class SAXHandler extends DefaultHandler implements LexicalHandler, DeclHandler { private static final String CVS_ID = "@(#) $RCSfile: SAXHandler.java,v $ $Revision: 1.10 $ $Date: 2001/05/09 07:11:46 $ $Name: $"; /** Document object being built */ private Document document; /** Element stack */ private Stack stack; /** Indicator of where in the document we are */ private boolean atRoot; /** Indicator of whether we are in a DTD */ private boolean inDTD = false; /** Indicator of whether we are in a CDATA */ private boolean inCDATA = false; /** Indicator of whether we should expand entities */ private boolean expand = true; /** Indicator of whether we are actively suppressing (non-expanding) a current entity */ private boolean suppress = false; /** How many nested entities we're currently within */ private int entityDepth = 0; /** Temporary holder for namespaces that have been declared with * startPrefixMapping, but are not yet available on the element */ private LinkedList declaredNamespaces; /** The namespaces in scope and actually attached to an element */ private LinkedList availableNamespaces; private Map externalEntities; /** The JDOMFactory used for JDOM object creation */ private JDOMFactory factory; /** *

* This will set the Document to use. *

* * @param document Document being parsed. * @throws IOException when errors occur. */ public SAXHandler(Document document) throws IOException { this.document = document; atRoot = true; stack = new Stack(); declaredNamespaces = new LinkedList(); availableNamespaces = new LinkedList(); availableNamespaces.add(Namespace.XML_NAMESPACE); externalEntities = new HashMap(); } /** *

* This will set the Document to use * and the factory for JDOM object creations *

* * @param document Document being parsed. * @param factory a JDOMFactory implementation * @throws IOException when errors occur. */ public SAXHandler(Document document, JDOMFactory factory) throws IOException { this.document = document; this.factory = factory; atRoot = true; stack = new Stack(); declaredNamespaces = new LinkedList(); availableNamespaces = new LinkedList(); availableNamespaces.add(Namespace.XML_NAMESPACE); externalEntities = new HashMap(); } // These methods from the DeclHandler interface we can ignore right now public void attributeDecl(String eName, String aName, String type, String valueDefault, String value) { } /** *

* This will report character data (within an element). *

* * @param ch char[] character array with character data * @param start int index in array where data starts. * @param length int length of data. * @throws SAXException when things go wrong */ public void characters(char[] ch, int start, int length) throws SAXException { if (suppress) return; String data = new String(ch, start, length); /** * This is commented out because of some problems with * the inline DTDs that Xerces seems to have. if (!inDTD) { if (inEntity) { ((Entity)stack.peek()).setContent(data); } else { Element e = (Element)stack.peek(); e.addContent(data); } */ if (inCDATA) { ((Element)stack.peek()).addContent(factory.cdata(data)); } else { Element e = (Element)stack.peek(); e.addContent(data); } } /** *

* This reports that a comments is parsed. If not in the * DTD, this comment is added to the current JDOM * Element, or the Document itself * if at that level. *

* * @param ch ch[] array of comment characters. * @param start int index to start reading from. * @param end int index to end reading at. */ public void comment(char[] ch, int start, int end) throws SAXException { if (suppress) return; String commentText = new String(ch, start, end); if ((!inDTD) && (!commentText.equals(""))) { if (stack.empty()) { document.addContent(factory.comment(commentText)); } else { ((Element)stack.peek()).addContent( factory.comment(commentText)); } } } public void elementDecl(String name, String model) { } /** *

* Report a CDATA section - ignored in SAXBuilder. *

*/ public void endCDATA() throws SAXException { if (suppress) return; inCDATA = false; } /** *

* This signifies that the reading of the DTD is complete. *

*/ public void endDTD() throws SAXException { inDTD = false; } /** *

* Indicates the end of an element * (</[element name]>) is reached. Note that * the parser does not distinguish between empty * elements and non-empty elements, so this will occur uniformly. *

* * @param namespaceURI String URI of namespace this * element is associated with * @param localName String name of element without prefix * @param qName String name of element in XML 1.0 form * @throws SAXException when things go wrong */ public void endElement(String namespaceURI, String localName, String qName) throws SAXException { if (suppress) return; Element element = (Element)stack.pop(); if (stack.empty()) { atRoot = true; } // Remove the namespaces that this element makes available List addl = element.getAdditionalNamespaces(); if (addl.size() > 0) { availableNamespaces.removeAll(addl); } } public void endEntity(String name) throws SAXException { entityDepth--; if (entityDepth == 0) { // No way are we suppressing if not in an entity, // regardless of the "expand" value suppress = false; } } /** *

* This will add the prefix mapping to the JDOM * Document object. *

* * @param prefix String namespace prefix. * @param uri String namespace URI. */ public void endPrefixMapping(String prefix) throws SAXException { if (suppress) return; // Remove the namespace from the available list // (Should find the namespace fast because recent adds // are at the front of the list. It may not be the head // tho because endPrefixMapping calls on the same element // can come in any order.) Iterator itr = availableNamespaces.iterator(); while (itr.hasNext()) { Namespace ns = (Namespace) itr.next(); if (prefix.equals(ns.getPrefix())) { itr.remove(); return; } } } /** * This is called when the parser encounters an external entity * declaration. *

* * @param name entity name * @param publicId public id * @param systemId system id * @throws SAXException when things go wrong */ public void externalEntityDecl(String name, String publicId, String systemId) throws SAXException { // Store the public and system ids for the name externalEntities.put(name, new String[]{publicId, systemId}); } /** *

* For a given namespace prefix, this will return the * {@link Namespace} object for that prefix, * within the current scope. *

* * @param prefix namespace prefix. * @return Namespace - namespace for supplied prefix. */ private Namespace getNamespace(String prefix) { Iterator i = availableNamespaces.iterator(); while (i.hasNext()) { Namespace ns = (Namespace)i.next(); if (prefix.equals(ns.getPrefix())) { return ns; } } return Namespace.NO_NAMESPACE; } /** *

* Capture ignorable whitespace as text *

* * @param ch [] - char array of ignorable whitespace * @param start int - starting position within array * @param length int - length of whitespace after start * @throws SAXException when things go wrong */ public void ignorableWhitespace(char[] ch, int start,int length) throws SAXException { if (suppress) return; ((Element)stack.peek()).addContent(new String(ch, start, length)); } public void internalEntityDecl(String name, String value) { } /** *

* This will indicate that a processing instruction (other than * the XML declaration) has been encountered. *

* * @param target String target of PI * @param data String * This sets whether or not to expand entities during the build. * A true means to expand entities as normal content. A false means to * leave entities unexpanded as EntityRef objects. The * default is true. *

* * @param expand boolean indicating whether entity expansion * should occur. */ public void setExpandEntities(boolean expand) { this.expand = expand; } /** *

* Report a CDATA section - ignored in SAXBuilder. *

*/ public void startCDATA() throws SAXException { if (suppress) return; inCDATA = true; } /** *

* This will signify that a DTD is being parsed, and can be * used to ensure that comments and other lexical structures * in the DTD are not added to the JDOM Document * object. *

* * @param name String name of element listed in DTD * @param publicId String public ID of DTD * @param systemId String syste ID of DTD */ public void startDTD(String name, String publicId, String systemId) throws SAXException { document.setDocType( factory.docType(name, publicId, systemId)); inDTD = true; } /** *

* This reports the occurrence of an actual element. It will include * the element's attributes, with the exception of XML vocabulary * specific attributes, such as * xmlns:[namespace prefix] and * xsi:schemaLocation. *

* * @param namespaceURI String namespace URI this element * is associated with, or an empty * String * @param localName String name of element (with no * namespace prefix, if one is present) * @param qName String XML 1.0 version of element name: * [namespace prefix]:[localName] * @param atts Attributes list for this element * @throws SAXException when things go wrong */ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { if (suppress) return; Element element = null; if ((namespaceURI != null) && (!namespaceURI.equals(""))) { String prefix = ""; // Determine any prefix on the Element if (localName != qName) { int split = qName.indexOf(":"); prefix = qName.substring(0, split); } Namespace elementNamespace = Namespace.getNamespace(prefix, namespaceURI); element = factory.element(localName, elementNamespace); // Remove this namespace from those in the temp declared list if (declaredNamespaces.size() > 0) { declaredNamespaces.remove(elementNamespace); } // It's now in available scope availableNamespaces.addFirst(elementNamespace); } else { element = factory.element(localName); } // Take leftover declared namespaces and add them to this element's // map of namespaces transferNamespaces(element); // Handle attributes for (int i=0, len=atts.getLength(); i 1) { // Short cut out if we're expanding or if we're nested return; } // Ignore DTD references, and translate the standard 5 if ((!inDTD) && (!name.equals("amp")) && (!name.equals("lt")) && (!name.equals("gt")) && (!name.equals("apos")) && (!name.equals("quot"))) { if (!expand) { String pub = null; String sys = null; String[] ids = (String[]) externalEntities.get(name); if (ids != null) { pub = ids[0]; // may be null, that's OK sys = ids[1]; // may be null, that's OK } EntityRef entity = new EntityRef(name, pub, sys); ((Element)stack.peek()).addContent(entity); suppress = true; } } } /** *

* This will add the prefix mapping to the JDOM * Document object. *

* * @param prefix String namespace prefix. * @param uri String namespace URI. */ public void startPrefixMapping(String prefix, String uri) throws SAXException { if (suppress) return; Namespace ns = Namespace.getNamespace(prefix, uri); declaredNamespaces.add(ns); } /** *

* This will take the supplied {@link Element} and * transfer its namespaces to the global namespace storage. *

* * @param element Element to read namespaces from. */ private void transferNamespaces(Element element) { Iterator i = declaredNamespaces.iterator(); while (i.hasNext()) { Namespace ns = (Namespace)i.next(); i.remove(); availableNamespaces.addFirst(ns); element.addNamespaceDeclaration(ns); } } }