我正在使用XSLT来转换XML文件,这个XPath只是其中很小的一部分。主要目标是性能问题。首先,我将描述上下文:
转换的一部分是一个复杂的分组操作,用于按出现的顺序对一系列相似的元素进行分组。这是数据中的一个小样本:
<!-- 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子元素(如Meaning或OpenBracket)的元素的上下文中,该XPath选择先前的兄弟元素,以及具有来自父/祖先MeaningSegment的先前兄弟元素的值的所有元素。实际上,它基本上选择了它之前的所有文本(或者,更确切地说,文本本身的祖辈)。
后来我意识到,对于具有值的元素的层和不同深度,可能会有更多的复杂性。我可能需要选择所有这些前面的元素,而不管它们的父元素和兄弟元素,但仍然在同一个块中。我用一个稍微简单一些的XPath表达式替换了“重要的部分”:
preceding::*[value and generate-id(ancestor-or-self::MeaningDefBlock) = generate-id(current()/ancestor-or-self::MeaningDefBlock)]
这只是检查它是否在同一个块中,并且它是有效的!即使带有值的元素和父元素混合在一起,它也会成功地选择块中前面的文本段。示例输入片段:
...
<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
发布于 2020-05-12 02:39:35
我猜你可能已经弄明白了
preceding::*[value and generate-id(ancestor-or-self::MeaningDefBlock)
= generate-id(current()/ancestor-or-self::MeaningDefBlock)]将搜索回到文档的开头;它不够聪明,不知道它只需要在包含的meaningDefBlock元素中搜索。
对此的一个解决方案是将其更改为以下内容:
ancestor-or-self::MeaningDefBlock//*[value][. << current()]<<运算符需要XPath 2.0,对于像这样复杂的问题,您真的应该考虑向前发展。但是,您可以在1.0中使用类似于generate-id(($A|$B)[1]) = generate-id($A)的表达式来模拟该操作符。
不能保证这会更快,但与您现有的解决方案不同的是,它应该独立于文档中有多少MeaningDefBlock元素。
https://stackoverflow.com/questions/61728593
复制相似问题