The Mystery of XmlNode.SelectNodes()

With the xml document found here and the following code, what will you get as the result?
Shortened version of the XML document:
    <Items>
        <ItemAttributes>
            <ListPrice>
                <FormattedPrice>$49.00</FormattedPrice>
            </ListPrice>
        </ItemAttributes>
        <OfferSummary>
            <LowestNewPrice>
                <FormattedPrice>$29.99</FormattedPrice>
            </LowestNewPrice>
            <LowestUsedPrice>
                <FormattedPrice>$24.99</FormattedPrice>
            </LowestUsedPrice>
        </OfferSummary>
        <Offers>
            <Offer>
                <FormattedPrice>$..(1)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(2)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(3)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(4)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(5)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(6)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(7)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(8)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(9)..</FormattedPrice>
            </Offer>
            <Offer>
                <FormattedPrice>$..(10)..</FormattedPrice>
            </Offer>
        </Offers>
    </Items>
</ItemLookupResponse>
XmlDocument xDoc = new XmlDocument();
xDoc.Load("resources/ItemLookupResponse.xml");
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xDoc.NameTable);
nsMgr.AddNamespace("aws", xDoc.DocumentElement.NamespaceURI);
XmlNode offersNode = xDoc.SelectSingleNode("//aws:Offers[1]", nsMgr);
XmlNodeList formattedPriceNodes = offersNode.SelectNodes("//aws:FormattedPrice", nsMgr);
return formattedPriceNodes.Count;
Since formattedPriceNodes selects nodes under offersNode, I expected it to return 10.
But it returns 13!!!
That means it parses the whole document (xDoc), not just the node (offersNode).
The problem seems to be on the XPath expression.
Using // will parse the whole document.
To get all the matched descendant nodes, use ".//" or "descendant::".
Change the line with //aws:FormattedPrice to be as followed:
XmlNodeList formattedPriceNodes = offersNode.SelectNodes("descendant::aws:FormattedPrice", nsMgr);
The result is now 10 as expected. :D

Enable an iterator for a class

To enable an iterator for a class, simply add GetEnumerator() method which return IEnumerator interface to that class.
Note that you'll need a list-type member (Array, ArrayList, LinkedList,...) that you want to loop through in your list.
Within that method, loop through the list and return each item on each loop.
Example:
#region VideoGame item public struct VideoGame{ private string asin; private string title; public string ASIN{ set{ asin = value; } get{ return asin; } } public string Title{ set{ title = value; } get{ return title; } } } #endregion #region VideoGame collection class public class VideoGameCollection{ private LinkedList m_VideoGameLinkedList = new LinkedList(); public VideoGameCollection{ // Default constructor. } public int Count{ get{ return m_VideoGameLinkedList.Count; } } public void Add(VideoGame vdoGame){ m_VideoGameLinkedList.AddLast(vdoGame); }
public IEnumerator GetEnumerator(){ foreach(VideoGame game in m_VideoGameLinkedList){ yield return game; }     }
 }  #endregion  #region Loop through VideoGameCollection in your main class ... VideoGameCollection games = new VideoGameCollection(); foreach(VideoGame game in games){ Console.WriteLine( game.Title ); } ...  #endregion
The colored lines are the key to enable "foreach" function to your class.
Please note that this requires System.Collections namespace.
Note that for the GetEnumerator() method, just return m_VideoGameLinedList.GetEnumerator(); should do the job.
I will get back again for an update.

Customize DataRow to keep an object

To enable a DataRow to keep a custom object, create a new class inheriting DataRow class.
The code below shows how to keep an object of a custom class named "VideoGame".
 
#region Custom DataRow
pubilc class VideoGameDataRow : DataRow{
        private VideoGame vdoGame;
        public VideoGameDataRow(DataRowBuilder rowBuilder) : base(rowBuilder)
                // Default constructor.
        }
        public VideoGame GameObject{
                set { vdoGame = value; }
                get { return vdoGame; }
        }
}
#endregion
 
You will also need a custom DataTable to handle this custom DataRow since the default DataTable can take only the default DataRow.
Override the NewRow method (NewRowBuilder()) to handle the custom DataRow.
 
#region Custom DataTable
public class VideoGameTable : DataTable{
        protected override DataRow NewRowBuilder(DataRowBuilder builder){
                return new VideoGameDataRow(builder);
        }
}
#endregion
 
When used, cast a created row as the custom DataRow.
 
#region Usage
...
        VideoGame game = new VideoGame();
        VideoGameDataTable vdoGameTable = new VideoGameDataTable();
        VideoGameDataRow newGameRow = (VideoGameDataRow)(vdoGameTable.NewRow());
        newGameRow.GameObject = game;
...
#endregion
 
Note that you cannot create a new DataRow using the new keyword (DataRow row = new DataRow();).
This will generate an error saying that DataRow(DataRowBuilder) is inaccessible due to its protection level.
This applies to a custom DataRow which inherits from DataRow as well.