Quisiera comentar un proyecto en el cual, lamentablemente, no he podido
avanzar demasiado por mi inexperiencia con Yacc y Lex.
Siendo que el PHP se usa principalmente para generar páginas de web y, creo
que cada vez más en el futuro también servicios de web (por ejemplo noticias
mediante RSS), esto implica emitir XML o HTML. En general, para no ser
repetivivo, en adelante me referiré al conjunto HTML (y todas sus
variedades) y XML simplemente como XML.
Es cierto que herramientas como Smarty u otros procesadores de templates
disminuyen la cantidad de XML generado y que mucho puede ser generado por
funciones o clases que encapsulen toda la generación de 'tags' de tal manera
que el programa de aplicación tenga poco de XML en el, sin embargo, no
importa cuántas capas se pongan entre medio de la aplicación y el navegador
o el programa cliente del servicio de web, el caso es que antes o después,
en algún punto se han de generar tags de XML.
PHP, como cualquier otro lenguaje de programación estructurado, y XML
comparten el concepto de bloque. En PHP cada bloque se encierra entre
llaves, en XML entre un tag de inicio y uno de final. Aunque se pueden
encontrar excepciones, es natural que ambos bloques se aniden de forma
compatible. Con esto quiero decir que, si uno emite un <p> dentro de un
condicional if(), es natural que antes de terminar el bloque if() se cierre
el <p> con un </p>. No hacerlo así implicaría desbalancear los bloques y
esto no estaría correcto (es cierto que un navegador puede ignorarlo y
presentarlo bien, pero no es el caso). Esto quiere decir que el bloque <p>
queda totalmente anidado dentro del bloque if().
Por ello, se me ocurrió que sería muy bueno que la estructura de bloques de
XML quedara claramente manifiesta dentro de la estructura de bloques de PHP
con una sintaxis compatible, de alguna forma, que el bloque de XML se
representara con instrucciones nativas de PHP y que sus bloques se
encerraran con llaves, al igual que los de PHP.
Asi es como se me ocurrió el agregado de dos instrucciones nuevas para PHP,
TAG y ATT, que se abreviarían con los símbolos < y @. Agregar instrucciones
a un lenguaje no es cosa fácil, implicaría un 'fork', una práctica que no es
bueno alentar. Por ello se me ocurrió que podría hacerse mediante un
precompilador, tal como inicialmente el C++ era un precompilador que
generaba código fuente en C.
Estas instrucciones se podrían usar de la siguiente forma, por ejemplo, un
párrafo de texto alineado sobre derecha se podría codificar así:
<p {
@align 'right';
echo 'Este es el texto alineado sobre derecha';
}
El <p emitiría un '<p', dejando pendiente el cierre del tag de inicio a la
espera de qué encuentre más adelante. El @align indicaría un atributo (en
inglés al signo @ se le llama 'at sign' de allí que lo usara para denominar
al ATributo). En este caso, el atributo es align y su valor el literal
'right'. Luego viene la instrucción echo, que forzaría al precompilador a
generar el cierre del tag de inicio, o sea el '>' y emitir el texto que se
le indica. Finalmente, la llave del cierre de bloque debe extraer la pila
el simbolo que representa la instrucción conque se ha iniciado el bloque, en
este caso la instruccion '<' y su valor 'p', con lo cual emitirá un '</p>'.
En definitiva, el ejemplo de código precedente habrá generado lo siguiente:
echo '<p', ' align="right"', '>', 'Este es el texto alineado sobre derecha',
'</p>';
En este código he omitido, por claridad, la simple optimización de ir
concatenando los literales para hacer una única cadena.
Formalmente, las instrucciones podría expresarse de la siguiente manera:
< etiqueta instrucción
Donde:
< es el símbolo '<',
etiqueta es un nombre válido de una etiqueta de XML
instrucción puede ser una única instrucción o un bloque encerrado entre
llaves, como en el ejemplo.
Alternativamente se puede prever que el símbolo < fuera seguido por una
variable, que sería fácil de distinguir de la etiqueta literal por compenzar
con $.
A su vez, la instrucción ATT se podría expresar como:
@nombre expresión
Donde:
@ es el símbolo '@',
nombre es un nombre válido de atributo (como en el caso anterior, podría
ser una variable)
expresión es cualquier expresión de PHP que genere el valor que se le
quiera dar al atributo.
Para mostrar el uso de variables, el ejemplo anterior podría escribirse así:
$parrafo = 'p';
$alineamiento = 'align';
$derecha = 'right';
$texto = 'Este es el texto alineado sobre derecha';
<$parrafo {
@$alineamiento $derecha;
echo $texto;
}
Que generaría:
echo '<',$parrafo,' ' ,
$alineamiento,'="',$derecha,'"',">",$texto,'</',$parrafo,'>';
Cuál es la razón de todo esto? Primero porque se me ocurrió, como afición,
que sería interesante que hiciera algo con el Yacc y el Lex, aunque admito
que me superó. Esto puede no ser compartido con los potenciales usuarios,
así que tiene que haber algo más.
Yo he llegado a programar en assembler y en montones de lenguajes que ya
casi no existen. Recuerdo cuando los lenguajes estructurados comenzaron a
aparecer. Alguien aún se acuerda de la instrucción Goto? Bueno, el goto
permitía saltar de un punto cualquiera de un programa a cualquier otro
lugar. Nada de estructura, nada de bloques, todo se podía. A este se le
llamaba 'código spaghetti' pues era todo enredado, imposible de seguir. El
código estructurado impedía esta libertad y nos costó bastante trabajo
darnos cuenta que eran más las ventajas que las limitaciones. El código era
más simple, mejor estructurado, más fácil de entender y todo ello llevaba a
que tuviera menos errores.
Actualmente, de la forma que generamos XML lo que estamos haciendo es muy
parecido al 'código spaghetti' que eran nuestros programas. Como en
definitiva no hacemos más que concatenar cadenas de texto arbitrarias,
podemos generar lo que querramos sea para bien o mal. Los estructura
impuesta por los lenguajes estructurados mejoró la calidad del código. Me
gustaría ver esa misma mejora de calidad a nivel del XML que generamos.
Al usar el mismo tipo de estructura para el código PHP que para el XML, una
ventaja es que podemos usar dos herramientas muy habituales de cualquier IDE
para validarlas. Por un lado un Code Beautifier permite hermosear ambos
bloques, por otro, la función de buscar llaves apareadas nos permite
asegurarnos de no dejar ningún tag sin cerrar. De las comillas que deben
encerrar los argumentos de un atributo, ni hablar pues esas las generaría el
precompilador por sí solo y no hay nada de qué preocuparse.
Finalmente, como una validación aún más estricta, podríamos declarar a qué
DTD o Schema debe responder el código XML generado, de tal maneras de
validar aquello que fueramos a generar a nivel compilador. Deberíamos
disponer de una declarativa que permitiera indicar el DTD o Schema y un
segundo argumento, opcional, que indicara la entidad o el xpath del Schema a
que debe responder una función, pues una función o clase, en general, sólo
generará un segmento del Schema. Esta misma declaración no sólo permitiría
validar el XML que genera la función sino que, además, permitiría verificar
que la función es llamada sólo en el punto adecuado de la estructura global
del XML de la página.
Dado que yo también uso templates y funciones para mis aplicaciones, debo
admitir que he debido buscar un poco entre mis fuentes para encontrar alguno
en que realmente hubiera generado HTML directamente, pero aquí les pongo un
ejemplo más completo de lo que propongo. Aprovecho para mostrar cómo
resuelvo el asunto de llenar un combo con valores dependientes de otro. En
este caso, cargo las provincias en función del pais seleccionado.
Les comento que al reescribirlo dentro de mi IDE, me encontré conque al
final me aparecía la última llave subrayada en rojo, claro indicio de que
estaba desapareada. El Code Beautifier me permitió encontrar rápidamente el
error, cosa que pude hacer a nivel del código fuente, en lugar de lo
habitual que es darle una corrida y ver qué ocurrió.
// Según $IdDireccion contenga un valor mayor que cero o no, sé si es un
registro existente para mostrar
// o un nuevo para generar.
if ($IdDireccion) {
// si existe, lo consulto
$row = mysql_query_row('
Select
Direccion1,Direccion2,CodPos,Ciudad,Direcciones.IdProvincia,Paises.IdPais
from Direcciones
inner join Provincias on Direcciones.IdProvincia =
Provincias.IdProvincia
inner join Paises on Provincias.IdPais = Paises.IdPais
where IdDireccion = '
. $IdDireccion
);
$IdPais = $row['IdPais'];
$IdProvincia = $row['IdProvincia'];
} else {
// si es un registro nuevo ($IdDireccion == 0) entonces cargo los
valores predeterminados de país y provincia
$IdPais = LeeParam('PaisPredeterminado', 'AR');
$IdProvincia = LeeParam('ProvinciaPredeterminada', 1);
}
// En este iframe es dónde se cargará dinámicamente el combo correspondiente
a las provincias.
// Inicialmente usa el pais predeterminado, luego es recargado a partir del
combo de paises
// Noten que aquí comienza el uso de las instrucciones < y @
<IFRAME {
@Id 'iFrameProvincias';
@src 'Select.php?Name=IdProvincia&Select=1&Valor=' . $IdPais;
@onLoad
'document.getElementById("xProvincias").innerHTML=iFrameProvincias.document.body.innerHTML';
@style 'display:none';
}
// Noten que en los atributos no estoy limitado a poner solamente cadenas de
caracteres,
// también puedo usar variables.
// El form esta ya abierto, no aparece dentro de este código
<input {
@type 'hidden';
@name 'IdDireccion';
@value $IdDireccion;
}
// Noten a partir de aquí cómo la estructura de la tabla se hace tan obvia
como la
// estructura misma del código en PHP
<table {
<tr {
<th {
@rowspan 2;
@valign "top";
// Aquí he usado otra abreviatura que no he mencionado, el signo
? en lugar de 'echo'
? 'Dirección';
}
<td {
<input {
@name 'Direccion1';
@size 50;
@value $row['Direccion1'];
}
}
}
// noten que el uso de las llaves es tan opcional como lo es en PHP.
// Si un bloque está compuesto de una única instrucción, no es necesario
encerrarlo entre llaves.
<tr <td <input {
@name 'Direccion2';
@size 50;
@value $row['Direccion2'];
}
<tr {
// Aqui vuelve a repetirse lo mismo. No es necesario poner las
llaves en el TH y el TD
// pero si lo ha sido en el TR precedente, pues encierra dos
instrucciones
// y también lo es en el INPUT, pues encierra dos instrucciones, en
este caso atributos.
<th 'Código Postal';
<td <input {
@name 'CodPos';
@value $row['CodPos'];
}
}
<tr {
<th 'Ciudad';
<td <input {
@name 'Ciudad';
@size 50;
@value $row['Ciudad'];
}
}
<tr {
<th 'Provincia';
<td {
@Id 'xProvincias';
// El contenido del combo es provisto por esta función que,
// a partir de una instrucción de SQL (la función ArmaSql la he
comentado en otro mensaje)
// genera el HTML correspondiente a un combo con el nombre
indicado en el primer argumento
// las duplas valor => descripción provistas por el SQL del
segundo argumento
// y si hubiera un tercer argumento, será el valor
preseleccionado
// Noten cómo es posible mezclar literales, variables,
funciones.
MuestraCombo(
'IdProvincia'
, CargaCombo(ArmaSql('Select IdProvincia,Descr from
Provincias where IdPais = ?s order by Descr' , $IdPais))
, $IdProvincia
);
}
}
<tr {
<th 'País';
// Este combo es el que selecciona pais y en función del pais
seleccionado
// recarga el combo de provincias con las que le correspondan.
// Noten en la función MuestraCombo un cuarto argumento que no se
usó en la llamada anterior
// este cuarto argumento es un array de la forma evento => acción
<td MuestraCombo('IdPais'
, CargaCombo('Select IdPais,Descr from Paises order by Descr')
, $IdPais
, array('onChange' =>
"document.getElementById('iFrameProvincias').src =
'Select.php?Name=IdProvincia&Select=1&Valor=' + this.value ")
);
}
}