Ver Mensaje Individual
  #11  
Antiguo 13-12-2005
Avatar de roman
roman roman is offline
Moderador
 
Registrado: may 2003
Ubicación: Ciudad de México
Posts: 20.269
Reputación: 10
roman Es un diamante en brutoroman Es un diamante en brutoroman Es un diamante en bruto
Como ya te comentó David, la implementación específica dependerá del sistema de foros que uses y yo desconozco por completo el que mencionas.

El resaltado en sí se hace mediante un script PHP. Por el momento no tengo pensado publicarlo pero puedo darte la idea general, que, en mi opinión, es mucho mejor que poner el código final.

Dado que estamos hablando de PHP, muevo además este hilo al foro correspondiente.

La base del resaltado es la identificación de los distintos tokens a resaltar mediante el uso de expresiones regulares.

A grandes rasgos, una expresión regular es un patrón que permite identificar porciones de texto. Sin duda todos hemos escrito alguna vez algo como:


Código:
msdos>dir arch.*bk
"arch.*bk" es un patrón que indica que buscamos todas las coincidencias de nombres de archivo que comiencen con "arch." y terminen con "bk". El asterisco * es un comodín que especifica que entre "arch." y "bk" puede haber cualquier cosa.

Sin embargo, el uso de comodines es una aproximación muy limitada a las expresiones regulares. PHP utiliza las expresiones PCRE (Perl Compatible Regular Expressions) que permiten detectar una amplia gama de patrones.

Por ejemplo, la expresión


Código:
[a-z_][a-z0-9_]*
detecta porciones de texto que:
  • comiencen con una letra minúscula o un símbolo de subrayado, es decir, con cualquier caracter del conjunto [a-z_]
  • que esté seguida de cualquier caracter alfanumérico o símbolo de subrayado
  • cualquier número de veces. El asterisco significa 0 o más ocurrencias.

Esto es, el patrón identificará, por ejemplo:


Código:
_hola
h
hola
hola2
pero no identificará


Código:
2hola
En otras palabras, la expresión regular detecta todos los identificadores válidos en la sintaxis de Pascal.

Otro ejemplo de expresión regular:


Código:
'.*'
detecta todo lo que esté entre apóstrofes '. El punto . indica cualquier caracter y el asterico, como antes, indica 0 o más ocurrencias. Como es de esperarse, la expresión detectará todas las cadenas dentro del código Delphi. Pero este caso presenta un problema. Si en el código pascal encontramos algo como:


Código:
Cadena := 'esta es ' + 'una cadena';
se resaltará, según se ve, también el símbolo +, lo cual es incorrecto. Esto es así porque el patrón detecta todo entre los apóstrofes de los extremos, y eso incluye los apóstrofes de enmedio.

Para evitar esto hay que utilizar la directiva ? para indicar que se detecte "lo mínimo posible":


Código:
'.*?'

Con esto, el compilador de expresiones regulares parará en el segundo apóstrofe indicando que la cadena 'esta es ' coincide con el patrón y volverá a comenzar en el tercer apóstrofe deteniéndose nuevamente en el cuarto indicando que 'una cadena' también satisface el patrón.

Una situación similar se presenta con los bloques de comentarios. La expresión regular sería:


Código:
{.*?}
Es decir, sustituyendo los apóstrofes por llaves. Sólo que en este caso hay otra consideración a tener en cuenta. Las llaves tienen un significado especial dentro de una expresión regular- sirven para expresar más precisamente el número de ocurrencias, por ejemplo


Código:
[a-z]{3,4}
es un patrón que detecta porciones de texto con tres o cuatro letras exactamente.

Para poder incluir las llaves (y otros caracteres especiales) como parte de la expresión regular, deben "escaparse" con el símbolo de escape \. Así, la expresión para bloques de comentarios quedaría:


Código:
\{.*?\}
Ahora bien, la sintaxis de Delphi permite otros dos tipos de comentarios, los encerrados entre (* y *) y los comentarios de línea que comienzan con //. Las expresiones regulares serían:


Código:
\(\*.*?\*\)

//.*?\n
Notemos que los paréntesis y asteriscos deben escaparse ya que tienen un significado especial en una expresión regular.

\n, en los comentarios de línea significa "final de línea", de manera que el patrón detectará todo lo que comience con // y termine cuando termine la línea.

Dos expresiones regulares, X y Z, pueden unirse con el operador | de disyunción:


Código:
X|Z
indicando que una porción de texto coincidirá con el patrón si coincide con X O si coincide con Z. Podemos entonces agrupar las distintas clases de comentarios en un sólo patrón:


Código:
\{.*?\}|\(\*.*?\*\)|//.*?\n|//.*$

Aquí agregué


Código:
//.*$
para abarcar el caso especial de un comentario de línea que esté al final del archivo.


Código:
unit Hola;

interface

implementation

end.

// esto es todo amigos
En este caso no se encontrará \n puesto que no hay cambio de línea. El símbolo $ indica "fin de texto".


