2009-03-13 14 views
16

Ho un documento XML che ho generato al volo e ho bisogno di una funzione per eliminare eventuali nodi duplicati da esso.Come posso rimuovere i nodi duplicati in XQuery?

La mia funzione è simile:

declare function local:start2() { 
    let $data := local:scan_books() 
    return <books>{$data}</books> 
}; 

Esempio di output è:

<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 quella voce nella mia tag libri radice, e ci sono altri tag, come dire pamphlet in là Anche questo ha bisogno di avere i duplicati rimossi. Qualche idea?


Aggiornamento dei seguenti commenti. Per nodi univoci, voglio dire rimuovere più ricorrenze di nodi che hanno esattamente lo stesso contenuto e struttura.

risposta

16

Un semplice e più soluzione di XPath one-liner diretta:

Basta utilizzare la seguente espressione XPath:

/*/book 
     [index-of(/*/book/title, 
        title 
       ) 
        [1] 
     ] 

Quando applicata, ad esempio, il 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 book, selezionare solo una delle sue occorrenze - tale che il suo indice in all-libri è il come il primo indice del suo title in tutti i titoli.

+0

Hey Dimitre, grazie per la risposta; ma se ho capito bene, dipende da tutti gli elementi che hanno la stessa struttura che è incorporata nella query - per esempio mostrerebbe due nodi uguali se avessero lo stesso titolo e autori diversi ... – Brabster

+0

@Brabster È non è affatto chiaro dalla tua domanda come dovrebbe essere definito il test per la disuguaglianza/unicità. Se lo definisci, ti aiuterà a trovare una soluzione più semplice –

+0

Questo non sembra funzionare con XPath 1.0, possiamo ottenere una soluzione XPath 1.0 funzionante? – abarax

1

Ho risolto il problema implementando una funzione di ricerca dell'unicità ricorsiva, basata esclusivamente sul contenuto testuale del mio documento per la corrispondenza di 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> 

Credo che se avete bisogno di un po 'diverso corrispondenza di equivalenza, è possibile modificare l'abbinamento nell'algoritmo di conseguenza. Dovresti iniziare in ogni caso.

5

È possibile utilizzare il built-in funzione di distinct-values() ...

+0

Come puoi usare quello? – obesechicken13

1

Che dire fn: distinct-values?

2

Una soluzione ispirata alla programmazione funzionale. Questa soluzione è estensibile in quanto è possibile sostituire il confronto "=" con la propria funzione personalizzata booleana local:compare($element1, $element2).Questa funzione ha la complessità quadratica nel caso peggiore nella lunghezza dell'elenco. È possibile ottenere la complessità n(log n) ordinando l'elenco prima della consegna e confrontando solo con l'immediato successore.

A mia conoscenza migliore, i fn:distinct-values (o fn:distinct-elements) funzioni non permette di utilizzare una funzione di confronto custom-built.

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) 
+0

Questa soluzione sembra funzionare. Potresti spiegare la riga "fn: exists ($ tail [. = $ Head])"? Ho modificato questo per essere "$ head = $ tail" e funziona. – abarax

0

È possibile utilizzare questa funzione functx: functx: distinta-profonda

Non c'è bisogno di reinventare la ruota

1

Per rimuovere i duplicati io di solito uso una funzione di supporto. Nel tuo caso sarà 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> 
}; 
Problemi correlati