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.

¿Fue útil?

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top