tags:

views:

366

answers:

3

I have some XML that looks something like the one below

<DriveLayout>
<Drive totalSpace="16" VolumeGroup="dg01" />
<Drive totalSpace="32" VolumeGroup="dg01" />
<Drive totalSpace="64" VolumeGroup="dg02" />
<Drive totalSpace="64" VolumeGroup="dg02" />
<VolumeGroups>
<VolumeGroup VolumeGroup="dg01" storageTier="1" />
<VolumeGroup VolumeGroup="dg02" storageTier="2" />
</VolumeGroups>
</DriveLayout>

I need a way to go back through the XML and add the attribute storageTier to each individual Drive node. Is there a way to loop through each drive node and grab the VolumeGroup then get the cooresponding storageTier out of the XML in the VolumeGroup node? I then need to inject the correct storageTier back into the XML drive node. I'm using the System.XML that's in C#.

Thanks

Any help would be greatly appreciated

A: 

I think you need XPath ( check this out )

var doc = new XmlDocument();
var xml =
    @"<DriveLayout>
<Drive totalSpace='16' VolumeGroup='dg01' />
<Drive totalSpace='32' VolumeGroup='dg01' />
<Drive totalSpace='64' VolumeGroup='dg02' />
<Drive totalSpace='64' VolumeGroup='dg02' />
<VolumeGroups>
<VolumeGroup VolumeGroup='dg01' storageTier='1' />
<VolumeGroup VolumeGroup='dg02' storageTier='2' />
</VolumeGroups>
</DriveLayout>
";

doc.LoadXml(xml);
var volumeGroups = doc.SelectNodes("/DriveLayout/VolumeGroups/VolumeGroup");
var storageTiers = new Dictionary<string, string>();
if (volumeGroups != null)
{
    foreach (var volumeGroup in volumeGroups)
    {
        var volumeGroupElement = (XmlElement) volumeGroup;
        storageTiers.Add(
            volumeGroupElement.Attributes["VolumeGroup"].Value,
            volumeGroupElement.Attributes["storageTier"].Value);
    }
}

var nodes = doc.SelectNodes("/DriveLayout/Drive");
if (nodes == null)
{
    return;
}

foreach (XmlNode node in nodes)
{
    var element = (XmlElement) node;
    var volumeGroupAttribute = element.Attributes["VolumeGroup"];
    if (volumeGroupAttribute == null)
    {
        continue;
    }

    var volumeGroup = volumeGroupAttribute.Value;

    var newStorageTier = doc.CreateAttribute("storageTier");
    newStorageTier.Value = storageTiers[volumeGroup];
    element.Attributes.Append(newStorageTier);
}
BigBlondeViking
"You gonna finish that example?"
John Saunders
Maybe after i leave my job, feel free to jump in and edit..
BigBlondeViking
Useful to see the completed example. Saying that, this algorithm is O(n^2), which is totally unnecessary. Plus, I think it just shows how nice LINQ is for these sorts of scenarios.
Noldorin
@Noldorin: you're right; I didn't stop and make a dictionary. I'll fix that so we're comparing apples to better apples.
John Saunders
@John: Ok, sounds good. I'm still hoping the OP will see the beauty of LINQ though. :)
Noldorin
The code I am working on uses .NET 2.0 to run instead of 3.5 thus var is not supported anyway you could help me with the data types?
Splashlin
A: 

What was wrong with XSLT (Question on XPATH for an XSLT File And XSLT If Statement)?

John Saunders
this is being done before I give the xml to the xslt. If there is a way to do this in xslt I would love to know how but I couldn't seem to find one.
Splashlin
It can be done, but if you're not that familiar with XSLT, then this way is probably better.
John Saunders
Plus, XSLT is *horribly* verbose. It's not loved by all, for sure.
Noldorin
@Splashlin: Agreed - the logic is probably too complex for XSLT to handle anyway, though I'm no expert there...
Noldorin
+7  A: 

This task can be done very succinctly using LINQ to XML. What is more, it uses simple LINQ queries and a dictionary to give an algorithm that runs in linear time.

var storageTiers = doc.Root.Element("VolumeGroups").Elements().ToDictionary(
    el => (string)el.Attribute("VolumeGroup"),
    el => (string)el.Attribute("storageTier"));
foreach (var driveElement in doc.Root.Elements("Drive"))
{
    driveElement.SetAttributeValue("storageTier", 
        storageTiers[(string)driveEl.Attribute("VolumeGroup")]);
}

If you are using C# 3.0, then this is without doubt the best way to go (unless your XML file is enormous and you need high efficiency, which seems unlikely).

Noldorin
I second using Linq to Xml
Vin
I get errors on the doc.Root it says that Root is undefined. Is there another library I need to add or ...?
Splashlin
@Splashlin: Yeah, the reference to the library is not included by default even in .NET 3.5 projects, so you'll need to reference `System.Xml.Linq`. Also, to load the docment, you just want to use `XDocument.Load(...`).
Noldorin
@Noldorin I added the System.Xml.Linq and it didn't help with my error. Any other suggestions?
Splashlin
@Splashline: Sorry... you need to import the namespace too! Add `using System.Xml.Linq;` to the top of the file.
Noldorin
@Noldorin, yeah I have that in my file and it still isn't recognizing it.
Splashlin
@Noldorin it appears to be tring to find it in System.Xml.XmlDocument even though I have Linq in the namespace now
Splashlin
@Splashlin: Notice that you want `XDocument`, not `XmlDocument` (as with `XElement` rather than `XmlElement`, etc.).
Noldorin
@Noldorin, thanks I just figured out that System.Xml and System.Xml.Linq don't play nice together. I moved all the stuff to XDocument and but it doesn't recognize your ToDictionary command and this line xsl.Transform(doc, null, sw); no longer works.
Splashlin
@Splashlin: I'm not sure what you'll need to do about the XSLT transform stuff. Regarding ToDictionary, you also need to import the `System.Linq` namespace.
Noldorin
Regarding your second error, XSLT transformation can be done quite simply in LINQ to XML too. See this page: http://msdn.microsoft.com/en-us/library/bb675186.aspx
Noldorin
@Noldorin I have the XSLT working and got the ToDictonary working. the errors I get now are on the line el => (string)el.Attribute("VolumeGroup"),el => (string)el.Attribute("storageTier"));It gives me errors on1) invalid expression '>'2) invalid expression term 'string'3) ) expected4) invalid term ','5) ; expectedSome of those are repeated more than once later.Thanks again for all of your help. I can't tell you how much I am learning here (even if it seems like I'm only asking questions)
Splashlin
@Splashlin: Ah, so it seems you have .NET 3.5 installed (and thus can reference the LINQ assemblies), but are only using C# 2.0. Sorry - I had assumed you noticed this from the question. If there is any possibility of converting the project to C# 3.0/.NET 3.5, that would be the easiest solution. Anyway, I'm glad to hear you're learning things from this - hopefully that will include LINQ when you get it working. :)
Noldorin
@Noldorin I gave u ups on every single one of your comments for your help in trying to get it to work. Moving to C# 3.0 wasn't possible so I implemented the other solution even though I had to modify it and I know it's not as efficient.
Splashlin
@Splashlin: Fair enough, and no worries. Hopefully this answer with help if you decide to move to .NET 3.5 though, or for someone else perhaps.
Noldorin