Come posso rimuovere nodi duplicati in XQuery?
-
22-07-2019 - |
Domanda
Ho un documento XML che ho generato al volo e ho bisogno di una funzione per eliminare eventuali nodi duplicati da esso.
La mia funzione è simile a:
declare function local:start2() {
let $data := local:scan_books()
return <books>{$data}</books>
};
L'output di esempio è:
<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>
Voglio solo una voce nel tag root dei miei libri, e ci sono altri tag, come dire anche un opuscolo che devono rimuovere i duplicati. Qualche idea?
Aggiornato dopo i commenti. Per nodi univoci, intendo rimuovere più occorrenze di nodi che hanno lo stesso contenuto e la stessa struttura.
Soluzione
Una soluzione XPath one-liner più semplice e diretta :
Basta usare la seguente espressione XPath :
/*/book
[index-of(/*/book/title,
title
)
[1]
]
Se applicato, ad esempio, sul seguente documento XML :
<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'espressione XPath sopra seleziona correttamente i seguenti nodi :
<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>
La spiegazione è semplice: per ogni libro
, seleziona solo una delle sue occorrenze, in modo tale che il suo indice in tutti i libri sia lo stesso del primo indice del suo titolo
in tutti i titoli .
Altri suggerimenti
È possibile utilizzare la funzione integrata distintivo-valori ()
...
Una soluzione ispirata alla programmazione funzionale. Questa soluzione è estensibile in quanto puoi sostituire il " = " con il tuo booleano locale
personalizzato: compare ($ element1, $ element2)
funzione. Questa funzione presenta il caso peggiore complessità quadratica nella lunghezza dell'elenco. È possibile ottenere la complessità n (log n)
ordinando l'elenco in anticipo e confrontandolo solo con il successore immediato.
Per quanto ne so, le funzioni fn: distinti valori
(o fn: distinti elementi
) non consentono di utilizzare un personalizzato funzione di confronto.
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)
Ho risolto il mio problema implementando una funzione di ricerca dell'unicità ricorsiva, basata esclusivamente sul contenuto testuale del mio documento per la corrispondenza dell'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
};
Chiamato come segue:
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()
uscita:
<?xml version="1.0" encoding="UTF-8"?>
<data>123</data>
<data>456</data>
Suppongo che se hai bisogno di una corrispondenza di equivalenza leggermente diversa, puoi modificare la corrispondenza nell'algoritmo di conseguenza. Dovresti iniziare comunque.
Che dire di fn: valori distinti?
Per rimuovere i duplicati di solito utilizzo una funzione di supporto. Nel tuo caso sembrerà così:
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>
};
È possibile utilizzare questa funzione functx: functx: distinto-profondo
Non è necessario reinventare la ruota