views:

244

answers:

4
+2  Q: 

Basic LINQ syntax

Lets say you have an XML like like this:

 <data>
  <messages>
   <message name="Person" id="P">
    <field name="FirstName" required="Y" />
    <field name="LastName" required="Y" />
    <field name="Sex" required="N" />
   </message>
   <message name="Car" id="C">
    <field name="Make" required="Y" />
    <field name="Model" required="Y" />
    <field name="Year" required="N" />
   </message>
  </messages>
 </data>

Using Linq, how would you get a list of all required field names for Person?

I just started playing with LINQ/XML today this is about as far as Ive gotten.

    var q = from c in loaded.Descendants("field")
            where (string)c.Attribute("required") == "Y" &&
            // how to check the parent node (message) has an attribute (id="P")           
            select (string)c.Attribute("name");

    foreach (string name in q)
        Console.WriteLine(name);
+1  A: 

You use the Ancestors() method or Parent property as defined by the System.Xml.Linq.XNode and System.Xml.Linq.XObject

John Leidegren
+1  A: 

This answer is totally wrong considering the questioner changed his xml. Should I delete this.

var q = from c in loaded.Descendants("field")
            where (string)c.Attribute("required") == "Y" &&
                    c.Parent.Attribute("id").Value == "P"
            select (string)c.Attribute("name");

Adding the Xml I used because there is some confusion over the right solution.

