XSL 风格:在 XSLT 中使用 <xsl:for-each select> 或 <xsl:template match> 或其他解决方案?

Posted

技术标签:

【中文标题】XSL 风格:在 XSLT 中使用 <xsl:for-each select> 或 <xsl:template match> 或其他解决方案?【英文标题】:XSL style: using <xsl:for-each select> or <xsl:template match> or other solutions in XSLT? 【发布时间】:2021-12-30 03:11:00 【问题描述】:

我正在学习使用 XSL 将 XML 解析为 html/XHTML。

XLST &lt;xsl:for-each&gt; 元素是允许循环的语言的核心元素。然而,这里和其他地方的帖子建议使用这对于初学者来说很常见(我就是!)而且风格很差。

我的问题是:&lt;xsl:for-each&gt; 循环有哪些更好的选择(如更高效/优雅/更好的风格),为什么?

在下面的示例中,我使用嵌套的&lt;xsl:for-each&gt;&lt;xsl:choose&gt; 元素通过条件&lt;xsl:when&gt; 测试循环遍历所需的节点。这可以正常工作并选择我需要的节点,但感觉相当笨重......

您的智慧和见解将不胜感激!

我的示例 XML 是由斯坦福 HIVdb 数据库查询生成的报告: https://hivdb.stanford.edu/hivdb/by-sequences/

XSD 架构在这里: https://hivdb.stanford.edu/DR/schema/sierra.xsd

我的示例 XML 报告在这里: https://github.com/delfair/xml_examples/blob/main/Resistance_1636677016671.xml

我的 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="/">

<html>
<head>
    <title>Example Report</title>
</head>
<body>

<h3>Significant mutations</h3>

<xsl:for-each select=".//geneData">
    <xsl:choose>
        <xsl:when test="gene='HIV1PR'">
        Protease inhibitor mutations<br/><br/>
        </xsl:when>
        <xsl:when test="gene='HIV1RT'">
        Reverse transcriptase inhibitor mutations<br/><br/>
        </xsl:when>
        <xsl:when test="gene='HIV1IN'">
        Integrase inhibitor mutations<br/><br/>
        </xsl:when>
    </xsl:choose>
<table>
<xsl:for-each select=".//mutation">
    <xsl:choose>
        <xsl:when test="classification='PI_MAJOR' or classification='PI_MINOR' or classification='NRTI' or classification='NNRTI' or classification='INI_MAJOR' or classification='INI_MINOR'">
        <tr>
        <td>Class</td>
        <td>Mutation</td>
        </tr>
        <tr>
            <td><xsl:value-of select="classification"/></td>
            <td><xsl:value-of select="mutationString"/></td>
        </tr>
        </xsl:when>
    </xsl:choose>
</xsl:for-each>
</table><br/>
</xsl:for-each>

</body>
</html>

</xsl:template>
</xsl:stylesheet>

生成的 HTML:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Example Report</title>
</head>
<body>
<h3>Significant mutations</h3>
Protease inhibitor mutations<br><br><table></table>
<br>
Reverse transcriptase inhibitor mutations<br><br><table>
<tr>
<td>Class</td>
<td>Mutation</td>
</tr>
<tr>
<td>NNRTI</td>
<td>K103N</td>
</tr>
</table>
<br>
Integrase inhibitor mutations<br><br><table></table>
<br>
</body>
</html>

【问题讨论】:

【参考方案1】:

我猜你所说的“高级风格”是指使用模板(进行模式匹配)而不是xsl:for-each“循环”。

您的代码的核心功能可以转换如下:

...
      <h3>Significant mutations</h3>

      <xsl:apply-templates select=".//geneData" />
      <table>
        <xsl:apply-templates select=".//mutation" />
      </table><br/>
    </body>
  </html>
</xsl:template>
<!-- End of main template matching "/" -->

<xsl:template match="geneData[gene='HIV1PR']">Protease inhibitor mutations<br/><br/></xsl:template>
<xsl:template match="geneData[gene='HIV1RT']">Reverse transcriptase inhibitor mutations<br/><br/></xsl:template>
<xsl:template match="geneData[gene='HIV1IN']">Integrase inhibitor mutations<br/><br/></xsl:template>
<xsl:template match="geneData" />   <!-- Discard all not matched 'geneData' elements -->    

