【问题标题】:XSLT: How to change an attribute value during <xsl:copy>?XSLT:如何在 <xsl:copy> 期间更改属性值?
【发布时间】:2010-10-11 13:56:10
【问题描述】:

我有一个 XML 文档,我想更改其中一个属性的值。

首先我复制了从输入到输出的所有内容:

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

现在我想更改任何名为"property" 的元素中"type" 属性的值。

【问题讨论】:

  • 对于那些想要通用解决方案的人:w3.org/1999/XSL/Transform" version="1.0">
  • 您的解决方案是不必要的冗长,并且部分错误。 xsl 命名空间的开头应该有“http://www.”。此外,匹配/选择node()|comment()|processing-instruction()|text() 是多余的,因为cmets、处理指令和文本节点由node() 匹配。
  • @Flynn1179 我的解决方案适用于所有情况。我不知道为什么复制/粘贴后http://不见了,这是一个错误,谢谢指出。我只是给出了一个可能的解决方案,而不是完美的解决方案。最重要的是,我的解决方案几乎适用于所有情况,尽管正如您所说的“它是多余的”。而另一方面,大多数其他答案,包括“xslt 专家”给出的答案都根本不起作用。但他们不承认。

标签: xslt xslt-1.0 xslt-2.0


【解决方案1】:

这个问题有一个经典的解决方案使用和覆盖the identity template 是最基本和最强大的 XSLT 设计模式之一

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

    <xsl:param name="pNewType" select="'myNewType'"/>

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

    <xsl:template match="property/@type">
        <xsl:attribute name="type">
            <xsl:value-of select="$pNewType"/>
        </xsl:attribute>
    </xsl:template>
</xsl:stylesheet>

应用于此 XML 文档时

<t>
  <property>value1</property>
  <property type="old">value2</property>
</t>

产生想要的结果

<t>
  <property>value1</property>
  <property type="myNewType">value2</property>
</t>

【讨论】:

  • 如果有命名空间定义,此解决方案将不起作用。几天前我写了一条评论,答案的作者回复了。但是现在它们已经消失了,所以我必须将评论重新发布给那些来这里的人,不要被那些错误的答案误导,尤其是那些倾向于误导的作家。
  • 也许你过于关注理论而不是问题本身。谷歌把我带到了这里,你的回答很有帮助,但根本无法解决我的问题。所以我终于得到了一个更好的,无论它在理论上是对还是错,或者可能导致一些人对命名空间的东西感到疯狂。我关心的是找到解决我问题的方法,我希望我的经验可以帮助其他有类似情况的人。你的回答真的很有帮助,你在这里真的是一个热情的回答者。但不得不说,你给这个问题的解决方案根本行不通。
  • 如果根元素上也有命名空间定义,则此解决方案对我不起作用。
  • @dps 您的问题与此问题正交(无关)。您的问题是关于 XPath 的最常见问题解答。只需搜索“XPath 默认命名空间”,您可能会找到数百个很好的答案和解释。
【解决方案2】:

在一个简单的例子上测试,工作正常:

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
</xsl:template>
<xsl:template match="@type[parent::property]">
  <xsl:attribute name="type">
    <xsl:value-of select="'your value here'"/>
  </xsl:attribute>
</xsl:template>

已编辑以包含 Tomalak 的建议。

【讨论】:

  • 另一种版本是
  • 这也是我想在原始评论中说的,但忘了实际输入。 ;-)
  • @Tomalak:视情况而定。我更喜欢父母/@type。但这显然是主观的。
  • property/@type 更好,因为它更清晰易懂。可能效率更高(几微秒:))
  • 也许,但幸运的是,这种情况很少见。鉴于 OP 从未指定涉及任何命名空间,因此描述一个不认为它们是“错误”的答案可能有点无情。但是,为了任何其他相关方的利益,一个更“完整”的答案可能包括“这仅在没有命名空间的情况下才有效”警告,但这绝不是完全回答所提出问题的必要条件。跨度>
【解决方案3】:

如果根元素中有 xmlns 定义,前两个答案将不起作用:

<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml">
    <property type="old"/>
</html>

所有解决方案都不适用于上述 xml。

可能的解决方案如下:

<?xml version="1.0"?> 

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

  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:template match="node()[local-name()='property']/@*[local-name()='type']">
      <xsl:attribute name="{name()}" namespace="{namespace-uri()}">
                some new value here
          </xsl:attribute>
  </xsl:template>

  <xsl:template match="@*|node()|comment()|processing-instruction()|text()">
      <xsl:copy>
          <xsl:apply-templates select="@*|node()|comment()|processing-instruction()|text()"/>
      </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

【讨论】:

  • 你让这变得比它需要的复杂得多。我发布了一个答案,展示了如何使前两个答案在您的情况下发挥作用。
  • 你的答案比我的复杂得多。我不明白你为什么在我的帖子之后给出额外的答案。你应该做的是加上我的回答。坦率地说,如果属性也有命名空间,你的答案就是错误的。
