Link to home
Start Free TrialLog in
Avatar of DanBAtkinson
DanBAtkinson

asked on

Reading XML into C#

This question can be found in many places across the net and the answer usually involves the ReadElementString method.

But... Why would I ask a 500 point question for something that easy you ask??? I've spoken to a lot of people about the data I'm using and they basically don't know because they know that it's awful XML and it's not elemented really.

Well... It's like this.

I need to extract  values like the following and put them into an editor.

Data:

<properties version="1.0">
<string id="name" value="my file"/>
<string id="auth" value="Dan Atkinson"/>
<string id="desc" value="This is Dan's file"/>
<string id="vers" value="1.0"/>
<colour id="bgcl" red="255" green="0" blue=""/>
<real id="bgop" value="0.2"/>
<integer id="cmod" value="1"/>
<colour id="ppcl" red="255" green="255" blue="255"/>
</properties>

I am using Visual C# 2005 Express and it's helping me a lot but I've come unstuck. I've scoured for an answer for this.

Here is what I have...

xtr = new XmlTextReader(openFileDialog1.FileName);
xtr.ReadStartElement();

String strSkinName = xtr.ReadString();
String strAuthor = xtr.ReadElementString();
String strDescription = xtr.ReadElementString();
String strVersionNo = xtr.ReadElementString();
String strParent = xtr.ReadElementString();
String strBgColRed = xtr.ReadElementString();
String strBgColGreen = xtr.ReadElementString();
String strBgColBlue = xtr.ReadElementString();
String strBgOpacity = xtr.ReadElementString();
String strColourMode = xtr.ReadElementString();
String strMenuPopTxtColourRed = xtr.ReadElementString();
String strMenuPopTxtColourGreen = xtr.ReadElementString();
String strMenuPopTxtColourBlue = xtr.ReadElementString();

this.skinName.Text = strSkinName;
this.author.Text = strAuthor;
this.description.Text = strDescription;
this.versionNo.Text = strVersionNo;
this.parent.Text = strParent;
this.bgColRed.Text = strBgColRed;
this.bgColGreen.Text = strBgColGreen;
this.bgColBlue.Text = strBgColBlue;
this.bgOpacity.Text = strBgOpacity;
this.colourMode.Text = strColourMode;
this.menuPopTxtColourRed.Text = strMenuPopTxtColourRed;
this.menuPopTxtColourGreen.Text = strMenuPopTxtColourGreen;
this.menuPopTxtColourBlue.Text = strMenuPopTxtColourBlue;

I already have the corresponding boxes set up in a form and it works ONLY if I convert the data into elements. Otherwise, the xmlTextReader doesn't understand it.

I am aware that some of these are not strings and some are integers and some are real numbers. But I wanted to get at least  a few of the sections reading in the xml before I start tackling the data types.

The data cannot be converted into standard elements (I know it can but the program that utilises it cannot understand it).

Any help on this would be greatly appreciated.

Also, for an added bonus...

Is there any way I can prevent the xmlTextReader accessing the .dtd file online (which points to a 404 address) when it loads? By preventing I mean bypassing the file.

And, as you can see, the two colour id's in the data section are split into RGB values and yet I want them to be shown seperately (in 3 RGB combo boxes (Red, Green, Blue all 0-255)). Is this possible to break it down into this?

Once all that is done I'll then have to recompile it all back into its rubbish form in an xml file. That may or may not be easy but if I can't do it then it will be a seperate question.

Thanks in advance.
Avatar of tomasX2
tomasX2

use the reader.GetAttribute() function...

for example to get the version.... of the properties...

