Как обернуть несколько узлов в документе XML на новый узел с помощью XQuery? - PullRequest
0 голосов
/ 30 августа 2018

Я хочу обернуть несколько узлов (определенных узлов) в новый отдельный узел в моем 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 to value7 являются названиями стран, я хочу поместить их в один и тот же родительский узел. Вывод должен выглядеть так:

output-

<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>

Точно так же, если мои другие значения принадлежат некоторым другим полям / свойствам, я хочу обернуть их в новый отдельный узел.

Есть предложения?

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

Вы можете достичь того, что вы пытаетесь сделать в XSLT, используя xsl:for-each-group.

Если вы хотите сгруппировать их, когда значение не равно «somevalue», то вы можете использовать group-adjacent, чтобы проверить, равно ли значение элемента «somevalue» или нет, а затем обернуть те, которые не находятся в элемент <country>.

Вы можете выполнить XSLT в вашем модуле XQuery в MarkLogic, например:

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)
0 голосов
/ 30 августа 2018

Для смежных элементов вы можете 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)).

Если вам нужно только обернуть все элементы value5, value6, value7, тогда я думаю, что вы можете просто использовать

/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

...