XQuery替换节点中的多个单词

时间:2015-08-21 07:05:26

标签: xquery basex

以下是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

2 个答案:

答案 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(?, ?, '')))