XMLNode.java

Go to the documentation of this file.
00001 
00002 //
00003 // XML storage classes version 1.5
00004 //
00005 // XMLNode.java
00006 //
00007 // Copyright (c) 2004, 2005, 2007, 2008, 2010, 2014 Martin Fuchs <martin-fuchs@gmx.net>
00008 //
00009 
00010 
00011 /*
00012 
00013  All rights reserved.
00014 
00015  Redistribution and use in source and binary forms, with or without
00016  modification, are permitted provided that the following conditions are met:
00017 
00018  * Redistributions of source code must retain the above copyright
00019     notice, this list of conditions and the following disclaimer.
00020  * Redistributions in binary form must reproduce the above copyright
00021     notice, this list of conditions and the following disclaimer in
00022     the documentation and/or other materials provided with the
00023     distribution.
00024 
00025  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00026  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00027  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00028  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00029  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00030  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00031  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00032  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00033  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00034  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00035  POSSIBILITY OF SUCH DAMAGE.
00036 
00037 */
00038 
00039 package de.mfuchs.xmlstorage;
00040 
00041 import java.io.IOException;
00042 import java.io.Writer;
00043 import java.util.Iterator;
00044 import java.util.LinkedList;
00045 import java.util.Map;
00046 import java.util.TreeMap;
00047 
00048 
00049 /**
00050  * XMLNode is the in memory representation of an XML node
00051  * in the XMLStorage library.
00052  */
00053 public class XMLNode extends AbstractXMLNode
00054 {
00055     public XMLNode(final String name)
00056     {
00057         _name = name;
00058     }
00059 
00060     public XMLNode(final String name, final String leading)
00061     {
00062          _name = name;
00063          _leading = leading;
00064     }
00065 
00066     public XMLNode(final XMLNode other)
00067     {
00068         assign(other);
00069     }
00070 
00071     protected String _name;
00072 
00073     public String getName()
00074     {
00075         return _name;
00076     }
00077 
00078     public String toString()
00079     {
00080         return _name;
00081     }
00082 
00083     /**
00084      * Children contains the list of immediate child nodes.
00085      */
00086     public static class Children extends LinkedList<XMLNode>
00087     {
00088         private static final long serialVersionUID = 1L;
00089 
00090         public void assign(final Children other)
00091         {
00092             this.clear();
00093 
00094             for(XMLNode node : other)
00095                 addLast(new XMLNode(node));
00096         }
00097 
00098         public void clear()
00099         {
00100             while(!isEmpty()) {
00101                 XMLNode node = getLast();
00102                 removeLast();
00103 
00104                 node.clear();
00105             }
00106         }
00107     }
00108 
00109     /**
00110      * AttributeMap is a sorted map of attributes,
00111      * used to prevent type cast warning in assign().
00112      */
00113     public static class AttributeMap extends TreeMap<String, String>
00114     {
00115         private static final long serialVersionUID = 1L;
00116     }
00117 
00118     protected Children _children = new Children();
00119     protected AttributeMap _attributes = new AttributeMap();
00120 
00121     protected String    _leading = "";
00122     protected String    _content = "";
00123     protected String    _endLeading = "";
00124     protected String    _trailing = "";
00125 
00126     public void clear()
00127     {
00128         _leading = "";
00129         _content = "";
00130         _endLeading = "";
00131         _trailing = "";
00132 
00133         _attributes.clear();
00134         _children.clear();
00135 
00136         _name = "";
00137     }
00138 
00139     public void assign(final XMLNode other)
00140     {
00141         _children.assign(other._children);
00142 
00143         _attributes = (AttributeMap) other._attributes.clone();
00144 
00145         _leading = other._leading;
00146         _content = other._content;
00147         _endLeading = other._endLeading;
00148         _trailing = other._trailing;
00149     }
00150 
00151      /// add a new child node
00152     public void addChild(XMLNode child)
00153     {
00154         _children.addLast(child);
00155     }
00156 
00157      /// read only access to an attribute
00158     public String get(final String attrName)
00159     {
00160         String found = _attributes.get(attrName);
00161 
00162         if (found != null)
00163             return found;
00164         else
00165             return "";
00166     }
00167 
00168      /// read only access to an attribute with default value
00169     public String get(final String attrName, final String def)
00170     {
00171         String found = _attributes.get(attrName);
00172 
00173         if (found != null)
00174             return found;
00175         else
00176             return def;
00177     }
00178 
00179      /// write access to an attribute
00180     public void put(final String attrName, final String value)
00181     {
00182         _attributes.put(attrName, value);
00183     }
00184 
00185      /// convenient value access in children node
00186     public String getSubattrib(final String nodeName, final String attrName)
00187     {
00188         final AbstractXMLNode node = find(nodeName);
00189 
00190         if (node != null)
00191             return node.get(attrName);
00192         else
00193             return "";
00194     }
00195 
00196     public String getSubattrib(final String nodeName, final String attrName, int n)
00197     {
00198         final AbstractXMLNode node = find(nodeName, n);
00199 
00200         if (node != null)
00201             return node.get(attrName);
00202         else
00203             return "";
00204     }
00205 
00206     /// convenient storage of distinct values in children node
00207     public void putSubattrib(final String nodeName, final String attrName, final String value)
00208     {
00209         XMLNode node = find(nodeName);
00210 
00211         if (node == null) {
00212             node = new XMLNode(nodeName);
00213             addChild(node);
00214         }
00215 
00216         node.put(attrName, value);
00217     }
00218 
00219     public void putSubattrib(final String nodeName, final String attrName, final String value, int n)
00220     {
00221         XMLNode node = find(nodeName, n);
00222 
00223         if (node == null) {
00224             node = new XMLNode(nodeName);
00225             addChild(node);
00226         }
00227 
00228         node.put(attrName, value);
00229     }
00230 
00231     public Children getChildren()
00232     {
00233         return _children;
00234     }
00235 
00236     public String getContent()
00237     {
00238         return XMLHelper.decodeXMLString(_content);
00239     }
00240 
00241     public void setContent(final String s)
00242     {
00243         setContent(s, false);
00244     }
00245 
00246     public void setContent(final String s, boolean cdata)
00247     {
00248         _content = XMLHelper.encodeXMLString(s, cdata);
00249     }
00250 
00251      // enum WRITE_MODE
00252     //TODO FORMAT_PLAIN
00253     public static final int FORMAT_SMART    = 0;    /// preserve original white space and comments if present; pretty print otherwise
00254     public static final int FORMAT_ORIGINAL = 1;    /// write XML stream preserving original white space and comments
00255     public static final int FORMAT_PRETTY   = 2;    /// pretty print node to stream without preserving original white space
00256 
00257     public void write(Writer out) throws Exception
00258     {
00259         write(out, FORMAT_SMART, 0);
00260     }
00261 
00262     public void write(Writer out, int mode) throws Exception
00263     {
00264         write(out, mode, 0);
00265     }
00266 
00267      /// write node with children tree
00268     public void write(Writer out, int mode/*=FORMAT_SMART*/, int indent/*=0*/) throws IOException
00269     {
00270         switch(mode) {
00271           case FORMAT_PRETTY:
00272             prettyWriteWorker(out, indent);
00273             break;
00274 
00275           case FORMAT_ORIGINAL:
00276             writeWorker(out, indent);
00277             break;
00278 
00279         default:     // FORMAT_SMART
00280             smartWriteWorker(out, indent);
00281         }
00282     }
00283 
00284     protected XMLNode getFirstChild()
00285     {
00286         if (!_children.isEmpty())
00287             return _children.getFirst();
00288         else
00289             return null;
00290     }
00291 
00292     /**
00293      * Find a children node by name.
00294      * @param nodeName
00295      * @return
00296      */
00297     public XMLNode find(final String nodeName)
00298     {
00299         for(XMLNode child : _children)
00300             if (child._name.equals(nodeName))
00301                 return child;
00302 
00303         return null;
00304     }
00305 
00306     /**
00307      * Find the n'th children node with tag nodeName.
00308      * @param nodeName
00309      * @param n
00310      * @return
00311      */
00312     public XMLNode find(final String nodeName, int n)
00313     {
00314         for(XMLNode child : _children)
00315             if (child._name.equals(nodeName))
00316                 if (n-- == 0)
00317                     return child;
00318 
00319         return null;
00320     }
00321 
00322      /**
00323       * Search matching child node identified by key name and an attribute value.
00324       * @param nodeName
00325       * @param attrName
00326       * @param attrValue
00327       * @return XMLNode
00328       */
00329     public XMLNode find(final String nodeName, final String attrName, final String attrValue)
00330     {
00331         for(XMLNode child : _children)
00332             if (child._name.equals(nodeName) && child.get(attrName).equals(attrValue))
00333                 return child;
00334 
00335         return null;
00336     }
00337 
00338      /**
00339       * Search matching child node identified by key name and an attribute value.
00340       * @param nodeName
00341       * @param attrName
00342       * @param attrValue
00343       * @param n
00344       * @return XMLNode
00345       */
00346     public XMLNode find(final String nodeName, final String attrName, final String attrValue, int n)
00347     {
00348         for(XMLNode node : _children)
00349             if (node._name.equals(nodeName) && node.get(attrName).equals(attrValue))
00350                 if (n-- == 0)
00351                     return node;
00352 
00353         return null;
00354     }
00355 
00356      /// XPath find function
00357     XMLNode findRelative(XPath xpath)
00358     {
00359         XMLNode node = this;
00360 
00361         for(XPathElement xelem : xpath) {
00362             node = xelem.find(node);
00363 
00364             if (node == null)
00365                 break;
00366         }
00367 
00368         return node;
00369     }
00370 
00371      /// relative XPath create function
00372     AbstractXMLNode createRelative(XPath xpath)
00373     {
00374         XMLNode node = this;
00375 
00376         for(XPathElement elem : xpath) {
00377             XMLNode child = elem.find(node);
00378 
00379             if (child == null) {
00380                 child = new XMLNode(elem._childName);
00381                 node.addChild(child);
00382 
00383                 for(Map.Entry<String, String> it : elem._mAttrAndAttr.entrySet())
00384                     put(it.getKey(), it.getValue());
00385             }
00386 
00387             node = child;
00388         }
00389 
00390         return node;
00391     }
00392 
00393 
00394     /**
00395      * Write node with children tree using original white space.
00396      * @param out
00397      * @param indent
00398      * @throws IOException
00399      */
00400     protected void writeWorker(Writer out, int indent) throws IOException
00401     {
00402         out.write(_leading);
00403         out.write('<');
00404         out.write(XMLHelper.encodeXMLString(_name));
00405 
00406         for(Map.Entry<String,String> it : _attributes.entrySet()) {
00407             String attrValue = it.getValue();
00408 
00409             if (attrValue != null) {
00410                 out.write(' ');
00411                 out.write(XMLHelper.encodeXMLString(it.getKey()));
00412                 out.write("=\"");
00413                 out.write(XMLHelper.encodeXMLString(attrValue));
00414                 out.write("\"");
00415             }
00416         }
00417 
00418 
00419         if (!_children.isEmpty() || _content.length()>0) {
00420             out.write('>');
00421             out.write(_content);
00422 
00423             for(XMLNode child: _children)
00424                 child.writeWorker(out, indent+1);
00425 
00426             out.write(_endLeading);
00427             out.write("</");
00428             out.write(XMLHelper.encodeXMLString(_name));
00429             out.write('>');
00430         } else
00431             out.write("/>");
00432 
00433         out.write(_trailing);
00434     }
00435 
00436     public static final String XML_INDENT_SPACE = "  ";
00437 
00438     protected void prettyWriteWorker(Writer out, int indent) throws IOException
00439     {
00440         for(int i=indent; i-->0; )
00441             out.write(XML_INDENT_SPACE);
00442 
00443         out.write('<');
00444         out.write(XMLHelper.encodeXMLString(_name));
00445 
00446         for(Map.Entry<String,String> it : _attributes.entrySet()) {
00447             String attrValue = it.getValue();
00448 
00449             if (attrValue != null) {
00450                 out.write(' ');
00451                 out.write(XMLHelper.encodeXMLString(it.getKey()));
00452                 out.write("=\"");
00453                 out.write(XMLHelper.encodeXMLString(attrValue));
00454                 out.write("\"");
00455             }
00456         }
00457 
00458         if (!_children.isEmpty() || _content.length()>0) {
00459             out.write(">\n");
00460 
00461             for(XMLNode child : _children)
00462                 child.prettyWriteWorker(out, indent+1);
00463 
00464             for(int i=indent; i-->0; )
00465                 out.write(XML_INDENT_SPACE);
00466 
00467             out.write("</");
00468             out.write(XMLHelper.encodeXMLString(_name));
00469             out.write(">\n");
00470         } else
00471             out.write("/>\n");
00472     }
00473 
00474     protected void smartWriteWorker(Writer out, int indent) throws IOException
00475     {
00476         if (_leading.length() == 0)
00477             for(int i=indent; i-->0; )
00478                 out.write(XML_INDENT_SPACE);
00479         else
00480             out.write(_leading);
00481 
00482         out.write('<');
00483         out.write(XMLHelper.encodeXMLString(_name));
00484 
00485         for(Map.Entry<String,String> it : _attributes.entrySet()) {
00486             String attrValue = it.getValue();
00487 
00488             if (attrValue != null) {
00489                 out.write(' ');
00490                 out.write(XMLHelper.encodeXMLString(it.getKey()));
00491                 out.write("=\"");
00492                 out.write(XMLHelper.encodeXMLString(attrValue));
00493                 out.write("\"");
00494             }
00495         }
00496 
00497         if (_children.isEmpty() && _content.length()==0)
00498             out.write("/>");
00499         else {
00500             out.write('>');
00501 
00502             if (_content.length() == 0)
00503                 out.write('\n');
00504             else
00505                 out.write(_content);
00506 
00507             Iterator<XMLNode> it = _children.iterator();
00508 
00509             if (it.hasNext()) {
00510                 while(it.hasNext())
00511                     it.next().smartWriteWorker(out, indent+1);
00512 
00513                 if (_endLeading.length() == 0)
00514                     for(int i=indent; i-->0; )
00515                         out.write(XML_INDENT_SPACE);
00516                 else
00517                     out.write(_endLeading);
00518             } else
00519                 out.write(_endLeading);
00520 
00521             out.write("</");
00522             out.write(XMLHelper.encodeXMLString(_name));
00523             out.write('>');
00524         }
00525 
00526         if (_trailing.length() == 0)
00527             out.write('\n');
00528         else
00529             out.write(_trailing);
00530     }
00531 }