View Javadoc
1   /*
2    * Redistribution and use of this software and associated documentation ("Software"), with or
3    * without modification, are permitted provided that the following conditions are met:
4    *
5    * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
6    * must also contain a copy of this document.
7    *
8    * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
9    * conditions and the following disclaimer in the documentation and/or other materials provided with
10   * the distribution.
11   *
12   * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
13   * without prior written permission of Intalio, Inc. For written permission, please contact
14   * info@exolab.org.
15   *
16   * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
17   * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
18   * Intalio, Inc.
19   *
20   * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
21   *
22   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
23   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
24   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
25   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
29   * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30   *
31   * Copyright 2001-2003 (C) Intalio, Inc. All Rights Reserved.
32   *
33   * $Id$ Date Author Changes 04/06/2001 Arnaud Blandin Created
34   */
35  package org.exolab.castor.xml.util;
36  
37  import java.util.Stack;
38  
39  import org.exolab.castor.types.AnyNode;
40  import org.exolab.castor.xml.Namespaces;
41  import org.exolab.castor.xml.NamespacesStack;
42  import org.xml.sax.AttributeList;
43  import org.xml.sax.Attributes;
44  import org.xml.sax.ContentHandler;
45  import org.xml.sax.DocumentHandler;
46  import org.xml.sax.ErrorHandler;
47  import org.xml.sax.Locator;
48  import org.xml.sax.SAXException;
49  import org.xml.sax.SAXParseException;
50  
51  /**
52   * This class is a SAX Content Handler that build an AnyNode from a stream of SAX events (either
53   * SAX1 for compatibility or SAX2)
54   * 
55   * @author <a href="blandin@intalio.com">Arnaud Blandin</a>
56   * @version $Revision$ $Date: 2006-04-29 09:44:19 -0600 (Sat, 29 Apr 2006) $
57   */
58  public class SAX2ANY implements ContentHandler, DocumentHandler, ErrorHandler {
59    /**
60     * Prefix used by namespace declaration.
61     */
62    private final static String XMLNS_PREFIX = "xmlns";
63    private final static int XMLNS_PREFIX_LENGTH = XMLNS_PREFIX.length() + 1; // prefix + ':'
64  
65    /**
66     * The starting node.
67     */
68    private AnyNode _startingNode;
69  
70    /**
71     * The current AnyNode we are building
72     */
73    private AnyNode _node;
74  
75    /**
76     * A stack to store all the nodes we are creating
77     */
78    private Stack<AnyNode> _nodeStack = new Stack<>();
79  
80    /**
81     * A stack to store the namespaces declaration
82     */
83    private Stack<AnyNode> _namespaces = new Stack<>();
84  
85    /**
86     * A flag indicating if the SAX2 Parser is processing the namespace or not. 'true' will indicate
87     * that the code of this Content Handler will have to deal with Namespaces.This is the default
88     * value.
89     */
90    private boolean _processNamespace = true;
91  
92    /**
93     * A flag that indicates we are in a character section.
94     */
95    private boolean _character = false;
96  
97    /**
98     * Represents the namespaces stack.
99     */
100   private NamespacesStack namespacesStack;
101 
102   private boolean _wsPreserve = false;
103 
104   /**
105    * Default constructor
106    */
107   public SAX2ANY() {
108     super();
109     init();
110   }
111 
112   /**
113    * Constructs a SAX2ANY given a namespace context.
114    *
115    * @param namespacesStack the namespace stack
116    * @param wsPreserve if white spaces whould be preserved
117    */
118   public SAX2ANY(NamespacesStack namespacesStack, boolean wsPreserve) {
119     this.namespacesStack = namespacesStack;
120     _wsPreserve = wsPreserve;
121     init();
122   }
123 
124   private void init() {
125     if (this.namespacesStack == null)
126       this.namespacesStack = new NamespacesStack();
127   }
128 
129   /**
130    * Sets the document locator of the current parsed inputsource
131    * 
132    * @param locator the Locator of the current parsed inputsource
133    */
134   public void setDocumentLocator(final Locator locator) {}
135 
136   // ----------------- NOT IMPLEMENTED --------------
137   // we don't need to implement these methods since
138   // we are only dealing with xml fragments
139   public void startDocument() throws SAXException {}
140 
141   public void endDocument() throws SAXException {}
142 
143   public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
144     return;
145   }
146 
147   public void processingInstruction(String target, String data) throws SAXException {
148     return;
149   }
150 
151   public void skippedEntity(String name) throws SAXException {
152     return;
153   }
154   // -------------------------------------------------
155 
156   // --Namespace related (SAX2 Events)
157   public void startPrefixMapping(String prefix, String uri) throws SAXException {
158     AnyNode temp = new AnyNode(AnyNode.NAMESPACE, null, prefix, uri, null);
159     _namespaces.push(temp);
160     if (_processNamespace) {
161       namespacesStack.addNewNamespaceScope();
162       _processNamespace = true;
163     }
164     namespacesStack.addNamespace(prefix, uri);
165   }
166 
167   public void endPrefixMapping(String prefix) throws SAXException {
168     namespacesStack.removeNamespace(prefix);
169   }
170 
171   // --startElement methods SAX1 and SAX2
172   /**
173    * Implementation of {@link org.xml.sax.DocumentHandler#startElement}
174    */
175   public void startElement(String name, AttributeList atts) throws SAXException {
176     _character = false;
177     String qName;
178     String value;
179     AnyNode tempNode = null;
180 
181     // Namespace handling code to be moved once we integrate
182     // the new event API
183     ///////////////// NAMESPACE HANDLING/////////////////////
184     namespacesStack.addNewNamespaceScope();
185     String prefix = "";
186     String namespaceURI = null;
187     int idx = name.indexOf(':');
188     if (idx >= 0) {
189       prefix = name.substring(0, idx);
190     }
191     namespaceURI = namespacesStack.getNamespaceURI(prefix);
192     // --Overhead here since we process attributes twice
193     for (int i = 0; i < atts.getLength(); ++i) {
194       qName = atts.getName(i);
195       value = atts.getValue(i);
196       String nsPrefix = null;
197 
198       if (qName.startsWith(XMLNS_PREFIX)) {
199         // handles namespace declaration
200         // Extract the prefix if any
201         nsPrefix = (qName.equals(XMLNS_PREFIX)) ? null : qName.substring(XMLNS_PREFIX_LENGTH);
202         tempNode = new AnyNode(AnyNode.NAMESPACE, getLocalPart(qName), nsPrefix, value, null);
203         namespacesStack.addNamespace(nsPrefix, value);
204         _namespaces.push(tempNode);
205         if (prefix.equals(nsPrefix))
206           namespaceURI = value;
207       }
208     }
209     //////////////////////// END OF NAMESPACE HANDLING///////////////
210 
211     createNodeElement(namespaceURI, getLocalPart(name), name);
212     while (!_namespaces.empty()) {
213       tempNode = (AnyNode) _namespaces.pop();
214       _node.addNamespace(tempNode);
215     }
216 
217     // process attributes
218     for (int i = 0; i < atts.getLength(); ++i) {
219 
220       qName = atts.getName(i);
221       value = atts.getValue(i);
222 
223       // Namespace handling already done
224       if (!qName.startsWith(XMLNS_PREFIX)) {
225         tempNode = new AnyNode(AnyNode.ATTRIBUTE, getLocalPart(qName), null, null, value);
226         _node.addAttribute(tempNode);
227       }
228     }
229     tempNode = null;
230   }
231 
232   /**
233    * Implementation of {@link org.xml.sax.ContentHandler#startElement}
234    */
235   public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
236       throws SAXException {
237     AnyNode tempNode;
238 
239     // --SAX2 Parser has not processed the namespaces so we need to do it.
240     if (_processNamespace) {
241       // Namespace handling code to be moved once we integrate
242       // the new event API
243       ///////////////// NAMESPACE HANDLING/////////////////////
244       namespacesStack.addNewNamespaceScope();
245       String prefix = "";
246       int idx = qName.indexOf(':');
247       if (idx >= 0) {
248         prefix = qName.substring(0, idx);
249       }
250       namespaceURI = namespacesStack.getNamespaceURI(prefix);
251       // --Overhead here since we process attributes twice
252       for (int i = 0; i < atts.getLength(); ++i) {
253         String attrqName = atts.getQName(i);
254         String value = atts.getValue(i);
255         String nsPrefix = null;
256         // handles namespace declaration
257         if (attrqName.startsWith(XMLNS_PREFIX)) {
258           // Extract the prefix if any
259           nsPrefix =
260               (attrqName.equals(XMLNS_PREFIX)) ? null : attrqName.substring(XMLNS_PREFIX_LENGTH);
261           tempNode = new AnyNode(AnyNode.NAMESPACE, getLocalPart(attrqName), nsPrefix, value, null);
262           namespacesStack.addNamespace(nsPrefix, value);
263           _namespaces.push(tempNode);
264           if (prefix.equals(nsPrefix))
265             namespaceURI = value;
266         }
267       }
268       //////////////////////// END OF NAMESPACE HANDLING///////////////
269     }
270 
271     // create element
272     createNodeElement(namespaceURI, localName, qName);
273 
274     // process attributes
275     for (int i = 0; i < atts.getLength(); ++i) {
276 
277       String uri = atts.getURI(i);
278       String attqName = atts.getQName(i);
279       String value = atts.getValue(i);
280       String prefix = null;
281 
282       // -- skip namespace declarations? (handled above)
283       if (_processNamespace)
284         if (attqName.startsWith(XMLNS_PREFIX))
285           continue;
286 
287       // --attribute namespace prefix?
288       if ((attqName.length() != 0) && (attqName.indexOf(':') != -1))
289         prefix = attqName.substring(0, attqName.indexOf(':'));
290 
291       // --namespace not yet processed?
292       if (_processNamespace) {
293         // attribute namespace
294         if (prefix != null)
295           uri = namespacesStack.getNamespaceURI(prefix);
296       }
297       // --add attribute
298       tempNode = new AnyNode(AnyNode.ATTRIBUTE, getLocalPart(attqName), prefix, uri, value);
299       _node.addAttribute(tempNode);
300     }
301 
302     // --empty the namespace stack and add
303     // --the namespace nodes to the current node.
304     while (!_namespaces.empty()) {
305       tempNode = (AnyNode) _namespaces.pop();
306       _node.addNamespace(tempNode);
307     }
308     tempNode = null;
309   }
310 
311   // --endElement methods SAX1 and SAX2
312   public void endElement(String name) throws SAXException {
313     int idx = name.indexOf(':');
314     String prefix = (idx >= 0) ? name.substring(0, idx) : "";
315     String namespaceURI = namespacesStack.getNamespaceURI(prefix);
316     endElement(namespaceURI, getLocalPart(name), name);
317     namespacesStack.removeNamespaceScope();
318   }
319 
320   public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
321     _character = false;
322     String name = null;
323     // -- if namespace processing is disabled then the localName might be null, in that case
324     // -- we use the QName
325     if (localName != null && localName.length() > 0) {
326       name = localName;
327     } else {
328       name = getLocalPart(qName);
329     }
330 
331     // --if it is the starting element just returns
332     if (_startingNode.getLocalName().equals(name) && _nodeStack.empty())
333       return;
334 
335     // --else just add the node we have built to the previous node
336     _node = (AnyNode) _nodeStack.pop();
337 
338     // -- if the stack is empty, we have a new child for the root node
339     // -- or a new sibling for the first child of the root node
340     if (_nodeStack.empty()) {
341       _startingNode.addChild(_node);
342       _node = _startingNode;
343     } else {
344       AnyNode previousNode = (AnyNode) _nodeStack.peek();
345       previousNode.addChild(_node);
346       // --the node processing is finished -> come back to the previous node
347       _node = previousNode;
348     }
349   }
350 
351   public void characters(char[] ch, int start, int length) throws SAXException {
352     // create a Text Node
353     String temp = new String(ch, start, length);
354     // skip whitespaces
355     if (isWhitespace(temp) && !_wsPreserve && !_character)
356       return;
357     AnyNode tempNode = new AnyNode(AnyNode.TEXT, null, null, null, temp);
358     _node.addChild(tempNode);
359     _character = true;
360   }
361 
362 
363   /**************************************************************************/
364   // implementation of ErrorHandler
365   public void warning(SAXParseException e) throws SAXException {
366     String err = "SAX2ANY warning\n" + "Line : " + e.getLineNumber() + '\n' + "URI : "
367         + e.getSystemId() + '\n' + e.getMessage();
368     throw new SAXException(err, e);
369   } // warning
370 
371   public void error(SAXParseException e) throws SAXException {
372     String err = "SAX2ANY Error \n" + "Line : " + e.getLineNumber() + '\n' + "URI : "
373         + e.getSystemId() + '\n' + e.getMessage();
374     throw new SAXException(err, e);
375   } // error
376 
377   public void fatalError(SAXParseException e) throws SAXException {
378     String err = "SAX2ANY Fatal Error \n" + "Line : " + e.getLineNumber() + '\n' + "URI : "
379         + e.getSystemId() + '\n' + e.getMessage();
380     throw new SAXException(err, e);
381   } // fatalError
382 
383   /*************************************************************************/
384 
385   // Utility methods
386   public AnyNode getStartingNode() {
387     return _startingNode;
388   }
389 
390   /**
391    * Checks the given String to determine if it only contains whitespace.
392    *
393    * @param string the String to check
394    * @return true if the only whitespace characters were found in the given StringBuffer
395    */
396   private boolean isWhitespace(String string) {
397     for (int i = 0; i < string.length(); i++) {
398       char ch = string.charAt(i);
399       switch (ch) {
400         case ' ':
401         case '\n':
402         case '\t':
403         case '\r':
404           break;
405         default:
406           return false;
407       }
408     }
409     return true;
410   } // -- isWhitespace
411 
412   /**
413    * Returns the local part of the given NCName. The local part is anything following the namespace
414    * prefix. If there is no namespace prefix the returned name will be the same as the given name.
415    * 
416    * @return the local part of the given NCName.
417    */
418   private String getLocalPart(String ncName) {
419     int idx = ncName.indexOf(':');
420     if (idx >= 0)
421       return ncName.substring(idx + 1);
422     return ncName;
423   } // -- getLocalPart
424 
425   private void createNodeElement(String namespaceURI, String localName, String qName) {
426 
427     String prefix = null;
428     // retrieves the prefix if any
429     if (namespaceURI != null) {
430       prefix = namespacesStack.getNamespacePrefix(namespaceURI);
431     } else if (qName != null) {
432       if ((qName.length() != 0) && (qName.indexOf(':') != -1))
433         prefix = qName.substring(0, qName.indexOf(':'));
434     }
435 
436     String name = null;
437     // -- if namespace processing is disabled then the localName might be null, in that case
438     // -- we use the localpart of the QName
439     if (localName != null && localName.length() > 0)
440       name = localName;
441     else
442       name = getLocalPart(qName);
443 
444     // creates the starting ELEMENT node
445     // or a default ELEMENT node
446     if ((_nodeStack.empty()) && (_startingNode == null)) {
447       _startingNode = new AnyNode(AnyNode.ELEMENT, name, prefix, namespaceURI, null);
448       _node = _startingNode;
449     } else {
450       _node = new AnyNode(AnyNode.ELEMENT, name, prefix, namespaceURI, null);
451       // push the node in the stack
452       _nodeStack.push(_node);
453     }
454   }
455 
456 }