Reputation: 245
I am writing a function to update my XML and am having some issues. I'd hoped I could just set old XElement directly to the updated one but that doesn't work in the foreach loop (though should only ever be one query result). I was going to hard-code each field, but then decided to use a loop. However when it hits the section and it's sub-elements it reads it all as a single element and messed up the file
var myDevice = from c in appDataXml.Descendants("device")
where (string)c.Element("name") == App.CurrentDeviceName
&& (string)c.Element("ip") == App.CurrentIp
select c;
if (myDevice.Any())
{
foreach (XElement device in myDevice)
{
//device.Element("customname").Value = updatedDevice.Element("customname").Value;
for (int a = 0; a < device.Elements().Count(); a++)
{
string field = device.Elements().ElementAt(a).Name.ToString();
device.Element(field).Value = updatedDevice.Element(field).Value;
}
}
}
sample XML
<Devices>
<device>
<name>blah</name>
<customname>custom</customname>
<ip>192.168.1.100</ip>
<port>1000</port>
<sources>
<source>
<code>00</code>
<name>blah2</name>
<hidden>False</hidden>
</source>
<source>
...etc
</sources>
</device>
Upvotes: 0
Views: 340
Reputation: 13010
Don't use the XElement.Value Property. Use the XElement.ReplaceWith Method. Value's getter outputs
A String that contains all of the text content of this element. If there are multiple text nodes, they will be concatenated.
which means that the subelements' tags will be stripped out.
Try this line instead of using Value:
device.Element(field).ReplaceWith(updatedDevice.Element(field));
Upvotes: 0
Reputation: 11955
You need to abstract (make classes) of your xml, it will help you CRUD and search it.
Example: (using these extensions: http://searisen.com/xmllib/extensions.wiki)
public class Device
{
const bool ELEMENT = false;
const bool ATTRIBUTE = true;
XElement self;
public Device(XElement self) { this.self = self; }
public string CustomName
{
get { return self.Get("customname", string.Empty); }
set { self.Set("customname", value, ELEMENT); }
}
public string Name { get { return self.Get("name", string.Empty); } }
public string Ip { get { return self.Get("ip", "0.0.0.0"); } }
public int Port { get { return self.Get("port", 0); } }
public Source[] Sources
{
get { return _Sources ?? (_Sources = self.GetEnumerable("sources/source", xsource => new Source(xsource)).ToArray()); }
}
Source[] _Sources;
public class Source
{
XElement self;
public Source(XElement self) { this.self = self; }
public string Code { get { return self.Get("code", string.Empty); } }
public string Name { get { return self.Get("name", string.Empty); } }
public bool Hidden { get { return self.Get("hidden", false); } }
}
}
An example using it:
XElement xdevices = XElement.Load(file.FullName);
Device[] devices = xdevices.GetEnumerable("device", xdevice => new Device(xdevice)).ToArray();
var myDevice = devices
.Where(d => d.Name == App.CurrentDeviceName
&& d.Ip == App.CurrentIp);
foreach (Device device in myDevice)
{
string name = device.Name;
foreach (Device.Source source in device.Sources)
{
string sourceName = source.Name;
}
device.CustomName = "new name";
}
xdevices.Save(file.FullName);
It is a change in the way of thinking, so instead of worrying about how to reference a value, you create the class to read/write to the xml, and instead you just get/pass off data to a class, that then reads/writes the xml.
Edit: ---- Add to XElementConversions class ----
To be consistent with the file, and to work properly I made detailed versions. You can modify them to make the other types, like bool, DateTime, etc.
public static int GetInt(this XElement source, string name, int defaultValue)
{
source = NameCheck(source, name, out name);
return GetInt(source, source.GetDefaultNamespace() + name, defaultValue);
}
public static int GetInt(this XElement source, XName name, int defaultValue)
{
int result;
if (Int32.TryParse(GetString(source, name, null), out result))
return result;
return defaultValue;
}
Upvotes: 1
Reputation: 273244
Do you mean that var myDevice = ... select c;
will produce at most 1 element?
If so then replace the hole .Any()
and foreach()
with a .Single()
:
var myDevices = ... select c;
var myDevice = myDevices.Single(); // exception when Count != 1
And then I see no reason why this wouldn't work:
myDevice.Element("customname").Value = updatedDevice.Element("customname").Value;
Upvotes: 1
Reputation: 26922
You shouldn't use XElement.Value
but iterate over the XElement
elements within your device XElement
and change their XText
nodes. Alternatively you could use XAttribute
so you don't need to make so many nested elements.
<Devices>
<Device Name="blah" IP="192.168.1.100" Port="1000">
<Sources>
<Source Code="00" Name="blah2" Hidden="false"/>
</Sources>
</Device>
</Devices>
XElement.Value
is a concatenated string of all the XText
nodes that are a descendant of the XElement
. If you set
it, you consequently overwrite all the sub elements. Read more on XElement.Value
Upvotes: 0