Utiliser XPath sur un navigateur Android 2.x

Voici un article qui ne va pas servir à grand monde mais je l’écris pendant que c’est encore chaud. Il existe plusieurs manières de traverser un DOM, que ce soit un DOM HTML ou un DOM XML (puisque bon, du HTML reste du XML).  Vous pouvez utiliser jQuery pour vous faciliter la tâche avec la méthode $.find(query) qui va vous permettre d’atteindre facilement un élément:

var $xml = $(xml); // ici xml = un objet Document
var subNode = $xml.find("SubNode")[0];

Facile :). Mais vous n’utilisez pas forcement jQuery, ou vous n’avez pas envie de l’intégrer à votre page pour X raisons. Dans ce cas-là, vous pouvez toujours utiliser l’API « à l’ancienne » qui est l’API XPath:

http://www.w3schools.com/xpath/

De la même manière, vous allez prendre un noeud XML, lui passer une query au format XPath et vous allez récupérer un noeud XML ou un ensemble de noeud. Comme d’habitude, c’est standard mais tout le monde ne l’a pas implémenté ou pas implémenté de la même manière  ou implémenté avec les pieds.

Si vous utilisez la Google Closure Library (comme moi), vous avez accès à une API qui va se charger de faire les abstractions nécessaires: goog.dom.xml:

http://closure-library.googlecode.com/svn/docs/closure_goog_dom_xml.js.html

Vous avez donc des méthodes comme « goog.dom.xml.selectSingleNode » qui font:

/**
 * Selects a single node using an Xpath expression and a root node
 * @param {Node} node The root node.
 * @param {string} path Xpath selector.
 * @return {Node} The selected node, or null if no matching node.
 */
goog.dom.xml.selectSingleNode = function(node, path) {
  if (typeof node.selectSingleNode != 'undefined') {
    var doc = goog.dom.getOwnerDocument(node);
    if (typeof doc.setProperty != 'undefined') {
      doc.setProperty('SelectionLanguage', 'XPath');
    }
    return node.selectSingleNode(path);
  } else if (document.implementation.hasFeature('XPath', '3.0')) {
    var doc = goog.dom.getOwnerDocument(node);
    var resolver = doc.createNSResolver(doc.documentElement);
    var result = doc.evaluate(path, node, resolver,
        XPathResult.FIRST_ORDERED_NODE_TYPE, null);
    return result.singleNodeValue;
  }
  return null;
};

Pour reprendre l’exemple de tout à l’heure, cela donne:

var subNode = goog.dom.xml.selectSingleNode(xml, "/SubNode");

Dans la méthode goog.dom.xml.selectSingleNode, le if est le comportement spécial IE et le else pour tous les autres navigateurs. Il y a quand même un test intéressant:

if (document.implementation.hasFeature('XPath', '3.0')

On demande au navigateur de confirmer qu’il gère bien XPath avant de faire toute manipulation.

Quand les navigateurs nous mentent

Tout cela fonctionne très bien, sur IE, de vieux FF, Chrome, Safari, etc. Puis j’ai essayé sur une tablette Android 2.2 (et 2 téléphones en 2.2) et là, rien ne se passe. Comme il n’y a pas de console sur le navigateur Android de base, j’ai débuggé à grands coups de alert(…) à toutes les lignes jusqu’à trouver celle qui bloquait:

var resolver = doc.createNSResolver(doc.documentElement);

Avec un try / catch autour, on m’informe que:

object <Document> has no method 'createNSResolver'

Si vous souhaitez en savoir plus sur ce createNSResolver, voici les specs:

https://developer.mozilla.org/en/Introduction_to_using_XPath_in_JavaScript

Après quelques recherche, j’apprend que malgré le fait que le navigateur renvoie bien « true » au test document.implementation.hasFeature(‘XPath’, ‘3.0’), celui-ci n’a aucune des méthodes nécessaires pour réaliser le parse XPath. Un joli mensonge qui fait perdre du temps.

Apparemment, cela semble corrigé par les implémentations > 2.2 de Android, à vérifier. Toujours est-il qu’il reste pas mal de matos en 2.2 et qu’il me faut une solution

Parsers XPath en JavaScript

Lors de mes recherches, je suis tombé sur ce billet:

https://github.com/levand/domina/issues/12

Exactement le même problème que moi et enfin une solution. J’essaie donc d’ajouter le fichier xpath.js dans mon projet (le nom est prometteur):

https://github.com/levand/domina/blob/1.0.0-beta1/public/xpath.js

Cela ne marche toujours pas. Le script étant uniquement disponible en version minifiée, difficile de déterminer s’il faut utiliser une autre méthode ou quoi.

Je tombe ensuite sur une autre implémentation, par « Llama Internet Laboratory »:

http://llamalab.com/js/xpath/

Le nom ne porte pas beaucoup d’espoir mais j’essaie quand même d’intégrer les 2 fichiers dans mon projet. Et magie, cela fonctionne directement. Ce script est plus malin et remplace directement les méthodes de Document, ce qui me permet de ne pas modifier l’implémentation de Google Closure.

Conclusion

Plusieurs conclusions:

  • L’implémentation de  « Llama Internet Laboratory » et la plus light et permet de ne pas modifier les appels XPath
  • Ne faîtes pas forcement confiance à ce que vous dit le navigateur, il est mieux de tester l’existence de propriétés que d’utiliser les méthodes d’interrogation.
  • Essayez de vous passer de XPath et de parcourir le DOM de manière plus « classique », avec goog.dom.query(…) par exemple

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *