View Javadoc

1   /*
2    * Copyright (c) 2002 Peter Antman, Teknik i Media  <peter.antman@tim.se>
3    *
4    * $Id: DocumentUtil.java,v 1.1.1.1 2004/05/19 12:07:31 pra Exp $
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2 of the License, or (at your option) any later version
10   * 
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   */
20  package org.backsource.utils.xml;
21  import java.io.Reader;
22  import java.io.StringReader;
23  import java.io.StringWriter;
24  import java.io.BufferedReader;
25  import java.io.InputStream;
26  import java.io.File;
27  import java.io.FileReader;
28  import java.io.IOException;
29  
30  import javax.xml.parsers.*;
31  import javax.xml.transform.TransformerFactory;
32  import javax.xml.transform.Transformer;
33  import javax.xml.transform.SourceLocator;
34  import javax.xml.transform.TransformerException;
35  import javax.xml.transform.stream.StreamSource;
36  import javax.xml.transform.stream.StreamResult;
37  import javax.xml.transform.OutputKeys;
38  import javax.xml.transform.dom.DOMSource;
39  
40  import org.w3c.dom.Node;
41  import org.w3c.dom.Document;
42  import org.w3c.dom.DocumentType;
43  import org.w3c.dom.DocumentFragment;
44  import org.w3c.dom.Element;
45  import org.w3c.dom.NodeList;
46  import org.xml.sax.SAXException;
47  import org.xml.sax.InputSource;
48  import org.xml.sax.XMLReader;
49  /***
50   * <p>Util to handle stuff relating to documents and nodes.
51   *
52   *
53   * @author <a href="mailto:pra@tim.se">Peter Antman</a>
54   * @version $Revision: 1.1.1.1 $
55   */
56  
57  public class DocumentUtil{
58     public DocumentUtil (){
59        
60     }
61  
62     /***
63      * <p>Get the serialized XML from node.
64      *
65      * @return the serialized XML from node, wich will have content encoding iso-8859-1, it will be indented and have a prolog.
66      */
67     public static String  getXml(Node node)throws XmlException {
68        return getXml(node, true, "iso-8859-1", false);
69     }
70  
71  
72     /***
73      * Return the string XML rep of node.
74      *
75      *
76      * @param node the node to serialize.
77      * @param indent indent output if true.
78      * @param encoding the encoding to use, if null iso-8859-1
79      * @param omitDecl if true the xml prolog is omitted, which may be handly if the XML is to be used as a fragment.
80      */
81     public static String  getXml(Node node, boolean indent, String encoding, boolean omitDecl)  throws XmlException{
82        try {
83           // Get document and check if it contains any DOCTYPE
84           Document d = null;
85           DocumentType dt =null;
86           if ( node instanceof Document) {
87              d = (Document)node;
88           } else {
89              d = node.getOwnerDocument();
90           } // end of else
91           if (d != null ) {         
92              dt= d.getDoctype();
93           } // end of if ()
94  
95                            
96           TransformerFactory tfactory = TransformerFactory.newInstance();
97           Transformer serializer = tfactory.newTransformer();
98  
99           Resolver res = ResolverContext.get();
100          if ( res != null ) {
101              serializer.setURIResolver( res.getURIResolver());
102          } // end of if ()
103          
104          
105          
106          serializer.setOutputProperty(OutputKeys.METHOD, "xml");
107          if ( indent) {
108             serializer.setOutputProperty(OutputKeys.INDENT, "yes");
109          } 
110          if ( encoding != null) {
111             serializer.setOutputProperty(OutputKeys.ENCODING, encoding);
112          } else {
113             serializer.setOutputProperty(OutputKeys.ENCODING, "iso-8859-1");
114          } 
115          if ( omitDecl) {
116             serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
117          }
118          if ( dt != null) {
119             if ( dt.getPublicId()!= null ) {
120                serializer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC , dt.getPublicId());
121             } // end of if ()
122             if ( dt.getSystemId() != null) {
123                serializer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM , dt.getSystemId());
124             } // end of if ()
125             
126          } // end of if ()
127          
128          
129 
130          StringWriter wr = new StringWriter();
131          serializer.transform(new DOMSource(node), 
132                               new StreamResult(wr));
133          
134          return wr.toString();
135          
136       } catch (TransformerException ex) {
137          String reason = ex.getMessage();
138          SourceLocator loc = ex.getLocator();
139          if (loc != null) {
140             reason = reason + " Error at line=" + loc.getLineNumber() +
141                " column=" + loc.getColumnNumber() +
142                " in systemID=" + loc.getSystemId();
143          }
144          Throwable e = ex.getException();
145          if(e instanceof org.xml.sax.SAXParseException) {
146             org.xml.sax.SAXParseException s = (org.xml.sax.SAXParseException)e;
147             reason = reason + "SAX Exception " + s.getMessage()+
148                " at line=: " +s.getLineNumber() + 
149                " column=" +s.getColumnNumber() + " in systemID" +
150                s.getSystemId();
151          }
152          throw new XmlException("Could not serialize XML: " + reason,ex);
153       } 
154    }
155 
156    /***
157     * Replace oldNode with newNode and its children, importing the new node if needed.
158     */
159    public static void replace(Node oldNode, Node newNode) throws XmlException {
160       Document into = oldNode.getOwnerDocument();
161       Document from = newNode.getOwnerDocument();
162       
163       // Something is not working correctly when overwriting the root element
164       // with a fragment!
165       // So here's a special case when the node to replace is the root node.
166       if ( into.getDocumentElement() == oldNode) {
167          // Its the root node!
168          Node newTop = into.importNode(newNode, true);
169          into.replaceChild(newTop, into.getDocumentElement());
170          return;
171       } // end of if ()
172       
173       // Not a root node to replace
174       DocumentFragment frag = null;
175       if ( newNode instanceof DocumentFragment) {
176          frag = (DocumentFragment)newNode;
177       } else {
178          frag = getDocumentFragment(newNode);
179       } // end of else
180       
181       if ( !into.equals(from)) {
182          //frag = (DocumentFragment)into.importNode(frag,true);
183          frag = (DocumentFragment)importNode(into,frag);
184       } // end of if ()
185       Node parent = oldNode.getParentNode();
186       parent.replaceChild(frag,oldNode);
187       
188    }
189 
190    /***
191     * <p>Create a DocumentFragment from the XML given i string.
192     *
193     * <p>The fragment may not be wellformed XML and may contain multiple children without a root node. To only allow a document fragment to returned where all the children are elements turn forceElementTree on. Any whitespace - including new line - will be removed before trying.
194     *@param xml the string possibly containing and XML fragment. The string is trimed before conversion.
195     *@param forceElementTree the fragment is only valid if alla its children are elements, othervise an exception will be raised.
196     * @throws XMLException if the xml string was not possible to convert to a document fragment or if null.
197     * 
198     */
199    public static DocumentFragment getDocumentFragment(String xml, boolean forceElementTree) throws XmlException{
200       if ( xml == null) {
201          throw new XmlException("The xml string is not allowed to be null");
202       } // end of if ()
203       
204       Element root = getDocumentElement("<list>"+xml.trim()+"</list>");
205       DocumentFragment frag = root.getOwnerDocument().createDocumentFragment();
206       NodeList ch = root.getChildNodes();
207       int l = ch.getLength();
208       for (int i = 0;i<l;i++) {
209          Node n = (Node)ch.item(i);
210          //System.out.println("DEBUG checking node " + n.getNodeType() +":"+n.getNodeValue()+":"+n.getNodeName()+":"+n.getClass().getName()+":"+Node.TEXT_NODE);
211          if ( forceElementTree && n.getNodeType() != Node.ELEMENT_NODE) {
212             //Ok, we are nice. if its a Text and only contains whitespace,let
213             // its pas throug
214             if ( n.getNodeType() == Node.TEXT_NODE && "".equals(n.getNodeValue().trim())) {
215                 ; //Its all ok
216             } else {
217 
218                throw new XmlException("Could not create document fragment: Node was not of element type and forceElementTree was turned on");
219             }
220          } // end of if ()
221          Node imported = frag.appendChild(n.cloneNode(false));
222          
223          imported = copyInto(n,imported);
224       } // end of for ()
225       // FIXME We ought to check if there is at least one element if forceElementTree is true
226       return frag;
227       
228    }
229 
230 
231    /***
232     * Create a document fragment around the node and its children, which are cloned.
233     */
234    public static DocumentFragment getDocumentFragment(Node node) throws XmlException {
235       if ( node == null) {
236          throw new XmlException("node is not allowed to be null");
237       } // end of if ()
238 
239       DocumentFragment frag = node.getOwnerDocument().createDocumentFragment();
240       
241       Node root = frag.appendChild(node.cloneNode(false));
242 
243       root = copyInto(node,root);
244 
245       //System.out.println("DEBUG: frag :" + root);
246       /*
247       for (Node n = node.getFirstChild();
248            n != null;
249            n = n.getNextSibling()) {
250          root.appendChild(n.cloneNode(false));
251       }
252       */
253       return frag;
254             
255    }
256 
257    /***
258     * Copy the child nodes and its children from  fromNode into toNode.
259     */
260    public static Node copyInto(Node fromNode, Node toNode) {
261       for (Node n = fromNode.getFirstChild();
262            n != null;
263            n = n.getNextSibling()) {
264          Node cp = n.cloneNode(false);
265          toNode.appendChild(cp);
266          copyInto(n,cp);
267       }
268       return toNode;
269    }
270 
271    /***
272     * Import node into document, make shure deep works (manually).
273     */
274     public static Node importNode(Document document, Node toImport) {
275         if (toImport != null) {
276             Node root = toImport.cloneNode(false); // no deep cloning!
277             
278             root = document.importNode(root, false);
279             
280             for (Node n = toImport.getFirstChild();
281                  n != null;
282                  n = n.getNextSibling()) {
283                 root.appendChild(document.importNode(n, true));
284             }
285             
286             return root;
287         }
288         return null;
289     }
290 
291    /***
292     * Create a new Document from the node, where node becomes the new root node and all its children the rest of the document.
293     */
294    public static Document createDocument(Node node) throws XmlException {
295       	Document newDoc = node.
296 	    getOwnerDocument().
297 	    getImplementation().
298 	    createDocument(node.getNamespaceURI(), ((Element)node).getTagName(), null);
299 	Node newTop = newDoc.importNode(node, true);
300 	newDoc.replaceChild(newTop, newDoc.getDocumentElement());
301 	return newDoc;
302    }
303 
304 
305    /***
306     * Create a DOM from string and return its root element.
307     */
308    public static Element getDocumentElement(String xml) throws XmlException {
309       Document doc = getDocument(xml);
310       return doc.getDocumentElement();
311    }
312 
313    /***
314     * Get DOM document from xml string-
315     */
316    public static Document getDocument(String xml) throws XmlException {
317       return getDocument( new BufferedReader( new StringReader(xml) ) );
318    }
319 
320    /***
321     * If reading from a file, use this, since it will set the SystemId.
322     */
323    public static Document getDocument(File file) throws XmlException {
324       if ( file == null) {
325          throw new XmlException("File not allowed to be null");
326       } // end of if ()
327       BufferedReader reader;
328       try {
329          reader = new BufferedReader( new FileReader(file));
330       } catch (java.io.FileNotFoundException e) {
331          throw new XmlException("Could not find file: " + e,e);
332       } // end of try-catch
333 
334       InputSource is = new InputSource( reader );
335       is.setSystemId( file.toString());
336        return getDocument( is);
337    }
338 
339    public static Document getDocument(Reader reader) throws XmlException {
340        return getDocument( new InputSource( reader ));
341    }
342 
343    public static Document getDocument(InputStream stream) throws XmlException {
344       return getDocument( new InputSource( stream ));
345    }
346 
347    /***
348     * FIXME: Here we could fix our own factory/prop stuff to use alternative builders
349     * than system default.
350     */
351    public static Document getDocument(InputSource is) throws XmlException {
352       Document doc = null;
353       try {
354          UtilsConfig conf = UtilsConfig.getContextConfig();
355 
356          DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
357          if ( conf != null && conf.isNamespaceAware()) {
358             docBuilderFactory.setNamespaceAware(true); 
359          } // end of if ()
360          
361          DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
362 
363          Resolver res = ResolverContext.get();
364          if ( res != null ) {
365               docBuilder.setEntityResolver( res.getEntityResolver());
366          } // end of if ()
367 
368          doc =  docBuilder.parse(is);
369          
370          if ( doc !=null) {
371             return doc;
372          } else {
373             
374             throw new XmlException("Could not get document = null");
375          } // end of else
376          
377          
378 
379       } catch (SAXException ex) {
380          String reason = ex.getMessage();
381          if(ex instanceof org.xml.sax.SAXParseException) {
382             org.xml.sax.SAXParseException s = (org.xml.sax.SAXParseException)ex;
383             reason = reason + " at line="+s.getLineNumber() + " column=" +
384                s.getColumnNumber() +
385                " in systemID" + s.getSystemId();
386          }
387          throw new XmlException("Could not parse file: " + reason,ex);
388       } catch (ParserConfigurationException ex) {
389          throw new XmlException("Could not setup parser " + ex,ex);
390       } catch (IOException ex) {
391          throw new XmlException("Could not read xml " + ex,ex);
392       }
393       
394    }
395 
396    public static void main(String[] args){
397       
398 
399    }
400 } // DocumentUtil