C# 中的 XML 默认命名空间和 XSLT 转换问题



在这篇文章的附言中,你可以找到一个C#代码片段,它使用XSLT转换+正则表达式匹配从XML文档中获取货币汇率值。请阅读我的问题内联引用

/

/+

/

/-

注释行组。

谢谢。

附注代码:

   string currencyCode = "RUB";
var xml = new StringReader( 
    @"<gesmes:Envelope 
        xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01' 
        xmlns{{EmptyNameSpace}} ='http://www.ecb.int/vocabulary/2002-08-01/eurofxref'>
        <gesmes:subject>Reference rates</gesmes:subject>
        <gesmes:Sender>
        <gesmes:name>European Central Bank</gesmes:name>
    </gesmes:Sender>
    <Cube>
        <Cube time='2012-06-27'>
        <Cube currency='USD' rate='1.2478' />
        <Cube currency='RUB' rate='41.1252' />
        <Cube currency='ZAR' rate='10.4601' />
        </Cube>
    </Cube>
    </gesmes:Envelope>"
            .Replace("{{EmptyNameSpace}}", ":ns1")
            //+
            // I'd like to get this code working the same way as it does now
            // producing
            //
            // Result: EUR/RUB => 41.1252
            //
            // output but when the above code line will be commented
            // and the below code line uncommented
            //-
            //.Replace("{{EmptyNameSpace}}", "") 
);
var xslt = new XmlTextReader(new StringReader(
@"<xsl:stylesheet version='2.0' 
    xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
    xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01' 
    xmlns:ns1 ='http://www.ecb.int/vocabulary/2002-08-01/eurofxref'
    >
<xsl:variable name='x1' select='gesmes:Envelope/Cube/Cube/Cube[@currency=""{0}""]' />
<xsl:template match='gesmes:Envelope'>
  [<xsl:apply-templates select='$x1' />]
</xsl:template>
<xsl:template match='Cube'>
    <xsl:value-of select='@rate' />
</xsl:template>
</xsl:stylesheet>".Replace("{0}", currencyCode)  
));
var xDoc = new XPathDocument(xml);
var xTr = new System.Xml.Xsl.XslCompiledTransform(); 
xTr.Load(xslt) ;
StringBuilder sb = new StringBuilder(); 
StringWriter writer = new StringWriter(sb); 
xTr.Transform(xDoc, null, writer);
string pattern = @"[(?'name'[0-9]*.[0-9]*)]";
System.Console.WriteLine(
    "Result: EUR/{0} => {1}",
    currencyCode, 
    Regex.Match(sb.ToString(), pattern)
   .Groups["name"].Value);    
// Result: EUR/RUB => 41.1252

据我查看该代码可以看出,您的问题是处理 XPath 中的默认命名空间:

  • 只要{{EmptyNameSpace}} :ns1,一切都很好 - 在您的 Xml 文档中,定义了带有前缀 gesmesns1 的命名空间http://www.gesmes.org/xml/2002-08-01http://www.ecb.int/vocabulary/2002-08-01/eurofxref;您的 Xml 文档使用标识符,例如来自gesmes<gesmes:Envelope>标识符和其他标识符,即 <Cube> ,它们不与任何命名空间相关联。在 XSLT 代码中,您使用的是 XPath 表达式,例如(此处缩短)gesmes:Envelope/Cube/Cube/Cube ,这很好,因为此表达式引用使用 gesmes 前缀声明的命名空间中的 <Envelope> 元素,以及没有命名空间的<Cube>元素。
  • {{EmptyNameSpace}}设置为空字符串后,这将更改:现在,http://www.ecb.int/vocabulary/2002-08-01/eurofxref是文档的默认命名空间。因此,文档中的<Cube>元素被视为属于该命名空间。但是,XPath 表达式仍会将 XPath 中显示的<Cube>元素视为没有命名空间 - 它们没有任何命名空间前缀,并且 XPath 不知道默认命名空间。

作为解决方案,您应该将命名空间前缀添加到 XPath 表达式 - 如果 <Cube> 元素属于 http://www.ecb.int/vocabulary/2002-08-01/eurofxref 命名空间,则 XPath 应如下所示:

gesmes:Envelope/ns1:Cube/ns1:Cube/ns1:Cube

(当然,XSLT 中的其他 XPath 表达式必须相应地进行调整。

如果您需要对此进行任何进一步的解释,或者我的假设是否走上了错误的轨道,请告诉我。

更新:如果 Xml 文档具有默认命名空间,则 XSLT 应如下所示:

<xsl:stylesheet version="2.0" xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:gesmes='http://www.gesmes.org/xml/2002-08-01' xmlns:ns1='http://www.ecb.int/vocabulary/2002-08-01/eurofxref'>
    <xsl:variable name='x1' select='gesmes:Envelope/ns1:Cube/ns1:Cube/ns1:Cube[@currency="RUB"]'/>
    <xsl:template match='gesmes:Envelope'>
        [<xsl:apply-templates select='$x1'/>]
    </xsl:template>
    <xsl:template match='ns1:Cube'>
        <xsl:value-of select='@rate'/>
    </xsl:template>
</xsl:stylesheet>

最新更新