O formato XML (sigla em inglês para “Extensible Markup Language”) surgiu ainda no final dos anos 1990, sendo um padrão aberto voltado à geração de documentos hierárquicos e que conta com uma ampla adesão por parte de sistemas computacionais dos mais variados tipos.

Do ponto de vista estrutural, o padrão XML possui vários pontos em comum com a linguagem HTML, estando baseados em marcações (tags). Contudo, diferente de HTML em que há um conjunto pré-definido destes elementos, o formato XML pode ser definido como uma metalinguagem: em termos práticos, isso significa que não existem tags pré-definidas, com esses itens podendo ser definidos conforme necessidades específicas.

Um documento XML é formado por um conjunto de elementos (nodes), com as relações de dependência entre estes elementos sendo estabelecidas por meio de níveis hierárquicos. Todo elemento é constituído por uma tag e seu respectivo conteúdo. Quanto àquilo que consta em um elemento, o mesmo pode estar vazio, possuir atributos ou ainda, conter um agrupamento de outros elementos. Já um atributo nada mais é do que um par formado por um nome identificador e um valor correspondente (este último entre aspas), com estes dois itens separados por um sinal de “=” (“igual”), além de terem sido declarados dentro da tag que define um elemento.

A estrutura flexível oferecida pelo padrão XML é utilizada em larga escala em processos de integração entre sistemas via Web Services, no conteúdo de sites da Internet, em dispositivos móveis etc. Atualmente, este formato é suportado pelas principais linguagens de programação e sistemas operacionais, existindo diversas técnicas para a manipulação de dados neste padrão.

Considerando o caso específico da plataforma .NET, diversos métodos permitem a implementação de funcionalidades baseadas na leitura e/ou escrita de informações no padrão XML. LINQ to XML é uma dessas tecnologias, oferecendo vários meios que viabilizam a realização de operações sobre dados hierárquicos. Além daquilo que é oferecido nativamente pela extensão LINQ to XML, outras funcionalidades podem ainda ser implementadas com base neste mecanismo, sem que isto implique necessariamente em herdar classes pré-existentes a fim de incluir ou, até mesmo, redefinir comportamentos: isto é possível graças ao uso de construções de código conhecidas como "Extension Methods".

O objetivo deste artigo é discutir como Extension Methods podem ser combinados a expressões que envolvam o uso de LINQ to XML, simplificando assim o processo de leitura de informações contidas em documentos XML.

Conteúdo do arquivo XML utilizado nos exemplos

Na Listagem 1 é apresentado o conteúdo do arquivo XML (Prestadores.xml) em que se baseiam os exemplos apresentados mais adiante.

No arquivo “Prestadores.xml” constam dados de prestadores de serviço contratados junto a uma hipotética Consultoria de Tecnologia. É possível constatar neste documento XML a presença dos seguintes campos:

  • ID: Número inteiro que identifica um prestador de serviços;
  • CPF: CPF do profissional;
  • NomeProfissional: nome do prestador de serviços;
  • Empresa: empresa da qual o prestador é proprietário;
  • CNPJ: CNPJ da empresa prestadora de serviços;
  • Cidade: cidade em que está aberta a empresa prestadora;
  • Estado: estado da empresa prestadora;
  • InscricaoEstadual: inscrição estadual da empresa prestadora de serviços (o conteúdo deste campo é opcional);
  • ValorHora: valor recebido por hora trabalhada (preenchido quando o prestador de serviços ser pago de acordo como o número de horas trabalhadas);
  • ValorMensal: valor recebido mensalmente (preenchido quando o prestador de serviços for pago de acordo com um valor fixo mensal);
  • DataCadastro: data em que foi efetuado o cadastro do prestador de serviços;
  • InicioAtividades: data em que o prestador de serviços iniciou suas atividades (o conteúdo deste campo é opcional).

Listagem 1: Arquivo Prestadores.xml


