Search code examples
c#xmllinqlinq-to-xmlxelement

Accessing XElement with specific value


I am trying to save my battleship game to XML and so far it has gone pretty good. What I'm saving in the XML file is player names, coordinates for ships and misses.

The ID attribute for the ships is to know which ship coordinates makes a whole ship. Next up was marking each ship that it has been hit by adding an attribute to the correct element and I know what element to get by the value of it.

But I do not know how to get it. All I have found is how to get XElement by its name and all elements that has that name.

<?xml version="1.0" encoding="UTF-8" ?>
<Players>
  <Playerboard>
    <Name>Player</Name>
    <Ships>
      <ship id="1213">12_13</ship>
      <ship id="1213">12_14</ship>
      <ship id="1211">12_11</ship>
      <ship id="1211">12_12</ship>
    </Ships>
    <Misses>
      <Miss>3_9</Miss>
      <Miss>9_12</Miss>
      <Miss>10_12</Miss>
    </Misses>
  </Playerboard>
  <Computerboard>
    <Name>AI</Name>
    <Ships>
      <ship id="1010">10_10</ship>
      <ship id="1010">10_11</ship>
      <ship id="1010">10_12</ship>
      <ship id="1010">10_13</ship>
      <ship id="11">1_1</ship>
      <ship id="11">2_1</ship>
    </Ships> 
    <Misses>
      <Miss>0_2</Miss>
      <Miss>0_3</Miss>
      <Miss>1_3</Miss>
    </Misses>
  </Computerboard>
</Players>

this is as far as i've come with trying to get the correct element, which doesnt work. "k" in my example below is empty all the time.

XElement e = xelement.Element("Playerboard");
var ships = from s in e.Elements("Ships")            
                        where (string)s.Element("ship") == x + "_" +y
                        select s;

foreach (XElement k in ships) 
{ 
   Console.WriteLine(k);
}

So for example I want to add an attribute to the ship with value 12_12, how do I do that?


Solution

  • This works fine for me:

    var doc = XDocument.Parse(xml);
    
    var elements =
        doc
        .Root                       //Ignore the root
        .Element("Playerboard")     //The Playerboard element.
        .Element("Ships")           //There's only one Ships element.
        .Elements("ship")           //Get all the ship elements from Ships.
        .Where(ship => ship.Value == "12_12");  //.Value is a little more explicit but you
                                                //want strings anyway.
    
    foreach (var element in elements)
    {
        Console.WriteLine(element);
        element.Add(new XAttribute("Name", "Value")); //Adding your new attribute.
        Console.WriteLine(element); 
    }
    

    I'm using the lambda syntax because I prefer it (sorry, it just makes it much clearer what I'm doing).

    If you only expect one ship element to match the given value, you can just use .Single() at the end to avoid the collection too.