1. Introdução

Dentre as muitas vantagens que a XML oferece aos desenvolvedores, uma das mais interessantes é o fato de que não precisamos “inventar” o nosso próprio código para realizar o processamento de documentos XML. Por exemplo: o desenvolvedor não precisa perder tempo inventando uma lógica (algoritmo) para varrer um documento XML, identificando a abertura e o fechamento de tags para assim poder obter o conteúdo texto compreendido entre essas tags. Ao invés disso, os programas que lidam com XML podem fazer uso de bibliotecas especialmente projetadas para processar este tipo de estrutura.

Este artigo aborda uma das mais importantes bibliotecas da XML, denominada SAX (Simple API for XML). O restante do artigo está dividido da seguinte forma. Na Seção 2 explicamos o princípio de funcionamento da API SAX. A seguir, na Seção 3, mostramos apresentamos um exemplo prático de utilização da mesma em um programa Java. Na Seção 4, discutimos algumas de suas vantagens e limitações. Por fim, na Seção 5 apresentamos alguns comentários adicionais para quem pretende aprofundar-se em XML e SAX.

2. SAX - Princípio de Funcionamento

O primeiro passo para que você possa começar a trabalhar com a API SAX consiste em entender o seu princípio de funcionamento. Uma forma simples de fazer isso é imaginar que programar utilizando a SAX é uma atividade similar a participar de um jogo de pingue-pongue, onde, a cada momento, a bolinha será rebatida por um dos dois jogadores de cada lado da mesa. Nesse caso, o jogador 1 é o desenvolvedor e o jogador 2 é o processador SAX. Com o auxílio da Figura 1, explicaremos esse conceito de forma mais clara. Essa figura ilustra a forma com que a API SAX realiza o processamento de um documento XML bem simples.

Documento XML e eventos disparados pelo SAX

Figura 1: Documento XML e eventos disparados pelo SAX

O processamento de XML com SAX tem duas características interessantes: (i) produz um laço automático que varre o documento de início ao fim; e (ii) durante esse laço automático, dispara diversos eventos para possibilitar com que o desenvolvedor possa recuperar informações contidas no documento XML. Os passos a seguir mostram como funciona esse “jogo de pingue-pongue” entre o desenvolvedor e a API SAX.

  1. No início do jogo, a “bola está com a gente”, ou seja, nós desenvolvedores é que damos o “saque inicial”: em nosso programa, iremos indicar para o processador SAX o documento XML a ser processado e solicitaremos com que ele realize a operação de parsing (processamento).
  2. Iniciado o parsing, a “bola passa para o lado do SAX”: o processador SAX iniciará a operação de parsing. Essa operação produz um laço automático que varre o documento XML do início ao fim!!! Ou seja: você não precisa criar um loop para processar o documento XML, pois o SAX faz isso para você!
  3. Durante esse loop automático, todas as vezes que o SAX encontrar um evento relevante durante o processamento do documento, ele irá “rebater a bola” para o desenvolvedor, para que este possa tomar alguma ação. Mas o que significa “evento relevante”? É simplesmente uma parte do documento XML onde o desenvolvedor muito provavelmente precisará recuperar algum tipo de informação com o intuito de tratá-la em seu programa. Aqui estão alguns exemplos:
    • Início do documento
    • Início de uma tag,
    • Fechamento de uma tag
    • Valor entre compreendido entre uma tag de abertura e uma tag de fechamento.
    • Fim do documento.
  4. Em seu programa, o desenvolvedor deverá criar métodos para “rebater de volta a bola”, ou seja, métodos associados a cada evento disparado pelo processador SAX. É através do uso desses métodos - denominados métodos de call-back - que podemos recuperar as informações do XML e utilizá-las dentro do programa. Esses métodos devem possuir nomes padronizados, conforme veremos na seção a seguir.

3. SAX - Um Programa em Java

Nesta seção, apresentamos um programa Java que ilustra a utilização prática da API SAX. O programa realiza o processamento do documento XML com informações sobre países representado na Listagem 1.

Listagem 1: Documento XML de Países


