using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Xml;
using JD.Kernel.Classes;
namespace JD.Serializers.Xml
{
///
/// Class used to help reading xml files.
///
///
/// This is the fastest XmlSerializer for reading Xml files and it has a very low
/// memory overhead. It has some drawbacks though:
///
///
/// You cannot read attributes of an element after you've started reading its sub-elements,
/// meaning you have to read all the needed attributes of a level before going on further.
///
///
/// Being a forward-only serializer, it provides no enumerating capabilities.
///
///
/// Elements must be read in exactly the same order they were written.
///
///
///
public sealed class XmlSerializerReader : XmlSerializer, IEnumerable
{
#region Inner Classes
private class ElementReader : IXmlElement
{
#region Variables
private readonly XmlSerializerReader m_reader;
private readonly string m_elementName;
private readonly int m_depth;
private readonly bool m_needToClose;
private bool m_disposed;
#endregion
#region Constructor
///
/// Here we assume the reader is positioned on a start element tag.
///
///
public ElementReader(XmlSerializerReader reader)
{
System.Diagnostics.Debug.Assert(reader != null);
m_reader = reader;
m_elementName = m_reader.m_reader.LocalName;
m_depth = m_reader.m_currentDepth;
m_needToClose = !m_reader.IsEmptyElement;
++m_reader.m_currentDepth;
}
public void Dispose()
{
if (!m_disposed)
{
if (m_needToClose)
{
m_reader.EndElement(m_elementName, m_depth);
}
m_reader.m_nextElement = null;
m_reader.m_currentDepth = m_depth;
m_disposed = true;
}
}
#endregion
#region Properties
public string Name
{
get
{
return m_elementName;
}
}
#endregion
}
private class ElementEnumerator : IEnumerator
{
#region Variables
protected readonly XmlSerializerReader m_reader;
private IXmlElement m_currentElement;
#endregion
#region Constructor
public ElementEnumerator(XmlSerializerReader reader)
{
System.Diagnostics.Debug.Assert(reader != null);
m_reader = reader;
}
public void Dispose()
{
if (m_currentElement != null)
{
m_currentElement.Dispose();
}
}
#endregion
#region Properties
public string Current
{
get
{
return m_currentElement.Name;
}
}
object System.Collections.IEnumerator.Current
{
get
{
return Current;
}
}
#endregion
#region Methods
public bool MoveNext()
{
Dispose();
m_currentElement = GetNextElement();
return m_currentElement != null;
}
protected virtual IXmlElement GetNextElement()
{
return m_reader.StartElement();
}
public void Reset()
{
throw new InvalidOperationException("Cannot reset an xml enumerator.");
}
#endregion
}
private class NamedElementEnumerator : ElementEnumerator, IEnumerable
{
#region Variables
private readonly string m_name;
#endregion
#region Constructor
public NamedElementEnumerator(XmlSerializerReader reader, string name)
: base(reader)
{
m_name = name;
}
#endregion
#region Methods
protected override IXmlElement GetNextElement()
{
return m_reader.StartElement(m_name, false, false);
}
#endregion
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
return this;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
#endregion
#region Variables
private readonly XmlReader m_reader;
private readonly Version m_version;
private readonly IXmlLineInfo m_linePositionProvider;
private int m_currentDepth;
///
/// The next element is the next element that is already open (used with NextElementIs)
///
private IXmlElement m_nextElement;
#endregion
#region Constructor
///
/// XmlSerializerReader Constructor
///
/// Xml Stream to read
public XmlSerializerReader(System.IO.Stream stream)
: this(stream, null)
{ }
///
/// Constructor with a fallback filename.
///
///
///
public XmlSerializerReader(System.IO.Stream stream, string fallbackFilename)
: base(stream, fallbackFilename)
{
using (SafeHandle streamHandle = stream)
{
// For now we are reading too much content with this bufferedstream, renable we will split content and definition
//stream = new BufferedStream(stream, 64 * 1024);
m_reader = XmlReader.Create(stream, CreateDefaultSettings());
m_linePositionProvider = (IXmlLineInfo)m_reader;
// Read first element with version
m_reader.Read();
if (m_reader.NodeType == XmlNodeType.XmlDeclaration)
{
m_reader.Read();
}
string version = "0.0";
if (m_reader.LocalName == RootElement ||
// Backward compatibility!
m_reader.LocalName == "Mosaic")
{
string serializedVersion = Serialize(VersionAttribute);
if (!string.IsNullOrEmpty(serializedVersion))
{
version = serializedVersion;
}
}
else
{
m_nextElement = new ElementReader(this);
}
m_version = new Version(version);
streamHandle.Release();
}
}
private static XmlReaderSettings CreateDefaultSettings()
{
XmlReaderSettings settings = new XmlReaderSettings
{
IgnoreComments = true,
IgnoreWhitespace = true,
CloseInput = true
};
return settings;
}
///
/// XmlSerializerReader Constructor
///
/// File to read from
public XmlSerializerReader(string filename)
: this(File.OpenRead(filename))
{
}
///
/// Overriden.
///
///
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (m_reader != null)
{
m_reader.Close();
}
}
base.Dispose(disposing);
}
#endregion
#region Properties
///
/// Gets the number of attributes on the current node.
///
public int AttributeCount
{
get
{
return m_reader.AttributeCount;
}
}
///
/// Returns true if the current element is an empty xml node (<node />).
///
public bool IsEmptyElement
{
get
{
m_reader.MoveToElement();
return m_reader.IsEmptyElement;
}
}
///
/// Current line number.
///
public int LineNumber
{
get
{
return m_linePositionProvider.LineNumber;
}
}
///
/// Current column number.
///
public int ColumnNumber
{
get
{
return m_linePositionProvider.LinePosition;
}
}
#endregion
#region Methods
#region General
///
/// Starts a new element.
///
/// Name of the element to start.
/// The started element.
/// Thrown if no element of that name if found on the current level.
public override IXmlElement StartElement(string element)
{
return StartElement(element, true, true);
}
///
/// Starts a new element.
///
/// Name of the element to start.
/// Whether to skip elements which don't have the given name.
/// The started element.
/// Thrown if no element of that name if found on the current level.
public IXmlElement StartElement(string element, bool skipBadElements)
{
return StartElement(element, skipBadElements, true);
}
///
/// Starts a new element.
///
/// Name of the element to start.
/// Whether to skip elements which don't have the given name.
/// Whether to throw an exception if the element is not found on the current level.
/// The started element, or null if not found.
/// Thrown if no element of that name if found on the current level and is true.
public IXmlElement StartElement(string element, bool skipBadElements, bool throwIfNotFound)
{
while (true)
{
IXmlElement xmlElement = StartElement();
{
if (xmlElement == null && throwIfNotFound)
{
throw new XmlException("Element <" + element + "> not found.");
}
if (xmlElement == null || xmlElement.Name == element)
{
m_nextElement = null;
return xmlElement;
}
if (!skipBadElements)
{
m_nextElement = xmlElement;
return null;
}
}
DisposableHelper.SafeDispose(xmlElement);
}
}
///
/// Starts the next available element.
///
/// The started element, or null if not found.
public IXmlElement StartElement()
{
IXmlElement element = null;
// The node has already been started
if (m_nextElement != null)
{
element = m_nextElement;
m_nextElement = null;
return element;
}
Advance(delegate
{
if (m_reader.NodeType == XmlNodeType.Element && m_reader.Depth == m_currentDepth + 1)
{
// We found an element.
element = new ElementReader(this);
return false;
}
if (m_reader.NodeType == XmlNodeType.EndElement && m_reader.Depth == m_currentDepth)
{
// No sub elements found.
return false;
}
return true;
});
return element;
}
///
/// Reads xml inner data.
///
///
public string ReadInnerData()
{
string data = null;
Advance(delegate
{
if ((m_reader.NodeType == XmlNodeType.Text || m_reader.NodeType == XmlNodeType.CDATA) && m_reader.Depth == m_currentDepth + 1)
{
data = m_reader.Value;
}
else if (m_reader.NodeType == XmlNodeType.EndElement && m_reader.Depth == m_currentDepth)
{
return false;
}
return true;
});
return StringRecycler.Instance.Intern(data);
}
private bool AdvanceToStartElement()
{
m_reader.MoveToElement();
int depth = m_reader.Depth;
if (depth == m_currentDepth && m_reader.IsEmptyElement)
{
return false;
}
if ((depth == m_currentDepth && m_reader.NodeType == XmlNodeType.Element) ||
(depth == m_currentDepth + 1 && (m_reader.NodeType == XmlNodeType.EndElement || m_reader.IsEmptyElement)))
{
return true;
}
return false;
}
private delegate bool AdvanceDelegate();
private void Advance(AdvanceDelegate action)
{
System.Diagnostics.Debug.Assert(action != null);
if (AdvanceToStartElement())
{
while (m_reader.Read())
{
if (!action())
{
return;
}
}
}
}
///
/// Checks whether the next element is .
///
///
/// You cannot read the attributes of the current node after calling this method.
///
/// Name of the element to test for.
/// True if the name of the next element is .
public bool NextElementIs(string element)
{
IXmlElement xmlElement = m_nextElement ?? StartElement();
if (xmlElement == null)
{
return false;
}
m_nextElement = xmlElement;
return (m_nextElement.Name == element);
}
private void EndElement(string elementName, int depth)
{
do
{
if (m_reader.NodeType == XmlNodeType.EndElement && m_reader.LocalName == elementName && m_reader.Depth == depth + 1)
{
return;
}
}
while (m_reader.Read());
}
#endregion
#region GetEnumerator
///
/// Returns an to iterate through the elements of the current level with the given .
///
///
/// Iteration stops when encountering an element with a different name, even on the same level.
///
/// Name of the elements to iterate through.
///
public IEnumerable GetEnumerator(string element)
{
return new NamedElementEnumerator(this, element);
}
///
/// Returns an to iterate through the elements of the current level.
///
///
public IEnumerator GetEnumerator()
{
return new ElementEnumerator(this);
}
///
/// Returns an to iterate through the elements of the current level.
/// ///
///
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Serialize
///
/// Serializes a string value.
///
/// Name of the attribute to serialize
/// Value of the attribute
public string Serialize(string attribute)
{
if (!m_reader.MoveToAttribute(attribute))
{
return string.Empty;
}
return SerializeStringImpl();
}
///
/// Serializes any basic type.
///
/// The type of value to serialize
/// Name of the attribute to serialize
/// Value of the attribute
public T Serialize(string attribute)
{
return Serialize(attribute, default(T));
}
public T Serialize(string attribute, T defaultValue)
{
if (!m_reader.MoveToAttribute(attribute))
{
return defaultValue;
}
try
{
return SerializeImpl();
}
catch (Exception ex)
{
throw new XmlException("Error serializing attribute '" + attribute + "': " + ex.Message);
}
}
///
/// Serializes any type, using the given to convert the value from a string.
///
/// Name of the attribute to serialize
/// The converter to use to convert the attribute value from a string
/// Value of the attribute
public object Serialize(string attribute, Type type)
{
return Serialize