Problem appending a new element to xml file...

S

sherifffruitfly

Hi all,

I'm trying to distill all of the info from google searches into what I
need, with partial success. In truth, the whole xmlNode, Document,
Element, etc group of classes & methods is going over my head - lol!

The structure of the xml file I'm trying to append to is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!-- stuff -->
<!-- more stuff -->
<Scenarios attr="0">
<Scenario num="0">
<TimeSeries name="cdj" >
<Observation date="01/01/2001" value="1000"/>

a whole bunch more observations

</TimeSeries>
</Scenario>
</Scenarios>

I'm trying to code up a routine that adds another Observation to the
list. Here's what I've got:

public void WriteHistory(HistoryType histype, double val)
{
try
{
XmlDocument cdjDoc = new XmlDocument();
XmlNode parentNode;

cdjDoc.LoadXml(monthlyPath + filename);
parentNode = cdjDoc.DocumentElement;

XmlNode newElement = cdjDoc.CreateNode(XmlNodeType.Element,
"Observation", null);
cdjDoc.AppendChild(newElement);

XmlAttribute date = cdjDoc.CreateAttribute("date");
date.InnerText = DateTime.Now.ToString("MM/dd/yyyy");
newElement.Attributes.Append(date);

XmlAttribute rate = cdjDoc.CreateAttribute("value");
rate.InnerText = val.ToString();
newElement.Attributes.Append(rate);

XmlTextWriter writer = new XmlTextWriter(monthlyPath +
filename,null);
cdjDoc.Save(writer);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message,"Screwed!");
}

This doesn't work - throws an exception with message:

The data at the root level is invalid. Line 1, position 1.

I believe the problem is that I'm not "finding" the proper spot in the
xml structure to AppendChild my new element to. Can someone help me
with this? (Or any other problem, if I'm mistaken in my diagnosis?)

Thanks for any guidance,

cdj
 
M

Marc Gravell

You're making it very hard on yourself.

Try this (and note than most of the "code" is the XML block to make it
demonstrable); I have also used a more exotic query in SelectSingleNode
than is probably necessary... depends if you need to select a
*specific* scenario/series; if only one you can drop all the
[@something='value'] stuff...

using System;
using System.Xml;
class Program {
static void Main()
{
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8""?>

<Scenarios attr=""0"">
<Scenario num=""0"">
<TimeSeries name=""cdj"" >
<Observation date=""01/01/2001"" value=""1000""/>
<Observation date=""02/02/2001"" value=""2000""/>
<Observation date=""03/03/2001"" value=""3000""/>
</TimeSeries>
</Scenario>
</Scenarios>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml); //TODO: doc.Load(filename);
XmlElement el = (XmlElement)
doc.SelectSingleNode(@"Scenarios[@attr='0']/Scenario[@num='0']/TimeSeries[@name='cdj']");
XmlElement newObservation =
(XmlElement)el.AppendChild(doc.CreateElement("Observation"));
newObservation.SetAttribute("date",
DateTime.Today.ToString("MM/dd/yyyy"));
newObservation.SetAttribute("value",
DateTime.Today.ToString("3000"));

Console.WriteLine(doc.OuterXml); //TODO: doc.Save(filename);
}
}
 
M

Marc Gravell

Actually, I note your example hints at another level of xml nodes (not
shown)... in which case either include this in the XPath/XQuery, or
replace doc.SelectSingleNode(...) with
doc.DocumentElement.SelectSingleNode(...), which will take you inside
the outer element.

Marc
 
S

sherifffruitfly

Marc said:
Actually, I note your example hints at another level of xml nodes (not
shown)... in which case either include this in the XPath/XQuery, or
replace doc.SelectSingleNode(...) with
doc.DocumentElement.SelectSingleNode(...), which will take you inside
the outer element.

Marc

As the example structure I gave indicates, it is theoretically possible
for there to be multiple X's within a given Y for a variety of Y's.

In actual fact, the only case of interest to what I need is the
multiple Observations within a TimeSeries. Or to put it equivalently (I
hope): Observations are the only nodes with siblings.

Thanks again!

cdj
 
M

