JD2022-TU1/main/tools/framework/JD.Serializers/Xml/XmlSerializerReader.cs

925 lines
30 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Xml;
using JD.Kernel.Classes;
namespace JD.Serializers.Xml
{
/// <summary>
/// Class used to help reading xml files.
/// </summary>
/// <remarks>
/// This is the fastest XmlSerializer for reading Xml files and it has a very low
/// memory overhead. It has some drawbacks though:
/// <list type="bullet">
/// <item><description>
/// 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.
/// </description></item>
/// <item><description>
/// Being a forward-only serializer, it provides no enumerating capabilities.
/// </description></item>
/// <item><description>
/// Elements must be read in exactly the same order they were written.
/// </description></item>
/// </list>
/// </remarks>
public sealed class XmlSerializerReader : XmlSerializer, IEnumerable<string>
{
#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
/// <summary>
/// Here we assume the reader is positioned on a start element tag.
/// </summary>
/// <param name="reader"></param>
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<string>
{
#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<string>
{
#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<string> Members
public IEnumerator<string> 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;
/// <summary>
/// The next element is the next element that is already open (used with NextElementIs)
/// </summary>
private IXmlElement m_nextElement;
#endregion
#region Constructor
/// <summary>
/// XmlSerializerReader Constructor
/// </summary>
/// <param name="stream">Xml Stream to read</param>
public XmlSerializerReader(System.IO.Stream stream)
: this(stream, null)
{ }
/// <summary>
/// Constructor with a fallback filename.
/// </summary>
/// <param name="stream"></param>
/// <param name="fallbackFilename"></param>
public XmlSerializerReader(System.IO.Stream stream, string fallbackFilename)
: base(stream, fallbackFilename)
{
using (SafeHandle<System.IO.Stream> 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;
}
/// <summary>
/// XmlSerializerReader Constructor
/// </summary>
/// <param name="filename">File to read from</param>
public XmlSerializerReader(string filename)
: this(File.OpenRead(filename))
{
}
/// <summary>
/// Overriden.
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (m_reader != null)
{
m_reader.Close();
}
}
base.Dispose(disposing);
}
#endregion
#region Properties
/// <summary>
/// Gets the number of attributes on the current node.
/// </summary>
public int AttributeCount
{
get
{
return m_reader.AttributeCount;
}
}
/// <summary>
/// Returns true if the current element is an empty xml node (&lt;node /&gt;).
/// </summary>
public bool IsEmptyElement
{
get
{
m_reader.MoveToElement();
return m_reader.IsEmptyElement;
}
}
/// <summary>
/// Current line number.
/// </summary>
public int LineNumber
{
get
{
return m_linePositionProvider.LineNumber;
}
}
/// <summary>
/// Current column number.
/// </summary>
public int ColumnNumber
{
get
{
return m_linePositionProvider.LinePosition;
}
}
#endregion
#region Methods
#region General
/// <summary>
/// Starts a new element.
/// </summary>
/// <param name="element">Name of the element to start.</param>
/// <returns>The started element.</returns>
/// <exception cref="XmlException">Thrown if no element of that name if found on the current level.</exception>
public override IXmlElement StartElement(string element)
{
return StartElement(element, true, true);
}
/// <summary>
/// Starts a new element.
/// </summary>
/// <param name="element">Name of the element to start.</param>
/// <param name="skipBadElements">Whether to skip elements which don't have the given name.</param>
/// <returns>The started element.</returns>
/// <exception cref="XmlException">Thrown if no element of that name if found on the current level.</exception>
public IXmlElement StartElement(string element, bool skipBadElements)
{
return StartElement(element, skipBadElements, true);
}
/// <summary>
/// Starts a new element.
/// </summary>
/// <param name="element">Name of the element to start.</param>
/// <param name="skipBadElements">Whether to skip elements which don't have the given name.</param>
/// <param name="throwIfNotFound">Whether to throw an exception if the element is not found on the current level.</param>
/// <returns>The started element, or null if not found.</returns>
/// <exception cref="XmlException">Thrown if no element of that name if found on the current level and <paramref name="throwIfNotFound"/> is true.</exception>
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);
}
}
/// <summary>
/// Starts the next available element.
/// </summary>
/// <returns>The started element, or null if not found.</returns>
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;
}
/// <summary>
/// Reads xml inner data.
/// </summary>
/// <returns></returns>
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;
}
}
}
}
/// <summary>
/// Checks whether the next element is <paramref name="element"/>.
/// </summary>
/// <remarks>
/// You cannot read the attributes of the current node after calling this method.
/// </remarks>
/// <param name="element">Name of the element to test for.</param>
/// <returns>True if the name of the next element is <paramref name="element"/>.</returns>
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
/// <summary>
/// Returns an <see cref="IEnumerable{String}"/> to iterate through the elements of the current level with the given <paramref name="element"/>.
/// </summary>
/// <remarks>
/// Iteration stops when encountering an element with a different name, even on the same level.
/// </remarks>
/// <param name="element">Name of the elements to iterate through.</param>
/// <returns></returns>
public IEnumerable<string> GetEnumerator(string element)
{
return new NamedElementEnumerator(this, element);
}
/// <summary>
/// Returns an <see cref="IEnumerable{String}"/> to iterate through the elements of the current level.
/// </summary>
/// <returns></returns>
public IEnumerator<string> GetEnumerator()
{
return new ElementEnumerator(this);
}
/// <summary>
/// Returns an <see cref="IEnumerable{String}"/> to iterate through the elements of the current level.
/// </summary> ///
/// <returns></returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Serialize
/// <summary>
/// Serializes a string value.
/// </summary>
/// <param name="attribute">Name of the attribute to serialize</param>
/// <returns>Value of the attribute</returns>
public string Serialize(string attribute)
{
if (!m_reader.MoveToAttribute(attribute))
{
return string.Empty;
}
return SerializeStringImpl();
}
/// <summary>
/// Serializes any basic type.
/// </summary>
/// <typeparam name="T">The type of value to serialize</typeparam>
/// <param name="attribute">Name of the attribute to serialize</param>
/// <returns>Value of the attribute</returns>
public T Serialize<T>(string attribute)
{
return Serialize(attribute, default(T));
}
public T Serialize<T>(string attribute, T defaultValue)
{
if (!m_reader.MoveToAttribute(attribute))
{
return defaultValue;
}
try
{
return SerializeImpl<T>();
}
catch (Exception ex)
{
throw new XmlException("Error serializing attribute '" + attribute + "': " + ex.Message);
}
}
/// <summary>
/// Serializes any type, using the given <paramref name="type"/> to convert the value from a string.
/// </summary>
/// <param name="attribute">Name of the attribute to serialize</param>
/// <param name="type">The converter to use to convert the attribute value from a string</param>
/// <returns>Value of the attribute</returns>
public object Serialize(string attribute, Type type)
{
return Serialize<object>(attribute, GetConverter(type));
}
/// <summary>
/// Serializes any type, using the given <paramref name="converter"/> to convert the value from a string.
/// </summary>
/// <typeparam name="T">The type of value to serialize</typeparam>
/// <param name="attribute">Name of the attribute to serialize</param>
/// <param name="converter">The converter to use to convert the attribute value from a string</param>
/// <returns>Value of the attribute</returns>
public T Serialize<T>(string attribute, TypeConverter converter)
{
if (!m_reader.MoveToAttribute(attribute))
{
return default(T);
}
try
{
return SerializeImpl<T>(converter);
}
catch (Exception ex)
{
throw new XmlException("Error serializing attribute '" + attribute + "': " + ex.Message);
}
}
/// <summary>
/// Serializes an array made of any basic type.
/// </summary>
/// <typeparam name="T">The type of value the array contains</typeparam>
/// <param name="attribute">Name of the attribute to serialize</param>
/// <returns>The serialized array</returns>
public T[] SerializeArray<T>(string attribute)
{
return SerializeArray<T>(attribute, DefaultArraySeparator);
}
/// <summary>
/// Serializes an array made of any basic type.
/// </summary>
/// <typeparam name="T">The type of value the array contains</typeparam>
/// <param name="attribute">Name of the attribute to serialize</param>
/// <param name="separator">Element separator.</param>
/// <returns>The serialized array</returns>
public T[] SerializeArray<T>(string attribute, char separator)
{
if (!m_reader.MoveToAttribute(attribute))
{
return new T[0];
}
return SerializeArrayImpl<T>(separator);
}
/// <summary>
/// Serializes a string value.
/// </summary>
/// <param name="attributeIndex">Index of the attribute to serialize</param>
/// <returns>Value of the attribute</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="attributeIndex"/> is negative or larger than <see cref="AttributeCount"/>.</exception>
public string Serialize(int attributeIndex)
{
if (attributeIndex < 0 || attributeIndex >= m_reader.AttributeCount)
{
throw new ArgumentOutOfRangeException("attributeIndex");
}
m_reader.MoveToAttribute(attributeIndex);
return SerializeStringImpl();
}
/// <summary>
/// Serializes any basic type from a text node. (Using its type converter)
/// </summary>
/// <typeparam name="T">The type of value to serialize</typeparam>
/// <returns>Value of the node</returns>
public T Serialize<T>()
{
return (T)(Serialize(typeof(T)));
}
/// <summary>
/// Serializes any basic type from a text node. (Using its type converter)
/// </summary>
/// <param name="expectedType">The type of value to serialize</param>
/// <returns>Value of the node</returns>
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);
}
}
/// <summary>
/// Serializes any basic type.
/// </summary>
/// <typeparam name="T">The type of value to serialize</typeparam>
/// <param name="attributeIndex">Index of the attribute to serialize</param>
/// <returns>Value of the attribute</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="attributeIndex"/> is negative or larger than <see cref="AttributeCount"/>.</exception>
public T Serialize<T>(int attributeIndex)
{
if (attributeIndex < 0 || attributeIndex >= m_reader.AttributeCount)
{
throw new ArgumentOutOfRangeException("attributeIndex");
}
m_reader.MoveToAttribute(attributeIndex);
try
{
return SerializeImpl<T>();
}
catch (Exception ex)
{
throw new XmlException("Error serializing attribute number '" + attributeIndex + "': " + ex.Message);
}
}
/// <summary>
/// Serializes an array made of any basic type.
/// </summary>
/// <typeparam name="T">The type of value the array contains</typeparam>
/// <param name="attributeIndex">Index of the attribute to serialize</param>
/// <returns>The serialized array</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="attributeIndex"/> is negative or larger than <see cref="AttributeCount"/>.</exception>
public T[] SerializeArray<T>(int attributeIndex)
{
return SerializeArray<T>(attributeIndex, DefaultArraySeparator);
}
/// <summary>
/// Serializes an array made of any basic type.
/// </summary>
/// <typeparam name="T">The type of value the array contains</typeparam>
/// <param name="attributeIndex">Index of the attribute to serialize</param>
/// <param name="separator">Element separator.</param>
/// <returns>The serialized array</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="attributeIndex"/> is negative or larger than <see cref="AttributeCount"/>.</exception>
public T[] SerializeArray<T>(int attributeIndex, char separator)
{
if (attributeIndex < 0 || attributeIndex >= m_reader.AttributeCount)
{
throw new ArgumentOutOfRangeException("attributeIndex");
}
m_reader.MoveToAttribute(attributeIndex);
return SerializeArrayImpl<T>(separator);
}
private string SerializeStringImpl()
{
return StringRecycler.Instance.Intern(m_reader.Value);
}
private T SerializeImpl<T>()
{
return SerializeImpl<T>(m_reader.Value);
}
private T SerializeImpl<T>(TypeConverter converter)
{
return SerializeImpl<T>(m_reader.Value, converter);
}
private T SerializeImpl<T>(string invariantString)
{
return (T)SerializeImpl(invariantString, typeof(T));
}
private static T SerializeImpl<T>(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<T>(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;
}
/// <summary>
/// Serializes any object type.
/// </summary>
/// <param name="attribute">Name of the attribute to serialize.</param>
/// <returns>The serialized object</returns>
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
/// <summary>
/// Gets all the attributes in a key-value pair form on the current node.
/// </summary>
/// <returns></returns>
public IEnumerable<KeyValuePair<string, string>> GetAttributes()
{
for (int i = 0; i < m_reader.AttributeCount; i++)
{
m_reader.MoveToAttribute(i);
yield return new KeyValuePair<string, string>(m_reader.Name, m_reader.Value);
}
}
/// <summary>
/// Verifies if the attribute exists
/// </summary>
/// <param name="attribute">name of the attribute</param>
/// <returns></returns>
public bool AttributeExists(string attribute)
{
return m_reader.MoveToAttribute(attribute);
}
#endregion
#endregion
}
}