using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Xml; using SerializationException = System.Runtime.Serialization.SerializationException; namespace Compat.Runtime.Serialization { internal class ExtensionDataReader : XmlReader { private enum ExtensionDataNodeType { None, Element, EndElement, Text, Xml, ReferencedElement, NullElement, } private readonly Hashtable cache = new Hashtable(); private ElementData[] elements; private ElementData element; private ElementData nextElement; private ReadState readState = ReadState.Initial; private ExtensionDataNodeType internalNodeType; private XmlNodeType nodeType; private int depth; private string localName; private string ns; private string prefix; private string value; private int attributeCount; private int attributeIndex; private XmlNodeReader xmlNodeReader; private Queue deserializedDataNodes; private readonly XmlObjectSerializerReadContext context; private static readonly Dictionary nsToPrefixTable; private static readonly Dictionary prefixToNsTable; static ExtensionDataReader() { nsToPrefixTable = new Dictionary(); prefixToNsTable = new Dictionary(); AddPrefix(Globals.XsiPrefix, Globals.SchemaInstanceNamespace); AddPrefix(Globals.SerPrefix, Globals.SerializationNamespace); AddPrefix(string.Empty, string.Empty); } internal ExtensionDataReader(XmlObjectSerializerReadContext context) { attributeIndex = -1; this.context = context; } internal void SetDeserializedValue(object obj) { IDataNode deserializedDataNode = (deserializedDataNodes == null || deserializedDataNodes.Count == 0) ? null : deserializedDataNodes.Dequeue(); if (deserializedDataNode != null && !(obj is IDataNode)) { deserializedDataNode.Value = obj; deserializedDataNode.IsFinalValue = true; } } internal IDataNode GetCurrentNode() { IDataNode retVal = element.dataNode; Skip(); return retVal; } internal void SetDataNode(IDataNode dataNode, string name, string ns) { SetNextElement(dataNode, name, ns, null); element = nextElement; nextElement = null; SetElement(); } internal void Reset() { localName = null; ns = null; prefix = null; value = null; attributeCount = 0; attributeIndex = -1; depth = 0; element = null; nextElement = null; elements = null; deserializedDataNodes = null; } private bool IsXmlDataNode => (internalNodeType == ExtensionDataNodeType.Xml); public override XmlNodeType NodeType => IsXmlDataNode ? xmlNodeReader.NodeType : nodeType; public override string LocalName => IsXmlDataNode ? xmlNodeReader.LocalName : localName; public override string NamespaceURI => IsXmlDataNode ? xmlNodeReader.NamespaceURI : ns; public override string Prefix => IsXmlDataNode ? xmlNodeReader.Prefix : prefix; public override string Value => IsXmlDataNode ? xmlNodeReader.Value : value; public override int Depth => IsXmlDataNode ? xmlNodeReader.Depth : depth; public override int AttributeCount => IsXmlDataNode ? xmlNodeReader.AttributeCount : attributeCount; public override bool EOF => IsXmlDataNode ? xmlNodeReader.EOF : (readState == ReadState.EndOfFile); public override ReadState ReadState => IsXmlDataNode ? xmlNodeReader.ReadState : readState; public override bool IsEmptyElement => IsXmlDataNode ? xmlNodeReader.IsEmptyElement : false; public override bool IsDefault => IsXmlDataNode ? xmlNodeReader.IsDefault : base.IsDefault; public override char QuoteChar => IsXmlDataNode ? xmlNodeReader.QuoteChar : base.QuoteChar; public override XmlSpace XmlSpace => IsXmlDataNode ? xmlNodeReader.XmlSpace : base.XmlSpace; public override string XmlLang => IsXmlDataNode ? xmlNodeReader.XmlLang : base.XmlLang; public override string this[int i] => IsXmlDataNode ? xmlNodeReader[i] : GetAttribute(i); public override string this[string name] => IsXmlDataNode ? xmlNodeReader[name] : GetAttribute(name); public override string this[string name, string namespaceURI] => IsXmlDataNode ? xmlNodeReader[name, namespaceURI] : GetAttribute(name, namespaceURI); public override bool MoveToFirstAttribute() { if (IsXmlDataNode) { return xmlNodeReader.MoveToFirstAttribute(); } if (attributeCount == 0) { return false; } MoveToAttribute(0); return true; } public override bool MoveToNextAttribute() { if (IsXmlDataNode) { return xmlNodeReader.MoveToNextAttribute(); } if (attributeIndex + 1 >= attributeCount) { return false; } MoveToAttribute(attributeIndex + 1); return true; } public override void MoveToAttribute(int index) { if (IsXmlDataNode) { xmlNodeReader.MoveToAttribute(index); } else { if (index < 0 || index >= attributeCount) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.InvalidXmlDeserializingExtensionData)); } nodeType = XmlNodeType.Attribute; AttributeData attribute = element.attributes[index]; localName = attribute.localName; ns = attribute.ns; prefix = attribute.prefix; value = attribute.value; attributeIndex = index; } } public override string GetAttribute(string name, string namespaceURI) { if (IsXmlDataNode) { return xmlNodeReader.GetAttribute(name, namespaceURI); } for (int i = 0; i < element.attributeCount; i++) { AttributeData attribute = element.attributes[i]; if (attribute.localName == name && attribute.ns == namespaceURI) { return attribute.value; } } return null; } public override bool MoveToAttribute(string name, string namespaceURI) { if (IsXmlDataNode) { return xmlNodeReader.MoveToAttribute(name, ns); } for (int i = 0; i < element.attributeCount; i++) { AttributeData attribute = element.attributes[i]; if (attribute.localName == name && attribute.ns == namespaceURI) { MoveToAttribute(i); return true; } } return false; } public override bool MoveToElement() { if (IsXmlDataNode) { return xmlNodeReader.MoveToElement(); } if (nodeType != XmlNodeType.Attribute) { return false; } SetElement(); return true; } private void SetElement() { nodeType = XmlNodeType.Element; localName = element.localName; ns = element.ns; prefix = element.prefix; value = string.Empty; attributeCount = element.attributeCount; attributeIndex = -1; } public override string LookupNamespace(string prefix) { if (IsXmlDataNode) { return xmlNodeReader.LookupNamespace(prefix); } if (!prefixToNsTable.TryGetValue(prefix, out string ns)) { return null; } return ns; } public override void Skip() { if (IsXmlDataNode) { xmlNodeReader.Skip(); } else { if (ReadState != ReadState.Interactive) { return; } MoveToElement(); if (IsElementNode(internalNodeType)) { int depth = 1; while (depth != 0) { if (!Read()) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.InvalidXmlDeserializingExtensionData)); } if (IsElementNode(internalNodeType)) { depth++; } else if (internalNodeType == ExtensionDataNodeType.EndElement) { ReadEndElement(); depth--; } } } else { Read(); } } } private bool IsElementNode(ExtensionDataNodeType nodeType) { return (nodeType == ExtensionDataNodeType.Element || nodeType == ExtensionDataNodeType.ReferencedElement || nodeType == ExtensionDataNodeType.NullElement); } public override void Close() { if (IsXmlDataNode) { xmlNodeReader.Close(); } else { Reset(); readState = ReadState.Closed; } } public override bool Read() { if (nodeType == XmlNodeType.Attribute && MoveToNextAttribute()) { return true; } MoveNext(element.dataNode); switch (internalNodeType) { case ExtensionDataNodeType.Element: case ExtensionDataNodeType.ReferencedElement: case ExtensionDataNodeType.NullElement: PushElement(); SetElement(); break; case ExtensionDataNodeType.Text: nodeType = XmlNodeType.Text; prefix = string.Empty; ns = string.Empty; localName = string.Empty; attributeCount = 0; attributeIndex = -1; break; case ExtensionDataNodeType.EndElement: nodeType = XmlNodeType.EndElement; prefix = string.Empty; ns = string.Empty; localName = string.Empty; value = string.Empty; attributeCount = 0; attributeIndex = -1; PopElement(); break; case ExtensionDataNodeType.None: if (depth != 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.InvalidXmlDeserializingExtensionData)); } nodeType = XmlNodeType.None; prefix = string.Empty; ns = string.Empty; localName = string.Empty; value = string.Empty; attributeCount = 0; readState = ReadState.EndOfFile; return false; case ExtensionDataNodeType.Xml: // do nothing break; default: throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SerializationException(SR.InvalidStateInExtensionDataReader)); } readState = ReadState.Interactive; return true; } public override string Name { get { if (IsXmlDataNode) { return xmlNodeReader.Name; } Fx.Assert("ExtensionDataReader Name property should only be called for IXmlSerializable"); return string.Empty; } } public override bool HasValue { get { if (IsXmlDataNode) { return xmlNodeReader.HasValue; } Fx.Assert("ExtensionDataReader HasValue property should only be called for IXmlSerializable"); return false; } } public override string BaseURI { get { if (IsXmlDataNode) { return xmlNodeReader.BaseURI; } Fx.Assert("ExtensionDataReader BaseURI property should only be called for IXmlSerializable"); return string.Empty; } } public override XmlNameTable NameTable { get { if (IsXmlDataNode) { return xmlNodeReader.NameTable; } Fx.Assert("ExtensionDataReader NameTable property should only be called for IXmlSerializable"); return null; } } public override string GetAttribute(string name) { if (IsXmlDataNode) { return xmlNodeReader.GetAttribute(name); } Fx.Assert("ExtensionDataReader GetAttribute method should only be called for IXmlSerializable"); return null; } public override string GetAttribute(int i) { if (IsXmlDataNode) { return xmlNodeReader.GetAttribute(i); } Fx.Assert("ExtensionDataReader GetAttribute method should only be called for IXmlSerializable"); return null; } public override bool MoveToAttribute(string name) { if (IsXmlDataNode) { return xmlNodeReader.MoveToAttribute(name); } Fx.Assert("ExtensionDataReader MoveToAttribute method should only be called for IXmlSerializable"); return false; } public override void ResolveEntity() { if (IsXmlDataNode) { xmlNodeReader.ResolveEntity(); } else { Fx.Assert("ExtensionDataReader ResolveEntity method should only be called for IXmlSerializable"); } } public override bool ReadAttributeValue() { if (IsXmlDataNode) { return xmlNodeReader.ReadAttributeValue(); } Fx.Assert("ExtensionDataReader ReadAttributeValue method should only be called for IXmlSerializable"); return false; } private void MoveNext(IDataNode dataNode) { switch (internalNodeType) { case ExtensionDataNodeType.Text: case ExtensionDataNodeType.ReferencedElement: case ExtensionDataNodeType.NullElement: internalNodeType = ExtensionDataNodeType.EndElement; return; default: Type dataNodeType = dataNode.DataType; if (dataNodeType == Globals.TypeOfClassDataNode) { MoveNextInClass((ClassDataNode)dataNode); } else if (dataNodeType == Globals.TypeOfCollectionDataNode) { MoveNextInCollection((CollectionDataNode)dataNode); } else if (dataNodeType == Globals.TypeOfISerializableDataNode) { MoveNextInISerializable((ISerializableDataNode)dataNode); } else if (dataNodeType == Globals.TypeOfXmlDataNode) { MoveNextInXml((XmlDataNode)dataNode); } else if (dataNode.Value != null) { MoveToDeserializedObject(dataNode); } else { Fx.Assert("Encountered invalid data node when deserializing unknown data"); throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SerializationException(SR.InvalidStateInExtensionDataReader)); } break; } } private void SetNextElement(IDataNode node, string name, string ns, string prefix) { internalNodeType = ExtensionDataNodeType.Element; nextElement = GetNextElement(); nextElement.localName = name; nextElement.ns = ns; nextElement.prefix = prefix; if (node == null) { nextElement.attributeCount = 0; nextElement.AddAttribute(Globals.XsiPrefix, Globals.SchemaInstanceNamespace, Globals.XsiNilLocalName, Globals.True); internalNodeType = ExtensionDataNodeType.NullElement; } else if (!CheckIfNodeHandled(node)) { AddDeserializedDataNode(node); node.GetData(nextElement); if (node is XmlDataNode) { MoveNextInXml((XmlDataNode)node); } } } private void AddDeserializedDataNode(IDataNode node) { if (node.Id != Globals.NewObjectId && (node.Value == null || !node.IsFinalValue)) { if (deserializedDataNodes == null) { deserializedDataNodes = new Queue(); } deserializedDataNodes.Enqueue(node); } } private bool CheckIfNodeHandled(IDataNode node) { bool handled = false; if (node.Id != Globals.NewObjectId) { handled = (cache[node] != null); if (handled) { if (nextElement == null) { nextElement = GetNextElement(); } nextElement.attributeCount = 0; nextElement.AddAttribute(Globals.SerPrefix, Globals.SerializationNamespace, Globals.RefLocalName, node.Id.ToString(NumberFormatInfo.InvariantInfo)); nextElement.AddAttribute(Globals.XsiPrefix, Globals.SchemaInstanceNamespace, Globals.XsiNilLocalName, Globals.True); internalNodeType = ExtensionDataNodeType.ReferencedElement; } else { cache.Add(node, node); } } return handled; } private void MoveNextInClass(ClassDataNode dataNode) { if (dataNode.Members != null && element.childElementIndex < dataNode.Members.Count) { if (element.childElementIndex == 0) { context.IncrementItemCount(-dataNode.Members.Count); } ExtensionDataMember member = dataNode.Members[element.childElementIndex++]; SetNextElement(member.Value, member.Name, member.Namespace, GetPrefix(member.Namespace)); } else { internalNodeType = ExtensionDataNodeType.EndElement; element.childElementIndex = 0; } } private void MoveNextInCollection(CollectionDataNode dataNode) { if (dataNode.Items != null && element.childElementIndex < dataNode.Items.Count) { if (element.childElementIndex == 0) { context.IncrementItemCount(-dataNode.Items.Count); } IDataNode item = dataNode.Items[element.childElementIndex++]; SetNextElement(item, dataNode.ItemName, dataNode.ItemNamespace, GetPrefix(dataNode.ItemNamespace)); } else { internalNodeType = ExtensionDataNodeType.EndElement; element.childElementIndex = 0; } } private void MoveNextInISerializable(ISerializableDataNode dataNode) { if (dataNode.Members != null && element.childElementIndex < dataNode.Members.Count) { if (element.childElementIndex == 0) { context.IncrementItemCount(-dataNode.Members.Count); } ISerializableDataMember member = dataNode.Members[element.childElementIndex++]; SetNextElement(member.Value, member.Name, string.Empty, string.Empty); } else { internalNodeType = ExtensionDataNodeType.EndElement; element.childElementIndex = 0; } } private void MoveNextInXml(XmlDataNode dataNode) { if (IsXmlDataNode) { xmlNodeReader.Read(); if (xmlNodeReader.Depth == 0) { internalNodeType = ExtensionDataNodeType.EndElement; xmlNodeReader = null; } } else { internalNodeType = ExtensionDataNodeType.Xml; if (element == null) { element = nextElement; } else { PushElement(); } XmlNode wrapperElement = XmlObjectSerializerReadContext.CreateWrapperXmlElement(dataNode.OwnerDocument, dataNode.XmlAttributes, dataNode.XmlChildNodes, element.prefix, element.localName, element.ns); for (int i = 0; i < element.attributeCount; i++) { AttributeData a = element.attributes[i]; XmlAttribute xmlAttr = dataNode.OwnerDocument.CreateAttribute(a.prefix, a.localName, a.ns); xmlAttr.Value = a.value; wrapperElement.Attributes.Append(xmlAttr); } xmlNodeReader = new XmlNodeReader(wrapperElement); xmlNodeReader.Read(); } } private void MoveToDeserializedObject(IDataNode dataNode) { Type type = dataNode.DataType; bool isTypedNode = true; if (type == Globals.TypeOfObject) { type = dataNode.Value.GetType(); if (type == Globals.TypeOfObject) { internalNodeType = ExtensionDataNodeType.EndElement; return; } isTypedNode = false; } if (!MoveToText(type, dataNode, isTypedNode)) { if (dataNode.IsFinalValue) { internalNodeType = ExtensionDataNodeType.EndElement; } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.Format(SR.InvalidDataNode, DataContract.GetClrTypeFullName(type)))); } } } private bool MoveToText(Type type, IDataNode dataNode, bool isTypedNode) { bool handled = true; switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (bool)dataNode.Value); break; case TypeCode.Char: value = XmlConvert.ToString((int)(isTypedNode ? ((DataNode)dataNode).GetValue() : (char)dataNode.Value)); break; case TypeCode.Byte: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (byte)dataNode.Value); break; case TypeCode.Int16: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (short)dataNode.Value); break; case TypeCode.Int32: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (int)dataNode.Value); break; case TypeCode.Int64: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (long)dataNode.Value); break; case TypeCode.Single: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (float)dataNode.Value); break; case TypeCode.Double: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (double)dataNode.Value); break; case TypeCode.Decimal: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (decimal)dataNode.Value); break; case TypeCode.DateTime: DateTime dateTime = isTypedNode ? ((DataNode)dataNode).GetValue() : (DateTime)dataNode.Value; value = dateTime.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK", DateTimeFormatInfo.InvariantInfo); break; case TypeCode.String: value = isTypedNode ? ((DataNode)dataNode).GetValue() : (string)dataNode.Value; break; case TypeCode.SByte: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (sbyte)dataNode.Value); break; case TypeCode.UInt16: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (ushort)dataNode.Value); break; case TypeCode.UInt32: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (uint)dataNode.Value); break; case TypeCode.UInt64: value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (ulong)dataNode.Value); break; case TypeCode.Object: default: if (type == Globals.TypeOfByteArray) { byte[] bytes = isTypedNode ? ((DataNode)dataNode).GetValue() : (byte[])dataNode.Value; value = (bytes == null) ? string.Empty : Convert.ToBase64String(bytes); } else if (type == Globals.TypeOfTimeSpan) { value = XmlConvert.ToString(isTypedNode ? ((DataNode)dataNode).GetValue() : (TimeSpan)dataNode.Value); } else if (type == Globals.TypeOfGuid) { Guid guid = isTypedNode ? ((DataNode)dataNode).GetValue() : (Guid)dataNode.Value; value = guid.ToString(); } else if (type == Globals.TypeOfUri) { Uri uri = isTypedNode ? ((DataNode)dataNode).GetValue() : (Uri)dataNode.Value; value = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); } else { handled = false; } break; } if (handled) { internalNodeType = ExtensionDataNodeType.Text; } return handled; } private void PushElement() { GrowElementsIfNeeded(); elements[depth++] = element; if (nextElement == null) { element = GetNextElement(); } else { element = nextElement; nextElement = null; } } private void PopElement() { prefix = element.prefix; localName = element.localName; ns = element.ns; if (depth == 0) { return; } depth--; if (elements != null) { element = elements[depth]; } } private void GrowElementsIfNeeded() { if (elements == null) { elements = new ElementData[8]; } else if (elements.Length == depth) { ElementData[] newElements = new ElementData[elements.Length * 2]; Array.Copy(elements, 0, newElements, 0, elements.Length); elements = newElements; } } private ElementData GetNextElement() { int nextDepth = depth + 1; return (elements == null || elements.Length <= nextDepth || elements[nextDepth] == null) ? new ElementData() : elements[nextDepth]; } internal static string GetPrefix(string ns) { ns = ns ?? string.Empty; if (!nsToPrefixTable.TryGetValue(ns, out string prefix)) { lock (nsToPrefixTable) { if (!nsToPrefixTable.TryGetValue(ns, out prefix)) { prefix = (ns == null || ns.Length == 0) ? string.Empty : "p" + nsToPrefixTable.Count; AddPrefix(prefix, ns); } } } return prefix; } private static void AddPrefix(string prefix, string ns) { nsToPrefixTable.Add(ns, prefix); prefixToNsTable.Add(prefix, ns); } } }