【问题标题】:using XSL to replace XML nodes with new nodes使用 XSL 将 XML 节点替换为新节点
【发布时间】:2010-06-01 19:16:14
【问题描述】:

我需要一个 XSL 解决方案来用新节点替换 XML 节点。

假设我有以下现有的 XML 结构:

<root>
    <criteria>
        <criterion>AAA</criterion>
    </criteria>
</root>

我想将一个标准节点替换为:

<criterion>BBB</criterion>
<criterion>CCC</criterion>
<criterion>DDD</criterion>

这样最终的XML结果是:

<root>
    <criteria>
        <criterion>BBB</criterion>
        <criterion>CCC</criterion>
        <criterion>DDD</criterion>
    </criteria>
</root>

我尝试使用 substring-before 和 substring-after 来复制结构的前半部分,然后只复制后半部分(以便在两半之间填充我的新节点),但似乎子字符串函数只能识别节点标签之间的文本,而不是像我想要的那样识别标签本身。 :( :(

还有其他解决方案吗?

【问题讨论】:

  • 好问题 (+1)。请参阅我的答案以获得正确且简短的解决方案。请注意,当前接受的解决方案有一个逻辑缺陷(它恰好适用于您的 XML 文档,但通常会产生其他 XML 文档的错误结果)。

标签: xml xslt xpath


【解决方案1】:

XSL 不能替换任何东西。您可以做的最好的事情是复制您想要保留的部分,然后输出您想要更改的部分而不是您不想保留的部分。


例子:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
    <xsl:output method="xml" indent="yes"/>

    <!-- This is an identity template - it copies everything
         that doesn't match another template -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>

  <!-- This is the "other template". It says to use your BBB-DDD elements
       instead of the AAA element -->
  <xsl:template match="criterion[.='AAA']">
    <xsl:element name="criterion">
      <xsl:text>BBB</xsl:text>
    </xsl:element>
    <xsl:element name="criterion">
      <xsl:text>CCC</xsl:text>
    </xsl:element>
    <xsl:element name="criterion">
      <xsl:text>DDD</xsl:text>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

模板匹配@* | node() 匹配任何属性或任何其他类型的节点。诀窍是模板匹配具有优先级。您可以将规则视为“更具体的比赛获胜”。 Anything 将比“任何属性或其他节点”更具体。这使得“身份”匹配的优先级很低。

当它匹配时,它简单地复制它在匹配的属性或节点中找到的任何节点。

您拥有的任何其他模板将具有更高的优先级。无论它们匹配什么,都会有更具体的模板中的代码生效。例如,如果您只是删除了 criterion[.='AAA'] 模板中的所有内容,您会发现您已经完全复制了您的输入,除了“AAA”元素。

【讨论】:

  • @iheartgreek 仔细查看 John 设置的模板。匹配 XML 元素的模板是有效使用 XSLT 的关键。
  • 感谢您提供代码示例。这正是我想要实现的目标!!虽然这个解决方案对我有用,但我也很想了解它。我对第二个模板 (match="criterion['AAA']") 做了类似的事情,但我不知道(或意识到)要做的关键部分是复制与另一个模板不匹配的所有内容的模板。因此....您能解释一下 XPath match="@* | node()" 是如何实现这一点的吗?我是 XPath 和 XSL 的新手,所以我仍然不知道其中的来龙去脉。
  • 谢谢!既然我知道模板有优先级,那就更有意义了! :D
  • @Downvoter:如果你有话要说,请用文字说出来。 “-1”不是很有说服力。
  • @Dimitre:非常好。我希望您不需要被提示解释您的反对意见。通过解释这一点,您对读者有很大帮助。
【解决方案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:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="criterion[. = 'AAA']">
  <criterion>BBB</criterion>
  <criterion>CCC</criterion>
  <criterion>DDD</criterion> </xsl:template>
</xsl:stylesheet>

当对提供的 XML 文档应用这种转换时,会产生想要的结果

<root>
    <criteria>
        <criterion>BBB</criterion>
        <criterion>CCC</criterion>
        <criterion>DDD</criterion>
    </criteria>
</root>

请注意

  1. 身份模板的使用

  2. 身份模板如何被特定模板覆盖——仅适用于criterion元素,其字符串值为'AAA'

【讨论】:

  • “相当不准确”?我相信您已经指出了一个错误,Mads Hansen 不仅指出了这一错误,而且还对其进行了解释和纠正。
  • @John-Saunders:我很高兴错误得到了纠正。将尝试取消我的反对票。希望这能帮助您下次多加注意。我的原则是对包含(至少是明显的)错误的任何内容投反对票。不幸的是,我没有时间更正别人答案中的错误。
  • @Dimitre:你的态度没有帮助。我试图回答这个问题。我犯了一个错误。我未更正代码中的测试评估为true()。结果,样式表似乎起作用了。您的“多加注意”评论没有理由。如果您懒得参与这里的社区(这确实涉及帮助纠正错误的答案),那么您可能会考虑另一种方式,您的无声投票的无益贡献将不会被注意到。跨度>
  • @John-Saunders:我真的很“厌烦”参与社区——尤其是当我看到一个不正确的答案挂了几个小时并且被不怀疑存在缺陷的人复制时它。至于编辑这样的答案——不,编辑它意味着完全重写它——我是通过发布我的答案来做到的。约翰,你的回答中有很多“小”问题,我真的没有时间。
  • @Dimitre:Mads Hansen 有时间 - 他不得不修改一行以防止人们复制一个重大错误。不再需要了。即使你在投反对票时添加了一条评论,说有错误所以不要复制代码,那也会让读者免于复制我的代码的悲剧。
【解决方案3】:

根据不止一种给猫剥皮的一般规则

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml"/>
    <xsl:template match="/">
        <xsl:apply-templates />
    </xsl:template>
    <!--  
        when you capture a node with the text 'AAA'
            emit the BBB, CCC, DDD nodes
     -->
    <xsl:template match="criterion[text() = 'AAA']">
        <xsl:element name="criterion">
            <xsl:text>BBB</xsl:text>
        </xsl:element>
        <xsl:element name="criterion">
            <xsl:text>CCC</xsl:text>
        </xsl:element>
        <xsl:element name="criterion">
            <xsl:text>DDD</xsl:text>
        </xsl:element>
    </xsl:template>
    <!--  identity template  -->
    <xsl:template match="@*|node()">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

【讨论】:

  • 您能否详细说明这与我的解决方案有何不同?
  • @John:没有区别,不过你的解释更多。我想我已经发布了它并且/然后/更新了...你只是打败了我:-)
猜你喜欢
  • 2014-02-01
  • 2014-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-27
  • 1970-01-01
相关资源
最近更新 更多