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(attribute, GetConverter(type)); } /// /// Serializes any type, using the given to convert the value from a string. /// /// The type of value to serialize /// Name of the attribute to serialize /// The converter to use to convert the attribute value from a string /// Value of the attribute public T Serialize(string attribute, TypeConverter converter) { if (!m_reader.MoveToAttribute(attribute)) { return default(T); } try { return SerializeImpl(converter); } catch (Exception ex) { throw new XmlException("Error serializing attribute '" + attribute + "': " + ex.Message); } } /// /// Serializes an array made of any basic type. /// /// The type of value the array contains /// Name of the attribute to serialize /// The serialized array public T[] SerializeArray(string attribute) { return SerializeArray(attribute, DefaultArraySeparator); } /// /// Serializes an array made of any basic type. /// /// The type of value the array contains /// Name of the attribute to serialize /// Element separator. /// The serialized array public T[] SerializeArray(string attribute, char separator) { if (!m_reader.MoveToAttribute(attribute)) { return new T[0]; } return SerializeArrayImpl(separator); } /// /// Serializes a string value. /// /// Index of the attribute to serialize /// Value of the attribute /// Thrown when is negative or larger than . public string Serialize(int attributeIndex) { if (attributeIndex < 0 || attributeIndex >= m_reader.AttributeCount) { throw new ArgumentOutOfRangeException("attributeIndex"); } m_reader.MoveToAttribute(attributeIndex); return SerializeStringImpl(); } /// /// Serializes any basic type from a text node. (Using its type converter) /// /// The type of value to serialize /// Value of the node public T Serialize() { return (T)(Serialize(typeof(T))); } /// /// Serializes any basic type from a text node. (Using its type converter) /// /// The type of value to serialize /// Value of the node public object Serialize(Type expectedType) { try { string innerData = ReadInnerData(); if (innerData == null) { return expectedType.IsValueType ? Activator.CreateInstance(expectedType) : null; } char whiteSpace; if (expectedType == typeof(char) && XmlWhiteSpaceEncoder.TryParse(innerData, out whiteSpace)) { return whiteSpace; } return SerializeImpl(innerData, expectedType); } catch (Exception ex) { string nextElementName = m_nextElement != null ? m_nextElement.Name : "[None]"; throw new XmlException("Error serializing value node '" + nextElementName + "': " + ex.Message); } } /// /// Serializes any basic type. /// /// The type of value to serialize /// Index of the attribute to serialize /// Value of the attribute /// Thrown when is negative or larger than . public T Serialize(int attributeIndex) { if (attributeIndex < 0 || attributeIndex >= m_reader.AttributeCount) { throw new ArgumentOutOfRangeException("attributeIndex"); } m_reader.MoveToAttribute(attributeIndex); try { return SerializeImpl(); } catch (Exception ex) { throw new XmlException("Error serializing attribute number '" + attributeIndex + "': " + ex.Message); } } /// /// Serializes an array made of any basic type. /// /// The type of value the array contains /// Index of the attribute to serialize /// The serialized array /// Thrown when is negative or larger than . public T[] SerializeArray(int attributeIndex) { return SerializeArray(attributeIndex, DefaultArraySeparator); } /// /// Serializes an array made of any basic type. /// /// The type of value the array contains /// Index of the attribute to serialize /// Element separator. /// The serialized array /// Thrown when is negative or larger than . public T[] SerializeArray(int attributeIndex, char separator) { if (attributeIndex < 0 || attributeIndex >= m_reader.AttributeCount) { throw new ArgumentOutOfRangeException("attributeIndex"); } m_reader.MoveToAttribute(attributeIndex); return SerializeArrayImpl(separator); } private string SerializeStringImpl() { return StringRecycler.Instance.Intern(m_reader.Value); } private T SerializeImpl() { return SerializeImpl(m_reader.Value); } private T SerializeImpl(TypeConverter converter) { return SerializeImpl(m_reader.Value, converter); } private T SerializeImpl(string invariantString) { return (T)SerializeImpl(invariantString, typeof(T)); } private static T SerializeImpl(string invariantString, TypeConverter converter) { return (T)SerializeImpl(invariantString, converter); } private object SerializeImpl(string invariantString, Type expectedType) { return SerializeImpl(invariantString, GetConverter(expectedType)); } private static object SerializeImpl(string invariantString, TypeConverter converter) { var result = converter.ConvertFromInvariantString(invariantString); if (result is string) { result = StringRecycler.Instance.Intern((string)result); } return result; } private T[] SerializeArrayImpl(char separator) { string[] splittedValue = m_reader.Value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries); Type type = typeof(T); Array arr = Array.CreateInstance(type, splittedValue.Length); TypeConverter c = GetConverter(type); for (int i = 0; i < arr.Length; ++i) { if (!string.IsNullOrEmpty(XmlConvert.DecodeName(splittedValue[i]))) { arr.SetValue(c.ConvertFromInvariantString(splittedValue[i]), i); } } return (T[])arr; } /// /// Serializes any object type. /// /// Name of the attribute to serialize. /// The serialized object public object SerializeObject(string attribute) { byte[] data = Convert.FromBase64String(Serialize(attribute)); using (MemoryStream stream = new MemoryStream(data)) { System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); return formatter.Deserialize(stream); } } #endregion #region Attributes /// /// Gets all the attributes in a key-value pair form on the current node. /// /// public IEnumerable> GetAttributes() { for (int i = 0; i < m_reader.AttributeCount; i++) { m_reader.MoveToAttribute(i); yield return new KeyValuePair(m_reader.Name, m_reader.Value); } } /// /// Verifies if the attribute exists /// /// name of the attribute /// public bool AttributeExists(string attribute) { return m_reader.MoveToAttribute(attribute); } #endregion #endregion } }