0

I'm querying a webservice in C# and it returns XML, which I've parsed into an XDocument. I remove an extraneous node that returns statistics about the service call and am left with something like this:

<xml>
   <element attr1="1" attr2="2" attr3="3" attr4="4" ... attrN="N" />
   <element attr1="a" attr2="b" attr3="c" ... attrN="N" />
   ...
   <element attr1="!" attr2="?" attr3=":" attr4="" ... attrN="N /> 
</xml>

I'm really only interested in retrieving attr2 and attr3, so I want to remove all the other attributes to wind up with this:

<xml>
   <element attr2="2" attr3="3" />
   <element attr2="b" attr3="c" />
   ...
   <element attr2="?" attr3=":" />
</xml>

However, here's the relevant bit of code I'm using and it is only removing attr1:

String[] WS_LookupFields = "attr2,attr3".Split(',');
var output = XDocument.Parse(WS_Output_Raw);
output.Descendants("system").Remove(); //removes query statistics
foreach (XAttribute attribute in output.Descendants("element").Attributes())
{
    if (!Array.Exists<String>(WS_LookupFields, fieldname => attribute.Name == fieldname)) attribute.Remove();
}

The problem is that the foreach only returns attr1 and not attr1 through attrN. Any suggestions?

5 Answers5

2

Use this:

var WS_LookupFields = "attr2,attr3".Split(',').ToList();
var output = XDocument.Parse(WS_Output_Raw);
foreach (var loElement in output.Descendants("element"))
{
    //loElement.Attributes()
    //    .Where(item => !WS_LookupFields.Any(name => item.Name == name))
    //    .ToList()
    //    .ForEach(item => item.Remove());
    //UPDATE: Charles Mager
    loElement.Attributes()
        .Where(item => !WS_LookupFields.Any(name => item.Name == name))
        .Remove();
}

Or:

foreach (var loAttribute in output.Descendants("element").Attributes().ToList())
{
    if (!WS_LookupFields.Contains(loAttribute.Name.ToString()))
        loAttribute.Remove();
}
PinBack
  • 2,499
  • 12
  • 16
  • Perfect. I was missing a reference to System.Linq, but once I added that, your code worked wonderfully. (I was wondering why I didn't see stuff like Where in the list of methods.) – Steve Cardella Aug 29 '17 at 14:39
  • 1
    I'd strongly advise not using `.ToList().ForEach(..)` (see [this question](https://stackoverflow.com/questions/7816781/tolist-foreach-in-linq)). Happily, you can replace with just [`.Remove()`](https://msdn.microsoft.com/en-us/library/bb343722(v=vs.110).aspx). – Charles Mager Aug 30 '17 at 10:44
  • @Charles Mager: I wasn't aware of the problem with foreach. Basically, the use of it should not be a problem. But you're right you can call directly `Remove` from `IEnumerable`. Thx – PinBack Aug 31 '17 at 07:34
2

There are methods designed to remove xml content from the tree. You used the one for Elements already, use the one for attributes as well.

Build up a query to select the attributes you want to remove, then remove them.

var attrs = new HashSet<string>("attr2,attr3".Split(','));
foreach (var e in output.Descendants("element"))
    e.Attributes().Where(a => !attrs.Contains(a.Name.LocalName)).Remove();

// or simply
output.Descendants("element")
    .Attributes()
    .Where(a => !attrs.Contains(a.Name.LocalName))
    .Remove();
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
0

I think you will need two loops:

// Loop all descendants
foreach (var element in output.Descendants("element"))
{
    // for that element, loop all attributes
    foreach (var attribute in element.Attributes())
    {
    if (!Array.Exists<String>(WS_LookupFields, fieldname => attribute.Name == 
fieldname)) attribute.Remove();
    }
}
Niklas Wulff
  • 3,497
  • 2
  • 22
  • 43
0

Just create new document with required attributes

var required = new HashSet<string> { "attr2", "attr3" };

var elements = original.Descendants("element").Select(element =>
{
    var attributes = element.Attributes().Where(a => required.Contains(a.Name.LocalName));
    return new XElement(element.Name, attributes);
});

var root = new XElement(original.Root.Name, elements);
var newDocument = new XDocument(original.Declaration, root);
Fabio
  • 31,528
  • 4
  • 33
  • 72
0

Just add ToArray() method to your foreach loop:

foreach (XAttribute attribute in output.Descendants("element").Attributes().ToArray())
{
    if (!Array.Exists<String>(WS_LookupFields, fieldname => attribute.Name == fieldname)) attribute.Remove();
}
alobster
  • 38
  • 1
  • 5