您的更新要求使问题变得更加复杂,因为“元素包装”可能发生在搜索令牌内的任意点,甚至可能跨越多个元素。我认为您无法在 XPath < 3.0 中编写查询(如果您只能在 XPath 中执行此操作)。我为此使用了 XQuery,它扩展了 XPath。代码在BaseX中运行良好,但也应该在所有其他 XQuery 引擎中运行(可能需要 XQuery 3.0,没看过)。
代码变得相当复杂,我想我在其中添加了足够的注释以使其易于理解。它要求节点位于下一个元素内,但稍作调整后,它也可用于遍历任意 XML 结构(想想带有<span/>
s 和其他标记的 HTML)。
(: functx dependencies :)
declare namespace functx = "http://www.functx.com";
declare function functx:is-node-in-sequence
( $node as node()? ,
$seq as node()* ) as xs:boolean {
some $nodeInSeq in $seq satisfies $nodeInSeq is $node
} ;
declare function functx:distinct-nodes
( $nodes as node()* ) as node()* {
for $seq in (1 to count($nodes))
return $nodes[$seq][not(functx:is-node-in-sequence(
.,$nodes[position() < $seq]))]
} ;
declare function local:search( $elements as item()*, $pattern as xs:string) as item()* {
functx:distinct-nodes(
for $element in $elements
return ($element[contains(./text(), $pattern)], local:start-search($element, $pattern))
)
};
declare function local:start-search( $element as item(), $pattern as xs:string) as item()* {
let $splits := (
(: all possible prefixes of search token :)
for $i in 1 to string-length($pattern) - 1
(: check whether element text starts with prefix :)
where ends-with($element/text(), substring($pattern, 1, $i))
return $i
)
(: go on for all matching prefixes :)
for $split in $splits
return
(: recursive call to next element :)
let $continue := local:continue-search($element/following-sibling::*[1], substring($pattern, $split+1))
where not(empty($continue))
return ($element, $continue)
};
declare function local:continue-search( $element as item()*, $pattern as xs:string) as item()* {
if (empty($element)) then () else
(: case a) text node contains whole remaining token :)
if (starts-with($element/text(), $pattern))
then ($element)
(: case b) text node is part of token :)
else if (starts-with($pattern, $element/text()))
then
(: recursive call to next element :)
let $continue := local:continue-search($element/following-sibling::*[1], substring($pattern, 1+string-length($element/text())))
where not(empty($continue))
return ($element, $continue)
(: token not found :)
else ()
};
let $token := 'll'
return local:search(//div, $token)