2011-05-08 32 views
2

我有一个正在发展的自定义XML模式:添加元素,删除其他元素,并更改名称空间以反映新版本(例如,从“http://foo/1.0”到“ HTTP://foo/1.1" )。我想编写一个将XML文档从旧模式转换为新模式的XSLT。我的第一次尝试是有效的,但它是冗长而不可缩放的,我需要帮助完善它。XSLT标识转换产生错误的名称空间

下面是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> 

的“东西”的因素是这是我想要的,但它被另一个“东西”元素包裹,用旧的名称空间标记。我不明白为什么会发生这种情况。我已经尝试了很多方法来解决这个问题,但一直没有成功。有什么建议么?谢谢。

+0

好问题,+1。查看我的答案,获得完整,简短和简单的解决方案。这是通过复制正确处理属性的唯一答案(当前两个)。而且,即使属于旧名称空间的属性也能正确处理 - 升级到新的名称空间)。 @Gunther在应用于我的答案中使用的XML文档时的答案产生了旧名称空间中不需要的名称空间节点。它也不必要地使用XSLT 2.0。 – 2011-05-08 20:50:04

回答

2

这种转变

<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> 

产生通缉,正确的结果obsolute元素已删除,nam协商升级,属性节点正确处理,包括分别在旧命名空间)节点:该溶液的

<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. 一般情况下,允许将旧名称空间和新名称空间作为外部参数传递给转换。

+0

谢谢!您的解决方案完美运作 – vocaro 2011-05-09 01:49:36

+0

@vocaro:不客气。 – 2011-05-09 02:20:07

2

虽然您可能想要复制属性,但需要在新名称空间中重新生成元素。所以,我建议做这样的:

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:foo1="http://foo/1.0"> 

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

    <xsl:template match="attribute()"> 
     <xsl:copy/> 
    </xsl:template> 

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

</xsl:stylesheet> 
+0

当您的转换应用于我的答案中使用的XML文档时,会生成旧的名称空间节点。尝试一下。 – 2011-05-08 20:55:42

+0

@Dimitre,你是对的,但这个问题是关于将元素移动到一个新的命名空间。我们没有任何迹象表明@vocaro是否对属性命名空间感兴趣。我宁愿保持简单的回应,在这种情况下,我觉得省略是没有害处的。当然,您的答案完整,简短,并且确实可以正确移动属性:+1。 – Gunther 2011-05-08 21:08:13

+0

谢谢。我理解你的解释,这是有道理的。您可以添加关于解决方案假设的通知。 – 2011-05-08 21:17:37