SimpleXML para PHP4
11 de Febrero de 2008 en Programación, PHP
Lecturas: 5,002

XMLDe un tiempo a ésta parte el formato XML se ha vuelto imprescindible para implementar protocolos de intercambio de datos entre sistemas y lenguajes.

Todos los lenguajes incorporan parsers con los que poder leer y generar XMLs de una forma más o menos amena. Con la evolución de PHP a su versión 5 apareció una extensión llamada SimpleXML que, con un funcionamiento al estilo de objetos, simplificaba el acceso a los datos abandonando el método de llamadas a ciertas funciones al encontrar tags de apertura y cierre en el archivo XML.

Es una forma muy cómoda de trabajar es muy fácil acostumbrarse, pero a veces nos encontramos con hostings sólo en PHP 4 y nos vemos obligados a programar parsers a la vieja usanza.

Recientemente encontré una portabilidad de SimpleXML para PHP 4 que, aunque no dispone de absolutamente toda la funcionalidad de la extensión original (sobretodo al generar XML de salida), suple perfectamente la necesidad.

SimpleXML44 es el nombre de este backport de la extensión SimpleXML (escrito en PHP5) a PHP4. Según la web del autor, simplifica y optimiza el acceso a los datos y permite cambios en nodos CDATA y atributos, pero no es posible agregar o borrar ciertos nodos del árbol DOM. En mi toma de contacto (sólo lectura) cumplió perfectamente mis expectativas y ya lo tengo en mi biblioteca de paquetes de programación PHP.

Está escrito íntegramente en PHP. Requiere la versión de PHP 4.4.2 o superior y tener instalada la extensión XML Parser (Expat). La licencia es de tipo BSD. Actualmente forma parte del Ister PHP4 Framework aunque es posible descargarlo atómicamente, con lo que facilita nuestro desarrollo en nuestro propio framework.

Se puede descargar desde SourgeForge. La documentación del SimpleXML44 es algo limitada, pues se dan pocos ejemplos, pero habiendo usado el SimpleXML original nos hacemos una idea rápida de su funcionamiento.

Ejemplo de funcionamiento

Vamos a suponer un ejemplo rápido para ver lo simple que es. Pongamos que tenemos un archivo XML de la siguiente forma, guardado como 'clientes.xml' en la raíz:

XML:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<clientes>
        <cartera>50011</cartera>
    <cliente id="101">
        <nombre>Pepito</nombre>
        <apellidos>Perez Garcia</apellidos>
        <mail>pepito@hotmail.com</mail>
    </cliente>
    <cliente id="201">
        <nombre>Juan</nombre>
        <apellidos>Apellido1 Apellido2</apellidos>
        <mail>juan@hotmail.com</mail>
    </cliente>
</clientes>

Pues bién, lo primero es descomprimir el archivo bajado en un directorio, donde queramos, por ejemplo en /simplexml44. En el script PHP pondremos lo siguiente:

PHP:
//Incluimos el paquete SimpleXML44
require_once 'simplexml44/IsterXmlSimpleXMLImpl.php';

//Creamos el objeto principal
$xml_object = new IsterXmlSimpleXMLImpl;
           
//Cargamos el documento a parsear
$doc = $xml_object->load_file('clientes.xml');

//Printamos la cartera
print 'Cartera: '.$doc->clientes->cartera->CDATA().'<br>';

//Recorremos los clientes y printamos su info
foreach($doc->clientes->cliente as $cliente)
{
    //Atributo ID
    $atributos = $cliente->attributes();
    print 'ID: '.$atributos['id'].'<br>';

    //Campos
    print 'Nombre Completo: '.$cliente->nombre->CDATA().' '.$cliente->apellidos->CDATA().'<br>';
    print 'e-mail: '.$cliente->mail->CDATA().'<br>';
}

Como vemos, es una forma de trabajar similar al SimpleXML original, muy sencilla e intuitiva.

Notas a tener en cuenta

  • Cada elemento hijo es un objeto.
  • Para extraer la información de un elemento se usa $elemento->CDATA(), aunque el campo no esté definido como CDATA.
  • Si un elemento tiene atributos, usando $elemento->attributes() obtenemos un array asociativo dónde la clave es el nombre del atributo y el valor es el valor del atributo.
  • Si un elemento está repetido (en nuestro caso el elemento 'cliente') en vez de un objeto se devuelve un array de objetos (en nuestro caso, un array de objetos 'cliente').
  • Si esperábamos un array de elementos pero sólo tenemos un sólo elemento, no se nos devuelve el array (lógico) y nuestro script puede fallar al esperar un array que no existe. La forma de manejarlo es mirando si es un array y si no lo es, poner el elemento dentro de un array:
    PHP:
    if(!is_array($doc->clientes->cliente)) $aux = array($doc->clientes->cliente);
    else $aux = $doc->clientes->cliente;

  • Si no es seguro que un elemento venga dado en el XML de origen, podemos preguntar si existe con isset():
    PHP:
    if(isset($doc->clientes->cartera)) ...

Los errores que devuelve SimpleXML44

SimpleXML44 devuelve errores si los encuentra. Tanto si el archivo de entrada no existe, como si hay algun fallo en el XML, devuelve un error al estilo Warning que no es enviado al log, sino que se muestra por pantalla en una forma un tanto peculiar.

Si no encuentra el archivo muestra por pantalla la ruta dónde debería estar, sin más, algo cómo:

/ruta/al/archivo.xml

Si el código del script hacer referencia a un elemento que no existe y no lo hemos protegido con el isset() se quejará devolviendo un Fatal Error así:

Fatal error: Call to a member function on a non-object in /var/www/script.php on line xx

Si el XML no está bién formado devolverá un Warning propio al estilo:

WARNING isterxmlexpatnonvalid->parse(): expat: mismatched tag [217021/43/1846]

dónde los números entre los corchetes significan lo siguiente:

  • El primero: Posición del Byte dónde está el error
  • El segundo: Columna en el archivo XML donde está el error
  • El tercero: Línia en el archivo XML donde está el error

Conclusión

SimpleXML44 es una portabilidad a PHP4 que nos permite la comodidad de la extensión SimpleXML de PHP5. Algo limitada y sufrida con los errores, pero versátil y fácil de usar. Si debes portar una aplicación a PHP4 a causa de las limitaciones que el servidor dónde te hospedas, usas XML y estás acostumbrado al SimpleXML original, dále un vistazo que puede ser muy útil.

Salu10

Tags: , , , ,
 Enviar a Fresqui

Leer los Comentarios

[ # 17556 ] Comment desde Covi [14 de Febrero de 2008, 07:41]

Hola, gran blog! ;)

Encontré la clase de este excelente y creo que poco conocido framework, en un plugin para Wordpress sobre el famoso servicio de Weather.com como base para lo que quería hacer.

En otras aplicaciones yo usaba, al menos, el DOM XML, pero en este servidor tampoco tenía la extensión instalada y por supuesto seguía con PHP4 como es lógico.

Antes de eso probé mil y una clases sin que me llegarán a gustar del todo hasta que como digo, dí de casualidad con ella y lo cierto es que como muy bien dices: si no te quieres complicar mucho la vida es genial. Sencilla e intuitiva sobre todo para solo lectura.

:) …lo que me trajo a este sitio es que, en mi opinión, la documentación del framework es algo pobre y aún no he conseguido editar los archivos XML de los que me sirvo…

Al principio solo los abría en modo lectura cargando los contenidos pero ni cargando el archivo consigo editarlo… me temo que será algún tipo de problema en la salida, quizá volver a escribirlo desde PHP con la salida modificada… el caso es que no sé y… no quiero dar la bara pero bueno… me vendría bien un poco de ayuda en este sentido ;)

