Usando Libxml para manipulação de XML em Ruby
A linguagem de programação Ruby vem sendo adotado em inúmeros projetos e tem
atraído cada vez mais novos adeptos pelo fato de suas características permitirem
desenvolvimento ágil de aplicações.
Este artigo irá mostrar como um programador Ruby pode usar a biblioteca
libxml-ruby para o processamento de documentos XML no desenvolvimento de suas
aplicações.
O projeto Libxml Ruby
Existem várias bibliotecas de manipulação de XML com suporte para a linguagem
Ruby. Libxml é um parser e toolkit para XML escrito em C que foi desenvolvido
para o projeto Gnome, mas também possui um binding para Ruby: a
biblioteca libxml-ruby.
Esta biblioteca, distribuída sob a licença MIT, é
reconhecida por ter melhor performance se comparada com algumas concorrentes
como, por exemplo, rexml – inteiramente construída em Ruby.
Figura 1. Comparação de performance libxml vs rexml

Fonte: Project Libxml Ruby. http://libxml.rubyforge.org
A figura 1 apresenta uma comparação de velocidade (em segundos) entre
libxml e rexml para algumas operações típicas em processamento de documentos
XML. Note que a libxml tem melhor performance em todas a operações testadas!
Instalando o gem libxml-ruby em Linux
A biblioteca libxml-ruby não é distribuída por padrão junto com a instalação de
Ruby, portanto, será necessário instalar ela no sistema. Existem basicamente
três maneiras de se instalar a biblioteca libxml-ruby em Linux (neste artigo é
usado o Ubuntu 10.04): usando o código fonte, gerenciador de pacotes da
distribuição Linux ou via pacote RubyGems.
Neste artigo iremos mostrar apenas como instalar a biblioteca empacotada e
distribuída em forma de um pacote rubygems, que é disponibilizado para os
sistemas operacionais MacOS, Windows e Linux.
A listagem 1 mostra como instalar o pacote “libxml-ruby” usando o comando
utilitário gem. O comando gem deve reportar se acontecer algum
erro durante a instalação, já que pode ser necessária a instalação de alguma
biblioteca ou rubygem que é dependência da libxml-ruby.
Normalmente, quando
feita a instalação em Linux, a falta de algum dos pacotes ruby-dev, libxml2-dev
ou zlibg-dev vai acarretar em erro na instalação. Portanto, é aconselhável
verificar se estes pacotes estão presentes no ambiente antes de proceder com a
instalação e, para isso, recomenda-se utilizar qualquer aplicativo de
gerenciamento de pacotes da distribuição Linux em uso (apt ou dpkg
no caso do Ubuntu Linux).
Listagem 1. Instalando o rubygems pacote “libxml-ruby”
$ sudo gem install -r libxml-ruby
Após feita a instalação (e se nenhum erro occoreu) é aconselhável verificar se a
API da biblioteca está disponível no ambiente de desenvolvimento antes de
utilizá-la em programas Ruby.
Este teste poder ser feito usando o comando
irb (um shell interativo normalmente instalado junto com o
interpretador Ruby), como pode ser observado na figura 2.
Figura 2. Verificando se libxml-ruby está disponível no ambiente