xtr = new XmlTextReader(openFileDialog1.FileName);
xtr.Read();
string s = reader.GetAttribute("version");
Or scan through the entire file one piece at a time.  Of course, your code would have to do something with the values.  You might have more elegant processing based in attribute id and element name as well.  This is very brute force and assumes elements are always in same order.  If you're converting RGB values to numbers, be careful as (at least in this XML) a blank string is legal color value text.  This will not convert to an int.  Other than the 404 error, does the lack of DTD cause problems since you are not using a validating reader?  It shouldn't care (or even try to get it as far as I know - which isn't far).

    XmlTextReader xtr = new XmlTextReader("..\\..\\XmlFile2.xml");
    xtr.WhitespaceHandling = WhitespaceHandling.Significant;
    xtr.MoveToContent(); // properties
    string xs = xtr.Name;
    xtr.MoveToFirstAttribute(); // version
    xs = xtr.Name;
    xs = xtr.Value;
    xtr.ReadStartElement(); // string
    xtr.MoveToFirstAttribute(); // id
    xs = xtr.Name;
    xs = xtr.Value;
    xtr.MoveToNextAttribute(); // value
    xs = xtr.Name;
    xs = xtr.Value;
    . . .
    xtr.ReadStartElement(); // colour
    xtr.MoveToFirstAttribute(); // id
    xs = xtr.Name;
    xs = xtr.Value;
    xtr.MoveToNextAttribute(); // red
    xs = xtr.Name;
    xs = xtr.Value;
    xtr.MoveToNextAttribute(); // green
    xs = xtr.Name;
    xs = xtr.Value;
    xtr.MoveToNextAttribute(); // blue
    xs = xtr.Name;
    xs = xtr.Value;
    ...
Avatar of DanBAtkinson

ASKER

Thanks to both of you for your responses.

TomasX2: Your method 'on paper' looks promising but it doesn't work either. It's very unusual as it looks like it should. When loaded, the program reads in the data and its corresponding textbox is blank where it should read the value for 'name' ("myfile").

DRichards: Unfortunately the data is not always in the same order (as some people put them in the wrong order. The end result is always the same though and that they are read but your method will not work.

What i wanted to do is have the RGB value read in and have a 'color picker' (the round palletes with colours on!!!) to choose the colours. This would enable me to reduce the number of comboboxes (by just having a 'color picker' for each value. I looked into this last night but didn't get very far as the reading is still the main sticking point!

As for the dtd... Technically it shouldn't read it but it does. It's annoying and it's getting to be a bit of a problem. Is there a short if statement out there that says something like:

psuedo:

If

First two lines = <xml definer thing> and <doctype:blah blah "http://www.myurl.com"">

Then

Skip to first attribute named <properties> (which is where the parent program reads the data from). I don't know if this is where the startelement comes in or not.

Either way, the program DOES connect to the Internet and DOES try to access the DTD file. I don't know if this is written into the XMLTextReader by default but it sure as hell is annoying!!!
ASKER CERTIFIED SOLUTION
Avatar of AvonWyss
AvonWyss
Flag of Switzerland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks for your post.

It says 'too many characters in character literal' citing every single one of the arguments in the brackets. ('string[@id="name"]/@value')
Ah, sorry, of course. Did too much Delphi programming lately... *g*

Please replace all the single quotes with double quotes, and all the double quotes with single quotes. E.g.:
"string[@id='name']/@value"
That should work!
Thanks. That solved that! How do I change the xmlDoc.Read("..\\..\\XmlFile2.xml"); to something so that it doesn't have to read a specific file and actually goes on the results of the openFileDialog1.FileName as I'm not trying to create a new one but edit an old one of the users choosing.

It also says:

'System.Xml.XmlDocument' does not contain a definition for 'Read'
Ahm, might be Load()... I should check my code before posting....
I believe they call them personal saviours...

You are one of them!!!

Thankyou!

Do you know of a way to bypass the first two lines of the xml at all?

These two:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE propertylist SYSTEM "http://www.myurl.com/dtds/propertylist.dtd">

I still get my firewall telling me it's trying to connect but the file results in a 404 error regardless of whether I let it connect! Somehow I just want to stop the program from trying to read the first two lines. The problem lies in the fact that not all of the xml documents have those first two lines so I can't ask it to skip them!

Thanks again. I think it's safe to say that the initial question has been answered, if you can help with the bypassing then I'll be eternally grateful!
Try this (not tested) instead of the xmlDoc.Load() line:

using (StreamReader sr=new StreamReader(yourfilename)) {
     XmlTextReader xmlr=new XmlTextReader(sr);
     xmlr.XmlResolver=null;
     xmlDoc.Load(xmlr);
}
It doesn't work but I don't think it's fair on you to answer this question and not get anymore points for it so I'll accept your first answer.

It now looks like this:

try {xtr = new XmlTextReader(openFileDialog1.FileName);

XmlDocument xmlDoc=new XmlDocument();

xmlDoc.Load(openFileDialog1.FileName);

this.skinName.Text = xmlDoc.DocumentElement.SelectSingleNode("string[@id='name']/@value").Value;
this.author.Text = xmlDoc.DocumentElement.SelectSingleNode("string[@id='auth']/@value").Value;
this.description.Text = xmlDoc.DocumentElement.SelectSingleNode("string[@id='desc']/@value").Value;
this.versionNo.Text = xmlDoc.DocumentElement.SelectSingleNode("string[@id='vers']/@value").Value;
this.parent.Text = xmlDoc.DocumentElement.SelectSingleNode("flags[@id='prnt']/@value").Value;
this.bgColRed.Text = xmlDoc.DocumentElement.SelectSingleNode("colour[@id='bgcl']/@red").Value;
this.bgColGreen.Text = xmlDoc.DocumentElement.SelectSingleNode("colour[@id='bgcl']/@green").Value;
this.bgColBlue.Text = xmlDoc.DocumentElement.SelectSingleNode("colour[@id='bgcl']/@blue").Value;
this.bgOpacity.Text = xmlDoc.DocumentElement.SelectSingleNode("real[@id='bgop']/@value").Value;
this.colourMode.Text = xmlDoc.DocumentElement.SelectSingleNode("integer[@id='cmod']/@value").Value;
this.menuPopTxtColourRed.Text = xmlDoc.DocumentElement.SelectSingleNode("colour[@id='ppcl']/@red").Value;
this.menuPopTxtColourGreen.Text = xmlDoc.DocumentElement.SelectSingleNode("colour[@id='ppcl']/@green").Value;
this.menuPopTxtColourBlue.Text = xmlDoc.DocumentElement.SelectSingleNode("colour[@id='ppcl']/@blue").Value;

Although the formatting is a lot better of course!!!

Anyway, it works so I thank you a lot.

Thanks also to drichards and tomasX2 who also provided comments and possible solutions!

Definately an 'A' grade for your help! Cheers once again!
Thank you. But try this:

XmlDocument xmlDoc=new XmlDocument();
using (StreamReader sr=new StreamReader(openFileDialog1.FileName)) {
     XmlTextReader xmlr=new XmlTextReader(sr);
     xmlr.XmlResolver=null;
     xmlDoc.Load(xmlr);
}
this.skinName.Text = xmlDoc.DocumentElement.SelectSingleNode('string[@id="name"]/@value').Value;

This should really work - your code did not work since you didn't replace the filename by the xtr in the Load() call.
I've fixed you doing the Delphi on the string (:p) but it's telling me that type StreamReader could not be found, even though I added it to the region using directives.
using System.IO;
using System.Text;

You have both of these?
Yes. They're both at the top of form1.cs. Nothing happens.

The error is:

The type or namespace name 'StreamReader' could not be found (are you missing a using directive or an assembly reference?)
Does it work if you specify the fully qualified name like this:

using (System.IO.StreamReader sr=new System.IO.StreamReader(openFileDialog1.FileName))
Thanks! That works loads!

I tried it with a file that doesn't have one of the strings. This one to be exact:

this.parent.Text = xmlDoc.DocumentElement.SelectSingleNode("flags[@id='prnt']/@value").Value;

Suffice to say it doesn't like it!!! It will read in the data up until it hits that point (as one would normally expect before debugging kicks in). Can I be bloody cheeky and ask if there a short way of stopping a crash occuring if the xml file does not have certain attribute?
Of course. SelectSingleNode() will return null if the string does not exist. therefore, try this:

public static string GetValueOrDefault(XmlNode node, string default) {
     return node!=null ? node.Value : default;
}

and then:

this.parent.Text = GetValueOrDefault(xmlDoc.DocumentElement.SelectSingleNode("flags[@id='prnt']/@value"), "");
Where do I place

public static string GetValueOrDefault(XmlNode node, string default) {
     return node!=null ? node.Value : default;
}

?
Well, it's a method, just put it before the method you're using it...
No! It goes crazy with no less than 19 errors about invalid tokens (!=), expectig ';' or '=', but not (XmlNode node, string default).

It tells me that public is an invalid method and that static is not valid for it! It goes right down to the fact that the comma after node shouldn't be there!