无法使用XSL向子元素添加命名空间前缀

7

我查看了很多答案,认为我已经接近成功了。有一件事情让我很困扰(而且由于某种原因,我的同行需要它),如下所示:

我有以下输入XML:

<?xml version="1.0" encoding="utf-8"?>
<MyRoot>
  <MyRequest CompletionCode="0" CustomerID="9999999999"/>
  <List TotalList="1">
    <Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
      <BillToAddress ZipCode="22221"/>
      <ShipToAddress ZipCode="22222"/>
      <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
    </Order>
  </List>
  <Errors/>
</MyRoot>

我被要求制作这个:

<ns:MyNewRoot xmlns:ns="http://schemas.foo.com/response"  
xmlns:N1="http://schemas.foo.com/request"  
xmlns:N2="http://schemas.foo.com/details">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <ns:List TotalList="1">
            <N2:Order CustomerID="999999999" Level="Preferred" Status="Shipped">
                    <N2:BillToAddress ZipCode="22221"/>
                    <N2:ShipToAddress ZipCode="22222"/>
                    <N2:Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
            </N2:Order>
    </ns:List>
    <ns:Errors/>
</ns:MyNewRoot>

请注意N2的子元素也需要N2前缀,以及其余元素需要ns前缀。

我使用以下XSL转换:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <xsl:template match="@* | node()">
  <xsl:copy>
   <xsl:apply-templates select="@* | node()"/>
  </xsl:copy>
 </xsl:template>


<xsl:template match="/MyRoot">
 <MyNewRoot xmlns="http://schemas.foo.com/response"
   xmlns:N1="http://schemas.foo.com/request"
   xmlns:N2="http://schemas.foo.com/details">
     <xsl:apply-templates/>
 </MyNewRoot>
 </xsl:template>

<xsl:template match="/MyRoot/MyRequest">
  <xsl:element name="N1:{name()}" namespace="http://schemas.foo.com/request">
    <xsl:copy-of select="namespace::*"/>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="/MyRoot/List/Order">
  <xsl:element name="N2:{name()}" namespace="http://schemas.foo.com/details">
    <xsl:copy-of select="namespace::*"/>
    <xsl:apply-templates select="@* | node()"/>
  </xsl:element>
 </xsl:template>

</xsl:stylesheet>

这个代码没有处理ns(我无法解决这个问题)。当我通过上述方式使用AltovaXML进行XSL转换时,最终结果如下:

<MyNewRoot xmlns="http://schemas.foo.com/response"  
xmlns:N1="http://schemas.foo.com/request"  
xmlns:N2="http://schemas.foo.com/details">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <List xmlns="" TotalList="1">
            <N2:Order CustomerID="999999999" Level="Preferred" Status="Shipped">
                    <BillToAddress ZipCode="22221"/>
                    <ShipToAddress ZipCode="22222"/>
                    <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
            </N2:Order>
    </List>
    <Errors/>
</MyNewRoot>

请注意,在XSL转换后,Order的子元素中没有N2:前缀。此外,Order头部有额外的xmlns=""(由于某种原因)。我无法弄清楚如何为其他元素(如Errors和List)添加ns:前缀。
首先,如果父元素已经有了命名空间,为什么我还需要为其子元素添加命名空间前缀呢?难道不是父命名空间决定子节点/属性命名空间吗?
其次,我想像预期那样在上面的XML中添加前缀,我该如何使用XSL实现呢?

欢迎来到SO。好问题!建议您花点时间阅读 FAQ。您会发现这是一个问答网站,与讨论论坛截然不同。这就是为什么我从您的问题中删除了“您好”和“谢谢”的原因 - 我们在这里没有讨论! - John Saunders
好问题(+1)。看看我的答案,目前为止它是最短、最简单且唯一正确的解决方案。 :) - Dimitre Novatchev
感谢大家提供宝贵的答案和见解。现在我对所有与XSL相关的事情有了更好的理解... - UbuntuEGGHead
2个回答

7

这个转换(仅42行)

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ns="http://schemas.foo.com/response"
 xmlns:N1="http://schemas.foo.com/request"
 xmlns:N2="http://schemas.foo.com/details"
 >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="*">
  <xsl:element name="ns:{name()}"
       namespace="http://schemas.foo.com/response">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="@*">
   <xsl:copy-of select="."/>
 </xsl:template>

 <xsl:template match="/MyRoot">
  <xsl:element name="ns:{name()}"
       namespace="http://schemas.foo.com/response">
    <xsl:copy-of select=
     "document('')/*/namespace::*[name()='N1' or name()='N2']"/>
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="MyRequest">
  <xsl:element name="N1:{name()}"
       namespace="http://schemas.foo.com/request">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>

 <xsl:template match="*[ancestor-or-self::Order]">
  <xsl:element name="N2:{name()}"
       namespace="http://schemas.foo.com/details">
    <xsl:apply-templates select="node()|@*"/>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档时:

<MyRoot>
  <MyRequest CompletionCode="0" CustomerID="9999999999"/>
  <List TotalList="1">
    <Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
      <BillToAddress ZipCode="22221"/>
      <ShipToAddress ZipCode="22222"/>
      <Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
    </Order>
  </List>
  <Errors/>
</MyRoot>

产生所需结果:

