/// <summary>
        /// Loads the XML data from a stream.
        /// </summary>
        /// <param name="xmlStream">The stream to read.</param>
        /// <param name="options">The parser options.</param>
        public static IDictionary <string, string> Parse(Stream xmlStream, Action <XmlStreamToDictionaryParserOptions> options = null)
        {
            var parserOptions = new XmlStreamToDictionaryParserOptions
            {
                KeyDelimiter     = ConfigurationPath.KeyDelimiter,
                IsIndexAttribute = (attribute, _) => string.Equals("Name", attribute, StringComparison.OrdinalIgnoreCase)
            };

            options?.Invoke(parserOptions);

            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
            };

            xmlStream.Position = 0;

            // ReSharper disable once ConvertToUsingDeclaration
            using (var reader = XmlReader.Create(xmlStream, readerSettings))
            {
                var prefixStack = new Stack <string>();

                if (parserOptions.Parents != null)
                {
                    foreach (var parent in parserOptions.Parents)
                    {
                        prefixStack.Push(parent);
                    }
                }

                SkipUntilRootElement(reader);

                // We process the root element individually since it doesn't contribute to prefix
                ProcessAttributes(reader, prefixStack, data, AddNamePrefix, parserOptions);
                ProcessAttributes(reader, prefixStack, data, AddAttributePair, parserOptions);

                var preNodeType = reader.NodeType;
                while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                    case XmlNodeType.Element:
                        prefixStack.Push(Capitalize(reader.LocalName));
                        ProcessAttributes(reader, prefixStack, data, AddNamePrefix, parserOptions);
                        ProcessAttributes(reader, prefixStack, data, AddAttributePair, parserOptions);

                        // 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 = Combine(prefixStack.Reverse(), parserOptions.KeyDelimiter);
                                data[key] = string.Empty;
                            }

                            prefixStack.Pop();
                        }
                        break;

                    case XmlNodeType.CDATA:
                    case XmlNodeType.Text:
                    {
                        var key = Combine(prefixStack.Reverse(), parserOptions.KeyDelimiter);
                        if (data.ContainsKey(key))
                        {
                            throw new FormatException($"A duplicate key '{key}' was found. {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($"Unsupported node type '{reader.NodeType}' was found. {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);
        }
        // The special attribute "Name" only contributes to prefix
        // This method adds a prefix if current node in reader represents a "Name" attribute
        private static void AddNamePrefix(XmlReader reader, Stack <string> prefixStack,
                                          IDictionary <string, string> data, XmlWriter writer, XmlStreamToDictionaryParserOptions options)
        {
            if (!options.IsIndexAttribute(reader.LocalName, prefixStack))
            {
                return;
            }

            if (prefixStack.Any())
            {
                prefixStack.Pop();
            }

            prefixStack.Push(reader.Value);
        }
        // Common attributes contribute to key-value pairs
        // This method adds a key-value pair if current node in reader represents a common attribute
        private static void AddAttributePair(XmlReader reader, Stack <string> prefixStack,
                                             IDictionary <string, string> data, XmlWriter writer, XmlStreamToDictionaryParserOptions options)
        {
            //if (options.IsIndexAttribute(reader.LocalName, prefixStack))
            //{
            //    return;
            //}

            prefixStack.Push(Capitalize(reader.LocalName));
            var key = Combine(prefixStack.Reverse(), options.KeyDelimiter);

            if (data.ContainsKey(key))
            {
                throw new FormatException($"A duplicate key '{key}' was found. {GetLineInfo(reader)}");
            }

            data[key] = reader.Value;

            prefixStack.Pop();
        }
        private static void ProcessAttributes(XmlReader reader, Stack <string> prefixStack, IDictionary <string, string> data,
                                              Action <XmlReader, Stack <string>, IDictionary <string, string>, XmlWriter, XmlStreamToDictionaryParserOptions> act, XmlStreamToDictionaryParserOptions options, XmlWriter writer = null)
        {
            for (int i = 0; i < reader.AttributeCount; i++)
            {
                reader.MoveToAttribute(i);

                // If there is a namespace attached to current attribute
                if (!string.IsNullOrEmpty(reader.NamespaceURI))
                {
                    throw new FormatException($"XML namespaces are not supported. {GetLineInfo(reader)}");
                }

                act(reader, prefixStack, data, writer, options);
            }

            // Go back to the element containing the attributes we just processed
            reader.MoveToElement();
        }