<?xml version="1.0" encoding="utf-8"?>
<Prestadores>
  <Prestador>
    <ID>131</ID>
    <CPF>111.111.111-11</CPF>
    <NomeProfissional>JOÃO DA SILVA</NomeProfissional>
    <Empresa>SILVA CONSULTORIA EM INFORMÁTICA LTDA</Empresa>
    <CNPJ>11.111.111/1111-11</CNPJ>
    <Cidade>São Paulo</Cidade>
    <Estado>SP</Estado>
    <InscricaoEstadual>1111-1</InscricaoEstadual>
    <ValorHora>50,00</ValorHora>
    <ValorMensal></ValorMensal>
    <DataCadastro>03/01/2013</DataCadastro>
    <InicioAtividades>03/01/2013</InicioAtividades>
  </Prestador>
  <Prestador>
    <ID>132</ID>
    <CPF>222.222.222-22</CPF>
    <NomeProfissional>JOAQUIM DE OLIVEIRA</NomeProfissional>
    <Empresa>SERVIÇOS DE TECNOLOGIA OLIVEIRA ME</Empresa>
    <CNPJ>22.222.222/2222-22</CNPJ>
    <Cidade>Belo Horizonte</Cidade>
    <Estado>MG</Estado>
    <InscricaoEstadual></InscricaoEstadual>
    <ValorHora></ValorHora>
    <ValorMensal>10527,35</ValorMensal>
    <DataCadastro>03/01/2013</DataCadastro>
    <InicioAtividades></InicioAtividades>
  </Prestador>
  <Prestador>
    <ID>133</ID>
    <CPF>333.333.333-33</CPF>
    <NomeProfissional>MARIA MARTINS</NomeProfissional>
    <Empresa>MARTINS TECNOLOGIA LTDA</Empresa>
    <CNPJ>33.333.333/3333-33</CNPJ>
    <Cidade>Rio de Janeiro</Cidade>
    <Estado>RJ</Estado>
    <InscricaoEstadual>33333</InscricaoEstadual>
    <ValorHora>65,00</ValorHora>
    <ValorMensal></ValorMensal>
    <DataCadastro>04/01/2013</DataCadastro>
    <InicioAtividades>10/01/2013</InicioAtividades>
  </Prestador>
  <Prestador>
    <ID>134</ID>
    <CPF>444.444.444-44</CPF>
    <NomeProfissional>JOANA SANTOS</NomeProfissional>
    <Empresa>CONSULTORIA SANTOS LTDA</Empresa>
    <CNPJ>44.444.444/4444-44</CNPJ>
    <Cidade>São Paulo</Cidade>
    <Estado>SP</Estado>
    <InscricaoEstadual></InscricaoEstadual>
    <ValorHora></ValorHora>
    <ValorMensal>8474,77</ValorMensal>
    <DataCadastro>04/01/2013</DataCadastro>
    <InicioAtividades></InicioAtividades>
  </Prestador>
</Prestadores>

Definindo a classe que corresponde ao conteúdo do arquivo XML de Prestadores

A Listagem 2 apresenta a implementação da classe Prestador. Instâncias deste tipo equivalem, basicamente, aos dados contidos em cada elemento “Prestador” do documento XML de exemplo.

É possível notar ainda que campos opcionais dos tipos decimal e DateTime estão com um sinal de interrogação (“?”). Este recurso é conhecido como Nullable, permitindo a propriedades e variáveis que o utilizam armazenar também o valor null, por mais que estas tenham sido definidas a partir de um tipo primitivo (decimal) ou estruturado (DateTime). Sem o uso de uma Nullable tais referências assumiriam, se nenhum valor fosse associado às mesmas, o valor default para seus respectivos tipos (o número zero no caso de um decimal).

Listagem 2: Classe Prestador


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TesteExtensionMethodsXML
{
    public class Prestador
    {
        public int ID { get; set; }
        public string CPF { get; set; }
        public string NomeProfissional { get; set; }
        public string Empresa { get; set; }
        public string CNPJ { get; set; }
        public string Cidade { get; set; }
        public string Estado { get; set; }
        public string InscricaoEstadual { get; set; }
        public decimal? ValorHora { get; set; }
        public decimal? ValorMensal { get; set; }
        public DateTime DataCadastro { get; set; }
        public DateTime? InicioAtividades { get; set; }
    }
}

Implementando a leitura do documento XML via LINQ to XML

A classe estática ArquivoPrestadores (Listagem 3) representa a estrutura responsável por efetuar a leitura do arquivo XML, a fim de com isto gerar uma coleção de objetos do tipo Prestador.

