XSLT文档中的模板以什么顺序执行,它们在源XML或缓冲的输出上是否匹配?


73

这总是让我对XSLT感到困惑:

  1. 模板以什么顺序执行,以及
  2. 当它们执行时,它们在(a)原始源XML或(b)到该点的XSLT当前输出上是否匹配?

例:

<person>
  <firstName>Deane</firstName>
  <lastName>Barker</lastName>
</person>

这是XSLT的片段:

<!-- Template #1 -->
<xsl:template match="/">
  <xsl:value-of select="firstName"/> <xsl:value-of select="lastName"/>
</xsl:template>

<!-- Template #2 -->
<xsl:template match="/person/firstName">
  First Name: <xsl:value-of select="firstName"/>
</xsl:template>

关于此的两个问题:

  1. 我假设模板#1将首先执行。我不知道为什么要这么做-仅仅是因为它首先出现在文档中吗?
  2. 模板2将执行吗?它与源XML中的一个节点匹配,但是当我们到达此模板时(假设它第二次运行),“ firstName”节点将不在输出树中。

那么,“较新的”模板是否会像“较早的”模板中发生的一样,还是它们在源文档上操作,而忽略了已转换为“更早”的模板呢?(所有这些词都用引号引起来,因为当我真的不知道如何首先确定模板顺序时,我很难讨论基于时间的问题。)

在上面的示例中,我们有一个模板在根节点(“ /”)上匹配,该模板在完成执行后实际上已从输出中删除了所有节点。在这种情况下,由于第一个模板完成后没有匹配的内容,这会阻止其他所有模板执行吗?

至此,我一直在担心以后的模板不执行,因为它们所操作的节点未出现在输出中,但是相反呢?“较早的”模板可以创建一个节点,“较晚的”模板可以执行此操作吗?

在与上述相同的XML上,考虑以下XSL:

<!-- Template #1 -->
<xsl:template match="/">
  <fullName>
    <xsl:value-of select="firstName"/> <xsl:value-of select="lastName"/>
  </fullName>
</xsl:template>

<!-- Template #2 -->
<xsl:template match="//fullName">
  Full Name: <xsl:value-of select="."/>
</xsl:template>

模板#1创建一个名为“ fullName”的新节点。模板#2在同一节点上匹配。模板2将执行,因为到模板2时输出中已经存在“ fullName”节点吗?

我意识到我对XSLT的“禅宗”一无所知。到目前为止,我的样式表由与根节点匹配的模板组成,然后从那里开始是完全过程性的。我厌倦了这样做。我宁愿实际上正确地理解XSLT,因此是我的问题。


您已经在示例xml中的第二个人标记中错过了/。
克里斯R

3
我学到的“禅”-XSLT的执行过程是以XML为中心,而不是以XSL为中心。 XML的结构驱动流程,而不是XSL的结构。 这是我这些年来都不了解的重要作品之一。
Deane

1
match =“ // fullName”与match =“ fullName”相同。甲图案测试给定节点是否从任何上下文匹配它,而不是一个XPath表达式,其选择从节点特定上下文。
Evan Lenz

Answers:


91

我爱你的问题。您对尚未理解的内容非常清楚。您只需要一些东西就可以将它们绑在一起。我的建议是您阅读“ XSLT的工作原理”,这是我写的一章,旨在确切地解决您所提出的问题。我很想听听它是否为您将事物联系在一起。

非正式地讲,我将回答您的每个问题。

  1. 模板以什么顺序执行,以及
  2. 当它们执行时,它们在(a)原始源XML或(b)到该点的XSLT当前输出上是否匹配?

从某种意义上讲,在XSLT处理中的任何给定点上,都有两个上下文,您将它们标识为(a)和(b):您在源树中的位置,以及您在结果树中的位置。您在源代码树中的位置称为当前节点。当您选择要使用XPath处理的任意节点集时,它可以在源树周围进行更改和跳转。但是,从概念上讲,您永远不会以相同的方式“跳过”结果树。XSLT处理器以有序的方式构造它。首先,它创建结果树的根节点。然后添加子项,以文档顺序构建结果(深度优先)。[您的帖子激发我再次为XSLT实验获得软件可视化效果...]

样式表中模板规则的顺序永远无关紧要。您仅通过查看样式表就无法确定模板规则将以什么顺序实例化,规则将被实例化多少次甚至根本无法实例化。(match="/"是一个例外;您始终可以知道它将被触发。)

我假设模板#1将首先执行。我不知道为什么要这么做-仅仅是因为它首先出现在文档中吗?

不。即使将其放在文档的最后,也将首先调用它。模板规则顺序从不重要(除非在错误条件下,当您有多个具有相同优先级且与同一节点匹配的模板规则时,即使在错误情况下,该规则对于实现者也是可选的,并且您永远不应依赖这种行为)。之所以首先调用它,是因为每次运行XSLT处理器时总是发生的第一件事就是对的虚拟调用<xsl:apply-templates select="/"/> 。一个虚拟调用将构建整个结果树。外面什么都没有发生。您可以通过定义模板规则来自定义或“配置”该指令的行为。

模板2将执行吗?它与源XML中的一个节点匹配,但是当我们到达此模板时(假设它第二次运行),“ firstName”节点将不在输出树中。

除非您<xsl:apply-templates/>match="/"规则中的某处有调用,否则模板2(以及其他任何模板规则)将永远不会触发。如果没有,则不会match="/"触发模板规则。这样想:要触发模板规则,它不能只匹配输入中的节点。它必须匹配您选择要处理的节点(使用<xsl:apply-templates/>)。相反,它将继续与您选择的处理节点的次数匹配。

match="/" 因为在第一个模板完成后没有匹配的内容,[模板]会阻止其他所有模板执行吗?

该规则优先于其他规则<xsl:apply-templates/>。仍然有很多的节点的在源代码树进行处理。他们总是在那里,采摘时机已经成熟。您可以根据需要多次处理每个。但是使用模板规则处理它们的唯一方法是调用<xsl:apply-templates/>

至此,我一直在担心以后的模板不执行,因为它们所操作的节点未出现在输出中,但是相反呢?“较早的”模板可以创建一个节点,“较晚的”模板可以执行此操作吗?

不是说“较早的”模板创建了一个要处理的新节点;而是 而是“早期”模板使用相同的指令(<xsl:apply-templates)反过来处理源树中的更多节点。您可以将其视为递归调用相同的“函数”,每次使用不同的参数(要处理的节点由上下文和select属性确定)。

最后,您得到的是对同一“函数”(<xsl:apply-templates>)进行递归调用的树结构堆栈。而且这种树结构与您的实际结果同构。并非每个人都意识到这一点或以这种方式考虑过;那是因为我们还没有任何有效的可视化工具...。

模板#1创建一个名为“ fullName”的新节点。模板#2在同一节点上匹配。模板2将执行,因为到模板2时输出中已经存在“ fullName”节点吗?

不。进行处理链的唯一方法是以这种方式显式设置它。创建一个$tempTree包含新<fullName>元素的变量,例如,然后像这样处理<xsl:apply-templates select="$tempTree">。为此,您需要在XSLT 1.0中使用扩展功能(例如exsl:node-set())包装变量引用,但是在XSLT 2.0中,它可以照原样工作。

无论是从原始源树还是在构造的临时树中处理节点,都需要明确说明要处理的节点。

我们还没有介绍XSLT如何获得其所有隐式行为。您还必须了解内置模板规则。我一直在编写样式表,甚至没有为根节点提供明确的规则(match="/")。相反,我依赖于根节点的内置规则(将模板应用于子代),这与元素节点的内置规则相同。因此,我可以忽略输入的大部分内容,让XSLT处理器自动遍历它,只有当它遇到我感兴趣的节点时,我才会做一些特别的事情。或者,我可以编写一条规则来递归地复制所有内容(称为身份转换),仅在必要时覆盖它,以对输入进行增量更改。阅读“ XSLT的工作原理”之后,您的下一个任务是查找“身份转换”。

我意识到我对XSLT的“禅宗”一无所知。到目前为止,我的样式表由与根节点匹配的模板组成,然后从那里开始是完全过程性的。我厌倦了这样做。我宁愿实际上正确地理解XSLT,因此是我的问题。

我为你鼓掌。现在是时候服用“红色药丸”了:阅读“ XSLT的工作原理”


顺便说一句,您尝试执行的操作(编写模板规则以处理由其他模板规则创建的节点)实际上很酷,但是正如我所说的,除非您使用变量和变量引用显式设置管道,否则该方法将不起作用。我有时想知道如何创建一个处理上下文,在该上下文中,XSLT处理器将在结果上反复调用,直到所有元素(例如,特定的宏命名空间中)都得到处理并且不再出现在结果中为止-就像递归一样包容机制。那会很方便。XSLT中的管道实际上在语法上有点笨拙。
伊万·伦兹

埃文,您的答案和示例章节确实很棒。我想我对此有所了解,只保留一个问题,我将单独发布。
Deane

6
来自@EvanLenz的精彩文章!非常感谢。
GuruM 2012年

出色的答案,确实帮助了我的理解。我同意可视化工具将提供巨大的帮助-特别是能够准确地查看哪些操作员选择源中的内容,在输出中生成内容以及如何管理当前节点和上下文。
chrispitude

近年来,我确实在可视化工具方面取得了一些进展:github.com/evanlenz/xslt-visualizer
Evan

6

