Descomprimiendo código resaltado y comprimido

botones contenedor código resaltado Todas las acciones de JavaScript relacionadas con el resaltado de texto se encuentra en el módulo resaltador.js que vinculo en la páginas que necesiten resaltar texto. Ahí se encuentra la función descomprimirResaltado(). Comprimir el código resaltado reduce el tamaño total, especialmente cuando el texto es grande y tiene muchos espacios o tabuladores seguidos. Como expliqué en el primer tema de esta serie, resaltar código supone incrementar el tamaño pues hemos de albergar el formato de colores con elementos extra como span. Pero al comprimirlo incluso podemos obtener una reducción de tamaño. Por ejemplo, el código sin minimizar de la página de inicio de este sitio ocupa actualmente 43.1 KB. Tras resaltarlo y comprimirlo se reduce hasta 41.7 KB con una reducción de un -3.2 %. Si no lo hubiésemos comprimido, el código resaltado ocuparía 87.1 KB con un incremento de un 102 %.

La ventaja en la reducción de tamaño es significativa. Pero para descomprimirlo hay que usar JavaScript. Esto hay que tenerlo en cuenta pues si tenemos muchos trozos de código en una página quizás es preferible traerlos sin comprimir, aunque suponga incrementar algo el tamaño del documento, puesto que la tarea de descomprimir puede alargar en demasía la carga final de la página. En este apartado y los siguientes se exponen todas las funciones que, como esta para descomprimir, usa el contenedor de código resaltado: numerar líneas, ver texto plano para copiar e imprimir código resaltado.

La función para descomprimir tiene el siguiente código actualmente (todos los códigos expuestos en esta página son los que actualmente estoy usando, pero puede que los modifique en el futuro. Para ver la versión más actualizada vea ese enlace al archivo resaltador.js):

Este propio código resaltado lo estamos cargando en ese contenedor en el window.onload de este documento con el script siguiente, donde he abreviado el contenido de la variable con los puntos suspensivos:

<script>
    window.onload = function(){
        var contenidoResaltado = '1`~*@?!^|,span_<span class="l"><hfunction> descomprimirResaltado(aqui, cadena){@?1<htry> ... ');
        descomprimirResaltado(document.getElementById("codigo1"), contenidoResaltado);
    };
</script>  

