views:

3296

answers:

2

I need to replace the contents of a node in an XElement hierarchy when the element name and all the attribute names and values match an input element. (If there is no match, the new element can be added.)

For example, if my data looks like this:

<root>
  <thing1 a1="a" a2="b">one</thing1>
  <thing2 a1="a" a2="a">two</thing2>
  <thing2 a1="a" a3="b">three</thing2>
  <thing2 a1="a">four</thing2>
  <thing2 a1="a" a2="b">five</thing2>
<root>

I want to find the last element when I call a method with this input:

<thing2 a1="a" a2="b">new value</thing2>

The method should have no hard-coded element or attribute names - it simply matches the input to the data.

A: 

You can do an XPathSelectElement with the path (don't quote me, been to the bar; will clean up in the morn) /root/thing2[@a1='a' and @a2='b'] and then take .LastOrDefault() (XPathSelectElement is an extension method in system.Linq.Xml).

That will get you the node you wish to change. I'm not sure how you want to change it, however. The result you get is the actual XElement, so changing it will change the tree element.

Will
+5  A: 

This will match any given element with exact tag name and attribute name/value pairs:

public static void ReplaceOrAdd(this XElement source, XElement node)
 { var q = from x in source.Elements()
           where    x.Name == node.Name
                 && x.Attributes().All
                                  (a =>node.Attributes().Any
                                  (b =>a.Name==b.Name && a.Value==b.Value))
           select x;

   var n = q.LastOrDefault();

   if (n == null) source.Add(node);
   else n.ReplaceWith(node);                                              
 }

var root   =XElement.Parse(data);
var newElem=XElement.Parse("<thing2 a1=\"a\" a2=\"b\">new value</thing2>");

root.ReplaceOrAdd(newElem);
Mark Cidade
One potential optimisation (not sure how data is stored internally) would be to specify the name as an argument to Elements() instead of including it in the Where clause.
Jon Skeet