public static File Parse(string filePath, string encoding, IXmlFlavor flavor)
        {
            Tracer.Trace($"Using {flavor.GetType().Name} flavor for '{filePath}'.");

            var encodingToUse = Encoding.GetEncoding(encoding);

            File file;

            using (var finder = CharacterPositionFinder.CreateFrom(filePath, encodingToUse))
            {
                file = ParseCore(filePath, finder, flavor, encodingToUse);

                Resorter.Resort(file);
                GapFiller.Fill(file, finder);
            }

            CommentCleaner.Clean(file);

            return(file);
        }
        public static File ParseCore(string filePath, CharacterPositionFinder finder, IXmlFlavor flavor, Encoding encoding)
        {
            using (var reader = new XmlTextReader(new StreamReader(SystemFile.OpenRead(filePath), encoding)))
            {
                var file = new File
                {
                    Name       = filePath,
                    FooterSpan = CharacterSpan.None,                // there is no footer
                };

                var fileBegin = new LineInfo(reader.LineNumber + 1, reader.LinePosition);

                try
                {
                    var dummyRoot = new Container();

                    // Parse the XML and display the text content of each of the elements.
                    // as there are XMLs that have the declaration on same line as the root element, we just loop and parse until the end
                    while (!reader.EOF)
                    {
                        Parse(reader, dummyRoot, finder, flavor);
                    }

                    var root    = dummyRoot.Children.OfType <Container>().Last();
                    var rootEnd = FixRoot(root, dummyRoot);

                    var fileEnd = new LineInfo(reader.LineNumber, reader.LinePosition - 1);

                    var positionAfterLastElement = new LineInfo(rootEnd.LineNumber, rootEnd.LinePosition + 1); // we calculate the next one (either a new line character or a regular one)

                    file.LocationSpan = new LocationSpan(fileBegin, fileEnd);

                    if (positionAfterLastElement < fileEnd)
                    {
                        file.FooterSpan = GetCharacterSpan(new LocationSpan(positionAfterLastElement, fileEnd), finder);
                    }

                    file.Children.Add(root);
                }
                catch (XmlException ex)
                {
                    // try to adjust location span to include full file content
                    // but ignore empty files as parsing errors
                    var lines = SystemFile.ReadLines(filePath).Count();
                    if (lines == 0)
                    {
                        file.LocationSpan = new LocationSpan(LineInfo.None, LineInfo.None);
                    }
                    else
                    {
                        file.ParsingErrors.Add(new ParsingError
                        {
                            ErrorMessage = ex.Message,
                            Location     = new LineInfo(ex.LineNumber, ex.LinePosition),
                        });

                        file.LocationSpan = new LocationSpan(new LineInfo(1, 0), new LineInfo(lines + 1, 0));
                    }
                }

                return(file);
            }
        }
 //// ATTENTION !!!! SIDE EFFECT AS WE READ FURTHER !!!!
 private static LocationSpan GetLocationSpanWithParseAttributes(XmlTextReader reader, Container container, CharacterPositionFinder finder, IXmlFlavor flavor)
 {
     return(GetLocationSpan(reader, _ =>
     {
         if (_.HasAttributes)
         {
             // we have to read the attributes
             ParseAttributes(_, container, finder, flavor);
         }
     }));
 }
        private static void ParseTerminalNode(XmlTextReader reader, Container parent, CharacterPositionFinder finder, IXmlFlavor flavor)
        {
            var name    = flavor.GetName(reader);
            var type    = flavor.GetType(reader);
            var content = flavor.GetContent(reader);

            var locationSpan = GetLocationSpan(reader);
            var span         = GetCharacterSpan(locationSpan, finder);

            var child = AddTerminalNode(parent, type, name, content, locationSpan, span);

            flavor.FinalAdjustAfterParsingComplete(child);
        }
        private static void ParseAttribute(XmlTextReader reader, Container parent, CharacterPositionFinder finder, IXmlFlavor flavor)
        {
            var attributeStartPos = new LineInfo(reader.LineNumber, reader.LinePosition);

            var name = flavor.GetName(reader);
            var type = flavor.GetType(reader);

            // important call to be able to read the attribute value
            reader.ReadAttributeValue();

            var content = flavor.GetContent(reader);

            var attributeEndPos = new LineInfo(reader.LineNumber, reader.LinePosition + content.Length);

            var startPos = finder.GetCharacterPosition(attributeStartPos);
            var endPos   = finder.GetCharacterPosition(attributeEndPos);

            var locationSpan = new LocationSpan(attributeStartPos, attributeEndPos);
            var span         = new CharacterSpan(startPos, endPos);

            var child = AddTerminalNode(parent, type, name, content, locationSpan, span);

            flavor.FinalAdjustAfterParsingComplete(child);
        }
        private static void ParseAttributes(XmlTextReader reader, Container parent, CharacterPositionFinder finder, IXmlFlavor flavor)
        {
            if (flavor.ParseAttributesEnabled)
            {
                reader.MoveToFirstAttribute();

                // read all attributes
                do
                {
                    ParseAttribute(reader, parent, finder, flavor);
                }while (reader.MoveToNextAttribute());
            }
        }
        private static void ParseElement(XmlTextReader reader, Container parent, CharacterPositionFinder finder, IXmlFlavor flavor)
        {
            var name    = flavor.GetName(reader);
            var type    = flavor.GetType(reader);
            var content = flavor.GetContent(reader);

            var container = new Container
            {
                Type    = type,
                Name    = name,
                Content = content,
            };

            var isEmpty = reader.IsEmptyElement;

            var startingSpan = GetLocationSpanWithParseAttributes(reader, container, finder, flavor);
            var headerSpan   = GetCharacterSpan(startingSpan, finder);

            if (isEmpty)
            {
                // there is no content, so we have to get away of the footer by just using the '/>' characters as footer
                var headerSpanCorrected = new CharacterSpan(headerSpan.Start, Math.Max(headerSpan.Start, headerSpan.End - 2));
                var footerSpanCorrected = new CharacterSpan(Math.Max(0, headerSpan.End - 1), headerSpan.End);

                container.LocationSpan = startingSpan;
                container.HeaderSpan   = headerSpanCorrected;
                container.FooterSpan   = footerSpanCorrected;
            }
            else
            {
                while (reader.NodeType != XmlNodeType.EndElement)
                {
                    Parse(reader, container, finder, flavor);

                    // we had a side effect (reading further on stream to get the location span), so we have to check whether we found already an element or end element
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        continue;
                    }

                    if (reader.NodeType == XmlNodeType.EndElement)
                    {
                        break;
                    }
                }

                var endingSpan = GetLocationSpan(reader);
                var footerSpan = GetCharacterSpan(endingSpan, finder);

                container.LocationSpan = new LocationSpan(startingSpan.Start, endingSpan.End);
                container.HeaderSpan   = headerSpan;
                container.FooterSpan   = footerSpan;
            }

            // check whether we can use a terminal node instead
            var child = flavor.FinalAdjustAfterParsingComplete(container);

            parent.Children.Add(child);
        }
        private static void Parse(XmlTextReader reader, Container parent, CharacterPositionFinder finder, IXmlFlavor flavor)
        {
            var nodeType = reader.NodeType;

            switch (nodeType)
            {
            case XmlNodeType.Element:
            {
                ParseElement(reader, parent, finder, flavor);
                break;
            }

            case XmlNodeType.ProcessingInstruction:
            case XmlNodeType.Comment:
            case XmlNodeType.XmlDeclaration:
            case XmlNodeType.CDATA:
            case XmlNodeType.Text:
            {
                ParseTerminalNode(reader, parent, finder, flavor);
                break;
            }

            default:
            {
                reader.Read();
                break;
            }
            }
        }