XMLStreamReader.java

Go to the documentation of this file.
00001 
00002 //
00003 // XML storage classes version 1.5
00004 //
00005 // XMLStreamReader.java
00006 //
00007 // Copyright (c) 2008 Martin Fuchs <martin-fuchs@gmx.net>
00008 //
00009 // XMLStreamReader requires Java 1.6
00010 //
00011 
00012 
00013 /*
00014 
00015  All rights reserved.
00016 
00017  Redistribution and use in source and binary forms, with or without
00018  modification, are permitted provided that the following conditions are met:
00019 
00020  * Redistributions of source code must retain the above copyright
00021     notice, this list of conditions and the following disclaimer.
00022  * Redistributions in binary form must reproduce the above copyright
00023     notice, this list of conditions and the following disclaimer in
00024     the documentation and/or other materials provided with the
00025     distribution.
00026 
00027  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00028  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00029  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00030  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00031  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00032  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00033  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00034  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00035  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00036  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00037  POSSIBILITY OF SUCH DAMAGE.
00038 
00039 */
00040 
00041 package de.mfuchs.xmlstorage.stream;
00042 
00043 import java.io.FileInputStream;
00044 import java.io.FileNotFoundException;
00045 import java.io.IOException;
00046 import java.io.InputStream;
00047 import java.util.Iterator;
00048 
00049 import javax.xml.stream.XMLInputFactory;
00050 import javax.xml.stream.XMLStreamException;
00051 import javax.xml.stream.events.XMLEvent;
00052 import javax.xml.transform.Source;
00053 import javax.xml.transform.stream.StreamSource;
00054 
00055 import de.mfuchs.xmlstorage.XMLHelper;
00056 import de.mfuchs.xmlstorage.XPath;
00057 import de.mfuchs.xmlstorage.XPathElement;
00058 
00059 
00060 /**
00061  * XMLStreamReader is the streaming XML file reader of XMLStorage.
00062  * XMLStreamReader requires Java 1.6.
00063  */
00064 public class XMLStreamReader implements Iterable<XMLStreamNode>
00065 {
00066     private InputStream _istream;
00067     protected javax.xml.stream.XMLStreamReader _reader;
00068 
00069     protected XMLStreamNode _node;
00070 
00071     protected String        _content = "";
00072     protected StringBuffer  _instructions = new StringBuffer(); 
00073 
00074     int _level  = 0;
00075 
00076     public XMLStreamReader(String path) throws FileNotFoundException, XMLStreamException
00077     {
00078         _istream = new FileInputStream(path);
00079 
00080         StreamSource source = new StreamSource(_istream);
00081         source.setSystemId(path);
00082 
00083         init(source);
00084     }
00085 
00086     public XMLStreamReader(Source source) throws XMLStreamException
00087     {
00088         init(source);
00089     }
00090 
00091     private void init(Source source) throws XMLStreamException
00092     {
00093         _node = new XMLStreamNode();
00094 
00095         XMLInputFactory factory = XMLInputFactory.newInstance();
00096 
00097         _reader = factory.createXMLStreamReader(source);
00098     }
00099 
00100     public void close() throws XMLStreamException, IOException
00101     {
00102         _reader.close();
00103         _reader = null;
00104 
00105         if (_istream != null) {
00106             _istream.close();
00107             _istream = null;
00108         }
00109     }
00110 
00111 
00112     public XMLStreamNode getNode()
00113     {
00114         return _node;
00115     }
00116 
00117     public Object getNodeName()
00118     {
00119         return _node.getName();
00120     }
00121 
00122     public boolean isAtNode(String nodeName)
00123     {
00124         return _node.getName().equals(nodeName);
00125     }
00126 
00127 
00128     /**
00129      * XMLStreamIterator iterates over the immediate children of the current XML node.
00130      */
00131     private static class XMLStreamIterator implements Iterator<XMLStreamNode>
00132     {
00133         XMLStreamIterator(XMLStreamReader reader)
00134         {
00135             _reader = reader;
00136 
00137             moveToChild();
00138         }
00139 
00140         public boolean hasNext()
00141         {
00142             return _hasNext;
00143         }
00144 
00145         public XMLStreamNode next()
00146         {
00147             moveToNext();
00148 
00149             return _reader.getNode();
00150         }
00151 
00152         public void remove()
00153         {
00154             throw new UnsupportedOperationException("remove() not implemented for XMLStreamIterator");
00155         }
00156 
00157         private final XMLStreamReader _reader;
00158         private boolean _hasNext;
00159 
00160         private void moveToChild()
00161         {
00162             try {
00163                 _hasNext = _reader.firstChild();
00164             } catch(XMLStreamException e) {
00165                 _hasNext = false;
00166                 e.printStackTrace();
00167             }
00168         }
00169 
00170         private void moveToNext()
00171         {
00172             try {
00173                 _hasNext = _reader.next();
00174             } catch(XMLStreamException e) {
00175                 _hasNext = false;
00176                 e.printStackTrace();
00177             }
00178         }
00179     }
00180 
00181     public Iterator<XMLStreamNode> iterator()
00182     {
00183         return new XMLStreamIterator(this);
00184     }
00185 
00186 
00187     /**
00188      * Return the value of an attribute in the current node or "".
00189      * @param attrName
00190      * @return
00191      */
00192     public String get(final String attrName)
00193     {
00194         return _node.get(attrName);
00195     }
00196 
00197     /**
00198      * Return the value of an attribute in the current node or a default value.
00199      * @param attrName
00200      * @param def default value
00201      * @return
00202      */
00203     public String get(final String attrName, final String def)
00204     {
00205         return _node.get(attrName, def);
00206     }
00207 
00208     /**
00209      * Go down to first child.
00210      * @return true if successfull
00211      * @throws XMLStreamException 
00212      */
00213     public boolean goDown() throws XMLStreamException
00214     {
00215         return firstChild(null);
00216     }
00217 
00218     /**
00219      * Search for child and go down.
00220      * @param name
00221      * @return
00222      * @throws XMLStreamException 
00223      */
00224     public boolean goDown(final String name) throws XMLStreamException
00225     {
00226         return firstChild(name);
00227     }
00228 
00229      /// move X-Path like to position in XML tree
00230     public boolean go(XPath xpath) throws XMLStreamException
00231     {
00232          // Absolute paths are not supported.
00233         if (xpath._absolute)
00234             return false;
00235 
00236         return findRelative(xpath);
00237     }
00238 
00239     /**
00240      * Move back to the next higher nesting level.
00241      * Remark: This implementation does _not_ jump more than one level up like XMLPos.node()
00242      * when using go() to move down more than one level at a time.
00243      * @return true if successfull
00244      * @throws XMLStreamException 
00245      */
00246     public boolean back() throws XMLStreamException
00247     {
00248         return findNode(null, -1, _level+1, false);
00249     }
00250 
00251     /**
00252      * @return content string
00253      */
00254     public String getContent()
00255     {
00256         return _node.getContent();
00257     }
00258 
00259     public String toString()
00260     {
00261         return _node.toString();
00262     }
00263 
00264 
00265     private void startElement(String nodeName)
00266     {
00267         _node.assign(nodeName);
00268         _node.setAttributes(_reader);
00269 
00270         _content = "";
00271 
00272         ++_level;
00273     }
00274 
00275     private void endElement(String nodeName)
00276     {
00277         _node.assign(nodeName);
00278 
00279         _content = "";
00280 
00281         --_level;
00282     }
00283 
00284     private void characters()
00285     {
00286         _content += XMLHelper.encodeXMLString(_reader.getText());
00287     }
00288 
00289 
00290     private void processingInstruction()
00291     {
00292         _instructions.append("<?").append(_reader.getPITarget())
00293             .append(" ").append(_reader.getPIData()).append("?>");
00294     }
00295 
00296     public String getInstructions()
00297     {
00298         return _instructions.toString();
00299     }
00300 
00301 
00302     /**
00303      * Move to the first child node.
00304      * @return true on success
00305      * @throws XMLStreamException
00306      */
00307     public boolean firstChild() throws XMLStreamException
00308     {
00309         return findNode(null, _level+1, _level, false);
00310     }
00311 
00312     /**
00313      * Move to the first child node with the specified name.
00314      * @return true on success
00315      * @throws XMLStreamException
00316      */
00317     public boolean firstChild(String nodeName) throws XMLStreamException
00318     {
00319         return findNode(nodeName, _level+1, _level, false);
00320     }
00321 
00322 
00323     /**
00324      * Move to the next sibling node independent from its name.
00325      * @return true on success
00326      * @throws XMLStreamException
00327      */
00328     public boolean next() throws XMLStreamException
00329     {
00330         return findNode(null, _level, _level-1, false);
00331     }
00332 
00333     /**
00334      * Move to the next sibling node with the specified name.
00335      * @return true on success
00336      * @throws XMLStreamException
00337      */
00338     public boolean nextSkipOthers(String nodeName) throws XMLStreamException
00339     {
00340         return findNode(nodeName, _level, _level-1, false);
00341     }
00342 
00343     /**
00344      * Move to the next sibling node with the specified name.
00345      * @return true on success
00346      * @throws XMLStreamException
00347      */
00348     public boolean nextStopOnOthers(String nodeName) throws XMLStreamException
00349     {
00350         return findNode(nodeName, _level, _level-1, true);
00351     }
00352 
00353 
00354     private boolean findNode(String targetTag, int targetLevel, int minLevel, boolean stopOnOthers) throws XMLStreamException
00355     {
00356         boolean found = false;
00357 
00358         parsingLoop: for(; _reader.hasNext(); _reader.next()) {
00359             switch(_reader.getEventType()) {
00360                 case XMLEvent.START_ELEMENT: {
00361                      // stop parsing if we found the target node and its content is already processed.
00362                     if (found)
00363                         break parsingLoop;
00364 
00365                     String nodeName = _reader.getLocalName();
00366 
00367                      // check for match in target level and eventually node name
00368                     if (_level+1 == targetLevel) {
00369                         if (targetTag==null || nodeName.equals(targetTag))
00370                             found = true;
00371                         else if (stopOnOthers)
00372                             return false;
00373                     }
00374 
00375 //                  System.out.print("<"+nodeName+">");
00376                     startElement(nodeName);
00377                     break;}
00378 
00379                 case XMLEvent.END_ELEMENT: {
00380                      // stop parsing if we found the target node and its content is already processed.
00381                     if (found)
00382                         break parsingLoop;
00383 
00384                      // stop before leaving the targeted level range; used by nextStopOnOthers()
00385                     if (_level == minLevel)
00386                         return false;
00387 
00388                     String nodeName = _reader.getLocalName();
00389                     endElement(nodeName);
00390 //                  System.out.print("</"+nodeName+">");
00391 
00392                      // stop searching if the current level leaves the target range; used by back()
00393                     if (_level < minLevel) {
00394                         _reader.next();
00395                         return false;
00396                     }
00397                     break;}
00398 
00399                 case XMLEvent.CHARACTERS:
00400                 case XMLEvent.COMMENT:
00401                 case XMLEvent.SPACE:
00402                 case XMLEvent.CDATA:
00403                     characters();
00404 //                  System.out.print(_reader.getText());
00405                     break;
00406 
00407                 case XMLEvent.PROCESSING_INSTRUCTION:
00408                     processingInstruction();
00409                     break;
00410             }
00411         }
00412 
00413         if (found) {
00414             _node.setContent(_content);
00415             _content = "";
00416             return true;
00417         } else
00418             return false;
00419     }
00420 
00421 
00422     /**
00423      * Find a children node by name.
00424      * @param nodeName
00425      * @return true on success 
00426      * @throws XMLStreamException 
00427      */
00428     public boolean find(final String nodeName) throws XMLStreamException
00429     {
00430         return firstChild(nodeName);
00431     }
00432 
00433     /**
00434      * Find the n'th children node with tag nodeName.
00435      * @param nodeName
00436      * @param n
00437      * @return true on success
00438      * @throws XMLStreamException 
00439      */
00440     public boolean find(final String nodeName, int n) throws XMLStreamException
00441     {
00442         if (!firstChild(nodeName))
00443             return false;
00444 
00445         for(int i=1; i<n; ++i)
00446             if (!nextSkipOthers(nodeName))
00447                 return false;
00448 
00449         return true;
00450     }
00451 
00452     /**
00453      * Search matching child node identified by key name and an attribute value.
00454      * @param nodeName
00455      * @param attrName
00456      * @param attrValue
00457      * @return true on success
00458      * @throws XMLStreamException 
00459      */
00460     public boolean find(final String nodeName, final String attrName, final String attrValue) throws XMLStreamException
00461     {
00462         if (firstChild(nodeName)) {
00463             if (_node.getName().equals(nodeName) && _node.get(attrName).equals(attrValue))
00464                 return true;
00465 
00466             while(nextSkipOthers(nodeName))
00467                 if (_node.getName().equals(nodeName) && _node.get(attrName).equals(attrValue))
00468                     return true;
00469         }
00470 
00471         return false;
00472     }
00473 
00474     /**
00475      * Search matching child node identified by key name and an attribute value.
00476      * @param nodeName
00477      * @param attrName
00478      * @param attrValue
00479      * @param n
00480      * @return true on success
00481      * @throws XMLStreamException 
00482      */
00483     public boolean find(final String nodeName, final String attrName, final String attrValue, int n) throws XMLStreamException
00484     {
00485         if (firstChild(nodeName)) {
00486             if (_node.getName().equals(nodeName) && _node.get(attrName).equals(attrValue))
00487                 if (n-- == 0)
00488                     return true;
00489 
00490             while(nextSkipOthers(nodeName))
00491                 if (_node.getName().equals(nodeName) && _node.get(attrName).equals(attrValue))
00492                     if (n-- == 0)
00493                         return true;
00494         }
00495 
00496         return false;
00497     }
00498 
00499     /**
00500      * XPath find function.
00501      * @param xpath
00502      * @return true on success
00503      * @throws XMLStreamException
00504      */
00505     boolean findRelative(final XPath xpath) throws XMLStreamException
00506     {
00507         for(XPathElement xelem : xpath) {
00508             if (!find(xelem))
00509                 return false;
00510         }
00511 
00512         return true;
00513     }
00514 
00515     /**
00516      * Search matching child node identified by key name and an attribute value.
00517      * @param xelem
00518      * @return true on success 
00519      * @throws XMLStreamException 
00520      */
00521     boolean find(XPathElement xelem) throws XMLStreamException
00522     {
00523         int[] n = new int[] {0};    // index in array for call-by-reference matches()
00524 
00525         if (firstChild(xelem._childName)) {
00526             if (xelem.matches(_node, n))
00527                 return true;
00528 
00529             while(nextSkipOthers(xelem._childName))
00530                 if (xelem.matches(_node, n))
00531                     return true;
00532         }
00533 
00534         return false;
00535     }
00536 }