DEV Community

Cover image for XML Parsing em NodeJS com XMLBuilder2
Marco Ollivier
Marco Ollivier

Posted on • Edited on

XML Parsing em NodeJS com XMLBuilder2

Bom, como dito no post que serviu como preâmbulo, eu tive um primeiro momento onde eu pensei que não iria ter acesso a definição do WebService. Dessa forma minha vida ficaria um pouco mais complexa. Não seria exatamente o fim do mundo, mas eu teria que fazer parsing de XML e isso é algo relativamente chato.

Bom. Foi quando eu conhecia a biblioteca XMLBuilder. Mais precisamente a v2 dessa lib (elas estão em repos diferentes).

GitHub logo oozcitak / xmlbuilder2

An XML builder for node.js

xmlbuilder2

An XML builder for node.js.

License NPM Version NPM Downloads jsDelivr

Node.js CI Code Coverage Dev Dependency Status

Installation:

npm install xmlbuilder2
Enter fullscreen mode Exit fullscreen mode

Documentation:

See: https://oozcitak.github.io/xmlbuilder2/

Usage:

xmlbuilder2 is a wrapper around DOM nodes which adds chainable functions to make it easier to create and work with XML documents. For example the following XML document:

<?xml version="1.0"?>
<root att="val">
  <foo>
    <bar>foobar</bar>
  </foo>
  <baz/>
</root>
Enter fullscreen mode Exit fullscreen mode

can be created with the following function chain:

const { create } = require('xmlbuilder2');
const root = create({ version: '1.0' })
  .ele('root', { att: 'val' })
    .ele('foo')
      .ele('bar').txt('foobar').up()
    .up()
    .ele('baz').up()
  .up
Enter fullscreen mode Exit fullscreen mode

Custo x Benefício da biblioteca

Eu não estou há taaantos anos assim no universo JS/TS. Meu maior background é de Java, Go e Clojure. Dessa forma eu sempre fico com um pé atrás de sair usando qualquer biblioteca que eu encontro por aí. Então eu recorri ao snyk.io para "qualidade" da biblioteca.

Eu coloquei qualidade entre aspas, porque isso não é o que realmente vai definir a qualidade dela, mas pelo menos me dá um score de algumas coisas que eu acho importante como: vulnerabilidades de segurança encontradas, quantidade de novos downloads por semana e suporte frequente pelo criados.

Package Health Score 16abr2023

Bom... a nota foi de 79 de 100. Não é das melhores, mas passa de ano. Uma das coisas que impactou a nota foi o tempo do último release. Mas eu levei em conta uma coisa pra decidir acabar utilizando: XML não é exatamente uma tecnologia que tem sofrido grandes mudanças nos últimos tempos. E se a lib não apresenta problemas com segurança, talvez faça sentido não ter sofrido alterações recentemente.

Utilização

Bom. Chega de enrolação. Pra demonstra como foi a utilização da biblioteca eu criei um projeto NodeJS bem simples que você vai encontrar nesse repo aqui...

GitHub logo marcopollivier / practice.node-with-xml

Projeto de PoC sobre como manipular XMLs com NodeJS de uma forma menos custosa

practice.node-with-xml




Pra instalar é o básico

$ npm install xmlbuilder2
Enter fullscreen mode Exit fullscreen mode

E como base vamos começar com um XML básico. Como, no caso de não termos um WSDL, o que faremos é uma requisição HTTP usando o método POST e passando um XML no payload, aqui vamos nos ater a simplesmente como vamos criar nosso XML pra enviar nesse payload. Todo o resto iria apenas atrapalhar nosso objetivo.

Então vamos tomar como base o seguinte XML inicialmente (que eu vou tentar deixar o mais próximo possível do XML de uma WebService SOAP):

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
               xmlns:tns="http://wms2.marcopollivier.dev/soap/webservice.xpto"
               xmlns:types="http://wms2.marcopollivier.dev/soap/webservice.xpto/encodedTypes"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

        <!-- Cadastra Livros - Requisição -->
        <tns:CadastraLivros id="id1" xsi:type="tns:CadastraLivros">
            <LivrosArray href="#id2"/>
        </tns:CadastraLivros>

        <!-- Livros - Array -->
        <soapenc:Array id="id2" soapenc:arrayType="tns:Livro[1]">
            <Item href="#id3"/>
        </soapenc:Array>

        <!-- Livros - Array -->
        <tns:Livro id="id3" xsi:type="tns:Livro">
            <Pedido xsi:type="xsd:string">O Conde de Monte Cristo</Pedido>

            <ExemplaresArray href="#id4"/>
        </tns:Livro>

        <!-- Exemplares - Array -->
        <soapenc:Array id="id4" soapenc:arrayType="tns:Exemplar[1]">
            <Item href="#id5"/>
        </soapenc:Array>

        <!-- Exemplar -->
        <tns:Exemplar id="id5" xsi:type="tns:Exemplar">
            <Quantidade xsi:type="xsd:nonNegativeInteger">5</Quantidade>
            <AnoEdicao xsi:type="xsd:string">2022</Pedido>
        </tns:Exemplar>


    </soap:Body>
</soap:Envelope>
Enter fullscreen mode Exit fullscreen mode

Eu sei que o XML ficou meio grande, mas eu queria representar algumas peculiaridades que são importantes desse xml que eu considerei importantes estarem aqui

Primeira tentativa: A solução padrão

Bom. O primeiro exemplo que a biblioteca oferece é você fazer de uma forma em que você meio que navega na hierarquia do XML.

Como essa biblioteca por baixo dos panos utiliza a estratégia de XPath pra navegar nas tags, essa estratégia é até meio óbvia e, talvez, por isso seja a primeira apresentada.

E francamente, eu vou fazer só as primeiras tags, porque essa também é a forma mais verbosa...

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
               xmlns:tns="http://wms2.marcopollivier.dev/soap/webservice.xpto"
               xmlns:types="http://wms2.marcopollivier.dev/soap/webservice.xpto/encodedTypes"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
Enter fullscreen mode Exit fullscreen mode

Talvez a parte mais chata seja esse começo. Onde eu tenho que definir alguns muitos atributos no elemento do soap:Envelop

create({ version: '1.0' })
  .ele('soap:Envelope', { 'xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/', 
                          'xmlns:soapenc': 'http://schemas.xmlsoap.org/soap/encoding/',
                          'xmlns:tns': 'http://wms2.marcopollivier.dev/soap/webservice.xpto',

                          'xmlns:types': 'http://wms2.marcopollivier.dev/soap/webservice.xpto/encodedTypes',
                          'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
                          'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
                        })
  .up();
Enter fullscreen mode Exit fullscreen mode

Só essa primeira parte que representa o soap:envelop já parece que vai ser uma forma muito verbosa. E isso se confirma com a continuação do código.

Se você bem notou, no final tem uma chamada de função up(). Lembra que eu disse que essa lib usa a estratégia de XPath por baixo dos panos? Aqui isso fica bem claro. Já que no, se eu não quero continuar aninhando elementos, eu tenho que dizer que eu quero subir um nível antes de criar o próximo. Ou seja:

el('a').el('b').el('c') vai gerar algo como:

<a>
 <b>
  <c>
  </c>
 </b>
</a>
Enter fullscreen mode Exit fullscreen mode

de modo que se você mudar para el('a').el('b').up().el('c').up(), você terá algo como:

<a>
 <b></b>
  <c></c>
</a>
Enter fullscreen mode Exit fullscreen mode

Então, dessa forma, pra representar o próximo nível do nosso exemplo (soap:body) basta fazer algo do tipo:

create({ version: '1.0' })
  .ele('soap:Envelope', { 'xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/', 
                          'xmlns:soapenc': 'http://schemas.xmlsoap.org/soap/encoding/',
                          'xmlns:tns': 'http://wms2.marcopollivier.dev/soap/webservice.xpto',

                          'xmlns:types': 'http://wms2.marcopollivier.dev/soap/webservice.xpto/encodedTypes',
                          'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
                          'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
                        })
    .ele('soap:Body', { 'id': '#1', 'soapenc:arrayType': "tns:Livro[1]" })
  .up();
Enter fullscreen mode Exit fullscreen mode

E agora o resto é repetição...

Segunda tentativa: Usar um objeto JSON

Sim. A lib oferece essa opção. E eu não apresentei ela primeiro porque eu queria reforçar que existe uma outra forma que pode ter suas vantagens.

O modelo anterior pode se mostrar verboso e repetitivo pra XMLs muito grandes (o que é o nosso caso), mas querendo ou não, você vai ter mais controle do que você tá fazendo. Pra XMLs mais simples, eu acho que iria pra essa opção.

Entretanto não dá pra negar o apelo que tem a possibilidade de criar um XML por meio de um JSON.

Só que logo uma coisa vem na minha cabeça: o XML tem os atributos, que, como vimos, é bem comum em XMLs SOAP.

Mas sim... ficou muito mais simples. Tanto que eu me empolguei e fiz mais que no exemplo anterior.


  const obj = {
    'soap:Envelope': {
      '@xmlns:soap': "http://schemas.xmlsoap.org/soap/envelope/",
      '@xmlns:soapenc': "http://schemas.xmlsoap.org/soap/encoding/",
      '@xmlns:tns': "http://wms2.marcopollivier.dev/soap/webservice.xpto",
      '@xmlns:types': "http://wms2.marcopollivier.dev/soap/webservice.xpto/encodedTypes",
      '@xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance",
      '@xmlns:xsd': "http://www.w3.org/2001/XMLSchema",

      'soap:Body': {
        '@soap:encodingStyle': 'http://schemas.xmlsoap.org/soap/encoding/',

        'tns:CadastraLivros': {
          '@id': 'id1',
          '@xsi:type': 'tns:CadastraLivros',

          'LivrosArray': {
            '@href': '#id2'
          }

        }

      }

    }
  };

Enter fullscreen mode Exit fullscreen mode

Como você já deve ter reparado, esse modelo usa o @ na frente pra poder indicar que aquilo é um atributo e que ele precisa saber montar de acordo.

Bom. Tudo vai indo bem, até que esbarramos no seguinte problema: Como o XML tem atributos que conseguem distinguir elementos com o mesmo nome, o nosso XML pode ter elementos repetidos no mesmo nível. O que é o caso dos elementos


        <soapenc:Array id="id2" soapenc:arrayType="tns:Livro[1]">
            <Item href="#id3"/>
        </soapenc:Array>

        <soapenc:Array id="id4" soapenc:arrayType="tns:Exemplar[1]">
            <Item href="#id5"/>
        </soapenc:Array>
Enter fullscreen mode Exit fullscreen mode

Entretanto, quando representamos isso no JSON ele simplesmente sobrescreve o primeiro elemento pelo segundo.

Para tentar resolver isso, eu tentei appendar o elemento, mas também não deu muito certo original.root().ele('soap:Body').ele('aaa')

Conclusão

Talvez ter chegado no final desse artigo tenha sido um pouco frustrante, mas foi importante pra mostrar que faz muita diferença a forma que integramos entre serviços. E que, quando você opta por fazer um WebService SOAP você vai criar um nível de complicação que talvez não seja necessário. Principalmente pensando que você tem outras formas de integração usando o mesmo protocolo e que vai gerar bem menos trabalho.

Quanto a biblioteca, ela se mostrou interessante, mas esbarramos em problemas que precisamos dar uma volta grande pra tentar resolver e, dependendo do problema, vamos esbarrar em algumas limitações. Como foi o caso da sobrescrita do elemento.

Francamente eu nem achei que valia mais a pena tentar resolver esse problema, pois nesse meio tempo descobri que eu iria ter acesso ao WSDL desse WS. Então desse texto, fica o quanto essa lib pode te ajudar e também algumas limitações dependendo do caso. Se você quiser avançar daqui e postar nos comentários como deixar essa solução redondinha, eu agradeço e certamente te darei os devidos créditos quando eu ajustar o texto. :)

Se você não tiver opções e tiver que integrar com um WebService SOAP, buque saber se o WSDL está disponível de alguma forma. Dessa forma sua vida ficará mais simples (como veremos no próximo texto).

Vale aqui eu finalizar com uma experiência passada com esse mesmo tipo de problema: nessa minha experiência prévia, nós havíamos feito uma integração também em SOAP parseando XML. O que tivemos foi uma alteração de contrato do lado do fornecedor do WS. Nós não conseguimos identificar que havia sido feita uma mudança e a falha na integração gerou uma perda financeira considerável. Então aqui ficam duas lições. Cuidado com os contratos das integrações e estejam com a observabilidade dos seus serviços em dia.

Top comments (0)