|

Mini parser para código PHP

En mi blog personal, me ocurre que cuando quiero mostrar código en un post tengo que modificarlo para que quede bonito, poniéndole estilos, etc… La verdad es que es bastante aburrido y no es algo que me guste mucho hacer, me da mucha pereza.

Supongo que hay más gente que se encuentra en mi situación y me entenderán. La solución es usar unas librerías que me modifiquen el código y lo muestre con colores y tabulado. No me he puesto a buscar en Google, pero habrá ya alguna, de todas formas, siempre está bien saber cómo se podría hacer.

php-parser.png

En este caso vamos a explicar como realizar un pequeño parser de código PHP, con tan solo unas funcionalidades: reconoce comentarios, palabras reservadas, funciones, variables y texto entrecomillado, a parte de realizar una mínima tabulación. No reconoce código HTML, ni realiza otras cosas, aunque las ampliaciones son posibles.

Primero se han de definir los estilos necesarios para las palabras reservadas, las funciones, los comentarios, las comillas y las variables:

span.comentario {
color: #AAAAAA;
}
span.comillas {
color: #BB0000;
}
span.reservadas {
font-weight: bold;
color: #0000BB;
}
span.funciones {
color: #5555FF;
}
span.variables {
color: #8A459F;
}

El código tiene dos partes fundamentales, una que se encarga de coger todas las etiquetas code que se encuentran dentro de etiquetas pre, limpiarlas de espacios y luego llamar a la otra función que parsea el código.

function modificaCodigo() {
var pres = document.getElementsByTagName("PRE");
for (var i=0; i<pres.length; i++) {
// Selecionamos los code
var codes = pres[i].getElementsByTagName("CODE");
for (j=0; j<codes.length; j++) {
var texto = codes[j].innerHTML;
// Primero eliminamos los dobles espacios al principio de línea y los tabuladores de inicio de línea
texto = texto.replace(/((^|\n)(\t|\s)+)+/g, "\n");
texto = texto.replace(/^\n+/, "");
texto = parseaTexto(texto);
codes[j].innerHTML = texto;
}
}
}

La parte más importante es la que se encarga de parsear el cógio, la cual tiene dos partes: una que se encarga de parsear los bloques de caracteres, por ejemplo comentarios y texto entrecomillado, y la otra que se encarga de comprobar los caracteres que se pulsan; por ejemplo, si se pulsa una comilla creamos un bloque tipo comentario que luego se trata directamente, y si no es un carácter especial sigue leyendo los caracteres y tratándolos.