O processamento do documento XML e a posterior geração de instâncias da classe Prestador acontece através do método CarregarInformacoes:

  • Inicialmente é gerada uma nova instância de XDocument (namespace System.Xml.Linq), a partir de uma chamada ao método Load, passando-se como parâmetro a este o nome do arquivo XML a ser carregado (parâmetro “arquivo”);
  • Prossegue-se agora com a geração das instâncias baseadas na classe Prestador. Isso é feito através de um loop foreach, acionando-se para isto o método Element a partir da instância do tipo XDocument; esta primeira ação irá selecionar o node “Prestadores”. Em seguida, invoca-se o método Elements, o qual irá retornar como resultado uma coleção de referências da classe XElement (namespace System.Xml.Linq) e que correspondem aos elementos de nome “Prestador” (subordinados ao node principal “Prestadores”);
  • A cada iteração do loop foreach é criada uma nova referência do tipo Prestador, atribuídos valores às propriedades deste objeto a partir de elementos presentes no node “Prestador” e, por fim, adicionada a instância em questão à coleção “prestadores” (a qual é retornada posteriormente como resultado da execução do método CarregarInformacoes);
  • A leitura de cada elemento pertencente ao node “Prestador” se faz por meio do método Element, a partir da instância de XElement (variável “dadosPrestador”) declarada no início do loop foreach. Para isto, são invocados o método Element de “dadosPrestador” e, na sequência, a propriedade Value. Conforme é possível observar, o retorno da propriedade Value é sempre uma string, o que força a realização de procedimentos de conversão de um tipo para outro; além disso, verificações adicionais são realizadas quando se estiver manipulando um campo cujo conteúdo é opcional (caso das propriedades InscricaoEstadual, ValorHora, ValorMensal, DataCadastro e InicioAtividades).

Listagem 3: Classe ArquivoPrestadores


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;

namespace TesteExtensionMethodsXML
{
    public static class ArquivoPrestadores
    {
        public static List<Prestador> CarregarInformacoes(
            string arquivo)
        {
            List<Prestador> prestadores = new List<Prestador>();
            Prestador prestador;

            string strInscrEstadual;
            string strValorHora;
            decimal valorHora;
            string strValorMensal;
            decimal valorMensal;
            string strDataCadastro;
            DateTime dataCadastro;
            string strInicioAtividades;
            DateTime inicioAtividades;

            XDocument xml = XDocument.Load(arquivo);
            foreach (XElement dadosPrestador in
                xml.Element("Prestadores").Elements("Prestador"))
            {
                prestador = new Prestador();

                prestador.ID = Convert.ToInt32(dadosPrestador
                    .Element("ID").Value);
                prestador.CPF = dadosPrestador
                    .Element("CPF").Value;
                prestador.Empresa = dadosPrestador
                    .Element("Empresa").Value;
                prestador.NomeProfissional = dadosPrestador
                    .Element("NomeProfissional").Value;
                prestador.CNPJ = dadosPrestador
                    .Element("CNPJ").Value;
                prestador.Cidade = dadosPrestador
                    .Element("Cidade").Value;
                prestador.Estado = dadosPrestador
                    .Element("Estado").Value;

                strInscrEstadual = dadosPrestador
                    .Element("InscricaoEstadual").Value;
                if (!String.IsNullOrWhiteSpace(strInscrEstadual))
                    prestador.InscricaoEstadual =
                        strInscrEstadual;

                strValorHora = dadosPrestador.
                    Element("ValorHora").Value;
                if (!String.IsNullOrWhiteSpace(strValorHora) &&
                    decimal.TryParse(strValorHora,
                                     out valorHora))
                    prestador.ValorHora = valorHora;

                strValorMensal =
                    dadosPrestador.Element("ValorMensal").Value;
                if (!String.IsNullOrWhiteSpace(strValorMensal) &&
                    decimal.TryParse(strValorMensal,
                                     out valorMensal))
                    prestador.ValorMensal = valorMensal;

                strDataCadastro = dadosPrestador
                    .Element("DataCadastro").Value;
                if (!String.IsNullOrWhiteSpace(strDataCadastro) &&
                    DateTime.TryParse(strDataCadastro,
                                      out dataCadastro))
                    prestador.DataCadastro = dataCadastro;

                strInicioAtividades = dadosPrestador
                    .Element("InicioAtividades").Value;
                if (!String.IsNullOrWhiteSpace(
                    strInicioAtividades) &&
                    DateTime.TryParse(strInicioAtividades,
                                      out inicioAtividades))
                    prestador.InicioAtividades = inicioAtividades;

                prestadores.Add(prestador);
            }

            return prestadores;
        }
    }
}

