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