Meu último artigo introduziu a ideia de que existem diferentes formas para abordar a análise do XML e ressaltou o ponto de que XML NÃO É UMA STRING; ao invés disso, é um modelo de documento orientado a objeto que pode ser representado utilizando uma string. O artigo de hoje continua essa discussão utilizando o cenário do Pete’s Perfect Pizza. Se você se lembra, Pete apareceu na porta e pediu que você melhorasse o sistema de forma que o balcão de atendimento (front desk) pudesse enviar pedidos de múltiplas pizzas em uma única mensagem XML. Você sabe que seu código de análise de string simples é falho e não será bem sucedido, então você pesquisa mais no Google[1] sobre XML e tem a ideia de utilizar SAX.

SAX – Simple API for XML – tem estado por aí por muitos anos e, se bem me recordo, foi originalmente um desenvolvimento liderado por David Megginson antes da virada do milênio. Nessa época, você tinha que baixar a versão Java do SAX direto do site pessoal de David. Isso se desenvolveu no SAX Project antes de ser finalmente adicionado ao Java Standard Edition 1.4.
SAX é uma interface de streaming para o XML, o que significa que aplicações utilizando SAX recebem notificações de evento sobre o documento XML sendo processado, um elemento e atributo por vez em ordem sequencial, partindo do início do documento e terminando com o fechamento do elemento ROOT. Isso significa que ele é extremamente eficiente para processar XML em um tempo linear sem demandar muitas exigências sobre a memória do sistema.
Voltando ao Pete’s, você trabalha duro e chega à seguinte classe baseada no parser SAX:
public class PizzaParser {
public List<PizzaOrder> order(InputStream xml) {
PizzaContentHandler handler = new PizzaContentHandler();
// do the parsing
try {
// Construct the parser by bolting together an XMLReader
// and the ContentHandler
XMLReader parser = XMLReaderFactory.createXMLReader();
parser.setContentHandler(handler);
// create an input source from the XML input stream
InputSource source = new InputSource(xml);
// Do the actual work
parser.parse(source);
return handler.getPizzaOrder();
} catch (Exception ex) {
throw new RuntimeException("Exception parsing xml message. Message: " + ex.getMessage(), ex);
}
}
static class PizzaOrder {
private final String pizzaName;
private final String base;
private final String quantity;
PizzaOrder(String pizzaName, String base, String quantity) {
this.pizzaName = pizzaName;
this.base = base;
this.quantity = quantity;
}
public String getPizzaName() {
return pizzaName;
}
public String getBase() {
return base;
}
public String getQuantity() {
return quantity;
}
}
/**
* Use this class the handle the SAX events
*/
class PizzaContentHandler extends DefaultHandler {
private String[] pizzaInfo;
private int index;
private List<PizzaOrder> outList;
private boolean capture;
/**
* Set things up at the start of the document.
*/
@Override
public void startDocument() {
outList = new ArrayList<PizzaOrder>();
}
/**
* Handle the startElement event
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
capture = true;
if ("pizzas".equals(qName)) {
capture = false;
} else if ("pizza".equals(qName)) {
pizzaInfo = new String[3];
capture = false;
} else if ("name".equals(qName)) {
index = 0;
} else if ("base".equals(qName)) {
index = 1;
} else if ("quantity".equals(qName)) {
index = 2;
}
}
/**
* Handle the endElement event
*/
@Override
public void endElement(String uri, String localName, String qName) {
if ("pizza".equals(qName)) {
outList.add(new PizzaOrder(pizzaInfo[0], pizzaInfo[1], pizzaInfo[2]));
}
}
/**
* Grab hold of incoming character data
*/
@Override
public void characters(char[] ch, int start, int length) {
if (capture) {
pizzaInfo[index] = new String(ch, start, length);
capture = false;
}
}
List<PizzaOrder> getPizzaOrder() {
return outList;
}
}
}
Este artigo não irá demonstrar como se usa SAX, existem muitos exemplos disponíveis se você procurar, mas vamos dar uma olhada no código. A primeira coisa a ser notada é que o método order(…) agora recebe um stream, em vez de uma string de forma condizente com a API de stream base:
public List<PizzaOrder> order(InputStream xml)
A próxima coisa a se notar é que PizzaParser utiliza uma classe aninhada, PizzaContentHandler, que estende a classe de ajuda do SAX DefaultHandler. A classe PizzaContentHandler captura uma lista de PizzaOrder e a passa de volta para a classe externa para retornar ao chamador. Isso significa que tudo que você precisa fazer para ter acesso aos eventos do SAX é sobrescrever métodos do handler, como startElement(…), endElement(…) etc.
Se você analisar melhor o código, vai perceber que ele é bastante complexo. Tudo que ele tem que fazer é criar uma lista de output, mesmo que haja muitas instruções if(), arrays temporários e switches booleanos que são usados para definir a parte correta da informação do ponto exato no documento. Esta é uma desvantagem do SAX: sua complexidade confere maior encargo ao programador e torna o seu código mais propenso a erros.
O SAX, entretanto, é mais flexível do que a tentativa anterior baseada em string, como pode ser observado com a unidade de teste abaixo:
public class PizzaParserTest {
private static final String ORDER_XML = //
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
"<pizza>\n" + // 8
" <name>Capricciosa</name>\n" + //
" <base>thin</base>\n" + //
" <quantity>2</quantity>\n" + //
"</pizza>\n";
private static final String ORDER_XML_2 = //
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><pizza><name>Capricciosa</name><base>thin</base><quantity>2</quantity></pizza>";
private static final String ORDER_XML_3 = //
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + //
"<pizzas>\n" + //
" <pizza>\n" + //
" <name>Capricciosa</name>\n" + //
" <base>thin</base>\n" + //
" <quantity>2</quantity>\n" + //
" </pizza>\n" + //
" <pizza>\n" + //
" <name>Margherita</name>\n" + //
" <base>thin</base>\n" + //
" <quantity>1</quantity>\n" + //
" </pizza>\n" + //
"</pizzas>";
private PizzaParser instance;
@Before
public void setUp() {
instance = new PizzaParser();
}
@Test
public void readOrderFromXML() {
List<PizzaOrder> results = instance.order(new ByteArrayInputStream(ORDER_XML.getBytes()));
assertEquals(1, results.size());
PizzaOrder result = results.get(0);
assertEquals("Capricciosa", result.getPizzaName());
assertEquals("thin", result.getBase());
assertEquals("2", result.getQuantity());
}
@Test
public void readOrderFromModifiedXML() {
List<PizzaOrder> results = instance.order(new ByteArrayInputStream(ORDER_XML_2.getBytes()));
assertEquals(1, results.size());
PizzaOrder result = results.get(0);
assertEquals("Capricciosa", result.getPizzaName());
assertEquals("thin", result.getBase());
assertEquals("2", result.getQuantity());
}
@Test
public void readOrderForMultiplePizza() {
List<PizzaOrder> results = instance.order(new ByteArrayInputStream(ORDER_XML_3.getBytes()));
PizzaOrder result = results.get(0);
assertEquals("Capricciosa", result.getPizzaName());
assertEquals("thin", result.getBase());
assertEquals("2", result.getQuantity());
result = results.get(1);
assertEquals("Margherita", result.getPizzaName());
assertEquals("thin", result.getBase());
assertEquals("1", result.getQuantity());
}
}
Esses testes demonstram os cenários para processar mensagens XML com e sem espaços em branco (corrigindo o problema anterior), juntamente com uma mensagem que inclui um pedido de múltiplas pizzas.
Está tudo funcionando muito bem, mas as grandes ideias de Pete estão surtindo efeito. Ele está agora expandindo para um cenário global, com várias cozinhas ao redor do mundo e uma presença online. Pete contrata alguns consultores de negócio que criam um novo esquema XML de pedido de pizza e o combinam com seu esquema de clientes existente. Isso está parado na sua caixa de entrada do e-mail e você quer saber o que fazer em seguida…
[1]Outros mecanismos de busca estão disponíveis.
Texto original disponível em http://www.captaindebug.com/2012/01/approaches-to-xml-part-2-what-about-sax.html



