如何使用 XQuery 将 XML 文档中的几个节点包装到一个新节点?
How to wrap several nodes within a XML document to a new node using XQuery?
我想在我的 xml 文档中将多个节点(特定节点)包装成一个 新的单个节点,然后插入它。
示例XML文档-
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
由于我的value5 到value7 是国家的名字,我想把它们放在同一个父节点。
输出需要如下所示:
输出-
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<Country>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
</Country>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
同样,如果我的其他值属于其他值 fields/properties,那么我想将它们包装在一个新的单个节点中。
有什么建议吗?
对于相邻元素,您可以使用 tumbling window
子句 https://www.w3.org/TR/xquery-31/#id-tumbling-windows:
declare variable $countries as xs:string* := ('Australia', 'India', 'USA');
<root>
{
for tumbling window $w in root/*
start $s when true()
end next $n when ($s = $countries) and not($n = $countries) or (not($s = $countries) and $n = $countries)
return
if ($w[1] = $countries)
then <Country>
{$w}
</Country>
else $w
}
</root>
https://xqueryfiddle.liberty-development.net/gWcDMeh/2
如果你想根据元素名称换行,那么你可以使用 window 子句
declare variable $countries as xs:QName* := (QName('', 'value5'), QName('', 'value6'), QName('', 'value7'));
<root>
{
for tumbling window $w in root/*
start $s when true()
end next $n
when ($s/node-name() = $countries) and not($n/node-name() = $countries)
or (not($s/node-name() = $countries) and $n/node-name() = $countries)
return
if ($s/node-name() = $countries)
then <Country>
{$w}
</Country>
else $w
}
</root>
https://xqueryfiddle.liberty-development.net/gWcDMeh/6
我现在也尝试避免使用 window
子句,而是使用递归函数实现包装:
declare variable $countries as xs:string* := ('Australia', 'India', 'USA');
declare function local:wrap($seq as item()*, $wrapper as element()) as item()*
{
let $first-item := head($seq)
return
if (not($first-item))
then (if (empty($wrapper/node())) then () else $wrapper)
else if (not($first-item[. = $countries]))
then
(if (empty($wrapper/node())) then () else $wrapper,
$first-item,
local:wrap(tail($seq), $wrapper!element {node-name()} {})
)
else local:wrap(tail($seq), $wrapper!element {node-name()} { node(), $first-item})
};
<root>
{
local:wrap(root/*, <countries/>)
}
</root>
似乎在 https://xqueryfiddle.liberty-development.net/gWcDMeh/4, I have no idea whether it makes sense with Marklogic. If you want to wrap based on the elements names and not values then you can adapt the code to https://xqueryfiddle.liberty-development.net/gWcDMeh/5 中也能完成这项工作,它声明
declare variable $countries as xs:QName* := (QName('', 'value5'), QName('', 'value6'), QName('', 'value7'));
然后比较else if (not($first-item/node-name() = $countries))
.
如果你只需要包装所有 value5
, value6
, value7
元素那么我想你可以简单地使用
/root/<root>
{
let $values := (value5, value6, value7)
return (
* except $values,
if ($values) then <countries>{ $values }</countries> else ()
)
}
</root>
您可以使用 xsl:for-each-group
.
在 XSLT 中实现您想做的事情
如果想在不等于"somevalue"的时候进行分组,那么可以用group-adjacent
来判断元素值是否等于"somevalue",然后将那些不在 <country>
元素中的包裹起来。
您可以在 MarkLogic 中的 XQuery 模块中执行 XSLT,如下所示:
xquery version "1.0-ml";
declare variable $doc := document {
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
};
declare variable $grouping-xslt :=
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent=". = 'somevalue'">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:copy-of select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<country>
<xsl:copy-of select="current-group()"/>
</country>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>;
xdmp:xslt-eval($grouping-xslt, $doc)
如果您有一个已知的国家/地区名称序列并希望作为分组依据,那么您可以使用 group-by
进行此操作并测试该值是否与任何国家/地区名称相匹配:
xquery version "1.0-ml";
declare variable $doc := document {
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
};
declare variable $grouping-xslt :=
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:param name="countries" />
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" group-by=". = $countries">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<country>
<xsl:copy-of select="current-group()"/>
</country>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>;
declare variable $params := map:new(map:entry("countries", ("Australia", "India", "USA")));
xdmp:xslt-eval($grouping-xslt, $doc, $params)
我想在我的 xml 文档中将多个节点(特定节点)包装成一个 新的单个节点,然后插入它。
示例XML文档-
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
由于我的value5 到value7 是国家的名字,我想把它们放在同一个父节点。 输出需要如下所示:
输出-
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<Country>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
</Country>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
同样,如果我的其他值属于其他值 fields/properties,那么我想将它们包装在一个新的单个节点中。
有什么建议吗?
对于相邻元素,您可以使用 tumbling window
子句 https://www.w3.org/TR/xquery-31/#id-tumbling-windows:
declare variable $countries as xs:string* := ('Australia', 'India', 'USA');
<root>
{
for tumbling window $w in root/*
start $s when true()
end next $n when ($s = $countries) and not($n = $countries) or (not($s = $countries) and $n = $countries)
return
if ($w[1] = $countries)
then <Country>
{$w}
</Country>
else $w
}
</root>
https://xqueryfiddle.liberty-development.net/gWcDMeh/2
如果你想根据元素名称换行,那么你可以使用 window 子句
declare variable $countries as xs:QName* := (QName('', 'value5'), QName('', 'value6'), QName('', 'value7'));
<root>
{
for tumbling window $w in root/*
start $s when true()
end next $n
when ($s/node-name() = $countries) and not($n/node-name() = $countries)
or (not($s/node-name() = $countries) and $n/node-name() = $countries)
return
if ($s/node-name() = $countries)
then <Country>
{$w}
</Country>
else $w
}
</root>
https://xqueryfiddle.liberty-development.net/gWcDMeh/6
我现在也尝试避免使用 window
子句,而是使用递归函数实现包装:
declare variable $countries as xs:string* := ('Australia', 'India', 'USA');
declare function local:wrap($seq as item()*, $wrapper as element()) as item()*
{
let $first-item := head($seq)
return
if (not($first-item))
then (if (empty($wrapper/node())) then () else $wrapper)
else if (not($first-item[. = $countries]))
then
(if (empty($wrapper/node())) then () else $wrapper,
$first-item,
local:wrap(tail($seq), $wrapper!element {node-name()} {})
)
else local:wrap(tail($seq), $wrapper!element {node-name()} { node(), $first-item})
};
<root>
{
local:wrap(root/*, <countries/>)
}
</root>
似乎在 https://xqueryfiddle.liberty-development.net/gWcDMeh/4, I have no idea whether it makes sense with Marklogic. If you want to wrap based on the elements names and not values then you can adapt the code to https://xqueryfiddle.liberty-development.net/gWcDMeh/5 中也能完成这项工作,它声明
declare variable $countries as xs:QName* := (QName('', 'value5'), QName('', 'value6'), QName('', 'value7'));
然后比较else if (not($first-item/node-name() = $countries))
.
如果你只需要包装所有 value5
, value6
, value7
元素那么我想你可以简单地使用
/root/<root>
{
let $values := (value5, value6, value7)
return (
* except $values,
if ($values) then <countries>{ $values }</countries> else ()
)
}
</root>
您可以使用 xsl:for-each-group
.
如果想在不等于"somevalue"的时候进行分组,那么可以用group-adjacent
来判断元素值是否等于"somevalue",然后将那些不在 <country>
元素中的包裹起来。
您可以在 MarkLogic 中的 XQuery 模块中执行 XSLT,如下所示:
xquery version "1.0-ml";
declare variable $doc := document {
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
};
declare variable $grouping-xslt :=
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent=". = 'somevalue'">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:copy-of select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<country>
<xsl:copy-of select="current-group()"/>
</country>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>;
xdmp:xslt-eval($grouping-xslt, $doc)
如果您有一个已知的国家/地区名称序列并希望作为分组依据,那么您可以使用 group-by
进行此操作并测试该值是否与任何国家/地区名称相匹配:
xquery version "1.0-ml";
declare variable $doc := document {
<root>
<value1>somevalue</value1>
<value2>somevalue</value2>
<value3>somevalue</value3>
<value4>somevalue</value4>
<value5>Australia</value5>
<value6>India</value6>
<value7>USA</value7>
<value8>somevalue</value8>
<value9>somevalue</value9>
<value10>somevalue</value10>
</root>
};
declare variable $grouping-xslt :=
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:param name="countries" />
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" group-by=". = $countries">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<country>
<xsl:copy-of select="current-group()"/>
</country>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>;
declare variable $params := map:new(map:entry("countries", ("Australia", "India", "USA")));
xdmp:xslt-eval($grouping-xslt, $doc, $params)