2

There is some german xml format for invoices, defined by the "Fraunhofer Institute" called OpenTrans, an this is about version 2.1 of that. By definition the header for such an invoice document has to look like this, including multiple namespaces:

<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<INVOICE version="2.1" xmlns="http://www.opentrans.org/XMLSchema/2.1"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd"
    xmlns:bmecat="http://www.bmecat.org/bmecat/2005"
    xmlns:xmime="http://www.w3.org/2005/05/xmlmime"/>

My first version of that - still in use - has been implemented within Dynamics Nav classic, which doesn't support .Net directly. Due to that I had to use the COM object MSXML2 back these days.

Now I'm trying to rewrite this in C# / .Net (4.5.1), and I'm getting some weired problem with one of the namespaces. While the above root node created by MSXML2 is correct (specificially the xsi:schemaLocation namespace) the output of my .Net code is not what I intended:

<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<INVOICE version="2.1" xmlns="http://www.opentrans.org/XMLSchema/2.1"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   d1p1:schemaLocation="http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd"
   xmlns:bmecat="http://www.bmecat.org/bmecat/2005"
   xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
   xmlns:d1p1="http://www.opentrans.org/XMLSchema/2.1">
</INVOICE>

xsi:schemaLocation has been converted to a default namespace d1p1:schemaLocation and d1p1 (of course) has been added to the list of namespaces.

To be able to compare both attempts, I converted my old Navision code based on MSXML2 to C#, using the same old MSXML2 (Microsoft XML, v6.0) and I'm getting the same CORRECT output, while the output of the .Net code doesn't create the namespaces I need to get.

Here's are both versions of my C# code:

if (mode == "com")
{
    MSXML2.DOMDocument60 comDoc = new MSXML2.DOMDocument60();
    MSXML2.IXMLDOMProcessingInstruction xmlProcessingInst = comDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"yes\"");
    comDoc.appendChild(xmlProcessingInst);
    MSXML2.IXMLDOMNode RootNode;
    MSXML2.IXMLDOMElement NewChildNode = comDoc.createElement("INVOICE");
    RootNode = comDoc.appendChild(NewChildNode);
    MSXML2.IXMLDOMAttribute XMLNewAttributeNode = RootNode.ownerDocument.createAttribute("version");
    XMLNewAttributeNode.nodeValue = "2.1";
    RootNode.attributes.setNamedItem(XMLNewAttributeNode);
    XMLNewAttributeNode = RootNode.ownerDocument.createAttribute("xmlns");
    XMLNewAttributeNode.nodeValue = "http://www.opentrans.org/XMLSchema/2.1";
    RootNode.attributes.setNamedItem(XMLNewAttributeNode);
    XMLNewAttributeNode = RootNode.ownerDocument.createAttribute("xmlns:xsi");
    XMLNewAttributeNode.nodeValue = "http://www.w3.org/2001/XMLSchema-instance";
    RootNode.attributes.setNamedItem(XMLNewAttributeNode);
    XMLNewAttributeNode = RootNode.ownerDocument.createAttribute("xsi:schemaLocation");
    XMLNewAttributeNode.nodeValue = "http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd";
    RootNode.attributes.setNamedItem(XMLNewAttributeNode);
    // the same code for "xmlns:bmecat" attribute;
    // the same code for "xmlns:xmime" attribute;
    comDoc.save(@"D:\testInvoice.xml");
}
else
{
    XmlDocument dotNetDoc = new XmlDocument();
    dotNetDoc.LoadXml("<INVOICE></INVOICE>");
    XmlElement root = dotNetDoc.DocumentElement;
    XmlDeclaration xmlDeclaration = dotNetDoc.CreateXmlDeclaration("1.0", "ISO-8859-1", "yes");
    dotNetDoc.InsertBefore(xmlDeclaration, root);
    root.SetAttribute("version", "2.1");
    root.SetAttribute("xmlns", "http://www.opentrans.org/XMLSchema/2.1");
    root.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
    XmlAttribute att;
    att = dotNetDoc.CreateAttribute("xsi", "schemaLocation", "http://www.opentrans.org/XMLSchema/2.1");
    att.Value = "http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd";
    root.SetAttributeNode(att);
    root.SetAttribute("xmlns:bmecat", "http://www.bmecat.org/bmecat/2005");
    root.SetAttribute("xmlns:xmime", "http://www.w3.org/2005/05/xmlmime");
    dotNetDoc.AppendChild(root);
    File.WriteAllText(@"\\mbps02\Verwaltung\EDI\openTrans\2_1\testInvoice.xml", dotNetDoc.OuterXml);
}

The namespace prefixes will be correct, if I use the same URL for xmlns:xsi and xsi:schemaLocation, but of course the created document can not be validated with that.

Tested with .Net from 3.x to 4.5.1.

Who's wrong - COM, .Net or me? Is it a bug or is it a feature?

5
  • Just google "d1p1 namespace". Covered well in the comments in this Q+A for example. Commented Dec 26, 2015 at 15:45
  • It wouldn't help, because he uses a wrong namespace when calling XmlDocument.CreateAttribute. Commented Dec 26, 2015 at 17:17
  • Hans: Thanks for your answer. I knew this any many other explanations, which all have in common, that I didn´t find the reason for my problem - at least not for XmlDocument. Adding a namespace by using the namespace manager is something I had tried already. but didin´t help. Than I found several google threads that say, namespace manager is only for xPath and has no effect on writing xml. Wrong? Commented Dec 26, 2015 at 18:26
  • @Yoh Deadfall: I tried all variation I could imagine - in your opinion, what would be the correct parameter for CreateAttribute. I also tried the usage of: root.SetAttribute("xsi:schemaLocation", "opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd") which results in an attribute schemaLocation="..." instead of xsi:schemaLocation ... Commented Dec 26, 2015 at 18:38
  • 1
    @Pidi I described in my answer what was wrong. See the arrows in the code. Commented Dec 26, 2015 at 21:21

2 Answers 2

2

No, it isn't a .Net bug or a feature. It's just a problem in your code. Let's look at the specified namespaces.

root.SetAttribute(
    "xmlns:xsi",
    "http://www.w3.org/2001/XMLSchema-instance" // <----
     );
XmlAttribute att;
att = dotNetDoc.CreateAttribute(
    "xsi",
    "schemaLocation",
    "http://www.opentrans.org/XMLSchema/2.1" // <----
    );

Do you see now the difference? So you should rewrite the attribute creation and provide the http://www.w3.org/2001/XMLSchema-instance as the namespace, because the xsi is mapped to it:

att = dotNetDoc.CreateAttribute(
    "xsi",
    "schemaLocation",
    "http://www.w3.org/2001/XMLSchema-instance" // <----
    );

NOTE. If you use the code above (with the CreateAttribute method), then you can omit the manual creation of the xsi declaration (i.e. root.SetAttribute("xmlns:xsi", "...")). This declaration will be generated implicitly.

Or you should use the next lines of code:

root.SetAttribute(
    "xmlns:xsi",
    "http://www.w3.org/2001/XMLSchema-instance" // <----
     );
root.SetAttribute(
    "schemaLocation",
    "http://www.w3.org/2001/XMLSchema-instance", // <----
    "http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd"
    );

But this is not the only one problem with your code, because you're mixing parsing XML to create the root element and DOM creation for other elements. So choose one of them.

Here's the final code and it's XML representation:

XmlDocument document = new XmlDocument();

XmlDeclaration declaration = document.CreateXmlDeclaration("1.0", "ISO-8859-1", "yes");
document.AppendChild(declaration);

XmlElement invoice = document.CreateElement("INVOICE", "http://www.opentrans.org/XMLSchema/2.1");
document.AppendChild(invoice);

invoice.SetAttribute("version", "2.1");
invoice.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
invoice.SetAttribute("schemaLocation", "http://www.w3.org/2001/XMLSchema-instance", "http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd");
invoice.SetAttribute("xmlns:bmecat", "http://www.bmecat.org/bmecat/2005");
invoice.SetAttribute("xmlns:xmime", "http://www.w3.org/2005/05/xmlmime");
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<INVOICE version="2.1"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd"
         xmlns:bmecat="http://www.bmecat.org/bmecat/2005"
         xmlns:xmime="http://www.w3.org/2005/05/xmlmime"
         xmlns="http://www.opentrans.org/XMLSchema/2.1" />

But if you are using .Net 3.5 and higher, use LINQ to XML because it's much more readable:

XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";

XDocument document = new XDocument(
    new XDeclaration("1.0", "ISO-8859-1", "yes"),
    new XElement(
        XName.Get("INVOICE", "http://www.opentrans.org/XMLSchema/2.1"),
        new XAttribute("version", "2.1"),
        new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"),
        new XAttribute(xsi + "schemaLocation", "http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd"),
        new XAttribute(XNamespace.Xmlns + "bmecat", "http://www.bmecat.org/bmecat/2005"),
        new XAttribute(XNamespace.Xmlns + "xmime", "http://www.w3.org/2005/05/xmlmime")
        )
    );
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks you sooooo much. Of course it works with root.SetAttribute("schemaLocation", "w3.org/2001/XMLSchema-instance", "opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd"); It´s so logical and suddenly everthing is totally clear. However this is probably the only variation, I didn´t give a try. The name of the attribute is "schemaLocation", its bound to the namespace xsi, which has been defined before, and its value is the 3rd parameter. Changed and works. You made my day. Now I´ll have a look at your other comments. Pidi
If it helped you, then mark the post as an answer. Take a tour to know how to do that.
I had copied that code from my class. What I´m trying to accompish is a class for Dynamics Nav so one can just write down the XML within a Nav Codeunit as easy as posible. Within my code however I´m loading the root node from a string, because the root node name had a prefix (soapenv:Envelope) and so far I couldn´t find another way to handle that. Thanks to you I do now know, how to handle this the correct way as well :-)
What do you mean under the easiest way to write an XML? Maybe the better way is to use the XmlWriter if nobody is interested in manipulations with DOM? The code will be not so readable as LINQ to XML code, but it will require less memory and will work much faster.
What I meant ist "the easiest way" to do it within Dynamics Nav! Nav has no XML capabilities and a fairly simple language. I tried to build kind of a framework for Nav Codeunits where you "just write down the xml" with some simple commands: SetVersion, AddNamespace(1..n), AddRoot(), AddNode ... Besides that I´ve tried to avoid all of that "IF (FieldHasData) THEN CreateNode". This is all handled by that C# class. The User in Nav will never see if it´s Linq or XmlWriter or DOM. And - thanks to you - it´s working perfectly well so far. Pidi
|
-2

Do it the easy way. Just parse a string xml

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication62
{
    class Program
    {
        static void Main(string[] args)
        {
            string xml =
               "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"yes\"?>" +
               "<INVOICE version=\"2.1\"" +
                   " xmlns=\"http://www.opentrans.org/XMLSchema/2.1\"" +
                   " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
                   " d1p1:schemaLocation=\"http://www.opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd\"" +
                   " xmlns:bmecat=\"http://www.bmecat.org/bmecat/2005\"" +
                   " xmlns:xmime=\"http://www.w3.org/2005/05/xmlmime\"" +
                   " xmlns:d1p1=\"http://www.opentrans.org/XMLSchema/2.1\">" +
               "</INVOICE>";

            XDocument doc = XDocument.Parse(xml);

        }
    }
}

2 Comments

Thanks for your tipp. Even with XlmDocument I can add the complete root node as a string - I know that. My question is mainly reffering to the fact, that I thought I would understand xml namespaces, and obviously I don´t.
I've had issue similar to yours in the past creating an xml root node with namespaces. The code is a lot simpler parsing a string than adding namespaces like you are doing. When developing code I often have to make the decision doing things the "Proper Way" and doing getting code to "Work". I've been trained to write code using "Software Standard", but occasionally have to bypass standards to write code that is understandable. Complicated structure like your code is not very understandable compared to my method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.