XmlBookmarkReader bug using ReadSubtree

E

eric.olstad

I have discovered a bug in XmlBookmarkReader (code provided by MSDN,
but not necessarily supported by Microsoft). The article is found
here:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnxmlnet/html/XmlBkMkRead.asp

Consider the following code to print xml. This code assumes that the
xml text given in the reader will be of a certain form -- basically
having a root node, which is not a crazy assumption at all. It just
makes the code simpler to demonstrate the bug.

public static void PrintXml(XmlReader reader)
{
bool inElement = false;
string elementName="";

XmlBookmarkReader bReader = new XmlBookmarkReader(reader);

bReader.Read();
if (bReader.HasAttributes)
{
Console.Write("<"+bReader.Name);
for (int i=0; i < bReader.AttributeCount; i++)
{
Console.Write(" ");
bReader.MoveToAttribute(i);
Console.Write("{0}=\"{1}\"", bReader.Name, bReader.Value);
}
bReader.MoveToElement();
Console.WriteLine(">");
}
else
{
Console.WriteLine("<"+bReader.Name+">");
}

while (bReader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (inElement)
{
bReader.ReturnToBookmark(elementName);
PrintXml(bReader.ReadSubtree());
inElement = false;
}
else
{
inElement = true;
elementName = bReader.Name;
bReader.SetBookmark(elementName);
if (!bReader.HasAttributes)
Console.Write("<" + bReader.Name + ">");
else
{
Console.Write("<" + bReader.Name);
for (int i = 0; i < bReader.AttributeCount; i++)
{
Console.Write(" ");
bReader.MoveToAttribute(i);
Console.Write("{0}=\"{1}\"", bReader.Name,
bReader.Value);
}
bReader.MoveToElement();
Console.Write(">");
}
}
break;
case XmlNodeType.EndElement:
Console.WriteLine("</" + bReader.Name + ">");
inElement = false;
bReader.RemoveBookmark(elementName);
break;
case XmlNodeType.Text:
Console.Write(bReader.Value);
break;
}
}
reader.Close();
}

Now pass the code above this XML text:
<Config>
<Id attrib="false">12345-6789-12364</Id>
<DisplayName attrib="true">Computer</DisplayName>
<ServerList count="4">
<Server0 checkedin="true">https://web1.com</Server0>
<Server1 checkedin="false">https://web2.com</Server1>
<Server2 checkedin="false">https://web3.com</Server2>
<Server3 checkedin="false">https://web4.com</Server3>
</ServerList>
</Config>

Code to turn that text into an XmlReader:
string xml = [XML text above];
XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("", "");
XmlParserContext parseContext =
new XmlParserContext(null, nsmgr, null, XmlSpace.None);
XmlTextReader reader =
new XmlTextReader(xml, XmlNodeType.Document, parseContext);
reader.WhitespaceHandling = WhitespaceHandling.None;
PrintXml(reader);

You get this result:
<Config>
<Id attrib="false">12345-6789-12364</Id>
<DisplayName attrib="true">Computer</DisplayName>
<ServerList count="4"><ServerList count="4">
<Server0 checkedin="true">https://web1.com</Server0>
<Server0 checkedin="true">https://web2.com</Server1>
<Server0 checkedin="true">https://web3.com</Server2>
<Server0 checkedin="true">https://web4.com</Server3>
</ServerList>
</Config>

Notice the Server1, Server2 and Server3 start elements all are Server0,
but the end elements are correct. Notice they all share the same
'checkedin' attribute values as Server0 as well. ServerList appearing
twice is a side effect of having to go back using the bookmark. That
can be dealt with, but for simplicity, I didn't bother to correct it
here.

I've pinned this bug down to an issue with XmlBookmarkReader and
ReadSubtree. There are some strange effects going on that I don't
understand. For example, removing the "checkedin="true"" from the
Server0 element in the original XML doesn't result in this behavior.
You get the results you would expect. In fact, adding any
non-attributed element before Server0 gives you the results you would
expect even an empty element.

The only bit of insight I have with this problem is that the original
reader, before the recursive call to PrintXml, is positioned on Server0
while the bookmark reader is positioned on the cached ServerList node
when the call to ReadSubtree is executed.

Any help pinning down the problem would be greatly appreciated.

My conclusion is that a call to ReadSubtree on CachedXmlNode in the
XmlBookmarkReader may have unexpected results," but I would still like
to solve this problem.
 
E

eric.olstad

A solution, though not as elegant, is to forget about using
ReadSubtree. Instead you assume either the first node read or the
current node is the root node and you read until you encouter the
EndElement with the same name as the root node. You call PrintXml
recursively like above, but you must remove all bookmarks (because
you're working with the same XmlBookmarkReader).

It works, but has a lot more potential for error. If anyone is
interested, I can post the code.
 
Top