2011-11-30 132 views
6

Tengo un archivo XML que contiene autores y editores.XQuery/XPath: utilizando la función count() y max() para la devolución del elemento con el recuento más alto

<?xml version="1.0" encoding="UTF-8"?> 
<?oxygen RNGSchema="file:textbook.rnc" type="compact"?> 
<books xmlns="books"> 

    <book ISBN="i0321165810" publishername="OReilly"> 
     <title>XPath</title> 
     <author> 
      <name> 
       <fname>Priscilla</fname> 
       <lname>Walmsley</lname> 
      </name> 
     </author> 
     <year>2007</year> 
     <field>Databases</field> 
    </book> 

    <book ISBN="i0321165812" publishername="OReilly"> 
     <title>XQuery</title> 
     <author> 
      <name> 
       <fname>Priscilla</fname> 
       <lname>Walmsley</lname> 
      </name> 
     </author> 
     <editor> 
      <name> 
       <fname>Lisa</fname> 
       <lname>Williams</lname> 
      </name> 
     </editor> 
     <year>2003</year> 
     <field>Databases</field> 
    </book> 

    <publisher publishername="OReilly"> 
     <web-site>www.oreilly.com</web-site> 
     <address> 
      <street_address>hill park</street_address> 
      <zip>90210</zip> 
      <state>california</state> 
     </address> 
     <phone>400400400</phone> 
     <e-mail>[email protected]</e-mail> 
     <contact> 
      <field>Databases</field> 
      <name> 
       <fname>Anna</fname> 
       <lname>Smith</lname> 
      </name> 
     </contact> 
    </publisher> 
</books> 

Estoy buscando una forma de devolver a la persona que ha sido mencionada la mayoría de las veces como autor y/o editor. La solución debe ser compatible con XQuery 1.0 (XPath 2.0).

Estaba pensando en usar una consulta FLWOR para repetir a través de todos los autores y editores, luego hacer un recuento de autores/editores únicos, y luego devolver el (los) autor (es)/editor (es) que coincidan con el recuento más alto. Pero no he podido encontrar la solución adecuada.

¿Alguien tiene alguna sugerencia sobre cómo se escribiría una consulta de FLWOR? ¿Podría hacerse esto de una manera más simple, usando XPath?

Saludos,

Jeanette

Respuesta

15

Esto puede ayudar:

