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 // </copyright> 5 // <summary>A container for project elements.</summary> 6 //----------------------------------------------------------------------- 7 8 using System; 9 using System.Text; 10 using System.Collections.Generic; 11 using System.Linq; 12 using System.Xml; 13 using System.Diagnostics; 14 using Microsoft.Build.Framework; 15 using Microsoft.Build.Shared; 16 using System.Collections.ObjectModel; 17 using Microsoft.Build.Construction; 18 using Microsoft.Build.Collections; 19 using Microsoft.Build.Internal; 20 using System.Reflection; 21 22 namespace Microsoft.Build.Construction 23 { 24 /// <summary> 25 /// A container for project elements 26 /// </summary> 27 public abstract class ProjectElementContainer : ProjectElement 28 { 29 const string DEFAULT_INDENT = " "; 30 31 /// <summary> 32 /// Number of children of any kind 33 /// </summary> 34 private int _count; 35 36 /// <summary> 37 /// Constructor called by ProjectRootElement only. 38 /// XmlElement is set directly after construction. 39 /// </summary> 40 /// <comment> 41 /// Should ideally be protected+internal. 42 /// </comment> ProjectElementContainer()43 internal ProjectElementContainer() 44 : base() 45 { 46 } 47 48 /// <summary> 49 /// Constructor called by derived classes, except from ProjectRootElement. 50 /// Parameters may not be null, except parent. 51 /// </summary> 52 /// <comment> 53 /// Should ideally be protected+internal. 54 /// </comment> ProjectElementContainer(XmlElement xmlElement, ProjectElementContainer parent, ProjectRootElement containingProject)55 internal ProjectElementContainer(XmlElement xmlElement, ProjectElementContainer parent, ProjectRootElement containingProject) 56 : base(xmlElement, parent, containingProject) 57 { 58 } 59 60 /// <summary> 61 /// Get an enumerator over all children, gotten recursively. 62 /// Walks the children in a depth-first manner. 63 /// </summary> 64 public IEnumerable<ProjectElement> AllChildren 65 { 66 get { return GetChildrenRecursively(); } 67 } 68 69 /// <summary> 70 /// Get enumerable over all the children 71 /// </summary> 72 public ICollection<ProjectElement> Children 73 { 74 [DebuggerStepThrough] 75 get 76 { 77 return new Microsoft.Build.Collections.ReadOnlyCollection<ProjectElement> 78 ( 79 new ProjectElementSiblingEnumerable(FirstChild) 80 ); 81 } 82 } 83 84 /// <summary> 85 /// Get enumerable over all the children, starting from the last 86 /// </summary> 87 public ICollection<ProjectElement> ChildrenReversed 88 { 89 [DebuggerStepThrough] 90 get 91 { 92 return new Microsoft.Build.Collections.ReadOnlyCollection<ProjectElement> 93 ( 94 new ProjectElementSiblingEnumerable(LastChild, false /* reverse */) 95 ); 96 } 97 } 98 99 /// <summary> 100 /// Number of children of any kind 101 /// </summary> 102 public int Count 103 { 104 [DebuggerStepThrough] 105 get 106 { return _count; } 107 } 108 109 /// <summary> 110 /// First child, if any, otherwise null. 111 /// Cannot be set directly; use <see cref="PrependChild">PrependChild()</see>. 112 /// </summary> 113 public ProjectElement FirstChild 114 { 115 [DebuggerStepThrough] 116 get; 117 [DebuggerStepThrough] 118 private set; 119 } 120 121 /// <summary> 122 /// Last child, if any, otherwise null. 123 /// Cannot be set directly; use <see cref="AppendChild">AppendChild()</see>. 124 /// </summary> 125 public ProjectElement LastChild 126 { 127 [DebuggerStepThrough] 128 get; 129 [DebuggerStepThrough] 130 private set; 131 } 132 133 /// <summary> 134 /// Insert the child after the reference child. 135 /// Reference child if provided must be parented by this element. 136 /// Reference child may be null, in which case this is equivalent to <see cref="PrependChild">PrependChild(child)</see>. 137 /// Throws if the parent is not itself parented. 138 /// Throws if the reference node does not have this node as its parent. 139 /// Throws if the node to add is already parented. 140 /// Throws if the node to add was created from a different project than this node. 141 /// </summary> 142 /// <remarks> 143 /// Semantics are those of XmlNode.InsertAfterChild. 144 /// </remarks> InsertAfterChild(ProjectElement child, ProjectElement reference)145 public void InsertAfterChild(ProjectElement child, ProjectElement reference) 146 { 147 ErrorUtilities.VerifyThrowArgumentNull(child, "child"); 148 149 if (reference == null) 150 { 151 PrependChild(child); 152 return; 153 } 154 155 VerifyForInsertBeforeAfterFirst(child, reference); 156 157 child.VerifyThrowInvalidOperationAcceptableLocation(this, reference, reference.NextSibling); 158 159 child.Parent = this; 160 161 if (LastChild == reference) 162 { 163 LastChild = child; 164 } 165 166 child.PreviousSibling = reference; 167 child.NextSibling = reference.NextSibling; 168 169 reference.NextSibling = child; 170 171 if (child.NextSibling != null) 172 { 173 ErrorUtilities.VerifyThrow(child.NextSibling.PreviousSibling == reference, "Invalid structure"); 174 child.NextSibling.PreviousSibling = child; 175 } 176 177 AddToXml(child); 178 179 _count++; 180 MarkDirty("Insert element {0}", child.ElementName); 181 } 182 183 /// <summary> 184 /// Insert the child before the reference child. 185 /// Reference child if provided must be parented by this element. 186 /// Reference child may be null, in which case this is equivalent to <see cref="AppendChild">AppendChild(child)</see>. 187 /// Throws if the parent is not itself parented. 188 /// Throws if the reference node does not have this node as its parent. 189 /// Throws if the node to add is already parented. 190 /// Throws if the node to add was created from a different project than this node. 191 /// </summary> 192 /// <remarks> 193 /// Semantics are those of XmlNode.InsertBeforeChild. 194 /// </remarks> InsertBeforeChild(ProjectElement child, ProjectElement reference)195 public void InsertBeforeChild(ProjectElement child, ProjectElement reference) 196 { 197 ErrorUtilities.VerifyThrowArgumentNull(child, "child"); 198 199 if (reference == null) 200 { 201 AppendChild(child); 202 return; 203 } 204 205 VerifyForInsertBeforeAfterFirst(child, reference); 206 207 child.VerifyThrowInvalidOperationAcceptableLocation(this, reference.PreviousSibling, reference); 208 209 child.Parent = this; 210 211 if (FirstChild == reference) 212 { 213 FirstChild = child; 214 } 215 216 child.PreviousSibling = reference.PreviousSibling; 217 child.NextSibling = reference; 218 219 reference.PreviousSibling = child; 220 221 if (child.PreviousSibling != null) 222 { 223 ErrorUtilities.VerifyThrow(child.PreviousSibling.NextSibling == reference, "Invalid structure"); 224 child.PreviousSibling.NextSibling = child; 225 } 226 227 AddToXml(child); 228 229 _count++; 230 MarkDirty("Insert element {0}", child.ElementName); 231 } 232 233 /// <summary> 234 /// Inserts the provided element as the last child. 235 /// Throws if the parent is not itself parented. 236 /// Throws if the node to add is already parented. 237 /// Throws if the node to add was created from a different project than this node. 238 /// </summary> AppendChild(ProjectElement child)239 public void AppendChild(ProjectElement child) 240 { 241 if (LastChild == null) 242 { 243 AddInitialChild(child); 244 } 245 else 246 { 247 ErrorUtilities.VerifyThrow(FirstChild != null, "Invalid structure"); 248 InsertAfterChild(child, LastChild); 249 } 250 } 251 252 /// <summary> 253 /// Inserts the provided element as the first child. 254 /// Throws if the parent is not itself parented. 255 /// Throws if the node to add is already parented. 256 /// Throws if the node to add was created from a different project than this node. 257 /// </summary> PrependChild(ProjectElement child)258 public void PrependChild(ProjectElement child) 259 { 260 if (FirstChild == null) 261 { 262 AddInitialChild(child); 263 } 264 else 265 { 266 ErrorUtilities.VerifyThrow(LastChild != null, "Invalid structure"); 267 InsertBeforeChild(child, FirstChild); 268 return; 269 } 270 } 271 272 /// <summary> 273 /// Removes the specified child. 274 /// Throws if the child is not currently parented by this object. 275 /// This is O(1). 276 /// May be safely called during enumeration of the children. 277 /// </summary> 278 /// <remarks> 279 /// This is actually safe to call during enumeration of children, because it 280 /// doesn't bother to clear the child's NextSibling (or PreviousSibling) pointers. 281 /// To determine whether a child is unattached, check whether its parent is null, 282 /// or whether its NextSibling and PreviousSibling point back at it. 283 /// DO NOT BREAK THIS VERY USEFUL SAFETY CONTRACT. 284 /// </remarks> RemoveChild(ProjectElement child)285 public void RemoveChild(ProjectElement child) 286 { 287 ErrorUtilities.VerifyThrowArgumentNull(child, "child"); 288 289 ErrorUtilities.VerifyThrowArgument(child.Parent == this, "OM_NodeNotAlreadyParentedByThis"); 290 291 child.ClearParent(); 292 293 if (child.PreviousSibling != null) 294 { 295 child.PreviousSibling.NextSibling = child.NextSibling; 296 } 297 298 if (child.NextSibling != null) 299 { 300 child.NextSibling.PreviousSibling = child.PreviousSibling; 301 } 302 303 if (Object.ReferenceEquals(child, FirstChild)) 304 { 305 FirstChild = child.NextSibling; 306 } 307 308 if (Object.ReferenceEquals(child, LastChild)) 309 { 310 LastChild = child.PreviousSibling; 311 } 312 313 RemoveFromXml(child); 314 315 _count--; 316 MarkDirty("Remove element {0}", child.ElementName); 317 } 318 319 /// <summary> 320 /// Remove all the children, if any. 321 /// </summary> 322 /// <remarks> 323 /// It is safe to modify the children in this way 324 /// during enumeration. See <cref see="RemoveChild">RemoveChild</cref>. 325 /// </remarks> RemoveAllChildren()326 public void RemoveAllChildren() 327 { 328 foreach (ProjectElement child in Children) 329 { 330 RemoveChild(child); 331 } 332 } 333 334 /// <summary> 335 /// Applies properties from the specified type to this instance. 336 /// </summary> 337 /// <param name="element">The element to act as a template to copy from.</param> DeepCopyFrom(ProjectElementContainer element)338 public virtual void DeepCopyFrom(ProjectElementContainer element) 339 { 340 ErrorUtilities.VerifyThrowArgumentNull(element, "element"); 341 ErrorUtilities.VerifyThrowArgument(this.GetType().IsEquivalentTo(element.GetType()), "element"); 342 343 if (this == element) 344 { 345 return; 346 } 347 348 this.RemoveAllChildren(); 349 this.CopyFrom(element); 350 351 foreach (var child in element.Children) 352 { 353 var childContainer = child as ProjectElementContainer; 354 if (childContainer != null) 355 { 356 childContainer.DeepClone(this.ContainingProject, this); 357 } 358 else 359 { 360 this.AppendChild(child.Clone(this.ContainingProject)); 361 } 362 } 363 } 364 365 /// <summary> 366 /// Appends the provided child. 367 /// Does not dirty the project, does not add an element, does not set the child's parent, 368 /// and does not check the parent's future siblings and parent are acceptable. 369 /// Called during project load, when the child can be expected to 370 /// already have a parent and its element is already connected to the 371 /// parent's element. 372 /// All that remains is to set FirstChild/LastChild and fix up the linked list. 373 /// </summary> AppendParentedChildNoChecks(ProjectElement child)374 internal void AppendParentedChildNoChecks(ProjectElement child) 375 { 376 ErrorUtilities.VerifyThrow(child.Parent == this, "Expected parent already set"); 377 ErrorUtilities.VerifyThrow(child.PreviousSibling == null && child.NextSibling == null, "Invalid structure"); 378 379 if (LastChild == null) 380 { 381 FirstChild = child; 382 } 383 else 384 { 385 child.PreviousSibling = LastChild; 386 LastChild.NextSibling = child; 387 } 388 389 LastChild = child; 390 391 _count++; 392 } 393 394 /// <summary> 395 /// Returns a clone of this project element and all its children. 396 /// </summary> 397 /// <param name="factory">The factory to use for creating the new instance.</param> 398 /// <param name="parent">The parent to append the cloned element to as a child.</param> 399 /// <returns>The cloned element.</returns> DeepClone(ProjectRootElement factory, ProjectElementContainer parent)400 protected internal virtual ProjectElementContainer DeepClone(ProjectRootElement factory, ProjectElementContainer parent) 401 { 402 var clone = (ProjectElementContainer)this.Clone(factory); 403 if (parent != null) 404 { 405 parent.AppendChild(clone); 406 } 407 408 foreach (var child in this.Children) 409 { 410 var childContainer = child as ProjectElementContainer; 411 if (childContainer != null) 412 { 413 childContainer.DeepClone(clone.ContainingProject, clone); 414 } 415 else 416 { 417 clone.AppendChild(child.Clone(clone.ContainingProject)); 418 } 419 } 420 421 return clone; 422 } 423 SetElementAsAttributeValue(ProjectElement child)424 private void SetElementAsAttributeValue(ProjectElement child) 425 { 426 // Assumes that child.ExpressedAsAttribute is true 427 Debug.Assert(child.ExpressedAsAttribute, nameof(SetElementAsAttributeValue) + " method requires that " + 428 nameof(child.ExpressedAsAttribute) + " property of child is true"); 429 430 string value = Microsoft.Build.Internal.Utilities.GetXmlNodeInnerContents(child.XmlElement); 431 ProjectXmlUtilities.SetOrRemoveAttribute(XmlElement, child.XmlElement.Name, value); 432 } 433 434 /// <summary> 435 /// If child "element" is actually represented as an attribute, update the value in the corresponding Xml attribute 436 /// </summary> 437 /// <param name="child">A child element which might be represented as an attribute</param> UpdateElementValue(ProjectElement child)438 internal void UpdateElementValue(ProjectElement child) 439 { 440 if (child.ExpressedAsAttribute) 441 { 442 SetElementAsAttributeValue(child); 443 } 444 } 445 446 /// <summary> 447 /// Adds a ProjectElement to the Xml tree 448 /// </summary> 449 /// <param name="child">A child to add to the Xml tree, which has already been added to the ProjectElement tree</param> 450 /// <remarks> 451 /// The MSBuild construction APIs keep a tree of ProjectElements and a parallel Xml tree which consists of 452 /// objects from System.Xml. This is a helper method which adds an XmlElement or Xml attribute to the Xml 453 /// tree after the corresponding ProjectElement has been added to the construction API tree, and fixes up 454 /// whitespace as necessary. 455 /// </remarks> AddToXml(ProjectElement child)456 internal void AddToXml(ProjectElement child) 457 { 458 if (child.ExpressedAsAttribute) 459 { 460 // todo children represented as attributes need to be placed in order too 461 // Assume that the name of the child has already been validated to conform with rules in XmlUtilities.VerifyThrowArgumentValidElementName 462 463 // Make sure we're not trying to add multiple attributes with the same name 464 ProjectErrorUtilities.VerifyThrowInvalidProject(!XmlElement.HasAttribute(child.XmlElement.Name), 465 XmlElement.Location, "InvalidChildElementDueToDuplication", child.XmlElement.Name, ElementName); 466 467 SetElementAsAttributeValue(child); 468 } 469 else 470 { 471 // We want to add the XmlElement to the same position in the child list as the corresponding ProjectElement. 472 // Depending on whether the child ProjectElement has a PreviousSibling or a NextSibling, we may need to 473 // use the InsertAfter, InsertBefore, or AppendChild methods to add it in the right place. 474 // 475 // Also, if PreserveWhitespace is true, then the elements we add won't automatically get indented, so 476 // we try to match the surrounding formatting. 477 478 // Siblings, in either direction in the linked list, may be represented either as attributes or as elements. 479 // Therefore, we need to traverse both directions to find the first sibling of the same type as the one being added. 480 // If none is found, then the node being added is inserted as the only node of its kind 481 482 ProjectElement referenceSibling; 483 Predicate<ProjectElement> siblingIsExplicitElement = _ => _.ExpressedAsAttribute == false; 484 485 if (TrySearchLeftSiblings(child.PreviousSibling, siblingIsExplicitElement, out referenceSibling)) 486 { 487 // Add after previous sibling 488 XmlElement.InsertAfter(child.XmlElement, referenceSibling.XmlElement); 489 if (XmlDocument.PreserveWhitespace) 490 { 491 // Try to match the surrounding formatting by checking the whitespace that precedes the node we inserted 492 // after, and inserting the same whitespace between the previous node and the one we added 493 if (referenceSibling.XmlElement.PreviousSibling != null && 494 referenceSibling.XmlElement.PreviousSibling.NodeType == XmlNodeType.Whitespace) 495 { 496 var newWhitespaceNode = XmlDocument.CreateWhitespace(referenceSibling.XmlElement.PreviousSibling.Value); 497 XmlElement.InsertAfter(newWhitespaceNode, referenceSibling.XmlElement); 498 } 499 } 500 } 501 else if (TrySearchRightSiblings(child.NextSibling, siblingIsExplicitElement, out referenceSibling)) 502 { 503 // Add as first child 504 XmlElement.InsertBefore(child.XmlElement, referenceSibling.XmlElement); 505 506 if (XmlDocument.PreserveWhitespace) 507 { 508 // Try to match the surrounding formatting by checking the whitespace that precedes where we inserted 509 // the new node, and inserting the same whitespace between the node we added and the one after it. 510 if (child.XmlElement.PreviousSibling != null && 511 child.XmlElement.PreviousSibling.NodeType == XmlNodeType.Whitespace) 512 { 513 var newWhitespaceNode = XmlDocument.CreateWhitespace(child.XmlElement.PreviousSibling.Value); 514 XmlElement.InsertBefore(newWhitespaceNode, referenceSibling.XmlElement); 515 } 516 } 517 } 518 else 519 { 520 // Add as only child 521 XmlElement.AppendChild(child.XmlElement); 522 523 if (XmlDocument.PreserveWhitespace) 524 { 525 // If the empty parent has whitespace in it, delete it 526 if (XmlElement.FirstChild.NodeType == XmlNodeType.Whitespace) 527 { 528 XmlElement.RemoveChild(XmlElement.FirstChild); 529 } 530 531 var parentIndentation = GetElementIndentation(XmlElement); 532 533 var leadingWhitespaceNode = XmlDocument.CreateWhitespace(Environment.NewLine + parentIndentation + DEFAULT_INDENT); 534 var trailingWhiteSpaceNode = XmlDocument.CreateWhitespace(Environment.NewLine + parentIndentation); 535 536 XmlElement.InsertBefore(leadingWhitespaceNode, child.XmlElement); 537 XmlElement.InsertAfter(trailingWhiteSpaceNode, child.XmlElement); 538 } 539 } 540 } 541 } 542 GetElementIndentation(XmlElementWithLocation xmlElement)543 private string GetElementIndentation(XmlElementWithLocation xmlElement) 544 { 545 if (xmlElement.PreviousSibling?.NodeType != XmlNodeType.Whitespace) 546 { 547 return string.Empty; 548 } 549 550 var leadingWhiteSpace = xmlElement.PreviousSibling.Value; 551 552 var lastIndexOfNewLine = leadingWhiteSpace.LastIndexOf("\n", StringComparison.Ordinal); 553 554 if (lastIndexOfNewLine == -1) 555 { 556 return string.Empty; 557 } 558 559 // the last newline is not included in the indentation, only what comes after it 560 return leadingWhiteSpace.Substring(lastIndexOfNewLine + 1); 561 } 562 RemoveFromXml(ProjectElement child)563 internal void RemoveFromXml(ProjectElement child) 564 { 565 if (child.ExpressedAsAttribute) 566 { 567 XmlElement.RemoveAttribute(child.XmlElement.Name); 568 } 569 else 570 { 571 var previousSibling = child.XmlElement.PreviousSibling; 572 573 XmlElement.RemoveChild(child.XmlElement); 574 575 if (XmlDocument.PreserveWhitespace) 576 { 577 // If we are trying to preserve formatting of the file, then also remove any whitespace 578 // that came before the node we removed. 579 if (previousSibling != null && previousSibling.NodeType == XmlNodeType.Whitespace) 580 { 581 XmlElement.RemoveChild(previousSibling); 582 } 583 584 // If we removed the last non-whitespace child node, set IsEmpty to true so that we get: 585 // <ItemName /> 586 // instead of: 587 // <ItemName> 588 // </ItemName> 589 if (XmlElement.HasChildNodes) 590 { 591 if (XmlElement.ChildNodes.Cast<XmlNode>().All(c => c.NodeType == XmlNodeType.Whitespace)) 592 { 593 XmlElement.IsEmpty = true; 594 } 595 } 596 } 597 } 598 } 599 600 /// <summary> 601 /// Sets the first child in this container 602 /// </summary> AddInitialChild(ProjectElement child)603 private void AddInitialChild(ProjectElement child) 604 { 605 ErrorUtilities.VerifyThrow(FirstChild == null && LastChild == null, "Expecting no children"); 606 607 VerifyForInsertBeforeAfterFirst(child, null); 608 609 child.VerifyThrowInvalidOperationAcceptableLocation(this, null, null); 610 611 child.Parent = this; 612 613 FirstChild = child; 614 LastChild = child; 615 616 child.PreviousSibling = null; 617 child.NextSibling = null; 618 619 AddToXml(child); 620 621 _count++; 622 623 MarkDirty("Add child element named '{0}'", child.ElementName); 624 } 625 626 /// <summary> 627 /// Common verification for insertion of an element. 628 /// Reference may be null. 629 /// </summary> VerifyForInsertBeforeAfterFirst(ProjectElement child, ProjectElement reference)630 private void VerifyForInsertBeforeAfterFirst(ProjectElement child, ProjectElement reference) 631 { 632 ErrorUtilities.VerifyThrowInvalidOperation(this.Parent != null || this.ContainingProject == this, "OM_ParentNotParented"); 633 ErrorUtilities.VerifyThrowInvalidOperation(reference == null || reference.Parent == this, "OM_ReferenceDoesNotHaveThisParent"); 634 ErrorUtilities.VerifyThrowInvalidOperation(child.Parent == null, "OM_NodeAlreadyParented"); 635 ErrorUtilities.VerifyThrowInvalidOperation(child.ContainingProject == this.ContainingProject, "OM_MustBeSameProject"); 636 637 // In RemoveChild() we do not update the victim's NextSibling (or PreviousSibling) to null, to allow RemoveChild to be 638 // called within an enumeration. So we can't expect these to be null if the child was previously removed. However, we 639 // can expect that what they point to no longer point back to it. They've been reconnected. 640 ErrorUtilities.VerifyThrow(child.NextSibling == null || child.NextSibling.PreviousSibling != this, "Invalid structure"); 641 ErrorUtilities.VerifyThrow(child.PreviousSibling == null || child.PreviousSibling.NextSibling != this, "Invalid structure"); 642 VerifyThrowInvalidOperationNotSelfAncestor(child); 643 } 644 645 /// <summary> 646 /// Verifies that the provided element isn't this element or a parent of it. 647 /// If it is, throws InvalidOperationException. 648 /// </summary> VerifyThrowInvalidOperationNotSelfAncestor(ProjectElement element)649 private void VerifyThrowInvalidOperationNotSelfAncestor(ProjectElement element) 650 { 651 ProjectElement ancestor = this; 652 653 while (ancestor != null) 654 { 655 ErrorUtilities.VerifyThrowInvalidOperation(ancestor != element, "OM_SelfAncestor"); 656 ancestor = ancestor.Parent; 657 } 658 } 659 660 /// <summary> 661 /// Recurses into the provided container (such as a choose) and finds all child elements, even if nested. 662 /// Result does NOT include the element passed in. 663 /// The caller could filter these. 664 /// </summary> GetChildrenRecursively()665 private IEnumerable<ProjectElement> GetChildrenRecursively() 666 { 667 ProjectElement child = FirstChild; 668 669 while (child != null) 670 { 671 yield return child; 672 673 ProjectElementContainer container = child as ProjectElementContainer; 674 675 if (container != null) 676 { 677 foreach (ProjectElement grandchild in container.AllChildren) 678 { 679 yield return grandchild; 680 } 681 } 682 683 child = child.NextSibling; 684 } 685 } 686 TrySearchLeftSiblings(ProjectElement initialElement, Predicate<ProjectElement> siblingIsAcceptable, out ProjectElement referenceSibling)687 private static bool TrySearchLeftSiblings(ProjectElement initialElement, Predicate<ProjectElement> siblingIsAcceptable, out ProjectElement referenceSibling) 688 { 689 return TrySearchSiblings(initialElement, siblingIsAcceptable, s => s.PreviousSibling, out referenceSibling); 690 } 691 TrySearchRightSiblings(ProjectElement initialElement, Predicate<ProjectElement> siblingIsAcceptable, out ProjectElement referenceSibling)692 private static bool TrySearchRightSiblings(ProjectElement initialElement, Predicate<ProjectElement> siblingIsAcceptable, out ProjectElement referenceSibling) 693 { 694 return TrySearchSiblings(initialElement, siblingIsAcceptable, s => s.NextSibling, out referenceSibling); 695 } 696 TrySearchSiblings( ProjectElement initialElement, Predicate<ProjectElement> siblingIsAcceptable, Func<ProjectElement, ProjectElement> nextSibling, out ProjectElement referenceSibling)697 private static bool TrySearchSiblings( 698 ProjectElement initialElement, 699 Predicate<ProjectElement> siblingIsAcceptable, 700 Func<ProjectElement, ProjectElement> nextSibling, 701 out ProjectElement referenceSibling) 702 { 703 if (initialElement == null) 704 { 705 referenceSibling = null; 706 return false; 707 } 708 709 var sibling = initialElement; 710 711 while (sibling != null && !siblingIsAcceptable(sibling)) 712 { 713 sibling = nextSibling(sibling); 714 } 715 716 referenceSibling = sibling; 717 718 return referenceSibling != null; 719 } 720 721 /// <summary> 722 /// Enumerable over a series of sibling ProjectElement objects 723 /// </summary> 724 private struct ProjectElementSiblingEnumerable : IEnumerable<ProjectElement> 725 { 726 /// <summary> 727 /// The enumerator 728 /// </summary> 729 private ProjectElementSiblingEnumerator _enumerator; 730 731 /// <summary> 732 /// Constructor 733 /// </summary> ProjectElementSiblingEnumerableMicrosoft.Build.Construction.ProjectElementContainer.ProjectElementSiblingEnumerable734 internal ProjectElementSiblingEnumerable(ProjectElement initial) 735 : this(initial, true) 736 { 737 } 738 739 /// <summary> 740 /// Constructor allowing reverse enumeration 741 /// </summary> ProjectElementSiblingEnumerableMicrosoft.Build.Construction.ProjectElementContainer.ProjectElementSiblingEnumerable742 internal ProjectElementSiblingEnumerable(ProjectElement initial, bool forwards) 743 { 744 _enumerator = new ProjectElementSiblingEnumerator(initial, forwards); 745 } 746 747 /// <summary> 748 /// Get enumerator 749 /// </summary> GetEnumeratorMicrosoft.Build.Construction.ProjectElementContainer.ProjectElementSiblingEnumerable750 public IEnumerator<ProjectElement> GetEnumerator() 751 { 752 return _enumerator; 753 } 754 755 /// <summary> 756 /// Get non generic enumerator 757 /// </summary> System.Collections.IEnumerable.GetEnumeratorMicrosoft.Build.Construction.ProjectElementContainer.ProjectElementSiblingEnumerable758 System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 759 { 760 return _enumerator; 761 } 762 763 /// <summary> 764 /// Enumerator over a series of sibling ProjectElement objects 765 /// </summary> 766 private struct ProjectElementSiblingEnumerator : IEnumerator<ProjectElement> 767 { 768 /// <summary> 769 /// First element 770 /// </summary> 771 private ProjectElement _initial; 772 773 /// <summary> 774 /// Current element 775 /// </summary> 776 private ProjectElement _current; 777 778 /// <summary> 779 /// Whether enumeration should go forwards or backwards. 780 /// If backwards, the "initial" will be the first returned, then each previous 781 /// node in turn. 782 /// </summary> 783 private bool _forwards; 784 785 /// <summary> 786 /// Constructor taking the first element 787 /// </summary> ProjectElementSiblingEnumeratorMicrosoft.Build.Construction.ProjectElementContainer.ProjectElementSiblingEnumerable.ProjectElementSiblingEnumerator788 internal ProjectElementSiblingEnumerator(ProjectElement initial, bool forwards) 789 { 790 _initial = initial; 791 _current = null; 792 _forwards = forwards; 793 } 794 795 /// <summary> 796 /// Current element 797 /// Returns null if MoveNext() hasn't been called 798 /// </summary> 799 public ProjectElement Current 800 { 801 get { return _current; } 802 } 803 804 /// <summary> 805 /// Current element. 806 /// Throws if MoveNext() hasn't been called 807 /// </summary> 808 object System.Collections.IEnumerator.Current 809 { 810 get 811 { 812 if (_current != null) 813 { 814 return _current; 815 } 816 817 throw new InvalidOperationException(); 818 } 819 } 820 821 /// <summary> 822 /// Dispose. Do nothing. 823 /// </summary> DisposeMicrosoft.Build.Construction.ProjectElementContainer.ProjectElementSiblingEnumerable.ProjectElementSiblingEnumerator824 public void Dispose() 825 { 826 } 827 828 /// <summary> 829 /// Moves to the next item if any, otherwise returns false 830 /// </summary> MoveNextMicrosoft.Build.Construction.ProjectElementContainer.ProjectElementSiblingEnumerable.ProjectElementSiblingEnumerator831 public bool MoveNext() 832 { 833 ProjectElement next; 834 835 if (_current == null) 836 { 837 next = _initial; 838 } 839 else 840 { 841 next = _forwards ? _current.NextSibling : _current.PreviousSibling; 842 } 843 844 if (next != null) 845 { 846 _current = next; 847 return true; 848 } 849 850 return false; 851 } 852 853 /// <summary> 854 /// Return to start 855 /// </summary> ResetMicrosoft.Build.Construction.ProjectElementContainer.ProjectElementSiblingEnumerable.ProjectElementSiblingEnumerator856 public void Reset() 857 { 858 _current = null; 859 } 860 } 861 } 862 } 863 } 864