Created
September 19, 2011 01:08
-
-
Save ovatsus/1225799 to your computer and use it in GitHub Desktop.
Streaming XML input with XElementReader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
#if XML_DEBUG_MODE | |
using System.Linq; | |
#endif | |
using System.Xml; | |
using System.Xml.Linq; | |
public class XElementReader : IDisposable { | |
private int targetDepth; | |
#if XML_DEBUG_MODE | |
private readonly XElement elem; | |
private XElementReader(XElement elem, int depth) { | |
this.elem = elem; | |
this.targetDepth = depth; | |
} | |
public XElementReader(string path) { | |
this.elem = XElement.Load(path); | |
} | |
public XElementReader(TextReader reader) { | |
this.elem = XElement.Load(reader); | |
} | |
public XElementReader(XmlReader reader) { | |
this.elem = XElement.Load(reader); | |
} | |
public XElementReader(Stream stream) { | |
this.elem = XElement.Load(XmlReader.Create(stream)); | |
} | |
public void Close() { | |
if (targetDepth != 0) { | |
throw new InvalidOperationException("Only the outermost reader should be closed"); | |
} | |
} | |
public XName Name { | |
get { return elem.Name; } | |
} | |
public string Value { | |
get { return elem.Value; } | |
} | |
public XAttribute Attribute(XName name) { | |
return elem.Attribute(name); | |
} | |
public IEnumerable<XAttribute> Attributes() { | |
return elem.Attributes(); | |
} | |
public XElementReader FirstElement() { | |
XElement subElem = elem.Elements().FirstOrDefault(); | |
return subElem == null ? null : new XElementReader(subElem, targetDepth + 1); | |
} | |
public XElementReader Element(XName name) { | |
XElement subElem = elem.Element(name); | |
return subElem == null ? null : new XElementReader(subElem, targetDepth + 1); | |
} | |
public IEnumerable<XElementReader> Elements() { | |
return elem.Elements().Select(e => new XElementReader(e, targetDepth + 1)); | |
} | |
public IEnumerable<XElementReader> Elements(XName name) { | |
return elem.Elements(name).Select(e => new XElementReader(e, targetDepth + 1)); | |
} | |
public XElement ToXElement() { | |
return elem; | |
} | |
#else | |
private readonly XmlReader reader; | |
private bool onlyAttributesWereRead = true; | |
private XElementReader(XmlReader reader, int depth) { | |
this.reader = reader; | |
this.targetDepth = depth; | |
} | |
public XElementReader(string path) { | |
this.reader = XmlReader.Create(path); | |
} | |
public XElementReader(TextReader reader) { | |
this.reader = XmlReader.Create(reader); | |
} | |
public XElementReader(XmlReader reader) { | |
this.reader = reader; | |
} | |
public XElementReader(Stream stream) { | |
this.reader = XmlReader.Create(stream); | |
} | |
public void Close() { | |
if (targetDepth != 0) { | |
throw new InvalidOperationException("Only the outermost reader should be closed"); | |
} | |
reader.Close(); | |
} | |
public XName Name { | |
get { | |
if (!onlyAttributesWereRead) { | |
throw new InvalidOperationException("This operation must be done before retrieving any element"); | |
} | |
GoToDepth(targetDepth); | |
return XName.Get(reader.LocalName, reader.NamespaceURI); | |
} | |
} | |
public string Value { | |
get { | |
if (!onlyAttributesWereRead) { | |
throw new InvalidOperationException("This operation must be done before retrieving any element"); | |
} | |
onlyAttributesWereRead = false; | |
GoToDepth(targetDepth); | |
return reader.ReadElementContentAsString(); | |
} | |
} | |
public XAttribute Attribute(XName name) { | |
if (!onlyAttributesWereRead) { | |
throw new InvalidOperationException("This operation must be done before retrieving any element"); | |
} | |
GoToDepth(targetDepth); | |
var attribute = reader.GetAttribute(name.LocalName, name.NamespaceName); | |
return attribute == null ? null : new XAttribute(name, attribute); | |
} | |
public IEnumerable<XAttribute> Attributes() { | |
if (!onlyAttributesWereRead) { | |
throw new InvalidOperationException("This operation must be done before retrieving any element"); | |
} | |
GoToDepth(targetDepth); | |
try { | |
for (int i = 0; i < reader.AttributeCount; ++i) { | |
reader.MoveToAttribute(i); | |
yield return new XAttribute(reader.Name, reader.Value); | |
} | |
} finally { | |
reader.MoveToElement(); | |
} | |
} | |
public XElementReader FirstElement() { | |
onlyAttributesWereRead = false; | |
if (!GoToDepth(targetDepth + 1)) { | |
return null; | |
} else { | |
return new XElementReader(reader, targetDepth + 1); | |
} | |
} | |
public XElementReader Element(XName name) { | |
onlyAttributesWereRead = false; | |
if (!GoToDepth(targetDepth + 1)) { | |
return null; | |
} | |
if (XName.Get(reader.LocalName, reader.NamespaceURI) == name) { | |
return new XElementReader(reader, targetDepth + 1); | |
} | |
if (reader.ReadToNextSibling(name.LocalName, name.NamespaceName)) { | |
return new XElementReader(reader, targetDepth + 1); | |
} | |
return null; | |
} | |
public IEnumerable<XElementReader> Elements() { | |
onlyAttributesWereRead = false; | |
do { | |
if (!GoToDepth(targetDepth + 1)) { | |
yield break; | |
} | |
XElementReader innerReader = new XElementReader(reader, targetDepth + 1); | |
yield return innerReader; | |
if (innerReader.onlyAttributesWereRead) { | |
reader.Skip(); | |
} | |
} while (reader.Depth >= targetDepth + 1); | |
} | |
public IEnumerable<XElementReader> Elements(XName name) { | |
onlyAttributesWereRead = false; | |
do { | |
if (!GoToDepth(targetDepth + 1)) { | |
yield break; | |
} | |
if (XName.Get(reader.LocalName, reader.NamespaceURI) == name) { | |
XElementReader innerReader = new XElementReader(reader, targetDepth + 1); | |
yield return innerReader; | |
if (innerReader.onlyAttributesWereRead) { | |
reader.Skip(); | |
} | |
} else { | |
if (!reader.ReadToNextSibling(name.LocalName, name.NamespaceName)) { | |
yield break; | |
} | |
} | |
} while (reader.Depth >= targetDepth + 1); | |
} | |
public XElement ToXElement() { | |
if (!onlyAttributesWereRead) { | |
throw new InvalidOperationException("This operation must be done before retrieving any element"); | |
} | |
onlyAttributesWereRead = false; | |
GoToDepth(targetDepth); | |
return (XElement)XNode.ReadFrom(reader); //XElement.Load won't work here if it's not the top level element | |
} | |
private bool GoToDepth(int targetDepth) { | |
int startDepth = reader.Depth; | |
if (reader.Depth > targetDepth) { //we need to go up | |
while (reader.NodeType != XmlNodeType.Element || reader.Depth > targetDepth) { | |
if (reader.NodeType == XmlNodeType.Element) { | |
reader.Skip(); | |
} else { | |
if (!reader.Read()) { | |
return false; | |
} | |
if (reader.Depth > startDepth) { | |
return false; | |
} | |
} | |
} | |
} else { //we need to go down (or we're already there) | |
while (reader.NodeType != XmlNodeType.Element || reader.Depth < targetDepth) { | |
if (reader.NodeType == XmlNodeType.Element && reader.IsEmptyElement) { | |
reader.Skip(); | |
return false; | |
} else { | |
if (!reader.Read()) { | |
return false; | |
} | |
if (reader.Depth < startDepth || reader.NodeType == XmlNodeType.EndElement) { | |
return false; | |
} | |
} | |
} | |
} | |
return reader.Depth == targetDepth; | |
} | |
#endif | |
void IDisposable.Dispose() { | |
Close(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment