Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Literate LINQ-to-XML: Best practice to deserialize attribute/value to structured variable/value pairs

I'm wrestling to deserialize the following XML:

<?xml version="1.0" encoding="utf-8" ?>

<conf name="settings">

    <item name="lorem"
        one="the"
        two="quick"
        three="brown"
        four="fox"
        five="jumps"
        six="over"
        seven="the"
        eight="lazy"
        nine="dog"
            />

    <item name="ipsum"
        one="how"
        two="many"
        three="roads"
        four="must"
        five="a"
        six="man"
        seven="walk"
        eight="down"
        nine="?"
            />

</conf>

hoping to do so in the most elegant and succinct way using LINQ-to-XML, but given that I'm not the smartest kid in town when it comes to nested methods, infered types, generics, et cétera, I thought it'd be a good idea to ask if any of you guys would like to go ahead and show me some LINQ literacy :)

Right now for every value I'm doing something like:

XDocument config = XDocument.Load("whatever.conf");

var one = from q in config.Descendants("item")
            select (string)q.Attribute("one");

var two = from q in config.Descendants("item")
            select (string)q.Attribute("two");

And I do know I'm totally missing the point, not only because I'm repeating myself a lot there but also because that queries only work when there's only one item so, again if you have any comment or suggestion it would be really appreciated. Thanks much in advance!

UPDATE: in case that perhaps the former example wasn't really helpful, here's a more sort of realistic scenario:

<?xml version="1.0" encoding="utf-8" ?>

<conf name="ftp-settings" freq="daily" time="23:00">
    <item   name="isis"
            host="10.10.1.250"
            user="jdoe"
            pass="4/cB0kdFGprXR/3oTs8mtw=="
            file="backup.tar.gz"
            path="/var/log"
        />
    <item   name="seth"
            host="10.10.2.250"
            user="jdoe"
            pass="4/cB0kdFGprXR/3oTs8mtw=="
            file="backup.tar.gz"
            path="/var/log"
        />
</conf>

Therefore for each of those items I'd like to generate variables so I can pass them as parameters for some FTP management method.

SOLVED:

It was as easy as doing a foreach:

var elements = from element in xml.Descendants("item") select element;

foreach (XElement item in elements) {
    ftp.DownloadFile(
        item.Attribute("host").Value,
        item.Attribute("user").Value,
        item.Attribute("pass").Value,
        item.Attribute("file").Value,
        item.Attribute("path").Value
        );
}
like image 810
Nano Taboada Avatar asked Oct 14 '22 14:10

Nano Taboada


1 Answers

Normally I'd expect to want a single representation of each element, rather than one sequence of "one" values and another sequence of "two" values. In other words, I'd expect something like:

var items = config.Descendants("item")
                  .Select(element => Item.FromXElement(element));

If you have a lot of attributes in a single element, I find it helpful to separate out the "make an object from this element" code into its own method (in this case Item.FromXElement) rather than stuff it all into the query. If Item shouldn't know about its LINQ to XML representation, put the method somewhere that should :)

items is then an IEnumerable<Item> which you can convert into a list or whatever else you want to do.

Of course it really depends on what you're trying to do - and we don't really know what this is meant to represent. Maybe you really do want each "attribute sequence" separately...


Item.FromXElement would look something like:

public static Item FromXElement(XElement element)
{
    string name = (string) element.Attribute("name");
    string host = (string) element.Attribute("host");
    // etc
    return new Item(name, host, ...);
}

(Where obviously you'd create a constructor with the appropriate parameters.)

like image 164
Jon Skeet Avatar answered Oct 18 '22 15:10

Jon Skeet