XSLT 用多个元素替换一个元素

XSLT replacing one elements with multiple elements

我想用以下多个元素替换 XML(大)中的一个元素:

原文XML:

<root>
  <cr>
   <id>1</id>
   <release>A</release>
  </cr>
  <cr>
   <id>2</id>
   <release>B</release>
  </cr>
</root>

我希望输出为:

<root>
  <cr>
   <id>1</id>
   <release>Aa</release>
   <release>Ab</release>
   <release>Ad</release>
  </cr>
  <cr>
   <id>2</id>
   <release>Bd</release>
   <release>Be</release>
  </cr>
</root>

原理是,每当有//release[text()='A'],替换上面三个元素,每当有//release[text()='B' ], 将元素替换为上面的两个等。如果发布 text() 是 "C" 或 "D" 或其他值,它们将保持相同的值。

我的尝试:

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

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> 
<xsl:strip-space elements="*"/>

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

<xsl:template match="//cr/release/text()">
    <xsl:if test=".='A'">
        <xsl:value-of select="Aa"/>
    </xsl:if>
</xsl:template>
</xsl:stylesheet>

它可以用一个 - >一个替换,但是如何做多个?非常感谢,

这样试试:

XSLT 1.0

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

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

<xsl:template match="release[.='A']">
    <release>Aa</release>
    <release>Ab</release>
    <release>Ad</release>
</xsl:template>

<xsl:template match="release[.='B']">
    <release>Bd</release>
    <release>Be</release>
</xsl:template>

</xsl:stylesheet>

测试输入

<root>
  <cr>
   <id>1</id>
   <release>A</release>
  </cr>
  <cr>
   <id>2</id>
   <release>B</release>
  </cr>  
  <cr>
   <id>3</id>
   <release>C</release>
  </cr>
</root>

结果

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <cr>
      <id>1</id>
      <release>Aa</release>
      <release>Ab</release>
      <release>Ad</release>
   </cr>
   <cr>
      <id>2</id>
      <release>Bd</release>
      <release>Be</release>
   </cr>
   <cr>
      <id>3</id>
      <release>C</release>
   </cr>
</root>

这是一个完全通用且简短的 XSLT 1.0 解决方案。它使用 映射 xml 文件指定每个需要的版本的替换:

(请在此答案的末尾找到 ultimately-generic 和用 XSLT 2.0 编写的简短解决方案。)

<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:variable name="vMap" select="document('mapping.xml')/*/*"/>

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

  <xsl:template match="release[. = document('mapping.xml')/*/release/@old]">
    <xsl:copy-of select="$vMap[@old = current()]/*"/>
  </xsl:template>
</xsl:stylesheet>

如果文件 mapping.xml 与转换(.xsl 文件)位于同一目录中,并且是:

<map>
    <release old="A">
        <release>Aa</release>
        <release>Ab</release>
        <release>Ad</release>
    </release>
    <release old="B">
        <release>Ba</release>
        <release>Bb</release>
        <release>Bd</release>
    </release>
</map>

然后当转换应用于此 XML 文档时:

<root>
  <cr>
   <id>1</id>
   <release>A</release>
  </cr>
  <cr>
   <id>2</id>
   <release>B</release>
  </cr>
  <cr>
   <id>3</id>
   <release>C</release>
  </cr>
  <cr>
   <id>4</id>
   <release>D</release>
  </cr>
</root>

产生了想要的、正确的结果:

<root>
   <cr>
      <id>1</id>
      <release>Aa</release>
      <release>Ab</release>
      <release>Ad</release>
   </cr>
   <cr>
      <id>2</id>
      <release>Ba</release>
      <release>Bb</release>
      <release>Bd</release>
   </cr>
   <cr>
      <id>3</id>
      <release>C</release>
   </cr>
   <cr>
      <id>4</id>
      <release>D</release>
   </cr>
</root>

注意:许多不同名称的元素可以用相同的转换替换。

如果我们有这个映射文件:

<map>
    <release old="A">
        <release>Aa</release>
        <release>Ab</release>
        <release>Ad</release>
    </release>
    <release old="B">
        <release>Ba</release>
        <release>Bb</release>
        <release>Bd</release>
    </release>
    <history old="p">
        <history>Pp</history>
        <history>Pq</history>
        <history>Pr</history>
    </history>
</map>

和此来源 XML 文档:

<root>
  <cr>
   <id>1</id>
   <history>p</history>
   <release>A</release>
  </cr>
  <cr>
   <id>2</id>
   <history>q</history>
   <release>B</release>
  </cr>
  <cr>
   <id>3</id>
   <history>r</history>
   <release>C</release>
  </cr>
  <cr>
   <id>4</id>
   <history>t</history>
   <release>D</release>
  </cr>
</root>

然后这个变换:

<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:variable name="vMap" select="document('mapping.xml')/*/*"/>

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

  <xsl:template match="release[. = document('mapping.xml')/*/release/@old]">
    <xsl:copy-of select="$vMap[@old = current()]/*"/>
  </xsl:template>
  <xsl:template match="history[. = document('mapping.xml')/*/history/@old]">
    <xsl:copy-of select="$vMap[@old = current()]/*"/>
  </xsl:template>
</xsl:stylesheet>

当应用于上述 XML 文档时,会产生想要的结果 -- 其中不止一个 differently-named 元素被映射和替换:

<root>
   <cr>
      <id>1</id>
      <history>Pp</history>
      <history>Pq</history>
      <history>Pr</history>
      <release>Aa</release>
      <release>Ab</release>
      <release>Ad</release>
   </cr>
   <cr>
      <id>2</id>
      <history>q</history>
      <release>Ba</release>
      <release>Bb</release>
      <release>Bd</release>
   </cr>
   <cr>
      <id>3</id>
      <history>r</history>
      <release>C</release>
   </cr>
   <cr>
      <id>4</id>
      <history>t</history>
      <release>D</release>
   </cr>
</root>

最后:可以用 XSLT 2.0 编写更短更通用的转换:

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

 <xsl:variable name="vMap" select="document('mapping.xml')/*/*"/>

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

  <xsl:template match=
   "*[. = $vMap[name() eq name(current())]/@old]">
    <xsl:copy-of select="$vMap[name() eq name(current()) and @old eq current()]/*"/>
  </xsl:template>
</xsl:stylesheet>

请注意:此转换中没有硬编码元素名称!

一个更大的优势:我们可以修改转换,所以我们可以在调用转换时将映射文档的 URI 作为全局参数传递——这样我们就可以拥有一个单一的通用转换,适用于任何未知的预先映射。

只有这些是必需的更改:

 <xsl:param name="pmapUrl" select="'file:///c:/temp/mapping.xml'"/>

 <xsl:variable name="vMap" select="document($pmapUrl)/*/*"/>

完整的转换(全局参数pmapUrl可以而且通常会由转换的调用者动态指定):

<xsl:stylesheet version="2.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="pmapUrl" select="'file:///c:/temp/mapping.xml'"/>

 <xsl:variable name="vMap" select="document($pmapUrl)/*/*"/>

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

  <xsl:template match=
   "*[for $name in name() 
        return
          . = $vMap[name() eq $name]/@old]">
    <xsl:copy-of select="$vMap[@old = current()]/*"/>
  </xsl:template>
</xsl:stylesheet>