2010-08-06 25 views
23

¿Cómo seleccionaría todas las tablas entre la tabla cuyo id es encabezado_completado y la primera tabla después del encabezado_completado que tiene un alineamiento de centro? Aquí está el código HTML estoy seleccionando desde:Seleccionar hermanos entre dos nodos usando XPath

<table border="0" cellpadding="0" cellspacing="0" width="920" align="center"></table> 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> 
<table border="0" cellpadding="0" cellspacing="0" width="920" align="center" class="header_completed"></table> 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> <-- 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> <-- 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> <-- these 5 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> <-- 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> <-- 
<table border="0" cellpadding="0" cellspacing="0" width="920" align="center"></table> 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> 
<table border="0" cellpadding="0" cellspacing="0" width="920"></table> 
<table border="0" cellpadding="0" cellspacing="0" width="920" align="center"></table> 

He intentado utilizar //table[@id="header_completed"]/following-sibling::node()[following-sibling::table[@align="center"][1]], pero no funcionó.

+0

Buena pregunta (+1). Vea mi respuesta para una explicación y una solución completa. –

+0

Maldición, realmente has hecho tu mejor esfuerzo para hacer una buena pregunta difícil. Para obtener más información sobre la utilidad y el diseño robusto, me gustaría expandirlo a 2 instancias de '[@ class = 'header_completed']' ... algunos nodos ... '[@ align = 'center' ] ', y capturando esos 2 conjuntos en 1 go. – Wrikken

+0

+1 para una buena pregunta. –

Respuesta

24

Creo que esta expresión XPath selecciona los nodos que desee:

//table[@class="header_completed"]/ 
    following-sibling::table[@align="center"][1]/ 
     preceding-sibling::table[ 
      preceding-sibling::table[@class="header_completed"] 
     ] 

Primero navego al table con @class="header_completed".

A partir de ahí, selecciono la primera tabla de hermanos siguiente con @align="center".

De allí selecciono todas las tablas de hermanos anteriores que tienen un hermano anterior que es la tabla con @class="header_completed".

+3

+1 para una solución realmente viable sin posiciones codificadas, buen pensamiento. Sin embargo, expandiéndolo, me preguntaba si tendrías alguna idea sobre la posibilidad que planteé en los comentarios a la pregunta: ¿y si tenemos 2 secuencias entre nodos que son válidas, es decir, si copiamos el ejemplo después de sí mismo, cómo para obtener los 10 nodos resultantes (entre 'match1_of_start' y' match1_of_end' + 'match2_of_start' &' match2_of_end') en lugar de toda la lista (entre 'match1_of_start' y' match2_of_end'). – Wrikken

+0

+1 Una gran solución y una que es fácil de comprender. Muchas gracias. – Alex

+0

@Wrikken Eso es un desafío seguro. Tendré que pensar en eso. – mwittrock

25

Utilice el método Kayessian de intersección conjunto de nodos:

La intersección de dos conjuntos de nodos $ns1 y $ns2 es evaluadas por la siguiente expresión XPath:

$ns1[count(.| $ns2)=count($ns2)] 

Si nos tiene el siguiente documento XML:

<t> 
    <table border="0" cellpadding="0" cellspacing="0" width="920" align="center"></table> 
    <table border="0" cellpadding="0" cellspacing="0" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="0" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="0" width="920" align="center" class="header_completed"></table> 
    <table border="0" cellpadding="0" cellspacing="1" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="2" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="3" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="4" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="5" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="0" width="920" align="center"></table> 
    <table border="0" cellpadding="0" cellspacing="0" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="0" width="920"></table> 
    <table border="0" cellpadding="0" cellspacing="0" width="920" align="center"></table> 
</t> 

entonces de acuerdo con la pregunta, tenemos:

$ns1 es:

/*/*[@class='header_completed'][1] 
        /following-sibling::* 

$ns2 es:

/*/*[@class='header_completed'][1] 
      /following-sibling::*[@align='center'][1] 
        /preceding-sibling::* 

simplemente sustituimos $ns1 y $ns2 en la fórmula Kayessian y obtenemos la siguiente expresión XPath, que selecciona exactamente los 5 elementos deseados TS:

