XMLStreamReader.cs

Go to the documentation of this file.
00001 
00002 //
00003 // XML storage classes version 1.5
00004 //
00005 // XMLStreamReader.cs
00006 //
00007 // Copyright (c) 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 using System;
00040 using System.IO;
00041 using System.Text;
00042 using System.Xml;
00043 using System.Collections.Generic;
00044 
00045 namespace XMLStorage
00046 {
00047 
00048 /**
00049  * XMLStreamReader is the streaming XML file reader of XMLStorage.
00050  */
00051 public class XMLStreamReader : IDisposable
00052 {
00053     private TextReader  _istream;
00054     protected XmlReader _reader;
00055 
00056     protected XMLStreamNode _node;
00057 
00058     protected string        _content = "";
00059     protected StringBuilder _instructions = new StringBuilder();
00060 
00061     int _level  = 0;
00062 
00063     public XMLStreamReader(string path, XmlReaderSettings settings=null)
00064     {
00065         _istream = new StreamReader(path);
00066 
00067         init(_istream, settings);
00068     }
00069 
00070     public XMLStreamReader(StreamReader source, XmlReaderSettings settings=null)
00071     {
00072         init(source, settings);
00073     }
00074 
00075     private void init(TextReader source, XmlReaderSettings settings=null)
00076     {
00077         _node = new XMLStreamNode();
00078 
00079         _reader = XmlReader.Create(source, settings);
00080     }
00081 
00082     public void Dispose()
00083     {
00084         _reader.Close();
00085         _reader = null;
00086 
00087         if (_istream != null) {
00088             _istream.Dispose();
00089             _istream = null;
00090         }
00091     }
00092 
00093 
00094     public XMLStreamNode getNode()
00095     {
00096         return _node;
00097     }
00098 
00099     public Object getNodeName()
00100     {
00101         return _node.getName();
00102     }
00103 
00104     public bool isAtNode(string nodeName)
00105     {
00106         return _node.getName() == nodeName;
00107     }
00108 
00109 
00110     /**
00111      * Iterates over the immediate children of the current XML node.
00112      */
00113     public IEnumerable<XMLStreamNode> iterator()
00114     {
00115         if (firstChild())
00116             yield return _node;
00117 
00118         while(next())
00119             yield return _node;
00120     }
00121 
00122     /**
00123      * Iterates over the immediate children of the current XML node and filters by node name.
00124      * (replaces XMLStreamFilter)
00125      */
00126     public IEnumerable<XMLStreamNode> filterChildren(XMLStreamReader reader, string name)
00127     {
00128         foreach(XMLStreamNode node in iterator())
00129         {
00130             if (node.getName() == name)
00131                 yield return node;
00132         }
00133     }
00134 
00135 
00136     /**
00137      * Return the value of an attribute in the current node or "".
00138      * @param attrName
00139      * @return
00140      */
00141     public string get(string attrName)
00142     {
00143         return _node.get(attrName);
00144     }
00145 
00146     /**
00147      * Return the value of an attribute in the current node or a default value.
00148      * @param attrName
00149      * @param def default value
00150      * @return
00151      */
00152     public string get(string attrName, string def)
00153     {
00154         return _node.get(attrName, def);
00155     }
00156 
00157     /**
00158      * Go down to first child.
00159      * @return true if successfull
00160      */
00161     public bool goDown()
00162     {
00163         return firstChild(null);
00164     }
00165 
00166     /**
00167      * Search for child and go down.
00168      * @param name
00169      * @return
00170      */
00171     public bool goDown(string name)
00172     {
00173         return firstChild(name);
00174     }
00175 
00176      /// move X-Path like to position in XML tree
00177     public bool go(XPath xpath)
00178     {
00179          // Absolute paths are not supported.
00180         if (xpath._absolute)
00181             return false;
00182 
00183         return findRelative(xpath);
00184     }
00185 
00186     /**
00187      * Move back to the next higher nesting level.
00188      * Remark: This implementation does _not_ jump more than one level up like XMLPos.node()
00189      * when using go() to move down more than one level at a time.
00190      * @return true if successfull
00191      */
00192     public bool back()
00193     {
00194         return findNode(null, -1, _level+1, false);
00195     }
00196 
00197     /**
00198      * @return content string
00199      */
00200     public string getContent()
00201     {
00202         return _node.getContent();
00203     }
00204 
00205     public override string ToString()
00206     {
00207         return _node.ToString();
00208     }
00209 
00210 
00211     private void startElement(string nodeName)
00212     {
00213         _node.assign(nodeName);
00214         _node.setAttributes(_reader);
00215 
00216         _content = "";
00217 
00218         ++_level;
00219     }
00220 
00221     private void endElement(string nodeName)
00222     {
00223         _node.assign(nodeName);
00224 
00225         _content = "";
00226 
00227         --_level;
00228     }
00229 
00230     private void characters(string str)
00231     {
00232         _content += XMLHelper.encodeXMLString(str);
00233     }
00234 
00235     private void processingInstruction(string target, string data)
00236     {
00237         _instructions.Append("<?").Append(target).Append(" ").Append(data).Append("?>");
00238     }
00239 
00240     public string getInstructions()
00241     {
00242         return _instructions.ToString();
00243     }
00244 
00245 
00246     /**
00247      * Move to the first child node.
00248      * @return true on success
00249      */
00250     public bool firstChild()
00251     {
00252         return findNode(null, _level+1, _level, false);
00253     }
00254 
00255     /**
00256      * Move to the first child node with the specified name.
00257      * @return true on success
00258      */
00259     public bool firstChild(string nodeName)
00260     {
00261         return findNode(nodeName, _level+1, _level, false);
00262     }
00263 
00264 
00265     /**
00266      * Move to the next sibling node independent from its name.
00267      * @return true on success
00268      */
00269     public bool next()
00270     {
00271         return findNode(null, _level, _level-1, false);
00272     }
00273 
00274     /**
00275      * Move to the next sibling node with the specified name.
00276      * @return true on success
00277      */
00278     public bool nextSkipOthers(string nodeName)
00279     {
00280         return findNode(nodeName, _level, _level-1, false);
00281     }
00282 
00283     /**
00284      * Move to the next sibling node with the specified name.
00285      * @return true on success
00286      */
00287     public bool nextStopOnOthers(string nodeName)
00288     {
00289         return findNode(nodeName, _level, _level-1, true);
00290     }
00291 
00292 
00293     private bool _pendingEndElement = false;
00294 
00295     private bool findNode(string targetTag, int targetLevel, int minLevel, bool stopOnOthers)
00296     {
00297         bool found = false;
00298 
00299         if (!handlePendingEndElement(minLevel))
00300             return false;
00301 
00302         do {
00303             switch(_reader.NodeType) {
00304                 case XmlNodeType.Element: {
00305                     // stop parsing if we found the target node and its content is already processed.
00306                     if (found)
00307                         goto break_parsingLoop;
00308 
00309                     // check for match in target level and eventually node name
00310                     int level = _pendingEndElement? _level-1: _level;
00311 
00312                     if (level+1 == targetLevel) {
00313                         if (targetTag==null || _reader.Name==targetTag)
00314                             found = true;
00315                         else if (stopOnOthers)
00316                             return false;
00317                     }
00318 
00319                     if (_reader.IsEmptyElement) { // node without sub nodes and text content, but maybe with attribute values {
00320                         if (!handlePendingEndElement(minLevel))
00321                             return false;
00322 
00323                         _pendingEndElement = true;
00324                     }
00325 
00326                     startElement(_reader.Name);
00327                     break;}
00328 
00329                 case XmlNodeType.EndElement: {
00330                     // stop parsing if we found the target node and its content is already processed.
00331                     if (found)
00332                         goto break_parsingLoop;
00333 
00334                     // stop before leaving the targeted level range; used by nextStopOnOthers()
00335                     int level = _pendingEndElement? _level-1: _level;
00336                     if (level == minLevel)
00337                         return false;
00338 
00339                     endElement(_reader.Name);
00340 
00341                     // stop searching if the current level leaves the target range; used by back()
00342                     if (_level < minLevel) {
00343                         _reader.Read();
00344                         return false;
00345                     }
00346                     break;}
00347 
00348                 case XmlNodeType.Text:
00349                     //Console.Write(_reader.Value);
00350                     characters(_reader.Value);
00351                     break;
00352 
00353                 case XmlNodeType.CDATA:
00354                     //Console.Write("<![CDATA[{0}]]>", _reader.Value);
00355                     characters(_reader.Value);
00356                     break;
00357 
00358                 case XmlNodeType.Comment:
00359                     //Console.Write("<!--{0}-->", _reader.Value);
00360                     characters(_reader.Value);
00361                     break;
00362 
00363                 case XmlNodeType.XmlDeclaration:
00364                     //Console.Write("<?xml version='1.0'?>");
00365                     break;
00366 
00367                 case XmlNodeType.ProcessingInstruction:
00368                     //Console.Write("<?{0} {1}?>", _reader.Name, _reader.Value);
00369                     processingInstruction(_reader.Name, _reader.Value);
00370                     break;
00371 
00372                 case XmlNodeType.DocumentType:
00373                     //Console.Write("<!DOCTYPE {0} [{1}]", _reader.Name, _reader.Value);
00374                     break;
00375 
00376                 //case XmlNodeType.Document:
00377                 //    break;
00378 
00379                 //case XmlNodeType.EntityReference:
00380                 //    Console.Write(reader.Name);
00381                 //    break;
00382             }
00383         } while(_reader.Read());
00384     break_parsingLoop:
00385 
00386         if (found) {
00387             _node.setContent(_content);
00388             _content = "";
00389             return true;
00390         } else
00391             return false;
00392     }
00393 
00394     private bool handlePendingEndElement(int minLevel)
00395     {
00396         if (_pendingEndElement) {
00397             _pendingEndElement = false;
00398 
00399             bool withAttributes = _node.hasAttributes();
00400 
00401             endElement(_node.getName());
00402 
00403             // stop searching if the current level leaves the target range; used by back()
00404             if (_level < minLevel) {
00405                 // If there have been read attributes in AbstractXMLNode.setAttributes(), XMLReader already points to the next node,
00406                 // so don't call XmlReader.Read()
00407                 if (!withAttributes)
00408                     _reader.Read();
00409 
00410                 return false;
00411             }
00412         }
00413 
00414         return true;
00415     }
00416 
00417 
00418     /**
00419      * Find a children node by name.
00420      * @param nodeName
00421      * @return true on success 
00422      */
00423     public bool find(string nodeName)
00424     {
00425         return firstChild(nodeName);
00426     }
00427 
00428     /**
00429      * Find the n'th children node with tag nodeName.
00430      * @param nodeName
00431      * @param n
00432      * @return true on success
00433      */
00434     public bool find(string nodeName, int n)
00435     {
00436         if (!firstChild(nodeName))
00437             return false;
00438 
00439         for(int i=1; i<n; ++i)
00440             if (!nextSkipOthers(nodeName))
00441                 return false;
00442 
00443         return true;
00444     }
00445 
00446     /**
00447      * Search matching child node identified by key name and an attribute value.
00448      * @param nodeName
00449      * @param attrName
00450      * @param attrValue
00451      * @return true on success
00452      */
00453     public bool find(string nodeName, string attrName, string attrValue)
00454     {
00455         if (firstChild(nodeName)) {
00456             if (_node.getName()==nodeName && _node.get(attrName)==attrValue)
00457                 return true;
00458 
00459             while(nextSkipOthers(nodeName))
00460                 if (_node.getName()==nodeName && _node.get(attrName)==attrValue)
00461                     return true;
00462         }
00463 
00464         return false;
00465     }
00466 
00467     /**
00468      * Search matching child node identified by key name and an attribute value.
00469      * @param nodeName
00470      * @param attrName
00471      * @param attrValue
00472      * @param n
00473      * @return true on success
00474      */
00475     public bool find(string nodeName, string attrName, string attrValue, int n)
00476     {
00477         if (firstChild(nodeName)) {
00478             if (_node.getName()==nodeName && _node.get(attrName)==attrValue)
00479                 if (n-- == 0)
00480                     return true;
00481 
00482             while(nextSkipOthers(nodeName))
00483                 if (_node.getName()==nodeName && _node.get(attrName)==attrValue)
00484                     if (n-- == 0)
00485                         return true;
00486         }
00487 
00488         return false;
00489     }
00490 
00491     /**
00492      * XPath find function.
00493      * @param xpath
00494      * @return true on success
00495      */
00496     bool findRelative(XPath xpath)
00497     {
00498         foreach(XPathElement xelem in xpath) {
00499             if (!find(xelem))
00500                 return false;
00501         }
00502 
00503         return true;
00504     }
00505 
00506     /**
00507      * Search matching child node identified by key name and an attribute value.
00508      * @param xelem
00509      * @return true on success 
00510      */
00511     bool find(XPathElement xelem)
00512     {
00513         int[] n = new int[] {0};    // index in array for call-by-reference matches()
00514 
00515         if (firstChild(xelem._childName)) {
00516             if (xelem.matches(_node, n))
00517                 return true;
00518 
00519             while(nextSkipOthers(xelem._childName))
00520                 if (xelem.matches(_node, n))
00521                     return true;
00522         }
00523 
00524         return false;
00525     }
00526 }
00527 
00528 } // namespace XMLStorage