1

I am trying to compose an algorithm that will take XML as input, and find a value associated with a particular element, but the position of the element within the XML body varies. I have seen many examples of using XDocument.Descendants() but most (if not all) the examples expect the structure to be consistent, and descendants well known. I presume I will need to recurse the XML to find this value, but before heading that way, ask the general population.

What is the best way to find an element in an XDocument when the path for the element may be different on each call? Just need the first occurrence found, not in any particular order. Can be first occurrence found by going wide, or by going deep.

For example, if I am trying to find the element "FirstName", and if the input XML for Call one looks like..

<?xml version="1.0"?>
<PERSON><Name><FirstName>BOB</FirstName></Name></PERSON>

and the next call looks like:

<?xml version="1.0"?>
<PERSONS><PERSON><Name><FirstName>BOB</FirstName></Name></PERSON></PERSONS>

What do you recommend? Is there a "Find" option in XDocument that I have not seen?

UPDATE:

Simple example above works with lazyberezovsky answer of XDocument.Descendents, but real XML does not. My problematic XML...

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://localhost:52087/Service1.svc</To>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IService1/GetDataUsingDataContract</Action>
  </s:Header>
  <s:Body>
    <GetDataUsingDataContract xmlns="http://tempuri.org/">
      <composite xmlns:a="http://schemas.datacontract.org/2004/07/WcfService2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <a:BoolValue>false</a:BoolValue>
        <a:Name>
          <a:FirstName>BOB</a:FirstName>
        </a:Name>
        <a:StringValue i:nil="true" />
      </composite>
    </GetDataUsingDataContract>
  </s:Body>
</s:Envelope>

UPDATE:

lazyberezovsky helped immensely showing me how Descendents is supposed to work. Be careful of namespaces in XML. Lesson learned. Found another . article with similar issues..

Search XDocument using LINQ without knowing the namespace

Resolved using the following snippet...

var xdoc = XDocument.Parse(xml);
var name = (from p in xdoc.Descendants() where p.Name.LocalName == "FirstName" select p.Value).FirstOrDefault();
Community
  • 1
  • 1
barrypicker
  • 9,740
  • 11
  • 65
  • 79
  • For these examples you could just use a regular expression to search the document text. Any reason that wouldn't work? – Matthew Strawbridge Feb 17 '13 at 22:06
  • Could you provide an example of the regex you are referring to? The example above is a simple example of the problem, but the XML I am working with has variable namespaces, so string parsing becomes difficult if not impossible. – barrypicker Feb 17 '13 at 22:10
  • @barrypicker whats wrong with using Descendants? – Sergey Berezovskiy Feb 17 '13 at 22:14
  • @lazyberezovsky - perhaps I simply am using Descendents incorrectly, but when I try, the first call finds the next level of elements. If i specify by name "FirstName", as in my example above, and the element does not exist, I get no results. If I specify no name, I get all elements below current element, then I need to go another level deeper. This is what I mean by the need to recurse the structure. I feel this will probably be my best option... – barrypicker Feb 17 '13 at 22:20

3 Answers3

1

When you are using Descendants for finding first occurrence of element, you don't need to know structure of file. Following code will work for both your cases:

var xdoc = XDocument.Load(path_to_xml);
var name = (string)xdoc.Descendants("FirstName").FirstOrDefault();

Same with XPath

var name = (string)xdoc.XPathSelectElement("//FirstName[1]");
Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
  • I like the direction your code examples take me. I tried the XDocument Descendents example you provided and it worked for the simple XML example I provided, but won't work the actual XML I am dealing with. This leads me to believe there is something special about my XML, not the use of Descendents. – barrypicker Feb 17 '13 at 22:39
  • I added my problematic XML to the question. Any ideas? – barrypicker Feb 17 '13 at 22:46
  • 1
    OK, @lazyberezovsky you were definitely on the right track. Namespaces were throwing me. I found another \. article at... http://stackoverflow.com/questions/2610947/search-xdocument-using-linq-without-knowing-the-namespace the trick here is a little LINQ. var xdoc = XDocument.Parse(xml); var name = (from p in xdoc.Descendants() where p.Name.LocalName == "FirstName" select p.Value).FirstOrDefault(); – barrypicker Feb 17 '13 at 23:03
  • @barrypicker sorry, was AFK. Thanks for accepting, and glad to see you managed namespaces! Btw if namespace of `FirstName` element stays same, you can create XNamespace object, and query for element like this `Descendants(ns + "FirstName")` – Sergey Berezovskiy Feb 18 '13 at 06:50
0

Without knowing all the possible permutations of the XML document (which is very unusual by the way) I don't think anyone could hope to give you any worthwhile recommendations.

Spencer Ruport
  • 34,865
  • 12
  • 85
  • 147
0

"Just need the first occurrence found, not in any particular order." I think Descendants do trick. Look at this:

string xml = @"<?xml version=""1.0""?>
             <PERSONS>
                  <PERSON>
                      <Name>
                         <FirstName>BOB</FirstName>
                      </Name>
                  </PERSON>
             </PERSONS>";
XDocument doc = XDocument.Parse(xml);
Console.WriteLine(string.Join(",", doc.Descendants("FirstName").Select(e =>(string)e)));

xml = @"<?xml version=""1.0""?>
            <PERSON>
               <Name>
                   <FirstName>BOB</FirstName>
               </Name>
            </PERSON>";
doc = XDocument.Parse(xml);
Console.WriteLine(string.Join(",", doc.Descendants("FirstName").Select(e =>(string)e)));
Hamlet Hakobyan
  • 32,965
  • 6
  • 52
  • 68