0

I am looking to parse and modify a project file of type .dbproj.

Sample XML

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
    <ProjectGroup></ProjectGroup>
    <ItemGroup></ItemGroup>
    <ItemGroup></ItemGroup>
    <ItemGroup></ItemGroup>
    <ItemGroup>
      <Build Include="myfile.cs">
           <SubType>Code</SubType>
          </Build>
      <Build Include="myfile2.cs">
           <SubType>Code</SubType>
          </Build>
      <Build Include="myfile3.cs">
           <SubType>Code</SubType>
          </Build>
    </ItemGroup>
    <ItemGroup>
      <NotInBuild Include="myfile4.cs">
        <SubType>Code</SubType>
      </NotInBuild>
    </ItemGroup>
</Project>

I want to take the item from the 4th <ItemGroup> with the attribute Include="myfile.cs" and remove it from said <ItemGroup>. Next, I want to add this element:

<NotInBuild Include="myfile.cs">
    <SubType>Code</SubType>
</NotInBuild>

It is slightly modified from the one removed to the 5th ItemGroup. The result should be:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
    <ProjectGroup></ProjectGroup>
    <ItemGroup></ItemGroup>
    <ItemGroup></ItemGroup>
    <ItemGroup></ItemGroup>
    <ItemGroup>
      <Build Include="myfile2.cs">
           <SubType>Code</SubType>
          </Build>
      <Build Include="myfile3.cs">
           <SubType>Code</SubType>
          </Build>
    </ItemGroup>
    <ItemGroup>
      <NotInBuild Include="myfile.cs">
        <SubType>Code</SubType>
      </NotInBuild>
      <NotInBuild Include="myfile4.cs">
        <SubType>Code</SubType>
      </NotInBuild>
    </ItemGroup>
</Project>

Per another SO post by Skeet, I started going down the LINQ path.I don't know where to go from here

XDocument doc = XDocument.Load(XmlDocumentPath);
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
                var query = doc.Root          
                .Elements(ns + "ItemGroup");
Community
  • 1
  • 1
P.Brian.Mackey
  • 43,228
  • 68
  • 238
  • 348
  • Do the IntemGroup tags have any Id/Name to distinguish them? Working with 3rd and 4th seems a bit sloppy. – H H Feb 22 '12 at 18:07
  • @HenkHolterman - Nope. Thats how they look in the project files. – P.Brian.Mackey Feb 22 '12 at 18:08
  • Proof in point: the 3rd ItemGrp is empty. – H H Feb 22 '12 at 18:19
  • @HenkHolterman - I'm not sure what you are trying to establish. I inserted empty elements simply to show that prior elements exist and thus require a solution that uses some kind of index. The elements are populated, yes. I did not populate them due to space constraints. I also considered it unecessary. If you feel they need to be populated simply insert an arbitrary node. Keep in mind, this XML is based off Microsoft generated files. Any complaints regarding lack of Ids should be directed to them. – P.Brian.Mackey Feb 22 '12 at 18:24
  • I mean there is no element with myfile.cs in that empty node. – H H Feb 22 '12 at 18:27
  • @HenkHolterman - Why did you remove the solution? I tried it and it seems to work. I was just about to mark it as answer. – P.Brian.Mackey Feb 22 '12 at 18:35
  • Well, it was untested and looked crummy. If you got it to work then you can post that version as an answer. – H H Feb 22 '12 at 22:22

1 Answers1

1

Using this collection class and the associated extensions: http://searisen.com/xmllib/xelementcollection.wiki

And these classes:

public class Project
{
    XElement self;
    public Project(FileInfo file)
    {
        self = XElement.Load(file.FullName);
    }

    public ItemGroup[] ItemGroup
    {
        get
        {
            return _ItemGroup
                ?? (_ItemGroup = self.GetEnumerable("ItemGroup", x => new ItemGroup(x)).ToArray());
        }
    }
    ItemGroup[] _ItemGroup;
}

public class ItemGroup
{
    XElement self;
    public ItemGroup(XElement self)
    {
        this.self = self;
    }

    public XElementCollection Build
    {
        get
        {
            return _Build
                ?? (_Build = new XElementCollection(self, "Build",
                         (a, b) => a.Get("Include", string.Empty) ==
                                   b.Get("Include", string.Empty),
                         false));
        }
    }
    XElementCollection _Build;

    public XElementCollection NotInBuild
    {
        get
        {
            return _NotInBuild
                ?? (_NotInBuild = new XElementCollection(self, "NotInBuild",
                         (a, b) => a.Get("Include", string.Empty) ==
                                   b.Get("Include", string.Empty),
                         false));
        }
    }
    XElementCollection _NotInBuild;
}

Then this sort of messy implementation:

Project project = new Project(xmlFile2);
XElement find = null;
ItemGroup item = project.ItemGroup.FirstOrDefault(i =>
    (find = i.Build.Find(x => x.Get("Include", string.Empty) == "myfile.cs")) != null);
if (null != find)
{
    XElement not = new XElement(find.Name.Namespace + "NotInBuild");
    not.Set("Include", "myfile.cs", true);
    foreach (var child in find.Elements().ToArray())
        not.Add(child);
    find.Remove();
    ItemGroup notGroup = project.ItemGroup.FirstOrDefault(i => i.NotInBuild.Count > 0);
    notGroup.NotInBuild.Add(not);
}

Using the extension methods mentioned at the top, the namespace issue is a non-issue.

Chuck Savage
  • 11,775
  • 6
  • 49
  • 69