Aviso: chapapost técnico al canto!
Digamos que tienes que sacar ciertos datos de un XML que no depende de tí (provisto por una ejem, 3rd party) y cuya semántica no es todo lo buena que a uno, friki de la semántica, le gustaría. De hecho, seamos claros, el XML tiene tanta semántica como una castaña en flor, y al abrirlo te encuentras con unas bonitas tablas que te recuerdan que el HTML es un hijo bastardo del XML.
Lo primero a lo que acude uno es a una expresion XPath a base de índices, de las de toda la vida. Por favor, deme ud. el TR[4] del TD[1], que ya se yo que eso se corresponde con el dato que quiero. Muchas gracias. Pero ahora viene tu amigo el 3rd party, y te dice que el XML te lo puede cambiar sin más ni más, y que posiblemente esas tablas cambien con el tiempo, y se les añada nuevos campos, o bien se reordenen o… Vamos, que te cagas en el 3rd party y en el marrón que te ha caído. Y es cuando decides que índices, no, gracias.
Primer caso
En este primer caso tenemos un XML que tiene una pinta tal que esta:
<RESOURCE name="books">
<TABLE name="books">
<FIELD name="title" datatype="char"/>
<FIELD name="author" datatype="char"/>
<FIELD name="pages" unit="–" datatype="int"/>
<DATA>
<TABLEDATA>
<TR>
<TD>Moby-Dick</TD>
<TD>Herman Melville</TD>
<TD>704</TD>
</TR>
<TR>
<TD>1984</TD>
<TD>George Orwell</TD>
<TD>176</TD>
</TR>
</TABLEDATA>
</DATA>
</TABLE>
</RESOURCE>
Semánticamente feo de narices, ya lo sabemos, pero a ti te toca lidiar con eso sí o sí. Lo suyo para evitar índices a mano, sería poder consultar en ese momento que posición tiene el atributo que buscas. Por ejemplo, para el atributo pages:
/RESOURCE[@name='books']/TABLE[@name='books']//TR[1]/TD[
count( /RESOURCE[@name='books']/TABLE[@name='books']/FIELD[@name='pages']/preceding-sibling::*)+1
]
Expliquemos un poco ese chorizo de expresión XPath. Empezaremos por la expresión más interna, que es la que tiene el quid de la cuestión:
count( /RESOURCE[@name='books']/TABLE[@name='books']/FIELD[@name='pages']/preceding-sibling::*)+1
Este XPath devuelve la posición (índice) del atributo pages dentro de la tabla. Para ello usa la expresión preceding-sibling::* que nos devuelve todos los nodos hermanos que tiene ese nodo XML por delante suya. En el caso de pages, devolvería los nodos de title y author, que están por delante suya en el mismo nivel del árbol XML. Así que si hacemos un count() de esa expresión y le sumamos uno tendríamos el índice de pages dentro de la tabla.
Y el resto del chorizo es bien sencillo, y se resume en lo que sería un acceso a una tabla mediante índices de los de toda la vida:
/RESOURCE[@name='books']/TABLE[@name='books']//TR[1]/TD[3]
pero sustituyendo el segundo índice (TD[3]) que en nuestro caso es el problemático, por la expresión count() de antes. De esta forma, ya nos da igual si el que provee el XML decide el día de mañana que pages debe ir antes que author, o que entre author y pages va a ir un nuevo atributo ISBN. Nuestra expresión XPath sabrá encontrarlo igualmente.
Segundo caso
Este segundo caso ya es un flagrante intento de hacer pasar un HTML por XML, con un mal disfraz y un bigote postizo de segunda mano:
<TABLE name="image">
<FIELD name="name" datatype="char"/>
<FIELD name="address" datatype="char"/>
<DATA>
<TABLEDATA>
<TR>
<TD>name</TD>
<TD>pic12316972.jpg</TD>
</TR>
<TR>
<TD>resolution</TD>
<TD>1024×768</TD>
</TR>
<TR>
<TD>timestamp</TD>
<TD>2008-04-03T11:57:40.015</TD>
</TR>
</TABLEDATA>
</DATA>
</TABLE>
Aquí, lo que se pretende, además de cagarnos en el que creo tal aberración de XML, es pedir la información asociada con resolution por ejemplo. Pero dado que resolution es parte de la información y no de la metainformación, como debería ser, es donde tenemos un problema.
El enfoque en esta expresión XPath es distinto de la anterior, pero igual de sencillo una vez que has dado con ella:
child::TR/child::TD[text()='resolution']/following::TD[1]
Lo que hace es, a partir de todos los nodos TR (child::TR), sacar un hijo TD cuyo texto corresponda con el atributo que buscamos (child::TD[text()='resolution']). Y una vez tenemos ese nodo, sólo nos queda irnos al TD siguiente a él (following::TD[1]) que es el que tiene el valor del atributo.
Compártelo
September 24th, 2008 | Programación |