Gestion des espaces dans le DOM

Le problème

La présence d'espaces et de blancs dans le DOM peut rendre la manipulation de l'arbre de contenu difficile dans des aspects qu'on ne prévoit pas forcément. Dans Mozilla, tous les espaces et blancs dans le contenu texte du document original sont représentés dans le DOM (cela ne concerne pas les blancs à l'intérieur des balises). (C'est nécessaire en interne afin que l'éditeur puisse conserver le formatage des documents et que l'instruction white-space: pre en CSS fonctionne.) Cela signifie que :

  • il y aura certains nœuds texte qui ne contiendront que du vide, et
  • certains nœuds texte commenceront ou se termineront par des blancs.

En d'autres termes, l'arbre DOM pour le document qui suit ressemblera à l'image ci-dessous (où « \n » représente un retour à la ligne) :

<!-- My document -->
<html>
<head>
  <title>My Document</title>
</head>
<body>
  <h1>Header</h1>
  <p>
    Paragraph
  </p>
</body>
</html>

Ceci peut rendre les choses un peu plus difficiles pour les utilisateurs du DOM qui aimeraient parcourir le contenu, sans se préoccuper des blancs.

Rendre les choses plus faciles

Le code JavaScript ci-dessous définit plusieurs fonctions facilitant la manipulation d'espaces dans le DOM :

/**
 * Les blancs sont définis comme l'un de ces caractères
 *  "\t" TAB \u0009
 *  "\n" LF  \u000A
 *  "\r" CR  \u000D
 *  " "  SPC \u0020
 *
 * On n'utilise pas les "\s" de JavaScript car les espaces insécables
 * (et quelques autres caractères) en font partie.
 */


/**
 * Détermine si un nœud texte est entièrement composé de blancs
 *
 * @param nod  Un nœud implémentant l'interface |CharacterData| (c'est-à-dire,
 *             un nœud |Text|, |Comment| ou |CDATASection|)
 * @return     Vrai si tout le contenu texte de |nod| est composé de blancs,
 *             faux dans les autres cas.
 */
function is_all_ws( nod )
{
  // Utilisation des fonctionnalités String et RegExp d'ECMA-262 Edition 3
  return !(/[^\t\n\r ]/.test(nod.data));
}


/**
 * Détermine si un nœud devrait être ignoré par les fonctions itérateurs.
 *
 * @param nod  Un objet implémentant l'interface DOM1 |Node|.
 * @return     vrai si le nœud est :
 *                1) un nœud |Text| composé uniquement de blancs
 *                2) un nœud de commentaire |Comment|
 *             et faux dans les autres cas.
 */

function is_ignorable( nod )
{
  return ( nod.nodeType == 8) || // Un nœud de commentaire
         ( (nod.nodeType == 3) && is_all_ws(nod) ); // un nœud texte, uniquement des blancs
}

/**
 * Version de |previousSibling| qui passe les nœuds entièrement constitués
 * de blancs ou les commentaires. (Normalement |previousSibling| est une propriété
 * de tous les nœuds DOM qui donne le nœud du même niveau, qui est lethat is
 * un enfant du même parent, qui se trouve immédiatement avant le nœud 
 * de référence.)
 *
 * @param sib  Le nœud de référence.
 * @return     Soit :
 *               1) Le nœud de même niveau le plus proche de |sib| qui n'est pas
 *                  ignorable d'après |is_ignorable|, ou
 *               2) null si un tel nœud n'existe pas.
 */
function node_before( sib )
{
  while ((sib = sib.previousSibling)) {
    if (!is_ignorable(sib)) return sib;
  }
  return null;
}

/**
 * Version de |nextSibling| qui passe les nœuds qui sont entièrement constitués de
 * blancs ou les commentaires.
 *
 * @param sib  Le nœud de référence.
 * @return     Soit :
 *               1) Le nœud suivant de même niveau le plus proche de |sib| qui n'est pas
 *                  ignorable selon |is_ignorable|, ou
 *               2) null si un tel nœud n'existe pas.
 */
function node_after( sib )
{
  while ((sib = sib.nextSibling)) {
    if (!is_ignorable(sib)) return sib;
  }
  return null;
}

/**
 * Version de |lastChild| qui passe les nœuds qui sont entièrement constitués de
 * blancs ou les commentaires.  (Normalement |lastChild| est une propriété
 * de tous les nœuds DOM qui donne le dernier des nœuds contenus directement
 * dans le nœud de référence.)
 *
 * @param sib  Le nœud de référence.
 * @return     Soit :
 *               1) Le dernier enfant de |sib| qui n'est pas
 *                  ignorable selon |is_ignorable|, ou
 *               2) null si un tel nœud n'existe pas.
 */
function last_child( par )
{
  var res=par.lastChild;
  while (res) {
    if (!is_ignorable(res)) return res;
    res = res.previousSibling;
  }
  return null;
}

/**
 * Version de |firstChild| qui passe les nœuds qui sont entièrement constitués de
 * blancs ou les commentaires.
 *
 * @param sib  Le nœud de référence.
 * @return     Soit :
 *               1) The premier enfant de |sib| qui n'est pas
 *                  ignorable selon |is_ignorable|, ou
 *               2) null si un tel nœud n'existe pas.
 */
function first_child( par )
{
  var res=par.firstChild;
  while (res) {
    if (!is_ignorable(res)) return res;
    res = res.nextSibling;
  }
  return null;
}

/**
 * Version de |data| qui ne renvoie pas les blancs en début et en fin
 * de chaîne et normalise tous les blancs en un seul espace. (Normalement,
 * |data| est une propriété des nœuds textes qui donne le texte du nœud.)
 *
 * @param txt  Le nœud texte dont les données doivent être renvoyées
 * @return     Une chaîne donnant le contenu du nœud texte avec
 *             tous les blancs réduits à un simple espace.
 */
function data_of( txt )
{
  var data = txt.data;
  // Utilisation des fonctionnalités String et RegExp d'ECMA-262 Edition 3
  data = data.replace(/[\t\n\r ]+/g, " ");
  if (data.charAt(0) == " ")
    data = data.substring(1, data.length);
  if (data.charAt(data.length - 1) == " ")
    data = data.substring(0, data.length - 1);
  return data;
}

Exemple

Le code qui suit montre l'utilisation des fonctions présentées plus haut. Il parcourt les enfants d'un élément (dont les enfants sont tous des éléments) pour trouver celui dont le texte est "Ceci est le troisième paragraphe", et change ensuite l'attribut class et le contenu de ce paragraphe.

var cur = first_child(document.getElementById("test"));
while (cur)
{
  if (data_of(cur.firstChild) == "Ceci est le troisième paragraphe.")
  {
      cur.className = "magic";
      cur.firstChild.data = "Ceci est un paragraphe magique.";
  }
  cur = node_after(cur);
}

Informations sur le document original

Étiquettes et contributeurs liés au document

Étiquettes : 
 Contributeurs à cette page : ethertank, Mgjbot, BenoitL
 Dernière mise à jour par : ethertank,