Wouter de Kort
Wouter de Kort

Reputation: 39898

Modify XML with LINQ to XML Functional Construction

I am trying to understand how LINQ to XML Functional Construction works.

I have the following sample XML:

string xml = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
                    <People>
                      <Person firstName=""John"" lastName=""Doe"">
                        <ContactDetails>
                          <EmailAddress>[email protected]</EmailAddress>
                        </ContactDetails>
                      </Person>
                      <Person firstName=""Jane"" lastName=""Doe"">
                        <ContactDetails>
                          <EmailAddress>[email protected]</EmailAddress>
                          <PhoneNumber>001122334455</PhoneNumber>
                        </ContactDetails>
                      </Person>
                    </People>";

I try to modify this XML by adding an IsMale attribute to the Person tag and by adding a PhoneNumber if it doesn't exist.

I can easily write this code by using some procedural code:

XElement root = XElement.Parse(xml);

foreach (XElement p in root.Descendants("Person"))
{
    string name =  (string)p.Attribute("firstName") + (string)p.Attribute("lastName");
    p.Add(new XAttribute("IsMale", IsMale(name)));

    XElement contactDetails = p.Element("ContactDetails");

    if (!contactDetails.Descendants("PhoneNumber").Any())
    {
        contactDetails.Add(new XElement("PhoneNumber", "001122334455"));
    }
}

But the documentation on MSDN says that Functional Construction should be easier and better to maintain. So I tried writing the same sample with Functional Construction.

XElement root = XElement.Parse(xml);

XElement newTree = new XElement("People",
    from p in root.Descendants("Person")
    let name = (string)p.Attribute("firstName") + (string)p.Attribute("lastName")
    let contactDetails = p.Element("ContactDetails")
    select new XElement("Person",
        new XAttribute("IsMale", IsMale(name)),
        p.Attributes(),
        new XElement("ContactDetails",
            contactDetails.Element("EmailAddress"),
            contactDetails.Element("PhoneNumber") ?? new XElement("PhoneNumber", "1122334455")
        )));

It could be me, but I don't find this code better readable.

How can I improve my functional construction? Is there a better way to write this code?

Upvotes: 1

Views: 318

Answers (3)

Jeff B
Jeff B

Reputation: 1866

Part of Microsoft's advice to use a functional constructor is to eliminate the static XML resource in the first place. If your initial XML string comes from an external source, then you must load it, modify it and return it.

But if the static XML comes from within the same code that will make the modifications, and your data is available directly, then you would use functional constructors to create the code with the modificiations already made.

Person[] peopleData = new Person[]
{
   new Person("John", "Doe", "[email protected]", ""),
   new Person("Jane", "Doe", "[email protected]", "001122334455")
}

XElement people = 
   new XElement("People",
      from p in peopleData
      select new XElement("Human",
                new XAttribute("isMale", p.IsMale),
                new XAttribute("name", p.FullName),
                new XAttribute("age", p.Age)
      )
   );

This approach is nearly as easy to read as the original XML, but is also much faster because the parsing of the original XML has been eliminated.

Upvotes: 0

rankAmateur
rankAmateur

Reputation: 2086

You may be able to increase the expressiveness of the code by breaking up the functionality a bit further, in particular by trying to hide all of those constructors that are being called in the query. I'd suggest a couple of utility functions:

XElement PersonWithGender(XElement person)
{
    string name = (string)p.Attribute("firstName") + (string)p.Attribute("lastName");
    XAttribute genderAttribute = new XAttribute("isMale", IsMale(name));
    return new XElement(genderAttribute, person.Attributes);
}

XElement PersonWithPhoneNumber(XElement person)
{
    XElement contactDetails = person.Element("ContactDetails");
    XElement emailAddress = contactDetails.Element("EmailAddress");
    XElement phoneNumber = contactDetails.Element("PhoneNumber") ?? new XElement("PhoneNumber", "1122334455");

    return new XElement("ContactDetails", emailAddress, phoneNumber);
}

These functions each map an initial XElement to a new improved XElement, and so should fit quite nicely into your query. If you feed the result of one into the other, then your query becomes:

XElement newTree = new XElement("People",
    from p in root.Descendants("Person")
    let pWithGender = PersonWithGender(p)
    select PersonWithPhoneNumber(pWithGender));

which I think is approaching the terseness and expressiveness that you'd expect from functional programming. Each of the new functions is short enough that they can be examined without much effort, and the query itself now declares its intention more clearly.

Upvotes: 0

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236268

As stated in msdn article you have referenced, it depends on what you are doing. If you are doing many changes to xml, then non functional approach will become complex and not easy to understand. What will be result in this case?

foreach (XElement p in doc.Descendants("Person"))
{
    var name = (string)p.Attribute("firstName") + " " + (string)p.Attribute("lastName");
    int age = (int)p.Attribute("age");
    p.RemoveAttributes();
    p.SetAttributeValue("isMale", IsMale(name));
    p.SetAttributeValue("name", name);
    p.SetAttributeValue("age", age);
    p.RemoveNodes();
    p.Name = "Human";
}

I have doubts you can see it in a glance. This example is not very descriptive as for me. Also I don't see which structure will have xml after changes.

XElement people = 
new XElement("People",
    from p in doc.Descendants("Person")
    let name = (string)p.Attribute("firstName") + " " +  (string)p.Attribute("lastName")
    select new XElement("Human",
                    new XAttribute("isMale", IsMale(name)),
                    new XAttribute("name", name),
                    p.Attribute("age")
    )
);

As for me, second example describes result and shows xml structure better. So, I'd go with non functional approach if I need to do small changes to existing xml. And I'd go with functional approach for big modifications. What is small and big? I think it's subjective part.

BTW In result I want only age attribute left and one name attribute instead of first and last name. Also instead of Person elements, I'd like to have Human elements:

<People>
  <Human isMale="false" name="John Doe" age="28" />
  <Human isMale="true" name="Jane Doe" age="27" />
</People>

Upvotes: 1

Related Questions