Disons que j'ai un document comme celui-ci:

    <root>
    <content>
        <z>valZ</z>
        <a>
            <b>
                <c>valC</c>
            </b>
            <b>
                <c>valC</c>
            </b>
        </a>
        <a>
            <d>valD</d>
        </a>
    </content>
</root>

Le nombre de nœuds "a" peut être compris entre 1 et un nombre indéfini ne dépassant pas 30 Le nombre de nœuds "b", "c" et "d" peut être compris entre 0 et un nombre non défini ne dépassant pas 20 également

Ce que je dois faire dans XQuery est de prendre la valeur du nœud "z" et de la copier sur chaque nœud "b" existant pour que la structure ressemble à chaque fois à ceci:

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

S'il y a même un bloc "b", je n'ai pas besoin d'en créer un autre, il suffit de mettre "z" à l'intérieur (ou à l'intérieur de plusieurs "b" si plus de 1 est présent) sinon dans chaque "a" dont j'ai besoin pour en créer un nouveau.

Mon fichier build.xml est le suivant:

Mais j'ai du mal à parcourir le même document et à le mettre à jour avec la bibliothèque inmemupdate.xq. Voici un extrait du code que j'utilise pour cela:

    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>
    )
    };

Le problème est évidemment d'insérer la méthode. Comme mentionné précédemment, j'utilise la bibliothèque mem, plus précisément une fonction:

   declare function mem:node-insert-child(
    $parentNode as element(),
    $newNode as node()*
    ) as node()

Et si j'écris insertNodeInBLevel et insertNodeInALevel comme ça:

    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))
    };

Il me renvoie plusieurs copies du document rootDoc avec des valeurs ajoutées à différents endroits au lieu d'un document avec des nœuds ajoutés à tous les endroits.

J'ai essayé de nombreuses solutions, y compris la récursivité et la boucle:

   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))
    };

Mais, eh bien, les valeurs sont immuables, donc je ne peux pas enregistrer dans le même rootDoc pour la deuxième fois et ainsi de suite ... Toute idée de comment résoudre ce problème, donc je vais éditer plusieurs nœuds du même document et retourner seulement celui-ci, pas son copies? Je suis développeur de langages orientés objet, les langages fonctionnels sont assez nouveaux pour moi et ils suivent un paradigme différent, en tant que tel peut-être ma façon de penser la solution est-elle imparfaite ...

2
Mateusz Chrzaszcz 4 janv. 2016 à 16:22

3 réponses

Meilleure réponse

Si vous pouvez utiliser XQuery Update (je pense que cela nécessite les variantes commerciales de Saxon, mais il existe également d'autres implémentations avec prise en charge de XQuery Update), utilisez simplement une transformation et insérez le nœud <z/> dans chaque {{ Nœud X1}}:

let $z := //z
return
  copy $result := /root
  modify
    for $node in $result//b
    return insert node $z into $node
  return $result

Sinon, parcourir l'arborescence de manière récursive et la reconstruire tout en la modifiant au besoin est un modèle courant dans 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)

Il existe une liste d'exemples d'utilisation de ce modèle pour différents cas d'utilisation dans le wikibook XQuery.

2
Jens Erat 4 janv. 2016 à 15:10

Cela peut être accompli avec un typewitch et une logique personnalisée pour ces éléments

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()))}
};
4
Mads Hansen 4 janv. 2016 à 16:41

Ce genre de chose est tellement plus facile en 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>
4
Michael Kay 5 janv. 2016 à 14:43