/* * Copyright 2007 Edward Kuns * * 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. * * $Id: XMLBuilder.java 0000 2007-01-11 00:00:00Z ekuns $ */ package org.castor.xmlctf.xmldiff.xml; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Stack; import org.castor.xmlctf.xmldiff.xml.nodes.Attribute; import org.castor.xmlctf.xmldiff.xml.nodes.Element; import org.castor.xmlctf.xmldiff.xml.nodes.Namespace; import org.castor.xmlctf.xmldiff.xml.nodes.ParentNode; import org.castor.xmlctf.xmldiff.xml.nodes.ProcessingInstruction; import org.castor.xmlctf.xmldiff.xml.nodes.Root; import org.castor.xmlctf.xmldiff.xml.nodes.Text; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; /** * A ContentHandler implementation that builds a tree of XMLNodes. * * @author <a href="mailto:edward.kuns@aspect.com">Edward Kuns</a> * @version $Revision: 0000 $ $Date: 2007-01-11 00:00:00 -0600 (Thu, 11 Jan 2007) $ * @since Castor 1.1 */ public class XMLContentHandler implements ContentHandler { /** The current node stack. */ private final Stack _nodeStack = new Stack(); /** Root BaseNode, the BaseNode being created.. */ private final Root _root = new Root(); /** SAX document Locator, set by a SAX Parser. */ private Locator _locator = null; /** The current node to which we are adding content. */ private ParentNode _currentNode = null; /** Keeps track of all URL mapping prefixes currently defined. */ private Map _prefixes = new HashMap(); /** * Creates a new XMLBuilder. */ public XMLContentHandler() { _nodeStack.push(_root); _currentNode = _root; } /** * Creates a new text node from incoming characters. * * @param chars The character array containing the XML content * @param start First index of character for our new Text node * @param length count of characters for our Text node. * @throws org.xml.sax.SAXException never */ public void characters(final char[] chars, final int start, final int length) throws org.xml.sax.SAXException { _currentNode.addChild(new Text(new String(chars, start, length))); } /** * Signals the end of the document. * @throws org.xml.sax.SAXException never */ public void endDocument() throws org.xml.sax.SAXException { // Nothing to do } /** * Signals the end of an Element. * @param uri The namespace URI * @param name the local name of the element. * @param qName the qualified naem of the element * @throws org.xml.sax.SAXException if we have a mismatched end element tag */ public void endElement(final String uri, final String name, final String qName) throws org.xml.sax.SAXException { final String localName = name; final int idx = qName.indexOf(':'); final String prefix = (idx >= 0) ? qName.substring(0, idx) : ""; // Check the prefix to make sure it is appropriate String uriOfPrefix = _currentNode.getNamespaceURI(prefix); String uriOfElement = _currentNode.getNamespaceURI(); if ((uriOfPrefix == null ^ uriOfElement == null) || (uriOfPrefix != null && !uriOfPrefix.equals(uriOfElement))) { throw new org.xml.sax.SAXException("In Element " + qName + ", URI of prefix " + uriOfPrefix + " does not match URI of Element " + uriOfElement); } String cName = _currentNode.getLocalName(); if (!cName.equals(localName)) { String err = "Element end tag mismatch: expecting </" + cName + "> but recieved </" + localName + ">"; throw new SAXException(err); } _nodeStack.pop(); _currentNode = (ParentNode) _nodeStack.peek(); } /** * Signals the end of prefix mapping. * @param prefix The namespace prefix mapping that is ending * @throws org.xml.sax.SAXException never */ public void endPrefixMapping(final String prefix) throws SAXException { _prefixes.remove(prefix); } /** * Returns the Root node. * @return the root node. */ public Root getRoot() { return _root; } /** * Ignores ignorable whitespace. * * @param chars The character array containing the XML content * @param start First index of the ignorable whitespace * @param length count of characters for the ignorable whitespace * @throws org.xml.sax.SAXException never */ public void ignorableWhitespace(final char[] chars, final int start, final int length) throws org.xml.sax.SAXException { // Deliberately ignore -- we don't care } /** * Creates a new Processing Instruction node. * * @param target the target of the processing instruction * @param data the content of the processing instruction * @throws org.xml.sax.SAXException never */ public void processingInstruction(final String target, final String data) throws org.xml.sax.SAXException { ProcessingInstruction pi = new ProcessingInstruction(target, data); _currentNode.addChild(pi); } /** * Configures the Locator we will use. * * @param locator the Locator used by this DocumentHandler. */ public void setDocumentLocator(final Locator locator) { _locator = locator; } /** * Gives notification about a skipped Entity during XML parsing. * @param name the name of the skipped entity. */ public void skippedEntity(final String name) { // Nothing to do } /** * Signals the beginning of the document. * @throws org.xml.sax.SAXException never */ public void startDocument() throws SAXException { // Nothing to do } /** * Signals the beginning of an Element node. * * @param uri The namespace URI * @param name the local name of the element. * @param qName the qualified naem of the element * @param atts a list of attributes for this Element * @throws org.xml.sax.SAXException If we are not given an element name. */ public void startElement(final String uri, final String name, final String qName, final Attributes atts) throws org.xml.sax.SAXException { if (qName == null) { throw new SAXException("No Element name given"); } final String prefix; final String localName; int idx = qName.indexOf(':'); if (idx >= 0) { prefix = qName.substring(0, idx); localName = qName.substring(idx + 1); } else { prefix = ""; localName = qName; } Element element = new Element(null, localName); if (_locator != null) { element.setLocation(new Location(_locator)); } _currentNode.addChild(element); // Add all current namespaces to this element for (Iterator i = _prefixes.entrySet().iterator(); i.hasNext(); ) { Map.Entry me = (Map.Entry) i.next(); element.addNamespace(new Namespace((String)me.getKey(), (String) me.getValue())); } // Then add all attributes if (atts != null && atts.getLength() > 0) { for (int i = 0; i < atts.getLength(); i++) { String attName = atts.getQName(i); String ns = null; idx = attName.indexOf(':'); if (idx > 0) { ns = element.getNamespaceURI(attName.substring(0, idx)); attName = attName.substring(idx + 1); } element.addAttribute(new Attribute(ns, attName, atts.getValue(i))); } } // Set the namespace on this Element, if one is explicit or defaulted if (prefix != null && prefix.length() > 0) { String namespace = element.getNamespaceURI(prefix); element.setNamespace(namespace); } else { String namespace = element.getNamespaceURI(""); if (namespace != null) { element.setNamespace(namespace); } } _nodeStack.push(element); _currentNode = element; } /** * Begins the scope of a prefix-URI Namespace mapping. * @param prefix The namespace prefix mapping that is ending * @param uri The namespace URI * @throws org.xml.sax.SAXException never */ public void startPrefixMapping(final String prefix, final String uri) { _prefixes.put(prefix, uri); } }