将命名空间添加到根元素

Add Namespaces to Root Element

我正在编写 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>

你的最小例子没有解释为什么你需要使用 xsl:element 而不是 xsl:copy and/or 文字结果元素,但是因为 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-setms:node-set 设置的节点)。

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

The created element node will also have a copy of the namespace nodes that were present on the element node in the stylesheet tree ...

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

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

这充分解释了为什么 Section 7.1.3 of the XSLT 1.0 spec 明确禁止通过 xsl:attribute 元素创建命名空间声明:

XSLT processors may make use of the prefix of the QName specified in the name attribute when selecting the prefix used for outputting the created attribute as XML; however, they are not required to do so and, if the prefix is xmlns, they must not do so. Thus, although it is not an error to do:

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

it will not result in a namespace declaration being output.

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

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

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

没有理由期望 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 元素创建元素——只有在需要计算其名称时才需要——那么您将需要复制一个命名空间节点来自输入树。