【解决方案4】:

您需要一个与您的目标属性匹配的模板,仅此而已。

<xsl:template match='XPath/@myAttr'>
  <xsl:attribute name='myAttr'>This is the value</xsl:attribute>
</xsl:template>

这是对您已有的“全部复制”的补充(实际上默认情况下在 XSLT 中始终存在)。如果有更具体的匹配,它将被优先使用。

【讨论】:

  • 我试过没有“全部复制”部分,它只得到标签之间的内容。标签本身或属性都没有被复制。
  • +1 因为它的简单性,并且因为这适用于所呈现的用例以及更复杂的 xpath,您只想在非常特定的 xpath 上更改元素的属性(即当我来到这个页面时,我正在寻找什么)。
【解决方案5】:

我有一个类似的情况,我想从一个简单的节点中删除一个属性,但不知道哪个轴可以让我读取属性名称。最后,我所要做的就是使用

@*[name(.)!='AttributeNameToDelete']

【讨论】:

【解决方案6】:

我也遇到了同样的问题,我解决了如下:

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

<!-- copy property element while only changing its type attribute -->
<xsl:template match="property">
  <xsl:copy>
    <xsl:attribute name="type">
      <xsl:value-of select="'your value here'"/>
    </xsl:attribute>
    <xsl:apply-templates select="@*[not(local-name()='type')]|node()"/>
  </xsl:copy>
</xsl:template>

【讨论】:

    【解决方案7】:

    对于以下 XML:

    <?xml version="1.0" encoding="utf-8"?>
    <root>
        <property type="foo"/>
        <node id="1"/>
        <property type="bar">
            <sub-property/>
        </property>
    </root>
    

    我能够让它与以下 XSLT 一起工作:

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:template match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
        <xsl:template match="//property">
            <xsl:copy>
                <xsl:attribute name="type">
                    <xsl:value-of select="@type"/>
                    <xsl:text>-added</xsl:text>
                </xsl:attribute>
                <xsl:copy-of select="child::*"/>
            </xsl:copy>
        </xsl:template>
    </xsl:stylesheet>
    

    【讨论】:

      【解决方案8】:

      如果您的源 XML 文档有自己的命名空间,您需要在样式表中声明命名空间,为其分配一个前缀,并在引用源 XML 的元素时使用该前缀 - 例如:

      <?xml version="1.0" encoding="UTF-8"?>
      <xsl:stylesheet version="1.0" 
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:xhtml="http://www.w3.org/1999/xhtml">
      
      <xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes" />
      
      <!-- identity transform -->
      <xsl:template match="node()|@*">
          <xsl:copy>
              <xsl:apply-templates select="node()|@*"/>
          </xsl:copy>
      </xsl:template>
      
      <!-- exception-->    
      <xsl:template match="xhtml:property/@type">
          <xsl:attribute name="type">
              <xsl:text>some new value</xsl:text> 
          </xsl:attribute>
      </xsl:template>
      
      </xsl:stylesheet>
      

      或者,如果您愿意:

      ...
      <!-- exception-->    
      <xsl:template match="@type[parent::xhtml:property]">
        <xsl:attribute name="type">
              <xsl:text>some new value</xsl:text> 
        </xsl:attribute>
      </xsl:template>
      ...
      

      附录: 在事先不知道 XML 命名空间的极不可能的情况下,您可以这样做:

      <?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" encoding="utf-8" indent="yes" omit-xml-declaration="yes" />
      
      <!-- identity transform -->
      <xsl:template match="node()|@*">
          <xsl:copy>
              <xsl:apply-templates select="node()|@*"/>
          </xsl:copy>
      </xsl:template>
      
      <!-- exception -->
      <xsl:template match="*[local-name()='property']/@type">
          <xsl:attribute name="type">
              <xsl:text>some new value</xsl:text> 
          </xsl:attribute>
      </xsl:template>
      

      当然,很难想象这样一种场景:您会提前知道源 XML 文档包含一个名为“property”的元素,以及一个需要替换的名为“type”的属性——但仍然不知道文件。我添加这个主要是为了展示如何简化您自己的解决方案。

      【讨论】:

      • 未知命名空间场景并非不可能。至少您可以编写一个 xslt 来处理所有 xml,而不管它们的名称空间是什么。例如,我需要将 的 src 属性转换为从互联网上抓取的数千个网站的页面的空图片。显然,它们的命名空间定义是不确定的。并且每次加入新项目时,如果需要 xslt,通用模板可以是您的基本工具包之一。您不必为不同的项目更改命名空间。
      • 如果属性也有命名空间,你的答案是错误的。我不知道你为什么在我的帖子之后给出另一个错误的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-20
      • 1970-01-01
      • 2012-07-28
      • 1970-01-01
      • 1970-01-01
      • 2020-03-16
      相关资源
      最近更新 更多