Todas as classe da libxml estão disponíveis no módulo LibXML::XML e a
maneira mais fácil de incluir este módulo em um programa ruby é
colocando a cláusula require ‘xml’ no começo do código. Se a biblioteca não estiver sido corretamente instalada, então require retornará false.
Validando documentos XML
Neste artigo vamos ver as operações mais comuns de processamento de arquivos XML, começando com a sua validação.
A listagem 2 contém um simples arquivo XML – que representa informações de posts de um Blog
– que vai ser servir de exemplo ao longo deste artigo. Primeiramente,
devemos validar o arquivo, tomando como base o XML schema da listagem 3 (detalhes sobre XML schema não serão abordados neste artigo, veja a seção de referências).
Listagem 2. Arquivo XML com informações de posts de um Blog
<posts>
<post id="1">
<titulo>Primeiro Post do blog</titulo>
<autor>José</autor>
<data>2010-11-02T12:00:01</data>
<conteudo>
<![CDATA[ Primeiro Post! ]]>
</conteudo>
</post>
<post id="2">
<titulo>Developerworks Brasil</titulo>
<autor>João</autor>
<data>2010-11-03T09:20:00</data>
<conteudo>
<![CDATA[Visite o site do Developerworks Brasil.
<a href="www.ibm.com/developerworks/br/">Link</a>]]>
</conteudo>
</post>
</posts>
Listagem 3. XML Schema do arquivo da listagem 2
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="posts" type="tipoPosts"/>
<xs:complexType name="tipoPosts">
<xs:sequence>
<xs:element name="post" type="tipoPost" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="tipoPost">
<xs:sequence>
<xs:element name="titulo" type="xs:normalizedString"/>
<xs:element name="autor" type="xs:string"/>
<xs:element name="data" type="xs:dateTime"/>
<xs:element name="conteudo" type="xs:string"/>
</xs:sequence>
<xs:attribute name="id" type="xs:nonNegativeInteger" use="required"/>
</xs:complexType>
</xs:schema>
A validação de XML usando Libxml-ruby pode ser feita utilizando o método validate_schema da classe XML::Document. Se o arquivo não for válido, este método retornará false.
A listagem 4 contém um simples script ruby usado para validar o arquivo blog_posts.xml (listagem 2) contra o XML schema definido em schema.xsd (listagem 3).
Listagem 4. Validando um documento XML
#!/usr/bin/env ruby
require 'rubygems'
require 'xml/libxml'
schema_document = XML::Document.file('./schema.xsd')
schema = XML::Schema.document(schema_document)
xml_instance = XML::Document.file('./blog_posts.xml')
result = xml_instance.validate_schema(schema)
Lendo documentos XML
Nesta seção serão abordadas as operações básicas de leitura de
arquivos XML usando a biblioteca libxml-ruby. A libxml-ruby fornece os
seguintes modos de leitura de um arquivo XML: em memória (in-memory), em streams (out-of-memory) ou baseado em eventos (event-driven Sax callbacks).
A leitura in-memory é feita usando a classe
XML::Parser, que cria em memória uma árvore representando o documento.
Cada nó da árvore é uma instância da classe XML::Node.
Normalmente esse é
o método mais simples e usado por desenvolvedores, porém, se o arquivo
XML for grande o suficiente ao ponto de comprometer o sistema devido à
falta de memória, então claramente este método deve ser evitado.
A classe XML::Reader deve ser usada para leitura em streams,
sendo o documento consumido conforme a necessidade de leitura dos
elementos – o que é ideal para grandes documentos XML que não cabem
inteiramente na memória do sistema.
Já a leitura baseada em eventos é realizada utilizando a
classe XML::SaxParser. A própria documentação de API de libxml-ruby cita
que tal método apresenta desvantagens por ser complexo e não
padronizado, portanto, não será abordada sua utilização neste artigo.
A listagem 5 contém um código ruby que utiliza XML::Parser para imprimir os posts da listagem 2. O método parse da classe XML::Parser retorna um objeto da classe XML::Document, que nada mais é do que uma representação DOM (Document Object Model) do documento em memória. Uma expressão xpath deve ser passada para o método find desta classe, para percorrer os nós da árvore DOM um a um.
Listagem 5. Lendo um documento XML usando XML::Parser
#!/usr/bin/env ruby
require 'rubygems'
require 'xml/libxml'
# Cria um parser XML
parser = XML::Parser.file('./blog_posts.xml')
# Cria uma arvore DOM representando o documento XML
dom = parser.parse
# Percorre a árvore DOM imprimindo os nós post
dom.find('/posts/post').each { |post|
titulo = post.find('titulo')[0].content
autor = post.find('autor')[0].content
data = post.find('data')[0].content
# Data no formato yyyy-mm-ddThh-mm-ss
(dia, hora) = data.split('T')
conteudo = post.find('conteudo')[0].content
puts "Título: #{titulo}"
puts "Autor: #{autor}"
puts "Data: #{dia} #{hora}"
puts "Conteúdo: #{conteudo}"
}
A listagem 6 contém um código ruby que utiliza XML::Reader para imprimir os posts da listagem 2. Um objeto da classe XML::Reader age como um cursor para os nós do documento XML, assim a cada chamava ao método read move a referência para o próximo elemento XML (cujo tipo pode ser verificado por meio do método node_type). Já o método value é usado para retornar o conteúdo do elemento atualmente referenciado.
Listagem 6. Lendo um documento XML usando XML::Reader
#!/usr/bin/env ruby
require 'rubygems'
require 'xml/libxml'
# Cria um reader XML
reader = XML::Reader.file('./blog_posts.xml')
# Percorre o arquivo imprimindo os nós post
while reader.read:
# ignora nos do tipo END_ELEMENT
if reader.node_type != XML::Reader::TYPE_END_ELEMENT
case reader.name
when "titulo"
reader.read
puts "Título: #{reader.value}"
when "autor"
reader.read
puts "Autor: #{reader.value}"
when "data"
reader.read
data = reader.value
(dia, hora) = data.split('T')
puts "Data: #{dia} #{hora}"
when "conteudo"
reader.read
# Procura por no CDATA.
# Whitespace eh um tipo de no que deve ser ignorado
while (reader.node_type != XML::Reader::TYPE_CDATA)
if not reader.read
break
end
end
puts "Conteúdo: #{reader.value}nn"
end
end
end
Manipulando documentos XML
Esta seção mostrará como manipular arquivos XML usando a libxml-ruby.
A primeira operação será criar um novo nó post no documento XML da listagem
2. Para tanto, vamos precisar abrir o arquivo, carregá-lo como uma
árvore DOM (usando a classe XML::Parser) e, finalmente, inserir um novo nó
(XML::Node) que contém informação do novo post.
Como dito em seção
anterior, um documento XML pode ser representado em memória como uma árvore e,
neste caso, os nós são objetos da classe XML::Node. O programa da listagem
7 demonstra como inserir esse novo nó post no documento XML.
Listagem 7. Adicionando um nó no XML da listagem 2
#!/usr/bin/env ruby
require 'rubygems'
require 'xml/libxml'
require 'date'
# Cria um parser XML
parser = XML::Parser.file('./blog_posts.xml', :options => XML::Parser::Options::NOBLANKS)
# Cria uma arvore DOM representando o documento XML
dom = parser.parse
# Referencia para nó raiz ('posts') do documento
root_node = dom.root
# Cria um novo nó 'post'
#
new_node = XML::Node.new('post')
new_node['id'] = '3'
new_node << XML::Node.new('titulo', 'Libxml-ruby: aprendendo a manipular documentos XML')
new_node << XML::Node.new('autor', 'Wainer')
new_node << XML::Node.new('data', DateTime.now.to_s)
conteudo_node = XML::Node.new('conteudo')
# Cria um nó CDATA para comportar o conteúdo do nó conteudo
conteudo = "Neste posto você irá aprender a manipular documentos XML...."
new_cdata = XML::Node.new_cdata(conteudo)
# Adiciona o nó CDATA no elemento 'conteudo'
conteudo_node << new_cdata
# Adiciona 'conteudo' em 'post'
new_node << conteudo_node
# Finalmente adiciona o novo nó 'post' como filho de 'posts'
root_node << new_node
# Grava alterações em um novo arquivo XML
dom.save('./blog_posts-mod.xml', :indent => true)
O programa da listagem 7, além de demonstrar como adicionar um novo nó XML
no arquivo, também contém instruções de como um arquivo pode ser salvo usando o
método save da classe XML::Document. A saída desse programa é mostrado na
listagem 8.
Listagem 8. Documento XML gerado pelo programa da listagem 7
<?xml version="1.0"?>
<posts>
<post id="1">
<titulo>Primeiro Post do blog</titulo>
<autor>José</autor>
<data>2010-11-02T12:00:01</data>
<conteudo><![CDATA[ Primeiro Post! ]]></conteudo>
</post>
<post id="2">
<titulo>Developerworks Brasil</titulo>
<autor>João</autor>
<data>2010-11-03T09:20:00</data>
<conteudo>
<![CDATA[Visite o site do Developerworks Brasil.
<a href="www.ibm.com/developerworks/br/">Link</a>]]>
</conteudo>
</post>
<post id="3">
<titulo>Libxml-ruby: aprendendo a manipular documentos XML</titulo>
<autor>Wainer</autor>
<data>2010-11-03T15:21:06-02:00</data>
<conteudo>
<![CDATA[Neste posto você irá aprender a manipular documentos XML....]]>
</conteudo>
</post>
</posts>
Agora iremos aprender como atravessar todos os nós do arquivo XML (listagem
2) para inserir um novo elemento chamado tag. O nó tag deve conter
palavras-chave (separadas por “;”) do post.
A listagem 9 apresenta
o código ruby que insere os elementos tag no arquivo – ele contém a lógica de
leitura (listagem 5) e de manipulação (listagem 7) de documentos
XML apresentados ao longo deste artigo.
Listagem 9. Exemplo de leitura e manipulação de documentos XML
#!/usr/bin/env ruby
require 'rubygems'
require 'xml/libxml'
require 'date'
# Cria um parser XML
parser = XML::Parser.file('./blog_posts.xml', :options => XML::Parser::Options::NOBLANKS)
# Cria uma arvore DOM representando o documento XML
dom = parser.parse
# Exemplo ilustrativo de algumas tags
hash_id2tag = { 1 => 'post;primeiro', 2 => 'developerworks;site'}
# Percorre os nós da árvore DOM adicionando um nó 'tag'
dom.find('/posts/post').each { |post_node|
# Pega o atributo 'id' do nós 'post'
post_id = post_node['id']
# Pega o valor de tags correspondente - não trataremos nil
tag = hash_id2tag[post_id.to_i]
# Cria um nó 'tag'
tag_node = XML::Node.new('tag', tag)
# Adiciona 'tag' ao nó 'post'
post_node << tag_node
}
# Grava alterações em um novo arquivo XML
dom.save('./blog_posts-mod2.xml', :indent => true)
Conclusão
Este artigo apresentou a biblioteca libxml-ruby, usada para o processamento de
documentos XML em Ruby. Foram apresentadas as principais vantagens deste
biblioteca em relação à outras disponíveis no ecossistema de desenvolvimento
Ruby.
Também foi mostrado por meio de exemplos práticos como realizar as
principais operações de leitura e manipulação de arquivos XML. Para maiores
informações sobre as capacidades da libxml-ruby, é recomendao a leitura de sua
API (veja seção de referências).
Referências
- Para mais informações sobre o gerenciador de pacotes RubyGems, aqui.
- REXML – XML
Toolkit for Ruby, in Ruby. - Extensible Markup Language (XML).
- XML Schema (XSD).
- Simple API for XML (Sax). Mais informações aqui.
- Libxml-ruby API, aqui.
- Document Object Model (DOM).
- XML Path Language (XPath).
***
artigo publicado originalmente no developerWorks Brasil, por Wainer Moschetta
Wainer Moschetta é engenheiro de software no Linux Technology Center da IBM Brasil
onde lidera um projeto de criação de ferramentas para remediação de código em
migrações de UNIX para Linux. Já trabalhou com teste de device drivers para System
X, testes de aplicações J2EE e desenvolvimento web usando Ruby on Rails.







