为什么处理父元素的模板会阻止处理子元素模板?

时间:2015-03-06 14:34:27

标签: xslt

考虑这个XML:

<person>
  <name>
    <firstName>James</firstName>
    <lastName>Bond</lastName>
  </name>
</person>

然后考虑这个XSL:

<xsl:template match="//name">
   [do stuff]
</xsl:template>

<xsl:template match="//firstName">
   [do stuff]
</xsl:template>

我说的是第一个模板的存在(&#34; //名称&#34;)&#34;隐藏&#34;第二个模板(&#34; // firstName&#34;)。我知道这是真的(我在发布前对此进行了测试),但我试图推导并理解一般规则。

...由于

  1. 它(&#34; //名称&#34;)不太具体
  2. 首先匹配
  3. 它不会调用&#34; apply-templates&#34;
  4. ...不会处理第二个模板。

    这种逻辑的机制是什么,如何预测?当&#34; //名称&#34;模板被处理,处理器以某种方式逻辑地&#34;检查&#34;所有子元素都不需要处理?他们是否被认为已经完成了#34;因为他们的父母被处理了?

    如果我想确保第二个模板运行,除了确保&#34; apply-templates&#34;之外还有其他方法吗?在第一个模板中调用?

    同样,我知道有很多方法,我只是想了解其背后的理论/理由。

3 个答案:

答案 0 :(得分:4)

michael.hor257k和Ian Roberts提供的答案是正确和完整的,但由于你似乎仍然有问题,或许用不同的方式解释它可能会有所帮助。

首先,请注意模式中的//基本上没有效果;如果匹配模式是"name""firstName",则模板的含义相同。如果在你的脑海中你一直认为//发出任何可能影响控制流的信息,那么最好打消这种想法。

其次,请注意,在树或文档的任何输入/处理/输出语言中,语言设计者可以采用一种“推送”方式。处理(处理器读取输入并处理输入中的每个项目,在适当的时候产生输出;当输入完成时,过程完成),或者一种“拉动”。处理(处理器按项目生成所需的输出,根据需要查询输入;当输出完成时,过程完成)。

在不允许重新排序数据的语言中,两种模式重合:输入从开始到结束以线性顺序处理,输出从开始到结束以线性顺序生成。

在XSLT或其前身DSSSL等语言中,如果考虑处理输入中的项目的顺序以及生成输出中项目的顺序,则两者之间的差异是可见的。在推送语言的直接(或:天真)实现中(如DSSSL的树到树转换过程),输入项按顺序处理,输出按顺序计算,并缓冲直到完成。在拉语言(如XSLT)中,输出按顺序计算,输入被缓冲,以便可以按任意顺序访问它。

请注意使用&#39; push&#39;并且&#39;拉&#39;这与许多XSLT程序员将这些术语应用于XSLT样式表的意义不完全相同 - 在语言层面,正如我刚刚描述的那些术语,XSLT是一种拉动语言。朴素实现缓冲所有输入。这里有一定的讽刺意味,因为在XSLT中,推送&#39;样式表通常比“拉”更具惯用性。样式表。

另请注意,我正在讨论天真的实现:XSLT规范纯粹是声明性的,处理器可以按照自己喜欢的顺序执行任务。但最简单和最简单的实现策略是按顺序评估描述样式表输出的表达式。默认情况下,输出由与模块节点匹配的模板的模板主体完整描述。


对于你展示的XML,样式表中的控制流(假设你只有两个模板)就像是这样,在一个简单,天真的XSLT实现中:

  1. 如果您正常调用它(即没有任何选项来覆盖默认行为),处理器将查找与文档节点匹配的模板。由于样式表中没有模板匹配/,因此应用默认规则,其中包含

    <xsl:apply-templates/>
    

    或,明确提供默认值:

    <xsl:apply-templates select="child::node()"/>
    

    处理器将此模板推送到堆栈并开始执行它。模板中唯一的内容是调用apply-templates,这就是处理器所做的事情。

  2. 处理器现在推送堆栈中的所有子项(按反向文档顺序);让我们假设这些孩子是(1)“人”的具体内容。元素,和(2)一个由换行符组成的文本节点,该换行符跟在“人物”之后。元件。对于它们中的每一个,处理器寻找匹配的模板。在这一点上,“人”#39; element位于堆栈的顶部,因此处理器会查找匹配的模板。

  3. 处理器在样式表中找不到与“&#39;”匹配的模板。元素,所以它使用Ian Roberts已经描述的默认模板,除了调用

    之外什么都不做
    <xsl:apply-templates select="child::node()"/>
    

    处理器按下&#39;人员的(默认)模板。元素进入堆栈并开始执行它。此模板除了在子项上调用apply-templates之外什么都不做。

  4. 处理器现在推动了这个人的所有孩子。堆栈上的元素。堆栈现在包含:

    • 包含零个或多个空格或制表符的文本节点,换行符和两个空格(在&#39; person&#39;元素的开头)
    • a&#39; name&#39;元件
    • 包含零个或多个空格或制表符和换行符的文本节点(在&#39; name&#39;和&#39; person&#39;)的结束标记之间
    • &#39;人物的模板&#39;元素,带有一个指令指针,指示执行应该继续执行&#39; apply-templates&#39;指令。
    • 包含新行的文本节点,该行位于&#39; person&#39;的结尾标记之后。在输入中(在步骤2中放置在堆栈中)
    • 文档根节点的模板,带有指示指针,指示在调用&#39; apply-templates&#39;之后应继续执行。
  5. 找不到与堆栈顶部的文本节点匹配的模板,处理器遵循默认规则,基本上调用

    <xsl:value-of select="."/>
    

    因此,处理器会写出文本节点的字符串值并弹出堆栈。

  6. &#39;名称&#39;元素现在位于堆栈的顶部;处理器现在寻找匹配它的模板。它找到样式表中的第一个模板,并将模板推送到堆栈中。

  7. 处理器评估匹配模板的主体 - 在显示的样式表版本中,它包含一个包含一些空格的文本节点,文本&#34; [do stuff]&#34;,和一些白色空间。它将此文本节点的字符串值写入输出,并且(已完成对模板的评估)弹出堆栈。

  8. 堆栈上的下一个节点是&#39; name&#39;之后的空白节点。元件。样式表没有与之匹配的模板,因此处理器使用默认的文本节点模板并将字符串值复制到输出。处理器现在完成了文本节点,该节点是“人”的最后一个孩子。元素,所以它会弹出堆栈。

  9. 堆叠的顶部现在被(默认)模板占用,该模板与“人”相匹配。元件。处理器在指令指针指示的点继续,这是在调用&#39; apply-templates&#39;之后。模板体中没有其他内容,因此模板体现在已经完整评估。处理器完成了模板,然后将其弹出堆栈。

  10. 现在,堆栈会显示“&#39;”之后的空白节点。元件。样式表没有与之匹配的模板,因此处理器使用默认的文本节点模板,将字符串值复制到输出,然后弹出堆栈。

  11. 现在堆栈中唯一的东西是与文档的根节点匹配的默认模板。我们定位在&#39; apply-templates&#39;指导,所以没有别的事可做。处理器弹出堆栈。

  12. 现在堆栈已空,样式表的评估现已完成。

  13. 当然,刚才应该给出的描述有一点点:它描述了控制的逻辑流程,而不一定是给定实现中的事件序列。例如,在实践中,处理器可以通过使用尾部调用优化的形式来节省堆栈空间,以避免在指令指针到达模板末尾时将模板留在堆栈上。更一般地说,XSLT有一个纯粹的声明性语义,因此任何关于样式表评估的讨论都会说“发生这种情况,然后就会发生这种情况”#34;对于&#34来说是一个简单的方法;一种方法就是这样做,然后那就是&#34; - 规范不会限制事物发生的顺序,只会限制输出的结构。


    简化,这种评估模型应该可以帮助您理解为什么永远不会调用第二个模板。你建议的原因是混合包:

    它与匹配模式的相对特异性无关&#34; //名称&#34;和&#34; // firstName&#34; - 由于它们永远不能匹配同一节点,因此这两种模式的相对特异性永远不会成为问题。 (无论如何,两种匹配模式具有相同的特异性,两种模板具有相同的默认优先级。)

    这与名字&#39;有关。元素首先匹配只是为了一个非常特殊(而不是非常精确)的概念,首先匹配&#34;&#34;也许意味着。时间关系不是由XSLT规范指定的,因此首先&#39;实际上并不是一个定义或可定义的概念。在样式表的任何评估中,&#39; name&#39;的模板都是正确的。可以说元素在“第一个名称”的模板之前是匹配的。元素,但只是在一个模板匹配的意义上,另一个是从不匹配(我们仍在等待它匹配,我们将永远等待)。名称&#39;的模板触发因为有一个点,正在执行的模板包含一个&#39; apply-templates&#39;使用&#39; select&#39;进行指导属性,其值包括名称的节点&#39;元件。 &#39; firstName&#39;的模板从来没有开火,因为“第一名”&#39;永远不会发生。 element:它永远不会出现在任何&#39; select&#39;的值中。属于任何&#39; apply-templates&#39;被评估。

    正如Ian Roberts指出的那样,如果你想要触发两个模板,在一个与当前模式类似的样式表中,解决方案是将<xsl:apply-templates/>添加到&#39的模板中;名称&#39;元件。

    [附录]读者努力控制自己的控制权可能会发现Evan Lenz's answer对相关问题有帮助。

答案 1 :(得分:2)

导致模板触发的唯一因素是调用xsl:apply-templates看起来这样的原因在很多情况下是自动的,XSLT定义了默认模板规则(XSLT 2.0XSLT 1.0),当没有与特定模板匹配的显式模板时触发节点。元素节点的默认规则实际上是

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

如果您为特定节点定义显式模板,则由您自行决定何时(或实际上)是apply-templates给其子节点。对于某些类型的转换,您可能希望将处理子的结果放在特定元素中,对于您可能需要在其之前或之后放置它的其他方案。 XSLT不会试图猜测这一点。

在您的示例中,您没有person的显式模板,因此默认规则适用,它将模板应用于person元素的三个子节点(之前的空白文本节点) name元素,name本身以及name之后的空白文本节点。对于name 是一个显式模板,因此它会触发,因为它不会apply-templates到递归的任何其他内容。

答案 2 :(得分:1)

  

...由于

     
      
  1. 它(&#34; //名称&#34;)不太具体
  2.   
  3. 首先匹配
  4.   
  5. 它不会调用&#34; apply-templates&#34;
  6.         

    ...不会处理第二个模板。

正确的答案是#3。

要理解这一点,您必须了解处理器的默认行为(即内置模板规则)是遍历整个输入树并访问每个节点。

处理器到达节点时的作用取决于匹配该节点的模板。如果模板告诉处理器继续遍历,那么该分支的其余部分将被放弃。 (请注意,默认模板 将模板应用于当前节点的子节点。)

  

如果我想确保运行第二个模板,还有其他方法   除了确保&#34; apply-templates&#34;在第一个被称为   模板?

是。您可以从第三个模板应用第二个模板。您不仅可以将模板应用于当前节点的子节点。您可以将模板应用于当前节点的兄弟姐妹,祖先,后代......甚至输入树的完全不同分支中的节点。