/*/*[@class='header_completed'][1] 
         /following-sibling::* 
       [count(.|/*/*[@class='header_completed'][1] 
          /following-sibling::*[@align='center'][1] 
           /preceding-sibling::*) 
       = 
       count(/*/*[@class='header_completed'][1] 
          /following-sibling::*[@align='center'][1] 
           /preceding-sibling::*) 
       ] 

para verificar que esto es realmente la solución, que utilizan esta transformación XSLT:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 

<xsl:variable name="ns1" select= 
     "/*/*[@class='header_completed'][1] 
         /following-sibling::* 
     "/> 
    <xsl:variable name="ns2" select= 
     "/*/*[@class='header_completed'][1] 
       /following-sibling::*[@align='center'][1] 
         /preceding-sibling::* 
     "/> 

    <xsl:template match="/"> 
     <xsl:copy-of select= 
     "$ns1[count(.| $ns2)=count($ns2)] 
     "/> 
     <DELIMITER/> 
     <xsl:copy-of select= 
     "/*/*[@class='header_completed'][1] 
         /following-sibling::* 
       [count(.|/*/*[@class='header_completed'][1] 
          /following-sibling::*[@align='center'][1] 
           /preceding-sibling::*) 
       = 
       count(/*/*[@class='header_completed'][1] 
          /following-sibling::*[@align='center'][1] 
           /preceding-sibling::*) 
       ] 
     "/> 
    </xsl:template> 
</xsl:stylesheet> 

Cuando se aplica esta transformación en el documento XML anterior, el resultado correcto se desea es producido:

<table border="0" cellpadding="0" cellspacing="1" width="920"/> 
<table border="0" cellpadding="0" cellspacing="2" width="920"/> 
<table border="0" cellpadding="0" cellspacing="3" width="920"/> 
<table border="0" cellpadding="0" cellspacing="4" width="920"/> 
<table border="0" cellpadding="0" cellspacing="5" width="920"/> 
<DELIMITER/> 
<table border="0" cellpadding="0" cellspacing="1" width="920"/> 
<table border="0" cellpadding="0" cellspacing="2" width="920"/> 
<table border="0" cellpadding="0" cellspacing="3" width="920"/> 
<table border="0" cellpadding="0" cellspacing="4" width="920"/> 
<table border="0" cellpadding="0" cellspacing="5" width="920"/> 

XPath 2.0 Solu ción:

En XPath 2.0 que puede utilizar el operador intersect y la >> y/o los << operadores.

La 2,0 expresión XPath que corresponde a la expresión XPath 1.0 utilizado anteriormente es:

 /*/*[ . 
     >> 
     /*/*[@class='header_completed'][1] 
     ] 

    intersect 

    /*/*[ /*/*[@class='header_completed'][1] 
       /following-sibling::*[@align='center'][1] 
      >> 
       . 
     ] 

Aquí es una solución XSLT 2.0, lo que demuestra la exactitud de esta XSLT 2.0 expresión:

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

<xsl:variable name="ns1" select= 
    "/*/*[ . 
     >> 
     /*/*[@class='header_completed'][1] 
     ] 
    "/> 

    <xsl:variable name="ns2" select= 
     "/*/*[ /*/*[@class='header_completed'][1] 
       /following-sibling::*[@align='center'][1] 
      >> 
       . 
      ] 
     "/> 

<xsl:template match="/"> 
    <xsl:sequence select="$ns1 intersect $ns2"/> 
    <DELIMITER/> 
    <xsl:sequence select= 
    "/*/*[ . 
     >> 
     /*/*[@class='header_completed'][1] 
     ] 

    intersect 

    /*/*[ /*/*[@class='header_completed'][1] 
       /following-sibling::*[@align='center'][1] 
      >> 
       . 
     ] 
    "/> 
</xsl:template> 
</xsl:stylesheet> 

cuando se aplica en el documento XML definido antes, de nuevo obtenemos la misma deseaba, resultado correcto:

<table border="0" cellpadding="0" cellspacing="1" width="920"/> 
<table border="0" cellpadding="0" cellspacing="2" width="920"/> 
<table border="0" cellpadding="0" cellspacing="3" width="920"/> 
<table border="0" cellpadding="0" cellspacing="4" width="920"/> 
<table border="0" cellpadding="0" cellspacing="5" width="920"/> 
<DELIMITER/> 
<table border="0" cellpadding="0" cellspacing="1" width="920"/> 
<table border="0" cellpadding="0" cellspacing="2" width="920"/> 
<table border="0" cellpadding="0" cellspacing="3" width="920"/> 
<table border="0" cellpadding="0" cellspacing="4" width="920"/> 
<table border="0" cellpadding="0" cellspacing="5" width="920"/> 
+0

+1 Solución muy elegante. Un problema menor: has codificado el uso del elemento * third * con '@ align = 'center'', mientras que OP indica el * primer elemento después de *' @ class =' ​​header_completed'' que tiene '@ align = 'centro' –

+0

@ Niels-van-der-Rest: Sí, gracias por notar esto. Actualizaré mi respuesta más tarde hoy. –

+0

+1 Una respuesta muy perspicaz y una que es muy útil. Tenía grandes explicaciones y pruebas. Gracias. – Alex

Cuestiones relacionadas