这是一件有趣的事情,我在这个过程中学到了一些东西。谢谢!
注意:为了获得您想要的结果,我修复了 A.xml 中的拼写错误(“Cricket”->“Cricket”)。
以下解决方案使用两个 MarkLogic 特定的函数:
-
cts:highlight
(用于用节点替换匹配的文本,然后您可以计算节点)
-
cts:tokenize
(用于将给定字符串分解为单词、空格和标点符号部分)
它还包括一些分别针对这两个函数的强大魔法:
- 特殊变量的动态绑定
$cts:text
(对于这个特定的用例来说这并不是真正必要的,但我离题了),并且
- the data model extension which adds these subtypes of
xs:string
:
-
cts:word
,
-
cts:space
, and
-
cts:punctuation
.
Enjoy!
xquery version "1.0-ml";
(: Generic function using MarkLogic's ability to find query matches within a single node :)
declare function local:find-matches($content, $search-text) {
cts:highlight($content, $search-text, <MATCH>{$cts:text}</MATCH>)
//MATCH
};
(: Generic function using MarkLogic's ability to tokenize text into words, punctuation, and spaces :)
declare function local:get-words($text) {
cts:tokenize($text)[. instance of cts:word]
};
(: The rest of this is pure XQuery :)
let $content := doc("A.xml")/root/content,
$lookup := doc("B.xml")/WordLookUp
return
<root>
{$content}
<updatedElement>
<companies>{
for $company in $lookup/companies/company
let $results := local:find-matches($content, string($company))
where exists($results)
return
<company count="{count($results)}">{string($company/@name)}</company>
}</companies>
<mood>{
sum(
for $mood in $lookup/moods/mood
let $results := local:find-matches($content, string($mood))
return count($results) * $mood/@number
)
}</mood>
<topics>{
for $topic in $lookup/topics/topic
let $results := local:find-matches($content, string($topic))
where exists($results)
return
<topic count="{count($results)}">{string($topic/@group)}</topic>
}</topics>
<word-count>{
count(local:get-words($content))
}</word-count>
</updatedElement>
</root>
如果您对上述所有内容的工作原理有任何后续问题,请告诉我。起初,我倾向于使用cts:search
or cts:contains
,这是 MarkLogic 中搜索的基础。但我意识到这个例子与其说是关于搜索(查找文档),不如说是关于在已经给定的文档中查找匹配的文本。如果您需要以某种方式扩展它以聚合大量文档,那么您需要研究以下附加用途cts:search
or cts:contains
.
最后一个警告:如果您认为您的内容可能有<MATCH>
元素已经存在,您需要在调用时使用不同的元素名称cts:highlight
(您可以保证的名称不会与内容的现有元素名称冲突)。否则,您可能会得到错误数量的结果(高于准确计数)。
附录:
我很好奇这是否可以在没有cts:highlight
, 鉴于cts:tokenize
已经为您将文本分解为所有单词。使用这种替代实现可以产生相同的结果local:find-matches
(假设您交换函数声明的顺序,因为一个函数声明依赖于另一个函数声明):
(: Find word matches by comparing them one-by-one :)
declare function local:find-matches($content, $search-text) {
local:get-words($content)[cts:stem(.) = cts:stem($search-text)]
};
It uses cts:stem
将给定单词标准化为其词干,因此,例如搜索“pass”将匹配“passed”等。但是,这仍然不适用于多单词(短语)搜索。所以为了安全起见,我会坚持使用cts:highlight
,其中,就像cts:search
and cts:contains
,可以处理您给它的任何 cts:query (包括像我们上面那样的简单单词/短语搜索)。