使用 XSLT 重命名节点

Posted

技术标签:

【中文标题】使用 XSLT 重命名节点【英文标题】:Rename nodes with XSLT 【发布时间】:2008-11-07 04:06:51 【问题描述】:

我正在尝试一些非常简单的方法,但由于某种原因它不起作用。基本上,我需要重命名 XML 文档中的一些节点。因此,我创建了一个 XSLT 文件来进行转换。

这是一个 XML 示例:

编辑:地址和地址元素出现在许多级别。这就是导致我不得不尝试应用 XSLT 的原因。 Visual Studio 类型化数据集功能(从 XSD 文件创建类型化数据集)不允许您对同一个表进行嵌套引用。因此,拥有 Businesses/Business/Addresses 和 Businesses/Business/Contact/Addresses 会导致 Load() 失败。这是一个已知问题,他们告诉您的只是“没有嵌套表引用...编辑您的 XSD 以停止它”。不幸的是,这意味着我们必须应用 XSLT 来使 XML 符合“被黑”的 XSD,因为这些文件来自第三方供应商。

因此,我们非常接近此处提供的帮助。最后几件事是:

1.) 如何使用 xsl:template 的 match 属性中的命名空间引用来指定我想将 Businesses/Business/Addresses 重命名为 BusinessAddresses,但重命名 Businesses/Business/Contacts/Contact/Addresses联系地址?

2.) 如何阻止 XSLT 使用显式命名空间引用来混淆每个新元素?它导致输出极度膨胀。

我创建了一个名为“steel”的命名空间,并且在以下方面取得了很好的成功:

<xsl:template match="steel:Addresses>
  <xsl:element name="BusinessAddresses>
</xsl:template>

这里最明显的问题是它将ALL 的Addresses 元素重命名为BusinessAddresses,即使我希望其中一些命名为ContactAddresses,等等。对所有重命名的节点进行不必要的显式命名空间引用也很麻烦。

我尝试过这种事情,但是一旦我在 match 属性中添加斜杠,它就是 XSLT 中的语法错误,如下所示:

<xsl:template match="steel:/Businesses/Business/Addresses">

我感觉非常接近,但需要一些关于如何混合使用命名空间和使用斜杠选择特定路径下的 ANY 节点的方法的指导。

<?xml version="1.0"?>
<Businesses>
  <Business>
    <BusinessName>Steel Stretching</BusinessName>
    <Addresses>
      <Address>
        <City>Pittsburgh</City>
      </Address>
      <Address>
        <City>Philadelphia</City>
      </Address>
    </Addresses>
    <Contacts>
      <Contact>
        <FirstName>Paul</FirstName>
        <LastName>Jones</LastName>
        <Addresses>
          <Address>
            <City>Pittsburgh</City>
          </Address>
        </Addresses>
      </Contact>
    </Contacts>
  </Business>
  <Business>
    <BusinessName>Iron Works</BusinessName>
    <Addresses>
      <Address>
        <City>Harrisburg</City>
      </Address>
      <Address>
        <City>Lancaster</City>
      </Address>
    </Addresses>
  </Business>
</Businesses>

对于直接在业务节点下的地址和地址的每个实例,我需要将地址重命名为业务地址,并且我需要将地址重命名为业务地址。我还需要将地址重命名为 ContactAddresses,并且我需要将地址重命名为 ContactAddress,对于直接在联系人节点下的地址和地址的每个实例。

我尝试了几种解决方案,但似乎都没有奏效。它们最终都会生成与原始文件相同的 XML。

这是我尝试过的一个示例:

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

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

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

这已经在至少 6 种不同的风格中进行了尝试,并在 VB.Net 中逐步完成了 XSLT 调试器。它从不执行地址的模板匹配。

为什么?

【问题讨论】:

我在下面给出了答案,但我不确定它是否有帮助。会不会是正在处理的 XML 在标签名称中有拼写错误?我不确定还有什么建议 - 可以提供更多细节吗? 命名空间问题如何?也许您已经在输入 XML 文档中声明了默认命名空间,但没有在 XSLT 中使用所需的前缀声明它? 【参考方案1】:

为什么 XSLT 会失败?

XSLT 会因为拼写错误等明显原因而失败。但是,最可能的情况与命名空间的使用有关。如果您为您的 XML 声明了一个默认名称空间,但没有将其包含在您的 XSLT 中,那么 XSLT 将不会像您预期的那样匹配模板。

以下示例添加了xmlns:business 属性,该属性声明由business 前缀限定的项目属于命名空间mynamespace.uri。然后我使用这个前缀来限定地址和地址模板匹配。当然,您需要将命名空间 URI 更改为与 XML 文件的默认命名空间匹配的任何内容。

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                xmlns:business="mynamespace.uri"
                exclude-result-prefixes="msxsl">
  <xsl:template match="/">
    <xsl:apply-templates select="@*|node()"/>
  </xsl:template>

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

  <xsl:template match="business:Addresses">
    <xsl:element name="BusinessAddresses">
      <xsl:apply-templates select="@*|node()" />
    </xsl:element>
  </xsl:template>

  <xsl:template match="business:Address">
    <xsl:element name="BusinessAddress">
      <xsl:apply-templates select="@*|node()"/>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

如何根据元素位置和名称匹配模板?

有几种方法可以解决您的问题的最后一部分,BusinessAddress 或 ContactAddress,但最简单的方法是修改模板 match 属性以考虑父节点。如果您将 match 属性视为节点的 XML 路径,则此选项会变得更加清晰(为简洁起见,模板的内容省略了):

<xsl:template match="business:Business/business:Addresses>
</xsl:template>

<xsl:template match="business:Business/business:Addresses/business:Address">
</xsl:template>

<xsl:template match="business:Contact/business:Addresses">
</xsl:template>

<xsl:template match="business:Contact/business:Addresses/business:Address">
</xsl:template>

如果match 仅基于元素名称,则存在其他方法来实现此目的,但它们更难实现、遵循和维护,因为它们涉及对元素的父节点层次结构使用条件检查正在处理中,全部在模板中。

【讨论】:

【参考方案2】:

也许是这样,如果你展示的数据真的和你要处理的一样

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

<xsl:template match="Businesses">
 <Businesses>
  <xsl:apply-templates/>
 </Businesses>
</xsl:template>

<xsl:template match="*">
 <xsl:copy-of select="."/>
</xsl:template>

<xsl:template match="Addresses">
 <BusinessAddresses>
  <xsl:apply-templates/>
 </BusinessAddresses>
</xsl:template>

<xsl:template match="Addresses/Address">
 <BusinessAddress>
  <xsl:value-of select="."/>
 </BusinessAddress>
</xsl:template>

</xsl:stylesheet>      

【讨论】:

以上是关于使用 XSLT 重命名节点的主要内容,如果未能解决你的问题,请参考以下文章

重命名元素的 XSLT 问题——更改命名空间

XSLT 用属性值重命名元素

从 C# 代码重命名 XSLT 属性值

[重命名应用程序中的树节点时重命名文件夹名称

如何在 WinForms 中禁用 TreeView 的节点重命名?

节点:尝试重命名目录中的每个文件会跳过一些