Con esto detectamos las partes más importantes para el resaltado de código pascal:
  • identificadores
  • cadenas de caracteres
  • bloques de comentarios

PHP incluye varias funciones para el manejo de expresiones regulares tipo Perl. La que usé en el resaltador es preg_replace_callback:


Código PHP:
echo preg_replace_callback("#$reg_exp#si"'replace'$code
El primer parámetro es la expresión regular. Toda expresión regular debe encerrarse entre dos caracteres iguales (# en este caso). Puede ser cualquier caracter que no forme parte de la expresión regular ni sea un caracter especial. Después del caracter de cierre se ponen modificadores de la expresión. Aquí, i significa que las coincidencias son indiferentes a mayúsculas o minúsculas, esto es, da lo mismo procedure que PROCEDURE. Sin este modificador, la expresión regular para los identificadores (ver arriba) sólo serviría para los escritos en minúsculas. El modificador s sirve para detectar los cambios de línea \n.

Ahora bien, lo que yo paso en $reg_exp, es la concatenación de todas las expresiones regulares descritas:


Código PHP:
$reg_exp = ($rgxp_comment)|($rgxp_quote)|($rgxp_ident
donde cada una de las tres variables, $rgxp_comment, $rgxp_quote y $rgxp_ident son las expresiones de comentarios, cadenas e identificadores. Todas están unidas por la disyunción | significando que habrpá una coincidencia si coincide con alguna de esas expresiones. Además, todas están encerradas entre paréntesis. Ya había mencionado que los paréntesis son especiales; sirven para agrupar expresiones. Estas agrupaciones nos servirán al momento de reemplazar las coincidencias ya que es necesario identificar con qué parte (o grupo) de la expresión se encontró una coincidencia.

Básicamente, cada coincidencia se reemplazará con el mismo texto encontrado pero rodeado de etiquetas <span>, por ejemplo, en


Código:
cadena := 'esta es una cadena';
la cadena 'esta es una cadena' se reemplazará con:


Código:
<span class="quote">'esta es una cadena'</span>
Así, el texto final se combinará con una hoja de estilo CSS que indicará de qué color y formato se marcan los distintos <span>.

El tercer parámetro de preg_replace_callback es el texto que deseamos resaltar y el segundo parámetro es el nombre de una función que es la que efectuará los reemplazos:


Código PHP:
function replace($matches)
{
  ...

preg_replace_callback analiza todo el texto pasado en el tercer parámetro y llamará a nuestra función por cada coincidencia que encuentre pasando un arreglo ($matches) como parámetro.

En este arreglo, el primer elemento, $matches[0] contiene la porción de texto que haya coincidido, $matches[1] la parte que haya concidido con el primer grupo, $matches[2] la parte que haya coincidido con el segundo grupo, etc.

En nuestra función de reemplazo tenemos entonces que comparar $matches[0] con cada grupo para saber qué reemplazar:


Código PHP:
function replace($matches)
{
  switch (
$matches[0])
  {
    case 
$matches[1]: // comentario
      
return "<span class='comment'>$matches[0]</span>";
      break;

    case 
$matches[2]: // cadena
      
return "<span class='quote'>$matches[0]</span>";
      break;

    case 
$matches[3]: // identificador
      
...
      break;
  }


Como se ve, la función debe regresar el texto reemplazado. En el caso de identificadores, no todo identificador es una palabara reservada, así que debemos cotejar el identificador contra una lista de palabras reservadas:


Código PHP:
case $matches[3]: // identificador
  
if (in_array($matches[0], $keywords))
    return 
"<span class='keyword'>$matches[0]</span>";
  else
    return 
$matches[0]; 
es decir, reemplazas si el identificador se encuentra en el arreglo $keywords que previamente se llena con todas las palabras reservadas. Si no coincide con ninguna, se devuelve el texto sin reemplazar. También se podría optar por resaltar los identificadores que no son palabras reservadas:


Código PHP:
case $matches[3]: // identificador
  
if (in_array($matches[0], $keywords))
    return 
"<span class='keyword'>$matches[0]</span>";
  else
    return 
"<span class='ident'>$matches[0]</span>"

Esto más o menos cubre todo lo necesario para el resaltado. Cabe notar que podríamos simplificarlo usando preg_replace en lugar de preg_replace_callback. Una vez para los comentarios, otra vez para las cadenas y una tercera vez para las palabras reservadas (preg_replace acepta un areglo como parámetro indicando que han de reemplazarse todos los elementos que encuentre en el arreglo), pero me pareció más flexible así ya que además podemos ampliar la expresión regular para abarcar, por ejemplo:
  • directivas al compilador
  • números (enteros, reales o hexadecimales)
  • símbolos (, ; < > = @ etcétera)
  • código en ensamblador (directiva asm en Delphi)

Obviamente, conviene un mediano entendimiento del uso de expresiones regulares para lo cual, además del mismo manual de PHP, podrás encontrar infinidad de tutoriales en la red. Yo, de hecho aprendí con éste.

// Saludos
Responder Con Cita