Leer XML con PHP (I)

Facebooktwittergoogle_pluslinkedinmailFacebooktwittergoogle_pluslinkedinmail

Ahora que ya sabemos crear un XML a partir de un dataset (por ejemplo una matriz) llega el momento de ser capaces de leer un fichero en formato XML.

En contra de lo que muchos piensan al principio, este es un problema inherentemente más complejo que la creación del XML. Esto se debe a que, cuando creamos un XML, nosotros decidimos como debe crearse, que estructura debe tener, cual será el nombre de los nodos y de los atributos, etc. En suma. Nosotros planteamos la estructura del fichero, y escribimos un script que cree ese fichero.

La lectura es justo la operación inversa. Partimos de un fichero XML que nos “viene dado” y debemos tener claros todos sus datos (nombre de los nodos, atributos, etc), porque eso es, exactamente, lo que debemos leer. Es decir, un lector de XML se hace para una estructura dada (siempre, claro está, que se trate de un XML válido y bien formado). Debemos conocer la estructura. Ya no la decidimos nosotros.

Para leer archivos XML en PHP contamos con varias soluciones. Ninguna es, definitivamente, la mejor. Algunas emplean más recursos pero, por otro lado, son más rápidas. Otras son más lentas, pero emplean menos recursos, o son más fáciles de programar… En general, hay que buscar una solución de compromiso. Buscamos una solución fácil, mantenible, lo más flexible que se pueda, y que emplee los resursos justos.

En este artículo vamos a leer el XML que creamos anteriormente, y recuperaremos los datos para que después puedan ser procesados como necesitemos.

USO DE SimpleXMLElement

Esta es, de lejos, la forma más fácil y operativa para leer y parsear un XML. Veamos cómo usarla. Para empezar vamos a partir de una versión simplificada del archivo obras.xml que tenemos de los ejercicios anteriores. Luego lo iremos complicando hasta llegar a entender todo lo necesario. La versión “reducida” es obras_1.xml y su listado es el siguiente:

Cómo ves, esta es una versión realmente simplificada, ya que cada nodo obra solo tiene su propio contenido (el nombre de la obra), sin atributos, ni otros nodos XML anidados. El resultado que esperamos obtener es una matriz que será, también, inherentemente simple y reducida, así:

El código para obtener este resultado es leer_1.php, listado a continuación:

El código está suficientemente documentado como para que puedas entender con facilidad lo que hemos hecho. No obstante, vamos a detenernos en algunos detalles.

EL CONSTRUCTOR

Para convertir una fuente de datos XML en un formato de objeto procesable empleamos el constructor de la clase SimpleXMLElement. Sólo el primer parámetro es obligatorio. Hace referencia a la fuente de datos XML. Por defecto esta sería una variable. Como, en este caso, se trata de un archivo externo, el tercer parámetro debe indicar, cómo ves, un valor true. El valor false (que es el que se asume por defecto) indicaría que el origen de datos XML debe buscarse en una variable, no en un archivo. Por ejemplo, si el contenido de datos XML estuviera en una variable de memoria, digamos $datosXML, el constructor se podría haber puesto así:

$contenidoXML = new SimpleXMLElement($datosXML);

Como no es el caso, ya que trabajamos a partir de un archivo externo, debemos asignar el valor true al tercer parámetro. Esto nos obliga a establecer, también el segundo parámetro. Este le ponemos, en este ejemplo, con el valor 0. El segundo parámetro son opciones para el constructor. Estas se refieren al uso de CDATA, DTD, etc. Como, por ahora, no vamos a usar estas opciones, mantenemos el 0 sin entrar en más detalles.

RECORRIENDO LOS NODOS

Ya tenemos un objeto con el contenido del XML. Ahora vamos a procesarlos. Los nodos hijos se identifican por su nombre, que se toma como una propiedad de cada nodo. Por eso insitíamos tanto, al principio de este artículo, en tener clara cual es la estructura del XML. Recorremos cada nodo como una variable que, en realidad, es un objeto en sí misma.

