Ir directamente al contenido de esta página
childNodes
y el problema de los nodos de texto vacíosEntre los métodos del DOM contamos con childNodes
para obtener una lista de los nodos hijos de un elemento. Así, por ejemplo, document.getElementsByTagName('body')[0].childNodes.length
nos devolvería el número de hijos del body
de nuestro documento. O casi.
En Firefox tenemos un pequeño problema, y es que los saltos de línea que aparecen en el código cuentan también como hijos, lo que en ocasiones puede provocar resultados inesperados a la hora de seleccionar, por ejemplo, document.getElementsByTagName('body')[0].childNodes
.
Primero, veamos el caso de estudio.
Los ejemplos 1 a 4 que presento aquí presentan una serie de errores al ser validados. Ello se debe a que incluyo los scripts en los propios documentos, para que puedan consultarse por medio de Ver → Ver código fuente de la página. Los errores se deben a que habría que sustituir ciertos caracteres por sus referencias de entidad —por ejemplo &
por &
o <
por <
—, pero en ese caso los scripts no funcionarían.
En el ejemplo número 1, tengo un elemento blockquote
que marca unos versos de Robert Frost:
<blockquote xml:lang="en">
<p>We dance around in a ring and suppose<br />but the secret stands in the middle and knows</p>
<p id="autor"><cite>Robert Frost</cite></p>
</blockquote>
Es decir, el bloque de cita tiene dos hijos, que son los párrafos. Al lanzar la función, en Internet Explorer obtengo correctamente en la alerta 2, pero Firefox me da un resultado de 5. ¿A qué se debe? Pues a está contabilizando como un nodo de texto el salto de línea que tengo en mi documento XHTML.
Si modifico el marcado para tener una sola línea…
<blockquote xml:lang="en"><p>We dance around in a ring and suppose<br />but the secret stands in the middle and knows</p><p id="autor"><cite>Robert Frost</cite></p></blockquote>
…con el mismo script obtengo el resultado de 2, como se puede ver en el ejemplo número 2.
Bueno, una posible solución es escribir todo el código de la página en una sola línea. Claro que es la solución suicida para el desarrollador que en un futuro tenga que modificar o corregir el código.
Otra posible solución es crear un script que elimine los nodos no deseados.
Para solucionar este pequeño problema, Aaron Gustafson en Web Design in a Nutshell (O'Reilly, 2006, pg. 493; la autora del libro es Jennifer Niederst) propone este script:
01 function stripsWS(el){
02 for (var i=0;i<el.childNodes.length;i++){
03 var node = el.childNodes[i];
04 if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
05 node.parentNode.removeChild(node);
06 }
07 }
Lo que hace aquí es crear una función a la que se le puede enviar como parámetro el elemento que se desea limpiar. Para cada hijo del elemento, comprueba si se trata de un nodo de texto (node.nodeType == 3
), y además si su valor es el de salto de línea (!/\S/.test(node.nodeValue)
); en tal caso, elimina el nodo. Aquí se puede ver un ejemplo de su funcionamiento.
Bueno, ya que estamos, mejorémoslo un poco.
Se me ocurre que el script se podría mejorar si pudiera hacer tres cosas más:
Con respecto al punto 3, supongamos que añadiese un comentario al blockquote
ejemplo número 3:
<blockquote xml:lang="en">
<!-- falta localizar la obra, que la tengo en la punta de la lengua -->
<p>We dance around in a ring and suppose<br />but the secret stands in the middle and knows</p>
<p id="autor"><cite>Robert Frost</cite></p>
</blockquote>
Si comprobamos lo que ocurre con el ejemplo 4, sin aplicar la función de Gustafson obtenemos 7 hijos, y aplicándola, 3, con lo que deducimos que en ambos casos el comentario cuenta como hijo. Los comentarios, como tales, son de una gran ayuda para el desarrollo, pero creo que los navegadores no deberían considerarlos parte del árbol del documento: estrictamente, son una herramienta del programador, y no un elemento del documento.
Así pues, primero probé con esta versión:
01 function buscar_y_destruir(){
02 var elementos = document.getElementsByTagName('*');
03 for (var k=0;k<elementos.length;k++){
04 for (var i=0;i<elementos[k].childNodes.length;i++){
05 var hijo = elementos[k].childNodes[i];
06 if ((hijo.nodeType == 3 && !/\S/.test(hijo.nodeValue))||(hijo.nodeType == 8)){
07 hijo.parentNode.removeChild(hijo);
08 }
09 }
10 }
11 }
12 buscar_y_destruir();
Lo que hago es obtener una lista de todos los elementos del documento (línea 2), y después para cada elemento —elegido sucesivamente en el primer bucle— creo un bucle secundario que analiza cada uno de sus hijos (líneas 4 a 9), y si cumplen las condiciones de Gustafson, o su nodeType
es 8 —el valor que identifica los comentarios—, los elimino (líneas 6 a 8). Inmediatamente después lanzo la función para que limpie el documento.
A simple vista puede parecer que la programación es correcta, pero al comprobar su funcionamiento en el ejemplo 5 me encontré con un problema en un primer momento desconcertante: los comentarios incluidos en body
y en blockquote
—que existen se puede comprobar en el ejemplo con Ver → Ver código fuente de la página— no se eliminaban (!).
La solución se merece otro artículo, concretamente «Problemas al eliminar elementos de una lista de nodos».
En definitiva, como donde hay una voluntad hay un camino, al final conseguí lo que pretendía:
01 function buscar(){
02 var elementos = document.getElementsByTagName('*');
03 for (var k=0;k<elementos.length;k++){
04 for (var i=0;i<elementos[k].childNodes.length;i++){
05 var hijo = elementos[k].childNodes[i];
06 if ((hijo.nodeType == 3 && !/\S/.test(hijo.nodeValue))||(hijo.nodeType==8)){
07 nodos_a_eliminar[nodos_a_eliminar.length] = hijo;
08 }
09 }
10 }
11 destruir();
12 }
13
14 function destruir(){
15 for(var d=0;d<nodos_a_eliminar.length;d++){
16 nodos_a_eliminar[d].parentNode.removeChild(nodos_a_eliminar[d]);
17 }
18 }
Ver un ejemplo de su funcionamiento.
Como ya he dicho, para que funcione correctamente debe incluirse el head
del documento.
He comprobado que este script funciona correctamente —sobre Windows— en: