Monday, April 01, 2013

XmlReader, XmlWriter and IsEmptyElement

Today I`ll explain the problem I faced with simple xml read/write. We all know,

xmlreader is used to read xml, actually it “Represents a reader that provides fast, noncached, forward-only access to XML data.” And
xmlwriter is used to write xml, in other words it “Represents a writer that provides a fast, non-cached, forward-only means of generating streams or files containing XML data.”

I had to create a xml config file with same nodes but with localized string of multiple markets. To solve this problem here is what I decided
1.      Read Xml from beginning to end using xmlreader as I do not wanted to use Xpath to make is dependent on XML, a simple start to end scan of nodes, was my plan.
2. When reader read a attribute or node value , localize it and pass it to writer
3. Write everything else unchanged.

Sample Xml
 <events text="EVENTS">
      <event key="Dividends" value="dividend"/>
      <event key="Splits" value="splits"/>
      <event key="Earnings" value="earnings"/>
    </events>

Sample code
while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Attribute:
                            writer.WriteStartAttribute(reader.Name);
                            writer.WriteValue(LocalizeIt(reader, country));
                            break;
                        case XmlNodeType.Element:
                            bool isEmptyNode = reader.IsEmptyElement;
                            writer.WriteStartElement(reader.Name);

                            if (reader.HasAttributes)
                            {
                                WriteAttributes(writer, reader, country, culture);
                            }

                            if (isEmptyNode)
                            {
                                writer.WriteEndElement();
                            }
                            break;
                        case XmlNodeType.EndElement:
                            writer.WriteEndElement();
                            break;
                        case XmlNodeType.Text:
                             writer.WriteValue(LocalizeIt(reader, country));
                            break;
                        case XmlNodeType.Whitespace:
                            writer.WriteWhitespace(reader.Value);
                            break;
                    }


I thought this switch clause will read each and every node and write it using writer but it failed for EMPTY NODES (e.g. event in sample xml) and resulted into currupted xml, none of the event tags were closed. After some research I found reader has a property and that saved me. I had to update the code and included clause to check if reader has IsEmptyElement flag set ? and if its true I had to close the node explicitely and before closing write each attributes, actually with this change there is no need of 1st case (XmlNodeType.Attribute) but I kept it in demo code.

Make sure that you read this property on Xml Node not of node attributes, to ensure this
1. Either read it in the beginning as I did or
2. Revert to Node using MoveToElement() on reader.

I am writing this post so that you do not need to re-invent it and save your time.

Thanks
Pradeep

No comments: