【问题标题】:How to wrap several nodes within a XML document to a new node using XQuery?如何使用 XQuery 将 XML 文档中的多个节点包装到新节点?
【发布时间】:2018-08-30 09:04:51
【问题描述】:

我想在我的 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>

同样,如果我的其他值属于其他一些字段/属性,那么我想将它们包装在一个新的单个节点中。

有什么建议吗?

【问题讨论】:

  • 您要用于分组/包装的国家/地区名称列表是否已知?您是否只想对那些相邻的元素进行分组,就像在您的示例中一样,还是将&lt;value&gt;Australia&lt;/value&gt;&lt;value&gt;foo&lt;/value&gt;&lt;value&gt;USA&lt;/value&gt; 的序列也包装为&lt;Country&gt;&lt;value&gt;Australia&lt;/value&gt;&lt;value&gt;USA&lt;/value&gt;&lt;/Country&gt;&lt;value&gt;foo&lt;/value&gt;
  • 是的,它是已知的(元素或国家名称),它们可以彼此相邻或不能彼此相邻。在我的数据库中,我确实有超过一千个文档,所以很难说这些元素是否总是彼此相邻。
  • 为了清楚地描述问题,我们需要知道您是只想用国家值包装相邻元素还是想简单地用国家值包装所有元素,因为第二个选项似乎是一个简单的分组group by $is-country := $value = $countries 而第一个选项最好使用 window 子句完成。因为我不使用 Marklogic,所以我无法帮助替换它,除了编写一个递归函数来收集序列的尾部,只要它找到国家值然后包装它们。
  • 在大部分文件中,我可以看到国家/地区的名称彼此相邻。但是我应该如何在不使用翻滚窗口方法的情况下在 XQuery 中实现这一点。有什么通用的方法吗? Marklogic 确实有类似 xdmp:node-insert-after/xdmp:node-insert-child 的 API,但我不确定它们是否对这种情况有用。

标签: xml xquery marklogic


【解决方案1】:

对于相邻元素,您可以使用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 上也能完成这项工作,我不知道这对 Marklogic 是否有意义。如果您想根据元素名称而不是值进行包装,那么您可以将代码调整为 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))

如果您只需要包装所有value5value6value7 元素,那么我认为您可以简单地使用

/root/<root>
{
    let $values := (value5, value6, value7)
    return ( 
        * except $values, 
        if ($values) then <countries>{ $values }</countries> else ()
    )

}
</root>

https://xqueryfiddle.liberty-development.net/gWcDMeh/7

【讨论】:

  • @Martin--感谢您的回答。你的方法在我看来很先进。但是当我试图在 MarkLogic 的查询控制台中运行相同的命令时,它给了我“翻滚窗口”的错误,就好像它不明白它是什么一样。恐怕 MarkLogic 还没有支持下面帖子中提到的翻滚和滑动窗口概念-developer.marklogic.com/pipermail/general/2015-January/…
  • @Shalini,我希望具有 Marklogic 专业知识的人可以帮助解决在那里工作的问题,我确信该用例之前已经以某种方式实现过。
  • @Martin-- 是的 :) 感谢您向我介绍翻转和滑动窗口的概念。对我来说这是非常新的。
  • @Martin-- 谢谢你给我另一种方法。我会尝试看看是否有帮助。
【解决方案2】:

您可以使用 xsl:for-each-group 在 XSLT 中实现您想要做的事情。

如果你想在值不等于“somevalue”时将它们分组,那么你可以使用group-adjacent来测试元素值是否等于“somevalue”,然后将那些不等于的包裹起来&lt;country&gt; 元素。

您可以在 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)

【讨论】:

  • @Mads-- 感谢您的回答。这对我来说看起来不错,但我有一个问题。您在 $params ("Australia","India","USA") 中获取的值 - 我不关注这些值,而是我想关注它们的节点,即;("value5","value6","值 7")。所有这些都应该包含在我的国家元素之下。我在这些元素中的价值是什么并不重要。它可以是任何国家名称。那么我应该如何根据它修改代码。
  • @Shalini,对于使用 XSLT 完成的相邻分组,您可以将 group-adjacent=". = 'somevalue'" 更改为 group-adjacent="not(self::value5 | self::value6 | self::value7)"。如果您只是想包装所有 value5、value6、value7 元素,那么我认为您可以在 XQuery 中使用 &lt;root&gt;{* except (value5, value6, value7), &lt;countries&gt;{value5, value6, value7}&lt;/countries&gt;}&lt;/root&gt; 轻松做到这一点。
  • @MartinHonnen 非常感谢 - 这对我很有用。我还有一个疑问是我的 $doc 是否有根节点并在它内部 - 它有更多节点,然后是文档结构,然后我应该如何处理它。我的意思是,如果我的文档看起来像 &lt;root&gt;&lt;sibling1&gt;&lt;sibling2&gt; &lt;value1&gt;somevalue&lt;/value1&gt; &lt;value2&gt;somevalue&lt;/value2&gt; &lt;value3&gt;somevalue&lt;/value3&gt;&lt;/sibling1&gt;&lt;/sibling2&gt;&lt;/root&gt;
  • 我不确定我是否对我的问题感到困惑-更简单的形式是,如果我的根节点发生更改并且在 root 中有多个子节点,我应该在 &lt;xsl:template match="root"&gt; 内进行哪些更改
  • 如果根元素是可变的,但您知道要处理根元素的子元素,那么您可以使用&lt;xsl:template match="/*"&gt; 而不是&lt;xsl:template match="root"&gt;。如果您不想处理根元素的子元素,而是处理名为sibling2 的元素的子元素,那么您将编写一个带有分组的模板&lt;xsl:template match="sibling2"&gt;。但不清楚你有哪种结构,你想要哪种结果,最好问一个新问题。
猜你喜欢
  • 1970-01-01
  • 2018-02-03
  • 2021-03-21
  • 1970-01-01
  • 2011-05-29
  • 2013-10-27
  • 1970-01-01
  • 2014-02-01
  • 1970-01-01
相关资源
最近更新 更多