declare default element namespace 'books'; 
(for $name in distinct-values($doc/books/*/*/name) 
let $entries := $doc/books/*[data(*/name) = $name] 
order by count($entries) descending 
return $entries/*/name)[1] 
+0

Gracias por la solución, Christian :) ¿Hay alguna manera de devolver más de un autor/editor (si corresponde)? Por ejemplo, si hay dos autores/editores que comparten el mismo (máximo) conteo como autor/editor? – Jea

+3

@Jea: Tanto en Christian como en mi solución simplemente elimine el final '[1]' y obtendrá todos los nodos que tengan el valor máximo. –

2

Está en el camino correcto. La forma más sencilla es la de convertir los nombres en cadenas (separadas por un espacio, por ejemplo) y el uso de éstos: (Tenga en cuenta que el siguiente código no se ha probado)

let $names := (//editor | //author)/concat(fname, ' ', lname) 
let $distinct-names := distinct-values($names) 
let $name-count := for $name in $distinct-names return count($names[. = $name]) 
for $name at $pos in $distinct-names 
where $name-count[$pos] = max($name-count) 
return $name 

O, otro enfoque:

(
    let $people := (//editor | //author) 
    for $person in $people 
    order by count($people[fname = $person/fname and 
         lname = $person/lname]) 
    return $person 
)[last()] 
+0

@_Oliver: Lo sentimos, pero incluso en XQuery 3.0/XPath 3.0, esto es un error. Sugerencia: observe: '$ names/count (index-of ($ names,.)'. '$ Names' pasa a ser una secuencia de valores atómicos, pero el operador'/'requiere un nodo (-set) como su operando a la izquierda –

+0

@_Oliver: su primer enfoque tampoco produce ningún resultado. Revisado con Saxon 9.3.05 bajo oXygen –

+0

@Dimitre: Buen punto re '/' He eliminado el ejemplo de XPath. Fue una solución horrible de todos modos. –

7

Aquí es una pura expresión XPath 2.0, ciertamente no para los débiles de corazón:

(for $m in max(for $n in distinct-values(/*/b:book/(b:author | b:editor) 
             /b:name/concat(b:fname, '|', b:lname)), 
       $cnt in count(/*/b:book/(b:author | b:editor) 
          /b:name[$n eq concat(b:fname, '|', b:lname) ]) 
       return $cnt 
       ), 
    $name in /*/b:book/(b:author | b:editor)/b:name, 
    $fullName in $name/concat(b:fname, '|', b:lname), 
    $count in count(/*/b:book/(b:author | b:editor) 
        /b:name[$fullName eq concat(b:fname, '|', b:lname)]) 
    return 
    if($count eq $m) 
     then $name 
     else() 
    )[1] 

donde el prefijo "b:" está asociado con el espacio de nombre "books".

XSLT 2.0 - basado en la verificación:

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:b="books"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 
<xsl:strip-space elements="*"/> 

<xsl:template match="/"> 
    <xsl:sequence select= 
    "(for $m in max(for $n in distinct-values(/*/b:book/(b:author | b:editor) 
              /b:name/concat(b:fname, '|', b:lname)), 
        $cnt in count(/*/b:book/(b:author | b:editor) 
           /b:name[$n eq concat(b:fname, '|', b:lname) ]) 
        return $cnt 
        ), 
     $name in /*/b:book/(b:author | b:editor)/b:name, 
     $fullName in $name/concat(b:fname, '|', b:lname), 
     $count in count(/*/b:book/(b:author | b:editor) 
         /b:name[$fullName eq concat(b:fname, '|', b:lname)]) 
     return 
     if($count eq $m) 
      then $name 
      else() 
     )[1] 
    "/> 
</xsl:template> 
</xsl:stylesheet> 

cuando se aplica esta transformación en el documento XML proporcionado:

<books xmlns="books"> 
    <book ISBN="i0321165810" publishername="OReilly"> 
     <title>XPath</title> 
     <author> 
      <name> 
       <fname>Priscilla</fname> 
       <lname>Walmsley</lname> 
      </name> 
     </author> 
     <year>2007</year> 
     <field>Databases</field> 
    </book> 
    <book ISBN="i0321165812" publishername="OReilly"> 
     <title>XQuery</title> 
     <author> 
      <name> 
       <fname>Priscilla</fname> 
       <lname>Walmsley</lname> 
      </name> 
     </author> 
     <editor> 
      <name> 
       <fname>Lisa</fname> 
       <lname>Williams</lname> 
      </name> 
     </editor> 
     <year>2003</year> 
     <field>Databases</field> 
    </book> 
    <publisher publishername="OReilly"> 
     <web-site>www.oreilly.com</web-site> 
     <address> 
      <street_address>hill park</street_address> 
      <zip>90210</zip> 
      <state>california</state> 
     </address> 
     <phone>400400400</phone> 
     <e-mail>[email protected]</e-mail> 
     <contact> 
      <field>Databases</field> 
      <name> 
       <fname>Anna</fname> 
       <lname>Smith</lname> 
      </name> 
     </contact> 
    </publisher> 
</books> 

se selecciona el deseado, correcta name elemento y la salida:

<name xmlns="books"> 
    <fname>Priscilla</fname> 
    <lname>Walmsley</lname> 
</name> 
4

Siempre he sentido que esto era una omisión en XPath: las funciones max() y min() devuelven el valor más alto/más bajo, mientras que lo que generalmente quiere es el objeto en una colección que tiene valor más alto/más bajo para alguna expresión. Una solución es ordenar los objetos en ese valor y tomar el primero/último de la lista, que parece poco elegante. Calcular el min/max y luego seleccionar los ítems cuyo valor coincide con esto parece igualmente desagradable. En Saxon ha habido durante mucho tiempo un par de funciones de extensión de orden superior saxon: highest() y saxon: lowest() que toman una secuencia y una función, y devuelven los elementos de la secuencia que tiene los valores más bajos o más altos de el resultado de la función. La buena noticia es que en XPath 3.0 puede escribir estas funciones usted mismo (de hecho, se dan como ejemplos de funciones escritas por el usuario en la especificación).

+0

¡Un enlace a esos ejemplos sería bueno! – grtjn

Cuestiones relacionadas