-1

How to get the contents(or perform some sort of checking) between two same nodes with consecutive attribute values. Take the below sample xml file

<items>
    <item id="0001" type="donut">
        <name>Cake</name>
        <ppu>0.55</ppu>
        <batters>
            <batter id="1001">Regular</batter> is good <ax>1023</ax> and <batter id="1002">Chocolate</batter> or maybe <batter id="1003">Blueberry</batter>
        </batters>
        <topping id="5001">None</topping>
        <topping id="5002">Glazed</topping>
        <topping id="5005">Sugar</topping>
        <topping id="5006">Sprinkles</topping>
        <topping id="5003">Chocolate</topping>
        <topping id="5004">Maple</topping>
    </item>
    ...
</items>

How do I get the contents between the nodes <batter id="..."> and the next <batter id="..."> i.e. is good <ax>1023</ax> and and or maybe using linq-to-xml?

Bumba
  • 321
  • 3
  • 13
  • List ids = doc.Descendants("topping").Where(x => ((int)x.Attribute("id") >= 5001) && ((int)x.Attribute("id") <= 5006)).tOlIST(); – jdweng Mar 02 '18 at 16:00
  • I think it is duplicate of https://stackoverflow.com/questions/22847802/split-a-list-into-sublist-by-checking-a-condition-on-elements - please comment if it is exactly what you are looking for so I can vote as duplicate. – Alexei Levenkov Mar 02 '18 at 16:01
  • @AlexeiLevenkov no it is not what I'm looking for – Bumba Mar 02 '18 at 16:04
  • Sure. You should than [edit] to clarify your question... So far it looks exactly as "I have list of nodes (children of a particular node) and want to separate them into groups marked by "batter" node"... Any chance you are just looking for ignoring "batter" nodes (like https://stackoverflow.com/questions/3279145/remove-item-from-list-based-on-condition)? (At least Leonardo Seccia read post that way) – Alexei Levenkov Mar 02 '18 at 16:16

1 Answers1

1

Assuming you simply want to take all child nodes but ignore nodes by condition (element name is "batter" in your case) then basic Where condition on all child nodes would do:

string s = @"<items>
    <item id='0001' type='donut'>
        <name>Cake</name>
        <ppu>0.55</ppu>
        <batters>
           <batter id='1001'>Regular</batter> is good <ax>1023</ax> and <batter id='1002'>Chocolate</batter> or maybe <batter id='1003'>Blueberry</batter>
           </batters>
        <topping id='5001'>None</topping>
        <topping id='5002'>Glazed</topping>
        <topping id='5005'>Sugar</topping>
        <topping id='5006'>Sprinkles</topping>
        <topping id='5003'>Chocolate</topping>
        <topping id='5004'>Maple</topping>
    </item>
</items>";

var yourNodes = XDocument.Parse(s)
   .Descendants("batters").Nodes()
   .Where(a=> 
       a.NodeType == XmlNodeType.Text || // take all text nodes
       // or elements that are not "batter".
       (a.NodeType == XmlNodeType.Element && ((XElement)a).Name 
!= "batter"));

string concatenated = yourNodes.Select(a=>a.ToString()).Aggregate((a,b)=>a+b);
Leo
  • 5,013
  • 1
  • 28
  • 65
  • thanks for updating the answer but there is still one small problem `is good 1023 and` is broken into three different values ` is good `, `1023` and ` and ` in the collection `yourNodes`.. – Bumba Mar 02 '18 at 16:25
  • How about `yourNodes.Select(a=>a.ToString()).Aggregate((a,b)=>a+b)`? I have added it to the answer too... – Leo Mar 02 '18 at 16:28
  • @AlexeiLevenkov i'm looking for the outer text between two `` nodes in a specific parent node. i.e. the text between one closing node `` and the next opening node ``, do you get it now? – Bumba Mar 02 '18 at 16:37
  • @Bumba you mean you want `is good 1023 and` as single string? That is very strange thing to wish for as you can't really (correctly) do much with such string since it is no longer valid XML... Leonardo's comment should give you what you want (also somewhat strange to re-implement string.Join using Aggregate - https://stackoverflow.com/questions/23082337/what-are-the-counterparts-of-innerxml-and-outerxml-in-xdocument) – Alexei Levenkov Mar 02 '18 at 16:52
  • @AlexeiLevenkov I know that...but I need everything between `` and the next opening node `` in a single string including other nodes and child nodes(if there is) whatever falls in between and so on for the next `` to `` ... – Bumba Mar 02 '18 at 16:57
  • @AlexeiLevenkov, the reason I used `yourNodes.Select(a=>a.ToString()).Aggregate((a,b)=>a+b)` is because it can be concatenated to the previous line if needed and personally I find it clearer than `string.Join("", yourNodes.Select(a=>a.ToString()).ToArray())`... Why do you find it strange? – Leo Mar 02 '18 at 17:50
  • @LeonardoSeccia for me `string.Join` self-documents what you trying to achieve, while `.Aggregate` can be used for pretty much anything and hence often requires explanation what is the actual goal. (But indeed it is personal preference - we seem to find each other approach strange which is perfectly fine) – Alexei Levenkov Mar 02 '18 at 18:05
  • @LeonardoSeccia note that constructing strings with `+` from a collection of strings could be performance issue - correct code with `.Aggregate` and `StringBuilder` is way less readable for me than `string.Join` (that properly uses `StringBuilder` inside already). – Alexei Levenkov Mar 02 '18 at 18:08
  • @AlexeiLevenkov - considering that in this case we are talking about 3 strings, it is likely that instantiating a StringBuilder object + calling `ToArray()` takes longer than just using `+` in the `Aggregate()` method... In any case the main reason to use `Aggregate()` is the fact that it can be appended to the same line ie. `XDocument.Parse(s).Descendants("batters").Nodes().Where(a=> a.NodeType == XmlNodeType.Text || (a.NodeType == XmlNodeType.Element && ((XElement)a).Name != "batter")).Select(a=>a.ToString()).Aggregate((a,b)=>a+b);` – Leo Mar 02 '18 at 18:13