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

关于 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会因为“ fullName”节点在我们到达模板 # 2的时候已经存在于输出中而执行吗?

我意识到我对 XSLT 的“禅”一无所知。到目前为止,我的样式表已经包含了一个与根节点匹配的模板,然后完全是过程性的。我受够了。我更愿意正确地理解 XSLT,因此我提出了这个问题。

28438 次浏览

模板 一直都是在源 XML 中匹配。所以顺序并不重要,除非2个或多个模板匹配相同的节点。在这种情况下,有点违反直觉的是,使用 最后匹配模板的规则被触发。

在第一个示例中运行 Template # 1,因为当您开始处理输入 xml 时,它从根开始,并且这是样式表中与根元素匹配的唯一模板。即使它是样式表中的第2个,它也会运行在第1个。

在这个示例中,模板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个示例中,模板 # 2不会执行,因为模板是针对输入 xml 而不是输出运行的。

我喜欢你的问题。你对自己还不明白的事情表达得很清楚。你只是需要一些东西把事情联系起来。我的建议是你阅读 “ XSLT 如何工作”,这是我写的一章,用来解决你正在问的问题。我很想听听你是否能把事情联系起来。

不那么正式的话,我会尝试回答你们每个人的问题。

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

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

样式表中模板规则的顺序从不重要。仅仅通过查看样式表,您无法判断模板规则将以何种顺序被实例化,一个规则将被实例化多少次,甚至根本无法判断它是否将被实例化。(match="/"是一个例外; 您总是知道它会被触发。)

我假设模板 # 1会 我不知道为什么我 假设这个,是不是因为 首先出现在文件中?

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

模板 # 2会执行吗? 它匹配源 XML 中的一个节点,但是 等我们到了这里 Template (假设它第二个运行) , “ firstName”节点将不在 输出树。

模板 # 2(或任何其他模板规则)将永远不会被触发,除非您在 match="/"规则的某个地方有一个 <xsl:apply-templates/>调用。如果您没有任何模板规则,那么除了 match="/"之外就不会触发其他模板规则。可以这样想: 要触发模板规则,它不能只匹配输入中的节点。它必须匹配您选择到 程序的节点(使用 <xsl:apply-templates/>)。相反,它将继续匹配节点,只要您选择处理它。

如果 match="/" [ template ]预先占用所有其他模板 从执行,因为没有什么 在第一个模板后进行匹配 完成了吗?

这条规则优先于其他任何地方,包括 <xsl:apply-templates/>在内。在源树中仍然有大量的 可以节点需要处理。它们总是在那里,等着你去采摘; 你想处理多少次就处理多少次。但是使用模板规则处理它们的唯一方法是调用 <xsl:apply-templates/>

到目前为止,我一直很担心 后面的模板没有执行 因为他们操作的节点 不会出现在输出中,但是 那么反过来呢 创建一个节点 一个“稍后”的模板可以做一些事情 用什么?

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

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

模板 # 1创建一个名为 “ fullName”. 模板 # 2匹配 同一个节点。将模板 # 2 执行,因为“ fullName”节点 存在于输出中 去看看模板2?

没有。执行处理链的唯一方法是以这种方式显式地设置它。创建一个变量,例如 $tempTree,它包含新的 <fullName>元素,然后处理 ,就像这个 <xsl:apply-templates select="$tempTree">。为了在 XSLT 1.0中实现这一点,您需要用扩展函数(例如 exsl:node-set())包装变量引用,但是在 XSLT 2.0中,它将按原样工作。

无论是处理来自原始源树的节点,还是处理构造的临时树中的节点,都需要显式说明要处理哪些节点。

我们没有讨论的是 XSLT 如何获得所有的隐式行为。您还必须了解 内置的模板规则。我一直编写样式表,甚至不包括根节点(match="/")的显式规则。相反,我依赖于根节点的内置规则(将模板应用于子节点) ,这与元素节点的内置规则相同。因此,我可以忽略大部分输入,让 XSLT 处理器自动遍历它,只有当它遇到我感兴趣的节点时,我才会执行一些特殊的操作。或者我可以编写一个递归复制所有内容的规则(称为身份转换) ,只在必要时重写它,以对输入进行增量更改。在阅读了“ How XSLT Works”之后,您的下一个任务是查找“标识转换”。

我知道我很无知 关于 XSLT 的“禅”。到目前为止,我的 样式表由一个 模板匹配根节点,然后 完全是程序化的。 我厌倦了这样做,我会的 实际上更了解 XSLT 正确,这就是我的问题。

我为你鼓掌。现在是吃“红色药丸”的时候了: 读 “ XSLT 如何工作”

埃文的回答基本上是好的。

然而,有一件事似乎确实缺乏的是不进行任何匹配就“调用”代码块的能力。至少在一些人看来,这将使结构更加完善。

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

<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>

[如果你看不到上面的代码,可以自上而下地滚动]

其工作方式是主模板 always match-match on/

它包含代码块-模板-这些代码块被调用。

这意味着在/上不可能匹配另一个模板,但是可以匹配 显式地放在一个指定的节点上,在本例中,该节点是 xml 调用的条目中的最高级别节点。

对代码的一个小小的修改产生了上面给出的示例。