模板始终在源XML中匹配。因此,顺序并不重要,除非两个或更多模板匹配相同的节点。在那种情况下,有点违反直觉,将触发带有最后一个匹配模板的规则。


2
我的理解是,如果2个或更多模板与同一个节点匹配,则将运行最匹配的模板。我认为您的意思是2个或多个完全相同的匹配条件?
克里斯R

2
sorta,规范中给出了规则:w3.org/TR/xslt#conflict。除非您使用优先级属性,否则许多模式最终都会具有相同的优先级。不过请注意规范中该部分的最后一段:“ XSLT处理器可能会发出错误信号”。
米罗德

我不知道关于相同优先级的规则,它会根据处理器的实现给出错误或使用最后一个优先级,或者您可以使用属性自行设置优先级,谢谢。
克里斯R

2

在您的第一个示例中,模板#1之所以运行,是因为当您开始处理输入xml时,它是从根开始的,这是样式表中唯一与根元素匹配的模板。即使它在样式表中为第二,它仍将运行第一。

在此示例中,模板2将不会运行,因为您已经使用模板1处理了根元素,并且在根之后没有其他要处理的元素。如果确实要使用其他模板处理其他元素,则应将其更改为。

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

然后,这使您可以为感兴趣的每个元素定义一个模板,并以更逻辑的方式处理xml,而不是按过程进行处理。

还要注意,此示例将不输出任何内容,因为在当前上下文(根)中没有firstName元素,只有person元素,因此应为:

<xsl:template match="/">
  <xsl:value-of select="person/firstName"/> <xsl:value-of select="person/lastName"/>
</xsl:template>

我发现更容易想到您正在逐步执行xml,从根目录开始,然后寻找与该元素匹配的模板,然后按照这些说明生成输出。XSLT将输入文档转换为输出,因此在转换开始时输出文档为空。输出不用作转换的一部分,而只是转换的输出。

在您的第二个示例中,模板#2将不会执行,因为模板是针对输入xml而非输出运行的。


0

埃文的答案基本上是一个不错的答案。

但是,似乎缺少的一件事是无需执行任何匹配就可以“调用”代码块的能力。至少在某些人看来,这将使结构更好。

为了说明我的意思,我举了一个小例子。

<xsl:template match="/" name="dotable">
<!-- Surely the common html part could be placed somewhere else -->
    <!-- the head and the opening body -->
<html>
<head><title>Salary table details</title></head>

    <body>
<!-- Comments are better than nothing -->
    <!-- but that part should really have been somewhere else ... -->

<!-- Now do what we really want here ... this really is making the table! -->

<h1>Salary Table</h1>
<table border = "3" width="80%">
<xsl:for-each select="//entry">
    <tr>
        <td><xsl:value-of select="name" /></td>
        <td><xsl:value-of select="firstname" /></td>
        <td><xsl:value-of select="age" /></td>
        <td><xsl:value-of select="salary" /></td>
    </tr>
</xsl:for-each>
</table>

<!-- Now close out the html -->
</body>
</html>
<!-- this should also really be somewhere else -->

<!-- This approach works, but leads to horribly monolithic code -->
    <!-- Further - it leads to templates including code which is strictly -->
    <!-- not relevant to them. I've not found a way round this yet -->
</xsl:template>

但是,经过一番摆弄之后,首先利用了以下提示:如果有两个匹配的模板,那么将选择代码中的最后一个,然后重组我的代码(此处未全部显示),我实现了这一点工作,并希望生成正确的代码,以及显示所需的数据-

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- <?xml version="1.0"?>-->

<xsl:template name="dohtml">
  <html>
      <xsl:call-template name="dohead" />
      <xsl:call-template name="dobody" />
  </html>
</xsl:template>

<xsl:template name="dohead">
<head>
    <title>Salary details</title>
</head>
</xsl:template>

<xsl:template name="dobody">
<body>
    <xsl:call-template name="dotable" />
</body>
</xsl:template>

<xsl:template match="/entries" name="dotable">

<h1>Salary Table</h1>
<table border = "3" width="80%">
<xsl:for-each select="//entry">
    <tr>
        <td><xsl:value-of select="name" /></td>
        <td><xsl:value-of select="firstname" /></td>
        <td><xsl:value-of select="age" /></td>
        <td><xsl:value-of select="salary" /></td>
    </tr>
</xsl:for-each>
</table>

</xsl:template>

<xsl:template  match="/" name="main">
            <xsl:call-template name="dohtml" />
</xsl:template> 

[如果看不到全部,请上下滚动代码]

主要模板始终匹配的工作方式-/上的匹配

它具有称为代码的代码块-模板。

现在,这意味着不可能在/上匹配另一个模板,但是可以在命名节点上显式匹配,在这种情况下,命名节点是xml中的最高级别节点-称为条目。

对代码进行小的修改就产生了上面给出的示例。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.