Já na Listagem 4 está um exemplo de trecho de código que emprega a classe ArquivoPrestadores, fornecendo para isto o caminho em que se encontra o arquivo “Prestadores.xml”.

Listagem 4: Exemplo de uso da classe ArquivoPrestadores


...

List<Prestador> prestadores = ArquivoPrestadores
    .CarregarInformacoes(@"C:\Devmedia\Prestadores.xml");

...

Por mais que a implementação da classe ArquivoPrestadores seja simples e não exija grandes esforços, a leitura e a conversão de valores originários de um arquivo XML pode gerar trechos extensos de código, bem como se repetir ao longo de uma aplicação. É neste ponto que o recurso conhecido como Extension Methods pode se revelar como extremamente útil, conforme será demonstrado na próxima seção. Este mecanismo possibilita não apenas um código mais enxuto, como também facilitando o uso de funcionalidades básicas para tratamento de dados no formato XML.

Utilizando Extension Methods

O recurso conhecido como Extension Methods foi disponibilizado a partir da versão 3.5 do .NET Framework. Em termos práticos, um Extension Method nada mais é do que um método estático, muito embora um desenvolvedor possa enxergar o mesmo como se fosse uma operação acessível a partir da instância de uma determinada classe.

Este tipo de construção evita a necessidade de se gerar uma nova classe, servindo como um meio para “adicionar” novas funcionalidades a um recurso já existente. O próprio .NET Framework emprega de forma extensiva essa prática: LINQ é um bom exemplo de mecanismo que faz uso de Extension Methods, de maneira a facilitar com isto a manipulação de coleções de objetos através de métodos como Where e OrderBy.

Na Listagem 5 encontra-se a implementação da classe estática LinqToXmlExtensions. Este tipo foi estruturado da seguinte forma:

  • O método estático GetStringValue recebe como parâmetros uma instância do tipo XElement, além do nome de um node que pertença a este elemento. Caso tal node exista e possua um valor associado ao mesmo, será retornada a string correspondente; do contrário, o método GetStringValue devolverá como resultado de sua execução o valor null;
  • As operações estáticas StringValue, IntValue, DecimalValue e DateTimeValue representam exemplos de Extension Methods, adicionando novas funcionalidades ao tipo XElement, sem porém exigir a necessidade de se gerar uma nova classe a partir do mesmo. Todos esses métodos recebem como parâmetro uma instância da classe XElement precedida pela palavra-chave “this” (isto é uma característica típica de um Extension Method), além de um segundo parâmetro com o nome do node a ser lido;
  • Os métodos StringValue, IntValue, DecimalValue e DateTimeValue acionam a operação GetStringValue, manipulando os valores fornecidos por esta última e devolvendo resultados baseados em seus respectivos tipos de retorno. No caso específico de IntValue, DecimalValue e DateTimeValue isto envolve a utilização de valores Nullable e da classe Convert em transformações de dados.

Listagem 5: Classe LinqToXmlExtensions


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace TesteExtensionMethodsXML
{
    public static class LinqToXmlExtensions
    {
        private static string GetStringValue(
            XElement element, string node)
        {
            string valor = null;

            XElement childElement = element.Element(node);
            if (childElement != null)
            {
                valor = childElement.Value;
                if (!String.IsNullOrWhiteSpace(valor))
                    valor = valor.Trim();
                else
                    valor = null;
            }

            return valor;
        }

        public static string StringValue(
            this XElement element, string node)
        {
            return GetStringValue(element, node);
        }

        public static int? IntValue(
            this XElement element, string node)
        {
            string valor = GetStringValue(element, node);
            if (valor != null)
                return Convert.ToInt32(valor);
            else
                return null;
        }

        public static decimal? DecimalValue(
            this XElement element, string node)
        {
            string valor = GetStringValue(element, node);
            if (valor != null)
                return Convert.ToDecimal(valor);
            else
                return null;
        }

        public static DateTime? DateTimeValue(
            this XElement element, string node)
        {
            string valor = GetStringValue(element, node);
            if (valor != null)
                return Convert.ToDateTime(valor);
            else
                return null;
        }
    }
}

