/// <summary> /// Determine whether the <see cref="XSNode"/>'s parent path is equal to the specified <see cref="XSPath"/>. /// </summary> /// <param name="node"> /// The <see cref="XSNode"/>. /// </param> /// <param name="parentPath"> /// The <see cref="XSPath"/>. /// </param> /// <returns> /// <c>true</c>, if the node's <see cref="XSPath.Parent"/> path is equal to <paramref name="parentPath"/>; otherwise, <c>false</c>. /// </returns> public static bool HasParentPath(this XSNode node, XSPath parentPath) { if (node == null) { throw new ArgumentNullException(nameof(node)); } if (parentPath == null) { throw new ArgumentNullException(nameof(parentPath)); } XSPath nodeParentPath = node.Path.Parent; if (nodeParentPath == null) { return(false); } // The common use case for this is checking if an element or attribute matches a relative parent path (e.g. match both Project/ItemGroup and Project/Target/ItemGroup). if (parentPath.IsRelative) { return(nodeParentPath.EndsWith(parentPath)); } return(node.Path.IsChildOf(parentPath)); }
/// <summary> /// Append <see cref="XSPathSegment"/>s to the <see cref="XSPath"/>. /// </summary> /// <param name="pathSegments"> /// The <see cref="XSPathSegment"/>s to append. /// </param> /// <returns> /// The new <see cref="XSPath"/>. /// </returns> public XSPath Append(IEnumerable <XSPathSegment> pathSegments) { if (pathSegments == null) { throw new ArgumentNullException(nameof(pathSegments)); } XSPathSegment[] actualPathSegments = pathSegments.ToArray(); if (actualPathSegments.Length == 0) { return(this); } ImmutableList <XSPathSegment> ancestorSegments = ImmutableList.CreateRange( actualPathSegments.Take(actualPathSegments.Length - 1) ); ImmutableList <XSPathSegment> segments = ancestorSegments.Add( actualPathSegments[actualPathSegments.Length - 1] ); XSPath appendPath = new XSPath(ancestorSegments, segments); if (appendPath.IsAbsolute) { return(appendPath); } return(new XSPath( parent: this, child: appendPath )); }
/// <summary> /// Determine whether the <see cref="XSPath"/> has the specified path as its direct child. /// </summary> /// <param name="childPath"> /// The other <see cref="XSPath"/>. /// </param> /// <returns> /// <c>true</c>, if the other <see cref="XSPath"/>'s ancestor segments are the same as the <see cref="XSPath"/>'s trailing segments. /// </returns> public bool IsParentOf(XSPath childPath) { if (childPath == null) { throw new ArgumentNullException(nameof(childPath)); } return(childPath.IsChildOf(this)); }
/// <summary> /// Determine whether the <see cref="XSPath"/> starts the specified base path. /// </summary> /// <param name="basePath"> /// The other <see cref="XSPath"/>. /// </param> /// <returns> /// <c>true</c>, if the <see cref="XSPath"/> starts with the same segments as the other <see cref="XSPath"/>. /// </returns> public bool StartsWith(XSPath basePath) { if (basePath == null) { throw new ArgumentNullException(nameof(basePath)); } return(_segments.StartsWith(basePath._segments)); }
/// <summary> /// Does the <see cref="XSPath"/> match the specified base path? /// </summary> /// <param name="basePath"> /// The base <see cref="XSPath"/> to match. /// </param> /// <returns> /// <c>true</c>, if <paramref name="basePath"/> is absolute, and the path starts with <paramref name="basePath"/>. /// <c>true</c>, if <paramref name="basePath"/> is relative, and the path ends with <paramref name="basePath"/>. /// Otherwise, <c>false</c>. /// </returns> public bool Matches(XSPath basePath) { if (basePath == null) { throw new ArgumentNullException(nameof(basePath)); } return(basePath.IsAbsolute ? StartsWith(basePath) : EndsWith(basePath)); }
/// <summary> /// Create new <see cref="XSElementText"/>. /// </summary> /// <param name="textNode"> /// The <see cref="XmlTextSyntax"/> represented by the <see cref="XSElementText"/>. /// </param> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the text. /// </param> /// <param name="element"> /// The element whose content includes the text. /// </param> public XSElementText(XmlTextSyntax textNode, Range range, XSElement element) : base(textNode, range) { if (element == null) { throw new ArgumentNullException(nameof(element)); } Element = element; _path = Element.Path + Name; }
/// <summary> /// Create a new <see cref="XSElement"/>. /// </summary> /// <param name="element"> /// The <see cref="XmlElementSyntaxBase"/> represented by the <see cref="XSElement"/>. /// </param> /// <param name="range"> /// The range, within the source text, spanned by the element. /// </param> /// <param name="nameRange"> /// The range, within the source text, spanned by the element's name. /// </param> /// <param name="attributesRange"> /// The range, within the source text, spanned by the element's attributes. /// </param> /// <param name="parent"> /// The <see cref="XSElement"/>'s parent element (if any). /// </param> protected XSElement(XmlElementSyntaxBase element, Range range, Range nameRange, Range attributesRange, XSElement parent) : base(element, range) { NameRange = nameRange; AttributesRange = attributesRange; ParentElement = parent; XSPath parentPath = parent?.Path ?? XSPath.Root; _path = parentPath + Name; }
/// <summary> /// Create a new <see cref="XSAttribute"/>. /// </summary> /// <param name="attribute"> /// The <see cref="XmlAttributeSyntax"/> represented by the <see cref="XSAttribute"/>. /// </param> /// <param name="element"> /// The element that contains the attribute. /// </param> /// <param name="attributeRange"> /// The <see cref="Range"/>, within the source text, spanned by the attribute. /// </param> /// <param name="nameRange"> /// The <see cref="Range"/>, within the source text, spanned by the attribute's name. /// </param> /// <param name="valueRange"> /// The <see cref="Range"/>, within the source text, spanned by the attribute's value. /// </param> public XSAttribute(XmlAttributeSyntax attribute, XSElement element, Range attributeRange, Range nameRange, Range valueRange) : base(attribute, attributeRange) { if (element == null) { throw new ArgumentNullException(nameof(element)); } NameRange = nameRange; ValueRange = valueRange; Element = element; _path = Element.Path + Name; }
/// <summary> /// Determine whether the <see cref="XSNode"/>'s path ends with the specified <see cref="XSPath"/>. /// </summary> /// <param name="node"> /// The <see cref="XSNode"/>. /// </param> /// <param name="path"> /// The <see cref="XSPath"/>. /// </param> /// <returns> /// <c>true</c>, if <see cref="XSNode.Path"/> ends with <paramref name="path"/>; otherwise, <c>false</c>. /// </returns> public static bool PathEndsWith(this XSNode node, XSPath path) { if (node == null) { throw new ArgumentNullException(nameof(node)); } if (path == null) { throw new ArgumentNullException(nameof(path)); } return(node.Path.EndsWith(path)); }
/// <summary> /// Create new <see cref="XSWhitespace"/>. /// </summary> /// <param name="range"> /// The <see cref="Range"/>, within the source text, spanned by the whitespace. /// </param> /// <param name="parent"> /// The <see cref="XSElement"/> that contains the whitespace. /// </param> public XSWhitespace(Range range, XSElement parent) : base(range) { if (parent == null) { throw new ArgumentNullException(nameof(parent)); } ParentElement = parent; XSPath parentPath = parent?.Path ?? XSPath.Root; _path = parentPath + Name; }
/// <summary> /// Concatenate an <see cref="XSPathSegment"/> and a string to create an <see cref="XSPath"/>. /// </summary> /// <param name="left"> /// The left-hand <see cref="XSPathSegment"/>. /// </param> /// <param name="right"> /// The right-hand path segment. /// </param> /// <returns> /// The new <see cref="XSPath"/>. /// </returns> public static XSPath operator +(XSPathSegment left, string right) { if (left == null) { throw new ArgumentNullException(nameof(left)); } if (right == null) { throw new ArgumentNullException(nameof(right)); } return(XSPath.FromSegment(left) + right); }
/// <summary> /// Create a new <see cref="XSPath"/> by adding a leaf <see cref="XSPathSegment"/> onto the parent <see cref="XSPath"/>'s list of segments. /// </summary> /// <param name="parent"> /// The parent <see cref="XSPath"/>. /// </param> /// <param name="leaf"> /// The leaf <see cref="XSPathSegment"/> to append. /// </param> XSPath(XSPath parent, XSPathSegment leaf) { if (parent == null) { throw new ArgumentNullException(nameof(parent)); } if (leaf == null) { throw new ArgumentNullException(nameof(leaf)); } _parent = parent; _ancestorSegments = _parent._segments;; _segments = _ancestorSegments.Add(leaf); }
/// <summary> /// Create a new <see cref="XSPath"/> by appending a child path to a parent path. /// </summary> /// <param name="parent"> /// The parent <see cref="XSPath"/>. /// </param> /// <param name="child"> /// The child <see cref="XSPath"/>. /// </param> XSPath(XSPath parent, XSPath child) { if (parent == null) { throw new ArgumentNullException(nameof(parent)); } if (child == null) { throw new ArgumentNullException(nameof(child)); } _parent = parent; _ancestorSegments = parent._segments.AddRange(child._ancestorSegments); _segments = _ancestorSegments.Add(child.Leaf); }
/// <summary> /// Append an <see cref="XSPath"/> to the <see cref="XSPath"/>. /// </summary> /// <param name="path"> /// The <see cref="XSPath"/> to append. /// </param> /// <returns> /// The new <see cref="XSPath"/>. /// </returns> public XSPath Append(XSPath path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } if (path.IsAbsolute) { return(path); } return(new XSPath( parent: this, child: path )); }
/// <summary> /// Determine whether the <see cref="XSPath"/> has the specified path as its direct ancestor. /// </summary> /// <param name="parentPath"> /// The other <see cref="XSPath"/>. /// </param> /// <returns> /// <c>true</c>, if the other <see cref="XSPath"/>'s trailing segments are the same as the <see cref="XSPath"/>'s ancestor segments. /// </returns> public bool IsChildOf(XSPath parentPath) { if (parentPath == null) { throw new ArgumentNullException(nameof(parentPath)); } if (IsAbsolute && !parentPath.IsAbsolute) { return(false); // Logical short-circuit: absolute path cannot be a child of another path. } // Special case: any relative path is considered a child of the root. if (IsRelative && parentPath == Root) { return(true); } return(_ancestorSegments.EndsWith(parentPath._segments)); }
/// <summary> /// Determine whether the <see cref="XSPath"/> has ends with the the specified path. /// </summary> /// <param name="ancestorPath"> /// The other <see cref="XSPath"/>. /// </param> /// <returns> /// <c>true</c>, if the other <see cref="XSPath"/>'s trailing segments are the same as the <see cref="XSPath"/>'s segments. /// </returns> public bool EndsWith(XSPath ancestorPath) { if (ancestorPath == null) { throw new ArgumentNullException(nameof(ancestorPath)); } if (ancestorPath.IsAbsolute) { return(StartsWith(ancestorPath)); } if (_ancestorSegments.Count == 0 && Leaf == ancestorPath.Leaf) { return(true); } return(_segments.EndsWith(ancestorPath._segments)); }
/// <summary> /// Create a new <see cref="XSElement"/>. /// </summary> /// <param name="element"> /// The <see cref="XmlElementSyntaxBase"/> represented by the <see cref="XSElement"/>. /// </param> /// <param name="range"> /// The range, within the source text, spanned by the element. /// </param> /// <param name="nameRange"> /// The range, within the source text, spanned by the element's name. /// </param> /// <param name="attributesRange"> /// The range, within the source text, spanned by the element's attributes. /// </param> /// <param name="parent"> /// The <see cref="XSElement"/>'s parent element (if any). /// </param> protected XSElement(XmlElementSyntaxBase element, Range range, Range nameRange, Range attributesRange, XSElement parent) : base(element, range) { if (nameRange == null) { throw new ArgumentNullException(nameof(nameRange)); } if (nameRange == null) { throw new ArgumentNullException(nameof(nameRange)); } NameRange = nameRange; AttributesRange = attributesRange; ParentElement = parent; XSPath parentPath = parent?.Path ?? XSPath.Root; _path = parentPath + Name; }
/// <summary> /// Does the location represent a place where an attribute value can be created or replaced by a completion? /// </summary> /// <param name="location"> /// The XML location. /// </param> /// <param name="targetAttribute"> /// The attribute (if any) whose value will be replaced by the completion. /// </param> /// <param name="onElementWithPath"> /// If specified, attribute's element must have the specified path. /// </param> /// <param name="forAttributeNamed"> /// If specified, the attribute must have one of the specified names. /// </param> /// <returns> /// <c>true</c>, if the location represents an attribute whose value can be replaced by a completion; otherwise, <c>false</c>. /// </returns> public static bool CanCompleteAttributeValue(this XmlLocation location, out XSAttribute targetAttribute, XSPath onElementWithPath = null, params string[] forAttributeNamed) { if (location == null) { throw new ArgumentNullException(nameof(location)); } targetAttribute = null; XSAttribute attribute; if (!location.IsAttributeValue(out attribute)) { return(false); } if (onElementWithPath != null && !attribute.HasParentPath(onElementWithPath)) { return(false); } if (forAttributeNamed.Length > 0 && Array.IndexOf(forAttributeNamed, attribute.Name) == -1) { return(false); } targetAttribute = attribute; return(true); }
/// <summary> /// Does the location represent a place where an attribute can be created or replaced by a completion? /// </summary> /// <param name="location"> /// The XML location. /// </param> /// <param name="element"> /// The element whose attribute will be completed. /// </param> /// <param name="replaceAttribute"> /// The attribute (if any) that will be replaced by the completion. /// </param> /// <param name="needsPadding"> /// An <see cref="PaddingType"/> value indicating what sort of padding (if any) is required before / after the attribute. /// </param> /// <param name="onElementWithPath"> /// If specified, the location's element must have the specified path. /// </param> /// <returns> /// <c>true</c>, if the location represents an element that can be replaced by completion; otherwise, <c>false</c>. /// </returns> public static bool CanCompleteAttribute(this XmlLocation location, out XSElement element, out XSAttribute replaceAttribute, out PaddingType needsPadding, XSPath onElementWithPath = null) { if (location == null) { throw new ArgumentNullException(nameof(location)); } replaceAttribute = null; needsPadding = PaddingType.None; XSAttribute attribute; if (location.IsAttribute(out attribute) && !location.IsValue()) { element = attribute.Element; if (location.Position == attribute.Start) { // Since we're on an existing attribute, we'll add a new attribute after it. attribute = null; needsPadding = PaddingType.Trailing; } } else if (location.IsElementBetweenAttributes(out element)) { if (element.Attributes.Count > 0) { // Check if we're directly before an attribute. foreach (XSAttribute currentAttribute in element.Attributes) { if (location.Position == currentAttribute.End) { needsPadding = PaddingType.Leading; } else { continue; } break; } } else if (location.Position == element.NameRange.End) // We're directly after the name. { needsPadding = PaddingType.Leading; } } else if (location.IsElement(out element)) { // Check if we're directly after the name. if (location.Position != element.NameRange.End) { return(false); } needsPadding = PaddingType.Leading; } else { return(false); } if (onElementWithPath != null && !element.Path.Matches(onElementWithPath)) { return(false); } replaceAttribute = attribute; return(true); }
/// <summary> /// Does the location represent a place where an element can be created or replaced by a completion? /// </summary> /// <param name="location"> /// The XML location. /// </param> /// <param name="replaceElement"> /// The element (if any) that will be replaced by the completion. /// </param> /// <param name="parentPath"> /// If specified, the location's node's parent must have the specified <see cref="XSPath"/>. /// </param> /// <returns> /// <c>true</c>, if the location represents an element that can be replaced by completion; otherwise, <c>false</c>. /// </returns> /// <remarks> /// We can replace "<>" and "<<Element />". /// </remarks> public static bool CanCompleteElement(this XmlLocation location, out XSElement replaceElement, XSPath parentPath = null) { if (location == null) { throw new ArgumentNullException(nameof(location)); } replaceElement = null; // Simplest case - we're on whitespace so we can simply insert an element without replacing anything. if (location.IsWhitespace(out XSWhitespace whitespace) && (parentPath == null || whitespace.HasParentPath(parentPath))) { return(true); } XSElement element; if (!location.IsElement(out element)) { return(false); } if (location.IsElementBetweenAttributes()) { return(false); } // Check if we need to perform a substition of the element to be replaced (the common case is simply replacing an existing element or partial element). if (element.IsValid) { // Do we have an invalid parent (e.g. "<<Foo />", which yields invalid element named "" with child element named "Foo")? bool isParentValid = element.ParentElement?.IsValid ?? true; if (!isParentValid) { // We can't handle the case where the parent isn't on the same line as the child (since that's not the case outlined above). if (element.ParentElement.Start.LineNumber != location.Node.Start.LineNumber) { return(false); } // But we *can* handle this case by targeting the "parent" element (since that's the element we're actually after anyway). if (location.Node.Start.ColumnNumber - element.ParentElement.Start.ColumnNumber == 1) { element = element.ParentElement; } } } if (parentPath != null && !element.HasParentPath(parentPath)) { return(false); } replaceElement = element; return(true); }