如何(很好地)模板匹配更广泛的XPATH中的多个特定子元素(联合)

时间:2015-09-09 19:25:27

标签: xslt xpath

我正在尝试匹配一组特定的元素,但只是那些是另一个元素结构的子元素(让我们说它的输入或仅在div s内的某个地方选择元素“class-sauce”在他们)。通常情况下,就XPATH而言,这很容易:我们可以通过括号方式联合目标儿童,如下所示:

 div[contains(@class, 'special-sauce')//(input | select)

但是当我们尝试将此作为模板匹配时(至少在Saxon中),这是XSLT抛出曲线球的地方:

 <xsl:template match="div[contains(@class, 'special-sauce')//(input | select)">
  

{“error”:“无法解析xsl文件(/section-settings.xsl)。   无法编译样式表。 1错误   检测 “” 代码 “:” TRANSFORM_ERROR “ ”位置“:空 ”使“:[” 致命   错误:令牌\“(\”这里不允许使用XSLT模式“]}

基本上,括号不允许作为主路径级别的模板匹配的一部分(显然它们在条件/等内部仍然可以正常工作)。

那该怎么办?

嗯,从技术上讲,使用联合仍然可以工作,但我们每次都必须重复祖先XPATH,因为我们不能用括号括起孩子:

 <xsl:template match="div[contains(@class, 'special-sauce')//input 
     | div[contains(@class, 'special-sauce')//select">

这是可行的(不是很漂亮,但确定,我们可以处理这些!换行可以在这里帮助我们的理智yay)在我们这里的简单示例中,但是对于更复杂的XPATH会有问题,特别是如果括号结合可能是在较长的xpath中间,或者是在很多元素中。

e.g。

div[contains(@class, 'major-mess')]/div[contains(@class, 'special-sauce')]//(dataset | optgroup | fieldset)//(button | option | label)

变为

a crazy mess.

好吧,在更复杂的例子中,这很快就会成为一种选择。虽然以不同方式构造我们的XSLT可能有所帮助(中间匹配,使用模态等),但问题仍然存在:

当括号不起作用时,如何使用更大的XPATH模式中的各个子元素的联合优雅地模板匹配?

第一个示例的示例表:

 <div class="special-sauce">
    <input class="form-control" type="text" value="" placeholder="INHERITED:" />
    <select class="form-control">
        <option value="INHERITED: ">INHERIT: </option>
        <option value=""></option>
    </select>
    <div class="radio">
        <label>
            <input type="radio" name="param3vals" value="INHERITED: " />
            INHERIT:
        </label>
    </div>
 </div>
 <div class="not-special"><input type="text" id="contact-info-include-path" size="90">     
     <label>contact</label>
 </input></div>
 <div class="sad-panda"><input type="text" id="sidenav-include-path" size="90">
     <label>sidenav</label>
 </input></div>

注意:这确实假设身份转换正在作为处理输入文档的主要方法运行。

虽然还有其他问题可以有效地得到类似的答案,例如我在下面给出的答案,但我觉得这些问题的背景通常更为笼统(这样一个顶级联盟就可以作为他们的回答并发症),更具体的方式不匹配,或根本不同。因此Q&amp; A格式。

XSLT 1.0 vs 2.0 vs 3.0

下面的Michael Kay correctly notes in his answer虽然这里尝试的原始模式在XSLT 1.0或2.0中不起作用,但它应该在(完全)XSLT 3.0兼容处理器中工作。我目前正在使用Saxon 9.3的系统,这在技术上是XSLT 2.0。对于那些使用3.0系统的人,我只想特别注意那个答案。

1 个答案:

答案 0 :(得分:4)

我看了一遍,大多数类似问题的答案都涉及将XPATH的重复部分复制到每个元素并将它们组合在一起。但有更好的方法!很容易忘记匹配特定元素相对于在XPATH中匹配该元素的名称。

使用name()local-name()而不是直接在模板匹配模式*中匹配元素。

在选择要使用的内容时,请注意您的命名空间问题/需求。这仍然允许对这些元素的属性/等进行高级条件化。

例如,第一场比赛变为:

 <xsl:template match="div[contains(@class, 'special-sauce')//
     element()[local-name() = ('input', 'select')]">

在空间或时间方面没有大幅增加来解决这个问题,但是我们减少了冗余以及可能导致的相关数据一致性错误(经常发生,特别是如果以后制作的话)变化)。

真正闪耀的是问题中的最后一个例子(混乱):

 <xsl:template match="div[contains(@class, 'major-mess')]/
     div[contains(@class, 'special-sauce')]//
     element()[local-name() = ('dataset', 'optgroup', 'fieldset')]//
     element()[local-name() = ('button', 'option', 'label')]">

由于我不记得是否完全符合XSLT / XPATH 1.0,通过括号创建元素树片段进行比较,如果你确实需要向后兼容“contains()与包围分隔符”(减少机会)来自另一个元素的误报是目标全名的子字符串模式总是有效:

 <xsl:template match="div[contains(@class, 'major-mess')]/
     div[contains(@class, 'special-sauce')]//
     element()[contains('|dataset|optgroup|fieldset|'), concat('|', local-name(), '|'))]//
     element()[contains('|button|option|label|', concat('|', local-name(), '|'))]">

* =“匹配模式”与“XPath”

如果您正在努力理解为什么天真的方法(我在问题中尝试的第一件事)在XSLT中失败,那么有助于理解像“匹配”这样的模板规则必须遵循XSLT patterns,这只是expression tokens本质上是一组有效的XPath表达式(这很容易使得区分和记忆更加困惑,特别是当很多来源只是假装它完全是XPath时)。请注意,括号仅显示为用作predicates的有效选项,仅在{{3}}内的表达式中找到,而不是位置路径或位置步骤的任何其他部分。

最终注意事项

性能:我不知道这种方法是否存在显着的性能差异,而不是将每个单独的元素组合为每个元素的完整路径,或者在本地寻址元素与作为谓词之间是否存在真正的性能差异匿名element()选择器。我怀疑,虽然大多数XSLT处理器在使用本机路径结构编写单个匹配时可能实现更快的DOM树搜索,而在匿名选择器上使用name()函数编写谓词,但联合情况可能会执行得更快,具体取决于如何处理器尝试预编译和优化逻辑模式。我将把这项任务留给别人去尝试基准测试,因为最终真正的障碍就是开发人员的理智和维护问题(可能会引发人为错误)。在复杂的匹配中,我觉得这种方法的简单易读性和减少/消除的数据冗余可以轻松地满足任何小的性能损失。