<?xml version="1.0" encoding="ISO-8859-1"?>
<paises>
  <pais sigla="BR">
	<nome>Brasil</nome>
	<moeda>Real</moeda>
	<populacao>196655014</populacao>
  </pais>
  <pais sigla="CA">
	<nome>Canadá</nome>
	<moeda>Dólar canadense</moeda>
	<populacao>34349561</populacao>
  </pais>
  <pais sigla="MX">
	<nome>México</nome>
	<moeda>Peso mexicano</moeda>
	<populacao>114793341</populacao>
  </pais>
</paises>

O programa Java é apresentado na Listagem 2. A classe DevmediaSAX processa o documento XML com o uso da API SAX, recuperando as informações de cada país e as imprimindo na tela (usando uma linha para cada país). A explicação sobre o funcionamento do programa é apresentada detalhadamente logo após o código.

Listagem 2: Programa Java


import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * classe DevmediaSAX: processa o documento XML de países com o uso da API SAX,
 * recuperando as informações de cada país e as imprimindo na tela (usando uma
 * linha para cada país).
 * 
 * A classe "DevmediaSAX" é derivada da classe "DefaultHandler" da biblioteca
 * org.xml.sax.helpers.DefaultHandler. Isso faz com que "DevmediaSAX" “ganhe”
 * automaticamente um processador SAX com o comportamento default.
 * 
 * @author Eduardo Corrêa Gonçalves
 * 
 */

public class DevmediaSAX extends DefaultHandler {

	private String tagAtual;
	private String siglaAtual;

	/**
	 * construtor default
	 */
	public DevmediaSAX() {
		super();
	}

	/**
	 * Método que executa o parsing: laço automático que varre o documento de
	 * início ao fim, disparando eventos relevantes
	 * 
	 * @param pathArq
	 */
	public void fazerParsing(String pathArq) {

		// Passo 1: cria instância da classe SAXParser, através da factory
		// SAXParserFactory
		SAXParserFactory factory = SAXParserFactory.newInstance();
		SAXParser saxParser;

		try {
			saxParser = factory.newSAXParser();

			// Passo 2: comanda o início do parsing
			saxParser.parse(pathArq, this); // o "this" indica que a própria
								// classe "DevmediaSAX" atuará como
								// gerenciadora de eventos SAX.

			// Passo 3: tratamento de exceções.
		} catch (ParserConfigurationException | SAXException | IOException e) {
			StringBuffer msg = new StringBuffer();
			msg.append("Erro:\n");
			msg.append(e.getMessage() + "\n");
			msg.append(e.toString());
			System.out.println(msg);
		}
	}

	// os métodos startDocument, endDocument, startElement, endElement e
	// characters, listados a seguir, representam os métodos de call-back da API
	// SAX

	/**
	 * evento startDocument do SAX. Disparado antes do processamento da primeira
	 * linha
	 */
	public void startDocument() {
		System.out.println("\nIniciando o Parsing...\n");
	}

	/**
	 * evento endDocument do SAX. Disparado depois do processamento da última
	 * linha
	 */
	public void endDocument() {
		System.out.println("\nFim do Parsing...");
	}

	/**
	 * evento startElement do SAX. disparado quando o processador SAX identifica
	 * a abertura de uma tag. Ele possibilita a captura do nome da tag e dos
	 * nomes e valores de todos os atributos associados a esta tag, caso eles
	 * existam.
	 */
	public void startElement(String uri, String localName, String qName,
			Attributes atts) {

		// recupera o nome da tag atual
		tagAtual = qName;

		// se a tag for "<pais>", recupera o valor do atributo "sigla"
		if (qName.compareTo("pais") == 0) {
			siglaAtual = atts.getValue(0);
		}
	}

	/**
	 * evento endElement do SAX. Disparado quando o processador SAX identifica o
	 * fechamento de uma tag (ex: </nome>)
	 */
	public void endElement(String uri, String localName, String qName)
			throws SAXException {

		tagAtual = "";
	}

	/**
	 * evento characters do SAX. É onde podemos recuperar as informações texto
	 * contidas no documento XML (textos contidos entre tags). Neste exemplo,
	 * recuperamos os nomes dos países, a população e a moeda
	 * 
	 */
	public void characters(char[] ch, int start, int length)
			throws SAXException {

		String texto = new String(ch, start, length);

		// ------------------------------------------------------------
		// --- TRATAMENTO DAS INFORMAÇÕES DE ACORDO COM A TAG ATUAL ---
		// ------------------------------------------------------------

		if (tagAtual.compareTo("nome") == 0) {

			System.out.print(texto + " - SIGLA: " + siglaAtual);
		}

		if (tagAtual.compareTo("moeda") == 0) {

			System.out.print(" - MOEDA: " + texto);
		}

		if (tagAtual.compareTo("populacao") == 0) {

			System.out.println(" - POPULACAO: " + texto);
		}
	}

