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