BadgerFish – traduzindo XML para JSON

Há uns seis meses descobri o ‘BadgerFish’, uma convenção para traduzir arquivos XML para objetos Javascript em forma de strings JSON (para compreender o BadgerFish, aprenda primeiro o JSON: 1, 2, 3, 4). Trata-se de um conjunto de regras extremamente simples, descrito em uma página de pouco texto e é isso aí, não achei quase nada mais. Depois achei essa aqui, que é apenas uma referência à outra, mas que tinha alguns comentários.
Na época criei uma comunidade no Orkut (), que nunca teve discussões (?!). Achei então que ainda não haveria discussões em português por enquanto em lugar nenhum e deixei pra lá, mas como o tema é mais atual que nunca, voltei a ele.
Hoje fiz uma busca no Google e até que agora há bastante coisa em inglês e japonês (…), mas numa busca por resultados em português, apenas dois resultados sem relevância. Uma outra busca no del.icio.us me devolveu apenas 7 páginas, Bloglines: nada, mas no Technorati a coisa já muda de figura… mas tudo em gringo. As novidades sempre surgem na blogsfera, já notou?

Bem, já que ninguém se manifesta, vamos nós ao BadgerFish, concebido por David Sklar.
Segue uma tradução (de partes) da página original:

BadgerFish é uma convenção para traduzir documentos XML para objetos JSON. Uma vez que você tem o XML em um objeto JSON, basta transformá-lo em um objeto Javascript e fica muito mais fácil manipulá-lo.
Se você está familiarizado com a extensão SimpleXML do PHP, pense em BadgerFish como algo que aponta para uma meta semelhante: criar uma forma simples para manipular documentos XML com uma estrutura previsível.

As regras

  1. Nomes de elementos tornam-se propriedades de objeto
  2. O texto dentro de uma TAG torna-se a propriedade ‘$’ do objeto
  3. Elementos aninhados tornam-se propriedades aninhadas
  4. Múltiplos elementos iguais no mesmo nível tornam-se arrays
  5. Atributos das TAGs tornam-se propriedades cujos nomes começam com ‘@’
  6. Namespaces ativos vão para a propriedade ‘@xmlns’
  7. O namespace default entra em @xmlns.$
  8. Outros namespaces viram outras propriedades de @xmlns
  9. Elementos com prefixo de namespace tornam-se também propriedades do objeto

(…)

O que falta fazer

  1. Se o conteúdo de uma TAG for composto de espaços em branco, ele será ignorado.
  2. Não há um método para trabalhar com fragmentos de texto em TAGs cujo conteúdo é mesclado de texto solto e outrasTAGs (ex. <p>Some text <strong>is important</strong>.</p>). Talvez esses fragmentos de texto devessem estar disponíveis em $1, $2, etc.
  3. BadgerFish cria a propriedade @xmlns em todos os elementos onde um namespace é ativo, não só no elemento onde o namespace é declarado. Isso pode serexcessivo para quem não se preocupa tanto com namespaces. Talvez um formato alternativo que ignore @xmlns completamente?
  4. Adaptadores para criar strings JSON formatadas como BadgerFish em idiomas diferentes de PHP.

(…) [visite]

Além do mostrado acima, a página oferece um script em PHP para tradizir, feito pelo próprio David Sklar (que depende do Services JSON, da Pear) e algumas outras referências.

Para dar um exemplo inicial, considere o trecho de XML abaixo:

<person><name>Fulado de Tal</name></person>

Cada elemento vira um objeto (regra 1, bem mal traduzida…) e é colocado como uma propriedade do objeto relativo ao seu elemento pai, a não ser no caso do elemento root, que é ‘o pai de todos’. A regra nº 2 (o texto de um elemento vira a propriedade “$” do objeto correspondente) também está em ação.

Veja o resultado:

var json_str = ‘{ “person”: { “name”: { “$”: “Fulado de Tal” } } }’;

