View Javadoc
1   /*
2    * Copyright 2007 Edward Kuns
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   *
14   * $Id: XMLBuilder.java 0000 2007-01-11 00:00:00Z ekuns $
15   */
16  package org.castor.xmlctf.xmldiff.xml;
17  
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.Map;
21  import java.util.Stack;
22  
23  import org.castor.xmlctf.xmldiff.xml.nodes.Attribute;
24  import org.castor.xmlctf.xmldiff.xml.nodes.Element;
25  import org.castor.xmlctf.xmldiff.xml.nodes.Namespace;
26  import org.castor.xmlctf.xmldiff.xml.nodes.ParentNode;
27  import org.castor.xmlctf.xmldiff.xml.nodes.ProcessingInstruction;
28  import org.castor.xmlctf.xmldiff.xml.nodes.Root;
29  import org.castor.xmlctf.xmldiff.xml.nodes.Text;
30  import org.xml.sax.Attributes;
31  import org.xml.sax.ContentHandler;
32  import org.xml.sax.Locator;
33  import org.xml.sax.SAXException;
34  
35  /**
36   * A ContentHandler implementation that builds a tree of XMLNodes.
37   *
38   * @author <a href="mailto:edward.kuns@aspect.com">Edward Kuns</a>
39   * @version $Revision: 0000 $ $Date: 2007-01-11 00:00:00 -0600 (Thu, 11 Jan 2007) $
40   * @since Castor 1.1
41   */
42  public class XMLContentHandler implements ContentHandler {
43  
44    /** The current node stack. */
45    private final Stack _nodeStack = new Stack();
46    /** Root BaseNode, the BaseNode being created.. */
47    private final Root _root = new Root();
48  
49    /** SAX document Locator, set by a SAX Parser. */
50    private Locator _locator = null;
51    /** The current node to which we are adding content. */
52    private ParentNode _currentNode = null;
53    /** Keeps track of all URL mapping prefixes currently defined. */
54    private Map _prefixes = new HashMap();
55  
56    /**
57     * Creates a new XMLBuilder.
58     */
59    public XMLContentHandler() {
60      _nodeStack.push(_root);
61      _currentNode = _root;
62    }
63  
64    /**
65     * Creates a new text node from incoming characters.
66     *
67     * @param chars The character array containing the XML content
68     * @param start First index of character for our new Text node
69     * @param length count of characters for our Text node.
70     * @throws org.xml.sax.SAXException never
71     */
72    public void characters(final char[] chars, final int start, final int length)
73        throws org.xml.sax.SAXException {
74      _currentNode.addChild(new Text(new String(chars, start, length)));
75    }
76  
77    /**
78     * Signals the end of the document.
79     * 
80     * @throws org.xml.sax.SAXException never
81     */
82    public void endDocument() throws org.xml.sax.SAXException {
83      // Nothing to do
84    }
85  
86    /**
87     * Signals the end of an Element.
88     * 
89     * @param uri The namespace URI
90     * @param name the local name of the element.
91     * @param qName the qualified naem of the element
92     * @throws org.xml.sax.SAXException if we have a mismatched end element tag
93     */
94    public void endElement(final String uri, final String name, final String qName)
95        throws org.xml.sax.SAXException {
96      final String localName = name;
97  
98      final int idx = qName.indexOf(':');
99      final String prefix = (idx >= 0) ? qName.substring(0, idx) : "";
100 
101     // Check the prefix to make sure it is appropriate
102     String uriOfPrefix = _currentNode.getNamespaceURI(prefix);
103     String uriOfElement = _currentNode.getNamespaceURI();
104     if ((uriOfPrefix == null ^ uriOfElement == null)
105         || (uriOfPrefix != null && !uriOfPrefix.equals(uriOfElement))) {
106       throw new org.xml.sax.SAXException("In Element " + qName + ", URI of prefix " + uriOfPrefix
107           + " does not match URI of Element " + uriOfElement);
108     }
109 
110     String cName = _currentNode.getLocalName();
111     if (!cName.equals(localName)) {
112       String err =
113           "Element end tag mismatch:  expecting </" + cName + "> but recieved </" + localName + ">";
114       throw new SAXException(err);
115     }
116 
117     _nodeStack.pop();
118     _currentNode = (ParentNode) _nodeStack.peek();
119   }
120 
121   /**
122    * Signals the end of prefix mapping.
123    * 
124    * @param prefix The namespace prefix mapping that is ending
125    * @throws org.xml.sax.SAXException never
126    */
127   public void endPrefixMapping(final String prefix) throws SAXException {
128     _prefixes.remove(prefix);
129   }
130 
131   /**
132    * Returns the Root node.
133    * 
134    * @return the root node.
135    */
136   public Root getRoot() {
137     return _root;
138   }
139 
140   /**
141    * Ignores ignorable whitespace.
142    *
143    * @param chars The character array containing the XML content
144    * @param start First index of the ignorable whitespace
145    * @param length count of characters for the ignorable whitespace
146    * @throws org.xml.sax.SAXException never
147    */
148   public void ignorableWhitespace(final char[] chars, final int start, final int length)
149       throws org.xml.sax.SAXException {
150     // Deliberately ignore -- we don't care
151   }
152 
153   /**
154    * Creates a new Processing Instruction node.
155    *
156    * @param target the target of the processing instruction
157    * @param data the content of the processing instruction
158    * @throws org.xml.sax.SAXException never
159    */
160   public void processingInstruction(final String target, final String data)
161       throws org.xml.sax.SAXException {
162     ProcessingInstruction pi = new ProcessingInstruction(target, data);
163     _currentNode.addChild(pi);
164   }
165 
166   /**
167    * Configures the Locator we will use.
168    *
169    * @param locator the Locator used by this DocumentHandler.
170    */
171   public void setDocumentLocator(final Locator locator) {
172     _locator = locator;
173   }
174 
175   /**
176    * Gives notification about a skipped Entity during XML parsing.
177    * 
178    * @param name the name of the skipped entity.
179    */
180   public void skippedEntity(final String name) {
181     // Nothing to do
182   }
183 
184   /**
185    * Signals the beginning of the document.
186    * 
187    * @throws org.xml.sax.SAXException never
188    */
189   public void startDocument() throws SAXException {
190     // Nothing to do
191   }
192 
193   /**
194    * Signals the beginning of an Element node.
195    *
196    * @param uri The namespace URI
197    * @param name the local name of the element.
198    * @param qName the qualified naem of the element
199    * @param atts a list of attributes for this Element
200    * @throws org.xml.sax.SAXException If we are not given an element name.
201    */
202   public void startElement(final String uri, final String name, final String qName,
203       final Attributes atts) throws org.xml.sax.SAXException {
204     if (qName == null) {
205       throw new SAXException("No Element name given");
206     }
207 
208     final String prefix;
209     final String localName;
210 
211     int idx = qName.indexOf(':');
212     if (idx >= 0) {
213       prefix = qName.substring(0, idx);
214       localName = qName.substring(idx + 1);
215     } else {
216       prefix = "";
217       localName = qName;
218     }
219 
220     Element element = new Element(null, localName);
221 
222     if (_locator != null) {
223       element.setLocation(new Location(_locator));
224     }
225 
226     _currentNode.addChild(element);
227 
228     // Add all current namespaces to this element
229     for (Iterator i = _prefixes.entrySet().iterator(); i.hasNext();) {
230       Map.Entry me = (Map.Entry) i.next();
231       element.addNamespace(new Namespace((String) me.getKey(), (String) me.getValue()));
232     }
233 
234     // Then add all attributes
235     if (atts != null && atts.getLength() > 0) {
236       for (int i = 0; i < atts.getLength(); i++) {
237         String attName = atts.getQName(i);
238         String ns = null;
239         idx = attName.indexOf(':');
240         if (idx > 0) {
241           ns = element.getNamespaceURI(attName.substring(0, idx));
242           attName = attName.substring(idx + 1);
243         }
244         element.addAttribute(new Attribute(ns, attName, atts.getValue(i)));
245       }
246     }
247 
248     // Set the namespace on this Element, if one is explicit or defaulted
249     if (prefix != null && prefix.length() > 0) {
250       String namespace = element.getNamespaceURI(prefix);
251       element.setNamespace(namespace);
252     } else {
253       String namespace = element.getNamespaceURI("");
254       if (namespace != null) {
255         element.setNamespace(namespace);
256       }
257     }
258 
259     _nodeStack.push(element);
260     _currentNode = element;
261   }
262 
263   /**
264    * Begins the scope of a prefix-URI Namespace mapping.
265    * 
266    * @param prefix The namespace prefix mapping that is ending
267    * @param uri The namespace URI
268    * @throws org.xml.sax.SAXException never
269    */
270   public void startPrefixMapping(final String prefix, final String uri) {
271     _prefixes.put(prefix, uri);
272   }
273 
274 }