예제 #1
0
        /// <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));
        }
예제 #2
0
        /// <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
                       ));
        }
예제 #3
0
        /// <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));
        }
예제 #4
0
        /// <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));
        }
예제 #5
0
        /// <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;
        }
예제 #7
0
        /// <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;
        }
예제 #8
0
        /// <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;
        }
예제 #9
0
        /// <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));
        }
예제 #10
0
        /// <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;
        }
예제 #11
0
        /// <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);
        }
예제 #12
0
        /// <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);
        }
예제 #13
0
        /// <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);
        }
예제 #14
0
        /// <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
                       ));
        }
예제 #15
0
        /// <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));
        }
예제 #16
0
        /// <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));
        }
예제 #17
0
        /// <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;
        }
예제 #18
0
        /// <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);
        }
예제 #19
0
        /// <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);
        }
예제 #20
0
        /// <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 "&lt;&gt;" and "&lt;&lt;Element /&gt;".
        /// </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);
        }