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 Microsoft.Build.BuildEngine.Shared; 8 using System.Xml; 9 using System.Collections; 10 11 namespace Microsoft.Build.BuildEngine 12 { 13 /// <summary> 14 /// Encapsulates, as far as possible, any XML behind the child of a BuildItemGroup element 15 /// </summary> 16 internal class BuildItemGroupChildXml 17 { 18 #region Fields 19 20 // The element itself 21 private XmlElement element; 22 // The Condition attribute on it, if any, to save lookup 23 private XmlAttribute conditionAttribute; 24 // The Include attribute on it, if any, to save lookup 25 private XmlAttribute includeAttribute; 26 // The Exclude attribute on it, if any, to save lookup 27 private XmlAttribute excludeAttribute; 28 // The Remove attribute on it, if any, to save lookup 29 private XmlAttribute removeAttribute; 30 // Whether this is represents an add, remove, or modify 31 private ChildType childType; 32 33 #endregion 34 35 #region Constructors 36 BuildItemGroupChildXml(XmlDocument ownerDocument, string name, string include)37 internal BuildItemGroupChildXml(XmlDocument ownerDocument, string name, string include) 38 { 39 this.element = ownerDocument.CreateElement(name, XMakeAttributes.defaultXmlNamespace); 40 this.Include = include; 41 42 } 43 BuildItemGroupChildXml(XmlElement element, ChildType childTypeExpected)44 internal BuildItemGroupChildXml(XmlElement element, ChildType childTypeExpected) 45 { 46 ErrorUtilities.VerifyThrow(element != null, "Need an XML node."); 47 ErrorUtilities.VerifyThrowNoAssert(childTypeExpected != ChildType.Invalid, "Can't expect invalid childtype"); 48 ProjectXmlUtilities.VerifyThrowProjectValidNameAndNamespace(element); 49 50 this.element = element; 51 52 // Loop through each of the attributes on the item element. 53 foreach (XmlAttribute attribute in element.Attributes) 54 { 55 switch (attribute.Name) 56 { 57 case XMakeAttributes.include: 58 this.includeAttribute = attribute; 59 break; 60 61 case XMakeAttributes.exclude: 62 this.excludeAttribute = attribute; 63 break; 64 65 case XMakeAttributes.condition: 66 this.conditionAttribute = attribute; 67 break; 68 69 case XMakeAttributes.xmlns: 70 // We already verified that the namespace is correct 71 break; 72 73 case XMakeAttributes.remove: 74 this.removeAttribute = attribute; 75 break; 76 77 case XMakeAttributes.keepMetadata: 78 case XMakeAttributes.removeMetadata: 79 case XMakeAttributes.keepDuplicates: 80 // Ignore these - they are part of the new OM. 81 break; 82 83 default: 84 ProjectXmlUtilities.ThrowProjectInvalidAttribute(attribute); 85 break; 86 } 87 } 88 89 this.childType = ChildType.Invalid; 90 91 // Default to modify, if that's one of the child types we are told to expect. 92 if ((childTypeExpected & ChildType.BuildItemModify) == ChildType.BuildItemModify) 93 { 94 this.childType = ChildType.BuildItemModify; 95 } 96 97 if (this.includeAttribute != null) 98 { 99 ProjectXmlUtilities.VerifyThrowProjectInvalidAttribute((childTypeExpected & ChildType.BuildItemAdd) == ChildType.BuildItemAdd, includeAttribute); 100 ProjectErrorUtilities.VerifyThrowInvalidProject(Include.Length > 0, element, "MissingRequiredAttribute", XMakeAttributes.include, element.Name); 101 ProjectXmlUtilities.VerifyThrowProjectInvalidAttribute(removeAttribute == null, removeAttribute); 102 this.childType = ChildType.BuildItemAdd; 103 } 104 105 if (this.excludeAttribute != null) 106 { 107 ProjectXmlUtilities.VerifyThrowProjectInvalidAttribute((childTypeExpected & ChildType.BuildItemAdd) == ChildType.BuildItemAdd, excludeAttribute); 108 ProjectErrorUtilities.VerifyThrowInvalidProject(Include.Length > 0, element, "MissingRequiredAttribute", XMakeAttributes.include, element.Name); 109 ProjectXmlUtilities.VerifyThrowProjectInvalidAttribute(removeAttribute == null, removeAttribute); 110 this.childType = ChildType.BuildItemAdd; 111 } 112 113 if (this.removeAttribute != null) 114 { 115 ProjectXmlUtilities.VerifyThrowProjectInvalidAttribute((childTypeExpected & ChildType.BuildItemRemove) == ChildType.BuildItemRemove, removeAttribute); 116 ProjectErrorUtilities.VerifyThrowInvalidProject(Remove.Length > 0, element, "MissingRequiredAttribute", XMakeAttributes.remove, element.Name); 117 ProjectXmlUtilities.VerifyThrowProjectInvalidAttribute(includeAttribute == null, includeAttribute); 118 ProjectXmlUtilities.VerifyThrowProjectInvalidAttribute(excludeAttribute == null, excludeAttribute); 119 this.childType = ChildType.BuildItemRemove; 120 } 121 122 if (this.childType == ChildType.Invalid) 123 { 124 // So the xml wasn't consistent with any of the child types that we were told to expect. 125 // Figure out the most reasonable message to produce. 126 if ((childTypeExpected & ChildType.BuildItemAdd) == ChildType.BuildItemAdd) 127 { 128 ProjectErrorUtilities.VerifyThrowInvalidProject(Include.Length > 0, element, "MissingRequiredAttribute", XMakeAttributes.include, element.Name); 129 } 130 else if ((childTypeExpected & ChildType.BuildItemRemove) == ChildType.BuildItemRemove) 131 { 132 ProjectErrorUtilities.VerifyThrowInvalidProject(Remove.Length > 0, element, "MissingRequiredAttribute", XMakeAttributes.remove, element.Name); 133 } 134 else 135 { 136 ErrorUtilities.ThrowInternalError("Unexpected child type"); 137 } 138 } 139 140 // Validate each of the child nodes beneath the item. 141 List<XmlElement> children = ProjectXmlUtilities.GetValidChildElements(element); 142 143 if (this.childType == ChildType.BuildItemRemove && children.Count != 0) 144 { 145 ProjectErrorUtilities.ThrowInvalidProject(element, "ChildElementsBelowRemoveNotAllowed", children[0].Name); 146 } 147 148 foreach (XmlElement child in children) 149 { 150 ProjectXmlUtilities.VerifyThrowProjectValidNameAndNamespace(child); 151 152 ProjectErrorUtilities.VerifyThrowInvalidProject(!FileUtilities.IsItemSpecModifier(child.Name), child, "ItemSpecModifierCannotBeCustomMetadata", child.Name); 153 ProjectErrorUtilities.VerifyThrowInvalidProject(XMakeElements.IllegalItemPropertyNames[child.Name] == null, child, "CannotModifyReservedItemMetadata", child.Name); 154 } 155 } 156 157 #endregion 158 159 160 #region Properties 161 162 internal string Name 163 { 164 get 165 { 166 return element.Name; 167 } 168 169 set 170 { 171 element = XmlUtilities.RenameXmlElement(element, value, XMakeAttributes.defaultXmlNamespace); 172 173 // Because this actually created a new element, we have to find the attributes again 174 includeAttribute = element.Attributes[XMakeAttributes.include]; 175 excludeAttribute = element.Attributes[XMakeAttributes.exclude]; 176 conditionAttribute = element.Attributes[XMakeAttributes.condition]; 177 removeAttribute = element.Attributes[XMakeAttributes.remove]; 178 } 179 } 180 181 internal string Include 182 { 183 get 184 { 185 return ProjectXmlUtilities.GetAttributeValue(includeAttribute); 186 } 187 188 set 189 { 190 element.SetAttribute(XMakeAttributes.include, value); 191 includeAttribute = element.Attributes[XMakeAttributes.include]; 192 } 193 } 194 195 internal string Exclude 196 { 197 get 198 { 199 return ProjectXmlUtilities.GetAttributeValue(excludeAttribute); 200 } 201 202 set 203 { 204 excludeAttribute = ProjectXmlUtilities.SetOrRemoveAttribute(element, XMakeAttributes.exclude, value); 205 } 206 } 207 208 internal string Remove 209 { 210 get 211 { 212 return ProjectXmlUtilities.GetAttributeValue(removeAttribute); 213 } 214 } 215 216 internal string Condition 217 { 218 get 219 { 220 return ProjectXmlUtilities.GetAttributeValue(conditionAttribute); 221 } 222 223 set 224 { 225 conditionAttribute = ProjectXmlUtilities.SetOrRemoveAttribute(element, XMakeAttributes.condition, value); 226 } 227 } 228 229 internal XmlElement Element 230 { 231 get { return element; } 232 } 233 234 internal XmlAttribute IncludeAttribute 235 { 236 get { return includeAttribute; } 237 } 238 239 internal XmlAttribute ExcludeAttribute 240 { 241 get { return excludeAttribute; } 242 } 243 244 internal XmlAttribute RemoveAttribute 245 { 246 get { return removeAttribute; } 247 } 248 249 internal XmlAttribute ConditionAttribute 250 { 251 get { return conditionAttribute; } 252 } 253 254 internal ChildType ChildType 255 { 256 get { return childType; } 257 } 258 259 #endregion 260 261 #region Methods 262 263 /// <summary> 264 /// Gets all child elements, ignoring whitespace and comments, and any conditions 265 /// </summary> GetChildren()266 internal List<XmlElement> GetChildren() 267 { 268 List<XmlElement> children = ProjectXmlUtilities.GetValidChildElements(element); 269 return children; 270 } 271 272 /// <summary> 273 /// Removes all child elements with the specified name. 274 /// </summary> RemoveChildrenByName(string name)275 internal void RemoveChildrenByName(string name) 276 { 277 List<XmlElement> children = GetChildren(); 278 foreach (XmlElement child in children) 279 { 280 if (String.Equals(name, child.Name, StringComparison.OrdinalIgnoreCase)) 281 { 282 element.RemoveChild(child); 283 } 284 } 285 } 286 287 /// <summary> 288 /// Ensures there's a child element with the specified name and value. 289 /// Disregards any Condition attributes on the children. 290 /// If several children are already present with the specified name, removes all except the last one. 291 /// If a child is present with the specified name, does not modify it if the value is already as specified. 292 /// Returns true if the XML was meaningfully modified. 293 /// </summary> SetChildValue(string name, string value)294 internal bool SetChildValue(string name, string value) 295 { 296 bool dirty = false; 297 XmlElement childToModify = null; 298 List<XmlElement> children = GetChildren(); 299 300 foreach (XmlElement child in children) 301 { 302 if (String.Equals(name, child.Name, StringComparison.OrdinalIgnoreCase)) 303 { 304 if (childToModify != null) 305 { 306 // We already found a matching child, remove that, we'll 307 // use this later one; the later one was winning anyway, 308 // so we don't consider this dirtying the item 309 element.RemoveChild(childToModify); 310 } 311 312 childToModify = child; 313 } 314 } 315 316 if (childToModify == null) 317 { 318 // create a new child as specified 319 childToModify = element.OwnerDocument.CreateElement(name, XMakeAttributes.defaultXmlNamespace); 320 element.AppendChild(childToModify); 321 dirty = true; 322 } 323 324 // if we just added this child, or the old value and new value are different... 325 if (dirty || !String.Equals(Utilities.GetXmlNodeInnerContents(childToModify), value, StringComparison.Ordinal)) 326 { 327 // give the child the new value 328 Utilities.SetXmlNodeInnerContents(childToModify, value); 329 dirty = true; 330 } 331 332 return dirty; 333 } 334 335 #endregion 336 } 337 338 /// <summary> 339 /// Type of the item group child element 340 /// </summary> 341 internal enum ChildType 342 { 343 Invalid = 0, 344 345 /// <summary> 346 /// Regular item, with Include and possibly Exclude attributes 347 /// </summary> 348 BuildItemAdd = 1, 349 350 /// <summary> 351 /// Remove item, with Remove attribute 352 /// </summary> 353 BuildItemRemove = 2, 354 355 /// <summary> 356 /// Modify item, with no attributes (except possibly Condition) 357 /// </summary> 358 BuildItemModify = 4, 359 360 /// <summary> 361 /// Add, remove, or modify item expression 362 /// </summary> 363 Any = BuildItemAdd | BuildItemRemove | BuildItemModify 364 } 365 366 } 367