Recebi um email me perguntando como fazer validação de um arquivo XML com Ruby, usando XML Schema.
Fui honesto e disse que eu nunca havia precisado usar esse lance em
Ruby mas fiquei curioso sobre como fazer, já que algum tempo atrás usei
algo similar em PHP. Vamos dar uma olhada em como fazer alguma coisa
disso.
Antes de mais nada, precisamos dos arquivos. Vou usar esse XML bem simples, que chamei de catalogo.xml:
<?xml version="1.0"?>
<catalogo loja="Livraria O Quarto Homem" ano="2009">
<categorias>
<categoria titulo="ficcao">
<livro>
<titulo>A Voz do Fogo</titulo>
<autor>Alan Moore</autor>
</livro>
</categoria>
</categorias>
</catalogo>
Agora vamos começar a construir o arquivo XSD para a validação, tipo esse que chamei de catalogo.xsd:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="catalogo">
<xs:complexType>
<xs:sequence>
<xs:element ref="categorias" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="loja" type="xs:string"/>
<xs:attribute name="ano" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="categorias">
<xs:complexType>
<xs:sequence>
<xs:element ref="categoria" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="categoria">
<xs:complexType>
<xs:sequence>
<xs:element ref="livro" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="titulo" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="livro">
<xs:complexType>
<xs:sequence>
<xs:element name="titulo" type="xs:string"/>
<xs:element name="autor" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Ok, até agora nenhum segredo. Arquivos básicos com validação básica.
O lance agora é: como vamos usar Ruby para validar esse XML com esse
XSD? Eu escolhi usar a libxml-ruby. Para instalá-la, apenas um
gem install libxml-ruby
e está tudo pronto para começarmos a fazer o nosso código de validação. No site da libxml-ruby há uma documentação ótima, mas vou dar uma resumida na coisa. Criei um arquivo chamado xsd.rb com o seguinte código dentro:
require "rubygems"
require "xml"
xml = XML::Document.file(ARGV[0])
schema = XML::Schema.document(XML::Document.file(ARGV[1]))
puts xml.validate_schema(schema)
O código fala por si mesmo: criamos um objeto novo para o arquivo XML, outro para o XSD, e pedimos para fazer a validação:
[taq@]$ ruby xsd.rb catalogo.xml catalogo.xsd
true
Ok! Tudo funcionando perfeitamente! Para fazer um pequeno teste, vou trocar o 2009 do arquivo XML para, sei lá, “homer”, e vamos rodar novamente:
[taq@]$ ruby xsd.rb catalogo.xml catalogo.xsd
Error: Element 'catalogo', attribute 'ano': 'homer' is not a valid value of the
atomic type 'xs:integer'. at catalogo.xml:2.
xsd.rb:6:in `validate_schema': Error: Element 'catalogo', attribute 'ano':
'homer' is not a valid value of the atomic type 'xs:integer'. at
catalogo.xml:2. (LibXML::XML::Error)
from xsd.rb:6
A-há! Tivemos o comportamento esperado!
Uma das coisas do problema original era utilizar validação de algum elemento usando uma expressão regular. Vamos brincar um pouco com o nome do autor, dizendo que tem que ser composto por dois nomes ou a palavra “Desconhecido”. Para isso vamos mudar nosso arquivo XSD para:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="catalogo">
<xs:complexType>
<xs:sequence>
<xs:element ref="categorias" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="loja" type="xs:string"/>
<xs:attribute name="ano" type="xs:integer"/>
</xs:complexType>
</xs:element>
<xs:element name="categorias">
<xs:complexType>
<xs:sequence>
<xs:element ref="categoria" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="categoria">
<xs:complexType>
<xs:sequence>
<xs:element ref="livro" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="titulo" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="livro">
<xs:complexType>
<xs:sequence>
<xs:element name="titulo" type="xs:string"/>
<xs:element ref="autor" minOccurs="1" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="autor">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="\w+ \w+|Desconhecido"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:schema>
Notem as mudanças nas linhas 34 e da 39 até a 45 e brinquem um pouco com o arquivo XML.
O \w deve funcionar até para todos os caracteres
acentuados, mas se por acaso ocorrer algum caso muito específico onde
houver a necessidade de deixar explícita alguma exceção do Unicode, ou
você estiver rodando a sua validação em algum lugar onde não tenha
controle dos softwares instalados e haja algum problema em relação à
libxml com caracteres acentuados, algum problema de encoding dos
arquivos etc. etc., podemos utilizar alguns padrões bem documentados para contornar isso. Vamos supor que nosso arquivo XML fosse assim:
<?xml version="1.0"?>
<catalogo loja="Livraria O Quarto Homem" ano="2009">
<categorias>
<categoria titulo="ficção">
<livro>
<titulo>A Voz do Fogo</titulo>
<autor>Alan Moore</autor>
</livro>
</categoria>
<categoria titulo="programação">
<livro>
<titulo>Ruby: Conhecendo a Linguagem</titulo>
<autor>Eustáquio Rangel</autor>
</livro>
</categoria>
</categorias>
</catalogo>
Notem o acento no nome desse quem vos escreve, na linha 13. Agora vamos supor, para efeitos didáticos, que por alguma razão maluca não estamos utilizando o \w na expressão para validar o nome do autor, sei lá, de repente ele não funciona, e sim algo do tipo:
<xs:pattern value="[A-z]+ [A-z]+|Desconhecido"/>
O arquivo nunca seria validado:
[taq@]$ ruby xsd.rb catalogo.xml catalogo.xsd
Error: Element 'autor': [facet 'pattern'] The value 'Eustáquio Rangel' is not
accepted by the pattern '[A-z]+ [A-z]+|Desconhecido'. at catalogo.xml:13.
Error: Element 'autor': 'Eustáquio Rangel' is not a valid value of the local
atomic type. at catalogo.xml:13.
xsd.rb:6:in `validate_schema': Error: Element 'autor': 'Eustáquio Rangel' is
not a valid value of the local atomic type. at catalogo.xml:13.
(LibXML::XML::Error) from xsd.rb:6
Aí poderíamos ter escrito a expressão dessa maneira:
<xs:pattern value="\p{L}+ \p{L}+|Desconhecido"/>
E o arquivo validaria de boa:
[taq@]$ ruby xsd.rb catalogo.xml catalogo.xsd
true
O \p{L} ali no caso representa um caractere na categoria “Letra” do Unicode, em qualquer língua. Mais um monte de exemplos de categorias do Unicode vocês podem encontrar aqui e podem ser uma mão na roda para alguma situação mais específica que possa aparecer.