¿Cómo puedo eliminar nodos duplicados en XQuery?
-
22-07-2019 - |
Pregunta
Tengo un documento XML que genero sobre la marcha, y necesito una función para eliminar cualquier nodo duplicado de él.
Mi función se ve así:
declare function local:start2() {
let $data := local:scan_books()
return <books>{$data}</books>
};
La salida de muestra 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 solo una entrada en la etiqueta raíz de mis libros, y hay otras etiquetas, como decir panfleto allí, que necesitan eliminarse los duplicados. ¿Alguna idea?
Actualizados los siguientes comentarios. Por nodos únicos, me refiero a eliminar múltiples ocurrencias de nodos que tienen exactamente el mismo contenido y estructura.
Solución
Una solución XPath de una línea más simple y directa :
Simplemente use 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 anterior selecciona correctamente 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: para cada libro
, seleccione solo una de sus ocurrencias, de modo que su índice en todos los libros sea lo mismo que el primer índice de su título
en todos los títulos .
Otros consejos
Puede usar la función incorporada distinct-values ??()
...
Una solución inspirada en la programación funcional. Esta solución es extensible porque puede reemplazar la comparación " = " por su personalizado booleano local: compare ($ element1, $ element2)
función. Esta función tiene peor caso complejidad cuadrática en la longitud de la lista. Puede obtener la complejidad de n (log n)
al ordenar la lista de antemano y solo compararla con el sucesor inmediato.
Que yo sepa, las funciones fn: distinct-values ??
(o fn: distinct-elements
) no permiten utilizar un función de comparación.
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)
Resolví mi problema implementando una función de búsqueda de unicidad recursiva, basada únicamente en el contenido de texto de mi documento para la coincidencia de unicidad.
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()
salida:
<?xml version="1.0" encoding="UTF-8"?>
<data>123</data>
<data>456</data>
Supongo que si necesita una coincidencia de equivalencia ligeramente diferente, puede modificar la coincidencia en el algoritmo en consecuencia. Debería comenzar de todos modos.
¿Qué pasa con fn: valores distintos?
Para eliminar duplicados, generalmente uso una función auxiliar. 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>
};
Puede usar esta función functx: functx: distinct-deep
No es necesario reinventar la rueda