Utilizando LINQ e Extensions Methods para a leitura de arquivos XML
Veja neste artigo como efetuar a leitura de arquivos XML de uma forma mais produtiva, empregando para isto recursos de LINQ to XML e de construções de código conhecidas como Extension Methods.
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).
<?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).
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).
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”.
...
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.
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.
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.
...
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.
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!
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo