El proyecto
A programar se aprende con una pequeña base teórica, que ya la tenemos, y sobretodo jugando y practicando con las herramientas de programación. De modo, que nos planteamos un proyecto relativamente sencillo para ver con más claridad como se crea un Servicio Web. Se trata de ofrecer la posibilidad de traducir un texto del Inglés al Castellano. Esto sería una versión muy "light" del Servicio Web que ofrece Babelfish de Altavista.
Hemos escogido PHP como lenguaje de programación porque es una preferencia entre muchos programadores Linux, y además es muy claro e intuitivo. El Servicio Web lo vamos a desarrollar paso a paso, porque requiere varias etapas:
- Buscamos un diccionario Inglés-Castellano.
- Implementamos un traductor de palabras individuales en PHP.
- Ampliamos el traductor para procesar textos completos.
- Ofrecemos en Internet el Servicio Web de forma elemental.
- Ampliamos el servicio, para que funcione bajo XML-RPC.
Buscamos un diccionario Inglés-Castellano
Si existiera para Liunx un buen programa traductor, podríamos usarlo en nuestro desarrollo, invocándolo desde línea de comando. Lamentablemente, por mucho que hemos buscado, parece que no hay tales programas aún para nuestras plataforma favorita. Por lo que tendremos que conformarnos con un simple diccionario y efectuar una traducción completamente literal.
Diccionarios sí que podemos encontrar, aunque no muy completos.
Aqui (317 Kb) tenemos el diccionario que utiliza wordtrans. Es un diccionario Inglés-Castellano. Lo descomprimimos como es habitual con un comando como éste: "tar -zxvf i2edict_20010903-2.tar.gz". Obtendremos un fichero llamado i2e.dict. Examinamos la estructura del diccionario:
$more i2e.dict
A (abbr. for ampere) : A (abrev. de amperio)
abacterial : abacteriano
abacus : ábaco
abaft : a popa
abaft : en popa
abaft : hacia popa
abandoned : abandonado
abandonee : abandonado
abandonee : desamparado
abandonment : abandono
...
Podemos apreciar que la manipulación de este diccionario va a resultar sencilla, ya que es un fichero de texto plano con una estructura muy clara.
Implementamos un traductor de palabras individuales en PHP
Suponiendo que tenemos unos conocimientos básicos de PHP, podemos rápidamente desarrollar un script que carga en una tabla hash las palabras del diccionario Inglés => Castellano:
#!/usr/local/bin/php -q
<?
// Se lee el fichero del diccionario
$filename = "i2edict-20010903/i2e.dict";
$fp = @fopen($filename, "r"); if (!$fp) { die("ERROR: Imposible leer el
fichero '$filename'\n");}
while (($linea = fgets($fp, 1024)) && (!feof($fp))) {
// Se eliminan caracteres residuales
$linea = eregi_replace("\([^)]*\)", "", $linea); /* comentarios
entre parentesis */
$linea = eregi_replace(" : ", ":", $linea); /* separacion
entre idiomas */
$linea = eregi_replace("[\n\r]*", "", $linea); /* se elimina el
cambio de linea */
// Se separan los idiomas en 2 variables
$matches = split (":", $linea);
$palabraingles = $matches[0];
$palabraespanol = $matches[1];
// Se meten las palabras en la tabla hash
$ingles2espanol[$palabraingles] = $palabraespanol;
$espanol2ingles[$palabraespanol] = $palabraingles;
}
fclose ($fp);
// Se define una rutina que sepa traducir palabras sueltas del Castellano
al Inglés
function palabra_espanol2ingles($espanol) {
global $espanol2ingles;
if ($espanol2ingles[$espanol] != '') {
return $espanol2ingles[$espanol];
} else {
return $espanol;
}
}
// Se define una rutina que sepa traducir palabras sueltas del Inglés al
Castellano
function palabra_ingles2espanol($ingles) {
global $ingles2espanol;
if ($ingles2espanol[$ingles] != '') {
return $ingles2espanol[$ingles];
} else {
return $ingles;
}
}
// Se muestran dos resultados de ejemplo del traductor
print "árbol => " . palabra_espanol2ingles("árbol") . "\n";
print "house => " . palabra_ingles2espanol("house") . "\n";
?>
El resultado de la ejecución de este script, que hemos decidido llamar "dicionariohash.php", es esto:
$ ./dicionariohash.php
árbol => tree
house => casa
Bien, parece que traduce bien palabra por palabra, en un sentido, como en su opuesto. Para no complicarnos vamos a centrarnos sólo en la traducción del Inglés al Castellano.
Ampliamos el traductor para procesar textos completos
El siguiente objetivo es que este script sea capaz de traducir textos completos, aunque lo haga palabra por palabra, literalmente. A modo de ejemplo, hemos escogido el siguiente texto en Inglés, para probar nuestro desarrollo:
"Linux is a UNIX-like operating system that was designed to provide personal computer users a free or very low-cost operating system comparable to traditional and usually more expensive UNIX systems. Linux has a reputation as a very efficient and fast-performing system. Linux's kernel (the central part of the operating system) was developed by Linus Torvalds at the University of Helsinki in Finland. To complete the operating system, Torvalds and other team members made use of system components developed by members of the Free Software Foundation for the GNU project."
Para pasar de traducir una sola palabra a traducir un texto completo, simplemente hay que considerar que un texto está formado por palabras separadas entre sí por espacios en blanco o signos de puntuación. Aplicamos esta idea sobre el código, obteniendo el siguiente script:
<?
$texto='Linux is a UNIX-like operating system that ... etc.';
// Se define una rutina que cargue en memoria un diccionario
function cargadiccionario($filename) {
global $ingles2espanol, $espanol2ingles;
// Se lee el fichero del diccionario
$fp = @fopen($filename, "r"); if (!$fp) { die("ERROR: Imposible leer
el fichero '$filename'\n");}
while (($linea = fgets($fp, 1024)) && (!feof($fp))) {
// Se eliminan caracteres residuales
$linea = eregi_replace("\([^)]*\)", "", $linea); /* comentarios
entre parentesis */
$linea = eregi_replace(" : ", ":", $linea); /* separacion
entre idiomas */
$linea = eregi_replace("[\n\r]*", "", $linea); /* se elimina el
cambio de linea */
// Se separan los idiomas en 2 variables
$matches = split (":", $linea);
$palabraingles = $matches[0];
$palabraespanol = $matches[1];
// Se meten las palabras en la tabla hash
$ingles2espanol[$palabraingles] = $palabraespanol;
$espanol2ingles[$palabraespanol] = $palabraingles;
}
fclose ($fp);
}
// Se carga el diccionario que trae la aplicación libre i2edict
cargadiccionario("i2edict-20010903/i2e.dict");
// Se define una rutina que sepa traducir palabras sueltas del inglés al
español
function palabra_ingles2espanol($ingles) {
global $ingles2espanol;
if ($ingles2espanol[$ingles] != '') {
return $ingles2espanol[$ingles];
} else {
return $ingles;
}
}
// Se lee un fichero con texto en ingles
$lineas = split ("\n", $texto);
while (list($key, $val) = each($lineas)) {
$linea = $val;
// Se reemplazan los signos de puntuación, para preservarlos
$linea = eregi_replace("\.", " --PUNTO-- ", $linea);
$linea = eregi_replace("\,", " --COMA-- ", $linea);
$linea = eregi_replace("\;", " --PUNTOYCOMA-- ", $linea);
$linea = eregi_replace("\(", " --PARABR-- ", $linea);
$linea = eregi_replace("\)", " --PARCIE-- ", $linea);
// Se desglosa la linea en palabras individuales
$matches = split (" ", $linea);
// Se efectua la traduccion de cada palabra, recorriendo el array obtenido
$linea_traducida = '';
while (list($key, $val) = each($matches)) {
$linea_traducida .= palabra_ingles2espanol($val) . " ";
}
// Se reemplazan a la inversa los signos de puntuación
$linea_traducida = eregi_replace(" --PUNTO-- ", ".", $linea_traducida);
$linea_traducida = eregi_replace(" --COMA-- ", ",", $linea_traducida);
$linea_traducida = eregi_replace(" --PUNTOYCOMA-- ", ";", $linea_traducida);
$linea_traducida = eregi_replace(" --PARABR-- ", "(", $linea_traducida);
$linea_traducida = eregi_replace(" --PARCIE-- ", ")", $linea_traducida);
// Se muestra en pantalla la linea traducida
print $linea_traducida . "\n";
}
?>
Si probamos el script encontraremos como resultado una traducción bastante inexacta, debida a la imprecisón del diccionario con el que trabajamos. Con un pequeño truco podemos mejorar el diccionario agregando o suplantando algunos terminos. Para ello, incorporamos al código esta dos líneas:
// Se carga un diccionario con terminos adicionales que podemos personalizar
cargadiccionario("mio.dict");
y creamos un fichero llamado "mio.dict" que contiene las siguientes palabras:
is : es
the : el
was : fue
designed : diseñado
provide : proveer
users : usuarios
low-cost : bajo coste
systems : sistemas
has : tiene
fast-performing : alto rendimiento
developed : desarrollado
by : por
University : Universidad
Finland : Finlandia
members : miembros
made : hecho
components : componentes
project : proyecto
to : para
that : que
Ahora, probamos nuevamente el script, y éste el fantástico resultado:
"Linux es un UNIX-like operativo sistema que fue diseñado para proveer personal ordenador usuarios un libre o muy bajo coste operativo sistema comparable para tradicional y usualmente más caro UNIX sistemas. Linux tiene un reputación como un muy eficaz y alto rendimiento sistema. Linux's núcleo (el central pieza de el operativo sistema) fue desarrollado por Linus Torvalds a el Universidad de Helsinki en Finlandia. To completo el operativo sistema, Torvalds y otros equipo miembros hecho uso de sistema componentes desarrollado por miembros de el Free Software Foundation por el GNU proyecto."
Ofrecemos en Internet el Servicio Web de forma elemental
La forma más simple de que este script funcione en Internet y terceros usuarios o programadores puedan aprovecharse de él es creando un sencillo formulario HTML donde se solciite el texto a traducir, y dar como respuesta una página web dinámica cuyo contenido es la traducción.
El formulario HTML puede ser algo tan sencillo como esto:
<form name="fromulariotraduccion"
action="http://www.midominio.com/ingles2espanol.php" method="POST">
<textarea name="texto" rows="7" cols="40"></textarea>
<input type="submit" value="Traducir >>">
</form>
Lo llamamos "index.html" y lo colocamos en una carpeta del servidor web de http://www.midominio.com/
Por último, creamos el script "ingles2espanol.php" (en la misma carpeta del servidor web) que devuelve la traducción, a partir de la información extraida del formulario:
<?
// Se capturan las variables CGI a partir de PHP 4.2.0
function php420global($var) {
global $$var;
if (!isset($$var)) {$$var = $_GET["$var"];}
if (!isset($$var)) {$$var = $_POST["$var"];}
if (!isset($$var)) {$$var = $_COOKIE["$var"];}
}
php420global("texto");
// Se define una rutina que cargue en memoria un diccionario
function cargadiccionario($filename) {
global $ingles2espanol, $espanol2ingles;
// Se lee el fichero del diccionario
$fp = @fopen($filename, "r"); if (!$fp) { die("ERROR: Imposible leer
el fichero '$filename'\n");}
while (($linea = fgets($fp, 1024)) && (!feof($fp))) {
// Se eliminan caracteres residuales
$linea = eregi_replace("\([^)]*\)", "", $linea); /* comentarios
entre parentesis */
$linea = eregi_replace(" : ", ":", $linea); /* separacion
entre idiomas */
$linea = eregi_replace("[\n\r]*", "", $linea); /* se elimina el
cambio de linea */
// Se separan los idiomas en 2 variables
$matches = split (":", $linea);
$palabraingles = $matches[0];
$palabraespanol = $matches[1];
// Se meten las palabras en la tabla hash
$ingles2espanol[$palabraingles] = $palabraespanol;
$espanol2ingles[$palabraespanol] = $palabraingles;
}
fclose ($fp);
}
// Se carga el diccionario que trae la aplicación libre i2edict
cargadiccionario("i2edict-20010903/i2e.dict");
// Se carga un diccionario con terminos adicionales que podemos personalizar
cargadiccionario("mio.dict");
// Se define una rutina que sepa traducir palabras sueltas del inglés al español
function palabra_ingles2espanol($ingles) {
global $ingles2espanol;
if ($ingles2espanol[$ingles] != '') {
return $ingles2espanol[$ingles];
} else {
return $ingles;
}
}
// Se lee un fichero con texto en ingles
$lineas = split ("\n", $texto);
while (list($key, $val) = each($lineas)) {
$linea = $val;
// Se reemplazan los signos de puntuación, para preservarlos
$linea = eregi_replace("\.", " --PUNTO-- ", $linea);
$linea = eregi_replace("\,", " --COMA-- ", $linea);
$linea = eregi_replace("\;", " --PUNTOYCOMA-- ", $linea);
$linea = eregi_replace("\(", " --PARABR-- ", $linea);
$linea = eregi_replace("\)", " --PARCIE-- ", $linea);
// Se desglosa la linea en palabras individuales
$matches = split (" ", $linea);
// Se efectua la traduccion de cada palabra, recorriendo el array obtenido
$linea_traducida = '';
while (list($key, $val) = each($matches)) {
$linea_traducida .= palabra_ingles2espanol($val) . " ";
}
// Se reemplazan a la inversa los signos de puntuación
$linea_traducida = eregi_replace(" --PUNTO-- ", ".", $linea_traducida);
$linea_traducida = eregi_replace(" --COMA-- ", ",", $linea_traducida);
$linea_traducida = eregi_replace(" --PUNTOYCOMA-- ", ";", $linea_traducida);
$linea_traducida = eregi_replace(" --PARABR-- ", "(", $linea_traducida);
$linea_traducida = eregi_replace(" --PARCIE-- ", ")", $linea_traducida);
// Se muestra en pantalla la linea traducida
print $linea_traducida . "\n";
}
?>
Ya está. Acabamos de crear un Servicio Web, que aunque no respeta ningún estándar (XML-RPC o SOAP) funciona bajo la misma filosofía. Podemos jugar con el ejemplo aquí descrito en la siguiente URL de Internet
Ampliamos el servicio, para que funcione bajo XML-RPC
Retomamos el pequeño programa traductor Inglés => Castellano que dejamos a punto de convertirse en un servicio XML-RPC. Si queremos crear realmente un Servicio Web estándar, la comunicación entre éste y el cliente (normalmente el cliente es otro programa que nosotros u otros desarrolladores estén realizando) debe llevarse a cabo a través de un protocolo como XML-RPC o SOAP.
La implementación de XML-RPC como servidor, se incorpora en PHP a través de una herramienta ya creada (desarrollada por Ivan Ristic, ivanr@webkreator.com) y disponible en Internet gratuitamente llamada "XML-RPC Class Server". La podemos encontrar en esta web y
descargarla.
Descomprimimos el fichero obtenido: "unzip xcs-1.2.zip" en la carpeta del servidor web, donde hemos colocado el script de traducción. Se crean varios ficheros, aunque sólo nos vamos a centrar en uno de ellos, que es muy sencillo: "class.test.php"
En "class.test.php" se define la función PHP a ejecutar por el Servicio Web, respetando la estructura inicial. De modo que la incorporación de nuestro traductor sería algo así:
<?
class Test {
// funcion de pruebas, que nos permite ver el código XML intercambiado
function Test($secret) {
if ($secret != '42') {
trigger_error("Secret does not match.", E_USER_ERROR);
}
}
// funcion respuesta del objeto invocado por XML-RPC
function ingles2espanol($texto) {
// Se define una rutina que cargue en memoria un diccionario
function cargadiccionario($filename) {
global $ingles2espanol, $espanol2ingles;
// Se lee el fichero del diccionario
$fp = @fopen($filename, "r"); if (!$fp) { die("ERROR: Imposible leer
el fichero '$filename'\n");}
while (($linea = fgets($fp, 1024)) && (!feof($fp))) {
// Se eliminan caracteres residuales
$linea = eregi_replace("\([^)]*\)", "", $linea); /* comentarios
entre parentesis */
$linea = eregi_replace(" : ", ":", $linea); /* separacion
entre idiomas */
$linea = eregi_replace("[\n\r]*", "", $linea); /* se elimina el
cambio de linea */
// Se separan los idiomas en 2 variables
$matches = split (":", $linea);
$palabraingles = $matches[0];
$palabraespanol = $matches[1];
// Se meten las palabras en la tabla hash
$ingles2espanol[$palabraingles] = $palabraespanol;
$espanol2ingles[$palabraespanol] = $palabraingles;
}
fclose ($fp);
}
// Se define una rutina que sepa traducir palabras sueltas del inglés al español
function palabra_ingles2espanol($ingles) {
global $ingles2espanol;
if ($ingles2espanol[$ingles] != '') {
return $ingles2espanol[$ingles];
} else {
return $ingles;
}
}
// Se carga el diccionario que trae la aplicación libre i2edict
cargadiccionario("./i2edict-20010903/i2e.dict");
// Se carga un diccionario con terminos adicionales que podemos personalizar
cargadiccionario("./mio.dict");
// Se traduce el texto en ingles
$lineas = split ("\n", $texto);
while (list($key, $val) = each($lineas)) {
$linea = $val;
// Se reemplazan los signos de puntuación, para preservarlos
$linea = eregi_replace("\.", " --PUNTO-- ", $linea);
$linea = eregi_replace("\,", " --COMA-- ", $linea);
$linea = eregi_replace("\;", " --PUNTOYCOMA-- ", $linea);
$linea = eregi_replace("\(", " --PARABR-- ", $linea);
$linea = eregi_replace("\)", " --PARCIE-- ", $linea);
// Se desglosa la linea en palabras individuales
$matches = split (" ", $linea);
// Se efectua la traduccion de cada palabra, recorriendo el array obtenido
$linea_traducida = '';
while (list($key, $val) = each($matches)) {
$linea_traducida .= palabra_ingles2espanol($val) . " ";
}
// Se reemplazan a la inversa los signos de puntuación
$linea_traducida = eregi_replace(" --PUNTO-- ", ".", $linea_traducida);
$linea_traducida = eregi_replace(" --COMA-- ", ",", $linea_traducida);
$linea_traducida = eregi_replace(" --PUNTOYCOMA-- ", ";", $linea_traducida);
$linea_traducida = eregi_replace(" --PARABR-- ", "(", $linea_traducida);
$linea_traducida = eregi_replace(" --PARCIE-- ", ")", $linea_traducida);
$lineas_traducidas .= $linea_traducida . "\n";
}
return $lineas_traducidas;
}
}
?>
¡Ya está! Ahora sí que hemos creado un Servicio Web completamente standard, bajo XML-RPC. Veamos si funciona. Ver si funciona un Servicio Web es complicado, ya que para invocarlo tenemos que enviar una solicitud web en lenguaje XML, y la respuesta es una página web cuyo código es XML. Por eso, com veis en el código PHP anterior, se ha definido la función "Test", dentro del objeto que representa al Servicio Web. Esta función "Test" nos permite ejecutar el Servicio Web, sin necesidad de enviar una solicitud en formato XML, sino simplemente una solicitud GET corriente, como ésta:
http://www.midominio.com/xcs.php/test/42?_method=ingles2espanol&texto=tree
Si todo va bien, el resultado es éste:
<?xml version='1.0' encoding="iso-8859-1" ?>
<methodResponse>
<params>
<param>
<value>
<string>árbol</string>
</value>
</param>
</params>
</methodResponse>
En fase de producción, un Servicio Web se invoca directamente desde el código de programación de la aplicación cliente. Para efectuar dicha invocación, se utilizan rutinas de cliente XML-RPC que traducen a XML nuestra llamada a rutina y sus variables, invocan por HTTP el Servicio Web, descargan el XML resultante, y lo procesan convirtiéndolo en variables resultado. A nivel de programador, un Servicio Web hace todo esto de forma transparente, por lo que es equivalente a ejecutar una rutina.
<?
include_once("./class.remotetest.php");
RemoteClassRegistry::addUrl('DEFAULT_URL', 'http://www.midominio.com/xcs.php');
$test = new Test(42);
print $test->ingles2espanol('tree');
?>
Este sencillo código invoca la creación de un objeto remoto de tipo "Test", y luego ejecuta su función "ingles2espanol" con el parámetro de ejemplo "tree". El resultado, como podemos imaginar es "árbol". De igual modo, podemos probar el sistema con un texto más largo.
La potencia de este esquema de programación (los Servicios Web) está en colocar el PHP del cliente en un servidor distinto al PHP del Servicio Web.
Ahora nos queda claro que la rutina de traducción sólo reside en un servidor de Internet, y otros pueden invocar la función tantas veces como quieran desde cualquier punto de Internet.
Hay un detalle que aún no hemos comentado. Para que PHP pueda trabajar con el protocolo XML-RPC, hay que compilarlo con unas determinadas funciones especiales. Vemos a continuación los pasos en la instalación de PHP, para que todo funcione bien.