| File: Utilities\XmlUtilities.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.Xml; using Microsoft.Build.Construction; #nullable disable namespace Microsoft.Build.Shared { /// <summary> /// This class contains utility methods for XML manipulation. /// </summary> internal static class XmlUtilities { /// <summary> /// This method renames an XML element. Well, actually you can't directly /// rename an XML element using the DOM, so what you have to do is create /// a brand new XML element with the new name, and copy over all the attributes /// and children. This method returns the new XML element object. /// If the name is the same, does nothing and returns the element passed in. /// </summary> /// <param name="oldElement"></param> /// <param name="newElementName"></param> /// <param name="xmlNamespace">Can be null if global namespace.</param> /// <returns>new/renamed element</returns> internal static XmlElementWithLocation RenameXmlElement(XmlElementWithLocation oldElement, string newElementName, string xmlNamespace) { if (String.Equals(oldElement.Name, newElementName, StringComparison.Ordinal) && String.Equals(oldElement.NamespaceURI, xmlNamespace, StringComparison.Ordinal)) { return oldElement; } XmlElementWithLocation newElement = (XmlElementWithLocation)((XmlDocumentWithLocation)oldElement.OwnerDocument).CreateElement(newElementName, xmlNamespace ?? string.Empty, oldElement.Location); // Copy over all the attributes. foreach (XmlAttribute oldAttribute in oldElement.Attributes) { XmlAttribute newAttribute = (XmlAttribute)oldAttribute.CloneNode(true); newElement.SetAttributeNode(newAttribute); } // Move over all the child nodes - no need to change their identity while (oldElement.HasChildNodes) { // This conveniently updates FirstChild and HasChildNodes on oldElement. newElement.AppendChild(oldElement.FirstChild); } // Add the new element in the same place the old element was. oldElement.ParentNode?.ReplaceChild(newElement, oldElement); return newElement; } /// <summary> /// Verifies that a name is valid for the name of an item, property, or piece of metadata. /// If it isn't, throws an ArgumentException indicating the invalid character. /// </summary> /// <remarks> /// Note that our restrictions are more stringent than the XML Standard's restrictions. /// </remarks> /// <throws>ArgumentException</throws> /// <param name="name">name to validate</param> internal static void VerifyThrowArgumentValidElementName(string name) { ArgumentException.ThrowIfNullOrEmpty(name); int firstInvalidCharLocation = LocateFirstInvalidElementNameCharacter(name); if (-1 != firstInvalidCharLocation) { ErrorUtilities.ThrowArgument("OM_NameInvalid", name, name[firstInvalidCharLocation]); } } /// <summary> /// Verifies that a name is valid for the name of an item, property, or piece of metadata. /// If it isn't, throws an InvalidProjectException indicating the invalid character. /// </summary> /// <remarks> /// Note that our restrictions are more stringent than the XML Standard's restrictions. /// </remarks> internal static void VerifyThrowProjectValidElementName(string name, IElementLocation location) { ArgumentException.ThrowIfNullOrEmpty(name); int firstInvalidCharLocation = LocateFirstInvalidElementNameCharacter(name); if (-1 != firstInvalidCharLocation) { ProjectErrorUtilities.ThrowInvalidProject(location, "NameInvalid", name, name[firstInvalidCharLocation]); } } /// <summary> /// Verifies that a name is valid for the name of an item, property, or piece of metadata. /// If it isn't, throws an InvalidProjectException indicating the invalid character. /// </summary> /// <remarks> /// Note that our restrictions are more stringent than the XML Standard's restrictions. /// </remarks> internal static void VerifyThrowProjectValidElementName(XmlElementWithLocation element) { string name = element.Name; int firstInvalidCharLocation = LocateFirstInvalidElementNameCharacter(name); if (-1 != firstInvalidCharLocation) { ProjectErrorUtilities.ThrowInvalidProject(element.Location, "NameInvalid", name, name[firstInvalidCharLocation]); } } /// <summary> /// Indicates if the given name is valid as the name of an item, property or metadatum. /// </summary> /// <remarks> /// Note that our restrictions are more stringent than those of the XML Standard. /// </remarks> /// <param name="name"></param> /// <returns>true, if name is valid</returns> internal static bool IsValidElementName(string name) { return LocateFirstInvalidElementNameCharacter(name) == -1; } /// <summary> /// Finds the location of the first invalid character, if any, in the name of an /// item, property, or piece of metadata. Returns the location of the first invalid character, or -1 if there are none. /// Valid names must match this pattern: [A-Za-z_][A-Za-z_0-9\-.]* /// Note, this is a subset of all possible valid XmlElement names: we use a subset because we also /// have to match this same set in our regular expressions, and allowing all valid XmlElement name /// characters in a regular expression would be impractical. /// </summary> /// <remarks> /// Note that our restrictions are more stringent than the XML Standard's restrictions. /// PERF: This method has to be as fast as possible, as it's called when any item, property, or piece /// of metadata is constructed. /// </remarks> internal static int LocateFirstInvalidElementNameCharacter(string name) { // Check the first character. // Try capital letters first. // Optimize slightly for success. if (!IsValidInitialElementNameCharacter(name[0])) { return 0; } // Check subsequent characters. // Try lower case letters first. // Optimize slightly for success. for (int i = 1; i < name.Length; i++) { if (!IsValidSubsequentElementNameCharacter(name[i])) { return i; } } // If we got here, the name was valid. return -1; } internal static bool IsValidInitialElementNameCharacter(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'); } internal static bool IsValidSubsequentElementNameCharacter(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_') || (c == '-'); } } }