A simples definição da classe LinqToXmlExtensions em um projeto de testes fará com que o próprio IntelliSense do Visual Studio liste os métodos que estendem uma classe. É o que pode ser observado na Figura 1 com o método IntValue ou ainda, com a operação StringValue conforme demonstrado na Figura 2.

Selecionando o método IntValue através do IntelliSense

Figura 1: Selecionando o método IntValue através do IntelliSense

Selecionando o método StringValue através do IntelliSense

Figura 2: Selecionando o método StringValue através do IntelliSense

Na Listagem 6 são apresentadas algumas instruções da classe ArquivoPrestador já modificadas. Nota-se que o uso dos métodos IntValue, StringValue, DecimalValue e DateTimeValue simplificou consideravelmente a implementação do método CarregarInformacoes.

Listagem 6: Utilizando Extension Methods em instruções da classe ArquivoPrestador

	
...

prestador.ID =
    dadosPrestador.IntValue("ID").Value;
prestador.CPF =
    dadosPrestador.StringValue("CPF");
prestador.Empresa =
    dadosPrestador.StringValue("Empresa");

...

prestador.InscricaoEstadual =
    dadosPrestador.StringValue("InscricaoEstadual");
prestador.ValorHora =
    dadosPrestador.DecimalValue("ValorHora");
prestador.ValorMensal =
    dadosPrestador.DecimalValue("ValorMensal");
prestador.DataCadastro =
    dadosPrestador.DateTimeValue("DataCadastro").Value;
prestador.InicioAtividades =
    dadosPrestador.DateTimeValue("InicioAtividades");

...

Já a Listagem 7 demonstra outra implementação possível para a classe ArquivoPrestadores, combinando para isto o uso de uma consulta LINQ em conjunto com as classes XDocument e XElement, além dos Extension Methods definidos anteriormente através da classe LinqToXmlExtensions.

Esta nova versão da classe ArquivoPrestadores constitui um bom exemplo de como o uso de Extension Methods pode contribuir para uma codificação menos extensa. Por meio deste recurso conseguiu-se, basicamente, evitar a escrita de longos trechos de código visando a leitura e o tratamento de dados no formato XML. Importante frisar que os métodos definidos em LinqToXmlExtensions também poderiam ser facilmente reutilizados em outros pontos de uma aplicação hipotética.

Listagem 7: Classe ArquivoPrestadores modificada


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;

namespace TesteExtensionMethodsXML
{
    public static class ArquivoPrestadores
    {
        public static List<Prestador> CarregarInformacoes(
            string arquivo)
        {
            XDocument xml = XDocument.Load(arquivo);
            return (from XElement dadosPrestador in xml
                    .Element("Prestadores").Elements("Prestador")
                    select new Prestador()
                    {
                        ID = dadosPrestador
                            .IntValue("ID").Value,
                        CPF = dadosPrestador
                            .StringValue("CPF"),
                        Empresa = dadosPrestador
                            .StringValue("Empresa"),
                        NomeProfissional = dadosPrestador
                            .StringValue("NomeProfissional"),
                        CNPJ = dadosPrestador
                            .StringValue("CNPJ"),
                        Cidade = dadosPrestador
                            .StringValue("Cidade"),
                        Estado = dadosPrestador
                            .StringValue("Estado"),
                        InscricaoEstadual = dadosPrestador
                            .StringValue("InscricaoEstadual"),
                        ValorHora = dadosPrestador
                            .DecimalValue("ValorHora"),
                        ValorMensal = dadosPrestador
                            .DecimalValue("ValorMensal"),
                        DataCadastro = dadosPrestador
                            .DateTimeValue("DataCadastro").Value,
                        InicioAtividades = dadosPrestador
                            .DateTimeValue("InicioAtividades")
                    }).ToList();
        }
    }
}

Conclusão

Procurei com este artigo demonstrar como LINQ e o recurso conhecido como Extension Methods pode simplificar bastante a manipulação de arquivos XML. A definição de métodos que estendam as capacidades de tipos como XElement evita não apenas a criação de diversas subclasses (algo que provavelmente aumentaria a complexidade de um projeto), como também possibilita o reuso de determinadas funcionalidades por toda uma aplicação (ou mesmo em diversas soluções de software).

Espero que o conteúdo aqui apresentado possa ser útil no seu dia-a-dia.

Até uma próxima oportunidade!