Daily Archives: January 20, 2014

XML (part 2, basics), embedded, XDocument, XmlDocument, XmlTextReader, XPathNavigator

Embedded

We covered basic file IO for XML files yesterday. This is very useful for settings files. There are some XML files though that you want to hide from the user. I am not talking about encryption. Today we are going to embed the XML file the project itself.
We are still using yesterday’s “Walmart.xml” example. Create a folder “XmlFiles” in your Solution Explorer and drag the file into it.

XmlFileLocation

Now right-click the file “Walmart.xml” and select “Properties”. Change the “Build Action” to “Embedded Resource”.

EmbeddedResourceProperty

Use the following code to load the resource as an XDocument:

// since .Net 3.5
// namespace System.Xml.Linq
// xPath: namespace.folder.file.ext => here: "DemoApp.XmlFiles.Walmart.xml"
public static XDocument getAssemblyXDocument(string xPath) {
    Assembly lAssembly = Assembly.GetExecutingAssembly();
    using (Stream lStream = lAssembly.GetManifestResourceStream(xPath)) {
        XDocument lXDocument = XDocument.Load(lStream);
        return lXDocument;
    }
} //

You can also load the document as an XmlDocument.

// since .Net 1.1
// namespace System.Xml
// xPath: namespace.folder.file.ext => here: "DemoApp.XmlFiles.Walmart.xml"
public static XmlDocument getAssemblyXml(string xPath) {
    Assembly lAssembly = Assembly.GetExecutingAssembly();
    using (Stream lStream = lAssembly.GetManifestResourceStream(xPath)) {
        XmlDocument lXmlDocument = new XmlDocument();
        lXmlDocument.Load(lStream);
        return lXmlDocument;
    }
} //
public static void LoadXmlExample() {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    XmlDocument lXml = getAssemblyXml(lPath);
    XDocument lXDoc = getAssemblyXDocument(lPath);
} //

Using an XmlDocument is similar to XDocument. XmlDocument does not support LINQ, which is a big disadvantage.

XmlDocument reads the entire document and initializes a tree structure, which needs a lot of memory. Processing bigger Xml files can cause issues. The tree can be accessed either by browsing through the nodes or by using XPath queries.
You can browse through documents forward/backward and carry out modifications.

XDocument (LINQ to XML) is easy to code and understand. Again the entire document is loaded into memory. To read Xml files we can use the XDocument or XElement class. Both classes support Load() methods. XElement represents an XML fragment while XDocument represents an entire XML document.

XmlTextReader is very fast and memory efficient. Unlike XmlDocument it can only read in forward direction. Searching for something specific is awkward. As the name says, there is no method to write or to modify data. And data validation is also left behind.

XPathNavigator is more complex and slower than XmlReader. XPathNavigator has to read enough data to be able to execute XPath queries. XmlDocument internally uses XPathNavigator. Using XPathNavigator directly is faster than using it via XmlDocument.

Using XmlTextReader:

public static void UseTextReader() {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    Assembly lAssembly = Assembly.GetExecutingAssembly();

    using (Stream lStream = lAssembly.GetManifestResourceStream(lPath)) {
        using (XmlTextReader lXmlReader = new XmlTextReader(lStream)) {
            while (lXmlReader.Read()) {
                if (!lXmlReader.IsStartElement()) continue;
                switch (lXmlReader.Name) {
                    case "name":
                        Console.WriteLine("name: " +  lXmlReader.ReadString());
                        break;
                    case "price":
                        Console.WriteLine("price: " + lXmlReader.ReadString());
                        break;
                    case "description":
                        Console.WriteLine("description: " + lXmlReader.ReadString());
                        break;
                    case "somethingElse":
                        Console.WriteLine("somethingElse: " + lXmlReader.ReadString());
                        break;
                    default:
                        Console.WriteLine(Environment.NewLine + lXmlReader.Name);
                        break;
                }
            }
        }
    }
    Console.ReadLine();
} //

example output:

Walmart

food
name: Banana
price: 1.99
description: Mexican delicious

food
name: Rice
price: 0.79
description: the best you can get

food
name: Cornflakes
price: 3.85
description: buy some milk

food
name: Milk
price: 1.43
description: from happy cows

electronic
name: Kindle fire
price: 100
description: Amazon loves you
somethingElse: the perfect Xmas gift for your kids

food
name: baked beans
price: 1.35
description: very British

Using XPathNavigator:

public static void UseXPathNavigator() {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    Assembly lAssembly = Assembly.GetExecutingAssembly();

    using (Stream lStream = lAssembly.GetManifestResourceStream(lPath)) {
        XPathDocument lXPathDoc = new XPathDocument(lStream);
        XPathNavigator lXNavi = lXPathDoc.CreateNavigator();
        lXNavi.MoveToRoot();
        lXNavi.MoveToFirstChild();

        do {
            if (lXNavi.MoveToFirstAttribute()) {
                Console.WriteLine(lXNavi.Name + "=" + lXNavi.Value);
                lXNavi.MoveToParent();
            }

            if (!lXNavi.MoveToFirstChild()) continue;
            do {
                if (!lXNavi.MoveToFirstChild()) break;
                do {
                    Console.WriteLine(lXNavi.Name + "=" + lXNavi.Value);
                } while (lXNavi.MoveToNext());
                Console.WriteLine();
                lXNavi.MoveToParent();
            } while (lXNavi.MoveToNext());
            lXNavi.MoveToParent();
        } while (lXNavi.MoveToNext());

        Console.ReadLine();
    }
} //

example output:
name=Banana
price=1.99
description=Mexican delicious

name=Rice
price=0.79
description=the best you can get

name=Cornflakes
price=3.85
description=buy some milk

name=Milk
price=1.43
description=from happy cows

name=Kindle fire
price=100
description=Amazon loves you
somethingElse=the perfect Xmas gift for your kids

name=baked beans
price=1.35
description=very British

Using XPathNavigator with query:

public static void UseXPathNavigator(string xQuery) {
    string lPath = "DemoApp.XmlFiles.Walmart.xml";
    Assembly lAssembly = Assembly.GetExecutingAssembly();

    using (Stream lStream = lAssembly.GetManifestResourceStream(lPath)) {
        XPathDocument lXPathDoc = new XPathDocument(lStream);
        XPathNavigator lXNavi = lXPathDoc.CreateNavigator();

        XPathNodeIterator lIterator = lXNavi.Select(xQuery);

        if (lIterator.Count <= 0) {
            Console.WriteLine("Nothing found!");
            return;
        }

        while (lIterator.MoveNext()) {
            Console.WriteLine(lIterator.Current.Value);
        }
    }            
} //

public static void TestQueries() {
    UseXPathNavigator(@"/Walmart/electronic");
    Console.WriteLine();
    UseXPathNavigator(@"/Walmart/food[name='Banana']");
            
    Console.ReadLine();
} //

example output:
Kindle fire100Amazon loves youthe perfect Xmas gift for your kids

Banana1.99Mexican delicious

My opinion is: Make use of LINQ to XML. It is very comfortable and can save you a lot of time. If you have a lot of data, then you should consider a database rather than XML files.

We will discuss XML object serialization tomorrow. Let C# save and load objects the nice way.