private static void ReadAttributes(XmlReader reader, XmlConfigurationElement element)
        {
            if (reader.AttributeCount > 0)
            {
                element.Attributes = new List <XmlConfigurationElementAttributeValue>();
            }

            var lineInfo = reader as IXmlLineInfo;

            for (int i = 0; i < reader.AttributeCount; i++)
            {
                reader.MoveToAttribute(i);

                var lineNumber   = lineInfo?.LineNumber;
                var linePosition = lineInfo?.LinePosition;

                // If there is a namespace attached to current attribute
                if (!string.IsNullOrEmpty(reader.NamespaceURI))
                {
                    throw new FormatException(SR.Format(SR.Error_NamespaceIsNotSupported, GetLineInfo(reader)));
                }

                element.Attributes !.Add(new XmlConfigurationElementAttributeValue(reader.LocalName, reader.Value, lineNumber, linePosition));
            }

            // Go back to the element containing the attributes we just processed
            reader.MoveToElement();
        }
        /// <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));
        }
        private static IDictionary <string, string> ProvideConfiguration(XmlConfigurationElement root)
        {
            var configuration = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);

            if (root == null)
            {
                return(configuration);
            }

            var rootPrefix = new Prefix();

            // The root element only contributes to the prefix via its Name attribute
            if (!string.IsNullOrEmpty(root.Name))
            {
                rootPrefix.Push(root.Name);
            }

            ProcessElementAttributes(rootPrefix, root);
            ProcessElementContent(rootPrefix, root);
            ProcessElementChildren(rootPrefix, root);

            return(configuration);

            void ProcessElement(Prefix prefix, XmlConfigurationElement element)
            {
                ProcessElementAttributes(prefix, element);

                ProcessElementContent(prefix, element);

                ProcessElementChildren(prefix, element);
            }

            void ProcessElementAttributes(Prefix prefix, XmlConfigurationElement element)
            {
                // Add attributes to configuration values
                if (element.Attributes != null)
                {
                    for (var i = 0; i < element.Attributes.Count; i++)
                    {
                        var attribute = element.Attributes[i];

                        prefix.Push(attribute.Attribute);

                        AddToConfiguration(prefix.AsString, attribute.Value, attribute.LineNumber, attribute.LinePosition);

                        prefix.Pop();
                    }
                }
            }

            void ProcessElementContent(Prefix prefix, XmlConfigurationElement element)
            {
                // Add text content to configuration values
                if (element.TextContent != null)
                {
                    var textContent = element.TextContent;
                    AddToConfiguration(prefix.AsString, textContent.TextContent, textContent.LineNumber, textContent.LinePosition);
                }
            }

            void ProcessElementChildren(Prefix prefix, XmlConfigurationElement element)
            {
                if (element.SingleChild != null)
                {
                    var child = element.SingleChild;

                    ProcessElementChild(prefix, child, null);

                    return;
                }

                if (element.ChildrenBySiblingName == null)
                {
                    return;
                }

                // Recursively walk through the children of this element
                foreach (var childrenWithSameSiblingName in element.ChildrenBySiblingName.Values)
                {
                    if (childrenWithSameSiblingName.Count == 1)
                    {
                        var child = childrenWithSameSiblingName[0];

                        ProcessElementChild(prefix, child, null);
                    }
                    else
                    {
                        // Multiple children with the same sibling name. Add the current index to the prefix
                        for (int i = 0; i < childrenWithSameSiblingName.Count; i++)
                        {
                            var child = childrenWithSameSiblingName[i];

                            ProcessElementChild(prefix, child, i);
                        }
                    }
                }
            }

            void ProcessElementChild(Prefix prefix, XmlConfigurationElement child, int?index)
            {
                // Add element name to prefix
                prefix.Push(child.ElementName);

                // Add value of name attribute to prefix
                var hasName = !string.IsNullOrEmpty(child.Name);

                if (hasName)
                {
                    prefix.Push(child.Name);
                }

                // Add index to the prefix
                if (index != null)
                {
                    prefix.Push(index.Value.ToString(CultureInfo.InvariantCulture));
                }

                ProcessElement(prefix, child);

                // Remove index
                if (index != null)
                {
                    prefix.Pop();
                }

                // Remove 'Name' attribute
                if (hasName)
                {
                    prefix.Pop();
                }

                // Remove element name
                prefix.Pop();
            }

            void AddToConfiguration(string key, string value, int?lineNumber, int?linePosition)
            {
#if NETSTANDARD2_1
                if (!configuration.TryAdd(key, value))
                {
                    var lineInfo = lineNumber == null || linePosition == null
                        ? string.Empty
                        : SR.Format(SR.Msg_LineInfo, lineNumber.Value, linePosition.Value);
                    throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key, lineInfo));
                }
#else
                if (configuration.ContainsKey(key))
                {
                    var lineInfo = lineNumber == null || linePosition == null
                        ? string.Empty
                        : SR.Format(SR.Msg_LineInfo, lineNumber.Value, linePosition.Value);
                    throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key, lineInfo));
                }

                configuration.Add(key, value);
#endif
            }
        }