Mostrando entradas con la etiqueta XML. Mostrar todas las entradas
Mostrando entradas con la etiqueta XML. Mostrar todas las entradas

lunes, 14 de junio de 2010

Como devolver arreglos y estructuras de datos en SOAP transparentemente, utilizando PHP y NuSOAP


¡Saludos nuevamente blogueros!

En este artículo veremos como resolver una duda que apareció hace un tiempo, acerca del envío de datos por servicios web utilizando la biblioteca NuSOAP de PHP. En un artículo pasado, hablamos de como implementar un servicio web desde PHP utilizando esta biblioteca; este servicio contaba con dos métodos, uno de ellos devolvía una cadena y el otro un arreglo asociativo de PHP, se pudo consumir el servicio desde un cliente AJAX sin problema procesando el código XML directamente, sin embargo, al trabajar de esta manera se pierde uno de los puntos importantes para utilizar protocolos de comunicación entre aplicaciones: la transparencia. Es decir, al consumir el servicio el cliente no pudo extraer la estructura de datos recibida, sin necesidad de darle un tratamiento al XML de la respuesta y teniendo que saber de antemano cuál era la estructura XML que recibiría.

Ahora, la idea es corregir este problema de transparencia, al mismo tiempo que mostramos como mandar diferentes tipos de datos complejos, como son: estructuras, arreglos y arreglos de estructuras.

Manos a la obra

Para poder llegar a este nivel de transparencia entre aplicaciones es que el protocolo SOAP utiliza el WSDL, el cual además de contener la definición de cada uno de los métodos que posee un servicio web, puede tener también la definición de tipos de datos complejos. Un tipo de dato complejo consiste una colección de valores agrupados dentro de una estructura, y dentro de este tipo de datos es que encontramos los objetos y arreglos.

Podemos definir estos datos complejos utilizando la clase soap_server de la biblioteca NuSOAP. Como recordarán -y si no es así lo menciono- esta clase nos permite crear servicios web en nuestro servidor para que lo consuman los clientes. Dentro de esta clase se encuentra un objeto llamado wsdl, definido como una propiedad de la misma clase, el cual posee una serie de métodos para definir las características del WSDL del servicio web. El método que emplearemos concretamente para definir los nuevos tipos de datos será el addComplexType. El primer paso consistirá en instanciar la clase soap_server de la siguiente forma:

require_once('nusoap.php');
$servidor = new soap_server;


Una vez hecho esto contamos con un objeto de esta clase, para el cual podemos definir sus características:

$ns = "http://nuestroespaciodenombres";
$servidor->configureWSDL('NombreDelServicio',$ns);
$servidor->wsdl->schematargetnamespace = $ns;


Ahora viene lo bueno, definir tipos de datos complejos para que sean devueltos por los métodos de nuestro servicio. Para este ejemplo definiremos 3 estructuras diferentes para probar las posibilidades que nos ofrece esta clase:

  • Primero un arreglo de cadenas sencillo:

$servidor->wsdl->addComplexType(
        'ArregloDeCadenas',
        'complexType',
        'array',
        'sequence',
        'http://schemas.xmlsoap.org/soap/encoding/:Array',
        array(),
        array(array('ref' => 'http://schemas.xmlsoap.org/soap/encoding/:arrayType',
         'wsdl:arrayType' => 'xsd:string[]')
        ),
        'xsd:string'  
);


Como vemos, se incluye una referencia(http://schemas.xmlsoap.org/soap/encoding/:Array) a la definición del formato que debe tener un dato de tipo complejo Array.

  • Después una estructura de datos, similar a los arreglos asociativos, que contendrá una serie de valores distinguidos por un nombre:


  • $servidor->wsdl->addComplexType(
            'Estructura',
            'complexType',
            'struct',
            'all',
            '',
            array(
            'Nombre' => array('name' => 'Nombre', 'type' => 'xsd:string'),
            'Apellidos'=>array('name' => 'Apellidos', 'type' => 'xsd:string'),
            'Edad'=>array('name' => 'Edad', 'type' => 'xsd:integer')
            )
    );


  • Finalmente -y lo más interesante-, un arreglo de estructuras de datos:

$servidor->wsdl->addComplexType(
        'ArregloDeEstructuras',
        'complexType',
        'array',
        'sequence',
        'http://schemas.xmlsoap.org/soap/encoding/:Array',
        array(),
        array(array('ref' => 'http://schemas.xmlsoap.org/soap/encoding/:arrayType',
         'wsdl:arrayType' => 'tns:Estructura[]')
        ),
        'tns:Estructura'  
);


Digo que el último ejemplo me parece el más interesante porque con este podemos definir datos de entrada y de salida para los métodos que consistirán en listas de estructuras de datos, similares a las que encontramos en muchos lenguajes de programación de alto nivel y sobre todo muy útiles para representar registros de bases de datos y archivos estructurados.

Para incluir estos datos de entrada y de salida de los métodos, basta con declararlo en el método registrer donde se declaran los métodos, y asegurarnos por supuesto de que la estructura de los parámetros de estos métodos que hagan referencia los tipos de datos complejos, corresponda con la estructura declarada, es decir:

function consultaPersonas($param) {
    $arreglo = array();
    $arreglo[] = array('Nombre'=>"Juan", 'Apellidos'=>"Torres", 'Edad'=>18);
    $arreglo[] = array('Nombre'=>"Teresa", 'Apellidos'=>"Jiménez Sánchez", 'Edad'=>19);
    $arreglo[] = array('Nombre'=>"Efraín", 'Apellidos'=>"Ovalles López", 'Edad'=>22);
    return $arreglo;
}

$servidor->register('consultaPersonas',
    array('param'=>'xsd:string'),
    array('return'=>'tns:ArregloDeEstructuras'),
    $ns
);



Si por ejemplo creamos un método que devuelva el tipo de dato complejo ArregloDeEstructuras, en el código de nuestro método deberemos incluir una estructura de datos equivalente, en este caso un arreglo asociativo, donde cada llave del arreglo corresponde al identificador de los datos de nuestra estructura previamente definida -llamada Estructura en este ejemplo-; así en el método, cada entrada del arreglo contendrá a la vez un arreglo asociativo, que corresponderá a esta estructura de datos.

De esta forma es posible crear servicios web que devuelvan a nuestras aplicaciones arreglos de estructuras de datos, e incluso arreglos de arreglos, lo cual ayudará a que nuestros servicios puedan generar mayor variedad de datos de salida, y de esta forma aumentaremos el poder las capacidades nuestras aplicaciones. Además de esto, se logra mucha mayor transparencia, ya que cualquier aplicación de terceros o que nosotros mismos desarrollemos, solo tendrá que seguir el formato definido en el WSDL de nuestros servicios web, para poder desentrañar estos tipos de datos complejos. Algunos entornos de desarrollo -como el de .Net- incluso permiten implementar el consumo de estos servicios web interpretando los tipos de datos complejos como objetos nativos del lenguaje, lo que nos permite manipularlos con mucha mayor facilidad, así que las posibilidades son muchas.

Hasta la próxima.

jueves, 12 de febrero de 2009

Juntando todo: AJAX + XML + PHP + MySQL

En este ejemplo llegaremos finalmente a combinar todas las piezas que hemos estado revisando. Para poder dar inicio, es necesario que la tabla autores -creada en el post anterior- cuente con algunos registros, para lo que utilizamos la siguiente instrucción:
INSERT INTO `autores` ( `Id` , `Nombre` , `Nacionalidad` , `Profesion` )
VALUES (
'1', 'Juan Rulfo', 'Mexicano', 'Novelista'
),(
'2', 'Pablo Neruda', 'Chileno', 'Poeta'
),(
'3', 'Miguel de Cervantes Saavedra', 'Español', 'Novelista, poeta y dramaturgo'
),(
'4', 'Jorge Luis Borges', 'Argentino', 'Ensayista, escritor y poeta'
);

que al ejecutarla en nuestro analizador de consultas -ya sea phpMyAdmin, el cliente de la línea de comandos o algún administrador visual-, inserta estos cuatro registros en la tabla.

Bien, ahora el último paso para completar la actividad es acceder a nuestra base de datos obras_literarias con PHP al recibir una petición de AJAX. El siguiente esquema que ilustra un poco el la forma en que interactuarán los diferentes componentes para este propósito:


Programación del lado del cliente

El cliente Ajax de este ejemplo tomará como base el último post sobre la refactarización, agregando un formulario para capturar el nombre de un autor que se consultará en la base de datos. El código completo es el siguiente:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Sexto ejemplo con Ajax, combinándolo con PHP, XML y MySQL</title>
<script language="javascript">
var READY_STATE_COMPLETE=4;
var peticionHttp = null;
//Función que devuelve el objeto de la clase XMLHttpRequest
function inicializaXhr() {
    if(window.XMLHttpRequest) {
        return new XMLHttpRequest();
}
    else if(window.ActiveXObject) {
        return new ActiveXObject("Microsoft.XMLHTTP");
    }
}

function creaCadenaDeDatos() {
    var nombre = document.getElementById("txtNombre");
    return "txtNombre=" + encodeURIComponent(nombre.value) + "&nocache=" + Math.random();
}
//Función que inicia todo el proceso y lleva a cabo la petición al servidor
function consulta() {
peticionHttp = inicializaXhr();
if(peticionHttp) {
peticionHttp.onreadystatechange = procesaRespuestaXML;
peticionHttp.open("POST", "http://localhost/consultaMySQL.php", true);
peticionHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var datos = creaCadenaDeDatos();
peticionHttp.send(datos);
}
}
//Función en la que se procesa el documento XML recibido
function procesaRespuestaXML() {
if(peticionHttp.readyState == READY_STATE_COMPLETE) {
if(peticionHttp.status == 200) {
var documentoXml = peticionHttp.responseXML; //Creamos el objeto de tipo documento XML
if(!documentoXml.getElementsByTagName('Error')[0]) {//Comprobamos que no se regrese un error como respuesta
var root = documentoXml.getElementsByTagName('Consulta')[0]; //Obtenemos la raíz del documento
//Accedermos al contenido de cada campo contenido en el elemento Informacion
var tabla = root.getElementsByTagName('Tabla')[0];
exploraRegistrosDeLaTabla(tabla);//Desplegamos todos los registros del elemento Tabla
}
else {//En caso de obtener un error desplegamos el mensaje de éste
var error = documentoXml.getElementsByTagName('Error')[0].firstChild.nodeValue;
alert(error);
}
}
}
}
/*Función que recibe el elemento que contiene los registros del documento XML, recorre cada uno de los
/*registros y almacena el valor de todos sus campos en el objeto objRenglon, al que después envía como
/*parámetro a la función muestraRenglon para desplegarlos en la tabla especificada
*/
function exploraRegistrosDeLaTabla(tablaXML) {
var objRenglon = new Object();
var tope1 = tablaXML.getElementsByTagName('Registro').length;
for(var i = 0; i < tope1; i++) {
registro = tablaXML.getElementsByTagName('Registro')[i];
tope2 = registro.getElementsByTagName('Campo').length;
for(var j = 0; j < tope2; j++) {
campo = registro.getElementsByTagName('Campo')[j];
objRenglon[campo.getAttribute('nombre')] = campo.firstChild.nodeValue;
}
muestraRenglon(objRenglon, 'tblInfo');
}
}
/**función que despliega un renglon dentro de la tabla HTML específicada a partir de las propiedades del
/* objeto objRenglon
*/
function muestraRenglon(objRenglon, idTabla) {
var tbody = document.getElementById(idTabla).getElementsByTagName("TBODY")[0]; //Referencia a la tabla
var renglon = document.createElement("TR"); //Crea un renglon
for(var llave in objRenglon){
var columna = document.createElement("TD"); //Crea una columna
columna.appendChild(document.createTextNode(objRenglon[llave])); //Agrega texto a la columna
renglon.appendChild(columna); //Agrega la columna al renglon
}
tbody.appendChild(renglon); //Agrega el renglon a la tabla
}
</script>
</head>
<body>
<form>
Nombre del autor : <input type="text" id="txtNombre" size="40" />
<input type="button" value="Buscar autor" onclick="consulta()" />
</form>
<table id="tblInfo" cellspacing="1">
<tbody>
<tr style="background:#E1E1E1">
<td><strong>ID</strong></td>
<td><strong>Nombre</strong></td>
<td><strong>Nacionalidad</strong></td>
<td><strong>Profesión</strong></td>
</tr>
</tbody>
</table>
</body>
</html>

En este ejemplo se tomará el nombre capturado en el cuadro de texto txtNombre, para enviarlo como un parámetro dentro de la petición para que PHP, por lo que también vemos que se incluye la función creaCadenaDeDatos():
function creaCadenaDeDatos() {
var nombre = document.getElementById("txtNombre");
return "txtNombre=" + encodeURIComponent(autor.value) "&nocache=" + Math.random();
}

para generar la cadena que se enviará en a través de del método send que aparece en la función consulta(), justo después de crear esta cadena:
peticionHttp.onreadystatechange = procesaRespuestaXML;
peticionHttp.open("POST", "http://localhost/consultaMySQL.php", true);
var cadenaDeDatos = creaCadenaDeDAtos();
peticionHttp.send(cadenaDeDatos);

Además, dentro de la función procesaResuestaXML se incluye una validación que comprueba si se generó algún error dentro del programa PHP al comprobar la existencia del elemento Error dentro del documento XML recibido:

var documentoXml = peticionHttp.responseXML;
if(!documentoXml.getElementsByTagName('Error')[0]) {
    var root = documentoXml.getElementsByTagName('Consulta')[0];
    var tabla = root.getElementsByTagName('Tabla')[0];
    exploraRegistrosDeLaTabla(tabla);
}
else {
    var error = documentoXml.getElementsByTagName('Error')[0].firstChild.nodeValue;
    alert(error);
}


de no existir la respuesta se procesa normalmente, en caso contrario se obtiene el contenido del elemento Error y lo despliega con un alert.

Programación del lado del servidor

¿Qué es lo que se hará en PHP?, bueno pués como dijimos accederá a la base de datos para ejecutar una consulta con los datos recibidos, para esto nos conectaremos con el SMBD MySQL por medio del conjunto de funciones que proporciona la extensión mysql dentro de PHP. También existe otra extensión que permite realizar un acceso mejorado a bases de datos MySQL llamada mysqli, la cual veremos en ejemplos posteriores ya que para éste bastará con la extensión básica.

Nuestro programa PHP recibirá el nombre de consultaMySQL.php y el código fuente completo que contendrá es el siguiente:


<?
require("ConsultaXML.php"); //Referencia al archivo que contiene la clase ConsultaXML
//Creación del documento XML
$consultaXML = new ConsultaXML();
//Parámetros para la conexión a la base de datos
$servidor = "localhost";
$usuario = "root";
$contrasenia = "[TuConstraseña]";
$baseDeDatos = "obras_literarias";
$conexion = mysql_connect($servidor,$usuario,$contrasenia); //Abrimos la conexión con MySQL
if(mysql_select_db($baseDeDatos,$conexion)) { //Accedemos a la base de datos
//Establecemos que la información que se intermabiará estará codificada en utf-8
mysql_query("SET NAMES 'utf8'",$conexion);
//Comprobamos que los datos se enviaron correctamente
if(isset($_POST['txtNombre']) && ($nombre = stripslashes(trim($_POST['txtNombre'])))!="") {
$consulta = "SELECT * FROM autores WHERE Nombre LIKE '%".addslashes($nombre)."%'";
$resultado = mysql_query($consulta,$conexion); //Realizamos la consulta
if($resultado) {
if(mysql_num_rows($resultado) > 0){//Comprobamos que si encontraron registros
//Creamos el documento XML con el resultado de la consulta
$consultaXML->iniciaDocumento();
$consultaXML->startElement("Tabla");
$consultaXML->writeAttribute("nombre","autores");
while($renglon = mysql_fetch_assoc($resultado)) {//Extrae un renglon del resultado como un arreglo asosciativo
$consultaXML->creaRegistroDesdeArreglo($renglon, "Id");
}
$consultaXML->endElement();
$consultaXML->salida();
}
else{
$mensajeDeError = "Autor no encontrado";
}
}
else {
$mensajeDeError = mysql_error();
}
}
else {
$mensajeDeError = "Datos imcompletos";
}
}
else{
$mensajeDeError = mysql_error();
}
if($mensajeDeError != "") {//En caso de que exista algún mensaje de error lo devolvemos como respuesta
$consultaXML->iniciaDocumento("Error");
$consultaXML->text($mensajeDeError);
$consultaXML->salida();
}
?>

Dentro de él se utiliza nuevamente la clase ConsultaXML para generar la respuesta XML. Después de esto aparecen declaradas una serie de variables, las cuales servirán para configurar el acceso a la base de datos de MySQL:
$servidor = "localhost";
$usuario = "root";
$contrasenia = "[TuConstraseña]";
$baseDeDatos = "obras_literarias";

En la variable $constrasenia es donde deben escribir la constraseña de root que definieron al instalar el servidor de bases de datos MySQL, en caso de no haber definido una contraseña la variable debe contener una cadena vacía.

El código siguiente abre la conexión con el servidor de bases de datos con la función mysql_connect y accede a la base de datos obras_literarias con mysql_select_db.

Después de acceder a la base de datos se comprueba que se haya recibido el dato enviado por POST txtNombre, para después elaborar la consulta SQL con este dato:
$consulta = "SELECT * FROM autores WHERE Nombre LIKE '%".addslashes($nombre)."%'";
$resultado = mysql_query($consulta,$conexion);

La cual es ejecutada por medio de la función mysql_query.

El resultado devuelto, que guarda la referencia a la tabla generada por la consulta que es almacenada en la memoria del servidor de bases de datos, se utiliza para extraer cada uno de los registros de esta tabla, dentro del ciclo while que aparece.

Cada uno de los registros devueltos serán agregados al documento XML a través del método creaRegistroDesdeArreglo del objeto consultaXML.

Para probar el funcionamiento de todo el sistema, bastará con que almacenemos el archivo consultaXML.php en el directorio de páginas de nuestro servidor Web, y que abramos el archivo HTML que contiene el cliente AJAX en nuestro navegador.

Para probar un consulta, podemos escribir cualquiera de los nombres de los autores que en un principio dimos de alta:



Si introducimos algún nombre que no exista en la base de datos se desplegará el siguiente mensaje de error:



Conclusiones

Llegamos finalmente a la etapa de acceder a una base de datos, y ya hemos completado nuestro primer paso al realizar consultas sencillas a una tabla, con lo cual ya nos podemos hacer una idea de las posibilidades que tienen los sistemas basados en la tecnología AJAX, combinándose con programación del lado del servidor. Un uso común es el realizar validaciones en las bases de datos de los datos introducidos en nuestros formularios HTML, por ejemplo: utilizar este método para comprobar la existencia de un nombre de usuario dentro de los sistemas de cuentas de usuarios por Internet, donde vemos que el sistema nos alerta de sobre una existencia similar a la que acabamos de escribir, otra aplicación diferente sería utilizarlo para consultar y actualizar la información de un foro de discusión o de una página de publicación de noticias, también existe la posibilidad de trabajar con multimedia, y mucho más, por lo que el límite realmente es el que nosotros queramos poner. En los siguientes ejemplos veremos más operaciones con bases de datos en MySQL y el acceso a un recurso diferente: un servicio Web.

Hasta la próxima.

viernes, 6 de febrero de 2009

Refactorizar el código que procesa XML, Parte 2

Refactorización del lado del cliente

Ahora como prometimos, atenderemos la cuestión del cliente. Las modificaciones se centraran en el modo de extraer y desplegar la información del documento XML. En el último ejemplo de Ajax a partir de la raíz de este documento, se extrajeron secuencialmente los valores de cada uno de los elementos que serían desplegados, almacenándolo en una serie de variables, que después eran pasadas a una función que las desplegaba agregándolas a los renglones de una tabla HTML:


function procesaRespuestaXML() {
if(peticionHttp.readyState == READY_STATE_COMPLETE) {
if(peticionHttp.status == 200) {
var documentoXml = peticionHttp.responseXML; //Creamos el objeto de tipo documento XML
var root = documentoXml.getElementsByTagName("Informacion")[0]; //Obtenemos la raíz del documento
//Accedermos al contenido de cada campo contenido en el elemento Informacion
var infoCliente = root.getElementsByTagName("Cliente")[0].firstChild.nodeValue;
muestraRenglon("Navegador",infoCliente);
var infoSoftware = root.getElementsByTagName("Software")[0].firstChild.nodeValue;
muestraRenglon("Software del servidor",infoSoftware);
var infoFecha = root.getElementsByTagName("Fecha")[0].firstChild.nodeValue;
muestraRenglon("Fecha del servidor",infoFecha);
var infoHora = root.getElementsByTagName("Hora")[0].firstChild.nodeValue;
muestraRenglon("Hora del servidor",infoHora);
}
}
}
//Función que agrega un nuevo renglon a una tabla especificada, utilizando los valores recibidos

function muestraRenglon(colEtiqueta, colValor){
var tbody = document.getElementById('tblInfo').getElementsByTagName("TBODY")[0]; //Referencia a la tabla
var renglon = document.createElement("TR"); //Crea un renglon
var columna = document.createElement("TD"); //Crea una columna
columna.appendChild(document.createTextNode(colEtiqueta)); //Agrega texto a la columna
renglon.appendChild(columna); //Agrega la columna al renglon
columna = document.createElement("TD"); //Crea una nueva columna
columna.appendChild(document.createTextNode(colValor));
renglon.appendChild(columna);
tbody.appendChild(renglon); //Agrega el renglon a la tabla
}


Éste método resultaría en un código bastante largo si aumentara el número de elementos que serán desplegados, así que lo mejor es automatizar el proceso de extraer cada uno de los elementos del documento XML, procurando evitar los pasos secuenciales en que cae la forma anterior. Para este propósito dentro del programa Javascript se utilizará una función adicional, y además se modificara la función muestraRenglon para hacerla más compacta.

De modo que el código completo del cliente será el siguiente:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Quinto ejemplo con Ajax, combinándolo con PHP y XML (Refactorizado)</title>
<script language="javascript">
var READY_STATE_COMPLETE=4;
var peticionHttp = null;
//Función que devuelve el objeto de la clase XMLHttpRequest
function inicializaXhr() {
if(window.XMLHttpRequest) {
return new XMLHttpRequest();
}
else if(window.ActiveXObject) {
return new ActiveXObject("Microsoft.XMLHTTP");
}
}
//Función que inicia todo el proceso y lleva a cabo la petición al servidor
function solicita() {
peticionHttp = inicializaXhr();
if(peticionHttp) {
peticionHttp.onreadystatechange = procesaRespuestaXML;
peticionHttp.open("POST", "http://localhost/generaXMLConClase.php", true);
peticionHttp.send(null);
}
}
//Función en la que se procesa el documento XML recibido
function procesaRespuestaXML() {
if(peticionHttp.readyState == READY_STATE_COMPLETE) {
if(peticionHttp.status == 200) {
var documentoXml = peticionHttp.responseXML; //Creamos el objeto de tipo documento XML
var root = documentoXml.getElementsByTagName('Consulta')[0]; //Obtenemos la raíz del documento
//Accedermos al contenido de cada campo contenido en el elemento Informacion
var tabla = root.getElementsByTagName('Tabla')[0];
exploraRegistrosDeLaTabla(tabla);//Desplegamos todos los registros del elemento Tabla
}
}
}
/*Función que recibe el elemento que contiene los registros del documento XML, recorre cada uno de los
/*registros y almacena el valor de todos sus campos en el objeto objRenglon, al que después envía como
/*parámetro a la función muestraRenglon para desplegarlos en la tabla especificada
*/
function exploraRegistrosDeLaTabla(tablaXML) {
var objRenglon = new Object();
var tope1 = tablaXML.getElementsByTagName('Registro').length;
for(var i = 0; i < tope1; i++) {
registro = tablaXML.getElementsByTagName('Registro')[i];
tope2 = registro.getElementsByTagName('Campo').length;
for(var j = 0; j < tope2; j++) {
campo = registro.getElementsByTagName('Campo')[j];
objRenglon[campo.getAttribute('nombre')] = campo.firstChild.nodeValue;
}
muestraRenglon(objRenglon, 'tblInfo');
}
}
/**función que despliega un renglon dentro de la tabla HTML específicada a partir de las propiedades del
/* objeto objRenglon
*/
function muestraRenglon(objRenglon, idTabla) {
var tbody = document.getElementById(idTabla).getElementsByTagName("TBODY")[0]; //Referencia a la tabla
var renglon = document.createElement("TR"); //Crea un renglon
for(var llave in objRenglon){
var columna = document.createElement("TD"); //Crea una columna
columna.appendChild(document.createTextNode(objRenglon[llave])); //Agrega texto a la columna
renglon.appendChild(columna); //Agrega la columna al renglon
}
tbody.appendChild(renglon); //Agrega el renglon a la tabla
}
</script>
</head>
<body>
Para realizar la petición al servidor Web
<input type="button" value="presiona aqui" onclick="solicita()" />
<table id="tblInfo" cellspacing="1">
<tbody>
<tr style="background:#E1E1E1">
<td><strong>ID</strong></td>
<td><strong>Nombre</strong></td>
<td><strong>Nacionalidad</strong></td>
<td><strong>Profesión</strong></td>
</tr>
</tbody>
</table>
</body>
</html>


En ésta ocasión vemos que se hicieron unos cambios en la función procesaRespuestaXML, ya no se procesa el contenido completo del documento XML:
function procesaRespuestaXML() {
   if(peticionHttp.readyState == READY_STATE_COMPLETE) {
      if(peticionHttp.status == 200) {
         var documentoXml = peticionHttp.responseXML;
         var root = documentoXml.getElementsByTagName('Consulta')[0];
         var tabla = root.getElementsByTagName('Tabla')[0];
         exploraRegistrosDeLaTabla(tabla);
      }
   }
}

En cambio, después de acceder al elemento raíz del documento -en este caso Consulta- se crea el objeto tabla a partir del elemento Tabla, y éste es pasado como un parámetro a la función exploraRegistrosDeLaTabla, dentro de la cual se lleva todo a cabo la extracción de cada uno de los elementos que serán desplegados:
function exploraRegistrosDeLaTabla(tablaXML) {
   var objRenglon = new Object();
   var tope1 = tablaXML.getElementsByTagName('Registro').length;
   for(var i = 0; i < tope1; i++) {
      registro = tablaXML.getElementsByTagName('Registro')[i];
      tope2 = registro.getElementsByTagName('Campo').length;
      for(var j = 0; j < tope2; j++) {
         campo = registro.getElementsByTagName('Campo')[j];
         objRenglon[campo.getAttribute('nombre')] = campo.firstChild.nodeValue;
      }
      muestraRenglon(objRenglon, 'tblInfo');
   }
}


Ésta función esta formada por dos bucles for, dentro del primero se recorreran todos los elementos Registro que contenga el elemento Tabla, en el segundo bucle se recorrerán todos los elementos Campo que contenga del registro que sea accedido en el ciclo externo, almacenando los valores de cada uno de los campos en las propiedades del objeto objRenglon. Al terminar de recorrer todos los campos del registro se llamará a la función muestraRenglon, pesándole este objeto para que despliegue dentro de la tabla tblInfo un renglon que contenga los valores recogidos:
function muestraRenglon(objRenglon, idTabla) {
   var tbody = document.getElementById(idTabla).getElementsByTagName("TBODY")[0];
   var renglon = document.createElement("TR");
   for(var llave in objRenglon){
      var columna = document.createElement("TD");
      columna.appendChild(document.createTextNode(objRenglon[llave]));
      renglon.appendChild(columna);
   }
   tbody.appendChild(renglon);
}

Ésta función muestraRenglon tiene el propósito de agregar un nuevo renglón a la tabla especificada, con los valores que recibe como parámetro, solo que estos valores van contenidos dentro de las propiedades del objeto objRenglon, las cuales pueden ser extraídas como si se tratara de un arreglo asociativo dentro del bucle for de la función.

De esta forma queda finalizada la refactorización del cliente Ajax para el procesamiento de documentos XML, generados en PHP. Lo que queda en este caso es probar este nuevo programa almacenando en cliente en un archivo HTML, y guardando los archivos PHP ConsultaXML.php y generaXMLConClase.php en el directorio de páginas de nuestro servidor Web. Al probar el programa Javascript en el navegador deberemos obtener un resultado como el siguiente:



Conclusiones

El código PHP y Ajax que obtuvimos de este ejercicio nos será de gran utilidad en los ejemplos siguientes, sobre todo al momento de incursionar en el manejo de las bases de datos, al facilitarnos en gran medida el proceso de devolver el resultado de una consulta a nuestro cliente, así como el procesamiento de la información que éste lleva a cabo. El propósito de ésta práctica era contar con mejores armas para encarar los nuevos retos que nos estamos planteando, y aunque ahora puedan representa más trabajo, al adentrarse más en las aplicaciones de Ajax se verá rendir fruto a estos esfuerzos.

Hasta la próxima.

miércoles, 4 de febrero de 2009

Refactorizar el código que procesa XML, Parte 1


Los tres pasos de la buena programación

En el post anterior utilizamos un método para generar código XML del lado de PHP bastante simple, que funciona bien para el ejemplo debido la cantidad de datos que estábamos utilizando, sin embargo, al incrementarse el tamaño del documento XML generado, utilizar solamente llamadas a la función echo resultaría en un código fuente bastante largo y difícil de mantener, además la creación de documentos XML desde PHP se llevará a cabo en más de un programa, por lo que una forma de agilizar este proceso consiste en crear bloques de código reutilizables, que nos ahorren una parte considerable del trabajo al teclear nuestros programas. Una forma de hacerlo es utilizando grupos de funciones, cada una correspondiendo a un paso específico del proceso, otra forma sería el uso de clases para manejar el proceso completo, lo cual implicaría adentrarnos un poco en programación orientada a objetos en este lenguaje. Para la generación del documento XML diseñaremos una sencilla clase en PHP, utilizando el modelo de objetos de su versión 5. De esta forma la escritura de los siguientes programas será más ágil y sobre todo más fácil de mantener.


Consideraciones previas

La principal cuestión antes de continuar es conocer que formato tendrán los documentos XML que crearemos para los siguientes ejemplos, debido a que el próximo paso será el manejo de las bases de datos, consideraremos que los elementos del documento XML representarán los registros y campos obtenidos al realizar consultas a las bases de datos relacionales. Sería algo como lo que sigue:

<?xml version="1.0" encoding="utf-8"?>
<Consulta>
<Tabla nombre="Autores">
<Registro id="1">
<Campo nombre="Nombre">Juan Rulfo</Campo>
<Campo nombre="Nacionalidad">Mexicano</Campo>
<Campo nombre="Profesion">Novelista</Campo>
</Registro>
<Registro id="2">
<Campo nombre="Nombre">Pablo Neruda</Campo>
<Campo nombre="Nacionalidad">Chileno</Campo>
<Campo nombre="Profesion">Poeta</Campo>
</Registro>
<Registro id="3">
<Campo nombre="Nombre">Miguel de Cervantes Saavedra</Campo>
<Campo nombre="Nacionalidad">Español</Campo>
<Campo nombre="Profesion">Novelista, Poeta y Dramaturgo</Campo>
</Registro>
</Tabla>
</Consulta>


Así, los elementos principales estarán representados por la etiqueta Tabla, y cada tabla tendrá un identificador que será único, representado en este caso por el atributo nombre, que representa el nombre de la tabla dentro de la base de datos. En el ejemplo existe únicamente la tabla Autores, dentro de la cual encontramos un grupo de registros, los cuales se identifican por el atributo id. Dentro de todo registro, aparecerá una serie de elementos llamados Campos, que serán identificados por el atributo nombre, y los valores de cada campo están contenidos en sus propias etiquetas.

Refactorización del lado del Servidor

De forma que el código en PHP necesario para crear este tipo de documentos será el siguiente:


<?php
//Clase creada para manejar documentos XML que tengan el formato de una consulta a una base de datos
class ConsultaXML extends XMLWriter{
//Función que inicializa el documento XML, establece el contenido de su encabezado y abre el elemento raíz
function iniciaDocumento($elementoRaiz = "Consulta", $version = "1.0", $juegoDeCarateres = "UTF-8") {
$this->openMemory();
$this->setIndent(true);
$this->setIndentString(" ");
$this->startDocument($version,$juegosDeCaracteres);
$this->startElement($elementoRaiz);
}
/*
* Método que crea el elemento Campo dentro del documento, recibiendo como parámetros el nombre del campo
* y el valor que contendrá.
*/
public function creaCampo($nombreDelCampo, $valor) {
$this->startElement("Campo");
$this->writeAttribute("nombre",$nombreDelCampo);
$this->text($valor);
$this->endElement();
}
/*
* Método que crea el elemento Registro dentro del documento, recibiendo como parámetros el arreglo asociativo
* que contendrá los nombres y valores de los campos que poseerá el registro, junto con el identificaor del
* registro que será almacenado en su atributo "id".
*/
public function creaRegistroDesdeArreglo($arreglo, $idRegistro) {
$this->startElement("Registro");
$this->writeAttribute("id",$idRegistro);
if(is_array($arreglo)){
foreach ($arreglo as $llave => $valor){
$this->creaCampo($llave, $valor);
}
}
$this->endElement();
}
/*
* Método que crea el elemento Tabla dentro del documento, recibiendo como parámetros una matriz de arreglos
* asociativos, el nombre de la tabla y el nombre del campo que identificará a los registros que contendrá la
* tabla. Dentro de un ciclo se llamará a al método creaRegistro para cada uno de los arreglos asociativos
* que contenga la raíz, de esta forma se creará un documento XML que contenga todos los registros de una
* tabla obtenida en una consulta.
*/
public function creaTablaDesdeMatriz($matriz, $nombreDeLaTabla, $campoIdRegistro) {
$this->startElement("Tabla");
$this->writeAttribute("nombre",$nombreDeLaTabla);
if(is_array($matriz) && gettype($campoIdRegistro)=="string"){
foreach ($matriz as $llave => $renglon){
if($campoIdRegistro == "")
$this->creaRegistroDesdeArreglo($renglon, $llave);
else
$this->creaRegistroDesdeArreglo($renglon, $renglon[$campoIdRegistro]);
}
}
$this->endElement();
}

//Cierra el elemento raíz y finaliza el documento, regresando además el contenido del documento actual.
public function finalizaDocumento(){
$this->endElement();
$this->endDocument();
return $this->outputMemory();
}

/**
* Establece que el programa PHP actual generará como salida un archivo XML y envía a esta la salida el contenido del
* documento creado.
*/
public function salida(){
header('Content-type: text/xml');
echo $this->finalizaDocumento();
}
}
?>


Así, nuestra clase recibirá el nombre de ConsultaXML, ya que el propósito de ésta clase es crear documentos XML que representen conjuntos de datos estructurados, como lo que se obtienen de las consultas a bases de datos o a los archivos estructurados, o cualquier otro tipo de información que necesite de un formato similar.

Esta clase hereda sus métodos y propiedades de la clase predefinida XMLWriter, lo cual está indicado por la sentencia class ConsultaXML extends XMLWritter. Utilizamos esta clase predefinida porque ya posee todos los métodos necesarios para escribir documentos XML con etiquetas y atributos, adicionando además un control de errores para prevenir que los documentos creados contengan errores en su estructura. Otro punto importante es que XMLWriter permite dirigir el salida completa del archivo tanto a la memoria como al disco duro, de esta forma podemos utilizar la función echo para dirigir esta salida como respuesta del servidor al navegador, tal y como lo hicimos en el último ejemplo al respecto.

El primer método que encontramos en la clase es iniciaDocumento:
$this->openMemory();
$this->setIndent(true);
$this->setIndentString(" ");
$this->startDocument($version,$juegosDeCaracteres);
$this->startElement($elementoRaiz);

el cual deberá ser el primer método que se llama para crear el documento XML, en éste se establece que el nuevo objeto XMLWrite tendrá una salida en forma de cadena en la memoria, a través de la sentencia $this->openMemory(), se establece que existirá indentación hacia la derecha en el documento con $this->setIndent(true) y con $this->setIndentString(" ") definimos que el caracter espacio en blanco será utilizada para esta indentación. Se establece además el encabezado del documento, y finalmente se crea el primer elemento del documento, que es indicado por el parámetro $elementoRaiz. Como también podemos apreciar, en los parámetros se definen valores por defecto para facilitarnos el uso de este método, ya que éstos serán los que requeriremos en la mayoría de los ejemplos, teniendo siempre la posibilidad de establecer nuevos valores.

Los métodos creaCampo, creaRegistro y creaTabla llevarán a cabo toda la tarea de llenar el contenido del documento XML, permitiendo cada una de ellas crear los respectivos elementos Campo, Registro y Tabla, como habíamos establecido en las consideraciones previas.

El método finalizaDocumento cerrará la etiqueta del primer elemento que fue creado con iniciaDocumento, además finaliza el documento y devuelve todo su contenido como respuesta.

$this->endElement();
$this->endDocument();
return $this->outputMemory();

Esta respuesta puede ser recibida de diferentes formas, ya sea siendo almacenada en una variable como una cadena de texto, o mandada a alguna función para crear un archivo, o puede ser enviada a la función echo para el propósito que ya se describió, lo cual es precisamente lo que se hace en el siguiente método:
public function salida(){
header('Content-type: text/xml');
echo $this->getDocument();
}

Que antes de mandar el contenido como salida del programa, establece que el tipo de salida será la de un archivo XML, para que el navegador tenga conocimiento y la trate como tal.

Este código puede ser incluido dentro del script donde se vaya a llevar a cabo la generación del documento XML, o puede ser almacenado en un archivo .php aparte que será llamado por medio de la función require, lo cual personalmente recomiendo para facilitar su reutilización en otros programas. Para este ejemplo el código se almacenará en una archivo llamado ConsultaXML.php.

Implementación del nuevo código

Ahora pasemos a lo bueno, que es utilizar esta clase, para lo que escribiremos un nuevo programa en PHP cuyo archivo recibirá el nombre de generaXMLConClase.php, y creará un documento a partir de una matriz, conformada por arreglos asociativos. El código es el siguiente:


<?
require("PruebasPHP/ConsultaXML.php"); //Referencia al archivo que contiene la clase ConsultaXML
//Arreglos que representan la información del documento XML
$registro1 = array('idAutor'=>1,'Nombre'=>"Juan Rulfo",'Nacionalidad'=>"Mexicano",'Profesion'=>"Novelista");
$registro2 = array('idAutor'=>2,'Nombre'=>"Pablo Neruda",'Nacionalidad'=>"Chileno",'Profesion'=>"Poeta");
$registro3 = array('idAutor'=>3,'Nombre'=>"Miguel de Cervantes Saavedra",'Nacionalidad'=>"Español",'Profesion'=>"Novelista, Poeta y Dramaturgo");
$tabla = array($registro1, $registro2, $registro3);
//Creación del documento XML
$consultaXML = new ConsultaXML();
$consultaXML->iniciaDocumento();
$consultaXML->creaTablaDesdeMatriz($tabla, "Autores", "idAutor");
$consultaXML->salida();
?>


Nota: Tal vez está de más señalarlo pero recuerda que la ruta que coloques en la función require indica la ubicación del archivo ConsultaXML.php, por lo que si lo almacenas en un directorio distinto al de generaXMLConClase.php deberás indicar ese cambio en el código anterior.

Después del require, creamos una serie de arreglos asociativos que representan el contenido del documento XML definido previamente. En seguida se crea una instancia de la clase ConsultaXML llamada $consultaXML, y como vemos, al utilizar este objeto el código se vuelve bastante pequeño, que es una de las cosas que me gustan de la utilización de objetos: el encapsulamiento que ocurre dentro de los métodos, con el cual se facilita enormemente el trabajo con las clases diseñadas.

Al utilizar el método salida() todo el contenido será regresado al navegador como un archivo XML, y podemos probarlo utilizando un cliente como Internet Explorer o Mozilla Firefox, y mandando llamar al archivo PHP escribiendo una URL como la que sigue en su barra de direcciones:
http://localhost/generaXMLConClase.php

La salida se verá directamente en la pantalla del navegador, como se muestra en la siguiente ejemplo capturado, en el que se utilizó Firefox:



De ésta forma queda lista la refactorización del código para generación de XML del lado del servidor. El siguiente paso será llevar a cabo un cambio más, pero ahora del lado del cliente, para mejorar la forma en la que se acceda a los elementos del documento XML recibido. Sin embargo, ese será el tema de nuestro siguiente post.

Hasta la próxima.

martes, 3 de febrero de 2009

Generando una respuesta XML desde PHP


Saludos blogueros, y una disculpa porque no tuve la oportunidad de postear este fin de semana largo, pero ya estamos de vuelta para continuar con la esta serie de anotaciones sobre Ajax, ahora combinándolo con programación del lado del servidor.

En el ejemplo del último post al respecto, desde PHP se generó una respuesta en forma de contenido HTML, la cual fué recibida por una función en Javascript que desplegó este contenido directamente en la pantalla. Como se habrán dado cuenta existe una limitación en este método, y es que la respuesta consiste únicamente en texto plano, con formato HTML sí, pero cuyo único uso práctico sería desplegarse en pantalla. En uno de los ejemplos anteriores, vimos que una forma de obtener una respuesta del servidor con información estructurada, es utilizando archivos XML, cuya información puede ser procesada por una función Javascript utilizando el DOM. Ahora, como también indicamos la tecnología PHP no solo se limita a devolver contenido HTML, sino que también puede enviar una respuesta en diferentes tipos de archivos, entre ellos XML. Así, este ejemplo consistirá en programar un cliente Ajax que solicitará el archivo de un programa PHP, y esperará una respuesta XML, mientras del lado del servidor el programa PHP creará un grupo de datos, y generará un salida con éstos en forma de un archivo XML.

Para este ejemplo dejaremos un momento las obras literarias, y realizaremos una petición de información relacionada con nuestro servidor Web, solicitando los datos del navegador desde el que se realiza la petición, la información del software que utiliza el servidor Web, así como la fecha y hora del sistema operativo de este mismo servidor. La petición será procesada en PHP, y dado que la respuesta consistirá en varios datos, estos serán devueltos en formato XML, en el cual se indicarán tanto el nombre del dato que se devuelve como el valor que tienen cada uno, considerando los siguientes datos:

-Información del navegador utilizado por el usuario.
-Software del servidor Web.
-Fecha y hora del servidor Web.

El siguiente esquema puede ayudar a ilustrar el proceso completo:



Así, por medio de Ajax el cliente realizará una petición HTTP como las que ya hemos visto, solicitando el recurso http://localhost/generaXML.php. Desde el archivo generaXML.php se obtendrán los datos del servidor que se consideraron y a partir de ellos creará un documento XML, cuyo contenido será generado por medio de la función echo, utilizando previamente la función header, para indicarle al navegador que le será devuelto un archivo XML como respuesta. Este contenido será recibido por medio de Ajax como la respuesta a la petición inicial, y por medio de una función Javascript será desplegado en pantalla dentro de una tabla.

Programación del cliente

En este ejemplo, el cliente solamente contendrá un botón para iniciar la petición de la información, y la información será desplegada en una tabla HTML, una vez que se haya recibido la respuesta del servidor. El código HTML que deberemos escribir en nuestro editor de texto es el siguiente:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Cuarto ejemplo con Ajax, combinándolo con PHP y XML</title>
<script language="javascript">
<!--Sitio del código Javascript-->
</script>
</head>
<body>
Para realizar la petición al servidor Web
<input type="button" value="presiona aqui" onclick="solicita()" />
<table id="tblInfo" cellspacing="1" bordercolor="#CCCCCC">
<tr style="background:#E1E1E1"><td><strong>Dato devuelto</strong></td>
<td><strong>Valor</strong></td>
</tr>
</tbody>
</table>
</body>
</html>


Recordando que la codificación del archivo al momento de almacenarse debe ser UTF-8. En este ejemplo nuevamente colocaremos por separado el código Javascript del HTML, de forma que para probarlo solo será necesario unir ambos, colocando el siguiente código Javascript dentro del las etiquetas que se señalan:

var READY_STATE_COMPLETE=4;
var peticionHttp = null;
//Función que devuelve el objeto de la clase XMLHttpRequest
function inicializaXhr() {
   if(window.XMLHttpRequest) {
      return new XMLHttpRequest();
   }
   else if(window.ActiveXObject) {
         return new ActiveXObject("Microsoft.XMLHTTP");
   }
}
//Función que inicia todo el proceso y lleva a cabo la petición al servidor
function solicita() {
   peticionHttp = inicializaXhr();
   if(peticionHttp) {
      peticionHttp.onreadystatechange = procesaRespuestaXML;
      peticionHttp.open("POST", "http://localhost/generaXML.php", true);
      peticionHttp.send(null);
   }
}
//Función en la que se procesa el documento XML recibido
function procesaRespuestaXML() {
   if(peticionHttp.readyState == READY_STATE_COMPLETE) {
      if(peticionHttp.status == 200) {
         var documentoXml = peticionHttp.responseXML; //Creamos el objeto de tipo documento XML
         var root = documentoXml.getElementsByTagName("Informacion")[0]; //Obtenemos la raíz del documento
         //Accedermos al contenido de cada campo contenido en el elemento Informacion
         var infoCliente = root.getElementsByTagName("Cliente")[0].firstChild.nodeValue;
         muestraRenglon("Navegador",infoCliente);
         var infoSoftware = root.getElementsByTagName("Software")[0].firstChild.nodeValue;
         muestraRenglon("Software del servidor",infoSoftware);
         var infoFecha = root.getElementsByTagName("Fecha")[0].firstChild.nodeValue;
         muestraRenglon("Fecha del servidor",infoFecha);
         var infoHora = root.getElementsByTagName("Hora")[0].firstChild.nodeValue;
         muestraRenglon("Hora del servidor",infoHora);
      }
   }
}
//Función que agrega un nuevo renglon a una tabla especificada, utilizando los valores recibidos
function muestraRenglon(colEtiqueta, colValor){
   var tbody = document.getElementById('tblInfo').getElementsByTagName("TBODY")[0]; //Referencia a la tabla
   var renglon = document.createElement("TR"); //Crea un renglon
   var columna = document.createElement("TD"); //Crea una columna
   columna.appendChild(document.createTextNode(colEtiqueta)); //Agrega texto a la columna
   renglon.appendChild(columna); //Agrega la columna al renglon
   columna = document.createElement("TD"); //Crea una nueva columna
   columna.appendChild(document.createTextNode(colValor));
   renglon.appendChild(columna);
   tbody.appendChild(renglon); //Agrega el renglon a la tabla
}

Revisando el código nos damos cuenta que reutilizamos parte del código de los ejemplos anteriores para llevar a cabo una petición que se inicia con la función solicita():
function solicita() {
  peticionHttp = inicializaXhr();
  if(peticionHttp) {
    peticionHttp.onreadystatechange = procesaRespuestaXML;
    peticionHttp.open("POST", "http://localhost/generaXML.php", true);
    peticionHttp.send(null);
  }
}

Dentro de la función solicita se crea el objeto peticionHttp, y después de validar que se creo correctamente con la condición, indicamos que en cuanto se reciba la respuesta del servidor Web, se ejecute la función procesaRespuestaXML, después de lo cual realizamos la petición del recurso, en este caso identificado por la URL http://localhost/generaXML.php. Para éste ejemplo no se enviarán datos al servidor Web, lo cual indicamos pasando al método send un valor null.

En la función procesaRespuestaXML es donde se lleva a cabo el procesamiento del documento XML, que es enviado como respuesta por el servidor Web:
function procesaRespuestaXML() {
  if(peticionHttp.readyState == READY_STATE_COMPLETE) {
    if(peticionHttp.status == 200) {
      ...

Después de que validamos que se haya recido una respuesta completa, podremos pasar a procesar el documento XML.
var documentoXml = peticionHttp.responseXML;
var root = documentoXml.getElementsByTagName("Informacion")[0];

En esta dos líneas creamos el objeto documentoXML, que utilizamos para acceder al contenido del documento, y en seguida creamos el objeto root, con el que accedemos a la raíz del oducmento XML, en este caso el elemento Información. En seguida, accederemos a cada uno de los campos que encontramos dentro del elemento éste elemento, almacenando el contenido de cada campo en su respectiva variable.
var infoCliente = root.getElementsByTagName("Cliente")[0].firstChild.nodeValue;
muestraRenglon("Navegador",infoCliente);
En las dos líneas anteriores que se tomaron como referencia a este proceso, el contenido del elemento Cliente es alamacenado en la variable infoCliente, y desplegado en la página HTML dentro de la tabla tblInfo por medio de la función muestraRenglon.

La función muestraRenglon utiliza la funcionalidad que brinda el DOM del navegador, para crear objetos de tipo renglón y columna, permitiendo que se agreguen dinámicamente nuevos renglones y columnas a una tabla HTML, identificada por el id tblInfo.

De esta forma queda concluida la programación del lado del cliente para este ejemplo, pudiendo guardar este archivo HTML con cualquier nombre que nosotros deseemos, para probarlo posteriomente. Ahora, solo queda programar la aplicación PHP, que generará el documento XML de respuesta.

Programación del lado del servidor

Este pequeño programa recibirá el nombre de generaXML.php y el código que contendrá es el siguiente:

<?
header("Content-type:text/xml");
echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
echo "<Informacion>";
echo "<Cliente>".$_SERVER['HTTP_USER_AGENT']."</Cliente>";
echo "<Software>".$_SERVER['SERVER_SOFTWARE']."</Software>";
setlocale ( LC_TIME, 'spanish' );
echo "<Fecha>".strftime("%A %d de %B del %Y")."</Fecha>";
echo "<Hora>".strftime("%H:%M:%S")."</Hora>";
echo "</Informacion>";
?>

Al analizarlo vemos que la primer línea que aparece es la función header:
header("Content-type:text/xml");
En ésta se indica que el tipo de contenido que enviará el servidor Web como respuesta es un archivo XML, es decir que declaramos el tipo de salida que recibirá el navegador.

El resto del código consiste en realizar la creación del contenido de éste archivo XML, iniciando con:
echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>";
que es un encabezado que declara la versión de XML que estamos utilizando, así como la codificación de los caracteres utilizada, en este caso utf-8. El resto del código crea cada uno de los elementos del documento XML, que representarán los campos con la información que desde un inicio teniamos pensado solicitar al servidor Web, y cada uno de ellos corresponde a los elementos que será accedido desde Javascript en el cliente.

Al almacenar el archivo PHP en el servidor Web podemos pasar a la prueba, si seguimos cada uno de los pasos indicados aquí, la respuesta que obtendremos será similar a esta:



Conclusiones

El punto central de este ejercicio es demostrar como desde PHP se puede mandar un conjunto de datos en XML, y procesarlos en Javascript pudiendo discriminarlo para desplegarlos de manera que se respete la estructura original. Esta práctica es importante porque utilizando los mismos principios, es posible mandar información estructurada en mayores volumnes ,desde el servidor hasta el programa cliente en Javascript. Se pondrían mandar, por ejemplo una serie de registros que representen los renglones de una tabla en una base de datos, o que representen una serie de mensajes generados que describan el resultado de un proceso que ocurrió en el servidor. Entonces, debido a la utilidad que representan las respuestas XML desde el servidor, este será el estándar que utilizaremos para entregar las respuestas a nuestros clientes Ajax, en los ejemplos que seguirán de ahora en adelante.

En los siguientes post veremos como refactorizar el código tanto del cliente como del servidor para automatizar más el manejo de XML, además haremos una introducción al manejo de bases de datos combinando Ajax con PHP.

Hasta la próxima.

lunes, 26 de enero de 2009

Accediendo a un archivo XML con Ajax


En el post anterior vimos un ejemplo sencillo del uso de Ajax, veamos que más podemos hacer:

Supongamos que deseamos acceder a un archivo de texto estructurado, en este caso un archivo XML, que contenga la información de una serie de elementos que queremos desplegar en nuestra página web.

Para el ejemplo, el archivo XML al que vamos a acceder tendrá una estructura como la que se muestra a continuación:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<Obras_literarias>
  <Libro>
     <Autor>Juan Rulfo</Autor>
     <Titulo>Pedro Páramo</Titulo>
  </Libro>
  <Libro>
     <Autor>Juan Rulfo</Autor>
     <Titulo>El Llano en Llamas</Titulo>
  </Libro>
  <Libro>
     <Autor>Pablo Neruda</Autor>
     <Titulo>Veinte poemas de amor y una canción desesperada</Titulo>
  </Libro>
  <Libro>
     <Autor>Miguel de Cervantes Saavedra</Autor>
     <Titulo>Don Quijote de la Mancha</Titulo>
  </Libro>
  <Libro>
     <Autor>Jorge Luis Borges</Autor>
     <Titulo>Ficciones</Titulo>
  </Libro>
</Obras_literarias>

Este archivo, que podemos crear en un editor de texto sencillo como el bloc de notas, recibirá el nombre listaX.xml. Posee una estructura XML algo simple, ya que solo contiene una serie de entradas denominadas elementos sin contar con algún atributo. Más que nada se trata de una lista de libros con sus autores y títulos, que -como en todo archivo XML- se encuentra estructurada en una jerarquía de árbol, en las que el elemento padre -más externo- puede contener contener uno o más elementos hijos -más internos-

Ahora nuestro código Ajax solicitará al servidor Web que le sea devuelto éste archivo de texto, y una vez que el contenido haya sido recibido, el programa procesará la información utilizando un objeto de tipo documento XML, de forma que el contenido podrá ser procesado con métodos DOM para documentos XML, es decir, que el documento XML será manejado como un objeto y poseerá sus propios métodos y propiedades. De modo que crearemos un nuevo archivo HTML basándonos en el código del primer ejemplo con los siguientes cambios:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/
xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Obtener lista XML de autores con Ajax</title>
<script type="text/javascript">
function descargaArchivo() {
  // Obtener la instancia del objeto XMLHttpRequest
  if(window.XMLHttpRequest) {
     peticionHttp = new XMLHttpRequest();
  }
  else if(window.ActiveXObject) {
    peticionHttp = new ActiveXObject("Microsoft.XMLHTTP");
  }
  // Preparar la funcion de respuesta
  peticionHttp.onreadystatechange = muestraContenido;
  // Realizar peticion HTTP
  peticionHttp.open('GET', 'http://localhost/listaX.xml', true);
  peticionHttp.send(null);
  function muestraContenido() {
    if(peticionHttp.readyState == 4) {
      if(peticionHttp.status == 200) {
        //Creamos el objeto de tipo documento XML
        var documentoXml = peticionHttp.responseXML;
        //Obtenemos la raíz del documento
        var root = documentoXml.getElementsByTagName("Obras_literarias")[0];
        var tope = root.getElementsByTagName("Libro").length;
        //Recorremos todos los elementos Libro del documento
        for(var i = 0; i < tope; i++){
          libro = root.getElementsByTagName("Libro")[i];
          autor = libro.getElementsByTagName("Autor")[0].firstChild.nodeValue;
          titulo = libro.getElementsByTagName("Titulo")[0].firstChild.nodeValue;
          muestraHTML('pRespuesta',"Autor: "+autor+", t&iacute;tulo: "+titulo+"<br/>");
        }
      }
    }
  }
  function muestraHTML(id, texto){
    if(document.getElementById){
      document.getElementById(id).innerHTML += texto;
    }
  }
}
window.onload = descargaArchivo;
</script>
</head>
<body>
<p id="pRespuesta"></p>
</body>
</html>

Podemos ver que el proceso ver que el proceso de petición del archivo es casi idéntico al del ejemplo anterior, con la diferencia del nombre del archivo solicitado al servidor Web:
peticionHttp.open('GET', 'http://localhost/listaX.xml', true);

La parte interesante se da al momento de procesar la respuesta dentro de la función muestraContenido, cuando se accede al contenido del archivo XML:

var documentoXml = peticionHttp.responseXML;

En ésta primer línea utilizamos la propiedad responseXML para crear un objeto que manejará la información del documento XML llamado documentoXML. Siguiendo con el código:
var root = documentoXml.getElementsByTagName("Obras_literarias")[0];
var tope = root.getElementsByTagName("Libro").length;

En la primer línea se crea un nuevo objeto llamado root que contendrá la raíz del documento XML, es decir el elemento padre Obras_literarias, y a partir de éste objeto accederemos al resto de la información del documento. En la segunda línea guardamos en la variable tope la cantidad total de elementos hijo Libro que contiene root por medio de su propiedad length.
for(var i = 0; i < tope; i++){
   libro = root.getElementsByTagName("Libro")[i];
   autor = libro.getElementsByTagName("Autor")[0].firstChild.nodeValue;
   titulo = libro.getElementsByTagName("Titulo")[0].firstChild.nodeValue;
   muestraHTML('pRespuesta',"Autor: "+autor+", t&iacute;tulo: "+titulo+"<br/>");
}

Ésta parte del código consiste en un sencillo ciclo for que recorrerá todos los elementos Libro del objeto root. Con cada elemento Libro se creará un objeto llamado libro, después obtendremos el contenido de los elementos Autor y Titulo de este objeto, almacenando el valor de sus contenidos en las variables autor y titulo respectivamente. Por último, una vez que tenemos estos valores los mostramos en nuestra página web por medio de la función muestraHTML, cuya explicación aparece a continuación:
function muestraHTML(id, texto){
  if(document.getElementById){
    document.getElementById(id).innerHTML += texto;
  }
}

Ésta función recibe como primer parámetro el identificador del elemento de la página HTML, en el que se mostrará el texto que recibe como segundo parámetro.

Una vez que creamos el archivo listaX.xml, lo almacenamos en el servidor Web e implementamos el código HTML que acabamos de revisar, al momento de probar este programa debemos obtener en la pantalla de nuestro navegador un resultado como el que se muestra a continuación:

Autor: Juan Rulfo, título: Pedro Páramo
Autor: Juan Rulfo, título: El Llano en Llamas
Autor: Pablo Neruda, título: Veinte poemas de amor y una canción desesperada
Autor: Miguel de Cervantes Saavedra, título: Don Quijote de la Mancha
Autor: Jorge Luis Borges, título: Ficciones

Donde apreciamos como se accedió correctamente a cada uno de los elementos con contenido de texto del archivo, formándose una lista sencilla de autores y obras literarias.

La importancia de éste segundo ejemplo radica en que al utilizar Ajax, muchas veces es útil obtener respuestas en formato XML, para poder conseguir información más estructurada de nuestro servidor Web y no solo texto simple, sobre todo al momento de combinar Ajax con lenguajes del lado de servidor como PHP y trabajar con bases de datos. A medida que vayamos avanzando en el manejo de Ajax y veamos ejemplos más avanzados, se verá el potencial del manejo de respuestas XML.

En el ejemplo de nuestro siguiente post, veremos como se puede combinar Ajax con PHP, para desde un formulario HTML enviar datos al servidor Web que serán procesados en un programa PHP.

Hasta la próxima.