0

我正在编写一个 XSLT 转换,我希望在根元素上定义所有命名空间前缀。默认情况下,MS 似乎会在 XML 层次结构中的第一个元素上创建一个新的前缀定义以使用该模式。这意味着如果这些元素与同一模式的共享祖先不相关,则可以在多个元素上引用相同的模式。

通过像这样对根元素进行编码,所有工作都按预期工作:

<!-- ... -->

<ns0:root xmlns:ns0="http://some/schema" xmlns:ns1 = "http://another/schema">
    <!-- rest of XSLT; including calls to other templates -->
</ns0:root>

<!-- ... -->

但是我找不到任何方法来使用xsl:element; 例如

<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns0="http://some/schema" 
    xmlns:ns1 = "http://another/schema"
>
    <!-- ... -->

    <xsl:element name="ns0:root">
        <xsl:attribute name="ns1" namespace="http://www.w3.org/2000/xslns/">http://another/schema</xsl:attribute>
        <!-- rest of XSLT; including calls to other templates -->
    </xsl:element> 

    <!-- ... -->

xls:element是否可以针对除该元素本身之外的模式声明命名空间前缀?


完整示例

XML

<Demo xmlns="http://some/schema">
    <a>Hello</a>
    <b>World</b>
</Demo>

XSLT

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns0="http://some/schema" 
    xmlns:ns1 = "http://another/schema"
    exclude-result-prefixes="xsl"
>

    <xsl:output method="xml" indent="yes" version="1.0"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*"> 
        <xsl:element name="{name(.)}" namespace="{namespace-uri(.)}">
            <xsl:apply-templates select="@* | node()" />
        </xsl:element>
    </xsl:template> 

    <xsl:template match="/ns0:Demo/ns0:a">
        <xsl:element name="ns1:z">
            <xsl:value-of select="./text()" />
        </xsl:element>
    </xsl:template> 

    <xsl:template match="/ns0:Demo/ns0:b">
        <xsl:element name="ns1:y">
            <xsl:value-of select="./text()" />
        </xsl:element>
    </xsl:template> 

</xsl:stylesheet>

结果

<Demo xmlns="http://some/schema">
    <ns1:z xmlns:ns1="http://another/schema">Hello</ns1:z>
    <ns1:y xmlns:ns1="http://another/schema">World</ns1:y>
</Demo>

期望的结果

<Demo xmlns="http://some/schema" xmlns:ns1="http://another/schema">
    <ns1:z>Hello</ns1:z>
    <ns1:y>World</ns1:y>
</Demo>

或者

<ns0:Demo xmlns:ns0="http://some/schema" xmlns:ns1="http://another/schema">
    <ns1:z>Hello</ns1:z>
    <ns1:y>World</ns1:y>
</ns0:Demo>
4

2 回答 2

3

您的最小示例没有解释为什么您需要使用xsl:element而不是xsl:copy和/或文字结果元素,但由于 XSLT 1.0 没有xsl:namespace说明(https://www.w3.org/TR/xslt20/#creating-namespace-nodes)您的唯一的方法是从样式表根目录复制命名空间节点,如

<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns0="http://some/schema" 
    xmlns:ns1 = "http://another/schema"
    exclude-result-prefixes="xsl"
    >

    <xsl:output method="xml" indent="yes" version="1.0"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/*"> 
        <xsl:element name="{name(.)}" namespace="{namespace-uri(.)}">
            <xsl:copy-of select="document('')/*/namespace::*[. = 'http://another/schema']"/>
            <xsl:apply-templates select="@* | node()" />
        </xsl:element>
    </xsl:template> 

    <xsl:template match="/ns0:Demo/ns0:a">
        <xsl:element name="ns1:z">
            <xsl:value-of select="./text()" />
        </xsl:element>
    </xsl:template> 

    <xsl:template match="/ns0:Demo/ns0:b">
        <xsl:element name="ns1:y">
            <xsl:value-of select="./text()" />
        </xsl:element>
    </xsl:template> 

</xsl:stylesheet>

(或具有该节点的任何其他节点,例如参数或变量,但这样您还可以将结果树片段转换为首先使用exsl:node-setor设置的节点ms:node-set)。

至于为什么字面结果元素xsl:element会给你不同的结果,嗯,https ://www.w3.org/TR/xslt#literal-result-element说:

创建的元素节点还将具有样式表树中元素节点上存在的命名空间节点的副本...

https://www.w3.org/TR/xslt#section-Creating-Elements-with-xsl:element并没有这么说。

于 2017-06-23T15:47:57.563 回答
2

重要的是要理解,尽管它们在 XML 文档中通过名称空间声明属性表示,但在 XPath 和 XSLT 的数据模型中,每个元素的范围内名称空间是通过名称空间节点而不是属性节点建模的。此外,不同的元素不共享命名空间节点;每个都有自己的一套。使用 XML 输出方法时,XSLT 处理器负责生成正确表示结果树中存在的名称空间节点的名称空间声明属性。

这完全解释了为什么XSLT 1.0 规范的第 7.1.3 节xsl:attribute明确禁止通过元素创建命名空间声明:

XSLT 处理器在选择用于将创建的属性输出为 XML 的前缀时,可以使用 name 属性中指定的 QName 的前缀;但是,他们不是必须这样做,如果前缀是xmlns,他们绝不能这样做。因此,尽管这样做不是错误:

<xsl:attribute name="xmlns:xsl" namespace="whatever">http://www.w3.org/1999/XSL/Transform</xsl:attribute>

它不会导致输出命名空间声明。

(添加了重点。)如果允许以这种方式创建名称空间声明,那么它将允许结果文档表达结果树中实际不存在的名称空间节点。

结果树中的元素可以通过以下任何方式获取命名空间节点:

  • 通过原始元素的命名空间节点创建xsl:copyxsl:copy-of接收副本的结果元素。
  • 通过样式表树中的文字结果元素创建的结果元素获取样式表元素的所有命名空间节点的副本,无论是直接在该元素上声明还是在祖先元素上声明,但有一些例外。
  • 通过xsl:element样式表元素创建的结果元素没有明确指定接收任何命名空间节点,但实际上,为了正确实现规范,它们需要接收元素名称的命名空间(如果有的话)的命名空间节点。
  • 因为只有元素具有命名空间节点,所以(但未明确指定)每个元素还必须为其属性名称之一所属的每个命名空间接收一个命名空间节点,如果该命名空间不同于元素自己的名称。
  • 如另一个答案所示,可以将命名空间节点本身复制到结果树中。

除了这些之外,没有理由期望 XSLT 处理器会在结果树中创建额外的名称空间节点。特别是,虽然这可能提供对结果树进行更简单的 XML 序列化的可能性,但树本身会更加复杂。

那么,为了确保<Demo>结果文档中的元素携带一个命名空间声明,而不是该元素自己的命名空间,一种方法是通过从输入树复制结果元素或元素的属性获得的任何命名空间声明,是使用文字结果元素:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:ns0="http://some/schema" 
    xmlns:ns1 = "http://another/schema">

    <xsl:output method="xml" indent="yes" version="1.0"/>
    <xsl:strip-space elements="*"/>

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

    <!-- ... -->

</xsl:stylesheet>

另一方面,如果你必须通过一个元素创建xsl:element元素——只有在需要计算它的名称时才需要——那么你需要从输入树中复制一个命名空间节点。

于 2017-06-23T16:31:23.677 回答