1

I've been having problems writing XML and reading it in. I have a handwritten XML that gets read in fine, but after I write the XML it acts funny.

The output of the WriteXML: http://www.craigmouser.com/random/test.xml

It works if you hit enter after the (specials) tag. I.E. make (specials)(special) look like

(specials)
(special)

If I step through it, when reading it, it goes to the start node of specials, then the next iteration reads it as an EndElement with name Shots. I have no idea where to go from here. Thanks in advance.

Code: Writing

        public void SaveXMLFile(string filename, Bar b, Boolean saveOldData)
    {
        XmlWriter xml;
        if(filename.Contains(".xml"))
        {
            xml = XmlWriter.Create(filename);
        }
        else 
        {
            xml = XmlWriter.Create(filename + ".xml");
        }
        xml.WriteStartElement("AggievilleBar");
        xml.WriteElementString("name", b.Name);
        xml.WriteStartElement("picture");
        xml.WriteAttributeString("version", b.PictureVersion.ToString());
        xml.WriteEndElement();
        xml.WriteElementString("location", b.Location.Replace(Environment.NewLine, "\n"));
        xml.WriteElementString("news", b.News.Replace(Environment.NewLine, "\n"));
        xml.WriteElementString("description", b.Description.Replace(Environment.NewLine, "\n"));
        xml.WriteStartElement("specials");
        xml.WriteString("\n"); //This line fixes the problem... ?!?!
        foreach (Special s in b.Specials)
        {
            if (s.DayOfWeek > 0 || (s.DayOfWeek == -1 
                && ((s.Date.CompareTo(DateTime.Today) < 0 && saveOldData )
                || s.Date.CompareTo(DateTime.Today) >= 0)))
            {
                    xml.WriteStartElement("special");
                    xml.WriteAttributeString("dayofweek", s.DayOfWeek.ToString());
                    if (s.DayOfWeek == -1)
                        xml.WriteAttributeString("date", s.Date.ToString("yyyy-MM-dd"));
                    xml.WriteAttributeString("price", s.Price.ToString());
                    xml.WriteString(s.Name);
                    xml.WriteEndElement();
            }
        }
        xml.WriteEndElement();
        xml.WriteEndElement();
        xml.Close();
    }