Un saludo.
PD: Me ha gustado mucho el artículo, claro y sencillo.

[ # 17559 ] Comment desde Covi [14 de Febrero de 2008, 08:16]

Ups… no problem, perdón, eran dudas tontas, los ajustes al XML solo se refieren a la salida, creo que no se escribe en el fichero original :(

Muy obvio por otra parte ^^!
Lo dicho, sorry y saludos.

[ # 17593 ] Comment desde Xavi [15 de Febrero de 2008, 09:12]

Yo sólo lo uso para lectura. Si necesito generar alguna modificación me monto un pequeño template, header y listo ;)

Yo no he tenido que modificar nada de los archivos originales. Saludos!

[ # 18211 ] Comment desde Gustavo [03 de Marzo de 2008, 01:53]

como hago para llamar a los atributos de una etiqueta que se llama por ejemplo:
yweather:condition
probe con yweather:location->attributes() y no me deja por los :, me tira error de php probe con:
{’yweather:location’}->attributes(); y como que no lo encuentra…

pd: estoy ingresando bien la ruta, lo que pongo ahi es para no escribir todo…

Saludos

[ # 18212 ] Comment desde Xavi [03 de Marzo de 2008, 01:59]

No estoy seguro que se soporte… yo no lo ho probado nunca así…

[ # 19614 ] Comment desde Taha Paksu [11 de Abril de 2008, 08:19]

Please check www.phpclasses.org/browse/package/4484.html for my version of the PHP4 alternative of simplexml_load_file.

[ # 19616 ] Comment desde Xavi [11 de Abril de 2008, 08:31]

I’ll do.

[ # 20196 ] Comment desde Pau [22 de Abril de 2008, 08:30]

si el tag tiene dos puntos “:” prueba haciendo caso solo de la segunda parte del tag.

Es decir: location->attributes()

La primera parte indica el grupo/tipo al que pertenece.

El tutorial muy bueno, me has salvado!

[ # 24594 ] Comment desde Raul Sanchez [14 de Junio de 2008, 03:13]

Espectacular!

Una duda (seguramente estúpida)
¿Qué ocurre si en el archivo XML hay un tag que contiene un guión (-)?
Me tira un error por éso, ¿Cómo hago para escribirlo?

[ # 25298 ] Comment desde Raul Sanchez [20 de Junio de 2008, 02:15]

Realmente fue una pregunta estúpida :)

Por ejemplo, para el tag bastó con escribir {’tag-tag’} en el código

[ # 26674 ] Comment desde oscar [30 de Junio de 2008, 08:23]

Muy buena class :)

Una pregunta, como puedo hacer para que no me imprima los WARNING?

saludos

[ # 26675 ] Comment desde Xavi [30 de Junio de 2008, 08:34]

La única forma que tengo para no mostrarlo es usando un XML válido ;)

Puedes tirar por hacer unas verificaciones previas antes de pasar el string al parseador, como mirar si el string está vacío, si lleva una cabecera XML…

De todas formas, ése Warning lo printa directamente el propio SimpleXML4, así que puedes hacer un grep por el código fuente y cuando encuentres dónde se printa, comentes la línea, aunque no lo aconsejo, ya que entonces no sabrás si el XML es malo.

También puedes capturar la salida al navegador como si fuera un try/catch, con el ob_start(), así podrías analizar la salida y buscar si hay un Warning dentro antes de que sea enviado al navegador.

Salu10!

[ # 26714 ] Comment desde oscar [30 de Junio de 2008, 07:44]

gracias xavi, vamos a ver cual sera la solucion que tome.

Saludos ;)

[ # 27246 ] Comment desde Raul Sanchez [04 de Julio de 2008, 07:33]

Cito:

Si el código del script hacer referencia a un elemento que no existe y no lo hemos protegido con el isset() se quejará devolviendo un Fatal Error así:

Fatal error: Call to a member function on a non-object in /var/www/script.php on line xx

Me está ocurriendo ésto en momentos puntuales (yo creo que son momentos en los que los archivos XML estan siendo modificados o algo así, por lo que he podido ir viendo)

Para solucionarlo, se me había ocurrido…

$doc = $xml_object->load_file($url);
if (isset($doc)) {

codigo si xml ok

} else {

codigo si xml no ok

}

Pero no funciona… sigue tirando el error… :S

[ # 27638 ] Comment desde Xavi [07 de Julio de 2008, 08:22]

Si, yo también me he encontrado con éso. La solución que propones analiza el objeto accediendo a él, con lo que si no es un objeto válido petará. Yo, en cambio, pregunto si se trata de un objeto válido con instanceof. La clase con la que debes comparar creo que es la misma creada (IsterXmlSimpleXMLImpl), pero no estoy seguro porque se genera un objeto de objetos. Puede que sea algo como IsterXmlSimpleXMLElement.

Salu10.

[ # 28620 ] Comment desde Raul Sanchez [16 de Julio de 2008, 02:33]

Al final lo solucioné con ‘is_object’
Saludos!

[ # 31918 ] Comment desde oscar [12 de Agosto de 2008, 08:39]

bueno ahora me encuentro con este problema. resulta que parseo varios xml en el mismo script, el problema es que si alguno de esos xml esta mal formado me muestra el famosos WARNING y pues los xml de ahi en adelante ni modo.

el problema es que no tengo control sobre esos xml para hacerlos validos. que se podria hacer para que ignorara el warning o solo lo aplicara a ese xml en concreto, y siguiera con los demas sin problema?

Saludos ;)

[ # 31964 ] Comment desde Xavi [13 de Agosto de 2008, 09:08]

Si no tienes control sobre ellos, lo tienes difícil. Sabes qué puede estar mal? Temas de comillas? UTF8? Prueba de pre-tratar el mensaje recibido antes de enviárlselo al SimpleXML. Pasa los mensajes por un validador y que te diga dónde fallan. Es muy posible que simplemente sea cosa de codificación de carácteres. Prueba el utf8_encode()

Salu10.

[ # 32000 ] Comment desde oscar [13 de Agosto de 2008, 04:27]

hola, no es problema de caracteres, es problema de acceso al xml, en un momento determinado uno de los xml puede no estar disponible.

Saludos

[ # 32001 ] Comment desde Xavi [13 de Agosto de 2008, 04:33]

Investiga un poco la clase… a parte del load_file() hay otra que lee de un string. Podrías capturar el XML con un file_get_contents() con el que tienes controlado la existencia del archivo y luego leer la cadena resultante, o simplemente hacer un file_exists() antes de hacer el load_file().

Salu10.

[ # 35557 ] Comment desde Cristian Mora [09 de Septiembre de 2008, 08:48]

como hago para recorrer un codigo XML que esta en una variable y no en un archivo

[ # 35618 ] Comment desde Xavi [10 de Septiembre de 2008, 08:31]

Cristian, mirando la clase de la que haces el require, el tercer método contando el constructor es el que te lee de un string:

function load_string($string, $classname = null)

Con él ya puedes leer directamente de una variable.

Salu10.

[ # 62726 ] Comment desde Carlos [11 de Marzo de 2009, 03:21]

Pues yo me encuentro con el errorcito de marras: PHP Fatal error: Call to a member function on a non-object in /var/www/vhosts/soyinternet.com/httpdoc
s/losada/librerias/pasarela.php on line 20

donde esa línea es:

$impl = new IsterXmlSimpleXMLImpl;
$resXml = $impl->load_file($file);
foreach ($resXml->tpv->respago->children() as $res){
$restmp[]=$res->CDATA();
}

En php 5 funciona bien, en 4 da ese error, ¿alguna idea?
Gracias.

Escribe un Comentario





Estadísticas