Geeks With Blogs
Freestyle Coding Programming in the Real World

Recently, I dove, headfirst, into a heaping batch of XML. I decided that I needed to create a custom schema for XML data. At minimum, I should require my XML database to enforce the same things as my SQL database, right? After much pain and anguish, the xsd files were created1.

Now that my XML files were validating, I created the necessary code to open, search, and modify the file. However, I kept receiving an exception during the save.

Time collapse about 2 days. The exception wasn't being thrown by the save. My save function would save the data. Then, it would attempt to refresh the screen. The screen refresh was throwing the exception. The function that refreshed the screen was the same one used during page initialization. Why would it work on initialization but not during usage?

When I originally created the schema, I followed a piece of the W3C recommendation. Namely:

<ns:schema targetNamespace="http://my.name.space/xsd" attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:mns="http://my.name.space/xsd" xmlns:ns="http://www.w3.org/2001/XMLSchema">

This tells the schema to require the mns namespace for references to tags within the schema. However, references to attributes do not require a namespace.

The schema is then referenced in my XML file as:

<Root xmlns="http://my.name.space/xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">

This tells the parser that my custom schema is the default for this document. However, you may also see referenced tags from the http://www.w3.org/2001/XMLSchema schema.

All of this is fine and dandy. The next step is to get .NET to use all of this information:

XmlDocument xmlDoc = new XmlDocument();
string DataSource = "http://localhost/data.xml";
string TargetNamespace = "http://my.name.space/xsd";
string NamespaceUri = TargetNamespace + "/schema.xsd";

xmlDoc.Schemas.ValidationEventHandler += new ValidationEventHandler( ValidationEventHandler );2
xmlDoc.Schemas.Add( TargetNamespace, NamespaceUri );
xmlDoc.Schemas.Compile();

ValidationEventHandler eventHandler = new ValidationEventHandler( ValidationEventHandler );
xmlDoc.Load( DataSource );
xmlDoc.Validate( eventHandler );

NamespaceMgr = new XmlNamespaceManager( xmlDoc.NameTable );
NamespaceMgr.AddNamespace( "mns", TargetNamespace );

The fun part comes into play when we call AddNamespace. This tells the XPath parser that all the default namespace references should be referenced with the mns namespace3. Let us assume our beginning XML document was:

<Root xmlns="http://my.name.space/xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<node Id="Id1">
Value1
</node>
<node Id="Id2">
Value2
</node>
<node Id="Id3">
Value3
</node>
</Root>

This allows us to do fun things like:

string xpath = "/mns:Root/mns:node";
XmlNodeList nodeList = xmlDoc.SelectNodes( xpath, NamespaceMgr );
XmlNode node = xmlDoc.SelectSingleNode( xpath + "[@Id='Id1']", NamespaceMgr );

Now, I can select the node with Id1 because my schema tells me that Attributes do not require namespaces. Furthermore, the document did not refer to a namespace for that attribute. However, things change when you create an attribute:

XmlAttribute att = xmlDoc.CreateAttribute( "Id", TargetNamespace );
att.Value = "Id4";

XmlElement ele = xmlDoc.CreateElement( "node", TargetNamespace );
ele.AppendChild( XmlData.CreateTextNode( "Value4" ) );
ele.Attributes.Append( att );

xmlDoc.AppendChild( ele );

This code will produce the following XML:

<Root xmlns="http://my.name.space/xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<node Id="Id1">
Value1
</node>
<node Id="Id2">
Value2
</node>
<node Id="Id3">
Value3
</node>
<node d3p1:Id="Id4" xmlns:d3p1="http://my.name.space/xsd">
Value4
</node>
</Root>

Thus, any future calls to SelectSingleNode it retrieve Id4 all failed. This is because the XmlDocument object created a local namespace for Id when it wasn't found in the default namespace!

Fortunately, this problem has a simple fix. Ignore the TargetNamespace when creating XmlAttribute:

XmlAttribute att = xmlDoc.CreateAttribute( "Id" );
att.Value = "Id4";

XmlElement ele = xmlDoc.CreateElement( "node", TargetNamespace );
ele.AppendChild( XmlData.CreateTextNode( "Value4" ) );
ele.Attributes.Append( att );

xmlDoc.AppendChild( ele );

The XmlDocument object will create the attribute in the default namespace. Then, when I attach my attribute to my "typed" element, the schema sees that the Id belongs on the node, and takes care of the details.

1As time goes by, we will have plenty of posts on this topic.
2This is a callback for validation errors. It’s documented in MSDN.
3Normally, I would complain about this. However, it’s buried deep in the XPath specification.

Posted on Monday, August 16, 2010 9:01 PM .NET , C# , Schema , Xml | Back to top


Comments on this post: XmlDocument.CreateAttribute has some strange quirks

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Chris Gardner | Powered by: GeeksWithBlogs.net