Wednesday, June 17, 2009

xml:lang

This post attempts to explain and document the issue that the poor fellow here has. I have been bitten by the same behavior, and I have played with the map compiler enough now that I think I know what the root cause of the issue is. It appears to be a bug in the map compiler. I haven't yet found out if it's been fixed in BTS09, but this is definitely present in BTS06R2.

Long story short: If you have schemas that use the attributes defined in the http://www.w3.org/XML/1998/namespace namespace, you're going to have problems if you use those schemas in maps. The reason is the way that the VS BizTalk schema editor and the map compiler handle this special-case namespace.

The easiest way to include the elements in the http://www.w3.org/XML/1998/namespace namespace, like lang, is to add BTS.xml as an import. You can find it if your BizTalk project has an assembly reference to Microsoft.BizTalk.GlobalPropertySchemas, which it should by default. BTS.xml is a copy of what you'll find if you browse to http://www.w3.org/XML/1998/namespace, and it has been placed there during the initial install of BizTalk Server.

This namespace is a special case - it is uniquely and permanently mapped to the prefix "xml". Attempting to assign it to another prefix, or attempting to assign another namespace to the "xml" prefix, can result in errors.

So what exactly is the issue? If you import BTS.xml into your schema (or even if you create your own copy of the http://www.w3.org/XML/1998/namespace schema and import that) and use that schema in a map, the mapper creates a new prefix, like "ns0", and assigns http://www.w3.org/XML/1998/namespace to it. This causes two issues:

1) If you do a Validate Map to generate the XSL transform from the map file, the XSLT that's generated won't run on it's own in the Visual Studio XSLT debugger. You will get a complaint that http://www.w3.org/XML/1998/namespace can't be mapped to a new prefix. This doesn't occur if you use Test Map from the context menu, run the map in BizTalk deployment, or call the Transform method on the Transform property of the compiled map type (for unit testing, as explained here. Only applies to BTS 2006, since BTS2009 has the new test wrappers for schema and map types. If this bug is still present in BTS09, I don't know if it affects the test wrappers or not.).

2). If you are using the map to create one of the http://www.w3.org/XML/1998/namespace attributes in the output document, the resulting xml will be invalid, because BizTalk will drop the http://www.w3.org/XML/1998/namespace declaration into the output file along with its erroneous prefix assignment.

If you scroll to the end of the post I linked to at the beginning of this entry and look at the writer's response to himself, he mentions that he has found 3 workarounds, all of which he notes don't really work that well, and one of which I have spent a little more time analyzing.

His first workaround is to explicitly add the declaration "xmlns:xml="http://www.w3.org/XML/1998/namespace"" to the rootnode of the schema using a text editor. This will cause the namespace to appear with the correct prefix in the map XSLT. Unfortunately, as he notes, this declaration vanishes the next time you save the schema using the VS BizTalk schema editor - it apparently recognizes the redundant declaration and removes it. However, what he didn't examine further is that this only works if the BTS.xml schema import and manual namespace declaration are both done on the target schema - if the import/declaration are only done on the source schema, the map compiler still generates a bad prefix. You can't just add the declaration on the target schema either - you have to add the declaration and import BTS.xml. This has to be a bug in the map compiler.

His second workaround is to extract the map XSLT, fix the declaration there, and use that XSLT as the input to Custom XSL Path. This works, but it's extremely unintuitive if your code has to be maintained, especially if you leave all the functoids and links in the original map so the XSLT can easily be extracted again if changes are made. If you do that, no one will ever notice the Custom XSL Path property and will wonder why map changes don't have any effect!

His third workaround is to use custom XSLT inline scripting to create the xml:lang attribute. He mentions that this still results in a bad prefix, so it doesn't really fix the problem.

Hopefully this bug has been fixed in the BTS09 map compiler. If I find out, I'll update this post.