none
XML to LinQ - General questions RRS feed

  • Question

  • Hello,
    How can I create a query and make sure if the element does not exist, an exception is thrown?
    With
    resultPosition?.Element
    is not working.
    I must check it this way.
    if (resultPosition.Element("PROCESS") == null)
    	continue;
    // ------------------------------------
    Which spelling is better? What are they called? Isn't both LinQ?
    var resultProcessStep2 = (from x in resultPosition?.Element("PROCESS")
    //or
    var resultProcessStep = resultPosition?.Element("PROCESS")?.Elements()
    // ------------------------------------	
    How can I get the next element and output the name? NextElement does not exist. 
     xeStep.HasElements
    Is perhaps a variant faster? 
    // -------------------------------------

    FirstOrDefault() --> can be null
    First()          --> if null get an exception, right?
    Select.Single()
    My XML and my coding. Work well now. 
    <SIDE1>
    <POSITIONS>
    	<POSITION>
    		<NUMBER value="1" />
    		<ACTIVE value="1" />
    		<PROCESS>
    			<STEP value="GROUP_RECEIVER">
    				<COLOR1 value="green" />
    				<COLOR2 value="red" />
    			</STEP>
    			<STEP value="GROUP_RADAR">
    
    ....
    <SIDE2>
    ...
    <SIDE3>
    for (int side = 1; side <= 3; side++)
    {
    	var resultPositions = xDocProgramID.Element("ROOT").
    Element("PROGRAM").
    Element($"SIDE{side}").Element("POSITIONS")?.Elements(); foreach (var resultPosition in resultPositions) { if (resultPosition.Element("PROCESS") == null) continue; var resultProcessStep2 = (from x in resultPosition?.
    Element("PROCESS")?.
    Elements() where x?.Name?.LocalName == "STEP" && x.Attribute("value")?.
    Value.ToLower() == "group_receiver" select x).ToList(); var resultProcessStep = resultPosition?.
    Element("PROCESS")?.Elements().
    Where(x => x.Name?.LocalName == "STEP" &&
    x.Attribute("value")?.Value.ToLower() == "markposition").ToList(); foreach (XElement xeStep in resultProcessStep.Elements()) { //XElement xeCheckIt = (XElement) xeStep.NextNode; switch (xeStep?.Name.LocalName) { case "COLOR1": xeStep.SetAttributeValue
    ("value", newColor1);

    Many thanks for tips and your help in advance.
    Many greetings
     Markus




    Friday, November 1, 2019 10:31 AM

Answers

  • Node? There is a difference between XML nodes and elements. Everything in XML is a node (elements, attributes, comments, declarations, etc). Elements are the typical things you want (those in begin/end tags). `Elements` only returns elements. It skips non-element nodes as this is most likely what you want. If you really want the nodes then use the `Nodes` method instead. Either way `FirstOrDefault` will return the first one, if any.

    Given the XML you posted earlier, you only have elements and attributes so I don't think you want the next node. If you want the attributes of an element use `Attributes`. But taking a closer look at your code it seems like you're doing way too much. Each time you're starting with the root and working your way down. If you know which element(s) you want then you can use XPath to get them.

    //Create some extension methods to make your XML easier to read
    public static class XElementExtensions
    {
        public static string GetValue ( this XElement source ) => source?.Attribute("value")?.Value ?? "";
    
        public static bool HasValue ( this XElement source, string name ) => String.Compare(source?.GetValue(), name, true) == 0;
    
        public static void SetValue ( this XElement source, string value ) => source.Attribute("value").Value = value;
    }
    
    //The original code you posted, modified to use XPath and the helpers
    static void ProcessSide ( XDocument doc, string sideName )
    {
        //Get the side, if any - only doing this in case you want to get other elements later
        var side = doc.XPathSelectElement($"{sideName}");
    
        //Get the steps - this just saves a longer query later...
        var steps = side.XPathSelectElements("POSITIONS/POSITION/PROCESS/STEP");
    
        //Couple of different ways to go here, if most the steps you don't care about then just query for the ones you want
        var groupReceiverSteps = steps.Where(x => x.HasValue("group_receiver"));
        foreach (var step in groupReceiverSteps)
        { };
    
        //Or, if you care about all the steps you just process them differently then just enumerate all the steps
        foreach (var step in steps)
        {
            //Take a look at the switch pattern matching in C# 7.3 if you want to simplify this
            //Or if you have a lot of elements use a dictionary to map names to methods to be called
            switch (step.GetValue().ToLower())
            {
                case "group_receiver": ProcessGroupReceiver(step); break;
            };
        };
    }
    
    //Using separate functions makes it easier to read your code
    static void ProcessGroupReceiver ( XElement element )
    {
        //Get the colors
        var color1 = element.Element("COLOR1").GetValue();
        var color2 = element.Element("COLOR2").GetValue();
    
        //Now what??
    }
    
    //How you might use it - using XDocument.Parse here but it doesn't matter where XDocument comes from
    var xDoc = XDocument.Parse(xml);
    
    for (var side = 1; side <= 3; ++side)
    {
        //Breaking this up so it is easier to see what is going on
        ProcessSide(xDoc, $"SIDE{side}");                
    };
    


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by Markus Freitag Saturday, November 2, 2019 5:08 PM
    Saturday, November 2, 2019 12:55 PM
    Moderator

All replies

  • "How can I create a query and make sure if the element does not exist, an exception is thrown?
    With
    resultPosition?.Element
    is not working.

    I must check it this way."

    What you're asking for and what the code you posted is doing are 2 different things. Do you want an exception if `resultPosition`is `null`? If so then don't use the null conditional operator (?.)

    //Throws a NullReferenceException if resultPosition is null
    resultPosition.Element("PROCESS")

    If you want to check a value for null and throw if it is then use a throw expression.

    //Gets someExpression if it is not null or throws otherwise
    someExpression ?? throw new ArgumentNullException("arg");

    As for the final block of code you posted it seems like you don't want to throw an exception. If you cannot find `resultPosition` or the element `PROCESS` in it then you are skipping the current loop. Therefore the null conditional check you originally posted is correct.

    //If we cannot find resultPosition or PROCESS within then 
    //skip this element
    if (resultPosition?.Element("PROCESS")) == null)
       continue;

    "Which spelling is better? What are they called? Isn't both LINQ?"

    What spelling are you talking about? The `from` syntax is known as LINQ syntax. It is equivalent to calling the extension methods defined by LINQ. Either syntax is appropriate depending upon your preference. In most code you'll find a mixture because sometimes the syntax makes more sense and in other cases the extension methods do. It has no impact on the final code as it compiles down to the same thing.

    "How can I get the next element and output the name? NextElement does not exist. "

    Not sure what you mean here. `FirstOrDefault` gets the first item (if any) in an `IEnumerable{T}`. If you want the first child of an existing element then it is appropriate.

    var firstChild = someElement.Elements().FirstOrDefault();

    There is no need to test for elements first as it already handles that. Personally I always use this method. `First` will throw an exception if there are no elements and I generally build code such that the elements are optional anyway. However if you call `FirstOrDefault` and then want to throw if there are none then use `First` instead.

    `Single` throws if there is not exactly 1 (e.g. 0 or 2+). In my experience it is not useful.


    Michael Taylor http://www.michaeltaylorp3.net

    Friday, November 1, 2019 2:40 PM
    Moderator

  • Not sure what you mean here. `FirstOrDefault` gets the first item (if any) in an `IEnumerable{T}`. If you want the first child of an existing element then it is appropriate.

    var firstChild = someElement.Elements().FirstOrDefault();


    Hello,

    I'm looking for NextNode. OK, if I need the next node (element), I can use FirstOrDefault.

    Right? Or Do you know a way to cast the element name?

    With best regards Markus 

    Saturday, November 2, 2019 11:20 AM
  • Node? There is a difference between XML nodes and elements. Everything in XML is a node (elements, attributes, comments, declarations, etc). Elements are the typical things you want (those in begin/end tags). `Elements` only returns elements. It skips non-element nodes as this is most likely what you want. If you really want the nodes then use the `Nodes` method instead. Either way `FirstOrDefault` will return the first one, if any.

    Given the XML you posted earlier, you only have elements and attributes so I don't think you want the next node. If you want the attributes of an element use `Attributes`. But taking a closer look at your code it seems like you're doing way too much. Each time you're starting with the root and working your way down. If you know which element(s) you want then you can use XPath to get them.

    //Create some extension methods to make your XML easier to read
    public static class XElementExtensions
    {
        public static string GetValue ( this XElement source ) => source?.Attribute("value")?.Value ?? "";
    
        public static bool HasValue ( this XElement source, string name ) => String.Compare(source?.GetValue(), name, true) == 0;
    
        public static void SetValue ( this XElement source, string value ) => source.Attribute("value").Value = value;
    }
    
    //The original code you posted, modified to use XPath and the helpers
    static void ProcessSide ( XDocument doc, string sideName )
    {
        //Get the side, if any - only doing this in case you want to get other elements later
        var side = doc.XPathSelectElement($"{sideName}");
    
        //Get the steps - this just saves a longer query later...
        var steps = side.XPathSelectElements("POSITIONS/POSITION/PROCESS/STEP");
    
        //Couple of different ways to go here, if most the steps you don't care about then just query for the ones you want
        var groupReceiverSteps = steps.Where(x => x.HasValue("group_receiver"));
        foreach (var step in groupReceiverSteps)
        { };
    
        //Or, if you care about all the steps you just process them differently then just enumerate all the steps
        foreach (var step in steps)
        {
            //Take a look at the switch pattern matching in C# 7.3 if you want to simplify this
            //Or if you have a lot of elements use a dictionary to map names to methods to be called
            switch (step.GetValue().ToLower())
            {
                case "group_receiver": ProcessGroupReceiver(step); break;
            };
        };
    }
    
    //Using separate functions makes it easier to read your code
    static void ProcessGroupReceiver ( XElement element )
    {
        //Get the colors
        var color1 = element.Element("COLOR1").GetValue();
        var color2 = element.Element("COLOR2").GetValue();
    
        //Now what??
    }
    
    //How you might use it - using XDocument.Parse here but it doesn't matter where XDocument comes from
    var xDoc = XDocument.Parse(xml);
    
    for (var side = 1; side <= 3; ++side)
    {
        //Breaking this up so it is easier to see what is going on
        ProcessSide(xDoc, $"SIDE{side}");                
    };
    


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by Markus Freitag Saturday, November 2, 2019 5:08 PM
    Saturday, November 2, 2019 12:55 PM
    Moderator
  • Hello,

    Thank you very much for your answer, tips and hints.

    public static bool HasValue ( this XElement source, string name ) =>

    String.Compare(source?.GetValue(), name, true) == 0;

    Like that, is helpful to me.

    Greetings Markus

    Saturday, November 2, 2019 5:08 PM