Code: Reading

        public Bar LoadXMLFile(string filename)
    {
        List<Special> specials = new List<Special>();
        XmlReader xml;
        try
        {
            xml = XmlReader.Create(filename);
        }
        catch (Exception)
        {

            MessageBox.Show("Unable to open file. If you get this error upon opening the program, we failed to pull down your current data. You will most likely be unable to save, but you are free to try. If this problem persists please contact us at [email protected]",
                "Error Opening File", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return null;
        }

        Bar current = new Bar();
        Special s = new Special();
        while (xml.Read())
        {
            if (xml.IsStartElement())
            {
                switch (xml.Name)
                {
                    case "AggievilleBar":
                        current = new Bar();
                        break;
                    case "name":
                        if (xml.Read())
                            current.Name = xml.Value.Trim();
                        break;
                    case "picture":
                        if (xml.HasAttributes)
                        {
                            try
                            {
                                current.PictureVersion = Int32.Parse(xml.GetAttribute("version"));
                            }
                            catch (Exception)
                            {

                                MessageBox.Show("Error reading in the Picture Version Number.","Error",MessageBoxButtons.OK,MessageBoxIcon.Error);
                            }
                        } 
                        break;
                    case "location":
                        if (xml.Read())
                            current.Location = xml.Value.Trim();
                        break;
                    case "news":
                        if (xml.Read())
                            current.News = xml.Value.Trim();
                        break;
                    case "description":
                        if (xml.Read())
                            current.Description = xml.Value.Trim();
                        break;
                    case "specials":
                        if (xml.Read())
                            specials = new List<Special>();
                        break;
                    case "special":
                        s = new Special();
                        if (xml.HasAttributes)
                        {
                            try
                            {
                                s.DayOfWeek = Int32.Parse(xml.GetAttribute(0));
                                if (s.DayOfWeek == -1)
                                {
                                    s.Date = DateTime.Parse(xml.GetAttribute(1));
                                    s.Price = Int32.Parse(xml.GetAttribute(2));
                                }
                                else
                                    s.Price = Int32.Parse(xml.GetAttribute(1));
                            }
                            catch (Exception)
                            {

                                MessageBox.Show("Error reading in a special.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            }
                        }
                        if (xml.Read())
                            s.Name = xml.Value.Trim();
                        break;
                }
            }
            else
            {
                switch (xml.Name)
                {
                    case "AggievilleBar":
                        xml.Close();
                        break;
                    case "special":
                        specials.Add(s);
                        break;
                    case "specials":
                        current.Specials = specials;
                        break;
                }
            }
        }

        return current;
    }
8
  • When do you get the children of the node? Commented Dec 12, 2010 at 4:39
  • I'm not real familiar with XML lingo, what exactly does that mean? Commented Dec 12, 2010 at 4:46
  • 1
    You should use XML Serialization, and let .NET do all the heavy lifting for you. Then you don't have to write custom code to write and read your XML for you. Commented Dec 12, 2010 at 4:54
  • We will be developing on other platforms as well and would like to use this XML format. Commented Dec 12, 2010 at 5:19
  • @mouser58907: XML Serialization does not affect the other platforms. The exact same XML format is used and honored. All that serialization does is move the data between the XML format you specify and some classes that you define (or have defined for you, if you decide to use a tool like xsd.exe). Commented Dec 12, 2010 at 5:29

2 Answers 2

1

Without seeing your code it's hard to really give a straight answer to that question. However, I can suggest using Linq-to-XML instead of XMLReader/XMLWriter -- it's so much easier to work with when you don't have to read each node one at a time and determine what node you're working with, which sounds like the problem you're having.

For example, code like:

using (var reader = new XmlReader(...))
{
    while reader.Read()
    {
        if (reader.Name = "book" && reader.IsStartElement)
        {
            // endless, confusing nesting!!!
        }
    }
}

Becomes:

var elem = doc.Descendants("book").Descendants("title")
           .Where(c => c.Attribute("name").Value == "C# Basics")
           .FirstOrDefault();

For an introduction to LINQ-to-XML, check out http://www.c-sharpcorner.com/UploadFile/shakthee/2868/, or just search for "Linq-to-XML". Plenty of examples out there.

Edit: I tried your code and I was able to reproduce your problem. It seems that without a newline before the special tag, the first special element is read in as IsStartElement() == false. I wasn't sure why this is; even skimmed through the XML Specifications and didn't see any requirements about newlines before elements.

I rewrote your code in Linq-to-XML and it worked fine without any newlines:

var xdoc = XDocument.Load(filename);
var barElement = xdoc.Element("AggievilleBar");
var specialElements = barElement.Descendants("special").ToList();
var specials = new List<Special>();
specialElements.ForEach(s =>
    {
        var dayOfWeek = Convert.ToInt32(s.Attribute("dayofweek").Value);
        var price = Convert.ToInt32(s.Attribute("price").Value);
        var date = s.Attribute("date");
        specials.Add(new Special
        {
            Name = s.Value,
            DayOfWeek = dayOfWeek,
            Price = price,
            Date = date != null ? DateTime.Parse(date.Value) : DateTime.MinValue
        });
    });
var bar = new Bar() {
    Name = barElement.Element("name").Value,
    PictureVersion = Convert.ToInt32(barElement.Elements("picture").Single()
        .Attribute("version").Value),
    Location = barElement.Element("location").Value,
    Description = barElement.Element("description").Value,
    News = barElement.Element("news").Value,
    Specials = specials
};
return bar;

Would you consider using Linq-to-XML instead of XMLReader? I've had my share of trouble with XMLReader in the past and once I switched to Linq-to-XML haven't looked back!

EDIT: I know this question is rather old now, but I just came across an article that reminded me of this question and might explain why this is happening: --> http://www.codeproject.com/KB/dotnet/pitfalls_xml_4_0.aspx

The author states:

In this light, a nasty difference between XmlReaders/Writers and XDocument is the way whitespace is treated. (See http://msdn.microsoft.com/en-us/library/bb387014.aspx.)

From msdn:

In most cases, if the method takes LoadOptions as an argument, you can optionally preserve insignificant white space as text nodes in the XML tree. However, if the method is loading the XML from an XmlReader, then the XmlReader determines whether white space will be preserved or not. Setting PreserveWhitespace will have no effect.

So perhaps, since you're loading using an XmlReader, the XmlReader is making the determination as to whether or not it should preserve white space. Most likely it IS preserving the white space which is why the newline (or lack thereof) makes a difference. And it doesn't seem like you can do anything to change it, so long as you're using an XmlReader! Very peculiar.

Sign up to request clarification or add additional context in comments.

1 Comment

Hmm, you're right, it does skip the first special element. Or at least, it registers it as an "EndElement" instead of a "StartElement." I'm not sure why, I skimmed through the XML specs (w3.org/TR/2008/REC-xml-20081126) and I didn't see anything about nested elements requiring newlines before/after them.
1

I'd recommend you use the XmlDocument class and its Load and Save methods, and then work with the XML tree instead of messing around with XmlReader and XmlWriter. In my experience using XmlDocument has fewer weird formatting problems.

1 Comment

Given what I just found out about XmlReader, I believe your answer is 100% correct. If the OP did not use XmlReader, it would probably not preserve the whitespace and would work fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.