Skip to content

Commit e39a9be

Browse files
Make ProjectElementContainers.DeepCopyFrom copy all descendants (dotnet#7454)
When "DeepCopy"ing ProjectItemGroupElements, we copied attributes but not children, which meant we would XML embedded in metadata if present. Fixes dotnet#7435 Co-authored-by: Rainer Sigwald <[email protected]>
1 parent bde2b99 commit e39a9be

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

src/Build.OM.UnitTests/Construction/ProjectItemGroupElement_tests.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Xml;
66

77
using Microsoft.Build.Construction;
8+
using Shouldly;
89
using Xunit;
910

1011
#nullable disable
@@ -70,6 +71,49 @@ public void ReadItemGroupTwoItems()
7071
Assert.Equal("i2", items[1].Include);
7172
}
7273

74+
[Fact]
75+
public void DeepCopyFromItemGroupWithMetadata()
76+
{
77+
string content = @"
78+
<Project>
79+
<ItemGroup>
80+
<i Include='i1'>
81+
<M>metadataValue</M>
82+
</i>
83+
<i Include='i2'>
84+
<M>
85+
<Some>
86+
<Xml With='Nesting' />
87+
</Some>
88+
</M>
89+
</i>
90+
</ItemGroup>
91+
</Project>
92+
";
93+
94+
ProjectRootElement project = ProjectRootElement.Create(XmlReader.Create(new StringReader(content)));
95+
ProjectItemGroupElement group = (ProjectItemGroupElement)Helpers.GetFirst(project.Children);
96+
97+
ProjectRootElement newProject = ProjectRootElement.Create();
98+
ProjectItemGroupElement newItemGroup = project.AddItemGroup();
99+
100+
newItemGroup.DeepCopyFrom(group);
101+
102+
var items = Helpers.MakeList(newItemGroup.Items);
103+
104+
items.Count.ShouldBe(2);
105+
106+
items[0].Include.ShouldBe("i1");
107+
ProjectMetadataElement metadataElement = items[0].Metadata.ShouldHaveSingleItem();
108+
metadataElement.Name.ShouldBe("M");
109+
metadataElement.Value.ShouldBe("metadataValue");
110+
111+
items[1].Include.ShouldBe("i2");
112+
metadataElement = items[1].Metadata.ShouldHaveSingleItem();
113+
metadataElement.Name.ShouldBe("M");
114+
metadataElement.Value.ShouldBe("<Some><Xml With=\"Nesting\" /></Some>");
115+
}
116+
73117
/// <summary>
74118
/// Set the condition value
75119
/// </summary>

src/Build/Construction/ProjectElementContainer.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,13 +407,46 @@ protected internal virtual ProjectElementContainer DeepClone(ProjectRootElement
407407
}
408408
else
409409
{
410-
clone.AppendChild(child.Clone(clone.ContainingProject));
410+
ProjectElement childClone = child.Clone(clone.ContainingProject);
411+
clone.AppendChild(childClone);
412+
if (childClone.XmlElement is not null)
413+
{
414+
AppendAttributesAndChildren(childClone.XmlElement, child.XmlElement);
415+
}
411416
}
412417
}
413418

414419
return clone;
415420
}
416421

422+
private void AppendAttributesAndChildren(XmlNode appendTo, XmlNode appendFrom)
423+
{
424+
appendTo.RemoveAll();
425+
// Copy over the attributes from the template element.
426+
if (appendFrom.Attributes is not null)
427+
{
428+
foreach (XmlAttribute attribute in appendFrom.Attributes)
429+
{
430+
XmlAttribute attr = appendTo.OwnerDocument.CreateAttribute(attribute.LocalName, attribute.NamespaceURI);
431+
attr.Value = attribute.Value;
432+
appendTo.Attributes.Append(attr);
433+
}
434+
}
435+
436+
// If this element has pure text content, copy that over.
437+
if (appendFrom.ChildNodes.Count == 1 && appendFrom.FirstChild.NodeType == XmlNodeType.Text)
438+
{
439+
appendTo.AppendChild(appendTo.OwnerDocument.CreateTextNode(appendFrom.FirstChild.Value));
440+
}
441+
442+
foreach (XmlNode child in appendFrom.ChildNodes)
443+
{
444+
XmlNode childClone = appendTo.OwnerDocument.CreateNode(child.NodeType, child.Prefix, child.Name, child.NamespaceURI);
445+
appendTo.AppendChild(childClone);
446+
AppendAttributesAndChildren(childClone, child);
447+
}
448+
}
449+
417450
internal static ProjectElementContainer DeepClone(ProjectElementContainer xml, ProjectRootElement factory, ProjectElementContainer parent)
418451
{
419452
return xml.DeepClone(factory, parent);

0 commit comments

Comments
 (0)