|
// 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;
}
}
}
|