foreach ($contenidoXML->obra as $obra) {

Dada la estructura tan sencilla que estamos leyendo, cada una de las variables $obra (en cada iteración del bucle) contiene el valor del nodo, es decir, en este caso, el nombre de la obra a que se refiere. Sin embargo, lo contiene como u objeto, por lo que, para poder almacenarlo de forma cómoda (es decir, manejable) en una variable, debemos hacerle un casting para pasarlo a cadena, y recortar los posibles espacios y saltos de línea, ya que en el XML los hay a ambos lados del valor del nodo.

trim((string)$obra)

Y ya está. Con este proceso tan simple hemos obtenido la matriz del XML.

CON ATRIBUTOS

Ahora vamos a complicar un poco más la cosa. Lo normal es que los nodos XML puedan tener atributos y, en ese caso, también debemos ser capaces de recuperarlos en la matriz. Suponte la siguiente versión de nuestro archivo XML, al que hemos llamado obras_2.xml:

Esta vez debemos obtener una matriz en la que cada elemento obra esté formado no sólo por el valor del nodo, sino también por el de los atributos, así:

Para ello, vamos a recurrir al script leer_2.php, cuyo listado es el siguiente:

Observa las líneas resaltadas. Lo primero que llama la atención es que, una vex dentro del nodo, recuperamos todos los atributos mediante el método attributes(), así:

$atributosDeNodoObra = $obra->attributes();

Y aquí la cosa se complica un poco más. Lo que obtenemos es una matriz con los atributos del nodo que estamos procesando. Cada uno de ellos se identifica con una propiedad que coindice con el nombre que tiene el atributo en el XML (una vez más, aparece la necesidad de conocer la estructura del XML. El valor de estas propiedades también debe sufrir un casting para poder procesarlo cómo una cadena, como en esta línea de ejemplo:

$elementoObra["fecha_de_inicio"] = (string)$atributosDeNodoObra->inicio;

Es decir. El nodo es un objeto, y los atributos son, a su vez, objetos.

NODOS ANIDADOS

Nuestro XML original contenía, dentro de cada nodo obra, un nodo anidado que se llama personal_tecnico, que, a su vez, tiene un atributo, llamado miembros, que contiene el número de miembros de la dirección facultativa de la obra. En conjunto, podemos considerar obras_3.xml:

La matriz que vamos a obtener es la siguiente:

Y el código que la obtiene, leer_3.php, tiene el siguiente listado:

Cómo ya conocemos la mecánica, entender esto es un poco más llevadero. El código está ampliamente comentado, para poder seguirlo. Presta especial atención a las líneas resaltadas.

EL FICHERO DE OBRAS COMPLETO.

Ahora ya tenemos la soltura necesaria para comprender cómo leer el fichero XML con todos los datos que teníamos en el original. Su listado responde a obras_4.xml:

Aquí la cosa ya se pone “peluda y con dientes”. Dentro de cada nodo obra, aparte del valor del elemento y sus propios atributos, tenemos un sub-nodo personal_tecnico, que también tiene un atributo con el número de miembros. Pero, además, cada uno, tiene unos sub-nodos miembro, con un atributo para el cargo del miembro y, como valor, el nombre de la persona que ostenta dicho cargo. Uffff… La matriz a obtener es la que vemos a continuación:

Como ves, esta sí es (por fin), la matriz completa original, que hemos recuperado para poder procesarla cómo convenga. El script que hace esta lectura es leer_4.php:

Cómo ves, si analizas el código, es siempre “más de lo mismo”. La razón por la que hemos hecho todos estos pasos es para que veas que se puede parsear cualquier XML, por complejo que sea, mientras tengamos clara cual es la estructura, y cual es el resultado que deseamos obtener.

En el próximo artículo aprenderemos más cosas sobre el uso de estas técnicas, para procesos más complejos. De momento, lo dejamos aquí (que ya vale, ¿no?).

     

3 comentarios:

  1. Pingback: Leer XML con PHP (II) » eldesvandejose.com

  2. Saludos Jose.

    He leído tu artículo y me ha resuelto varias dudas que tenía, pero por desgracia no todas. Explico lo que quiero hacer.

    En mi misma máquina, quiero enviar a través de una petición POST con CURL mediante una línea de comandos un archivo .xml (el cuál está validado de antemano) a la clase del servicio del webservice, adjunto el comando tal como creo que debe de ser:

    curl -v -X POST -k -f -H “Content-Type: application/xml” -d @xml1.xml https://localhost/ruta_de_mi_webservice/index.php?class=nombre_de_mi_clase_php

    El tema es que haciendo pruebas dentro de la misma clase php funciona y puedo obtener sus valores ( o sea parsearlo ) sin ningún tipo de problema, la dificultad la tengo en recuperar/instanciar/rescatar (llámalo como quieras) el .xml que le estoy enviendo a través de curl. No encuentro la manera de recuperarlo de ninguna forma
    dentro de mi clase php a la cual se lo estoy enviando.
    No sé si puedes ayudarme.

    Gracias de antemano y un saludo.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *