/// <summary> /// Inspect the specified location in the XML. /// </summary> /// <param name="position"> /// The location's position. /// </param> /// <returns> /// An <see cref="XmlLocation"/> representing the result of the inspection. /// </returns> public XmlLocation Inspect(Position position) { // Internally, we always use 1-based indexing because this is what the System.Xml APIs (and I'd rather keep things simple). position = position.ToOneBased(); XSNode nodeAtPosition = FindNode(position); if (nodeAtPosition == null) { return(null); } // If we're on the (seamless, i.e. overlapping) boundary between 2 nodes, select the next node. if (nodeAtPosition.NextSibling != null) { if (position == nodeAtPosition.Range.End && position == nodeAtPosition.NextSibling.Range.Start) { Serilog.Log.Debug("XmlLocator.Inspect moves to next sibling ({NodeKind} @ {NodeRange} -> {NextSiblingKind} @ {NextSiblingRange}).", nodeAtPosition.Kind, nodeAtPosition.Range, nodeAtPosition.NextSibling.Kind, nodeAtPosition.NextSibling.Range ); nodeAtPosition = nodeAtPosition.NextSibling; } } int absolutePosition = _documentPositions.GetAbsolutePosition(position); XmlLocationFlags flags = ComputeLocationFlags(nodeAtPosition, absolutePosition); XmlLocation inspectionResult = new XmlLocation(position, absolutePosition, nodeAtPosition, flags); return(inspectionResult); }
/// <summary> /// Create a new <see cref="XmlLocation"/>. /// </summary> /// <param name="position"> /// The location's position, in line / column form. /// </param> /// <param name="absolutePosition"> /// The location's (0-based) absolute position. /// </param> /// <param name="node"> /// The <see cref="XSNode"/> closest to the location's position. /// </param> /// <param name="flags"> /// <see cref="XmlLocationFlags"/> describing the location. /// </param> public XmlLocation(Position position, int absolutePosition, XSNode node, XmlLocationFlags flags) { if (node == null) { throw new ArgumentNullException(nameof(node)); } Position = position; AbsolutePosition = absolutePosition; Node = node; Flags = flags; }
/// <summary> /// Determine <see cref="XmlLocationFlags"/> for the current position. /// </summary> /// <returns> /// <see cref="XmlLocationFlags"/> describing the position. /// </returns> XmlLocationFlags ComputeLocationFlags(XSNode node, int absolutePosition) { if (node == null) { throw new ArgumentNullException(nameof(node)); } XmlLocationFlags flags = XmlLocationFlags.None; if (!node.IsValid) { flags |= XmlLocationFlags.Invalid; } switch (node) { case XSEmptyElement element: { flags |= XmlLocationFlags.Element | XmlLocationFlags.Empty; XmlEmptyElementSyntax syntaxNode = element.ElementNode; TextSpan nameSpan = syntaxNode.NameNode?.Span ?? new TextSpan(); if (nameSpan.Contains(absolutePosition)) { flags |= XmlLocationFlags.Name; } TextSpan attributesSpan = new TextSpan(); if (syntaxNode.SlashGreaterThanToken != null) // This is the most accurate way to measure the span of text where the element's attributes can be located. { attributesSpan = new TextSpan(start: syntaxNode.NameNode.Span.End, length: syntaxNode.SlashGreaterThanToken.Span.Start - syntaxNode.NameNode.Span.End); } else if (syntaxNode.AttributesNode != null) { attributesSpan = syntaxNode.AttributesNode.FullSpan; // We don't rely on the span of the syntaxNode.AttributesNode unless we have to, because it's often less accurate than the measurement above. } if (attributesSpan.Contains(absolutePosition)) { flags |= XmlLocationFlags.Attributes; } break; } case XSElementWithContent elementWithContent: { flags |= XmlLocationFlags.Element; XmlElementSyntax syntaxNode = elementWithContent.ElementNode; TextSpan nameSpan = syntaxNode.NameNode?.Span ?? new TextSpan(); if (nameSpan.Contains(absolutePosition)) { flags |= XmlLocationFlags.Name; } TextSpan startTagSpan = syntaxNode.StartTag?.Span ?? new TextSpan(); if (startTagSpan.Contains(absolutePosition)) { flags |= XmlLocationFlags.OpeningTag; } TextSpan attributesSpan = new TextSpan(); if (syntaxNode.StartTag?.GreaterThanToken != null) // This is the most accurate way to measure the span of text where the element's attributes can be located. { attributesSpan = new TextSpan(start: syntaxNode.NameNode.Span.End, length: syntaxNode.StartTag.GreaterThanToken.Span.Start - syntaxNode.NameNode.Span.End); } else if (syntaxNode.AttributesNode != null) { attributesSpan = syntaxNode.AttributesNode.FullSpan; // We don't rely on the span of the syntaxNode.AttributesNode unless we have to, because it's often less accurate than the measurement above. } if (attributesSpan.Contains(absolutePosition) || absolutePosition == attributesSpan.End) // In this particular case, we need an inclusive comparison. { flags |= XmlLocationFlags.Attributes; } TextSpan endTagSpan = syntaxNode.EndTag?.Span ?? new TextSpan(); if (endTagSpan.Contains(absolutePosition)) { flags |= XmlLocationFlags.ClosingTag; } if (absolutePosition >= startTagSpan.End && absolutePosition <= endTagSpan.Start) { flags |= XmlLocationFlags.Value; } break; } case XSInvalidElement invalidElement: { flags |= XmlLocationFlags.Element; XmlElementSyntaxBase syntaxNode = invalidElement.ElementNode; TextSpan nameSpan = syntaxNode.NameNode?.Span ?? new TextSpan(); if (nameSpan.Contains(absolutePosition)) { flags |= XmlLocationFlags.Name; } TextSpan attributesSpan = syntaxNode.AttributesNode?.FullSpan ?? new TextSpan(); if (attributesSpan.Contains(absolutePosition)) { flags |= XmlLocationFlags.Attributes; } break; } case XSAttribute attribute: { flags |= XmlLocationFlags.Attribute; XmlAttributeSyntax syntaxNode = attribute.AttributeNode; TextSpan nameSpan = syntaxNode.NameNode?.Span ?? new TextSpan(); if (nameSpan.Contains(absolutePosition)) { flags |= XmlLocationFlags.Name; } TextSpan valueSpan = syntaxNode.ValueNode?.Span ?? new TextSpan(); if (absolutePosition >= valueSpan.Start + 1 && absolutePosition <= valueSpan.End - 1) { flags |= XmlLocationFlags.Value; } break; } case XSElementText text: { flags |= XmlLocationFlags.Text | XmlLocationFlags.Element | XmlLocationFlags.Value; break; } case XSWhitespace whitespace: { flags |= XmlLocationFlags.Whitespace | XmlLocationFlags.Element | XmlLocationFlags.Value; break; } } return(flags); }