/// <summary> /// Perform post-parse processing on the node to ensure that <see cref="ExpressionNode.Range"/>s are populated and children are connected via the usual relationships (<see cref="ExpressionNode.Parent"/>, <see cref="ExpressionNode.PreviousSibling"/>, and <see cref="ExpressionNode.NextSibling"/>). /// </summary> /// <typeparam name="TNode"> /// The root node type. /// </typeparam> /// <param name="root"> /// The root node. /// </param> /// <param name="textPositions"> /// A <see cref="TextPositions"/> used to map absolute node positions to line / column. /// </param> /// <returns> /// The root node (enables inline use). /// </returns> public static TNode PostParse <TNode>(this TNode root, TextPositions textPositions) where TNode : ExpressionNode { if (root == null) { throw new System.ArgumentNullException(nameof(root)); } if (textPositions == null) { throw new System.ArgumentNullException(nameof(textPositions)); } Dictionary <int, Position> positionCache = new Dictionary <int, Position>(); void SetRange(ExpressionNode node) { Position start; if (!positionCache.TryGetValue(node.AbsoluteStart, out start)) { start = textPositions.GetPosition(node.AbsoluteStart); positionCache.Add(node.AbsoluteStart, start); } Position end; if (!positionCache.TryGetValue(node.AbsoluteEnd, out end)) { end = textPositions.GetPosition(node.AbsoluteEnd); positionCache.Add(node.AbsoluteEnd, end); } node.Range = new Range(start, end); } SetRange(root); foreach (ExpressionContainerNode parent in root.DescendantNodes().OfType <ExpressionContainerNode>()) { SetRange(root); ExpressionNode previousSibling = null; foreach (ExpressionNode nextSibling in parent.Children) { SetRange(nextSibling); nextSibling.Parent = parent; nextSibling.PreviousSibling = previousSibling; if (previousSibling != null) { previousSibling.NextSibling = nextSibling; } previousSibling = nextSibling; } } return(root); }
/// <summary> /// Convert the <see cref="TextSpan"/> to its native equivalent. /// </summary> /// <param name="span"> /// The <see cref="TextSpan"/> to convert. /// </param> /// <param name="textPositions"> /// The textual position lookup used to map absolute positions to lines and columns. /// </param> /// <returns> /// The equivalent <see cref="Range"/>. /// </returns> public static Range ToNative(this TextSpan span, TextPositions textPositions) { if (textPositions == null) { throw new ArgumentNullException(nameof(textPositions)); } Position startPosition = textPositions.GetPosition(span.Start); Position endPosition = textPositions.GetPosition(span.End); if (endPosition.ColumnNumber == 0) { throw new InvalidOperationException("Should not happen anymore"); } return(new Range(startPosition, endPosition)); }
/// <summary> /// Inspect the specified position in the XML. /// </summary> /// <param name="absolutePosition"> /// The target position (0-based). /// </param> /// <returns> /// An <see cref="SourceLocation"/> representing the result of the inspection. /// </returns> public SourceLocation Inspect(int absolutePosition) { if (absolutePosition < 0) { throw new ArgumentOutOfRangeException(nameof(absolutePosition), absolutePosition, "Absolute position cannot be less than 0."); } return(Inspect( _documentPositions.GetPosition(absolutePosition) )); }
public void GetAbsolutePosition_UnixLineEndings(int line, int column, char expectedChar) { const string text = TestData.TextWithUnixLineEndings.Text; TextPositions textPositions = new TextPositions(text); int absolutePosition = textPositions.GetAbsolutePosition(line, column); Assert.Equal(expectedChar, text[absolutePosition]); Position roundTripped = textPositions.GetPosition(absolutePosition).ToZeroBased(); Assert.Equal(roundTripped.LineNumber, line); Assert.Equal(roundTripped.ColumnNumber, column); }
public void GetPosition_WindowsLineEndings(char forChar, int expectedLine, int expectedColumn) { const string text = TestData.TextWithWindowsLineEndings.Text; int absolutePosition = text.IndexOf(forChar); Assert.InRange(absolutePosition, 0, text.Length - 1); TextPositions textPositions = new TextPositions(text); Position position = textPositions.GetPosition(absolutePosition); Assert.True(position.IsOneBased); Assert.Equal(expectedLine, position.LineNumber); Assert.Equal(expectedColumn, position.ColumnNumber); int absolutePositionRoundTripped = textPositions.GetAbsolutePosition(position); Assert.Equal(absolutePosition, absolutePositionRoundTripped); }
/// <summary> /// Find the spaces between elements and use that to infer whitespace. /// </summary> void ComputeWhitespace() { // TODO: Merge contiguous whitespace. var discoveredElements = DiscoveredNodes.OfType <XSElementWithContent>() .OrderBy(discoveredNode => discoveredNode.Range.Start) .ThenBy(discoveredNode => discoveredNode.Range.End); foreach (XSElementWithContent element in discoveredElements) { int startOfNextNode, endOfNode, whitespaceLength; XSWhitespace whitespace; endOfNode = element.ElementNode.StartTag.Span.End; for (int contentIndex = 0; contentIndex < element.Content.Count; contentIndex++) { if (element.Content[contentIndex] is XSElementText text) { startOfNextNode = _textPositions.GetAbsolutePosition(text.Range.Start); whitespaceLength = startOfNextNode - endOfNode; if (whitespaceLength > 0) { whitespace = new XSWhitespace( range: new Range( start: _textPositions.GetPosition(endOfNode), end: _textPositions.GetPosition(startOfNextNode) ), parent: element ); element.Content = element.Content.Insert(contentIndex, whitespace); DiscoveredNodes.Add(whitespace); } endOfNode = _textPositions.GetAbsolutePosition(text.Range.End); } if (element.Content[contentIndex] is XSElement childElement) { startOfNextNode = childElement.ElementNode.Span.Start; whitespaceLength = startOfNextNode - endOfNode; if (whitespaceLength > 0) { whitespace = new XSWhitespace( range: new Range( start: _textPositions.GetPosition(endOfNode), end: _textPositions.GetPosition(startOfNextNode) ), parent: element ); element.Content = element.Content.Insert(contentIndex, whitespace); DiscoveredNodes.Add(whitespace); } endOfNode = childElement.ElementNode.Span.End; } } // Any trailing whitespace before the closing tag? startOfNextNode = element.ElementNode.EndTag.Span.Start; whitespaceLength = startOfNextNode - endOfNode; if (whitespaceLength > 0) { whitespace = new XSWhitespace( range: new Range( start: _textPositions.GetPosition(endOfNode), end: _textPositions.GetPosition(startOfNextNode) ), parent: element ); element.Content = element.Content.Add(whitespace); DiscoveredNodes.Add(whitespace); } } }