2009-03-13 42 views
16

Tengo un documento XML que genero sobre la marcha, y necesito una función para eliminar cualquier nodo duplicado de él.¿Cómo puedo eliminar nodos duplicados en XQuery?

Mi función será similar a:

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

Salida de ejemplo es:

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

quiero sólo una entrada en mi etiqueta de libros de raíz, y hay otras etiquetas, como dicen folleto en que hay también que necesitan tener duplicados eliminados. ¿Algunas ideas?


Actualizado los comentarios siguientes. Por nodos únicos, me refiero a eliminar múltiples ocurrencias de nodos que tienen exactamente el mismo contenido y estructura.

Respuesta

16

A más simple y más solución XPath de una sola línea directa:

sólo tiene que utilizar la siguiente expresión XPath:

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

Cuando se aplica, por ejemplo, en el siguiente 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> 

la expresión XPath selecciona correctamente por encima de los siguientes nodos:

<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 explicación es simple: por cada book, seleccionar sólo una de sus ocurrencias - de tal manera que su índice en todos los libros es la igual que el primer índice de su title en todos los títulos.

+0

Hola Dimitre, gracias por la respuesta; pero si lo entiendo correctamente, depende de que todos los elementos tengan la misma estructura que está incorporada en la consulta, por ejemplo, mostraría dos nodos iguales si tuvieran el mismo título y diferentes autores ... – Brabster

+0

@Brabster Es para nada claro de su pregunta cómo debería definirse la prueba de la desigualdad/unicidad. Si lo define, le ayudará a encontrar una solución más simple –

+0

Esto no parece funcionar con XPath 1.0, ¿podemos obtener una solución de trabajo XPath 1.0? – abarax

1

Resolví mi problema mediante la implementación de una función de búsqueda recursiva de exclusividad, basada únicamente en el contenido de texto de mi documento para la coincidencia de singularidades.

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

Llamado de la siguiente manera:

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

de salida:

<?xml version="1.0" encoding="UTF-8"?> 
<data>123</data> 
<data>456</data> 

supongo que si usted necesita un poco diferente juego de equivalencia, se puede alterar el juego en el algoritmo en consecuencia. Debería comenzar en cualquier caso.

5

Usted puede utilizar el incorporado en función de distinct-values() ...

+0

¿Cómo puedes usar eso? – obesechicken13

1

¿Qué pasa con fn: valores distintos?

2

Una solución inspirada en la programación funcional. Esta solución es extensible ya que puede reemplazar la "=" por su función de boolean local:compare($element1, $element2) boolean.Esta función tiene el peor caso complejidad cuadrática en la longitud de la lista. Puede obtener la complejidad n(log n) ordenando la lista de antemano y solo comparando con el sucesor inmediato.

A mi leal saber y entender, los fn:distinct-values (o fn:distinct-elements) funciones no permite utilizar una función de comparación hecha a la medida.

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

Esta solución parece funcionar. ¿Podría explicar la línea "fn: exists ($ tail [. = $ Head])"? He modificado esto para que sea "$ head = $ tail" y funciona. – abarax

0

Se puede utilizar esta función functx: functx: distinta de profundidad

No hay necesidad de reinventar la rueda

1

Para eliminar duplicados que suelen utilizar una función de ayuda. En su caso, se verá así:

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