|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE
// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL
// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED.
using System;
using System.IO;
using System.Xml;
using Microsoft.Build.BuildEngine.Shared;
namespace Microsoft.Build.BuildEngine
{
/// <summary>
/// This class has static methods to determine line numbers and column numbers for given
/// XML nodes.
/// </summary>
/// <owner>RGoel</owner>
internal static class XmlSearcher
{
/// <summary>
/// Given an XmlNode belonging to a document that lives on disk, this method determines
/// the line/column number of that node in the document. It does this by re-reading the
/// document from disk and searching for the given node.
/// </summary>
/// <param name="xmlNodeToFind">Any XmlElement or XmlAttribute node (preferably in a document that still exists on disk).</param>
/// <param name="foundLineNumber">(out) The line number where the specified node begins.</param>
/// <param name="foundColumnNumber">(out) The column number where the specified node begins.</param>
/// <returns>true if found, false otherwise. Should not throw any exceptions.</returns>
/// <owner>RGoel</owner>
internal static bool GetLineColumnByNode
(
XmlNode xmlNodeToFind,
out int foundLineNumber,
out int foundColumnNumber
)
{
// Initialize the output parameters.
foundLineNumber = 0;
foundColumnNumber = 0;
if (xmlNodeToFind == null)
{
return false;
}
// Get the filename where this XML node came from. Make sure it still
// exists on disk. If not, there's nothing we can do. Sorry.
string fileName = XmlUtilities.GetXmlNodeFile(xmlNodeToFind, String.Empty);
if ((fileName.Length == 0) || (!File.Exists(fileName)))
{
return false;
}
// Next, we need to compute the "element number" and "attribute number" of
// the given XmlNode in its original container document. Element number is
// simply a 1-based number identifying a particular XML element starting from
// the beginning of the document, ignoring depth. As you're walking the tree,
// visiting each node in order, and recursing deeper whenever possible, the Nth
// element you visit has element number N. Attribute number is simply the
// 1-based index of the attribute within the given Xml element. An attribute
// number of zero indicates that we're not searching for a particular attribute,
// and all we care about is the element as a whole.
int elementNumber;
int attributeNumber;
if (!GetElementAndAttributeNumber(xmlNodeToFind, out elementNumber, out attributeNumber))
{
return false;
}
// Now that we know what element/attribute number we're searching for, find
// it in the Xml document on disk, and grab the line/column number.
return GetLineColumnByNodeNumber(fileName, elementNumber, attributeNumber,
out foundLineNumber, out foundColumnNumber);
}
/// <summary>
/// Determines the element number and attribute number of a given XmlAttribute node,
/// or just the element number for a given XmlElement node.
/// </summary>
/// <param name="xmlNodeToFind">Any XmlElement or XmlAttribute within an XmlDocument.</param>
/// <param name="elementNumber">(out) The element number of the given node.</param>
/// <param name="attributeNumber">(out) If the given node was an XmlAttribute node, then the attribute number of that node, otherwise zero.</param>
/// <returns>true if found, false otherwise. Should not throw any exceptions.</returns>
/// <owner>RGoel</owner>
internal static bool GetElementAndAttributeNumber
(
XmlNode xmlNodeToFind,
out int elementNumber,
out int attributeNumber
)
{
ErrorUtilities.VerifyThrow(xmlNodeToFind != null, "No Xml node!");
// Initialize output parameters.
elementNumber = 0;
attributeNumber = 0;
XmlNode elementToFind;
// First determine the XmlNode in the main hierarchy to search for. If the passed-in
// node is already an XmlElement or Text node, then we already have the node
// that we're searching for. But if the passed-in node is an XmlAttribute, then
// we want to search for the XmlElement that contains that attribute.
// If the node is any other type, try the parent node. It's a better line number than no line number.
if ((xmlNodeToFind.NodeType != XmlNodeType.Element) &&
(xmlNodeToFind.NodeType != XmlNodeType.Text) &&
(xmlNodeToFind.NodeType != XmlNodeType.Attribute))
{
if (xmlNodeToFind.ParentNode != null)
{
xmlNodeToFind = xmlNodeToFind.ParentNode;
}
}
if ((xmlNodeToFind.NodeType == XmlNodeType.Element) || (xmlNodeToFind.NodeType == XmlNodeType.Text))
{
elementToFind = xmlNodeToFind;
}
else if (xmlNodeToFind.NodeType == XmlNodeType.Attribute)
{
elementToFind = ((XmlAttribute)xmlNodeToFind).OwnerElement;
ErrorUtilities.VerifyThrow(elementToFind != null, "How can an xml attribute not have a parent?");
}
else
{
// We don't support searching for anything other than XmlAttribute, XmlElement, or text node.
return false;
}
// Figure out the element number for this particular XML element, by iteratively
// visiting every single node in the XmlDocument in sequence. Start with the
// root node which is the XmlDocument node.
XmlNode xmlNode = xmlNodeToFind.OwnerDocument;
while (true)
{
// If the current node is an XmlElement or text node, bump up our variable which tracks the
// number of XmlElements visited so far.
if ((xmlNode.NodeType == XmlNodeType.Element) || (xmlNode.NodeType == XmlNodeType.Text))
{
elementNumber++;
// If the current XmlElement node is actually the one the caller wanted
// us to search for, then we've found the element number. Yippee.
if (xmlNode == elementToFind)
{
break;
}
}
// The rest of this is all about moving to the next node in the tree.
if (xmlNode.HasChildNodes)
{
// If the current node has any children, then the next node to visit
// is the first child.
xmlNode = xmlNode.FirstChild;
}
else
{
// Current node has no children. So we basically want its next
// sibling. Unless of course it has no more siblings, in which
// case we want its parent's next sibling. Unless of course its
// parent doesn't have any more siblings, in which case we want
// its parent's parent's sibling. Etc, etc.
while ((xmlNode != null) && (xmlNode.NextSibling == null))
{
xmlNode = xmlNode.ParentNode;
}
if (xmlNode == null)
{
// Oops, we reached the end of the document, so bail.
break;
}
else
{
xmlNode = xmlNode.NextSibling;
}
}
}
if (xmlNode == null)
{
// We visited every XmlElement in the document without finding the
// specific XmlElement we were supposed to. Oh well, too bad.
elementNumber = 0;
return false;
}
// If we were originally asked to actually find an XmlAttribute within
// an XmlElement, now comes Part 2. We've already found the correct
// element, so now we just need to iterate through the attributes within
// the element in order until we find the desired one.
if (xmlNodeToFind.NodeType == XmlNodeType.Attribute)
{
bool foundAttribute = false;
XmlAttribute xmlAttributeToFind = xmlNodeToFind as XmlAttribute;
foreach (XmlAttribute xmlAttribute in ((XmlElement)elementToFind).Attributes)
{
attributeNumber++;
if (xmlAttribute == xmlAttributeToFind)
{
foundAttribute = true;
break;
}
}
if (!foundAttribute)
{
return false;
}
}
return true;
}
/// <summary>
/// Read through the entire XML of a given project file, searching for the element/attribute
/// specified by element number and attribute number. Return the line number and column
/// number where it was found.
/// </summary>
/// <param name="projectFile">Path to project file on disk.</param>
/// <param name="xmlElementNumberToSearchFor">Which Xml element to search for.</param>
/// <param name="xmlAttributeNumberToSearchFor">
/// Which Xml attribute within the above Xml element to search for. Pass in zero
/// if you are searching for the Element as a whole and not a particular attribute.
/// </param>
/// <param name="foundLineNumber">(out) The line number where the given element/attribute begins.</param>
/// <param name="foundColumnNumber">The column number where the given element/attribute begins.</param>
/// <returns>true if found, false otherwise. Should not throw any exceptions.</returns>
/// <owner>RGoel</owner>
internal static bool GetLineColumnByNodeNumber
(
string projectFile,
int xmlElementNumberToSearchFor,
int xmlAttributeNumberToSearchFor,
out int foundLineNumber,
out int foundColumnNumber
)
{
ErrorUtilities.VerifyThrow(xmlElementNumberToSearchFor != 0, "No element to search for!");
ErrorUtilities.VerifyThrow(!string.IsNullOrEmpty(projectFile), "No project file!");
// Initialize output parameters.
foundLineNumber = 0;
foundColumnNumber = 0;
try
{
// We're going to need to re-read the file from disk in order to find
// the line/column number of the specified node.
using (XmlTextReader reader = new XmlTextReader(projectFile))
{
reader.DtdProcessing = DtdProcessing.Ignore;
int currentXmlElementNumber = 0;
// While we haven't reached the end of the file, and we haven't found the
// specified node ...
while (reader.Read() && (foundColumnNumber == 0) && (foundLineNumber == 0))
{
// Read to the next node. If it is an XML element or Xml text node, then ...
if ((reader.NodeType == XmlNodeType.Element) || (reader.NodeType == XmlNodeType.Text))
{
// Bump up our current XML element count.
currentXmlElementNumber++;
// Check to see if this XML element is the one we've been searching for,
// based on if the numbers match.
if (currentXmlElementNumber == xmlElementNumberToSearchFor)
{
// We've found the desired XML element. If the caller didn't care
// for a particular attribute, then we're done. Return the current
// position of the XmlTextReader.
if (0 == xmlAttributeNumberToSearchFor)
{
foundLineNumber = reader.LineNumber;
foundColumnNumber = reader.LinePosition;
if (reader.NodeType == XmlNodeType.Element)
{
// Do a minus-one here, because the XmlTextReader points us at the first
// letter of the tag name, whereas we would prefer to point at the opening
// left-angle-bracket. (Whitespace between the left-angle-bracket and
// the tag name is not allowed in XML, so this is safe.)
foundColumnNumber--;
}
}
else if (reader.MoveToFirstAttribute())
{
// Caller wants a particular attribute within the element,
// and the element does have 1 or more attributes. So let's
// try to find the right one.
int currentXmlAttributeNumber = 0;
// Loop through all the XML attributes on the current element.
do
{
// Bump the current attribute number and check to see if this
// is the one.
currentXmlAttributeNumber++;
if (currentXmlAttributeNumber == xmlAttributeNumberToSearchFor)
{
// We found the desired attribute. Return the current
// position of the XmlTextReader.
foundLineNumber = reader.LineNumber;
foundColumnNumber = reader.LinePosition;
}
} while (reader.MoveToNextAttribute() && (foundColumnNumber == 0) && (foundLineNumber == 0));
}
}
}
}
}
}
catch (XmlException)
{
// Eat the exception. If anything fails, we simply don't surface the line/column number.
}
catch (IOException)
{
// Eat the exception. If anything fails, we simply don't surface the line/column number.
}
catch (UnauthorizedAccessException)
{
// Eat the exception. If anything fails, we simply don't surface the line/column number.
}
return (foundColumnNumber != 0) && (foundLineNumber != 0);
}
}
}
|