「要素のラップ」は検索トークン内の任意のポイントで発生し、場合によっては複数の要素にまたがる可能性があるため、更新要件によって問題はさらに複雑になります。XPath < 3.0 でクエリを作成できるとは思いません (とにかく XPath でしかクエリを実行できない場合)。これには、XPath を拡張した XQuery を使用しました。コードは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)