Marc Gravell

Glad to help - any problems, be sure to get back...

For completeness: your original code touched on XmlWriter; this is
typically used *instead of* the XmlDocument model in those scenarios
where XmlDocument is too heavy - just as very large xml fragments; with
XmlWriter you could write a 500Mb file from a database by writing one
row at a time directly to hte stream; with the XmlDocument you would
need to hold it all in memory first, probably taking about 4-times
that, so 2Gb (better hope for Win64 memory addressing!).
XmlDocument, however, uses the "all in memory" model to provide
services such as XPath/XQuery, and simple element manipulation. There
are other differences, but that is the general principle; for
small-to-mid-size xml, XmlDocument is a reasonable choice.

Marc
 
S

sherifffruitfly

Marc said:
Glad to help - any problems, be sure to get back...

For completeness: your original code touched on XmlWriter; this is
typically used *instead of* the XmlDocument model

The biggest problem I'm having now is preserving all of the pretty
tabs/formatting in the xml document. The PreserveWhiteSpace property
does better than nothing, but falls substantially short of what might
be expected. For example, even with PreserveWhiteSpace, a newline is
*not* inserted after the new Observation is added - this surprises me.

Is there a straightforward way to maintain the tab/newline structure?
(It's intuitively obvious what structure I'm talking about - tabs to
indicate hierarchy-level-descent, newlines for new elements.)

You brought up a new issue to me: the xmlDocument vs XmlWriter. The
code I've been working with, per your suggestion (I thought - lol) is:

XmlDocument cdjDoc = new XmlDocument();
cdjDoc.PreserveWhitespace = true;

XmlTextReader reader = new XmlTextReader(monthlyPath + filename);
cdjDoc.Load(reader);
reader.Close();

XmlElement el =
(XmlElement)cdjDoc.SelectSingleNode(@"Scenarios[@version='1']/Scenario[@id='0']/TimeSeries[@name='cdj']");
XmlElement newObservation =
(XmlElement)el.AppendChild(cdjDoc.CreateElement("Observation"));
newObservation.SetAttribute("date",
DateTime.Today.ToString("MM/dd/yyyy"));
newObservation.SetAttribute("value", val.ToString());

XmlTextWriter writer = new XmlTextWriter(monthlyPath +
filename,null);
cdjDoc.Save(writer);
writer.Close();

Is there a better way to load/save the xml file to/from disk besides
this? Do tell. lol

thanks again!

cdj
 
S

sherifffruitfly

sherifffruitfly said:
The biggest problem I'm having now is preserving all of the pretty
tabs/formatting in the xml document.

Nevermind on the prettyprinting. The following lines, in the right
place, completely suffice for me:

cdjDoc.CreateNode(XmlNodeType.Text, "\n", null);

writer.Formatting = Formatting.Indented;
writer.Indentation = 4;

Thanks for your previous help tho!

cdj
 
M

Marc Gravell

I was going to suggest:

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineHandling = NewLineHandling.Entitize;
using (Stream fs = File.OpenWrite(@"c:\out.xml"))
using (XmlWriter writer = XmlWriter.Create(fs, settings))
{
doc.Save(writer);
writer.Close();
fs.Close();
}

I guess either route achieves the same...

Marc
 
M

Marc Gravell

Or (shorter)

XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.NewLineHandling = NewLineHandling.Entitize;
using (XmlWriter writer = XmlWriter.Create(@"c:\out.xml",
settings))
{
doc.Save(writer);
writer.Close();
}
 
M

Morten Wennevik

Tip:

Use \r\n for new lines, not just \n, as many controls (like TextBox) do
not recognize just \n as new line.
 
M

Marc Gravell

Environment.NewLine

In general a good approach; you sometimes need to be careful that this
doesn't make your code break the spec, though - as some formats explicitely
expect a given line ending; fortunately for xml IIRC the spec makes it fine
to use any of the common line-end combinations.

Note, however, that it can be easier to get the writer to do this via the
settings (as previous) than by having to manually insert *any* explicit form
of new-line. Let the xml-writer worry about it, without needing extra text
nodes in your document...

Marc
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Top