private static CharacterSpan GetCharacterSpan(LocationSpan locationSpan, CharacterPositionFinder finder)
        {
            var startPos = finder.GetCharacterPosition(locationSpan.Start);
            var endPos   = finder.GetCharacterPosition(locationSpan.End);

            return(new CharacterSpan(startPos, endPos));
        }
예제 #2
0
        public void PrepareTest()
        {
            var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName;
            var fileName        = Path.Combine(parentDirectory, "Resources", "test.xml");

            _objectUnderTest = CharacterPositionFinder.CreateFrom(fileName, Encoding.Unicode);
        }
        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;
            }
            }
        }
        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 int AdjustHeaderEnd(Container node, CharacterPositionFinder finder, LineInfo position)
        {
            var characterPosition = finder.GetCharacterPosition(position);

            node.HeaderSpan = new CharacterSpan(node.HeaderSpan.Start, characterPosition);

            return(characterPosition + 1);
        }
        private static int AdjustParentFooter(Container parent, CharacterPositionFinder finder, LineInfo position)
        {
            var characterPosition = finder.GetCharacterPosition(position);

            parent.FooterSpan = new CharacterSpan(characterPosition, parent.FooterSpan.End);

            return(characterPosition - 1);
        }
        private static int AdjustHeaderToLineEnd(Container node, CharacterPositionFinder finder)
        {
            var info       = finder.GetLineInfo(node.HeaderSpan.End);
            var lineLength = finder.GetLineLength(info.LineNumber);

            var endPosition = new LineInfo(info.LineNumber, lineLength);

            return(AdjustHeaderEnd(node, finder, endPosition));
        }
        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());
            }
        }
        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 void Fill(File file, CharacterPositionFinder finder)
        {
            foreach (var root in file.Children)
            {
                var children = root.Children.Where(IsNoAttribute).ToList();
                if (children.Any())
                {
                    AdjustBegin(root, children, 0, finder);
                    AdjustEnd(root, children, children.Count - 1, finder);
                }

                // adjust based on gaps, but only adjust child nodes that are no attributes and no text
                for (var index = 0; index < children.Count; index++)
                {
                    var child = children[index];
                    if (child.Type != NodeType.Text)
                    {
                        AdjustNode(root, children, index, finder);
                    }
                }
            }
        }
        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);
        }
        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);
            }
        }
        private static int AdjustBegin(Container parent, IList <ContainerOrTerminalNode> parentChildren, int indexInParentChildren, CharacterPositionFinder finder)
        {
            var node = parentChildren[indexInParentChildren];

            int newStartPos;

            var first = node == parentChildren.First();

            if (first)
            {
                // first child, so adjust parent's header span and terminal node's line start and span begin
                newStartPos = parent.HeaderSpan.End + 1;

                var parentHeaderSpanEnd = finder.GetLineInfo(parent.HeaderSpan.End);
                if (parentHeaderSpanEnd.LineNumber < node.LocationSpan.Start.LineNumber)
                {
                    // different lines, so adjust line end of parent
                    newStartPos = AdjustHeaderToLineEnd(parent, finder);
                }
            }
            else
            {
                var siblingBefore = parentChildren.ElementAt(indexInParentChildren - 1);
                newStartPos = siblingBefore.GetTotalSpan().End + 1;

                var siblingBeforeEndLineNumber = siblingBefore.LocationSpan.End.LineNumber;
                if (siblingBeforeEndLineNumber != node.LocationSpan.Start.LineNumber)
                {
                    newStartPos = finder.GetCharacterPosition(new LineInfo(siblingBeforeEndLineNumber + 1, 1));
                }
            }

            return(newStartPos);
        }
        private static void AdjustNode(Container parent, IList <ContainerOrTerminalNode> parentChildren, int indexInParentChildren, CharacterPositionFinder finder)
        {
            var newStartPos = AdjustBegin(parent, parentChildren, indexInParentChildren, finder);
            var newEndPos   = AdjustEnd(parent, parentChildren, indexInParentChildren, finder);

            // somewhere in the middle, so adjust only node span and location, as well as that from the siblings
            var newStartLine = finder.GetLineInfo(newStartPos);
            var newEndLine   = finder.GetLineInfo(newEndPos);

            var node = parentChildren[indexInParentChildren];

            node.LocationSpan = new LocationSpan(newStartLine, newEndLine);

            // now adjust terminal node's start position
            if (node is Container c)
            {
                // only adjust child nodes that are no attributes
                var children = c.Children.Where(IsNoAttribute).ToList();
                if (children.Any())
                {
                    c.HeaderSpan = new CharacterSpan(newStartPos, c.HeaderSpan.End);

                    for (var index = 0; index < children.Count; index++)
                    {
                        AdjustNode(c, children, index, finder);
                    }

                    c.FooterSpan = new CharacterSpan(c.FooterSpan.Start, newEndPos);
                }
                else
                {
                    var headerEndLine   = finder.GetLineInfo(c.HeaderSpan.End).LineNumber;
                    var footerStartLine = finder.GetLineInfo(c.FooterSpan.Start).LineNumber;

                    if (headerEndLine != footerStartLine)
                    {
                        var endPos       = finder.GetLineLength(headerEndLine);
                        var headerEndPos = finder.GetCharacterPosition(headerEndLine, endPos);

                        c.HeaderSpan = new CharacterSpan(newStartPos, headerEndPos);
                        c.FooterSpan = new CharacterSpan(headerEndPos + 1, newEndPos);
                    }
                    else
                    {
                        c.HeaderSpan = new CharacterSpan(newStartPos, c.FooterSpan.Start - 1);
                        c.FooterSpan = new CharacterSpan(c.FooterSpan.Start, newEndPos);
                    }
                }
            }
            else if (node is TerminalNode t)
            {
                t.Span = new CharacterSpan(newStartPos, newEndPos);
            }
        }
        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);
        }
 //// 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 int AdjustEnd(Container parent, IList <ContainerOrTerminalNode> parentChildren, int indexInParentChildren, CharacterPositionFinder finder)
        {
            var node = parentChildren[indexInParentChildren];

            int newEndPos;

            var last = node == parentChildren.Last();

            if (last)
            {
                // last child, so adjust parent's footer span and terminal node's end start and span end
                newEndPos = parent.FooterSpan.Start - 1;

                var parentFooterLocation = finder.GetLineInfo(parent.FooterSpan.Start);
                if (parentFooterLocation.LineNumber != node.LocationSpan.End.LineNumber)
                {
                    var startPosition = new LineInfo(parentFooterLocation.LineNumber, 1);
                    newEndPos = AdjustParentFooter(parent, finder, startPosition);
                }
            }
            else
            {
                var siblingAfter = parentChildren.ElementAt(indexInParentChildren + 1);
                newEndPos = siblingAfter.GetTotalSpan().Start - 1;

                var startLine = siblingAfter.LocationSpan.Start.LineNumber;
                if (startLine != node.LocationSpan.End.LineNumber)
                {
                    var lineLength = finder.GetLineLength(node.LocationSpan.End);
                    var lineInfo   = new LineInfo(node.LocationSpan.End.LineNumber, lineLength);
                    newEndPos = finder.GetCharacterPosition(lineInfo);
                }
            }

            return(newEndPos);
        }