| File: ElementLocation\XmlElementWithLocation.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics; using System.Xml; using Microsoft.Build.ObjectModelRemoting; #nullable disable namespace Microsoft.Build.Construction { /// <summary> /// Derivation of XmlElement to implement IXmlLineInfo /// </summary> /// <remarks> /// It would be nice to add some helper overloads of base class members that /// downcast their return values to XmlElement/AttributeWithLocation. However /// C# doesn't currently allow covariance in method overloading, only on delegates. /// The caller must bravely downcast. /// </remarks> internal class XmlElementWithLocation : XmlElement, IXmlLineInfo, ILinkedXml { /// <summary> /// Line, column, file information /// </summary> private ElementLocation _elementLocation; /// <summary> /// Constructor without location information /// </summary> public XmlElementWithLocation(string prefix, string localName, string namespaceURI, XmlDocumentWithLocation document) : this(prefix, localName, namespaceURI, document, 0, 0) { } /// <summary> /// Constructor with location information /// </summary> public XmlElementWithLocation(string prefix, string localName, string namespaceURI, XmlDocumentWithLocation document, int lineNumber, int columnNumber) : base(prefix, localName, namespaceURI, document) { // Subtract one, just to give the same value as the old code did. // In the past we pointed to the column of the open angle bracket whereas the XmlTextReader points to the first character of the element name. // In well formed XML these are always adjacent on the same line, so it's safe to subtract one. // If we're loading from a stream it's zero, so don't subtract one. XmlDocumentWithLocation documentWithLocation = (XmlDocumentWithLocation)document; int adjustedColumn = (columnNumber == 0) ? columnNumber : columnNumber - 1; _elementLocation = ElementLocation.Create(documentWithLocation.FullPath, lineNumber, adjustedColumn); } ProjectElementLink ILinkedXml.Link => null; XmlElementWithLocation ILinkedXml.Xml => this; /// <summary> /// Returns the line number if available, else 0. /// IXmlLineInfo member. /// </summary> public int LineNumber { [DebuggerStepThrough] get { return Location.Line; } } /// <summary> /// Returns the column number if available, else 0. /// IXmlLineInfo member. /// </summary> public int LinePosition { [DebuggerStepThrough] get { return Location.Column; } } /// <summary> /// Provides an ElementLocation for this element, using the path to the file containing /// this element as the project file entry. /// Element location may be incorrect, if it was not loaded from disk. /// Does not return null. /// </summary> /// <remarks> /// Should have at least the file name if the containing project has been given a file name, /// even if it wasn't loaded from disk, or has been edited since. That's because we set that /// path on our XmlDocumentWithLocation wrapper class. /// </remarks> internal ElementLocation Location { get { // Caching the element location object saves significant memory XmlDocumentWithLocation ownerDocumentWithLocation = (XmlDocumentWithLocation)OwnerDocument; if (!String.Equals(_elementLocation.File, ownerDocumentWithLocation.FullPath ?? String.Empty, StringComparison.OrdinalIgnoreCase)) { _elementLocation = ElementLocation.Create(ownerDocumentWithLocation.FullPath, _elementLocation.Line, _elementLocation.Column); } return _elementLocation; } } /// <summary> /// Whether location is available. /// IXmlLineInfo member. /// </summary> public bool HasLineInfo() { return Location.Line != 0; } /// <summary> /// Returns the XmlAttribute with the specified name or null if a matching attribute was not found. /// </summary> public XmlAttributeWithLocation GetAttributeWithLocation(string name) { XmlAttribute attribute = GetAttributeNode(name); if (attribute == null) { return null; } return (XmlAttributeWithLocation)attribute; } /// <summary> /// Overridden to convert the display of the element from open form (separate open and closed tags) to closed form /// (single closed tag) if the last child is being removed. This is simply for tidiness of the project file. /// For example, removing the only piece of metadata from an item will leave behind one tag instead of two. /// </summary> public override XmlNode RemoveChild(XmlNode oldChild) { XmlNode result = base.RemoveChild(oldChild); if (!HasChildNodes) { IsEmpty = true; } return result; } /// <summary> /// Gets the location of any attribute on this element with the specified name. /// If there is no such attribute, returns null. /// </summary> internal ElementLocation GetAttributeLocation(string name) { XmlAttributeWithLocation attributeWithLocation = GetAttributeWithLocation(name); return attributeWithLocation?.Location; } } }