APIs Java para XMLÍndice
2.1 Os princípios do processamento DOM 2.2 Introdução a navegação no DOM 2.3.1 Uma classe biblioteca de DOMs 2.3.2 Localizando Elementos pelo Tipo 2.3.4 Acessando Atributos por nome
3.2 A Interface SAX DocumentHandler
1. Introdução
Este trabalho procura mostrar como diferentes filosofias de programação Java para XML são utilizados. Duas APIs são apresentadas: DOM e SAX.
1.1. Document Object Model - DOM
Neste modelo, o documento XML inteiro é armazenado na memória num formato de árvore de nodos, todos descendendo de uma raiz. O programador pode então aplicar vários métodos para localizar e manipular os nodos. Este é seu modelo conceitual:
Essencialmente, o programador configura um parser com um XML fonte, e espera terminar. Se existir algum erro, o programador recebe um relatório do erro, caso contrário é retornado um objeto DOM que pode ser manipulado.
De uma maneira geral, para cada procura ou algum tipo de manipulação, é preciso começar pelo elemento raiz e ir subindo na hierarquia. Como todas as informações estão disponíveis na memória, é possível correlacionar e combinar informações como desejar.
1.2. Simple API for XML - SAX
Utilizando-se SAX , o documento XML é lido por um parser, que identifica cada elemento no momento em que é encontrado, fazendo uma chamada para um método especificado pelo programador enquanto o documento é lido. Este é o seu modelo conceitual:
Resumidamente, o programador configura um parser com uma fonte de entrada e o conecta com um conjunto de handler methods. Quando o parser é executado, ele dispara eventos que são capturados pelos handler methods. Cada vez que o parser detecta uma parte importante do documento XML, ele dispara o handler apropriado. Um erro pode acontecer em qualquer momento durante o parsing, então o programador receberá informações sobre os elementos do documento até o acontecimento do erro.
2. A API DOM
A API para trabalhar com DOM é fornecido pela recomendação World Wide Web Consortium (W3C) . Mais detalhes é encontrado de www.w3c.org/DOM/.
A unidade básica considerada no DOM é o Nodo. A interface Node é implementada por todas as diferentes subcategorias de Node listadas na tabela abaixo. Esta forma de representação é muito interessante para as linguagens orientadas a objetos como Java. Todos os tipos de Node possuem uma interface correspondente em Java.
Tabela 2.1 : Subtipos de Node em DOM
Além de vários subtipos de Node , DOM define interfaces de coleções de Nodes como o NodeList e o NamedNodeMap. DOM também especifica uma interface DOMException que pode ser utilizada para comunicar erros de exceção.
Essas interfaces são encontradas no pacote org.w3c.dom. 2.1. Os princípios do processamento DOM
A idéia básica é que um documento XML e transformado em um DOM constituído de objetos que implementam interfaces. Cada parte do documento é transformado em um objeto, e as conexões entre os objetos refletem a hierarquia do documento.
Ao se considerar em adotar um determinado parser DOM, é preciso prestar atenção em alguns aspectos. Um deles é o fato do parser validar ou não validar o documento fonte. A validação requer mais memória e processamento, mas é preciso garantir que o documento é bem formado e válido. Outro aspecto diz respeito a compatibilidade com os padrões estabelecidos pelos consórcios.
2.2. Introdução a Navegação no DOM
Vários métodos na interface Node do DOM define relacionamentos entre nodos que provem formas de movimentação pelas conexões entre Nodes. O diagrama da figura abaixo ilustra isso.
2.3. Exemplo de utilização DOM
Neste exemplo, nos utilizaremos o parser XML da Sun: o JAXP API parser toolkit, que substitui diversos toolkits diferentes. A Sun buscou neste novo API flexibilidade, utilizando-se apenas dois arquivos JAR – jaxp.jar e parser.jar - que devem ser colocados no diretório de extensões padrão. Estes pacotes devem ser incluídos
import java.io.*; import java.util.*; import javax.xml.parsers.*; import org.xml.sax.*; import org.w3c.dom.*;
Pelo fato do parser fazer todo o trabalho,tudo que se deve fazer é usar um DocumentBuilderFactory para criar um DocumentBuilder e especificar o arquivo de entrada. Por exemplo:
File xmlFile = new File( src ); //onde src é uma string DocumentBuilderFactory dbf = new DocumentBuilderFactory.newInstance(); DocumentBuider db = dbf.newDocumentBuilder(); Document doc = db.parse(xmlFile);
O Document é uma referência para um objeto que implementa a interface Document definida no pacote org.w3c.dom. A classe específica é fornecida pelo DocumentBuilder; mas não é preciso se preocupar com os detalhes internos: a interface Document fornece todos os métodos necessários.
O código de parsing também precisa considerar a captura de erros de exceção para reportar os vários erros que podem acontecer, como:
2.3.1. Uma classe biblioteca de DOM
Para ilustrar o uso de DOM, nos apresentamos uma classe util Java para manter objetos DOM residentes na memória. Essa classe pode ser útil em aplicações de servidores Web, mantendo na memória os arquivos que são freqüentemente usados, ao invés de fazer o parsing a cada pedido.
A primeira listagem de código nos mostra o método estático usado para obter uma referência a um único objeto DOMLibrary e as variáveis da instância.
import java.io.* ;import java.util.* ;import javax.xml.parsers.* ;import org.xml.sax.* ;import org.w3c.dom.* ; public class DOMlibrary { private static DOMlibrary theLib ; public synchronized static DOMlibrary getLibrary(){ if( theLib == null ) theLib = new DOMlibrary(); return theLib ; } // instance variables below this private Hashtable domHash ; private String lastErr = "none" ; // private constructor to ensure singleton private DOMlibrary(){ domHash = new Hashtable(); }
Um método completo para a criação de um DOM é mostrado na listagem abaixo, Este método é chamado com uma string, dando a localização de um arquivo XML e uma variável booleana com o valor true se a validação do documento é desejada. O método retorna uma referência para um Document caso execute com sucesso ou uma String contendo uma mensagem de erro caso contrário. Note que se o erro for causado por uma SAXParseException, a String conterá detalhes sobre a localização do erro.
// retorna ou um Document ou uma String se acontecer algum erro private Object loadXML( String src, boolean validate ) { File xmlFile = new File( src ) ; String err = null ; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setValidating( validate ); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse( xmlFile ); return doc ; }catch(ParserConfigurationException pce){ err = pce.toString(); }catch(SAXParseException spe ){ StringBuffer sb = new StringBuffer( spe.toString() ); sb.append("\n Line number: " + spe.getLineNumber()); sb.append("\nColumn number: " + spe.getColumnNumber() ); sb.append("\n Public ID: " + spe.getPublicId() ); sb.append("\n System ID: " + spe.getSystemId() + "\n"); err = sb.toString(); }catch( SAXException se ){ err = se.toString(); if( se.getException() != null ){ err += " caused by: " + se.getException().toString() ; } }catch( IOException ie ){ err = ie.toString(); } return err ; } // end loadXML
Outras classes acessam documentos na biblioteca DOM chamando o método getDOM mostrado no código abaixo. Este método é chamado passando uma String com a localização do arquivo XML. Se este arquivo já sofreu parsing, a referência para o objeto Document é retornada. Caso contrário, o arquivo sofrerá o parsing através da chamada do método loadXML.
// ou retorna um Document ou null se tiver problemas public synchronized Document getDOM( String src, boolean validate ){ Object doc = domHash.get( src ); File f = null ; if( doc == null ){ System.out.println("DOMlibrary.getDOM new " + src ); doc = loadXML( src, validate ); domHash.put( src, doc ); if( doc instanceof String ){ lastErr = (String) doc ; } } // se não for um documento, então deve ser uma String de erro if( doc instanceof Document ) { return (Document) doc ; } return null ; }
O final da biblioteca DOMLibrary é mostrada abaixo com alguns métodos úteis.
// utilize isto para forçar a remoção de um DOM. Retorna// a última cópia do DOM ou null caso não exista public synchronized Document removeDOM( String src ){ Document dom = (Document)domHash.get( src ); if( dom != null ){ domHash.remove( src ); // System.out.println("Removed " + src ); } return dom ; } // utilize isto para forçar uma atualização de um DOM public synchronized Document reloadDOM( String src, boolean validate ){ if( domHash.get( src ) != null ){ domHash.remove( src ); } return getDOM( src, validate ); } public String getLastErr(){ return lastErr ; } }
2.3.2. Localizando Elementos pelo Tipo
Depois que o Document é criado, todas as operações de programação o usarão como ponto de partida. O arquivo XML é fechado e o parser é descartado. Vamos dar uma olhada no que se pode fazer com o documento.
Na nomenclatura para DOMs descrita no W3C, todas as partes do documento é representada por um objeto do tipo Node. Os subtipos de Node definem vários tipos de comportamentos requeridos. O tipo de Node mais comum é o do tipo Element. O DOM fornece um tipo especial de interface de coleção chamada NodeList usada para referenciar uma lista de referências do tipo Node.
Suponha que temos uma lista de livros em XML com o elemento raiz Publications,onde cada tag Book contem uma série de outros elementos descritos no código XML da próxima seção. Obtemos uma NodeList de elementos Book da seguinte maneira:
Element dE = doc.getDocumentElement(); NodeList booklist = dE.getElementsByTagName(“Book”);
Sabemos o número de Elements desta maneira:
int bookCt = booklist.getLenght();
e podemos recuperar um determinado Elemento desta maneira:
Element bookE = (Element) booklist.item( n );
Onde n é o índice entre 0 e bookCt menos 1. Note que NodeList preserva a ordem dos Elementos encontrados no documento original. Além disso, a NodeList não é uma estrutura estática, mas sim dinamicamente reflete as mudanças no DOM feitos pelo programa. Também note que é preciso fazer um cast ao recuperar um elemento da NodeList, porque o método retorna uma referência a um objeto do tipo Node.
2.3.3. Navegando no Modelo DOM
Para trabalharmos sobre alguns exemplos práticos de código Java para navegação no modelo DOM, vamos utilizar o documento XML descrito abaixo, criado para armazenar informações pertinentes a livros e publicações:
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE Publications SYSTEM "publications.dtd" > <Publications date="May 5, 2000"> <Book isbn="1576102912" > <Title>Java 2 Exam Cram</Title> <Author>Bill Brogden</Author> <Edition edition="1" /> <DatePublished year="1999" /> <Publisher>The Coriolis Group</Publisher> <Press>Certification Insider Press</Press> <Series>Exam Cram</Series> <Size pp="388"/> <Cover img="images/j2ec.gif" /> <Topic>Java</Topic> <Topic>Certification</Topic> <Topic>Exam 310-025</Topic> <Topic>Certified Programmer for the Java 2 Platform</Topic> <Topic>Study Guide</Topic> <Errata code="ecj2" /> <BriefDescription>This compact study guide concentrates on the topics covered in Sun's Java 2 programmer certification exam. Numerous questions similar to those on the real exam are presented and discussed. </BriefDescription> </Book> <Book isbn="076450360X"> <Title>HTML 4 For Dummies</Title> <Edition edition="5"/> <Publisher>IDG Books Worldwide</Publisher> <Series>Dummies</Series> <DatePublished month="7" year="1999" /> <Size pp="400"/> <Author>Natanya Pitts</Author> <Author>Ed Tittel</Author> <Topic>HTML</Topic> <Topic>WWW</Topic> <BriefDescription> The fifth edition of this introductory book about the Hypertext Markup Language, the markup used to build documents for use on the Web. This book covers elements of page design, comprehensive markup definitions and examples, and includes a CD with examples taken from the text, along with a set of Web pages, templates, and online resources built specifically for the book's readers. </BriefDescription> </Book> </Publications>
Dado um Element representando um Book, é possível obter uma lista dos filhos este elemento. Por exemplo, o seguinte fragmento de código recupera uma NodeList de Author Elements pertencentes a um determinado livro. Caso nenhum elemento deste tipo é encontrado, a NodeList terá comprimento zero:
NodeList authors = bookE.getElementsByTagName(“Author”);
O texto associado com o Author Element é tratado como um Node filho; logo, para imprimir o nome dos autores neste exemplo, nos utilizamos o seguinte código:
for( int i = 0; i < authors.getLength(); i++) { Element aE = (Element) authors.item( i ); String txt = aE.getFirstChild().getNodeValue(); System.out.println(“Author” + txt ); }
Localizando Nodos Filhos
O texto de um Element pode ser armazenado no DOM em mais de um Node filho. No exemplo XML de um questionário a seguir, uma seção CDATA é utilizada para que marcação HTML seja incorporada:
<Qtext> <![CDATA[Please fill in <b>all</b> Fields ]]> </Qtext>
O modelo DOM resultante tem três filhos Nodes. O final de linha depois de <Qtext> é um Node, a seção CDATA é um Node, e a linha após o ]]> é um Node. Para recuperar todo o texto de um elemento, utilize o método a seguir:
String getChildrenText (Element e) { StringBuffer sb = new StringBuffer(); NodeList nl = e.getChildNodes(); for( int i = 0 ; i < nl.getLength(); i++){ sb.append( nl.item(i).getNodeValue(); ); } return sb.toString(); }
Navegando entre irmãos
Um objeto Element também conhece seus relacionamentos próximos no DOM. É possível localizar elementos anteriores e próximos no mesmo nível de hierarquia com o seguintes métodos:
Element prev = aE.getPreviousSibling(); Element next = aE.getNextSibling();
Estes métodos retornam null quando não existem irmãos naquele ponto.
2.3.4 Acessando atributos por nome
Atributos pertencentes a um Element podem ser acessados por nome para recuperar o seu valor, como em:
String isbnStr = bookE.getAttribute(“isbn”);
A String retornada será vazia caso não exista atributo com aquele nome. Este método funciona bem em XML quando os valores de atributos são apenas texto. Caso seu XML tenha referências de entidades nos atributos, como a seguir, onde Attr é uma extensão da interface Node:
Attr isbnStr = bookE.getAttribute(“isbn”);
É possível recuperar todos os atributos relacionados com um elemento com um NamedNodeMap, como a seguir:
NamedNodeMap map = bookE.getAttributes();
O conteúdo de um determinado atributo pode ser acessado pelo nome:
Node nd = map.getNamedItem(“isbn”);
Note que será retornado um Node do tipo Attr, ou null caso o atributo não exista. Para realmente recuperar o conteúdo do atributo faça o seguinte:
String value = nd.getNodeValue();
realmente recuperar o conteúdo do atributo faça o seguinte:
2.3.5 Modificando um DOM
Quando temos um Document na memória, podemos modifica-lo através de diversas maneira. Por exemplo, caso quiséssemos adicionar um atributo printing no tag Edition de um determinado livro, podemos fazer da seguinte maneira:
NodeList editNL = bookE.getElementsByTagName(“Edition”); Element edition = (Element) editNL.item( 0 ); Edition.setAttribute(“printing”, “3”);
A interface Document especifica métodos para criar objetos Node de todos os tipos. Por exemplo, se desejamos adicionar um novo livro ao Document exemplo:
Element addBook = doc.createElement(“Book”); doc.appendChild( addBook );
Title, Author e outros elementos podem ser criados e adicionados como filhos do objeto addBook. Existe também um método de inserir um Node filho antes de um determinado Node.
Para extrair porções de um Document ou reagrupar partes de um documento, DOM fornece a interface DocumentFragment.
3. A API SAX
O padrão SAX cresceu do fato do método DOM era muito complexo e inadequado para várias aplicações. Além disso, até então, cada parser XML para Java tinha seu próprio padrão de interface. Programadores determinados a trazer ordem para este caos produziram os primeiros rascunhos do SAX – num curto espaço de tempo.
O padrão SAX é hoje aceito largamente e forma a base dos parsers do modelo DOM bem como parsers que simplesmente provem uma interface SAX. O pacote que contém todas essas interfaces é o org.xml.sax.
Tabela 3.1 : Interfaces SAX
Basicamente o programador cria um parser SAX para ler uma determinada fonte e registra objetos implementando as várias interfaces com o parser. Quando o parser é executado, os objetos registrados são notificados quando eventos disparados pelo parser ocorrem.
3.1. Entrada do Parser SAX
A generalização das possíveis fontes de um parser é provido por uma classe InputSource. Uma InputSource pode ser criada por um fluxo de caracteres unicode ou um fluxo de bytes. Como as classes de IO do Java são na sua maioria orientadas a ler ou escrever em fluxo de bytes ou caracteres, existem muitas maneiras de criar um objeto InputSource.
A outra forma de fonte do parser é especificada em termos de sistema de nomeação Uniform Resource Identifier (URI). A fonte pode ser um nome de arquivo ou mesmo uma Uniform Resource Location (URL) para o uso de um arquivo remoto.
3.2. A Interface SAX DocumentHandler
Tipicamente a maioria do trabalho em um programa que usa o método SAX acontece nos métodos definido na interface DocumentHandler. Os métodos são listados abaixo:
Tabela 3.2 : Eventos disparados pelo parser SAX
3.3. Relatório SAX de Erro
A classe SAXException é uma classe básica para relatar erros. Pelo fato de que muita coisa errada pode acontecer durante um parsing, a classe SAXException é usada como um capturador genérico de problemas como erro de rede e arquivos. Maiores detalhes são fornecidos por uma SAXParseException no caso da descoberta de um erro de estruturação do documento pelo parser.
A interface ErrorHandler dispara três métodos que recebem uma SAXParseException: warning, error e fatalError. Quando ocorre um fatalError, a sua chamada é o ultimo evento disparado pelo parser antes de terminar. Métodos da SAXParseException permitem localizar exatamente o ponto onde ocorreu o erro no documento, em termos de linha e posição do caractere que causou o erro.
3.4. Classe HandlerBase
O pacote org.xml.sax prove uma classe chamada HandlerBase. Essa classe implementa as interfaces DocumentHandler, DTDHandler, ErrorHandler e EntityResolver com métodos que não fazem nada. Um programador tem que implementar apenas aqueles métodos relacionados com os eventos que lhe interessam.
3.5. Exemplo de SAX
Programar em SAX requer uma grande mudança de vista em relação a programação DOM. O parser SAX passa apenas uma vez pelo documento fonte, e o programador deve fazer tudo que ele precisa nesta única passagem. Uma das vantagens do SAX é que ele requer pouca memória, e a quantidade não dependo do tamanho do documento XML.
Vamos utilizar na construção do nosso exemplo o seguinte documento XML de notícias:
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE moreovernews SYSTEM "moreovernews.dtd"> <moreovernews> <article id="_8510757"> <url>http://c.moreover.com/click/here.pl?x8510756</url> <headline_text>Cyclone Commerce Poised to Fulfill Promise of E-Signature Legislation</headline_text> <source>Java Industry Connection</source> <media_type>text</media_type> <cluster>Java news</cluster> <tagline> </tagline> <document_url>http://industry.java.sun.com/javanews/more/hotnews/ </document_url> <harvest_time>Jul 25 2000 8:34AM</harvest_time> <access_registration> </access_registration> <access_status> </access_status> </article> <article id="_8514989"> <url>http://c.moreover.com/click/here.pl?x8510853</url> <headline_text>Schlumberger Showcases Integrated Smart Card-Based Solutions for Campus Market at Nacubo 2000</headline_text> <source>Java Industry Connection</source> <media_type>text</media_type> <cluster>Java news</cluster> <tagline> </tagline> <document_url>http://industry.java.sun.com/javanews/more/hotnews/ </document_url> <harvest_time>Jul 25 2000 8:34AM</harvest_time> <access_registration> </access_registration> <access_status> </access_status> </article>
A implementação do parser SAX da Sun no pacote javax.xml.parsers é bastante flexível. Um parser é obtido via SAXParserFactory da seguinte maneira:
SAXParserFactory fac = SAXParserFactory.newInstance(); SAXParser parser = fac.newSAXParser();
O parser é iniciado chamando um dos métodos parse fornecido. Estes diferem na maneira em que foi especificado o documento de entrada do parser. É possível usar um objeto InputStream, um objeto File, um URI, ou um objeto org.xml.sax.InputSource. O método parse também requer uma referência para um objeto que estende a classe HandlerInput.
3.5.1 Criando HandlerBase
HandlerBase é uma classe de utilidade que provê métodos nulos para cada método que o parser irá chamar quando ele detecta os eventos de parsing. A classe criada pelo programador deve estender a HandlerBase e prover métodos que sobrescrevem os eventos nos quais está interessado. Numa aplicação típica, este métodos são:
public void startDocument() {} public void endDocument() {} public void startElement ( String name, AttributeList attrib) {} public void endElement( String name ) {} public void characters ( char[] buf, int start, int length) {}
O método characters retorna o texto contido em um elemento, como o headline_text do nosso exemplo. Entretanto, uma chamada deste método não garante que todos os caracteres contidos no elemento serão retornados. Vejamos um exemplo:
<article id=”_8510757”> <url>http://c.moreover.com/click/here.pl?x8510756</url> <headline_text>Aqui vai uma manchete explicando a notícia</headline_text>
Os métodos chamados neste exemplo são os seguintes em ordem:
O problema da programação em SAX é que o programador tem apenas uma passada para fazer o que precisa. Caso se esteja procurando headlines que contem o valor “virus”, por exemplo, quando o programador detectou a palavra, o elemento url já havia passado, se ele não guardou o elemento url em algum lugar, já era.
A seguir o código de um exemplo simples de programação SAX. Ele usa o arquivo XML descrito acima e salva apenas os elementos url e headline_text em um arquivo contendo links chamado System.out. Caso uma palavra seja especificada como argumento de execução, apenas headlines com aquela palavra serão gravados.
import java.io.* ;import java.util.* ;import org.xml.sax.* ;import javax.xml.parsers.* ; public class SaxTest extends org.xml.sax.HandlerBase{ public static void main(String[] args){ if( args.length < 1 ){ System.out.println("Expects xml file name on command line"); System.exit(1); } try { SaxTest st = new SaxTest( args ); st.parse(); }catch(Exception ex){ ex.printStackTrace( System.out ); } } // instance variables File sourceFile ; String keyword ; StringBuffer urlSB, headlineSB ; boolean inUrl, inHeadline ; SaxTest(String[] args ) { sourceFile = new File( args[0] ) ; if( args.length > 1 ){ keyword = args[1].toUpperCase(); } } void parse() throws Exception { SAXParserFactory fac = SAXParserFactory.newInstance(); fac.setValidating( true ); SAXParser parser = fac.newSAXParser(); parser.parse( sourceFile, this ); } public void startDocument(){ System.out.println("Start parsing " + sourceFile.getAbsolutePath() ); } public void endDocument(){ System.out.println("End parsing " ); } public void startElement( String name, AttributeList attrib ){ if( inUrl = name.equals("url")){ urlSB = new StringBuffer( 50 ); } else if( inHeadline = name.equals("headline_text")){ headlineSB = new StringBuffer(200); } } public void endElement( String name ){ if( name.equals("headline_text") ){ String tmp = headlineSB.toString(); if( keyword != null ){ if( tmp.toUpperCase().indexOf( keyword ) < 0 ){ return ; } } String url = urlSB.toString(); System.out.println( "
Note que para capturar o texto dos dois elementos de interesse, é preciso concatenar s arquivos recebidos de characters em um objeto StringBuffer. Variáveis booleanas são setadas e resetadas pelo método startElement. Essa abordagem é típica do processamento SAX em qualquer linguagem.
3.5.2 Localizando Parse Errors
Problemas sérios de parsing causarão a chamada do SAXParseException. A listagem abaixo mostra como extrair informações ao máximo da exceção, transformando em uma string:
} catch (SAXParseException spe) { StringBuffer sb = new StringBuffer (spe.toString()); sb.append(“\n Line number: ”+ spe.getLineNumber()); sb.append(“\n Column number: ” + spe.getColumnNumber()); sb.append(“\n Public ID: ” + spe.getPublicId()); sb.append(“\n System ID” + spe.getSystemId()); return sb.toString(); }
4. Links RelacionadosW3C XML Page – http://www.w3c.org Página da Sun – http://sun.java.com Tecnologias XML e Java – http://www.sun.com.br/produtos-solucoes/software/apis.html API Overview, JavaSoft XML APIs – http://java.sun.com/xml/jaxp-1.1/docs/tutorial/overview/3_apis.html
5. Bibliografia
Além dos links acima, foram consultados os seguintes livros:
|