Search code examples
c#xmllinq

How to access a XML Parent Attribute with LINQ in C#


I cant seem to get the Linq syntax correct - or I dont know how to reference XML objects.

I have XML like

<?xml version="1.0" encoding="UTF-8"?>
<Targets>
    <FileType TargetId="image">
        <FileExtension>jpg</FileExtension>
        <FileExtension>jpeg</FileExtension>
        <FileExtension>tif</FileExtension>
        <FileExtension>tiff</FileExtension>
        <FileExtension>gif</FileExtension>
        <FileExtension>png</FileExtension>
    </FileType>
</Targets>

I want to lookup/extract the 'TargetId' where the file extension matches the one I am looking for, say "jpeg"

Firstly, this is the 1st time I am using a structure with a list of the same element names 'FileExtension'. And it never seems to find it.

I am basing this on previous test code where, with XML like...

<body>
  <Customers>
    <Client TestAttr=""testing-123"">
        <Firstname>someguy</Firstname>
        <LastName>some other last name again</LastName>
        <PhoneNumber>12345634543</PhoneNumber>
        <City>some other town</City>
        <State>some other state</State>
    </Client>
  </Customers>
</body>

the code

 IEnumerable<XElement> someClients =
    from el in doc.Elements("Customers").Elements("Client")
    where (string)el.Element("PhoneNumber") == phoneNumberToFind
    select el;

does find the one I am looking for. then I can get another element within the same 'Client' with

Console.WriteLine("Found client named '{0}'", (string)el.Element("LastName").Value );

and printing 'el' gives the following - everything is in the one chunk:-

<Client TestAttr="testing-123">
  <Firstname>someguy</Firstname>
  <LastName>some other last name again</LastName>
  <PhoneNumber>12345634543</PhoneNumber>
  <City>some other town</City>
  <State>some other state</State>
</Client>

Secondly, All my attempts to access the Attribute at the parent level are met with

An unhandled exception of type 'System.NullReferenceException' occurred in ......dll: 'Object reference not set to an instance of an object.'

things tried include

string fTestAttr1=  (string)el.Element("Client").Value;
string fTestAttr2=  (string)el.Element("Client").Attribute("TestAttr");
string fTestAttr3=  (string)el.Element("Client").Attribute("TestAttr").Value; 
string fTestAttr4=  (string)el.Element("Client").FirstAttribute.Value;   
string fTestAttr4=  (string)el.Element("Client").FirstAttribute.Value)   

So clearly I dont know adress the parent. Not helped by VS Code debug where I cas see 'el' is [XElement] but it does not have any 'Element'

Thanks for any support JC


Solution

  • With the query, it returns IEnumerable of Client/FileType XElement. Hence you don't need the extra .Element("Client")/.Element("FileType") as it will treat as there is a descendant with Client/<FileType>.

    Obtain the attribute value: result.Attribute("TargetId").Value.

    string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
                <Targets>
                    <FileType TargetId=""image"">
                        <FileExtension>jpg</FileExtension>
                        <FileExtension>jpeg</FileExtension>
                        <FileExtension>tif</FileExtension>
                        <FileExtension>tiff</FileExtension>
                        <FileExtension>gif</FileExtension>
                        <FileExtension>png</FileExtension>
                    </FileType>
                    <FileType TargetId=""doc"">
                        <FileExtension>doc</FileExtension>
                    </FileType>
                </Targets>";
    
    XDocument doc = XDocument.Parse(xml);
    XElement root = doc.Root;
    
    XElement result = (from el in doc.Elements("Targets").Elements("FileType")
        where el.Elements("FileExtension").Any(x => (string)x == "jpg")
        select el).FirstOrDefault();
    
                
    Console.WriteLine(result.Attribute("TargetId").Value);
    

    Alternatively, working with XPath:

    string targetId = root.XPathSelectElement("/Targets/FileType[FileExtension='jpg']").Attribute("TargetId").Value;
    

    The same goes for Client: Obtain the attribute value: result2.Attribute("TestAttr").Value.

    string xml2 = @"<body>
              <Customers>
                <Client TestAttr=""testing-123"">
                    <Firstname>someguy</Firstname>
                    <LastName>some other last name again</LastName>
                    <PhoneNumber>12345634543</PhoneNumber>
                    <City>some other town</City>
                    <State>some other state</State>
                </Client>
              </Customers>
            </body>";
            
    XDocument doc2 = XDocument.Parse(xml2);
    XElement root2 = doc2.Root;
            
    XElement result2 = (from el in doc2.Element("body").Elements("Customers").Elements("Client")
        where el.Elements("PhoneNumber").Any(x => (string)x == "12345634543")
        select el).FirstOrDefault();
            
    Console.WriteLine(result2.Attribute("TestAttr").Value);
    

    Alternatively, working with XPath:

    string testAttr = root2.XPathSelectElement("/body/Customers/Client[PhoneNumber=12345634543]").Attribute("TestAttr").Value;