/// <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); }
/// <summary> /// Does the location represent an element's attributes range (but not a specific attribute)? /// </summary> /// <param name="location"> /// The XML location. /// </param> /// <param name="element"> /// Receives the <see cref="XSElement"/>. /// </param> /// <returns> /// <c>true</c>, if the location represents an element; otherwise, <c>false</c>. /// </returns> public static bool IsElementBetweenAttributes(this XmlLocation location, out XSElement element) { if (location == null) { throw new ArgumentNullException(nameof(location)); } if (location.IsElementBetweenAttributes()) { element = (XSElement)location.Node; return(true); } else { element = null; return(false); } }
/// <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); }