.NET

5 ago, 2011

Formatar XML com XSL usando .NET

Publicidade

Com o advento de
integração SOA e Web Services, cada dia mais temos que adaptar o
conteúdo de nossos dados com agilidade. No meu caso, eu atuo em uma
empresa na qual temos vários parceiros comerciais que consomem nosso
catálogo de produtos, e a forma de integração que temos é com o
uso de XML – bom para acelerar o desenvolvimento do XML para cada
parceiro.

Nós criávamos um XML para cada um, só que isso não era
feito de forma automática. A codificação de cada XML era feita
individualmente e os objetos eram reutilizados. Só que então tivemos
problemas com performance pois a consulta era feita em tempo real,
ou seja, os parceiros concorriam com os clientes que navegavam no
site.

O XML demorava para ser carregado e, quando era necessário
efetuar algum filtro por categoria, gênero, marca, isso era
feito na aplicação, ou seja, o catálogo era carregado inteiro e
filtrado através de um delegate. Quando eram poucos parceiros e poucos clientes, tudo bem. Mas um ano depois com muito mais acessos o
negócio ficou ruim. É isso que vou mostrar como fazer aqui, como
esse problema foi resolvido e os passos abaixo:

  1. Não acessar mais
    o banco diretamente, mas sim um XML base;
  2. Efetuar a
    formatação dos dados baseado em um XSL e não mais fazer isso na
    codificação;

  3. Colocar os
    critérios dentro do XSL em vez de fazer na aplicação ou em
    query.

Neste artigo, utilizei
Visual C# 2010 Express, Framework 4.0 e para o aplicativo teste fiz
um console application.

O que é um arquivo XSL e para que serve

Segundo a Wikpedia, XSL Transformations ou XSLT (eXtensible Stylesheet Language for
Transformation – linguagem extensível para folhas de estilo de
transformações) é uma linguagem de marcação XML usada para
criar documentos XSL que, por sua vez, definem a apresentação dos
documentos XML nos browsers e outros aplicativos que a suportem. Ou seja, serve para formatar a saída de dados do XML

Fonte: XSLT – Wikipédia

Agora que estamos
alinhados com o cenário, vamos começar.

XML Base

Isso é algo muito
tranquilo. Foi criado um JOB que gera um XML em uma pasta visível
pela aplicação com todo catálogo de produtos.

Para esse estudo,
vamos usar o exemplo abaixo:


<?xml version="1.0"
encoding="utf-8" ?>
<catalogo>
<item>
<Sku>500135</Sku>

<Descricao><![CDATA[Bola de T&#234;nis Wilson
Championship ]]></Descricao>

<CodigoCategoria>102</CodigoCategoria>
<Categoria>RACKET
</Categoria>

<CodigoMarca>15</CodigoMarca>

<Marca>WILSON</Marca>
</item>
<item>
<Sku>500139</Sku>

<Descricao><![CDATA[Haltere Ferro Centauro Meio
Kg]]></Descricao>

<CodigoCategoria>107</CodigoCategoria>
<Categoria>BODY
</Categoria>

<CodigoMarca>999</CodigoMarca>

<Marca>FORTE</Marca>
</item>
<item>
<Sku>500140</Sku>

<Descricao><![CDATA[Halteres Ferro Centauro 1 Kg
]]></Descricao>

<CodigoCategoria>107</CodigoCategoria>
<Categoria>BODY
</Categoria>

<CodigoMarca>999</CodigoMarca>

<Marca>FORTE</Marca>
</item>
<item>
<Sku>500141</Sku>

<Descricao><![CDATA[Halteres Ferro Centauro 2Kg
]]></Descricao>

<CodigoCategoria>107</CodigoCategoria>
<Categoria>BODY
</Categoria>

<CodSubGrupo>34</CodSubGrupo>

<Marca>FORTE</Marca>
</item>
<item>
<Sku>500142</Sku>

<Descricao><![CDATA[Halteres Ferro Centauro 3
kg]]></Descricao>

<CodigoCategoria>107</CodigoCategoria>
<Categoria>BODY
</Categoria>

<CodigoMarca>999</CodigoMarca>

<Marca>FORTE</Marca>
</item>
<item>
<Sku>500143</Sku>

<Descricao><![CDATA[Halteres Ferro Centauro 4Kg
]]></Descricao>

<CodigoCategoria>107</CodigoCategoria>
<Categoria>BODY
</Categoria>

<CodigoMarca>999</CodigoMarca>

<Marca>FORTE</Marca>
</item>
</catalogo>

XSL

Bom, o XSL pode ficar
ao gosto de cada um, eu vou colocar dois exemplos aqui. O primeiro
nos dará a seguinte saída do XML Base:

<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output
method='xml' indent='yes' version='1.0' encoding='UTF-8'
standalone='yes' cdata-section-elements='NOME'/>
<xsl:template
match="/">
<produtos>
<xsl:for-each
select="catalogo/item[CodigoCategoria='102' and
CodigoMarca='999']">
<PRODUTO>
<SKU>

<xsl:value-of select="Sku"/>
</SKU>
<NOME>

<xsl:value-of select="Descricao"/>
</NOME>
<CATEGORIA>

<xsl:value-of select="Categoria"/>
</CATEGORIA>
</PRODUTO>
</xsl:for-each>
</produtos>
</xsl:template>
</xsl:stylesheet>

O segundo oferece a
saída abaixo:

