使用 XSLT 从一个 "template" XML 和另一个 "arguments" XML 生成文本的通用方法

Generic way to use XSLT to generate text from one "template" XML and another "arguments" XML

我有下面问题的解决方案,但我需要它更通用,所以我需要帮助。

问题:

我们有一个 "template.xml",它由 <verbatim> 元素和 <argument id='foo'/> 元素混合组成,它们可能嵌套也可能不嵌套在 <part id='part001'> 元素中。 (因此逐字和参数可以在根元素中,也可以在 <part> ... </part> 个元素中)。

示例"template.xml":

<template_root>
  <verbatim>Title</verbatim>
  <part id='part001'>
    <verbatim>Nested in part 1.</verbatim>
    <argument id='keywords'>ARGKEY1, ARGKEY2, ARGKEY3</argument>
    <verbatim>End of part 1.</verbatim>
  </part>
  <part id='DoNotUse'>
    <verbatim>This should not be in the output</verbatim>
  </part>
  <verbatim>End of article</verbatim>
</template_root>

我们有另一个文件,"instance.xml",它由“”元素组成(参考template.xml中的) again 可能嵌套也可能不嵌套在与上面相同的“”元素中。

示例"instance.xml":

<instance_root template='template.xml'>
  <part id='part001'>
    <argument id="keywords" enabled='true'/>
  </part>
</instance_root>

我们想要一个 XSLT,它从 "instance.xml" 中读取以下信息并生成

  1. 读入模板文件名(例如"template.xml")然后
  2. 对于 "template.xml" 中的所有元素:
    1. 按原样复制 template.xml 的 <template_root> 中所有 <verbatim> 的值。
    2. 复制 template.xml 的 <template_root> 中所有 <argument> 的值,仅当它也在 "instance.xml" 的 <instance_root> 中以相同的 @id 和属性 @enabled='true'.
    3. 对在 "template.xml" 中找到的每个 <part> 的所有 children 执行与上面的 (21) 和 (22) 相同的操作,并且仅当 <part> 具有同样的 @id 也出现在 "instance.xml" 中,忽略 "template.xml".
    4. 中的所有其他 <part>

所以我们不复制的是: 1. "template.xml" 中的所有 <argument> 在 "instance.xml" 中没有对应的 <argument>。 2. "template.xml" 中的所有 <part> 在 "instance.xml".

中没有对应的 <part>

示例文本输出:

Title
Nested in part 1.
ARGKEY1, ARGKEY2, ARGKEY3
End of part 1.
End of article

我正在使用 xsltproc,想知道执行此操作的通用方法(或您能想到的最佳方法)是什么? "generic" 我的意思是我真的不想对复杂的 XPath 进行硬编码或引用单个元素(使用 [1]、[2] 等)。

我希望这个解决方案已经足够通用了。看看下面几行:

扩展 template.xml:

<template_root>
    <verbatim>Title</verbatim>
    <argument id="key">ARGKEY0</argument>
    <part id="part001">
        <verbatim>Nested in part 1.</verbatim>
        <argument id="keywords">ARGKEY1, ARGKEY2, ARGKEY3</argument>
        <verbatim>End of part 1.</verbatim>
    </part>
    <part id="part002">
        <verbatim>Nested in part 2.</verbatim>
        <argument id="keywords">ARGKEY4, ARGKEY5, ARGKEY6</argument>
        <verbatim>End of part 2.</verbatim>
    </part>
    <part id="part003">
        <verbatim>Nested in part 3.</verbatim>
        <argument id="keywords">ARGKEY7, ARGKEY8, ARGKEY9</argument>
        <verbatim>End of part 3.</verbatim>
    </part>
    <part id="DoNotUse">
        <verbatim>This should not be in the output</verbatim>
    </part>
    <argument id="word">ARGKEY10</argument>
    <verbatim>End of article</verbatim>
</template_root>

扩展 instance.xml:

<instance_root template='template.xml'>
    <part id='part001'>
        <argument id="keywords" enabled='true'/>
    </part>
    <part id="part002">
        <argument id="keywords" enabled="false"/>
    </part>
    <part id="part003">
        <argument id="keywords" enabled="true"/>
    </part>
    <argument id="key" enabled='false'/>
    <argument id="word" enabled='true'/>
</instance_root>

样式表:

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

    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:variable name="instance" select="document('instance.xml')/*"/>

    <xsl:template match="template_root">
        <xsl:copy>
            <xsl:apply-templates select="
                verbatim
                | argument[@id = $instance/argument[@enabled = 'true']/@id]
                | part[@id = $instance/part/@id]"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="part">
        <xsl:copy>
            <xsl:apply-templates select="
                verbatim
                | argument[@id = $instance/part[@id = current()/@id]/argument[@enabled = 'true']/@id]
                "/>
        </xsl:copy>
    </xsl:template>

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

[提示:无法使用模板匹配,因为无法在模板匹配中使用变量。]

结果为XML:

<?xml version="1.0" encoding="UTF-8"?>
<template_root>
  <verbatim>Title</verbatim>
  <part>
    <verbatim>Nested in part 1.</verbatim>
    <argument id="keywords">ARGKEY1, ARGKEY2, ARGKEY3</argument>
    <verbatim>End of part 1.</verbatim>
  </part>
  <part>
    <verbatim>Nested in part 2.</verbatim>
    <verbatim>End of part 2.</verbatim>
  </part>
  <part>
    <verbatim>Nested in part 3.</verbatim>
    <argument id="keywords">ARGKEY7, ARGKEY8, ARGKEY9</argument>
    <verbatim>End of part 3.</verbatim>
  </part>
  <argument id="word">ARGKEY10</argument>
  <verbatim>End of article</verbatim>
</template_root>

结果为文本

Title
Nested in part 1.
ARGKEY1, ARGKEY2, ARGKEY3
End of part 1.
Nested in part 2.
End of part 2.
Nested in part 3.
ARGKEY7, ARGKEY8, ARGKEY9
End of part 3.
ARGKEY10
End of article

你必须改变一些小东西,才能达到你的精确输出! Change/Add 以下:

<xsl:output method="text" encoding="UTF-8"/>

<xsl:template match="text()" priority="1.1">
    <xsl:value-of select="concat(., '&#10;')"/>
</xsl:template>

最重要也可能是缺点:这不适用于 //part/part! [无法检查 ancestor::part 是否全部匹配正确 @id]

感谢@uL1 的回复和更多的调整,下面是一个非常好的解决方案,可以正确完成工作。

唯一缺少的功能(我在原来的问题中确实没有问过,但我真的很想添加)是能够处理任意数量的嵌套 <part> 元素。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text" encoding="UTF-8" indent="yes"/>
  <xsl:variable name="template" select="document(//@template)"/>
  <xsl:variable name="instance" select="/"/>

  <xsl:template match="text()" priority="1.1">
    <xsl:value-of select="concat(., '&#10;')"/>
  </xsl:template>

  <xsl:template match="/"> <!-- Using match="instance_root" would work just as well. -->
    <xsl:apply-templates select="$template/*"/>
  </xsl:template>

  <xsl:template match="template_root">
    <xsl:copy>
        <xsl:apply-templates select="
            verbatim
            | argument[@id = $instance//argument[@enabled = 'true']/@id]
            | part[@id = $instance//part/@id]"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="part">
    <xsl:copy>
        <xsl:apply-templates select="
            verbatim
            | argument[@id = $instance//part[@id = current()/@id]/argument[@enabled = 'true']/@id]"/>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

填空是著名的XSLT设计模式。

查看this answer了解详细解释和实际代码。