1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Collections.Generic; 6 using System.Text; 7 using System.Xml; 8 using Microsoft.Build.BuildEngine.Shared; 9 10 namespace Microsoft.Build.BuildEngine 11 { 12 /// <summary> 13 /// Project-related Xml utilities 14 /// </summary> 15 internal class ProjectXmlUtilities 16 { 17 /// <summary> 18 /// Gets child elements, ignoring whitespace and comments. 19 /// </summary> 20 /// <exception cref="InvalidProjectFileException">Thrown for elements in the wrong namespace, or unexpected XML node types</exception> GetValidChildElements(XmlElement element)21 internal static List<XmlElement> GetValidChildElements(XmlElement element) 22 { 23 List<XmlElement> children = new List<XmlElement>(); 24 25 foreach (XmlNode child in element) 26 { 27 switch (child.NodeType) 28 { 29 case XmlNodeType.Comment: 30 case XmlNodeType.Whitespace: 31 // These are legal, and ignored 32 break; 33 34 case XmlNodeType.Element: 35 XmlElement childElement = (XmlElement)child; 36 VerifyThrowProjectValidNamespace(childElement); 37 children.Add(childElement); 38 break; 39 40 default: 41 ThrowProjectInvalidChildElement(child); 42 break; 43 } 44 } 45 return children; 46 } 47 48 /// <summary> 49 /// Throw an invalid project exception if the child is not an XmlElement 50 /// </summary> 51 /// <param name="childNode"></param> VerifyThrowProjectXmlElementChild(XmlNode childNode)52 internal static void VerifyThrowProjectXmlElementChild(XmlNode childNode) 53 { 54 if (childNode.NodeType != XmlNodeType.Element) 55 { 56 ThrowProjectInvalidChildElement(childNode); 57 } 58 } 59 60 /// <summary> 61 /// Throw an invalid project exception if there are any child elements at all 62 /// </summary> VerifyThrowProjectNoChildElements(XmlElement element)63 internal static void VerifyThrowProjectNoChildElements(XmlElement element) 64 { 65 List<XmlElement> childElements = GetValidChildElements(element); 66 if (childElements.Count > 0) 67 { 68 ThrowProjectInvalidChildElement(element.FirstChild); 69 } 70 } 71 72 /// <summary> 73 /// Throw an invalid project exception indicating that the child is not valid beneath the element 74 /// </summary> ThrowProjectInvalidChildElement(XmlNode child)75 internal static void ThrowProjectInvalidChildElement(XmlNode child) 76 { 77 ProjectErrorUtilities.ThrowInvalidProject(child, "UnrecognizedChildElement", child.Name, child.ParentNode.Name); 78 } 79 80 /// <summary> 81 /// Throws an InternalErrorException if the name of the element is not the expected name. 82 /// </summary> VerifyThrowElementName(XmlElement element, string expected)83 internal static void VerifyThrowElementName(XmlElement element, string expected) 84 { 85 ErrorUtilities.VerifyThrowNoAssert(String.Equals(element.Name, expected, StringComparison.Ordinal), "Expected " + expected + " element, got " + element.Name); 86 } 87 88 /// <summary> 89 /// Verifies an element has a valid name, and is in the MSBuild namespace, otherwise throws an InvalidProjectFileException. 90 /// </summary> VerifyThrowProjectValidNameAndNamespace(XmlElement element)91 internal static void VerifyThrowProjectValidNameAndNamespace(XmlElement element) 92 { 93 XmlUtilities.VerifyThrowProjectValidElementName(element); 94 VerifyThrowProjectValidNamespace(element); 95 } 96 97 /// <summary> 98 /// Verifies that an element is in the MSBuild namespace, otherwise throws an InvalidProjectFileException. 99 /// </summary> VerifyThrowProjectValidNamespace(XmlElement element)100 internal static void VerifyThrowProjectValidNamespace(XmlElement element) 101 { 102 if (element.Prefix.Length > 0 || 103 !String.Equals(element.NamespaceURI, XMakeAttributes.defaultXmlNamespace, StringComparison.OrdinalIgnoreCase)) 104 { 105 ProjectErrorUtilities.ThrowInvalidProject(element, "CustomNamespaceNotAllowedOnThisChildElement", element.Name, element.ParentNode.Name); 106 } 107 } 108 109 /// <summary> 110 /// If there are any attributes on the element, throws an InvalidProjectFileException complaining that the attribute is not valid on this element. 111 /// </summary> VerifyThrowProjectNoAttributes(XmlElement element)112 internal static void VerifyThrowProjectNoAttributes(XmlElement element) 113 { 114 foreach(XmlAttribute attribute in element.Attributes) 115 { 116 ThrowProjectInvalidAttribute(attribute); 117 } 118 } 119 120 /// <summary> 121 /// If the condition is false, throws an InvalidProjectFileException complaining that the attribute is not valid on this element. 122 /// </summary> VerifyThrowProjectInvalidAttribute(bool condition, XmlAttribute attribute)123 internal static void VerifyThrowProjectInvalidAttribute(bool condition, XmlAttribute attribute) 124 { 125 if (!condition) 126 { 127 ThrowProjectInvalidAttribute(attribute); 128 } 129 } 130 131 /// <summary> 132 /// Throws an InvalidProjectFileException complaining that the attribute is not valid on this element. 133 /// </summary> ThrowProjectInvalidAttribute(XmlAttribute attribute)134 internal static void ThrowProjectInvalidAttribute(XmlAttribute attribute) 135 { 136 ProjectErrorUtilities.ThrowInvalidProject(attribute, "UnrecognizedAttribute", attribute.Name, attribute.OwnerElement.Name); 137 } 138 139 /// <summary> 140 /// Get the Condition attribute, if any. Optionally, throw an invalid project exception if there are 141 /// any other attributes. 142 /// </summary> GetConditionAttribute(XmlElement element, bool verifySoleAttribute)143 internal static XmlAttribute GetConditionAttribute(XmlElement element, bool verifySoleAttribute) 144 { 145 XmlAttribute condition = null; 146 foreach (XmlAttribute attribute in element.Attributes) 147 { 148 switch (attribute.Name) 149 { 150 case XMakeAttributes.condition: 151 condition = attribute; 152 break; 153 154 // Label is only recognized by the new OM. 155 // Ignore BUT ONLY if the caller of this function is a 156 // PropertyGroup, ItemDefinitionGroup, or ItemGroup: the "Label" 157 // attribute is only legal on those element types. 158 case XMakeAttributes.label: 159 if (!( 160 String.Equals(element.Name, XMakeElements.propertyGroup, StringComparison.Ordinal) || 161 String.Equals(element.Name, XMakeElements.itemDefinitionGroup, StringComparison.Ordinal) || 162 String.Equals(element.Name, XMakeElements.itemGroup, StringComparison.Ordinal) 163 )) 164 { 165 ProjectErrorUtilities.VerifyThrowInvalidProject(!verifySoleAttribute, attribute, "UnrecognizedAttribute", attribute.Name, element.Name); 166 } 167 // otherwise, do nothing. 168 break; 169 170 default: 171 ProjectErrorUtilities.VerifyThrowInvalidProject(!verifySoleAttribute, attribute, "UnrecognizedAttribute", attribute.Name, element.Name); 172 break; 173 } 174 } 175 return condition; 176 } 177 178 /// <summary> 179 /// Sets the value of an attribute, but if the value to set is null or empty, just 180 /// removes the element. Returns the attribute, or null if it was removed. 181 /// </summary> SetOrRemoveAttribute(XmlElement element, string name, string value)182 internal static XmlAttribute SetOrRemoveAttribute(XmlElement element, string name, string value) 183 { 184 if (String.IsNullOrEmpty(value)) 185 { 186 // The caller passed in a null or an empty value. So remove the attribute. 187 element.RemoveAttribute(name); 188 return null; 189 } 190 else 191 { 192 // Set the new attribute value 193 element.SetAttribute(name, value); 194 XmlAttribute attribute = element.Attributes[name]; 195 return attribute; 196 } 197 } 198 199 /// <summary> 200 /// Returns the value of the attribute. 201 /// If the attribute is null, returns an empty string. 202 /// </summary> GetAttributeValue(XmlAttribute attribute)203 internal static string GetAttributeValue(XmlAttribute attribute) 204 { 205 return (attribute == null) ? String.Empty : attribute.Value; 206 } 207 } 208 } 209