Contenido

Usando xPath desde Javascript

10 dic

+ 4

Desde que vi que jQuery implementaba la posibilidad de buscar elementos de nuestra página web mediante expresiones xPath, quedé prendado de la potencia de este matrimonio. Llevo más de un año trabajando con XSL, un lenguaje en el que el xPath es más que importante… es el núcleo del lenguaje.

[Demo]

¿Que es xPath?

La wikipedia nos dice:

XPath (XML Path Language) es un lenguaje (no XML) con una sintaxis fija que permite seleccionar subconjuntos de un documento XML. La idea es parecida a las expresiones regulares para seleccionar partes de un texto sin atributos (plain text). XPath permite buscar y seleccionar teniendo en cuenta la estructura jerárquica del XML.

Exactamente xPath es un conjunto de funciones que nos permite movernos por dentro de el arbol generado por un XML. De esta forma podemos recorrer todas sus ramas obteniendo valores de forma fácil y rápida.

Para ampliar la información, leer tutorial o la especificación del lenguaje.

¿Como trabajar con xPath desde Javascript?

Buscando como hacer que esto fuera posible, encontré este manual que me ayudó a comprender como conseguirlo. Disponemos de un método del objeto Document con el cual podremos evaluar expresiones xPath de forma fácil. Todo este manual está destinado a Mozilla, en Internet Explorer 6.0 no me ha funcionado (no he probado en el 7.0), siguiendo la especificación del DOM 3 XPath.

document.evaluate

Se encarga de evaluar las expresiones xPath dentro de un documento XML (includos los xHTML). Esta función recibirá 5 parametros para devolvernos dentro de un objeto xpathResult uno o más nodos correspondientes al resultado de nuestra expresión evaluada.

 

var xpathResult = document.evaluate( xpathExpression, contextNode, namespaceResolver, resultType, result );

Parametros:

  • xpathExpression: Aqui indicaremos la expresión que deseamos buscar en nuestro XML.
  • contextNode: Se trata del nodo padre del que dependerá la expresión. Document suele ser el más empleado debido a que desde él puedes acceder a cualquier otro nodo.
  • namespaceResolver: En este parámetro indicaremos una función que nos permitirá resolver el prefijo del namespace que contenga nuestra expresión. Esta función puede ser:
  • Created, esta función debe emplearse siempre de modo virtual con el método createNSResolve de xpathEvaluator.
  • null, para documentos HTML o cuando no usamos namespaces.
  • Función definida por el usuario, cualquier funcionalidad que deseemos implementar.
  • resultType: Una constante que especifica el tipo deseado en el que queremos recibir el resultado, el más usado es ANY_TYPE.
    • ANY_TYPE (0): de forma automática decide el mejor tipo recomendado, en caso de devolver un conjunto de nodos revolverá siempre una lista no ordenada de nodos (UNORDERED_NODE_ITERATOR_TYPE).
    • NUMBER_TYPE (1): El resultado contiene un número, generalmente usado para la función count() de xPath.
    • STRING_TYPE (2): El resultado devuelto en un String, usado para devolver el valor del nodo.
    • BOOLEAN_TYPE(3): Devuelve un tipo booleano, usado para comprobar la existencia de un nodo (not()).
    • UNORDERED_NODE_ITERATOR_TYPE(4): Nos devuelve un listado de nodos, que no tienen por que estar colocados de igual forma en que aparezcan en el documento XML o HTML.
    • ORDERED_NODE_ITERATOR_TYPE(5): Un listado de nodos colocados de igual forma que su aparición en el documento.
    • UNORDERED_NODE_SNAPSHOT_TYPE(6): Nos devuelve un snapshot con los nodos resultantes de la expresión, no ordenados.
    • ORDERED_NODE_SNAPSHOT_TYPE(7): Devuelve un snapshot de nodos ordenados según su aparición.
    • ANY_UNORDERED_NODE_TYPE(8): Nos devuelve un solo nodo que no tiene por que ser el primer nodo del cocumento.
    • FIRST_ORDERED_NODE_TYPE(9): Nos devuelve el primer nodo del resultado de la expresión.
  • result: Este parametro está destinado para reutilizar objeto xPathResult, usando null crearemos uno nuevo.
  • Ejemplos de uso

    Usando la función count() de XPath contaremos el número de parrafos dentro de una web.

    var paragraphCount = document.evaluate( 'count(//p)', document, null, XPathResult.ANY_TYPE, null );
    
    alert( 'This document contains ' + paragraphCount.numberValue + ' paragraph elements' );

    Como podemos ver en este ejemplo estamos haciendo referencia al objeto document, osea el padre de nuestra página web para realizar nuestra consulta (//p) que nos buscará cualquier <p></p> que sea hijo de document, usaremos (//) para descender recursivamente.

    Iteradores y Snapshots

    Dependiendo del parametro que hayamos indicado en resultType, actuaremos de modo iterador o de modo snapshot.

    Pero, ¿que diferencia hay?

    Cuando obtenemos un iterador, se trata de un conjunto de nodos resultantes de la evaluación de nuestra expresión, este resultado de nodos no es más que una referencia a los nodos, de forma que si estos cambian, tambien cambiarán en nuestro resultado. En cambio un snapshot, es más bien una copia exacta de los nodos de forma que si los nodos orginales se ven modificados, los almacenados en el snapshot no sufrirán estos cambios.

    Recorrer iteradores

    Usaremos iterateNext() para obtener el siguiente nodo, y mientras vayan existiendo seguiremos con el siguiente,..

    var iterator = document.evaluate('//phoneNumber', documentNode, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null );
    
    try {
      while (thisNode = iterator.iterateNext()) {
        alert( thisNode.textContent );
      }	
    }
    catch (e) {
      dump( 'Error: Document tree modified during iteraton ' + e );
    }

    De esta forma estamos mostrando un alert por cada phoneNumber que encuentre en nuesto XML. 

    Recorrer Snapshot

    Un snapshot es un Array con los nodos resultantes de nuestra expresión de modo que recorrerlos es exactamente igual que si de un Array se tratara.

    var nodesSnapshot = document.evaluate('//phoneNumber', documentNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
    
    for ( var i=0 ; i < nodesSnapshot.snapshotLength; i++ )
    {
      dump( nodesSnapshot.snapshotItem(i).textContent );
    }

    Con Documentos HTML 

    Una gran utilidad que le podemos dar a estas expresiones es la de recorrer nuestro arbol DOM que en sí no es más que un XML formado de tags, que el navegador Web interpreta mostrandonos lo que conocemos como páginas web. 

    var headings = document.evaluate('//h2', document, null, XPathResult.ANY_TYPE, null );
    var thisHeading = headings.iterateNext();
    
    var alertText = 'Level 2 headings in this document are:\n'
    
    while (thisHeading) {
      alertText += thisHeading.textContent + '\n';
      thisHeading = headings.iterateNext();
    }

    Con esta función estamos recogiendo todos los <h2></h2> de nuestro documento y almacenando su contenido en una variable.

    Internet Explorer

    Aparte de usar new ActiveXObject() para generar el objeto Msxml.DOMDocument no he encontrado otra solución, quizas la versión 7.0 si que soporte este método y nos facilite la vida, pero por el momento no tengo una solución que nos sirva. ¿Alguien conoce alguna?

    Demo

    He montado una mini demo de como funciona, podeis verla aqui. Requiere Firefox para funcionar, aunque debería funcionar con los navegadores modernos que incorporen la nueva especificación, si lo has probado con algún otro navegador dime cosas he iré añadiendolo a la lista.

    Comentar

    #

    Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.