My intention is to iterate through my lovely dictionary (both key and value are strings) and create an xml file from it.
I get the error on the last line (saving the xml).
"InvalidOperationException was unhandled
Token EndDocument in state Document would result in an invalid XML document."
Looking through this using breakpoints it would seem that at reaching the end of this, only the initial bit (outside the for each loop) has been done..
I'm half asking what silly mistake I've made, I'm partly asking if there's a more eloquent way of doing this.
Sorry if I've missed anything, let me know if i have, will add.
XDocument xData = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"));
foreach (KeyValuePair<string, string> kvp in inputDictionary)
{
xData.Element(valuesName).Add(
new XElement(valuesName,
new XAttribute("key", kvp.Key),
new XAttribute("value", kvp.Value)));
}
xData.Save("C:\\xData.xml");
Currently you're adding multiple elements directly to the document - so you'd end up with either no root elements (if the dictionary is empty) or potentially multiple root elements (if there's more than one entry in the dictionary). You want a root element, and then your dictionary elements under that. Additionally, you're trying to find an element called valuesName without ever adding anything, so you'll actually get a NullReferenceException if there are any dictionary entries.
Fortunately, it's even easier than you've made it, because you can just use LINQ to transform your dictionary into a sequence of elements and put that in the doc.
var doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("root",
inputDictionary.Select(kvp => new XElement(valuesName,
new XAttribute("key", kvp.Key),
new XAttribute("value", kvp.Value)))));
doc.Save(#"c:\data.xml");
Complete sample app:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
class Test
{
static void Main()
{
XName valuesName = "entry";
var dictionary = new Dictionary<string, string>
{
{ "A", "B" },
{ "Foo", "Bar" }
};
var doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("root",
dictionary.Select(kvp => new XElement(valuesName,
new XAttribute("key", kvp.Key),
new XAttribute("value", kvp.Value)))));
doc.Save("test.xml");
}
}
Output (order of entries is not guaranteed):
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<root>
<entry key="A" value="B" />
<entry key="Foo" value="Bar" />
</root>
An alternative breakdown for this would be:
var elements = inputDictionary.Select(kvp => new XElement(valuesName,
new XAttribute("key", kvp.Key),
new XAttribute("value", kvp.Value)));
var doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("root", elements));
You may find this simpler to read.
try the following
XDocument xData = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"));
XElement myXml = new XElement("paramList");// make sure your root and your descendents do not shre same name
foreach (var entry in MyList)
{
myXml.Add(new XElement(valuesName,
new XElement("key", entry.key),
new XElement("value", entry.Value)
));
xData.Add(myXml);
Related
I am new to C# and Linq. I am trying to write my Dictionary to file as XML. Therefore I use a foreach loop add all XElements to a root XElement. This is added to the document. But it does only add the last iteration of my loop to the document. What am I doing wrong?
Here is the code
public void ToXml(string xmlFile)
{
//Dictionary used: Values
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", null));
XElement xRoot = new XElement("RootElement");
doc.Add(xRoot);
Dictionary<double, double[]>.KeyCollection keys = Values.Keys;
foreach(double key in keys)
{
XElement inner = new XElement("InnerElement",
new XAttribute("value", key),
new XElement("TestValue1", Values[key][0]),
new XElement("Testvalue2", Values[key][1]),
new XElement("TestValue3", Values[key][2]),
new XElement("TestValue4", Values[key][3]),
new XElement("TestValue5", Values[key][4]),
new XElement("TestValue6", Values[key][5]));
xRoot.Add(inner);
}
doc.Save(xmlFile);
}
This is the output:
<?xml version="1.0" encoding="utf-8"?>
<RootElement>
<InnerElement value="400">
<TestValue1>0</TestValue1>
<Testvalue2>0</Testvalue2>
<TestValue3>200</TestValue3>
<TestValue4>0</TestValue4>
<TestValue5>100</TestValue5>
<TestValue6>491</TestValue6>
</InnerElement>
</RootElement>
The following is supposed to generate a list of all messages.
In practice I get a list of the rought length, but with the same element over and over.
Message is a class that get populated from the XmlNode sent to the constructor.
_messages = new List<Message>();
/*This does it*/
foreach (XmlNode n in thread.SelectNodes("//messages/message"))
{
_messages.Add(new Message(n));
}
/*So does this*/
XmlNode msgItr = thread.SelectSingleNode("//messages").FirstChild;
while (msgItr != null)
{
_messages.Add(new Message(msgItr));
msgItr = msgItr.NextSibling;
}
You are pathing to the wrong location, just use //message.
Here is two ways of enumerating the nodes, I am using LinqPad which Dump() shows one the current state.
XDocument xd = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("Messages",
new XElement("Message", "Alpha"),
new XElement("Message", "Beta"),
new XElement("Message", "Omega")
));
xd.Descendants("Message").Dump("XDocument");
XmlDocument xmd = new XmlDocument();
xmd.LoadXml(xd.ToString());
xmd.SelectNodes("//Message")
.Dump("XmlDocument");
Here is the results for the first and second dumps
I have the following code:
XNamespace testNM = "urn:lst-emp:emp";
XDocument xDoc;
string path = "project_data.xml";
if (!File.Exists(path))
{
xDoc = new XDocument(
new XDeclaration("1.0", "UTF-16", null),
new XElement(testNM + "Test")
);
}
else
{
xDoc = XDocument.Load(path);
}
var element = new XElement("key",
new XElement("Type", type),
new XElement("Value", value));
xDoc.Element(testNM + "Test").Add(element);
// Save to Disk
xDoc.Save(path);
which produces an output in the XML file like this:
<?xml version="1.0" encoding="utf-16"?>
<Test xmlns="urn:lst-emp:emp">
<key xmlns="">
<Type>String</Type>
<Value>somestring</Value>
</key>
</Test>
How can I get an output like this in the XML file instead?
<?xml version="1.0" encoding="utf-16"?>
<Tests xmlns="urn:lst-emp:emp">
<key name="someString">
<Type>String</Type>
<Value>somestring</Value>
</key >
</Tests>
Note its only the 3rd line that has changed in the XML file.
You can do it this way:
var element = new XElement("key",
new XAttribute("name", "someString"),
new XElement("Type", "type"),
new XElement("Value", "value"));
To provide a complete version of Bilyukov's answer, this should produce the output expected. Obviously substitute the "someString" static string with a variable populated as you wish. Changes include using XName.Get(string, string) to create the appropriate XName objects for the XElement constructors.
XNamespace testNM = "urn:lst-emp:emp";
XDocument xDoc;
string path = "project_data.xml";
if (!File.Exists(path))
{
xDoc = new XDocument(
new XDeclaration("1.0", "UTF-16", null),
new XElement(XName.Get("Tests", testNM.NamespaceName))
);
}
else
{
xDoc = XDocument.Load(path);
}
var element = new XElement(XName.Get("key", testNM.NamespaceName),
new XAttribute("name", "someString"),
new XElement("Type", type),
new XElement("Value", value));
xDoc.Element(XName.Get("Tests", testNM.NamespaceName)).Add(element);
// Save to Disk
xDoc.Save(path);
Your XML has default namespace. Descendants of the element where default namespace declared is considered in the same default namespace, unless it is explicitly declared with different namespace. That's why you need to use the same XNamespace for <key> element. :
var element = new XElement(testNM +"key",
new XAttribute("name", "someString"),
new XElement(testNM +"Type", type),
new XElement(testNM +"Value", value));
I tried:
textBox1.Text = new XDocument(new XDeclaration("1.0", "UTF-8", "yes"),
new XElement("root1", new XAttribute( "xmlns", #"http://example.com"), new XElement("a", "b"))
).ToString();
But I get:
The prefix '' cannot be redefined from '' to 'http://example.com' within the same start element tag.
I also tried substituting (according to an answer I found) :
XAttribute(XNamespace.Xmlns,...
But got an error as well.
Note: I'm not trying to have more than one xmlns in the document.
The way the XDocument API works with namespace-scoped names is as XName instances. These are fairly easy to work with, as long as you accept that an XML name isn't just a string, but a scoped identifier. Here's how I do it:
var ns = XNamespace.Get("http://example.com");
var doc = new XDocument(new XDeclaration("1.0", "utf-8", null));
var root = new XElement(ns + "root1", new XElement(ns + "a", "b"));
doc.Add(root);
Result:
<root1 xmlns="http://example.com">
<a>b</a>
</root1>
Note the + operator is overloaded to accept an XNamespace and a String to result in and XName instance.
I try to create an GPX XML document by LINQ to XML.
Everything works great, except adding xmlns, xmlns:xsi attributes to the doc. By trying it different way I get different exceptions.
My code:
XDocument xDoc = new XDocument(
new XDeclaration("1.0", "UTF-8", "no"),
new XElement("gpx",
new XAttribute("creator", "XML tester"),
new XAttribute("version","1.1"),
new XElement("wpt",
new XAttribute("lat","7.0"),
new XAttribute("lon","19.0"),
new XElement("name","test"),
new XElement("sym","Car"))
));
The output should also contain this:
xmlns="http://www.topografix.com/GPX/1/1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
How can I add it by Linq to XML? I tried several ways but it does not work, exceptions during compile time.
See How to: Control Namespace Prefixes. You could use code like this:
XNamespace ns = "http://www.topografix.com/GPX/1/1";
XNamespace xsiNs = "http://www.w3.org/2001/XMLSchema-instance";
XDocument xDoc = new XDocument(
new XDeclaration("1.0", "UTF-8", "no"),
new XElement(ns + "gpx",
new XAttribute(XNamespace.Xmlns + "xsi", xsiNs),
new XAttribute(xsiNs + "schemaLocation",
"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"),
new XAttribute("creator", "XML tester"),
new XAttribute("version","1.1"),
new XElement(ns + "wpt",
new XAttribute("lat","7.0"),
new XAttribute("lon","19.0"),
new XElement(ns + "name","test"),
new XElement(ns + "sym","Car"))
));
You have to specify the namespace for each element, because that's what using xmlns this way means.
From http://www.falconwebtech.com/post/2010/06/03/Adding-schemaLocation-attribute-to-XElement-in-LINQ-to-XML.aspx:
To generate the following root node and namespaces:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:SchemaLocation="http://www.foo.bar someSchema.xsd"
xmlns="http://www.foo.bar" >
</root>
Use the following code:
XNamespace xsi = XNamespace.Get("http://www.w3.org/2001/XMLSchema-instance");
XNamespace defaultNamespace = XNamespace.Get("http://www.foo.bar");
XElement doc = new XElement(
new XElement(defaultNamespace + "root",
new XAttribute(XNamespace.Xmlns + "xsi", xsi.NamespaceName),
new XAttribute(xsi + "schemaLocation", "http://www.foo.bar someSchema.xsd")
)
);
Be aware - if you want to add elements to the document, you need to specify the defaultNamespace in the element name or you will get xmlns="" added to your element. For example, to add a child element "count" to the above document, use:
xdoc.Add(new XElement(defaultNamespace + "count", 0)