Codemountain, Paulo Suzart's Blog

clojure + xml com saxon e xstandard

leave a comment »

Outro dia no trabalho estava definindo alguns padrões de como um XSD deveria ser escrito. Coisas simples como formato de nomes de elements, complexTypes, se o XSD deveria ter targetNamespace e attributeFormDefault.

Foi aí que surgiu a ideia de fazer um script em clojure que validasse isso em um dado XML e indicasse o que está fora destes padrões. Claro, algo assim existe por aí. Mas não dá pra perder a oportunidade. Juntei algumas funções auxiliares em cima do Saxon, um wrapper clojure do Saxon XSLT and XQuery Processor. A ideia foi pegar a base do Saxon e usar de tal forma que eu pudesse aplicar algumas asserções a um documento XML.

Queria trabalhar com a idea de assertions. Elas, além do nome, seriam compostas por uma expressão xPath, uma mensagem de falha, uma segunda expressão xPath para localizar que atributo do nó sendo avaliado seria considerado como display-name e uma função de validação que receberá o nó sendo avaliado e simplesmente retornaria true ou false. O resultado foi uma pequena lib commitada no meu github: xstandard.

Por questão de praticidade, escolhi uma estrutura de map par armazenar as informações de uma assertion. Ah, e pra tornar ainda mais prático, criei uma macro que ajuda a definir uma assertion assim:

(defassertion element-name "//xsd:element[@name]"
  :msg "element %s does not match [a-z].*."
  :validator (attr-matches "name" #"[a-z].*")
  :display-name "data(./@name)")

Esta assertion fará análise – através do validator –  de todo elemento do meu XSD cujo atributo name esteja presente. Ok, mas decidi agrupar as assertions, eu poderia eventualmente tratar grupos diferentes de formas diferentes ou usar  a saída delas de forma diferente. Por isso existe uma outra macro que me permite fazer assim:


(defassertions *default-assertions*

  (defassertion element-name "//xsd:element[@name]"
    :msg "element %s does not match [a-z].*."
    :validator (attr-matches "name" #"[a-z].*")
    :display-name "data(./@name)")

  (defassertion type-name "//xsd:complexType[@name]"
    :msg "type %s does not match [A-Z].*Type."
    :validator (attr-matches "name" #"[A-Z].*Type")
    :display-name "data(./@name)")

  (defassertion element-form-default "/xsd:schema"
    :msg "schema hasn't attr elementFormDefault=\"qualified\""
    :validator (attr-eq "elementFormDefault" "qualified"))

  (defassertion target-ns "/xsd:schema"
    :msg "schema hasn't targetNamespace attr"
    :validator (attr-present "targetNamespace")))

Legal, agora temos um grupo de assertions. Mas como executá-las? Simples, basta passar o conjunto de assertions aqui chamado *default-assertions* para a função run provida pela lib. O que fica mais ou menos assim:

  (xs/run *default-assertions* xs/*nss* xmldoc)
  ;; *nss* is provided by the lib with a default namespace prefixe for xml schema.

Por padrão a lib vai retornar o resultado do processamento em um map contendo o nome de cada assertion, o caminho para o nó analisado no xml, a linha no arquivo:

{:assertion :element-name, :status false, :display-name Item,
 :details {:result-msg element Item does not match [a-z].*.,
           :line 25,
           :path /xs:schema/xs:element[1]/xs:complexType[1]/xs:sequence[1]/xs:element[3]}}

Note no código onde declarei todas as asserções, a função attr-matches sendo usada na assertion type-name. Ela recebe como parâmetro o nome do atributo do nó analisado e uma regex para verificar o formato do atributo. Note também que a função é executada no momento da montagem da assertion, pois ela retorna uma segunda função, que esta sim, receberá o nó analisado e fará a checkagem do formato. O código de attr-matches é:

(defn attr-matches
  "Validates the format of a given node `n` against `regex`."
  [attr regex]
  (fn [n]
    (not (nil? (re-matches regex (get-attr n attr))))))

Lindo, não? Bom, para se divertir mais no código basta acessar meu github.

Pra fechar, durante a criação desse projetinho tive a oportunidade de usar a IntelliJ IDEA. Surpreendente! Muito boa e estável, sem falar do plugin clojure e leiningen muito bons. Ah, pra somar ao aprendizado, utilizei o Marginalia pra gerar a documentação do xcode, veja aqui. Basta salvar o html e ver a doc do projeto de uma forma bem interessante. O Marginalia é um projeto do @fogus.

Não esqueça de me seguir no twitter: @paulosuzart.

Written by paulosuzart

fevereiro 7, 2011 às 12:00 pm

Publicado em clojure

Tagged with ,

Deixe um comentário