	/**
	 * Este é o saque inicial do jogo!! Recebe o nome o nome do arquivo XML de
	 * entrada, instancia um objeto da classe DevmediaSAX (myDevSax) e chama o
	 * método “fazerParsing” deste objeto
	 * 
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {

		if (args.length != 1) {
			System.err.println("ERRO: modo de chamar 'PaisesSAX nome_do_xml'");
			System.exit(1);
		}

		DevmediaSAX myDevSax = new DevmediaSAX();
		myDevSax.fazerParsing(args[0]);
	}

}

Para executar o programa, inicialmente salve o arquivo XML de países (Listagem 1) em uma pasta de seu computador. Supondo que você salvou o arquivo com o nome “paises.xml” na mesma pasta do programa DevmediaSAX, bastará fazer a seguinte chamada: java DevmediaSAX paises.xml.

A explicação detalhada sobre o o programa é apresentada a seguir:

  • O primeiro passo para usar SAX é criar uma classe filha da classe DefaultHandler da biblioteca org.xml.sax.helpers.DefaultHandler (veja que a classe DevmediaSAX estende DefaultHandler). Isso fará com que seu programa "ganhe" automaticamente um processador SAX com o comportamento default.
  • O método main (último método mostrado no código da classe) é utilizado simplesmente para dar o "saque inicial". Recorde que eu comentei que o desenvolvedor é quem dá o primeiro saque do jogo. Este método recebe o nome o nome do arquivo XML de entrada, depois instancia um objeto da classe DevmediaSAX (myDevSax) e chama o método "fazerParsing" deste objeto.
  • No método fazerParsing criamos uma instância da classe SAXParser (implementação do parser SAX propriamente dito). Esta instância é obtida através da factory SAXParserFactory. Depois disso, basta ordenar o início do processo de parsing. Como dissemos anteriormente o parsing é o laço automático que varrerá todo o documento XML e disparará automaticamente os eventos relevantes. No programa, o parsing é executado através do método "parse" de SAXParser: saxParser.parse(pathArq, this).
    • No comando acima, é importante comentar que o parâmetro "this" é utilizado para indicar que a própria classe "DevmediaSAX" atuará como gerenciadora de eventos SAX, ou seja, ela mesmo conterá em seu corpo os métodos de call-back (já que esses métodos poderiam ser definidos em outra classe).
    • Observe também que foi preciso realizar o tratamento de alguns tipos de exceção no corpo do método fazerParsing.
  • Os métodos seguintes representam os métodos de call-back, responsáveis pelo tratamento dos eventos SAX mostrados na Figura 1: startDocument, endDocument, startElement, endElement e characters.
  • O evento startDocument é disparado antes da primeira linha do arquivo XML ser processada. Em nosso programa exemplo, executamos uma única ação associada a este evento: imprimimos a mensagem "Iniciando o Parsing..." na tela.
  • Já o evento endDocument é disparado quando o SAX chega ao final do documento XML (após o processamento da última linha do arquivo). Em nosso programa, imprime-se a mensagem "Fim do Parsing..." quando o evento ocorre.
  • O evento startElement é bem mais interessante! Ele é disparado sempre que o processador SAX identifica a abertura de uma tag (ex: <nome>). Ele possibilita a captura do nome da tag e dos nomes e valores de todos os atributos associados a esta tag, caso eles existam. É exatamente esta ação que realizamos em nosso programa exemplo. Sempre que o evento ocorre, capturamos o nome da tag corrente (parâmetro "qName") e o armazenamos na variável "tagAtual". Caso o nome da tag seja "pais", também capturamos o valor do atributo "sigla" e o armazenamos na variável "siglaAtual".
  • O método startElement sempre deve ser declarado com quatro parâmetros, que foram denominados de "uri", "localName", "qName" e "atts" em nosso programa-exemplo.
    1. Parâmetro "uri": fornece a URI do namespace (em nosso programa o valor é vazio, pois não estamos utilizando namespaces).
    2. Parâmetro "localName": fornece o nome local da tag, sem prefixo de namespace (em nosso programa o valor é vazio, pois não estamos utilizando namespaces).
    3. Parâmetro "qName": é o mais utilizado. Fornece o nome do atributo (ou o nome qualificado se estivermos trabalhando com namespaces).
    4. Parâmetro "atts": caso a tag possua atributos, os dados dos mesmos podem ser recuperados através desse parâmetro. Em nosso exemplo, utilizamos esse parâmetro de forma bem simples, no comando atts.getValue(0), para recuperar o valor do primeiro atributo da tag "pais" (valor da sigla do país). O parâmetro atts é do tipo Attributes definido em org.xml.sax.Attributes.
  • O evento endElement é disparado quando o processador SAX identifica o fechamento de uma tag (ex: </nome>). Em nosso programa, utilizamos o evento apenas para "resetar" o valor da variável "tagAtual".
  • Por fim, temos o importantíssimo evento characters, disparado quando o processador SAX identifica uma seção de texto compreendida entre uma tag de abertura e uma tag de fechamento. Por exemplo: o texto "Brasil" entre as tags <nome> e </nome>. Em nosso programa-exemplo, utilizamos esse método para recuperar os nomes, a população e a moeda dos países e imprimir essas informações na tela.
    • Observe que precisamos fazer uso da variável "tagAtual" para podermos saber qual a tag associada ao texto! Na prática, quando você usar SAX sempre precisará utilizar algum tipo de recurso para armazenar a última tag processada pelo evento startElement. Só assim, você poderá saber quem é a tag "dona" do texto recuperado. Em nosso programa utilizamos uma simples variável string privada para esse controle, mas você pode pensar em outras formas.
    • Também é importante comentar que o método characters disponibiliza o texto no parâmetro "ch" que é um vetor de caracteres. Para jogá-lo numa variável string, basta fazer uso dos parâmetros "start" e "length", como mostrado no comando:
      • String texto = new String(ch, start, length).

A Figura 2 mostra o resultado da execução.

Resultado da Execução

Figura 2: Resultado da Execução

4. SAX - Vantagens e Limitações

A principal característica positiva da API SAX é a sua eficiência. É exatamente por esta razão que ela se tornou a principal API para a manipulação de documentos XML de tamanho grande. Ao contrário do que ocorre com a API DOM (outra API clássica para a manipulação de XML), quando trabalhamos com SAX o documento não é importado para a memória. O que é o processador SAX faz é simplesmente implementar um laço que varre o documento do início ao fim e dispara eventos sempre que um “trecho interessante” do documento é encontrado. O SAX foi exatamente criado com esse propósito: fazer o processamento de XML sem a necessidade de importar os dados para a memória (ou seja, o SAX contorna a deficiência que o modelo DOM possui para lidar com documentos muito grandes).

Embora eficiente, o esquema utilizado pelo SAX possui limitações. Alguns exemplos: durante o parsing, você não pode voltar para seções já processadas do documento. O processador SAX, em seu loop automático, anda apenas para frente. Além disso, a API SAX serve apenas para ler documentos XML e não para atualizá-los! Por sua vez, com o uso da API DOM é possível navegar em diferentes sentidos do XML e alterar o conteúdo do documento (mudar texto, remover elementos, inserir elementos, etc.). Na prática, tanto os modelos SAX e DOM são complementares. Dependendo do tamanho do documento e do problema que você deseja resolver você escolherá uma das duas API’s.

5. Comentários Finais

Este artigo apresenta apenas conceitos introdutórios sobre o uso da API SAX em programas Java. Apresentamos simplesmente uma “receita de bolo” para você manipular arquivos XML utilizando o modelo SAX. Como quase tudo ligado à XML, SAX é um tema rico e repleto de conceitos importantes. Alguns exemplos são: parsing com validação (uso de DTD’s), tratamento de caracteres especiais no texto (ex: texto com quebra de linha entre tags de abertura e fechamento), uso de namespaces, gerenciamento de exceções, tratamento de atributos (nosso documento exemplo contém “sigla” como único atributo), etc.

As referências básicas utilizadas para a elaboração deste texto foram as notas de aula da professora Vanessa Braganholo (IC/UFF). A Figura 1 foi adaptada de um exemplo mostrado no artigo “Desmistificando XML: da pesquisa à prática industrial”, de Mirella Moro e Vanessa Braganholo.