Let’s say that we want to use X++ to create an XML file with the following DocType definition:
<!DOCTYPE MyType SYSTEM "http://www.validome.org/check/test.dtd">
AX has Xml* classes (e.g. XmlDocument) for such purpose, but let’s build it with .NET classes first (you’ll see why in a moment). This is X++ code calling .NET classes through .NET Interop:
System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument(); xmlDoc.AppendChild(xmlDoc.CreateDocumentType('MyType', null, 'http://www.validome.org/check/test.dtd', null)); info(xmlDoc.get_OuterXml());
The output is exactly what we want.
Now let’s try to rewrite it with native X++ classes:
XmlDocument xmlDoc = new XMLDocument(); xmlDoc.appendChild(xmlDoc.createDocumentType('MyType', '', 'http://www.validome.org/check/test.dtd', '')); info(xmlDoc.xml());
Notice that it’s very similar to the previous example, but we had to replace null values with empty strings (”), because X++ doesn’t support null-valued strings.
The output is not correct in this case:
<!DOCTYPE MyType PUBLIC "" "http://www.validome.org/check/test.dtd"[]>
We can easily prove that the empty strings are to blame by rewriting our .NET-based example to use empty strings instead of nulls:
System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument(); xmlDoc.AppendChild(xmlDoc.CreateDocumentType('MyType', '', 'http://www.validome.org/check/test.dtd', '')); info(xmlDoc.get_OuterXml());
The output is identical to what we get from X++.
Let me explain what’s going on.
If you provide a value for publicId (the second parameter), the PUBLIC keyword is used, followed by the URI provided in publicId. That applies to empty URI as well, so we get:
<!DOCTYPE MyType PUBLIC "" (…)>
If you want to get SYSTEM keyword, publicId must be null, not “”. The fourth parameter, internalSubset, is a similar case.
Even if you aren’t familiar with .NET at all, you likely understand the difference between a null reference (x = null) and an empty object (x = new Object()). Unlike in X++, strings in .NET are objects (instances of System.String class) and therefore they can either refer to a value or be without any value at all (= null).
Now what we can do to set null values to XmlDocument.createDocumentType()? Well… nothing. All parameters are X++ strings and null isn’t a valid value for them. It would have to be designed differently, e.g. using strings wrapped in objects or providing an additional flag to say that the value should be ignored.
The workaround is simple – you can use .NET classes (from System.Xml namespace) as in the first code snippet.
Tested in AX 2012 R3.