<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output
method='xml' indent='yes' version='1.0' encoding='UTF-8'
standalone='yes' cdata-section-elements='NOME'/>
<xsl:template
match="/">
<produtos>
<xsl:for-each
select="catalogo/item[CodigoCategoria='102']">
<ITEM>
<CODIGO>

<xsl:value-of select="Sku"/>
</CODIGO>
<DESCRICAO>

<xsl:value-of select="Descricao"/>
</DESCRICAO>
</ITEM>
</xsl:for-each>
</produtos>
</xsl:template>
</xsl:stylesheet>

Esses arquivos devem
ser criados em um local no qual a aplicação possa lê-los.

Agora vamos entender
algumas tags do XSL:

Aqui é feita a
declaração do XSL, a versão e qual namespace (xmlns) é utilizado,
a tag deve ser fechada.

	<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

A saída do arquivo é
definida aqui, além do método/formato (method), a versão do formato
(version), a codificação (encoding) e as tags que terão a saída
com CDATA (cdata-section-elements).

	<xsl:output
method='xml' version='1.0' encoding='UTF-8'
cdata-section-elements='NOME'/>

Nesse momento, o nó
raiz é escolhido para trabalhar, a tag deve ser fechada.

	<xsl:template
match="/">

Tudo o que não
estiver usando o prefixo xsl será escrito da mesma forma como
estiver no XSL. Vamos ver o loop agora. Aqui é o começo do
loop:

	<xsl:for-each
select="catalogo/item">

E aqui é o seu final:

	</xsl:for-each>

Tudo o que estiver
entre essas tags será repetido até que todos os nós tenham sido
lidos. Para escrever os
valores que estão no XML, basta usar a tag value-of e, no atributo
select, colocar o campo que deseja, conforme o exemplo abaixo.

	<xsl:value-of
select="Sku"/>

Adicionando critérios ao XSL

Assim como em uma query, é possível adicionar
critérios a XSL, basta adicioná-los conforme o código abaixo na
tag for-each.

No primeiro exemplo, o XSL trará apenas os produtos com
o campo CodigoCategoria igual a 102, já no segundo exemplo todos com
CodigoCategoria
igual a 102 e o CodigoMarca igual 999.

<xsl:for-each
select="catalogo/item[CodigoCategoria='102']">
<xsl:for-each
select="catalogo/item[CodigoCategoria='102' and
CodigoMarca='999']">

Usando XSL para formatar o XML

O .NET Framework
nos dá suporte para trabalhar com arquivos XML através do Namespace
System.Xml e para trabalhar com XSL com o Namespace System.Xml.Xsl.

Vamos utilizar a
classe XslCompiledTransform
para formatar o XML com nosso XSL. Essa classe é
um processador de XSL e XSLT. Ela ainda é a sucessora da classe
XslTransform e, segundo a MSDN, oferece ganhos de performance com
relação à classe antiga (e obsoleta na versão 4.0 do .NET Framework).

Uma das sobrecargas do
construtor do XslCompiledTransform recebe um XmlReader, então
carreguei o XSL dentro de um XmlReader e criei o objeto do
XslCompiledTransform
.

O método Transform de nosso objeto
XslCompiledTransform tem uma sobrecarga que recebe o xml base, uma
lista de argumentos de XSLT que será passada como nulo e um objeto
que terá o resultado da transformação – nesse caso, é passado um
StringWriter. Pronto, agora o objeto StringWriter tem o XML carregado
conforme as definições do XSL, tudo bem simples.

Código

Aqui está todo o
código utilizado para a aplicação funcionar corretamente. Confira abaixo:

using System;
using System.Xml;
using System.Xml.Xsl;
using System.IO;
namespace
GeradorXmlParceiro
{
class Program
{
static string
caminhoXmlBase = @"C:XmlBaseXmlBase.xml";
static string
caminhoXsl = @"C:XslXsl1.xsl";
static string
caminhoXmlFormatado = @"C:XmlTransformadoXmlTransformado.xml";
static void
Main(string[] args)
{

CriarXmlComXsl();
}
static void
CriarXmlComXsl()
{
XmlDocument
xml = new XmlDocument();
xml =
FormartarXmlBaseParaFormatoComXsl(caminhoXsl);

xml.Save(caminhoXmlFormatado);
}
static
XmlDocument FormartarXmlBaseParaFormatoComXsl(string caminhoXsl)
{
XmlDocument
xml = new XmlDocument();

XslCompiledTransform xsl = new XslCompiledTransform();
XmlDocument
xmlBase = ObterXmlBase();
try
{

XmlReader reader = XmlReader.Create(caminhoXsl);

xsl.Load(reader);

StringWriter writer = new StringWriter();

xsl.Transform(xmlBase, null, writer);

xml.LoadXml(writer.ToString());
}
catch
(Exception ex)
{
throw
new Exception("Ocorreu um erro ao efetuar a transformação do
XML.", ex.InnerException);
}
return xml;
}
static
XmlDocument ObterXmlBase()
{
XmlDocument
xml = new XmlDocument();
try
{

xml.Load(caminhoXmlBase);
}
catch
(Exception ex)
{
throw
new Exception("Ocorreu um erro ao carregar o XML.",
ex.InnerException);
}
return xml;
}
}
}

É isso aí! Espero que
este artigo possa contribuir para o crescimento da comunidade. Até a
próxima!

Referências