/// <summary> /// Read a stream of XML values into a key/value dictionary. /// </summary> /// <param name="stream">The stream of XML data.</param> /// <param name="decryptor">The <see cref="XmlDocumentDecryptor"/> to use to decrypt.</param> /// <returns>The <see cref="IDictionary{String, String}"/> which was read from the stream.</returns> public static IDictionary <string, string> Read(Stream stream, XmlDocumentDecryptor decryptor) { var data = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); var readerSettings = new XmlReaderSettings() { CloseInput = false, // caller will close the stream DtdProcessing = DtdProcessing.Prohibit, IgnoreComments = true, IgnoreWhitespace = true }; using (var reader = decryptor.CreateDecryptingXmlReader(stream, readerSettings)) { var prefixStack = new Stack <string>(); SkipUntilRootElement(reader); // We process the root element individually since it doesn't contribute to prefix ProcessAttributes(reader, prefixStack, data, AddNamePrefix); ProcessAttributes(reader, prefixStack, data, AddAttributePair); var preNodeType = reader.NodeType; while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: prefixStack.Push(reader.LocalName); ProcessAttributes(reader, prefixStack, data, AddNamePrefix); ProcessAttributes(reader, prefixStack, data, AddAttributePair); // If current element is self-closing if (reader.IsEmptyElement) { prefixStack.Pop(); } break; case XmlNodeType.EndElement: if (prefixStack.Any()) { // If this EndElement node comes right after an Element node, // it means there is no text/CDATA node in current element if (preNodeType == XmlNodeType.Element) { var key = ConfigurationPath.Combine(prefixStack.Reverse()); data[key] = string.Empty; } prefixStack.Pop(); } break; case XmlNodeType.CDATA: case XmlNodeType.Text: { var key = ConfigurationPath.Combine(prefixStack.Reverse()); if (data.ContainsKey(key)) { throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key, GetLineInfo(reader))); } data[key] = reader.Value; break; } case XmlNodeType.XmlDeclaration: case XmlNodeType.ProcessingInstruction: case XmlNodeType.Comment: case XmlNodeType.Whitespace: // Ignore certain types of nodes break; default: throw new FormatException(SR.Format(SR.Error_UnsupportedNodeType, reader.NodeType, GetLineInfo(reader))); } preNodeType = reader.NodeType; // If this element is a self-closing element, // we pretend that we just processed an EndElement node // because a self-closing element contains an end within itself if (preNodeType == XmlNodeType.Element && reader.IsEmptyElement) { preNodeType = XmlNodeType.EndElement; } } } return(data); }
/// <summary> /// Read a stream of XML values into a key/value dictionary. /// </summary> /// <param name="stream">The stream of XML data.</param> /// <param name="decryptor">The <see cref="XmlDocumentDecryptor"/> to use to decrypt.</param> /// <returns>The <see cref="IDictionary{String, String}"/> which was read from the stream.</returns> public static IDictionary <string, string?> Read(Stream stream, XmlDocumentDecryptor decryptor) { var readerSettings = new XmlReaderSettings() { CloseInput = false, // caller will close the stream DtdProcessing = DtdProcessing.Prohibit, IgnoreComments = true, IgnoreWhitespace = true }; XmlConfigurationElement?root = null; using (XmlReader reader = decryptor.CreateDecryptingXmlReader(stream, readerSettings)) { // keep track of the tree we followed to get where we are (breadcrumb style) var currentPath = new Stack <XmlConfigurationElement>(); XmlNodeType preNodeType = reader.NodeType; while (reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: var element = new XmlConfigurationElement(reader.LocalName, GetName(reader)); if (currentPath.Count == 0) { root = element; } else { var parent = currentPath.Peek(); // If parent already has a dictionary of children, update the collection accordingly if (parent.ChildrenBySiblingName != null) { // check if this element has appeared before, elements are considered siblings if their SiblingName properties match if (!parent.ChildrenBySiblingName.TryGetValue(element.SiblingName, out var siblings)) { siblings = new List <XmlConfigurationElement>(); parent.ChildrenBySiblingName.Add(element.SiblingName, siblings); } siblings.Add(element); } else { // Performance optimization: parents with a single child don't even initialize a dictionary if (parent.SingleChild == null) { parent.SingleChild = element; } else { // If we encounter a second child after assigning "SingleChild", we clear SingleChild and initialize the dictionary var children = new Dictionary <string, List <XmlConfigurationElement> >(StringComparer.OrdinalIgnoreCase); // Special case: the first and second child have the same sibling name if (string.Equals(parent.SingleChild.SiblingName, element.SiblingName, StringComparison.OrdinalIgnoreCase)) { children.Add(element.SiblingName, new List <XmlConfigurationElement> { parent.SingleChild, element }); } else { children.Add(parent.SingleChild.SiblingName, new List <XmlConfigurationElement> { parent.SingleChild }); children.Add(element.SiblingName, new List <XmlConfigurationElement> { element }); } parent.ChildrenBySiblingName = children; parent.SingleChild = null; } } } currentPath.Push(element); ReadAttributes(reader, element); // If current element is self-closing if (reader.IsEmptyElement) { currentPath.Pop(); } break; case XmlNodeType.EndElement: if (currentPath.Count != 0) { XmlConfigurationElement parent = currentPath.Pop(); // If this EndElement node comes right after an Element node, // it means there is no text/CDATA node in current element if (preNodeType == XmlNodeType.Element) { var lineInfo = reader as IXmlLineInfo; var lineNumber = lineInfo?.LineNumber; var linePosition = lineInfo?.LinePosition; parent.TextContent = new XmlConfigurationElementTextContent(string.Empty, lineNumber, linePosition); } } break; case XmlNodeType.CDATA: case XmlNodeType.Text: if (currentPath.Count != 0) { var lineInfo = reader as IXmlLineInfo; var lineNumber = lineInfo?.LineNumber; var linePosition = lineInfo?.LinePosition; XmlConfigurationElement parent = currentPath.Peek(); parent.TextContent = new XmlConfigurationElementTextContent(reader.Value, lineNumber, linePosition); } break; case XmlNodeType.XmlDeclaration: case XmlNodeType.ProcessingInstruction: case XmlNodeType.Comment: case XmlNodeType.Whitespace: // Ignore certain types of nodes break; default: throw new FormatException(SR.Format(SR.Error_UnsupportedNodeType, reader.NodeType, GetLineInfo(reader))); } preNodeType = reader.NodeType; // If this element is a self-closing element, // we pretend that we just processed an EndElement node // because a self-closing element contains an end within itself if (preNodeType == XmlNodeType.Element && reader.IsEmptyElement) { preNodeType = XmlNodeType.EndElement; } } } return(ProvideConfiguration(root)); }