La variable contenidoResaltado trae el código resaltado y comprimido, como ya expliqué en el primer tema de esta serie. Empieza la cadena con un descriptor como 1`~*@?!^|,span_ cuya estructura es:

  • Un número 1|2 para indicar el número de letras que tienen las clases de estilo. Yo uso clases como class="a" por lo que siempre el descriptor empezará con un 1. Si tienen más de una letra entonces aquí aparecerá el número 2.
  • Luego vienen 8 caracteres de escape para comprimir el código. Estos escapes no tienen porqué ser siempre los mismos, pues la herramienta resaltadora los elige de un conjunto de 10 caracteres !$*+`?@^|~ entre aquellos que menos aparezcan en el código. En todo caso el orden de aparición en el descriptor distingue a cada uno:
    1. El escape (`) (Nota)Vuelvo a repetir que estos caracteres sólo sirven para ese código, pues la herramienta los elige entre los que menos aparecen en ese texto. Para otro texto estos caracteres serían otros y/o podrían estar dispuestos en otro orden. sustituye la cadena &lt;, a su vez un escape HTML. Con esto reducimos 3 caracteres por cada aparición de &lt;.
    2. El (~) sustituye a &gt;.
    3. El (*) sustituye una comilla simple '. Es necesaria escaparla pues el valor de la variable contenidoResaltado se encerrará siempre entre comillas simples.
    4. El (@) sustituye a un salto de línea \n para evitar complicaciones dentro de la variable JavaScript.
    5. El (?) señala el inicio de una serie consecutiva de espacios. Si encontramos algo como ?23 esto se traduce por una cadena de 23+3=26 espacios (ver explicación más abajo).
    6. El (!) hace lo mismo que el anterior pero para una serie consecutiva de espacios tabuladores.
    7. El (^) sirve para identificar el final de una serie consecutivas de espacios o tabuladores, cuyo inicio se identificó con los puntos anteriores.
    8. El y último escape (|) indica el inicio de un vínculo.
  • Una coma , que separa el descriptor en dos campos.
  • Una cadena para declarar un tag HTML, como span, como elemento donde incluir el formato del resalte.
  • Una guion bajo _ que indica el fin del descriptor.

Sobre el indicador final de espacios podemos decir que aparecerá solamente si la cadena de espacios precede algún caracter númerico. Por ejemplo, con el texto         712con 8 espacios antes de la cadena "712", el código resaltado y comprimido sería 1@^|~?`$*,span_?5$712. El ?5 identifica una cadena de 5+3=8 espacios. Siempre se suma 3 al identificador, de tal forma que ? serían 3 espacios, ?1 serían 4, ..., ?n serían n+3 espacios. No tiene ningún aprovechamiento comprimir menos de 3 espacios pues el escape ocupará lo mismo o más que esos espacios. El caracter $ señala (en este ejemplo) el final del número de espacios pues de otra forma no sabríamos diferenciar el número 5 del número 712 representado en el texto. Si después de ?5 hubiera cualquier caracter que no fuese un dígito, la herramienta no pondría el escape de final de espacios pues no habría confusión.

El resaltador permite incluir vínculos <a href="..."> en el código que se configura con el código de elementos como <link rel="stylesheet" href="..."> o <script src="...">. Por ejemplo en un código como el siguiente ya resaltado, podemos ver que hay un vínculo:

<script src="/res/inc/general.js"></script>

Si además lo comprimimos la cadena resultante sería la siguiente (NOTA)Los caracteres angulares y una letra como <c...> es la compresión de <span class="c">...</span> con los puntos suspensivos siendo el contenido propio del código para ese span:

1@^|~?`$*,span_<c@script> <dsrc>=<e"*/res/inc/general.js"><c^@/script^>

En este caso el escape de vínculo es *, observándose como todo lo que se encuentre tras ese escape hasta la aparición de la comilla de cierre es tratado por la herramienta como el destino de un vínculo. Al descomprimir se convierte en <a href="/res/srv/resaltador.php?u=...">...</a>, poniendo en los huecos ese destino. La página del resaltador.php nos sirve para exponer el código resaltado y comprimido de cualquier archivo HTML, CSS, JavaScript o PHP de este sitio. Desde el botón de Código en la barra de la cabecera de cada documento se puede acceder a esta utilidad.

Una vez explicado en que consiste el código resaltado y comprimido es más fácil (espero) entender la función descomprimirResaltado(aqui, cadena). El primer argumento es una referencia a un elemento pre donde se volcará el código resaltado ya descomprimido. El segundo argumento es el código comprimido. El script empieza validando el descriptor, pues si está mal formado no se podrá descomprimir adecuadamente. Poco más hay que decir, solo apuntar este par de detalles:

  • La herramienta que resalta código deja intactos los saltos de línea \n. Estos pueden perfectamente presentarse en un elemento <pre>. En la cadena comprimida ya se han escapado con alguno de los caracteres del descriptor, tal como indicamos más arriba. Al descomprimir los vamos a reponer como saltos HTML, es decir, con <br /> en lugar de \n. Esto nos facilitará usar expresiones regulares sin el modificador multilínea, aparte de otros efectos que ya expliqué en el tema anterior.
  • Una vez descomprimido el código hemos de meterlo en un elemento <pre>. Lo normal es hacer elemento.innerHTML = cadenaResaltada. Pero el navegador Internet Explorer resume más de un espacio en un único espacio cuando incorporamos literales con innerHTML. La solución pasa por construir una función para esto:
    function externalHtml(aqui, interior) {
        var tag = aqui.tagName.toLowerCase();
        var atributos = aqui.attributes;
        var cad = "";
        for (var i=0; i<atributos.length; i++) {
            if (cad != "") cad += " ";
            cad += atributos[i].name + '="' + atributos[i].value + '"';
        }
        return '<' + tag + ' ' + cad + '>' + interior + '</' + tag + '>';
    }
    Se trata de usar aqui.outerHTML = externalHtml(aqui, cadena) pues IE si respeta los espacios con outerHTML. Componemos de nuevo el elemento extrayendo su tag y sus atributos, incorporando el código resaltado en su interior.

Numerando líneas del código resaltado

Numerar las líneas de código es una utilidad que suele ofrecerse. Mi herramienta que resalta el código tiene la posibilidad de incluirlas, pero yo prefiero no hacerlo para que ocupe menos espacio inicialmente. Aunque luego ofrezco la posibilidad de añadirlas on-line con esta función numerarResalte():

El funcionamiento es sencillo. Recuperamos el HTML del contenedor con pre.innerHTML. Si ese texto viene con saltos de línea \n (o \r\n) los sustituimos por <br />. Si el código ya tiene números de líneas la acción de la función eliminará los numeros, en otro caso los agregará. Para detectar si ya hay números de línea miramos si empieza con <span class="b">1 , aunque como estamos leyendo HTML recuperado con innerHTML en el caso de IE habrá omitido las comillas en estos atributos. Si no hay números de línea los agregamos sustituyendo cada aparición de <br /> por ese salto más un número correlativo. Esto, por ahora, no he podido hacerlo sólo con expresiones regulares y he tenido que usar un bucle. Aunque hago un cambio temporal con una serie consecutiva de caracteres que no estarán en el texto, puesto que hay que reemplazar cada aparición de un salto con un número correlativo. Finalmente volvemos a usar externarHtml() para incorporar el literal HTML en el contenedor.

Extrayendo texto plano del código resaltado

La función textoPlanoResalte() nos sirve para extraer el texto plano del contenedor, es decir, de un fragmento HTML. La necesidad de usar un <textarea> para extraer el texto plano ya la expliqué en el tema anterior. La función es la siguiente:

En principio parecería que haciendo pre.innerText, es decir, recuperando el texto interno del contenedor, sería suficiente para extraer el texto. Pero IE no respeta los saltos de línea. Entonces extraemos el literal HTML del contenedor con pre.innerHTML y ahora reemplazamos los saltos de línea <br> (tal como salen del navegador) por un salto de línea de texto \r\n, puesto que el contenido irá a parar a un textarea que debe recibir este tipo de salto. Luego eliminamos todos los elementos <...> de resalte y convertimos los escapes HTML &amp;, &lt; y &gt; en sus originales &, < y >. Ahora ya sí metemos el texto en el <textarea>. Por último igualamos dimensiones y posicionamiento así como el scroll o barras de desplazamiento para que el texto se ubique en la misma posición.

Imprimir código resaltado del contenedor

Imprimir el código resaltado es otra opción que se ofrece en la nueva barra de botones del contenedor. Hay dos iconos, uno   nos permite visualizar una vista previa del contenido. El otro   enviará directamente al cuadro de diálogo de la impresora el contenido. Esta utilidad hace uso de la función imprimirTrozo() contenida en general.js que nos permite imprimir el contenido de cualquier elemento de la página. Esto se explica en el tema sobre imprimir páginas web. Haciendo uso de esa función ahora preparamos lo necesario para lanzar una impresión del contenedor con la función imprimirResalte():

La función imprimirTrozo(pre, inHead, vistaPrevia) necesita la referencia al elemento cuyo contenido se va a imprimir (argumento pre), un opcional literal HTML para agregar al <head> de la página a imprimir y un booleano para indicar si queremos una vista previa o imprimir directamente. Para incluir en el <head> se envía un enlace a la hoja de estilo resaltador.css que se necesita para ver los colores del texto resaltado. También se envía el estilo del contenedor que usaremos al imprimir.