【问题标题】:XML complex namespaces in .NET vs. COM.NET 与 COM 中的 XML 复杂命名空间
【发布时间】:2015-12-26 12:46:57
【问题描述】:

发票有一些德国 xml 格式,由名为 OpenTrans 的“弗劳恩霍夫研究所”定义,这是大约 2.1 版。根据定义,此类发票文档的标题必须如下所示,包括多个命名空间:

<?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"/>

我的第一个版本 - 仍在使用 - 已在 Dynamics Nav 经典版中实现,它不直接支持 .Net。因此,这些天我不得不使用 COM 对象 MSXML2。

现在我正在尝试用 C#/.Net (4.5.1) 重写它,但其中一个命名空间出现了一些奇怪的问题。虽然 MSXML2 创建的上述根节点是正确的(特别是 xsi:schemaLocation 命名空间),但我的 .Net 代码的输出不是我想要的:

<?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 已转换为默认命名空间 d1p1:schemaLocation 并且 d1p1(当然)已添加到命名空间列表中。

为了能够比较这两种尝试,我将基于 MSXML2 的旧 Navision 代码转换为 C#,使用相同的旧 MSXML2(Microsoft XML,v6.0),我得到了相同的正确输出,而输出.Net 代码不会创建我需要获取的命名空间。

这是我的 C# 代码的两个版本:

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);
}

如果我为 xmlns:xsixsi:schemaLocation 使用相同的 URL,命名空间前缀将是正确的,但当然创建的文档不能用它来验证。

使用 .Net 从 3.x 到 4.5.1 进行测试。

谁错了 - COM、.Net 还是我?是错误还是功能?

【问题讨论】:

  • 只需谷歌“d1p1 命名空间”。例如,在 this Q+A 的 cmets 中覆盖得很好。
  • 没用,因为他在调用XmlDocument.CreateAttribute时使用了错误的命名空间。
  • 汉斯:感谢您的回答。我知道这有很多其他的解释,它们都有共同点,我没有找到我的问题的原因 - 至少不是 XmlDocument。通过使用命名空间管理器添加命名空间是我已经尝试过的。但没有帮助。比我发现几个谷歌线程说,命名空间管理器仅适用于 xPath,对编写 xml 没有影响。错了吗?
  • @Yoh Deadfall:我尝试了所有我能想象到的变化——在您看来,CreateAttribute 的正确参数是什么。我还尝试了使用: root.SetAttribute("xsi:schemaLocation", "opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd") 导致属性 schemaLocation="..." 而不是 xsi:schemaLocation ...
  • @Pidi 我在回答中描述了问题所在。请参阅代码中的箭头。

标签: c# xml namespaces


【解决方案1】:

不,这不是 .Net 错误或功能。这只是您的代码中的一个问题。让我们看看指定的命名空间。

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" // <----
    );

你现在看到区别了吗?所以你应该重写属性创建并提供http://www.w3.org/2001/XMLSchema-instance作为命名空间,因为xsi映射到它:

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

注意。如果您使用上面的代码(使用CreateAttribute 方法),那么您可以省略手动创建xsi 声明(即root.SetAttribute("xmlns:xsi", "..."))。此声明将隐式生成。

或者你应该使用下一行代码:

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"
    );

但这不是您的代码的唯一问题,因为您正在混合解析 XML 以创建根元素和 DOM 创建其他元素。所以选择其中之一。

这是最终代码,它是 XML 表示:

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" />

但如果您使用的是 .Net 3.5 及更高版本,请使用 LINQ to XML,因为它更具可读性:

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")
        )
    );

【讨论】:

  • 非常感谢。当然它适用于 root.SetAttribute("schemaLocation", "w3.org/2001/XMLSchema-instance", "opentrans.org/XMLSchema/2.1 opentrans_2_1.xsd");它是如此合乎逻辑,突然间一切都变得清晰起来。然而,这可能是唯一的变化,我没有尝试。属性名称为“schemaLocation”,绑定到之前定义的命名空间xsi,其值为第三个参数。改变和工作。你让我今天一整天都感觉很好。现在我会看看你的其他cmets。皮迪
  • 如果对您有帮助,请将帖子标记为答案。采取tour 知道如何做到这一点。
  • 我从课堂上复制了该代码。我想要完成的是 Dynamics Nav 的一个类,因此人们可以尽可能简单地在 Nav Codeunit 中写下 XML。但是,在我的代码中,我正在从字符串中加载根节点,因为根节点名称有一个前缀 (soapenv:Envelope),到目前为止我找不到另一种处理方法。多亏了你,我现在知道了如何正确处理这个问题:-)
  • 最简单的 XML 编写方法是什么意思?如果没有人对 DOM 操作感兴趣,也许更好的方法是使用XmlWriter?该代码的可读性不如 LINQ to XML 代码,但它需要更少的内存并且运行速度更快。
  • 我的意思是在 Dynamics Nav 中“最简单的方法”! Nav 没有 XML 功能和相当简单的语言。我尝试为 Nav Codeunits 构建一种框架,您可以在其中使用一些简单的命令“只需写下 xml”:SetVersion、AddNamespace(1..n)、AddRoot()、AddNode ... 除此之外我还尝试过避免所有“IF(FieldHasData)THEN CreateNode”。这一切都由那个 C# 类处理。 Nav 中的用户永远不会看到它是 Linq 还是 XmlWriter 还是 DOM。而且 - 多亏了你 - 到目前为止它运行良好。皮迪
【解决方案2】:

用简单的方法来做。只需解析一个字符串 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);

        }
    }
}

【讨论】:

  • 感谢您的提示。即使使用 XlmDocument,我也可以将完整的根节点添加为字符串 - 我知道。我的问题主要是指这样一个事实,即我认为我会理解 xml 命名空间,但显然我不会。
  • 我在过去创建带有命名空间的 xml 根节点时遇到了与您类似的问题。与您正在做的那样添加名称空间相比,该代码解析字符串要简单得多。在开发代码时,我经常不得不做出决定以“正确的方式”做事并让代码“工作”。我接受过使用“软件标准”编写代码的培训,但有时不得不绕过标准来编写可以理解的代码。与我的方法相比,您的代码之类的复杂结构不太容易理解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-30
  • 2011-02-09
  • 2011-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多