首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >XPath如何在“前置”轴上优化性能?

XPath如何在“前置”轴上优化性能?
EN

Stack Overflow用户
提问于 2020-05-11 19:26:15
回答 1查看 170关注 0票数 0

我正在使用XSLT来转换XML文件,这个XPath只是其中很小的一部分。主要目标是性能问题。首先,我将描述上下文:

转换的一部分是一个复杂的分组操作,用于按出现的顺序对一系列相似的元素进行分组。这是数据中的一个小样本:

代码语言:javascript
复制
<!-- potentially a lot more data-->
<MeaningDefBlock>
    <!-- potentially a lot more data-->
    <MeaningSegment>
        <Meaning>
            <value> or </value>
        </Meaning>
    </MeaningSegment>
    <MeaningSegment>
        <MeaningInsert>
            <OpenBracket>
                <value>(</value>
            </OpenBracket>
            <Meaning>
                <value>ex.: </value>
            </Meaning>
            <IllustrationInsert>
                <value>ita, lics</value>
            </IllustrationInsert>
            <ClosedBracket>
                <value>)</value>
            </ClosedBracket>
        </MeaningInsert>
    </MeaningSegment>
    <!-- potentially a lot more data-->
</MeaningDefBlock>
<!-- potentially a lot more data-->

只有父元素(例如:MeaningInsert)和只包含包含文本的value元素(例如:IllustrationInsert)的元素。来自输入的文本被分组到具有以下文本段的元素中:or (ex.:ita, lics) (在本例中,"ita,lics“段将原本在一个组中的组分开)。主要的一点是,来自不同级别的元素可以分组。XPath用于通过之前的网段识别组,并键入XSL。整个关键是非常复杂的,并且不是问题的对象(但我仍然提供它作为上下文):

<xsl:key name="leavesInGroupL4" match="MeaningSegment//*[value]" use="generate-id(((preceding-sibling::*[value]|ancestor-or-self::MeaningSegment/preceding-sibling::MeaningSegment//*[value])[not(boolean(self::IllustrationInsert|self::LatinName)=boolean(current()/self::IllustrationInsert|current()/self::LatinName))]|ancestor-or-self::MeaningDefBlock)[last()])"/>

重要的部分是:

(preceding-sibling::*[value]|ancestor-or-self::MeaningSegment/preceding-sibling::MeaningSegment//*[value])[...]

从具有value子元素(如MeaningOpenBracket)的元素的上下文中,该XPath选择先前的兄弟元素,以及具有来自父/祖先MeaningSegment的先前兄弟元素的值的所有元素。实际上,它基本上选择了它之前的所有文本(或者,更确切地说,文本本身的祖辈)。

后来我意识到,对于具有值的元素的层和不同深度,可能会有更多的复杂性。我可能需要选择所有这些前面的元素,而不管它们的父元素和兄弟元素,但仍然在同一个块中。我用一个稍微简单一些的XPath表达式替换了“重要的部分”:

preceding::*[value and generate-id(ancestor-or-self::MeaningDefBlock) = generate-id(current()/ancestor-or-self::MeaningDefBlock)]

这只是检查它是否在同一个块中,并且它是有效的!即使带有值的元素和父元素混合在一起,它也会成功地选择块中前面的文本段。示例输入片段:

代码语言:javascript
复制
...
<OpenBracket>
    <value>(</value>
</OpenBracket>
<SomeParentElement>
    <LatinName>
        <value>also italics</value>
    </LatinName>
</SomeParentElement>
<ClosedBracket>
    <value>)</value>
</ClosedBracket>
...

第一种方法无法做到这一点,因为括号和LatinName不是兄弟关系。

但是,使用preceding:*的新方法非常慢!在实际的文档中,XSL转换最多需要5分钟,而原来的方法通常需要3秒(包括开销),这使时间增加了100倍。当然,这是因为preceding在执行时(多次)几乎会检查整个文档中的每个节点。该文档有许多MeaningDefBlock块(接近2000个),每个块都有几段文本(通常是一位数)和一堆与该文本无关的其他直接的元素/节点(通常在低数百个,每个块)。很容易看出这一切是如何提高preceding-sibling上的preceding垃圾处理性能的。

我想知道这是否可以以某种方式进行优化。在XSL中,键在我们的项目中多次极大地提高了性能,但我不确定preceding和键是否可以组合在一起,或者XPath是否需要更复杂并针对我的特定情况进行定制,可能会枚举它应该查看的元素(希望忽略其他所有内容)。

由于输入当前将始终与第一种方法一起工作,我已经承认并回滚了更改(并且可能宁愿每次都接受5分钟的命中,而不是自己尝试优化)。

我使用XSLT1.0和XPath 1.0

EN

回答 1

Stack Overflow用户

发布于 2020-05-12 02:39:35

我猜你可能已经弄明白了

代码语言:javascript
复制
    preceding::*[value and generate-id(ancestor-or-self::MeaningDefBlock)
 = generate-id(current()/ancestor-or-self::MeaningDefBlock)]

将搜索回到文档的开头;它不够聪明,不知道它只需要在包含的meaningDefBlock元素中搜索。

对此的一个解决方案是将其更改为以下内容:

代码语言:javascript
复制
ancestor-or-self::MeaningDefBlock//*[value][. << current()]

<<运算符需要XPath 2.0,对于像这样复杂的问题,您真的应该考虑向前发展。但是,您可以在1.0中使用类似于generate-id(($A|$B)[1]) = generate-id($A)的表达式来模拟该操作符。

不能保证这会更快,但与您现有的解决方案不同的是,它应该独立于文档中有多少MeaningDefBlock元素。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61728593

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档