<xsl:template match="mutation[classification='PI_MAJOR' or classification='PI_MINOR' or classification='NRTI' or classification='NNRTI' or classification='INI_MAJOR' or classification='INI_MINOR']">
        <tr>
            <td>Class</td>
            <td>Mutation</td>
        </tr>
        <tr>
            <td><xsl:value-of select="classification"/></td>
            <td><xsl:value-of select="mutationString"/></td>
        </tr>
</xsl:template>
<xsl:template match="mutation" />   <!-- Discard all not matched 'mutation' elements -->    

</xsl:stylesheet>

在上面的代码中,两组节点(.//geneData.//mutation)都被传递给xsl:apply-templatesxsl:apply-templates 将生成的节点传递给所有模板。那些匹配的人将被处决。因此,简短的 xsl:template 带有谓词(match="..."[...] 部分)替换代码的 xsl:whens。

这应该是 XSLT 开发的“标准”方法。在实践中,xsl:for-each 可能更适合代码清晰的用例,但通常这两种方法是可以互换的。

【讨论】:

模板优于 for-each 的最大原因之一是可扩展性。您可以导入给定的样式表并定义具有相同匹配标准的新模板,该匹配标准将覆盖原始模板行为。认为类似于扩展 java 类并覆盖继承的函数。提取 for-each 逻辑并移入模板类似于将工作单元重构为实用函数,然后如果您需要推理它的作用,并且无需复制/粘贴即可更轻松地自定义和覆盖较小的工作单元其余不变。 这真的很有用 - 谢谢。 @zx485 您的 好点。我没有测试过代码。问题是所有&lt;mutation&gt;元素都被xsl:apply-templates传递,但不是所有都匹配。不匹配的由 XSLT 的default templates 处理。解决方案很简单:像&lt;xsl:template match="mutation" /&gt; 这样为超集添加一个空模板。这将匹配并丢弃所有与更具体的模板规则不匹配的&lt;mutation&gt; 元素。 太好了 - 非常感谢。在这里短时间内学到了很多东西...【参考方案2】:

首先,xsl:for-each 不用于“循环”。它没有退出条件,也无法将一次迭代的结果传递给另一次迭代。

接下来,使用xsl:for-each 不仅限于初学者,也不是“糟糕的风格”——尽管您可能在这里或其他任何地方读过。

xsl:for-each 指令只不过是在特殊情况下使用的快捷方式。一般方法分为两个阶段:

首先,您选择一组节点并告诉处理器将模板应用于它们;

接下来,处理器找到与所选节点集中每个节点最匹配的模板并应用它。

如果 (1) 您希望对选定集中的所有节点应用统一处理,并且 (2) 不需要递归应用模板或重新使用它,您可以简单地告诉处理器:获取这些节点并将此模板应用于它们。

这正是xsl:for-each 指令的作用。它有一个select 属性来选择要处理的节点,它的内容是一个模板,以应用于所选的节点集。不多也不少。这里没有范式转变。没有“推式”与“拉式”。没有善恶之分。

xsl:for-each 的唯一问题在于它是样式表作者工具箱中的唯一工具。正如我所说,它是一种可以在特殊情况下使用的捷径。当这些情况不适用时,使用它会导致代码非常糟糕。

【讨论】:

啊循环描述来自w3schools.com/xml/xsl_for_each.asp 所以不是我的! 感谢您的回复 - 改进我的代码是目标 @DerekFairley W3Schools 只是一个教程站点,而不是参考。尽管有他们的名字,但他们与万维网联盟 (W3C) 没有任何关联。唯一的规范性参考是 XSLT 规范,例如 w3.org/TR/1999/REC-xslt-19991116。

以上是关于XSL 风格:在 XSLT 中使用 <xsl:for-each select> 或 <xsl:template match> 或其他解决方案?的主要内容,如果未能解决你的问题,请参考以下文章

在 XSLT 中动态包含其他 XSL 文件

在 XSLT 2.0 中定义变量时使用 xsl:copy-of

我们可以在 xslt 的 select 语句中使用动态变量名吗?

XSLT 中的 XSL 排序

在 XSLT 中检查字符串是不是为空或空

XSLT/XSL 递归嵌套元素