El texto se va leyendo carácter a carácter, si encuentra un carácter de inicio de comentario (/) o de inicio de texto entrecomillado (‘, “), avisará al sistema para que trate el texto que sigue como el bloque que corresponda (comentario o texto entrecomillado) hasta que encuentre el carácter último del bloque (salto de línea para comentarios de línea y */ para comentarios de bloque, o comillas simples o comillas dobles para texto entrecomillado). Si no se ha encontrado con uno de los caracteres anteriores, espera hasta que encuentra un carácter separador de palabras (espacio, salto de línea, punto y coma, …) y trata la palabra que se ha ido almacenando hasta entonces, y según sea el tipo de palabra (variable, función, …) la escribe según un estilo.

	function parseaTexto(texto) {
var res = "";
var esComentario = false;
var esComentarioBloque = false;
var esComillaSimple = false;
var esComillaDoble = false;
var posInicioPalabra = 0;
var esInicioPalabra = false;
var esSeparadorPalabra = true;
var nivel = 0;
var palabras = new Array();
// 1= palabras reservadas; 2= funciones
palabras["global"] = 1;
palabras["function"] = 1;
palabras["return"] = 1;
palabras["if"] = 1;
palabras["else"] = 1;
palabras["echo"] = 2;
palabras["print"] = 2;
palabras["count"] = 2;
for (var i=0; i<texto.length; i++) {
// Comprobaciones de tipos de codigo
if (esComentario) {
// Si es comentario normal, guarda el texto hasta el final de línea y luego cierra el span del comentario
try {
if (texto.charAt(i+1) == "\n") {
res += texto.charAt(i)+'</span>';
esComentario = false;
} else {
res += texto.charAt(i);
}
} catch (e) {
res += texto.charAt(i);
}
} else if (esComentarioBloque) {
// Si es comentario normal, guarda el texto hasta el final de línea y luego cierra el span del comentario
if (texto.charAt(i-1)+texto.charAt(i) == "*/") {
res += texto.charAt(i)+'</span>';
esComentarioBloque = false;
} else {
res += texto.charAt(i);
}
} else if (esComillaDoble) {
// Se escribe como comillas hasta que encuentre el fin de comillas
if (texto.charAt(i) == '"') {
res += texto.charAt(i)+'</span>';
esComillaDoble = false;
} else {
res += texto.charAt(i);
}
} else if (esComillaSimple) {
// Se escribe como comillas hasta que encuentre el fin de comillas
if (texto.charAt(i) == "'") {
res += texto.charAt(i)+'</span>';
esComillaSimple = false;
} else {
res += texto.charAt(i);
}
// Comprobaciones de tipo de caracter
} else if (texto.charAt(i) == "/") {
// Si es una barra /, puede ser que sea comentario si la siguiente tambien es "/"
try {
esComentario = texto.charAt(i+1) == "/";
if (!esComentario) {
esComentarioBloque = texto.charAt(i+1) == "*";
}
} catch (e) {
esComentario = false;
esComentarioBloque = false;
}
// Si es comentario empiezo a escribir el span de comentario
if (esComentario || esComentarioBloque) {
res += '<span class="comentario">'+texto.charAt(i);
}
esInicioPalabra = false;
} else if (texto.charAt(i) == '"') {
// Si es una comilla doble
esComillaDoble = true;
res += '<span class="comillas">'+texto.charAt(i);
esInicioPalabra = false;
} else if (texto.charAt(i) == "'") {
// Si es una comilla simple
esComillaSimple = true;
res += '<span class="comillas">'+texto.charAt(i);
esInicioPalabra = false;
// Separador palabras
} else if (texto.charAt(i) == " " ||
texto.charAt(i) == "\n" ||
texto.charAt(i) == "\r" ||
texto.charAt(i) == "\t" ||
texto.charAt(i) == "{" ||
texto.charAt(i) == "}" ||
texto.charAt(i) == "(" ||
texto.charAt(i) == ")" ||
texto.charAt(i) == "[" ||
texto.charAt(i) == "]" ||
texto.charAt(i) == ";") {
esSeparadorPalabra = (!esComentario && !esComentarioBloque && !esComillaSimple && !esComillaDoble);
if (esInicioPalabra) {
var palabra = texto.substring(posInicioPalabra, i);
if (palabra.charAt(0) == "$") {
// Si es variable
res += '<span class="variables">'+palabra+"</span>";
} else {
// Si es palabra reservada, funcion u otra palabra
switch (palabras[palabra]) {
case 1:
res += '<span class="reservadas">'+palabra+"</span>";
break;
case 2:
res += '<span class="funciones">'+palabra+"</span>";
break;
default:
res += palabra;
break;
}
}
res += texto.charAt(i);
if (texto.charAt(i) == "{") {
nivel++;
} else if (texto.charAt(i) == "}") {
nivel--;
} else if (texto.charAt(i) == "\n" || texto.charAt(i) == "\r") {
try {
if (texto.charAt(i+1) != "}") {
for (var j=0; j<nivel; j++) res += "  ";
}
} catch (e) {alert(e)};
}
esInicioPalabra = false;
} else {
res += texto.charAt(i);
if (texto.charAt(i) == "{") {
nivel++;
} else if (texto.charAt(i) == "}") {
nivel--;
} else if (texto.charAt(i) == "\n" || texto.charAt(i) == "\r") {
try {
if (texto.charAt(i+1) != "}") {
for (var j=0; j<nivel; j++) res += "  ";
} else {
for (var j=0; j<nivel-1; j++) res += "  ";
}
} catch (e) {alert(e)};
}
}
// En otro caso
} else {
if (esSeparadorPalabra) {
posInicioPalabra = i;
esSeparadorPalabra = false;
esInicioPalabra = true;
}
//				res += texto.charAt(i);
}
}
return res;
}

Similar Posts