Download Tema 2, Apartado 2.2: Parsing de Documentos XML
Document related concepts
no text concepts found
Transcript
2.2 Parsing de documentos XML Introducción (1) Un documento XML se apoya en dos ideas [Obligatoria] Tiene que estar bien formado, y en consecuencia, estar construido en base a las normas de XML (los tags se abren y cierran, los tags se anidan de manera jerárquica, los atributos tienen que ir entrecomillados, etc.) [Opcional] El conjunto de elementos y atributos, y sus restricciones, pueden estar descritas formalmente en algún tipo de esquema (esquema XML, DTD, etc.) de manera que se pueda comprobar que el documento es válido Consecuencias Es posible construir parsers genéricos que comprueban que el documento (1) está bien formado, y si se desea, (2) es válido Existen parsers para los lenguajes más usuales En un lenguaje orientado a objetos, un parser XML es una librería de clases Introducción (2) Tipos de parsers Parsers tipo DOM Parsers tipo “streaming” Parsers tipo DOM (Document Object Model) Construyen un árbol en memoria equivalente al documento XML (pueden hacerlo, dado que la sintaxis de XML sigue un modelo jerárquico) Ventajas Sencillos de utilizar: para acceder a la información del documento, basta recorrer el árbol Suelen permitir modificar/crear un árbol y generar XML a partir de él Desventajas Consumo de memoria alto en aplicaciones servidoras que reciben muchas peticiones que involucran parsear/generar documentos XML grandes (normalmente las aplicaciones servidoras sirven cada petición en un thread) Introducción (y 3) Parsers tipo “streaming” No construyen un árbol en memoria, sino que procesan secuencialmente el documento en bloques Ventajas Mínimo consumo de memoria Especialmente útiles en las situaciones en las que los parsers de tipo DOM son prohibitivos Desventajas No todos tienen soporte para generar XML Más difíciles de usar que los parsers de tipo DOM Caso de estudio: parsers Java (1) SAX (Simple API for XML) Parser tipo streaming Forma parte de Java SE (Standard Edition) API: familia de paquetes org.xml.sax Es un pequeño framework basado en eventos El programador proporciona uno o varios objetos callback a los que el parser llamará cada vez que ocurra un evento de interés (apertura de un tag, cierre de un tag, un error, etc.) Dentro de los parsers de tipo streaming es de tipo “push” El parser envía eventos al código escrito por el desarrollador No tiene soporte para generación de XML Disponible también para otros lenguajes Caso de estudio: parsers Java (2) StAX (Streaming API for XML) Parser tipo streaming Forma parte de Java EE (Enterprise Edition) API: familia de paquetes javax.xml.stream Dentro de los parsers de tipo streaming es de tipo “pull” El desarrollador utiliza el API del parser para pedir los datos Tiene soporte para generación de XML DOM (Document Object Model) Parser tipo DOM El API está estandarizada por el W3C (http://www.w3c.org) => disponible para varios lenguajes Forma parte de Java SE API: familia de paquetes org.w3c.dom Tiene soporte para generación de XML Caso de estudio: parsers Java (3) DOM: tipos principales de nodos Paquete org.w3c.dom <<interface>> Document <<interface>> Comment <<interface>> CharacterData <<interface>> Node 0..n <<interface>> NodeList <<interface>> Element <<interface>> Attr <<interface>> Text <<interface>> CDATASection Representa un atributo de un elemento. Es un subtipo de Node, sin embargo, no es un nodo del árbol (los atributos de un elemento forman parte de su objeto Element) Caso de estudio: parsers Java (4) DOM. Ejemplo: representación DOM de Movies.xml (apartado 2.1) Document Text (EBI) Text (EBI) Comment Text (EBI) Element (movies) Text (EBI) Element (movie) [La Maldición...] Text (EBI) • Flechas verticales: hijos • Flechas horizontales: hermanos • EBI: Espacio en Blanco Ignorable Comment Text (EBI) Element (movie) [Amelie] ... Text (EBI) Element (identifier) Text (EBI) ... Text (EBI) Element (releaseDate) ... synopsis Text Text Text (EBI) Text (EBI) Caso de estudio: parsers Java (5) JAXP (Java API for XML Processing) API: familia de paquetes javax.xml.parsers y javax.xml.transform Forma parte de Java SE El API Java de los parsers SAX y DOM viene definida por interfaces estándar JAXP proporciona, entre otras cosas, dos factorías abstractas (patrón “Factory”), para poder crear instancias de parsers DOM y SAX Este mecanismo permite “enchufar” en Java SE cualquier parser SAX y DOM que implemente los interfaces Existen diversas implementaciones de parsers SAX y DOM que implementan los interfaces estándar Cualquier implementación de Java SE proporciona una implementación por defecto de los parsers SAX y DOM El código que escribe el desarrollador no depende del parser concreto que se utilice (porque trabaja contra los interfaces estándar de los parsers) NOTA: El API de StAX también viene definida por interfaces, y el propio API de StAX proporciona mecanismos para trabajar con un parser concreto Caso de estudio: parsers Java (6) JDOM y DOM4J Alternativas famosas a DOM Open Source Utilizan un modelo similar a DOM Tienen soporte para generación de XML Pensados para el lenguaje Java y más fáciles de usar que el API de DOM JDOM: http://www.jdom.org DOM4J: http://www.dom4j.org El API de DOM es demasiado tediosa de usar porque es de muy bajo nivel (no automatiza aspectos comunes) No está acoplada con el lenguaje Java (e.g. no utiliza java.util.List, sino que dispone de sus propias listas) Especialmente más fáciles que DOM cuando se trabaja con documentos orientados a datos, que es nuestro caso Utilizaremos JDOM Caso de estudio: parsers Java (y 7) JAXB (Java Architecture for XML Binding) Forma parte de Java EE (Enterprise Edition) API: familia de paquetes javax.xml.bind Como siempre, un conjunto de interfaces Existen múltiples implementaciones, algunas Open Source Parsing y generación de XML automático A partir de un esquema XML, genera clases Java equivalentes a las estructuras definidas en el esquema A partir de un documento XML, crea automáticamente instancias de las clases generadas, que contienen los datos del documento A partir de instancias de las clases generadas, permite generar un documento XML Solución “más sencilla” que el uso de un parser de más bajo nivel, aunque quizás “menos flexible” cuando se utiliza un enfoque REST (apartado 3.3) En los apartados 3.4 y 3.5 (SOAP), indirectamente, utilizaremos una solución parecida a JAXB JDOM (1) Tres paquetes principales para parsing/generación de XML org.jdom org.jdom.input Clases para construir un árbol JDOM en memoria a partir de un documento XML org.jdom.output Clases que modelan los distintos tipos de nodos (parecido al API de DOM, pero “más amigable”) de un árbol JDOM Clases para generar un documento XML a partir de un árbol JDOM Internamente JDOM utiliza el API de SAX y/o DOM mediante JAXP JDOM (2) Ilustraremos el API básica de JDOM tomando como ejemplo parte de la clase MovieXMLConversor Pertenece al subsistema “Movies” de los ejemplos de la asignatura (los proporcionaremos en clase de prácticas). Forma parte de los casos de estudio de los apartados 3.3 (REST) y 3.5 (SOAP) Dispone de métodos para convertir instancias de MovieInformationTO a XML, y viceversa. En particular, estudiaremos la implementación de dos métodos toMovieInformation(java.io.InputStream) : MovieInformationTO Recibe un documento XML que tiene la información de una película y crea un objeto MovieInformationTO con la misma información toXML(MovieInformationTO, java.io.OutputStream) Recibe un objeto MovieInformationTO y genera un documento XML con la misma información JDOM (3) MovieInformationTO - identifier : Long title : String runtime : short releaseDate : Calendar directorNames : List<String> actorNames : List<String> genres : List<Genre> synopsis : String El tipo Genre está declarado dentro de MovieInformationTO: public enum Genre { COM, DRA, HOR, ROM, SFI, THR}; + Constructor + Métodos get/set MovieXMLConversor + toMovieInformation(in : InputStream) : MovieInformationTO + toXML(movieInformation : MovieInformationTO, out : OutputStream) : void JDOM (4) Ejemplo de un documento XML con la información de una película Elemento identifier es opcional <?xml version="1.0" encoding="UTF-8"?> <!-- Amelie. --> <movie xmlns="http://ws.udc.es/movies/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://ws.udc.es/movies/xml Movies.xsd"> <title>Amelie</title> <runtime>120</runtime> <releaseDate day="19" month="10" year="2001"/> <director>Jean-Pierre Jeunet</director> <actor>Audrey Tautou</actor> ... <actor>Dominique Pinon</actor> <genre>COM</genre> <genre>ROM</genre> <synopsis> Amelie no es una chica como las demás. Ha visto a su pez de colores deslizarse hacia las alcantarillas ... </synopsis> </movie> JDOM (5) Visión global de org.jdom: principales tipos de nodos y métodos Comment Content 0..n Text Attribute + métodos getXXXValue() : XXX + getValue() : String 0..n Element Document + Document(rootElement : Element) + getContent() : List + getRootElement() : Element + Element(name : String [, ns : Namespace]) + addContent(child : Content) : Element + addContent(collection : Collection) : Element + getAttribute(name : String [, ns : Namespace]) : Attribute + getAttributeValue(name : String [, ns : Namespace]) : String + getChild(name : String [, ns : Namespace]) : Element + getChildren(name : String [, ns : Namespace]) : List + getChildTextNormalize(name : String [, ns : Namespace]) : String + getChildTextTrim(name : String [, ns : Namespace]) : String + getTextNormalize() : String + getTextTrim() : String + setAttribute(name : String, value : String [, ns : Namespace]) + setText(text : String) JDOM (6) Representación JDOM del documento XML de ejemplo Document Comment Text (EBI) Element (title) Text (EBI) ... Text (EBI) Element (movie) Element (releaseDate) ... synopsis Text Text • Flechas verticales: hijos • EBI: Espacio en Blanco Ignorable Text (EBI) JDOM (7) Document Constructor Document(Element rootElement) Método List getContent() Crea un documento con ese elemento raíz Devuelve la lista de hijos del documento Método Element getRootElement() Devuelve el elemento raíz del documento Ejemplo: Element movieElement = document.getRootElement(); Element Hay dos versiones de cada uno de los métodos que muestra el diagrama UML con la notación [, Namespace ns] Versión con parámetro name y ns: cuando es necesario especificar el espacio de nombres (aunque sea el espacio de nombres por defecto) para un elemento hijo o atributo Versión con parámetro name: cuando no hay que especificar espacio de nombres para un elemento hijo o atributo A no ser que se indique lo contrario, los nombres de los atributos no se cualifican (e.g. recuérdese que con los esquemas XML el valor de attributeFormDefault es unqualified por defecto ) JDOM (8) Element (cont) Constructores Element(String name [, Namespace ns]) Crea un elemento con el nombre especificado Ejemplo movieElement = new Element("movie", Namespace.getNamespace("http://ws.udc.es/movies/xml")); Método Element addContent(Content child) Añade un hijo (por el final) Devuelve this Ejemplo movieElement.addContent(titleElement); Método Element addContent(Collection collection) Añade una colección de hijos (por el final) Devuelve this Ejemplo Collection<Element> children = ... movieElement.addContent(children); JDOM (9) Element (cont) Métodos Attribute getAttribute(String name [, Namespace ns]) Devuelven el objeto Attribute correspondiente al nombre de atributo especificado La clase Attribute dispone de los métodos getDoubleValue, getIntValue, getFloatValue, getLongValue y getValue para obtener el valor de atributo como un número (métodos getXXXValue) o un String (método getValue) Ejemplo: int day = releaseDateElement.getAttribute("day").getIntValue(); Métodos String getAttributeValue(String name [, Namespace ns]) Devuelven directamente el valor de un atributo como un String a partir de su nombre Ejemplo String dayAsString = releaseDateElement.getAttribute("day"); JDOM (10) Element (cont) Métodos Element getChild(String name [, Namespace ns]) Devuelven un elemento hijo a partir de su nombre Ejemplo Element titleElement = movieElement.getChild("title", Namespace.getNamespace("http://ws.udc.es/movies/xml")); Métodos List getChildren(String name [, Namespace ns]) Devuelven la lista de elementos hijo con el nombre especificado Ejemplo List<Element> actorElements = movieElement.getChildren("actor", Namespace.getNamespace("http://ws.udc.es/movies/xml")); JDOM (11) Element (cont) Métodos Devuelve el texto normalizado contenido en el elemento especificado Se asume que ese elemento sólo tiene texto Texto normalizado Elimina los espacios en blanco del principio y el final Cada espacio en blanco contenido en el texto se sustituye por un blanco NOTA: espacio en blanco → un blanco, un tabulador, un salto de línea o una secuencia de cualquiera de los anteriores Útil para elementos que contienen “descripciones” String getChildTextNormalize(String name [, Namespace ns]) Si el documento XML fue escrito con un editor, posiblemente el autor, por legibilidad, partió la descripción en varias líneas Cuando el XML se genera automáticamente (e.g. desde un cliente o un servidor), normalmente no se incluyen espacios en blancos innecesarios antes, en medio y después del texto Los métodos getChildTextNormalize nos abstraen de este problema Ejemplo String synopsis = movieElement.getChildTextNormalize("synopsis", Namespace.getNamespace("http://ws.udc.es/movies/xml")); sypnosis contendría el resumen de la película sin blancos innecesarios y sin saltos de línea También existe String getTextNormalize() JDOM (12) Element (cont) Métodos String Devuelve el texto, sin espacios en blanco al principio y al final, contenido en el elemento especificado Se asume que ese elemento sólo tiene texto Útil para elementos que contienen “datos” getChildTextTrim(String name [, Namespace ns]) Si el documento XML fue escrito con un editor, quizás el autor incluyó blancos antes y después Cuando el XML se genera automáticamente (e.g. desde un cliente o un servidor), normalmente no se incluyen espacios en blancos innecesarios antes y después Los métodos getChildTextTrim nos abstraen de este problema Ejemplo short runtime = Short.valueOf( movieElement.getChildTextTrim("runtime", XML_NS)); También existe String getTextTrim() JDOM (13) Element (cont) Métodos void setAttribute(String name, String value [, Namespace ns]) Da valor a un atributo Si el atributo no existía, lo añade Si el atributo ya existía, lo reemplaza por el nuevo Ejemplo int day = ... releaseDateElement.setAttribute("day", Integer.toString(day)); Método void setText(String text) Establece el texto pasado como el contenido del elemento Ejemplo String title = ... titleElement.setText(title); JDOM (14) En org.jdom.input SAXBuilder + SAXBuilder() + build(in : InputStream) : Document El método build parsea la entrada con un parser SAX y crea el objeto Document que contiene el árbol JDOM JDOM (y 15) En org.jdom.output XMLOutputter + XMLOuputter(format : Format) + output(doc : Document, out : OutputStream) Format Constructor Lo invocaremos pasándole Format.getPrettyFormat() XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); Genera el XML en formato “bonito” y con codificación UTF-8 Método output Escribe el XML correspondiente al documento pasado es.udc.ws.movies.xml.MovieXMLConversor (1) package es.udc.ws.movies.xml; import import import import import java.io.InputStream; java.io.OutputStream; java.util.ArrayList; java.util.Calendar; java.util.List; import import import import import import import org.jdom.DataConversionException; org.jdom.Document; org.jdom.Element; org.jdom.Namespace; org.jdom.input.SAXBuilder; org.jdom.output.Format; org.jdom.output.XMLOutputter; import es.udc.ws.movies.model.GenreOperations; import es.udc.ws.movies.model.MovieInformationTO; import es.udc.ws.util.exceptions.ParsingException; es.udc.ws.movies.xml.MovieXMLConversor (2) public class MovieXMLConversor { public final static Namespace XML_NS = Namespace.getNamespace("http://ws.udc.es/movies/xml"); private MovieXMLConversor() {} public final static MovieInformationTO toMovieInformation(InputStream in) throws ParsingException { try { SAXBuilder builder = new SAXBuilder(); Document document = builder.build(in); Element movieElement = document.getRootElement(); return toMovieInformation(movieElement); } catch (Exception e) { throw new ParsingException("Error deserializing instance" + " of " + MovieInformationTO.class, e); } } es.udc.ws.movies.xml.MovieXMLConversor (3) public final static void toXML(MovieInformationTO movieInformation, OutputStream out) throws ParsingException { try { Element movieElement = toXML(movieInformation); Document document = new Document(movieElement); XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); outputter.output(document, out); } catch (Exception e) { throw new ParsingException("Error serializing instance " + " of " + MovieInformationTO.class, e); } } // Otros métodos públicos... es.udc.ws.movies.xml.MovieXMLConversor (4) /* --------- Helper methods for XML to Java conversion. ----------- */ private final static MovieInformationTO toMovieInformation( Element movieElement) throws DataConversionException { Element identifierElement = movieElement.getChild("identifier", XML_NS); Long identifier = null; if (identifierElement != null) { identifier = Long.valueOf(identifierElement.getTextTrim()); } String title = movieElement.getChildTextNormalize("title", XML_NS); short runtime = Short.valueOf( movieElement.getChildTextTrim("runtime", XML_NS)); Calendar releaseDate = getReleaseDate( movieElement.getChild("releaseDate", XML_NS)); List<String> directorNames = getTextList( movieElement.getChildren("director", XML_NS)); List<String> actorNames = getTextList( movieElement.getChildren("actor", XML_NS)); es.udc.ws.movies.xml.MovieXMLConversor (5) List<String> genres = getTextList( movieElement.getChildren("genre", XML_NS)); String synopsis = movieElement.getChildTextNormalize("synopsis", XML_NS); MovieInformationTO movieInformation = new MovieInformationTO( identifier, title, runtime, releaseDate, directorNames,actorNames, GenreOperations.toListOfGenres(genres), synopsis); return movieInformation; } es.udc.ws.movies.xml.MovieXMLConversor (6) private final static Calendar getReleaseDate( Element releaseDateElement) throws DataConversionException { int day = releaseDateElement.getAttribute("day").getIntValue(); int month = releaseDateElement.getAttribute("month").getIntValue(); int year = releaseDateElement.getAttribute("year").getIntValue(); Calendar releaseDate = Calendar.getInstance(); releaseDate.set(Calendar.DAY_OF_MONTH, day); releaseDate.set(Calendar.MONTH, Calendar.JANUARY + month - 1); releaseDate.set(Calendar.YEAR, year); return releaseDate; } private final static List<String> getTextList( List<Element> elementList) { List<String> textList = new ArrayList<String>(); for (Element c : elementList) { textList.add(c.getTextNormalize()); } return textList; } es.udc.ws.movies.xml.MovieXMLConversor (7) /* --------- Helper methods for Java to XML conversion. ----------- */ public final static Element toXML( MovieInformationTO movieInformation) { Element movieElement = new Element("movie", XML_NS); if (movieInformation.getIdentifier() != null) { Element identifierElement = new Element("identifier", XML_NS); identifierElement.setText( movieInformation.getIdentifier().toString()); movieElement.addContent(identifierElement); } Element titleElement = new Element("title", XML_NS); titleElement.setText(movieInformation.getTitle()); movieElement.addContent(titleElement); Element runtimeElement = new Element("runtime", XML_NS); runtimeElement.setText( Short.toString(movieInformation.getRuntime())); movieElement.addContent(runtimeElement); es.udc.ws.movies.xml.MovieXMLConversor (8) Element releaseDateElement = getReleaseDate(movieInformation.getReleaseDate()); movieElement.addContent(releaseDateElement); List<Element> directorElements = getElementList( "director", XML_NS, movieInformation.getDirectorNames()); movieElement.addContent(directorElements); List<Element> actorElements = getElementList("actor", XML_NS, movieInformation.getActorNames()); movieElement.addContent(actorElements); List<Element> genreElements = getElementList("genre", XML_NS, movieInformation.getGenres()); movieElement.addContent(genreElements); Element synopsisElement = new Element("synopsis", XML_NS); synopsisElement.setText(movieInformation.getSynopsis()); movieElement.addContent(synopsisElement); return movieElement; } es.udc.ws.movies.xml.MovieXMLConversor (9) private final static Element getReleaseDate(Calendar releaseDate) { Element releaseDateElement = new Element("releaseDate", XML_NS); int day = releaseDate.get(Calendar.DAY_OF_MONTH); int month = releaseDate.get(Calendar.MONTH) – Calendar.JANUARY + 1; int year = releaseDate.get(Calendar.YEAR); releaseDateElement.setAttribute("day", Integer.toString(day)); releaseDateElement.setAttribute("month", Integer.toString(month)); releaseDateElement.setAttribute("year", Integer.toString(year)); return releaseDateElement; } es.udc.ws.movies.xml.MovieXMLConversor (y 10) private final static List<Element> getElementList( String elementName, Namespace namespace, List textList) { List<Element> elementList = new ArrayList<Element>(); for (Object t : textList) { Element element = new Element(elementName, namespace); element.setText(t.toString()); elementList.add(element); } return elementList; } Comentarios (1) es.udc.ws.util.exceptions.ParsingException Los métodos públicos de la clase MovieXMLConversor devuelven esta excepción cuando hay algún problema durante el proceso de parsing Forma parte de subsistema de utilidades de los ejemplos de la asignatura Extiende a RuntimeException Representa un error grave Sólo es preciso capturarla en los lugares en los que explícitamente se quiere tratar En resto de sitios, la excepción “fluye” hacia arriba Como cualquier tipo de excepción (java.lang.Throwable), permite especificar un mensaje y/o encapsular una excepción (la que produjo el problema) RuntimeException ParsingException Los constructores mostrados invocan a los constructores de la clase padre + ParsingException() + ParsingException(message : String) + ParsingException(message : String, cause : Throwable) + ParsingException(cause : Throwable) Comentarios (y 2) Validación y esquemas XML JDOM no tiene soporte directo para validar con esquemas XML, aunque existe un “workaround” sencillo para poder hacerlo En cualquier caso, y como justificaremos en el apartado 3.3, no siempre queremos que los clientes y/o servidores validen los documentos XML que reciben, sino simplemente que comprueben que Los documentos estén bien formados Los elementos y atributos que se requieran estén presentes NOTA: obsérvese que MovieXMLConversor hace ambas cosas En consecuencia, los documentos XML que produce MovieXMLConversor tampoco generan una referencia al esquema XML Los documentos XML (Movie-1.xml, Movie-2.xml, etc.) presentes en Subsystems/Movies/Documents incluyen una referencia al esquema XML Al editarlos con un editor con soporte para XML (e.g. el editor que viene con Eclipse Web Tools Platform), el editor puede detectar el uso de elementos y atributos incorrectos