Search code examples
c#xmllinqxelementsetvalue

c# linq change the value of xml element which contains xml tags


I tested with the following source xml File

    <?xml version="1.0" encoding="utf-8" standalone="no"?>
<root>
    <key name="some text" id="1"/>
    <key name="some text" id="2"/>
    <house id="H1">
        <floor id="F1">
            <room id="R1">Child1<electrics><socket id="C1S1"/></electrics></room>
            <room id="R2">Child2<electrics><socket id="C2S1"/></electrics></room>
        </floor>
    </house>
</root>

I would like to copy/clone the complete tag (room id="R1") and paste as last room within floor. At the same time I want to change the tag value of (room id="R1") to "Whatever" and the (socket id) within the (room)-Tag to "new room"

Finaly I want to get the following xml-structure

    <?xml version="1.0" encoding="utf-8"?>
<root>
  <key name="some text" id="1" />
  <key name="some text" id="2" />
  <house id="H1">
    <floor id="F1">
      <room id="R1">Child1<electrics><socket id="C1S1" /></electrics></room>
      <room id="R2">Child2<electrics><socket id="C2S1" /></electrics></room>
      <room id="R1">Whatever<electrics><socket id="new room" /></electrics></room>
    </floor>
  </house>
</root>

But I have problems with that. After creating the clone I can not change the value of the new room, without deleting the nested tags. The third last comand causes my problem. If you skip that comand you get a nearly perfect clone, but I do not know how to change the value.

Here my c# Code

    using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Xml.Linq;

namespace linqxmlTester
{
    public partial class Form1 : Form
    {
        private XElement xmlDoc;

        public Form1()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {

            xmlDoc = XElement.Load("house.xml");

            // Select rooms to clone I realy only want to selekt one room, but I have to go over rooms
            IEnumerable<XElement> newRoom = from rooms in xmlDoc.Element ("house")
                                                                .Element ("floor")
                                                                .Elements("room")

                                                    where rooms.ToString().IndexOf("C1S1") > 0

                                                    select rooms;
            // I've selected realy one room in rooms with the following I clone beheind last room
            xmlDoc.Element ("house")
                  .Element ("floor")
                  .Elements("room").Last()
                  .AddAfterSelf(newRoom.First()); // there is only one

            // This worked   :-)

            //But now I want to change socket id value (="C1S1") to something new
            //I select my last created room

            XElement newRoom1 = xmlDoc.Element("house")
                                      .Element("floor")
                                      .Elements("room").Last();

            //within the newRoom1 I search for the attribute id

            foreach (var r in newRoom1.Elements("electrics").Elements("socket"))
            {
                Debug.WriteLine("aktValue " + r.Attribute("id").Value);
                r.Attribute("id").Value = "new room";
                Debug.WriteLine("newValue " + r.Attribute("id").Value);
            }

            // this is working too :-)

            // Now I only still have to change the value of my newroom
            // it should be the following code

            Debug.WriteLine("OldValue " + newRoom1.Value); // prints only the value without the embeded xml tags

            newRoom1.SetValue("Whatever"); // this kills the complete xml structure with is embede at the side of the value

            Debug.WriteLine("NewValue " + newRoom1.Value); // prints only the newvalue without the embeded xml tags

            xmlDoc.Save("housenew.xml");
        }
}
}

How can I change the value in c# using linq, without deleting an embeded xml structure?

Thanks for your tips


Solution

  • You'll have to see your room element as a node of a tree. Everything below that is a node as well, so in fact, Child1 is a node (text) and electrics is node (element).

    Visually represented:

    * Element: room (with attribute id)
    |- Text: Child1
    |- Element: electrics
       |- Element: socket (with attribute id)
    

    So to get to that text-node (Child1), just change the first subnode of room like such:

    newRoom1.Nodes().First().ReplaceWith("Whatever");
    

    If you'd change .Value of room, you'd be replacing the entire subtree of room.

    I'd avoid this kind of structure though, if possible. Change the 'Child1' value so that it is either an attribute of room (preferred), or wrap it in a node so that <room id="1"><description>Child1</description></room>