<ns:MyRoot xmlns:N1="http://schemas.foo.com/request" xmlns:N2="http://schemas.foo.com/details" xmlns:ns="http://schemas.foo.com/response">
    <N1:MyRequest CompletionCode="0" CustomerID="9999999999"/>
    <ns:List TotalList="1">
        <N2:Order CustomerID="999999999" OrderNo="0000000001" Status="Shipped">
            <N2:BillToAddress ZipCode="22221"/>
            <N2:ShipToAddress ZipCode="22222"/>
            <N2:Totals Tax="0.50" SubTotal="10.00" Shipping="4.95"/>
        </N2:Order>
    </ns:List>
    <ns:Errors/>
</ns:MyRoot>

请注意:
1. 使用及其name和namespace属性。 2. 如何将“identity template”发展成转换的前两个模板——这个决定是基于只有在特殊情况下元素才不能在ns:命名空间中的事实。 3. 如何为Order元素或其任何后代元素指定N2:命名空间。

更简洁的答案。尽管我理解XSLT规范的措辞,但在xsl:element@name中指定的命名空间前缀不能保证有效,但可能会有效。http://www.w3.org/TR/xslt#section-Creating-Elements-with-xsl:element “XSLT处理器可以利用在名称属性中指定的QName的前缀来选择用于将创建的元素作为XML输出的前缀;但是,它们不必这样做。” - Mads Hansen
@Mads-Hansen:我已经使用三个不同的XSLT处理器进行了检查(只有三个,因为我现在正在旅行并使用我女儿的笔记本电脑),它们都可以正常使用前缀。 - Dimitre Novatchev
1
所以,这就是为什么在过去的几天里我能够比你先回答一些问题的原因!关于保留前缀,我怀疑它在几乎所有处理器中都可以工作。我曾与Ken Holman合作并参加了他的课程,我记得他指出了这一点 - 但他会承认有时候很迂腐。他还提供了类似的解决方案来回答一个类似的问题:http://www.stylusstudio.com/xsllist/200309/post80960.html - Mads Hansen
1
@Mads-Hansen:是的,Ken解释得非常好。现在我明白为什么你在XSLT方面这么擅长——受到Ken Holman的培训。 - Dimitre Novatchev
这也是一个优雅的解决方案。谢谢 Dimitre。我现在正在评估如何处理更复杂(多层)的输入 XML。看起来我会采用你的方案。一旦我确定了,我会发布结果。再次感谢。 - UbuntuEGGHead

4

如果您真的关心输出中的命名空间前缀,则应该在模板中使用literal-result元素,而不是xsl:element构造器:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns="http://schemas.foo.com/response"
    xmlns:N1="http://schemas.foo.com/request"  
    xmlns:N2="http://schemas.foo.com/details">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="MyRoot">
        <ns:MyNewRoot>
            <xsl:apply-templates/>
        </ns:MyNewRoot>
    </xsl:template>

    <xsl:template match="MyRequest">
        <N1:MyRequest>
            <xsl:apply-templates select="@* | node()"/>
        </N1:MyRequest>
    </xsl:template>

    <xsl:template match="List">
        <ns:List>
            <xsl:apply-templates select="@* | node()"/>
        </ns:List>
    </xsl:template>    

    <xsl:template match="Order">
        <N2:Order>
            <xsl:apply-templates select="@* | node()"/>
        </N2:Order>
    </xsl:template>

    <xsl:template match="BillToAddress">
        <N2:BillToAddress>
            <xsl:apply-templates select="@* | node()"/>
        </N2:BillToAddress>
    </xsl:template>

    <xsl:template match="ShipToAddress">
        <N2:ShipToAddress>
            <xsl:apply-templates select="@* | node()"/>
        </N2:ShipToAddress>
    </xsl:template>

    <xsl:template match="Totals">
        <N2:Totals>
            <xsl:apply-templates select="@* | node()"/>
        </N2:Totals>
    </xsl:template>

    <xsl:template match="Errors" />

</xsl:stylesheet>

你需要知道的是,命名空间很重要,而命名空间前缀并不重要。它只是一种语法糖。你可以将多个命名空间前缀绑定到同一个命名空间URI上,或者没有命名空间前缀,仍然可以生成与特定命名空间URI绑定的相同类型的元素。


+1 如果说前缀不重要。我更进一步:任何需要特定前缀的代码都是糟糕的,需要修复和/或公开嘲笑,因为它们没有遵循基本的 XML 标准。 - John Saunders
不幸的是,这个转换并没有产生想要的输出——MyRequest元素不在所需的命名空间中。此外,它太长了。 - Dimitre Novatchev
1
糟糕,我更正了 MyRequest 的命名空间。感谢 @Dimitre。由于某种原因,我记得 Ken Holman 警告说,在 xsl:element 声明的 @name 中指定命名空间前缀不能保证有效,这就是为什么我使用文字元素声明(以及为什么它如此冗长)的原因。如果不是这样的话,那么你的解决方案肯定更好。 - Mads Hansen
如果你指定了一个前缀,XSLT处理器会使用它,即使需要在结果文档中重新定义命名空间绑定。 - Dimitre Novatchev
这太棒了。感谢Mads/John/Dimitre,现在我对该做什么有了更好的理解,而不仅仅是让一个转换工作。 - UbuntuEGGHead
这个也行。问题在于现有的子节点没有得到前缀。对于这个例子,它肯定是有效的。再次感谢! - UbuntuEGGHead

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接