将命名空间添加到根元素
Posted
技术标签:
【中文标题】将命名空间添加到根元素【英文标题】:Add Namespaces to Root Element 【发布时间】:2017-11-27 02:57:12 【问题描述】:我正在编写一个 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>
【问题讨论】:
您可能希望向我们展示最小但完整的 XML 输入示例、您拥有的 XSLT 代码、您想要的结果以及您获得的结果,以便我们重现和理解问题。<xsl:element name="ns0:root">
的 sn-p 似乎没有必要,因为你没有在运行时计算元素名称,所以我不确定你为什么需要 xsl:element
。如果您希望命名空间声明在所有模板中的所有结果元素的范围内,那么您可以将它们放在xsl:stylesheet
上,但似乎您已经拥有了。
不用担心;请参阅更新版本的“完整示例”部分。
你为什么在乎?您呈现的期望结果和实际结果具有相同的语义。
@JohnBollinger 可读性和文件大小。当有数百个同级元素时,使用模式定义所有元素会使它们更难阅读,并显着增加文件大小。还有好奇心。同意我可以让这个 XSL 工作;但我觉得应该可以让它按照我想要的方式工作(特别是考虑到使用<Root>
代替<xsl:element name="Root">
/ <xsl:element name="name(.)">
; 当你期望所有方法都有相同的行为时(除了最后一个的名称根据输入 XML 的根元素而变化)。
【参考方案1】:
您的最小示例没有解释为什么您需要使用 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-set
或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 没有这么说。
【讨论】:
谢谢。规格上的惊人差异,但很高兴知道它已记录在案。知道他们为什么这样设计会很有趣......在我的XslCompiledTransform
上将EnableDocumentFunction
设置为true 后,上述技术完美运行。【参考方案2】:
了解虽然它们在 XML 文档中通过命名空间声明属性表示,但在 XPath 和 XSLT 的数据模型中,每个元素的范围内命名空间是通过命名空间节点而不是属性节点建模的,理解这一点很重要。此外,不同的元素不共享命名空间节点;每个都有自己的一套。使用 XML 输出方法时,XSLT 处理器负责生成正确表示结果树中存在的名称空间节点的名称空间声明属性。
这充分解释了为什么Section 7.1.3 of the XSLT 1.0 spec 明确禁止通过xsl:attribute
元素创建命名空间声明:
XSLT 处理器可以使用 QName 的前缀 选择用于的前缀时在名称属性中指定 将创建的属性输出为 XML;然而,他们不是 必须这样做,如果前缀是
xmlns
,则不得这样做。 因此,尽管这样做不是错误:<xsl:attribute name="xmlns:xsl" namespace="whatever">http://www.w3.org/1999/XSL/Transform</xsl:attribute>
它不会导致输出命名空间声明。
(添加了重点。)如果允许以这种方式创建命名空间声明,那么它将允许结果文档表达结果树中实际不存在的命名空间节点。
结果树中的元素可以通过以下任何方式获取命名空间节点:
通过xsl:copy
或xsl:copy-of
创建的结果元素接收原始元素的命名空间节点的副本。
通过样式表树中的文字结果元素创建的结果元素获取样式表元素的所有命名空间节点的副本,无论是直接在该元素上声明还是在祖先元素上声明,但有一些例外。
通过xsl:element
样式表元素创建的result 元素没有明确指定接收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
元素创建元素(仅在需要计算其名称时才需要),那么您需要从输入树中复制命名空间节点.
【讨论】:
以上是关于将命名空间添加到根元素的主要内容,如果未能解决你的问题,请参考以下文章