在内存中的 XQuery 中多次编辑同一个文档节点
Editing same document-node multiple times in XQuery in memory
假设我有这样一个文档:
<root>
<content>
<z>valZ</z>
<a>
<b>
<c>valC</c>
</b>
<b>
<c>valC</c>
</b>
</a>
<a>
<d>valD</d>
</a>
</content>
</root>
节点数"a" 可以是从 1 到某个不超过 30 的未定义数字
"b" 、 "c" 和 "d" 节点的数量也可以从 0 到某个不超过 20 的未定义数字
我需要在 XQuery 中做的是获取节点 "z" 的值并将其复制到每个现有节点 "b" 因此结构每次都将如下所示:
<root>
<content>
<z>valZ</z>
<a>
<b>
<c>valC</c>
<z>valZ</z>
</b>
<b>
<c>valC</c>
<z>valZ</z>
</b>
</a>
<a>
<d>valD</d>
<b> <!-- <b> was not present here before -->
<z>valZ</z>
</b>
</a>
</content>
</root>
即使只有一个方块 "b",我也不需要创建另一个方块,只需将 "z" 放入其中(如果超过 1 个,则放入多个 "b"存在)否则在每个 "a" 中我需要创建一个新的。
好像很简单?在除 XQuery 之外的任何其他语言中,我都同意 - 您只需获取文档,复制 "z" 的值,遍历整个文档以查找每个 "a",
检查找到的 "a" 是否有 "b",如果没有,则创建 "b",并在 "b" 内创建具有复制值的新 "z"。
但我很难迭代同一文档并使用 inmemupdate.xq 库更新它。这是我使用的代码片段:
declare function changeSourceForFieldZ($rootDoc as document-node()) as document-node()? {
let $value := getValueOfFieldZ($rootDoc)
return populateBLevelIfValueExists($rootDoc, $value)
};
declare %private function getValueOfFieldZ($rootDoc as document-node()) as text()? {
$rootDoc/*:root/*:content/*:z/text()
};
declare %private function populateBLevelIfValueExists($rootDoc as document-node(), $value as text()?) as document-node()? {
if(fn:exists($value)) then
addField($enrichment, $value)
else
$rootDoc
};
declare %private function addField($rootDoc as document-node(), $value as text()) as document-node()? {
if (hasBLevel($rootDoc)) then
insertNodeInBLevel($rootDoc, $value)
else if (hasALevel($rootDoc)) then
insertNodeInALevel($rootDoc, $value)
else ()
};
declare %private function hasBLevel($rootDoc as document-node()) as xs:boolean {
fn:exists($rootDoc/*:root/*:content/*:a/*:b)
};
declare %private function hasALevel($rootDoc as document-node()) as xs:boolean {
fn:exists($rootDoc/*:root/*:content/*:a)
};
declare %private function createTagWithZField($value as text()) {
<z>{$value}</z>
};
declare %private function createWholeBtagBlock($value as text()) {
(
<b>
<z>{$value}</z>
</b>
)
};
问题很明显是插入方法。如前所述,我使用 mem 库,更准确地说是一个函数:
declare function mem:node-insert-child(
$parentNode as element(),
$newNode as node()*
) as node()
如果我这样写 insertNodeInBLevel 和 insertNodeInALevel:
declare %private function insertNodeInBLevel($rootDoc as document-node(), $value as text()) {
mem:node-insert-child($rootDoc/*:root/*:content/*:a/*:b, createTagWithZField($value))
};
declare %private function insertNodeInALevel($rootDoc as document-node(), $value as text()) {
mem:node-insert-child($rootDoc/*:root/*:content/*:a, createWholeBtagBlock($value))
};
它 return 我是 rootDoc 文档的多个副本,在不同的地方添加了值,而不是在一个文档的所有地方都添加了节点。
我尝试了很多解决方案,包括递归和循环:
declare %private function insertNodeInBLevel($rootDoc as document-node(), $value as text()) {
if(fn:exists($rootDoc/*:root/*:content/*:a/*:b)) then
let $nodes := $rootDoc/*:root/*:content/*:a/*:b
for $node at $index in $nodes
let $rootDoc := exampleInsertWithIndex($rootDoc, $value, $index)
return $rootDoc
};
declare %private function exampleInsertWithIndex($rootDoc as document-node(), $value as text(), $index) {
mem:node-insert-child($rootDoc/*:root/*:content/*:a/*:b[$index], createTagWithZField($value))
};
但是,好吧,值是不可变的,所以不能第二次保存到同一个 rootDoc 等等......知道如何解决这个问题,所以我将编辑同一个文档的多个节点并且 return 只有这一个,而不是它的副本?我是面向对象语言的开发人员,函数式语言对我来说很新,它们遵循不同的范式,因此我对解决方案的思考方式可能有缺陷...
如果您能够使用 XQuery 更新(我认为这需要 Saxon 的商业变体,但也有其他实现支持 XQuery 更新),只需使用转换并插入 <z/>
节点进入每个 <b/>
节点:
let $z := //z
return
copy $result := /root
modify
for $node in $result//b
return insert node $z into $node
return $result
如果不是,递归遍历树并在根据需要修改它的同时重建它是 XQuery 中的常见模式:
declare function local:insert-z($subtree as element(), $z as element()) as element() {
element {node-name($subtree)}
{$subtree/@*,
for $node in $subtree/node()
return
if ($node instance of element())
then
(
local:insert-z($node, $z),
if ($subtree/self::b)
then $z
else ()
)
else $node
}
};
local:insert-z(/root, //z)
中有一个示例列表,说明如何将此模式用于不同的用例
这可以通过 typeswitch 和这些元素的一些自定义逻辑来实现
declare function local:transform($nodes as node()*) as item()* {
for $node in $nodes
return
typeswitch($node)
case text() return $node
case comment() return $node
case processing-instruction() return $node
case attribute() return $node
case element(a) return local:transform-a($node)
case element(b) return local:transform-b($node)
default return local:identity($node)
};
declare function local:transform-a($a as element(a)) as element(a) {
element a {
local:transform($a/(@* | node())),
if(not(exists($a/b))) then
element b { root($a[1])/content/z }
else ()
}
};
declare function local:transform-b($b as element(b)) as element(b) {
element b {
local:transform($b/(@* | node())),
if(not(exists($b/z))) then
root($b[1])/content/z
else ()
}
};
declare function local:identity($node as element()*) as item()* {
element {name($node)} {($node/@*, local:transform($node/node()))}
};
这种事情在 XSLT 中要容易得多!
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="a[not(b)]">
<a>
<xsl:apply-templates/>
<b><xsl:copy-of select="preceding-sibling::z"/></b>
</a>
</xsl:template>
<xsl:template match="b">
<b>
<xsl:apply-templates/>
<xsl:copy-of select="../preceding-sibling::z"/>
</b>
</xsl:template>
假设我有这样一个文档:
<root>
<content>
<z>valZ</z>
<a>
<b>
<c>valC</c>
</b>
<b>
<c>valC</c>
</b>
</a>
<a>
<d>valD</d>
</a>
</content>
</root>
节点数"a" 可以是从 1 到某个不超过 30 的未定义数字 "b" 、 "c" 和 "d" 节点的数量也可以从 0 到某个不超过 20 的未定义数字
我需要在 XQuery 中做的是获取节点 "z" 的值并将其复制到每个现有节点 "b" 因此结构每次都将如下所示:
<root>
<content>
<z>valZ</z>
<a>
<b>
<c>valC</c>
<z>valZ</z>
</b>
<b>
<c>valC</c>
<z>valZ</z>
</b>
</a>
<a>
<d>valD</d>
<b> <!-- <b> was not present here before -->
<z>valZ</z>
</b>
</a>
</content>
</root>
即使只有一个方块 "b",我也不需要创建另一个方块,只需将 "z" 放入其中(如果超过 1 个,则放入多个 "b"存在)否则在每个 "a" 中我需要创建一个新的。
好像很简单?在除 XQuery 之外的任何其他语言中,我都同意 - 您只需获取文档,复制 "z" 的值,遍历整个文档以查找每个 "a", 检查找到的 "a" 是否有 "b",如果没有,则创建 "b",并在 "b" 内创建具有复制值的新 "z"。
但我很难迭代同一文档并使用 inmemupdate.xq 库更新它。这是我使用的代码片段:
declare function changeSourceForFieldZ($rootDoc as document-node()) as document-node()? {
let $value := getValueOfFieldZ($rootDoc)
return populateBLevelIfValueExists($rootDoc, $value)
};
declare %private function getValueOfFieldZ($rootDoc as document-node()) as text()? {
$rootDoc/*:root/*:content/*:z/text()
};
declare %private function populateBLevelIfValueExists($rootDoc as document-node(), $value as text()?) as document-node()? {
if(fn:exists($value)) then
addField($enrichment, $value)
else
$rootDoc
};
declare %private function addField($rootDoc as document-node(), $value as text()) as document-node()? {
if (hasBLevel($rootDoc)) then
insertNodeInBLevel($rootDoc, $value)
else if (hasALevel($rootDoc)) then
insertNodeInALevel($rootDoc, $value)
else ()
};
declare %private function hasBLevel($rootDoc as document-node()) as xs:boolean {
fn:exists($rootDoc/*:root/*:content/*:a/*:b)
};
declare %private function hasALevel($rootDoc as document-node()) as xs:boolean {
fn:exists($rootDoc/*:root/*:content/*:a)
};
declare %private function createTagWithZField($value as text()) {
<z>{$value}</z>
};
declare %private function createWholeBtagBlock($value as text()) {
(
<b>
<z>{$value}</z>
</b>
)
};
问题很明显是插入方法。如前所述,我使用 mem 库,更准确地说是一个函数:
declare function mem:node-insert-child(
$parentNode as element(),
$newNode as node()*
) as node()
如果我这样写 insertNodeInBLevel 和 insertNodeInALevel:
declare %private function insertNodeInBLevel($rootDoc as document-node(), $value as text()) {
mem:node-insert-child($rootDoc/*:root/*:content/*:a/*:b, createTagWithZField($value))
};
declare %private function insertNodeInALevel($rootDoc as document-node(), $value as text()) {
mem:node-insert-child($rootDoc/*:root/*:content/*:a, createWholeBtagBlock($value))
};
它 return 我是 rootDoc 文档的多个副本,在不同的地方添加了值,而不是在一个文档的所有地方都添加了节点。
我尝试了很多解决方案,包括递归和循环:
declare %private function insertNodeInBLevel($rootDoc as document-node(), $value as text()) {
if(fn:exists($rootDoc/*:root/*:content/*:a/*:b)) then
let $nodes := $rootDoc/*:root/*:content/*:a/*:b
for $node at $index in $nodes
let $rootDoc := exampleInsertWithIndex($rootDoc, $value, $index)
return $rootDoc
};
declare %private function exampleInsertWithIndex($rootDoc as document-node(), $value as text(), $index) {
mem:node-insert-child($rootDoc/*:root/*:content/*:a/*:b[$index], createTagWithZField($value))
};
但是,好吧,值是不可变的,所以不能第二次保存到同一个 rootDoc 等等......知道如何解决这个问题,所以我将编辑同一个文档的多个节点并且 return 只有这一个,而不是它的副本?我是面向对象语言的开发人员,函数式语言对我来说很新,它们遵循不同的范式,因此我对解决方案的思考方式可能有缺陷...
如果您能够使用 XQuery 更新(我认为这需要 Saxon 的商业变体,但也有其他实现支持 XQuery 更新),只需使用转换并插入 <z/>
节点进入每个 <b/>
节点:
let $z := //z
return
copy $result := /root
modify
for $node in $result//b
return insert node $z into $node
return $result
如果不是,递归遍历树并在根据需要修改它的同时重建它是 XQuery 中的常见模式:
declare function local:insert-z($subtree as element(), $z as element()) as element() {
element {node-name($subtree)}
{$subtree/@*,
for $node in $subtree/node()
return
if ($node instance of element())
then
(
local:insert-z($node, $z),
if ($subtree/self::b)
then $z
else ()
)
else $node
}
};
local:insert-z(/root, //z)
中有一个示例列表,说明如何将此模式用于不同的用例
这可以通过 typeswitch 和这些元素的一些自定义逻辑来实现
declare function local:transform($nodes as node()*) as item()* {
for $node in $nodes
return
typeswitch($node)
case text() return $node
case comment() return $node
case processing-instruction() return $node
case attribute() return $node
case element(a) return local:transform-a($node)
case element(b) return local:transform-b($node)
default return local:identity($node)
};
declare function local:transform-a($a as element(a)) as element(a) {
element a {
local:transform($a/(@* | node())),
if(not(exists($a/b))) then
element b { root($a[1])/content/z }
else ()
}
};
declare function local:transform-b($b as element(b)) as element(b) {
element b {
local:transform($b/(@* | node())),
if(not(exists($b/z))) then
root($b[1])/content/z
else ()
}
};
declare function local:identity($node as element()*) as item()* {
element {name($node)} {($node/@*, local:transform($node/node()))}
};
这种事情在 XSLT 中要容易得多!
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="a[not(b)]">
<a>
<xsl:apply-templates/>
<b><xsl:copy-of select="preceding-sibling::z"/></b>
</a>
</xsl:template>
<xsl:template match="b">
<b>
<xsl:apply-templates/>
<xsl:copy-of select="../preceding-sibling::z"/>
</b>
</xsl:template>