Comment puis-je supprimer les nœuds en double dans XQuery?
-
22-07-2019 - |
Question
Je génère un document XML à la volée et j'ai besoin d'une fonction pour en éliminer les nœuds en double.
Ma fonction ressemble à:
declare function local:start2() {
let $data := local:scan_books()
return <books>{$data}</books>
};
Exemple de sortie:
<books>
<book>
<title>XML in 24 hours</title>
<author>Some Guy</author>
</book>
<book>
<title>XML in 24 hours</title>
<author>Some Guy</author>
</book>
</books>
Je ne veux qu'une entrée dans la balise racine de mes livres, et il existe d'autres balises, comme par exemple une brochure dans celle-ci, pour lesquelles il est nécessaire de supprimer les doublons. Des idées?
Mise à jour des commentaires suivants. Par nœuds uniques, j'entends supprimer plusieurs occurrences de nœuds ayant exactement le même contenu et la même structure.
La solution
Une solution XPath one-liner plus simple et plus directe :
Utilisez simplement l'expression XPath suivante :
/*/book
[index-of(/*/book/title,
title
)
[1]
]
Lorsqu'il est appliqué, par exemple, sur le document XML suivant :
<books>
<book>
<title>XML in 24 hours</title>
<author>Some Guy</author>
</book>
<book>
<title>Food in Seattle</title>
<author>Some Guy2</author>
</book>
<book>
<title>XML in 24 hours</title>
<author>Some Guy</author>
</book>
<book>
<title>Food in Seattle</title>
<author>Some Guy2</author>
</book>
<book>
<title>How to solve XPAth Problems</title>
<author>Me</author>
</book>
</books>
l'expression XPath ci-dessus sélectionne correctement les nœuds suivants :
<book>
<title>XML in 24 hours</title>
<author>Some Guy</author>
</book>
<book>
<title>Food in Seattle</title>
<author>Some Guy2</author>
</book>
<book>
<title>How to solve XPAth Problems</title>
<author>Me</author>
</book>
L’explication est simple: pour chaque livre
, sélectionnez une seule de ses occurrences, de sorte que son index dans tous les livres soit identique au premier index de son titre
dans tous les titres .
Autres conseils
Vous pouvez utiliser la fonction distinct-values ??()
intégrée ...
Une solution inspirée de la programmation fonctionnelle. Cette solution est extensible en ce sens que vous pouvez remplacer la comparaison " = = par votre code personnalisé booléen n (log n)
en triant la liste à l'avance et en ne comparant qu'avec le successeur immédiat.
À ma connaissance, les fonctions fn: valeurs distinctes
(ou fn: éléments distincts
) ne permettent pas d'utiliser une propriété personnalisée fonction de comparaison.
declare function local:deduplicate($list) {
if (fn:empty($list)) then ()
else
let $head := $list[1],
$tail := $list[position() > 1]
return
if (fn:exists($tail[ . = $head ])) then local:deduplicate($tail)
else ($head, local:deduplicate($tail))
};
let $list := (1,2,3,4,1,2,1) return local:deduplicate($list)
J'ai résolu mon problème en implémentant une fonction de recherche récursive d'unicité, basée uniquement sur le contenu textuel de mon document pour la correspondance d'unicité.
declare function ssd:unique-elements($list, $rules, $unique) {
let $element := subsequence($rules, 1, 1)
let $return :=
if ($element) then
if (index-of($list, $element) >= 1) then
ssd:unique-elements(insert-before($element, 1, $list), subsequence($rules, 2), $unique)
else <test>
<unique>{$element}</unique>
{ssd:unique-elements(insert-before($element, 1, $list), subsequence($rules, 2), insert-before($element, 1, $unique))/*}
</test>
else ()
return $return
};
Appelé comme suit:
declare function ssd:start2() {
let $data := ()
let $sift-this :=
<test>
<data>123</data>
<data>456</data>
<data>123</data>
<data>456</data>
<more-data>456</more-data>
</test>
return ssd:unique-elements($data, $sift-this/*, ())/*/*
};
ssd:start2()
sortie:
<?xml version="1.0" encoding="UTF-8"?>
<data>123</data>
<data>456</data>
Je suppose que si vous avez besoin d'une correspondance légèrement différente, vous pouvez modifier la correspondance dans l'algorithme en conséquence. De toute façon, vous devriez commencer.
Qu'en est-il de fn: distinct-values?
Pour supprimer les doublons, j'utilise généralement une fonction d'assistance. Dans votre cas, ça va ressembler à ça:
declare function local:remove-duplicates($items as item()*)
as item()*
{
for $i in $items
group by $i
return $items[index-of($items, $i)[1]]
};
declare function local:start2() {
let $data := local:scan_books()
return <books>{local:remove-duplicates($data)}</books>
};
Vous pouvez utiliser cette fonction functx: functx: distinct-deep
Pas besoin de réinventer la roue