以下是XML文件 -
<A>
<B>
<Data>John is a good</Data>
<Data>James loves cricket</Data>
<Data>John loves Hockey</Data>
</B>
<B>
<Data>Stuart loves cricket</Data>
<Data>Johny loves Hockey</Data>
</B>
</A>
我想从节点Data
替换名称(James,John,Stuart)以外的所有单词。我想一次性完成这项工作。
以下是XQuery -
for $words in ("Hockey", "crikcet", "is")
let $word := $words
for $x in doc('file')//Data
where contains($x, $word)
return replace value of node $x with normalize-space(replace($x, $word, ''))
我收到错误 -
[XUDY0017] Node can only be replaced once: element Data ...
我正在使用BaseX 7.6
答案 0 :(得分:4)
首先,您应该更新到最新版本的BaseX。目前版本为8.2.x和7.6。很长一段时间以来一直没有支持。
其次,你的整个方法并不像XQuery那样特别。例如,而不是做
for $words in ("Hockey", "crikcet", "is")
let $word := $words
以下行完全相同但更短更容易
for $word in ("Hockey", "crikcet", "is")
由于有两个for循环,如果一个数据值包含多个匹配,则此处可以使用相同的节点两次或更多次。这是当您的错误消息出现时(实际上在您提供的示例数据集中没有发生,因为每个数据值只包含一个搜索字符串)。
多次替换每个值不起作用,因为XQuery Update根据待定更新列表(PUL)应用更新,即在查询结束时。如果要两次替换相同的数据值,处理器当然不知道要替换它的内容。相反,您应该自己计算替换值,然后替换完整的值。这是递归进来,你的用例是一个很好的例子。因此,以下应该有效:
declare function local:replace-word($word as xs:string, $search as xs:string*, $replace as xs:string*) as xs:string {
if (empty($search)) then $word
else replace(local:replace-word($word, tail($search), tail($replace)), head($search), head($replace))
};
let $words := ("Hockey", "cricket", "is")
let $replace := ("Replace1", "Replace2", "Replace3")
for $x in //Data
return replace value of node $x with normalize-space(local:replace-word($x, $words, $replace))
那么,它做了什么?首先,我介绍了第二个包含要替换的值的序列。在您的查询中,您总是用空字符串替换,即删除单词,这不是您的问题所要求的。另外,我在cricket
中替换了你的拼写错误。
我们现在只有一个for循环迭代每个Data
元素。它调用local:replace-word
函数。此函数调用自身(因此:递归),直到序列中不再有搜索/替换字为止。 head()
获取序列中的第一项,而last()
获取序列中的所有其他值。
答案 1 :(得分:1)
只需通过交换两个循环确保每个节点只触摸一次。这是一种lambda magic的一种可能性,可以一个接一个地进行所有替换:
let $words := ("Hockey", "cricket", "is")
for $data in doc('file')//Data
where some $word in $words satisfies contains($data, $word)
return
replace value of node $data
with normalize-space(fold-left($words, $data, replace(?, ?, '')))