XDocument loaded = XDocument.Parse(@"
<message name=""Person"" id=""P"">
    <field name=""FirstName"" required=""Y"" /> 
    <field name=""LastName"" required=""Y"" />
    <field name=""Sex"" required=""N"" />
    <message name=""Car"" id=""C"">
        <field name=""Make"" required=""Y"" />
        <field name=""Model"" required=""Y"" />
        <field name=""Year"" required=""N"" />
    </message>
</message>");
jfar
Man I was soo close with c.Parent but just didnt see the attribute... Thank you!
Leroy Jenkins
+2  A: 

you can remove the separate foreach loop and ugly cast by doing the following as well

(from c in myXML.Descendants("field")
         where c.Attribute("required").Value == "Y" && 
         c.Parent.Attribute("id").Value == "P" 
         select c.Attribute("name").Value).ToList().ForEach(s => Console.WriteLine(s.ToString()));

Edit:

as the problem with null or optional attributes cropped up, here is an example of how to add an extension method to handle null attributes, you can pass it a default to use if one doesnt exist (that is the second parameter in the extension method).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;

namespace MyTestApp
{

class Program
{
    static void Main(string[] args)
    {
        XDocument myXML = XDocument.Load(@"C:\file.xml"); 

        (from c in myXML.Descendants("field")
         where c.Attribute("required")
               .GetAttributeValueOrDefault("N") == "Y" && 
                c.Parent.Attribute("id").Value == "P"      
         select 
         c.Attribute("name").Value).ToList().ForEach(s => Console.WriteLine(s.ToString()));

        Console.ReadLine();
    }
}

public static class XLinqHelper
{
    // extension method that handles an xattribute and returns the provided default if the Xattrib is null
    public static string GetAttributeValueOrDefault(this XAttribute s, string defaultValue)
    {
        string retVal;
        if (s == null)
            retVal = defaultValue;
        else
            retVal = s.Value;
        return retVal;


    }
}

}

Matt
strange, Im getting a NullReferenceException when I use .Valuethis is for both yours and Guffas examples.
Leroy Jenkins
How are you loading the XML
Matt
the xml you have up top is not valid, because you have no closing tags for the message elements. the parser will baulk at that anyway, so im assuming you have valid XML
Matt
XDocument loaded = XDocument.Load("file.xml");
Leroy Jenkins
yes the xml is valid.. I know I had typos and left things out.. it was just an example.
Leroy Jenkins
i cannot recreate a problem using a file with a root element and the tags closed, even without an XML declarartion. any chance you could post an excerpt of the file by editing the question?
Matt
I made some changes, but cant post the actual data, sorry. :(
Leroy Jenkins
i think ive worked out the problem, you must have elements where the required attribute is not present, in this case casting is more resilient, unless you have a value type in which case it will need to be nullable.
Matt
string is nullable (ref type) so when you cast it it just casts to null string with no issues, the Value isnt available where the attribute doesnt exist because you cant get the value property of null ... if that makes sense
Matt
so would the proper way (in this case) be to cast it to string?
Leroy Jenkins
you must be able to handle an attribute not being present, so the null case is important, casting handles this in either the value or ref type case as long as the type can handle nulls. you could knock together an extension method, but casting is useful for deriving other types from an xml document that arent string, you could also use the ternary operator in conjunction with Value but i think as long as you are clear with your intent it doesnt matter, personally i like an extension method solution as it can make clear your intent if well named
Matt
ive added another answer with an extension method to catch null attributes, run it as it appears (youll need an xml file like yours)
Matt
+2  A: 

I added a root element and closing tag for the messages to make the XML valid:

XDocument loaded = XDocument.Parse(@"
  <messages>
    <message name=""Person"" id=""P"">
      <field name=""FirstName"" required=""Y"" />
      <field name=""LastName"" required=""Y"" />
      <field name=""Sex"" required=""N"" />
    </message>
    <message name=""Car"" id=""C"">
      <field name=""Make"" required=""Y"" />
      <field name=""Model"" required=""Y"" />
      <field name=""Year"" required=""N"" />
    </message>
  </messages>");

Instead of looking up all fields and then check the parent for each of them, look up the one parent that you are interrested in so that you have less fields to examine:

IEnumerable<string> fields =
  loaded.Root.Elements()
  .Where(m => m.Attribute("id").Value == "P")
  .Single()
  .Elements("field")
  .Where(f => f.Attribute("required").Value == "Y")
  .Select(f => f.Attribute("name").Value);

Edit:
Added the specifier "field" for the child elements in case the message element contains any other kinds of elements.

Edit 2:
I put together a working example with a subset of the actual data:

XDocument loaded = XDocument.Parse(@"
  <fix major=""4"" minor=""4"">
    <header>
    </header>
    <trailer>
    </trailer>
    <messages>
      <message name=""ResendRequest"" msgtype=""2"" msgcat=""admin"">
        <field name=""BeginSeqNo"" required=""Y"" />
        <field name=""EndSeqNo"" required=""Y"" />
      </message>
      <message name=""Reject"" msgtype=""3"" msgcat=""admin"">
        <field name=""RefSeqNum"" required=""Y"" />
        <field name=""RefTagID"" required=""N"" />
        <field name=""RefMsgType"" required=""N"" />
        <field name=""SessionRejectReason"" required=""N"" />
        <field name=""Text"" required=""N"" />
        <field name=""EncodedTextLen"" required=""N"" />
        <field name=""EncodedText"" required=""N"" />
      </message>
    </messages>
  </fix>");

IEnumerable<string> fields =
  loaded.Root.Element("messages").Elements("message")
  .Where(m => m.Attribute("name").Value == "Reject")
  .Single()
  .Elements("field")
  .Where(f => f.Attribute("required").Value == "Y")
  .Select(f=>f.Attribute("name").Value);
Guffa
2nd guy to get this wrong. OQ said: // how to check the parent node (message) has an attribute (id="P") His Person has a Car. Its not Person and Car, its Person Has a Car
jfar
@jfar: What on earth are you talking about? Why do you think that there is a message node inside the message node?
Guffa
Right, there is a message in a message, not two separate messages.
jfar
Please dont read too much into the XML... I just made it up for an example.
Leroy Jenkins
@jfar: That doesn't answer my question. Why do you think that there is a message node inside the message node?
Guffa
@DataPimp, The xml layout changes the answer. I answered <message><message /></message>, Guffa is answering <messages><message /><message /></messages>...@Guffa, because he accepted my answer which assumes that? Does the accepted answer work with your xml?
jfar
@jfar: There is nothing in your answer that indicates any assumption about what the XML looks like. You have only revealed this assumption when you compain about other peoples answers.
Guffa
here is the real XML filehttp://www.quickfixengine.org/FIX44.xml
Leroy Jenkins
Personally I think its easier to assume somebody truncated the closing tags rather than malformed their xml document structure at a very basic level. This has been a good discussion though, now your right and I'm wrong since pimp posted his xml.
jfar
how about Im wrong for not making sure my xml was correct! you two just guessed at what I should have clarified in the first place.
Leroy Jenkins