O JSON em si é a string que está sendo atribuída a json_str. Criaremos a json_obj para receber a string ‘parseada’, assim:

var json_obj = eval(”(”+json_str+”)”);
// ou… (isso evita certos erros, retornando null)
var json_obj = new Function(’try { return ‘+json_str+’; } catch(e) { return null; }’)();

Assim, bastaria atribuir o objeto (transformado de string para objeto) a uma variável (json_obj) e para pegar o texto do elemento <name> usaríamos:

var name_content = json_obj.person.name.$; // ou
var name_content = json_obj[”person”][”name”][”$”];

Sim, mas e se fosse um arquivo XML completo? Veja:

<?xml version=”1.0″ encoding=”iso-8859-1″?>
<noticias>
<item>
<title>Caminhada na Natureza</title>
<description>Caminhadas e lazer na região de São Pedro e Lumiar</description>
<url></url>
<text>Percurso de 10 km, Padrão Internacional IVV, Inscrições no local</text>
</item>
</noticias>

Esse arquivo depois de ‘traduzido’ pelas normas do BadgerFish, seria uma string JSON assim (as quebras de linha não são necessárias… apenas para ilustrar):

{
  “noticias”: {
    ”item”: {
      ”title”: { “$”: “Caminhada na Natureza” },
      ”description”: { “$”: “Caminhadas e lazer na região de São Pedro e Lumiar” },
      ”url”: { },
      ”text”: { “$”: “Percurso de 10 km, Padrão Internacional IVV, Inscrições no local” }
    }
  }
}

Mas como ficaria se houvesse duas TAGs <item> na TAG root?

<?xml version=”1.0″ encoding=”iso-8859-1″?>
<noticias>
<item>
<title>Caminhada na Natureza</title>
<description>Caminhadas e lazer na região de São Pedro e Lumiar</description>
<url></url>
<text>Percurso de 10 km, Padrão Internacional IVV, Inscrições no local</text>
</item>
<item>
<title>Mostra de Orquídeas</title>
<description>Mostra de Orquídeas – exposição e vendas</description>
<url></url>
<text>Oficina de cultivo (gratuito), Dia 07: das 15h as 17h</text>
</item>
</noticias>

Como há duas TAGs iguais em seqüência e no mesmo nível, aplicamos a regra nº 4, transformando a seqüência em um array. Não poderíamos criar propriedades com o mesmo nome em um mesmo nível de um objeto JS – então fica assim:

{
  “noticias”: {
    ”item”: [
      {
        ”title”: { “$”: “Caminhada na Natureza” },
        ”description”: { “$”: “Caminhadas e lazer na região de São Pedro e Lumiar” },
        ”url”: { },
        ”text”: { “$”: “Percurso de 10 km, Padrão Internacional IVV, Inscrições no local” }
      },
      {
        ”title”: { “$”: “Mostra de Orquídeas” },
        ”description”: { “$”: “Mostra de Orquídeas - exposição e vendas” },
        ”url”: { },
        ”text”: { “$”: “Oficina de cultivo (gratuito), Dia 07: das 15h as 17h” }
      }
    ]
  }
}

E se os elementos tivessem propriedades e não apenas sub elementos? Aí se aplicam as regras nos 6, 7, 8 e 9 . Considere esse outro XML:

<?xml version=”1.0″ encoding=”iso-8859-1″?>
<livro_de_receitas xmlns=”http://www.w3c.org/some-namespace”>
<receita nome=”pão” tempo_de_preparo=”5 minutos” tempo_de_cozimento=”3 horas”>
<titulo>Pão simples</titulo>
<ingrediente quantidade=”3″ unidade=”xícaras”>Farinha</ingrediente>
<ingrediente quantidade=”7″ unidade=”gramas”>Fermento</ingrediente>
<ingrediente quantidade=”1.5″ unidade=”xícaras” estado=”morna”>Água</ingrediente>
<ingrediente quantidade=”1″ unidade=”colheres de chá”>Sal</ingrediente>
<instrucoes>
<passo>Misture todos os ingredientes, e dissolva bem.</passo>
<passo>Cubra com um pano e deixe por uma hora em um local morno.</passo>
<passo>Misture novamente, coloque numa bandeja e asse num forno.</passo>
</instrucoes>
</receita>
</livro_de_receitas>

