XSLT 身份转换产生错误的命名空间

标签 xslt namespaces

我有一个正在演变的自定义 XML 模式:添加元素、删除其他元素以及更改命名空间以反射(reflect)新版本(例如,从“http://foo/1.0”到“http://foo/1.1")。我想编写一个 XSLT,将 XML 文档从旧模式转换为新模式。我的第一次尝试有效,但它很冗长且不可扩展,我需要帮助来完善它。

这是 1.0 架构的示例文档:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo:rootElement xmlns:foo="http://foo/1.0">
    <obsolete>something</obsolete>
    <stuff>
        <alpha>a</alpha>
        <beta>b</beta>
    </stuff>
</foo:rootElement>

在 1.1 架构中,“过时”元素消失了,但其他所有内容仍然保留。因此 XSLT 需要做两件事:

  1. 删除标签
  2. 将命名空间从 http://foo/1.0 更改为至 http://foo/1.1

这是我的解决方案:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:foo1="http://foo/1.0"
    xmlns:foo="http://foo/1.1" 
    exclude-result-prefixes="foo1">

    <xsl:output method="xml" standalone="yes" indent="yes"/>

    <!-- Remove the "obsolete" tag -->    
    <xsl:template match="foo1:rootElement/obsolete"/>

    <!-- Copy the "alpha" tag -->
    <xsl:template match="foo1:rootElement/stuff/alpha">
        <alpha>
            <xsl:apply-templates/>
        </alpha>
    </xsl:template>

    <!-- Copy the "beta" tag -->
    <xsl:template match="foo1:rootElement/stuff/beta">
        <beta>
            <xsl:apply-templates/>
        </beta>
    </xsl:template>

    <!-- Copy the "stuff" tag -->
    <xsl:template match="foo1:rootElement/stuff">
        <stuff>
            <xsl:apply-templates/>
        </stuff>
    </xsl:template>

    <!-- Copy the "rootElement" tag -->
    <xsl:template match="foo1:rootElement">
        <foo:rootElement>
            <xsl:apply-templates/>
        </foo:rootElement>
    </xsl:template>

</xsl:stylesheet>

虽然这会产生我想要的输出,但请注意,我为模式中的每个元素都有一个复制模板:alpha、beta 等。对于具有数百种元素的复杂模式,我必须添加数百个模板!

我认为我可以通过将所有这些复制模板减少为单个身份转换来消除这个问题,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:foo1="http://foo/1.0"
    xmlns:foo="http://foo/1.1" 
    exclude-result-prefixes="foo1">

    <xsl:output method="xml" standalone="yes" indent="yes"/>

    <xsl:template match="foo1:rootElement/obsolete"/>

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

    <xsl:template match="foo1:rootElement/stuff">
        <xsl:copy>
            <xsl:call-template name="identity"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="foo1:rootElement">
        <foo:rootElement>
            <xsl:apply-templates/>
        </foo:rootElement>
    </xsl:template>

</xsl:stylesheet>

但它会产生:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo:rootElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:foo="http://foo/1.1">  
    <stuff xmlns:foo="http://foo/1.0">
      <stuff>
        <alpha>a</alpha>
        <beta>b</beta>
      </stuff>
   </stuff>
</foo:rootElement>

“stuff”元素被复制,这正是我想要的,但它被包装在另一个“stuff”元素中,并用旧的命名空间标记。我不明白为什么会发生这种情况。我尝试了很多方法来解决这个问题,但都没有成功。有什么建议么?谢谢。

最佳答案

这种转变:

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

 <xsl:param name="pOldNS" select="'http://foo/1.0'"/>
 <xsl:param name="pNewNS" select="'http://foo/1.1'"/>

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

 <xsl:template match="*">
  <xsl:element name="{name()}">
   <xsl:copy-of select="namespace::*[not(.=$pOldNS)]"/>

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

 <xsl:template match="@*">
  <xsl:choose>
   <xsl:when test="namespace-uri()=$pOldNS">
    <xsl:attribute name="{name()}" namespace="{$pNewNS}">
     <xsl:value-of select="."/>
    </xsl:attribute>
   </xsl:when>
   <xsl:otherwise>
    <xsl:copy-of select="."/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>

 <xsl:template match="obsolete"/>
</xsl:stylesheet>

应用于此 XML 文档时:

<foo:rootElement xmlns:foo="http://foo/1.0">
    <obsolete>something</obsolete>
    <stuff>
        <alpha x="y" foo:t="z">a</alpha>
        <beta>b</beta>
    </stuff>
</foo:rootElement>

产生想要的正确结果(删除了绝对元素、升级了命名空间、正确处理了属性节点,包括旧命名空间中的节点):

<foo:rootElement xmlns:foo="http://foo/1.1">
   <stuff>
      <alpha x="y" foo:t="z">a</alpha>
      <beta>b</beta>
   </stuff>
</foo:rootElement>

该解决方案的主要优点:

  1. 当存在属于旧架构命名空间的属性时,可以正常工作

  2. 常规,允许将旧的和新的命名空间作为外部参数传递给转换

关于XSLT 身份转换产生错误的命名空间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5929756/

相关文章:

security - Kubernetes - 动态命名空间/安全

c# - TypeScript 中的命名空间关键字

java - 如何在 1 个 XSL 中加入 2 个 XML

java - 递归 XSLT 生成与任何模板都不匹配的文本

xslt - xsl :attribute-set not inheriting from xsl:attribute-set correctly

xslt - 使用 XSLT 1.0 的元素上的多个命名空间

javascript - 如何读取 XSL-T 中的 URL 然后将其传递到 jQuery 函数?

javascript - 如何直接将createNSResolver与lookupNamespaceURI一起使用?

variables - tcl 中的变量作用域

language-agnostic - 命名类,如 "com.facebook.FacebookClient"与 "com.facebook.Client"