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