A nona e última regra “Elementos com prefixo de namespace tornam-se também propriedades do objeto“, traduzindo a minha tradução, significa que todos os elementos contidos em um elemento com um ou mais namespaces definidos devem também ter esse(s) namespace(s) como propriedades.
Eu considero um certo exagero, posto que o princípio da hereditariedade usado no próprio XML é aplicável aqui também. Mais embaixo (O que falta fazer, 3), o próprio David sugere ignorar inteiramente os namespaces… esse tem aplicação possível – nem sempre estamos interessados nos namespaces…

Resolvi usar a hereditariedade, declarando o namespace apenas no objeto reletivo ao elemento em que o tal namespace foi declarado.

Veja o resultado:

{
  “livro_de_receitas”: {
    ”@xmlns”: { “$”: “http://www.w3c.org/some-namespace” },
    ”receita”: {
      ”@nome”: “pão”,
      ”@tempo_de_preparo”: “5 minutos”,
      ”@tempo_de_cozimento”: “3 horas”,
      ”titulo”: { “$”: “Pão simples” },
      ”ingrediente”: [
        {
          ”@quantidade”: 3,
          ”@unidade”: “xícaras”,
          ”$”: “Farinha”
        },
        {
          ”@quantidade”: 7,
          ”@unidade”: “gramas”,
          ”$”: “Fermento”
        },
        {
          ”@quantidade”: “1.5″,
          ”@unidade”: “xícaras”,
          ”@estado”: “morna”,
          ”$”: “Água”
        },
        {
          ”@quantidade”: 1,
          ”@unidade”: “colheres de chá”,
          ”$”: “Sal”
        }
      ],
      ”instrucoes”: {
        “passo”: [
          { “$”: “Misture todos os ingredientes, e dissolva bem.” },
          { “$”: “Cubra com um pano e deixe por uma hora em um local morno.” },
          { “$”: “Misture novamente, coloque numa bandeja e asse num forno.” }
        ]
      }
    }
  }
}

Transcrevo abaixo o mesmo arquivo traduzido segundo a nona regra, literalmente (traduzido pelo script do David):

{
  ”livro_de_receitas”: {
    ”receita”: {
      ”titulo”: {
      ”$”: “Pão simples”,
      ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
      },
      ”ingrediente”: [
        {
          ”$”: “Farinha”,
          ”@quantidade”: “3″,
          ”@unidade”: “xícaras”,
          ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
        },
        {
          ”$”: “Fermento”,
          ”@quantidade”: “7″,
          ”@unidade”: “gramas”,
          ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
        },
        {
          ”$”: “Água”,
          ”@quantidade”: “1.5″,
          ”@unidade”: “xícaras”,
          ”@estado”: “morna”,
          ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
        },
        {
          ”$”: “Sal”,
          ”@quantidade”: “1″,
          ”@unidade”: “colheres de chá”,
          ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
        }
      ],
      ”instrucoes”: {
        ”passo”: [
          {
            ”$”: “Misture todos os ingredientes, e dissolva bem.”,
            ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
          },
          {
            ”$”: “Cubra com um pano e deixe por uma hora em um local morno.”,
            ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
          },
          {
            ”$”: “Misture novamente, coloque numa bandeja e asse num forno.”,
            ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
          }
        ],
        ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
      },
      ”@nome”: “pão”,
      ”@tempo_de_preparo”: “5 minutos”,
      ”@tempo_de_cozimento”: “3 horas”,
      ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
    },
    ”@xmlns”: {”$”: “http:\/\/www.w3c.org\/some-namespace”}
  }
}

Não vou julgar, pois esse é um tópico que me parece óbvio demais… provavelmente há algo que eu não percebi, não sei. Por quê declarar em todos os sub elementos? Bem, finalizando…

Fiz um script (qualquer hora mostro aqui) para fazer essa tradução, mas na hora da prática percebi alguns problemas… segue um trecho do texto que adicionei ao dito script:

O que ainda falta fazer

  • Tudo que estiver fora do escopo da TAG root será ignorado
    Como todo o conteúdo legível de um XML encontra-se encapsulado na sua TAG root e as regras para inclusão desses itens no objeto de retorno JSON ainda não estão estabelecidas, aqui trabalhamos apenas com o conteúdo da TAG root. Isso é um ponto importante, pois para podermos usar o termo ‘traduzir um documento XML’, essa questão deve ser resolvida.
  • Namespaces
    Resolvi declarar os namespaces apenas nos objetos relativos às TAGs onde eles foram declarados no XML e não em todos os childs, como recomendado no item 9 das regras. Posto que os childs já estão contidos no elemento onde o namespace está declarado, achei isso desnecessário. E com isso, chego a uma regra diferente da atual e diferente da sugestão de ignorar completamente os namespaces.
  • Fragmentos de texto em conteúdos mistos
    Os fragmentos, como descritos no item 2 de ‘O que falta fazer’, são tratados como sugerido no próprio texto: $1, $2, etc…, para cada TAG.
  • Os comentários (dentro do escopo da TAG root)
    Para os comentários, adotei o caracter ‘!’, usado na TAG correspondente (<!–…). Da mesma forma que os fragmentos de texto, as TAGs de comentário contidas em determinada TAG entrarão nas propriedades: !1, !2, etc…, dessa TAG.
  • As seções CDATA
    Para as TAGs CDATA, adotamos o caracter ‘#’, arbitrariamente. Da mesma forma que os fragmentos de texto, as TAGs CDATA contidas em determinada TAG entrarão nas propriedades: #1, #2, etc…, dessa TAG.

Colocado isso, aqui vão as regras que usei:

  1. Nomes de elementos tornam-se propriedades de objeto
  2. O texto dentro de uma TAG torna-se a propriedade ‘$’ do objeto
  3. Elementos aninhados tornam-se propriedades aninhadas
  4. Múltiplos elementos iguais no mesmo nível tornam-se arrays
  5. Atributos das TAGs tornam-se propriedades cujos nomes começam com ‘@’
  6. Namespaces ativos vão para a propriedade ‘@xmlns’ do objeto relativo à TAG onde foram declarados
  7. O namespace default entra em @xmlns.$
  8. Outros namespaces viram outras propriedades de @xmlns
  9. Elementos com prefixo de namespace tornam-se também propriedades do objeto (usei a hereditariedade)
  10. No caso de conteúdos mistos de TAGs e texto, os fragmentos entram em $1, $2, etc., na sequência respectiva
  11. Comentários entram nas propriedades !1, !2, etc., na sequência respectiva
  12. As seções CDATA entram nas propriedades #1, #2, etc., na sequência respectiva

O que ainda falta fazer

  • Um método para armazenar os dados em TAGs fora do escopo da TAG root
  • Conteúdos de TAG compostos apenas de espaços em branco ainda são ignorados..

Entrei em contato com o David Sklar a respeito das dúvidas colocadas acima, mas ele repondeu sem muito interesse, me deixando livre para criar minhas próprias regras complementares. Bem, foi o que fiz. Deixo isso claro, por que, se alguém achar que pode melhorar, por favor melhore!

Abraço, Cau Guanabara

Uma ideia sobre “BadgerFish – traduzindo XML para JSON

Deixe um Comentário

O seu endereço de email não será publicado Campos obrigatórios são marcados *

*

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>