Skip to content

Instantly share code, notes, and snippets.

@ovatsus
Created September 19, 2011 01:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ovatsus/1225799 to your computer and use it in GitHub Desktop.
Save ovatsus/1225799 to your computer and use it in GitHub Desktop.
Streaming XML input with XElementReader
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