private static ContainerOrTerminalNode GetNodeToAdjust(Container parent, TerminalNode comment, int index, int first, int last) { var containers = parent.Children; if (index == first && index == last) { return(parent); } if (index == first) { return(containers[index + 1]); } if (index == last) { return(containers[index - 1]); } var next = containers[index + 1]; var before = containers[index - 1]; var trailing = comment.LocationSpan.Start.LineNumber == before.LocationSpan.End.LineNumber; return(trailing ? before : next); }
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 LineInfo FixRoot(Container root, Container dummyRoot) { if (dummyRoot.Children.Any()) { // there might be no declaration, such as when trying to parse XAML files var xmlDeclaration = dummyRoot.Children.OfType <TerminalNode>().FirstOrDefault(_ => _.Type == NodeType.XmlDeclaration); if (xmlDeclaration != null) { // let root include the XML declaration AdjustRoot(root, xmlDeclaration); } else { // there might be a comment var xmlComment = dummyRoot.Children.OfType <TerminalNode>().FirstOrDefault(_ => _.Type == NodeType.Comment); if (xmlComment != null) { // so is it the first element? if (dummyRoot.Children.IndexOf(xmlComment) == 0) { // let root include the comment AdjustRoot(root, xmlComment); } } } } return(root.LocationSpan.End); }
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); }
public void PrepareTest() { var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName; var fileName = Path.Combine(parentDirectory, "Resources", "CommentWithoutDeclaration.xml"); _objectUnderTest = Parser.Parse(fileName); _root = _objectUnderTest.Children.Single(); }
public void PrepareTest() { var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName; var fileName = Path.Combine(parentDirectory, "Resources", "test_with_umlaut_and_wrong_encoding.xml"); _objectUnderTest = Parser.Parse(fileName); _root = _objectUnderTest.Children.Single(); }
private static void AdjustRoot(Container root, TerminalNode node) { var rootStart = node.LocationSpan.Start; // adjust positions root.LocationSpan = new LocationSpan(rootStart, root.LocationSpan.End); root.HeaderSpan = new CharacterSpan(node.Span.Start, root.HeaderSpan.End); }
public void Xml_without_declaration_can_be_read() { var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName; var fileName = Path.Combine(parentDirectory, "Resources", "test_without_declaration.xml"); _objectUnderTest = Parser.Parse(fileName); _root = _objectUnderTest.Children.Single(); Assert.That(_objectUnderTest.LocationSpan.Start, Is.EqualTo(new LineInfo(1, 0)), "Wrong start"); Assert.That(_objectUnderTest.LocationSpan.End, Is.EqualTo(new LineInfo(28, 0)), "Wrong end"); Assert.That(_objectUnderTest.FooterSpan, Is.EqualTo(new CharacterSpan(495, 496)), "Wrong footer"); }
public void PrepareTest() { var parentDirectory = Directory.GetParent(new Uri(GetType().Assembly.Location).LocalPath).FullName; var fileName = Path.Combine(parentDirectory, "Resources", "Xaml_ResourceDictionary.xml"); // we need to adjust line breaks because Git checkout on AppVeyor (or elsewhere) will adjust the line breaks var originalContent = File.ReadAllText(fileName); File.WriteAllText(fileName, originalContent.Replace(Environment.NewLine, "\n")); _objectUnderTest = Parser.Parse(fileName); _root = _objectUnderTest.Children.Single(); }
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 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 TerminalNode AddTerminalNode(Container parent, string type, string name, string content, LocationSpan locationSpan, CharacterSpan span) { var child = new TerminalNode { Type = type, Name = name, Content = content, LocationSpan = locationSpan, Span = span, }; parent.Children.Add(child); return(child); }
private static void Clean(Container parent, bool initial = false) { var children = parent.Children; const int first = 0; var last = children.Count - 1; var i = first; while (i <= last) { var child = children[i]; if (child is Container c) { Clean(c); } else { var comment = child as TerminalNode; if (comment?.Type == NodeType.Comment) { if (initial && i == last) { // special situation, a comment is the last element before the root footer // so we simply adjust the footer instead AdjustLocationSpan(comment, parent); parent.FooterSpan = new CharacterSpan(comment.Span.Start, parent.FooterSpan.End); } else { var node = GetNodeToAdjust(parent, comment, i, first, last); Adjust(comment, node); } children.Remove(comment); last--; i--; } } i++; } }
private static void AdjustSpan(TerminalNode comment, Container nodeToAdjust) { var commentStart = comment.Span.Start; var commentEnd = comment.Span.End; var headerStart = nodeToAdjust.HeaderSpan.Start; var headerEnd = nodeToAdjust.HeaderSpan.End; var footerStart = nodeToAdjust.FooterSpan.Start; var footerEnd = nodeToAdjust.FooterSpan.End; if (commentStart > headerEnd && commentStart < footerStart) { // comment if after header nodeToAdjust.HeaderSpan = new CharacterSpan(headerStart, commentEnd); } else { var min = Math.Min(headerStart, commentStart); var max = Math.Max(footerEnd, commentEnd); nodeToAdjust.HeaderSpan = new CharacterSpan(min, headerEnd); nodeToAdjust.FooterSpan = new CharacterSpan(footerStart, max); } }
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); } }
//// 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); } })); }