O NeoMatrix Tech está de casa nova!

Você deverá ser redirecionado em 6 segundos. Se não, visite:
http://www.leonelfraga.com/neomatrixtech
e atualize seus favoritos.

Aviso IMPORTANTÍSSIMO!

Aviso aos navegantes:

O NeoMatrix Tech mudou de casa!!!

A partir de agora, acessem pelo novo endereço:

http://www.leonelfraga.com/neomatrixtech

Ué... mas é só o domínio mudou de lugar?

R: Na verdade, não é bem assim hehe. Este domínio que você acessa agora aponta para um blog hospedado no Blogger, enquanto no novo, aponta para um blog na plataforma Wordpress, hospedagem própria, muito mais rápida e com um layout mais agradável de ler ;)

Não vou fechar este domínio igual ao que eu fiz com o NM Light (que já está 100% na nova plataforma). Talvez beeeeeeem depois eu faça isso.

Todos os posts daqui se encontram lá, e novos posts serão colocados somente no novo endereço.
A única coisa que não consegui importar foram os comentários. Mas em breve vai ter um post contando sobre a epopéia que foi migrar o NeoMatrix Tech!

Somente vou fechar a área de comentários daqui. Caso queiram comentar, favor ver o post correspondente no "Novo NeoMatrix Tech" e comentem por lá. É bem melhor! (pena que os permalinks "amigáveis para SEO" não funcionam lá, dá erro 404 e não consigo fazer a configuração funcionar. E olha que eu já vi vários artigos falando desse assunto :( ).

Quem assina o feed, já está lendo o conteúdo do novo NeoMatrix Tech!

sábado, 27 de junho de 2009

Fazendo Arquivos EDI com C# aplicando POO Parte 4 - Final

Finalmente chegamos na parte conclusiva desta série de artigos. Iremos construir uma aplicação que irá gravar/ler um arquivo EDI. Estarei no artigo comentando o arquivo exemplo, portanto, façam o download do arquivo em anexo à este post!

Para o exemplo, na época do artigo original utilizei a IDE Sharp Develop versão 2.2, com o .NET Framework versão 2.0.

Mas antes, uma ressalva sobre o código do artigo anterior:

Caso vocês tenham notado, após o comando LoadFromFile o arquivo não é carregado, a quantidade de linhas da propriedade Count de Lines é 0. Mas por que isso?

Se olharmos na classe base, TEDIFile, o método LoadFromFile (ou LoadFromStream) faz chamada ao método virtual DecodeLine, que recebe como parâmetro uma string, que
é a linha a ser decodificada, vinda do arquivo.

Como na implementação do método não temos nenhum código, a aplicação somente irá passar por ele e não fará nenhuma ação; o método LoadFromFile simplesmente fará um looping
no arquivo texto carregado.

Como solucionar?

A classe TEDIFile apresenta comportamento genérico. Um arquivo poderá ter diferentes tipos de registro, que podem ter layouts diferentes entre si, por exemplo, um header, linhas detalhe e um registro trailler. Isto é específico de cada arquivo. Para ser mais específico, a classe deveria ser mais especializada, não é? Mas embora seja especializada, tenha o comportamento da TEDIFile.

Para tal especialização, iremos criar uma classe derivada de TEDIFile e iremos sobreescrever o método DecodeLine para adicionar os objetos à representação do nosso arquivo EDI em memória:

   1: public class TMeuArquivo : TEDIFile
   2: {
   3:     public override void DecodeLine(string Line)
   4:     {
   5:         this.Lines.Add(new TRegistroEDI());
   6:         this.Lines[Lines.Count - 1].LinhaRegistro := Line;
   7:         this.Lines[Lines.Count - 1].DecodificarLinha();
   8:     }
   9: }

Pronto, problema resolvido!

Agora, caso você tenha mais de um tipo de layout no arquivo, primeiro verifique em cada linha se há um campo para identificar o tipo de registro.

Com isso, você poderá fazer o controle através do parâmetro Line da instrução DecodeLine.

Supomos que temos três tipos de layouts (TMeuHeader,TMeuDetalhe e TMeuTrailler) e que temos um campo na posição 1 de cada linha que indica o tipo de registro, sendo H,D e T para Header, Detalhe e Trailler respectivamente. O código ficará assim:

   1: public override void DecodeLine(string Line)
   2: {
   3:     if(Lines.Substring(0,1).Equals("H"))
   4:     {
   5:         this.Lines.Add(new TMeuHeader());
   6:     }
   7:     if(Lines.Substring(0,1).Equals("D"))
   8:     {
   9:         this.Lines.Add(new TMeuDetalhe());
  10:     }
  11:     if(Lines.Substring(0,1).Equals("T"))
  12:     {
  13:         this.Lines.Add(new TMeuTrailler());
  14:     }
  15:     this.Lines[Lines.Count - 1].LinhaRegistro := Line;
  16:     this.Lines[Lines.Count - 1].DecodificarLinha();
  17: }

Como os três tipos de registros possuem comportamento semelhante, já que são herdados de TRegistroEDI e a propriedade Lines é uma coleção deste tipo de registro, a chamada ao método DecodificarLinha() é a chamada da classe correspondente.

Programa Exemplo do Artigo

Sim, notei que a explicação acima está um pouco adiantada, mas ela se fez necessária. Se não entendeu bulhufas, agora vamos à explicação do programa passo a passo!

No projeto ArquivosEDI, há um arquivo de classes chamado TArtigoEDI.cs que contém as classes especializadas. É ele que iremos explicar. No projeto também há um formulário básico, criado apenas para demonstração sem se preocupar com validações e coisas do gênero, o "framework" para criação de arquivos EDI composto pelos arquivos EDIBasicTypes.cs e EDIFile.cs.

No formulário, há um DataTable que utilizo para guardar os registros que serão necessários para guardar e receber o arquivo. O código dos botões de navegação são básicos: é incrementada uma variável de controle que indica a linha da tabela; na seleção do registro, atribuo cada campo da tabela em uma caixa de texto. O botão Inserir é análogo. O botão Novo simplesmente deixa as caixas de texto em branco.

O botão Codificar Linha pega os registros das caixas de texto e faz a formatação conforme o layout indicado no primeiro artigo e coloca o resultado na caixa de texto indicada pelo label LinhaFormatada. O botão Decodificar Linha, pega o conteúdo da caixa LinhaFormatada e joga em cada campo.

O botão Carregar Arquivo carrega o arquivo indicado na caixa de texto Nome do Arquivo e carrega o nosso DataTable; o botão Salvar Arquivo varre o DataTable e salva-o em um arquivo Texto com o nosso layout.

Observem o código:

   1: public class TRegistroArtigoEDICS : TRegistroEDI
   2: {
   3:     #region Variáveis Privadas
   4:      private string _Nome = "";
   5:      private DateTime _DataNascimento = DateTime.MinValue;
   6:      private string _TipoPessoa = "";
   7:      private long _Documento = 0;
   8:      private Double _SaldoAtual = 0;
   9:     #endregion
  10:    
  11:     #region Propriedades (Públicas)
  12:      public string Nome
  13:      {
  14:          get{return _Nome;}
  15:          set{_Nome = value;}
  16:      }
  17:      public DateTime DataNascimento
  18:      {
  19:          get{return _DataNascimento;}
  20:          set{_DataNascimento = value;}
  21:      }
  22:      public string TipoPessoa
  23:      {
  24:          get{return _TipoPessoa;}
  25:          set{_TipoPessoa = value;}
  26:      }
  27:      public long Documento
  28:      {
  29:          get{return _Documento;}
  30:          set{_Documento = value;}
  31:      }
  32:      public Double SaldoAtual
  33:      {
  34:          get{return _SaldoAtual;}
  35:          set{_SaldoAtual = value;}
  36:      }
  37:     #endregion
  38:    
  39:    
  40:     public TRegistroArtigoEDICS()
  41:     {
  42:         this._CamposEDI.Add(new TCampoRegistroEDI(TTiposDadoEDI.ediAlpha,40,0,"",null,null,1));
  43:         this._CamposEDI.Add(new TCampoRegistroEDI(TTiposDadoEDI.ediDataDDMMAAAA,8,0,DateTime.MinValue,null,null,41));
  44:         this._CamposEDI.Add(new TCampoRegistroEDI(TTiposDadoEDI.ediAlpha,1,0,"",null,null,49));
  45:         this._CamposEDI.Add(new TCampoRegistroEDI(TTiposDadoEDI.ediInteiro,20,0,0,null,null,50));
  46:         this._CamposEDI.Add(new TCampoRegistroEDI(TTiposDadoEDI.ediNumericoSemSeparador,12,2,0,null,null,70));
  47:     }
  48:    
  49:     public override void CodificarLinha()
  50:     {
  51:         this._CamposEDI[0].ValorNatural = this._Nome;
  52:         this._CamposEDI[1].ValorNatural = this._DataNascimento;
  53:         this._CamposEDI[2].ValorNatural = this._TipoPessoa;
  54:         this._CamposEDI[3].ValorNatural = this._Documento;
  55:         this._CamposEDI[4].ValorNatural = this._SaldoAtual;
  56:         base.CodificarLinha();
  57:     }
  58:    
  59:     public override void DecodificarLinha()
  60:     {
  61:         base.DecodificarLinha();
  62:         this._Nome = (string)this._CamposEDI[0].ValorNatural;
  63:         this._DataNascimento = (DateTime)this._CamposEDI[1].ValorNatural;
  64:         this._TipoPessoa = (string)this._CamposEDI[2].ValorNatural;
  65:         this._Documento = (long)this._CamposEDI[3].ValorNatural;
  66:         this._SaldoAtual = (double)this._CamposEDI[4].ValorNatural;
  67:     }
  68: }
  69:  
  70:  
  71: public class TArquivoArtigoEDI : TEDIFile
  72: {
  73:     protected override void DecodeLine(string Line)
  74:     {
  75:         base.DecodeLine(Line);
  76:         Lines.Add(new TRegistroArtigoEDICS());
  77:         Lines[Lines.Count - 1].LinhaRegistro = Line;
  78:         Lines[Lines.Count - 1].DecodificarLinha();
  79:     }
  80: }

Acima temos a definição das duas classes "principais" deste artigo.

Implementamos o nosso layout através de uma classe derivada de TRegistroEDI chamada TRegistroArtigoEDICS, onde criamos e inicializamos as variáveis privadas (e as propriedades que as expõe) correspondentes aos campos do registro. Portanto, este objeto representa cada linha do arquivo EDI.

O layout em si é definido no construtor de TRegistroArtigoEDICS, onde adicionamos em _CamposEDI os objetos do tipo TCampoRegistroEDI com a definição dos itens básicos do layout, lembra (tipo de dado, tamanho, posição inicial...)? Note que eu declarei os campos conforme eles aparecem no layout. Se quiser, pode acrescentar um comentário em cada linha de declaração de campo, para lembrar o nome dele. É importante também lembrar-se da ordem, já que iremos acessá-los através do índice (começando em zero).

Para gerar a linha formatada deste registro, sobreescrevemos o método CodificarLinha. Nele, eu atribuo cada propriedade em cada campo adicionado, campos estes que estarão dentro da coleção _CamposEDI e é acessado através do índice deste. Atribuimos a propriedade da classe na propriedade ValorNatural de cada campo. Após feita as atribuições, chamamos o método CodificarLinha() da classe pai que é o responsável pela formatação propriamente dita. O resultado formatado será acessado através da propriedade LinhaRegistro da nossa classe TRegistroArtigoEDICS.

Analogamente, temos a sobreescrita do método DecodificarLinha(): Primeiro, no nosso programa, setamos a propriedade LinhaRegistro com uma linha formatada (isto é feito na classe do arquivo, que veremos adiante), após isso, já dentro do método DecodificarLinha() é chamado o método da classe pai, que será o responsável por separar cada substring de LinhaRegistro e separar em campos com o tipo de dado definido no layout em _CamposEDI.

Feita a conversão, atribuimos nas propriedades de TRegistroArtigoEDICS os valores dos campos em _CamposEDI correspondentes, acessados através da propriedade ValorNatural do mesmo.

Como ValorNatural é do tipo object, e a conversão para outros tipos de dado não é explícita como o inverso ( = atribuir qualquer tipo de dado em uma variável object ) é necessário fazer um cast para o tipo adequado na propriedade. Um cuidado a se tomar é com valores nulos ou que não podem ser convertidos facilmente, como DateTime (já tive muuuuuito trabalho com esse tipo de dado...).

Definida a classe de cada registro, vamos definir uma classe que será responsável pelo salvamento/carregamento do arquivo texto em si. Esta classe é derivada de TEDIFile, que contém os métodos adequados) e chamaremos-a de TArquivoArtigoEDI.

De modo geral, somente o método DecodeLine precisa ser sobreescrito. Os métodos LoadFromFile, LoadFromStream e SaveToFile trabalham com a linha já codificada de cada registro.

O método DecodeLine, chamado nas funções de carga (LoadFromFile e LoadFromStream) é o responsável por adicionar efetivamente um registro vindo do arquivo texto na coleção Lines.

Neste método, adicionamos na coleção Lines um objeto do tipo do layout. No nosso exemplo, como temos somente um tipo de registro, adicionaremos um objeto do tipo TRegistroArtigoEDICS. Caso o seu arquivo tenha vários tipos de registros (e várias tipos de layout, claro) devemos fazer uma verificação que pergunta "Qual é o tipo de registro correspondente a esta linha?" ANTES de adicionar o objeto.

Como o método DecodeLine recebe a linha a ser decodificada como parâmetro, essa verificação geralmente é feita através de uma substring. Na nota no início do artigo tem um ponto explicando isso!

Feita a adição do objeto na coleção Lines, atribuimos o parâmetro Line (a linha a ser decodificada) neste objeto que acabamos de adicionar. Em seguida, chamamos o método DecodificarLinha() e a adição da linha está completa.

No nosso formulário... Agora, veja o código dos botões de salvamento e carga do arquivo do form da nossa aplicação:

   1:  
   2: void BtnGravaArqClick(object sender, EventArgs e)
   3: {
   4:     TArquivoArtigoEDI arq = new TArquivoArtigoEDI();
   5:     foreach(DataRow r in tab.Rows)
   6:     {
   7:         TRegistroArtigoEDICS reg = new TRegistroArtigoEDICS();
   8:         reg.Nome = r[0].ToString();
   9:         reg.DataNascimento = (DateTime)r[2];
  10:         reg.TipoPessoa = r[1].ToString();
  11:         reg.Documento = (long)r[3];
  12:         reg.SaldoAtual = (double)r[4];
  13:         arq.Lines.Add(reg);
  14:     }
  15:     arq.SaveToFile(@tbxArq.Text);
  16: }
  17:  
  18: void BtnCarregarArqClick(object sender, EventArgs e)
  19: {
  20:     TArquivoArtigoEDI arq = new TArquivoArtigoEDI();
  21:     tab.Rows.Clear();
  22:     arq.LoadFromFile(@tbxArq.Text);
  23:     foreach(TRegistroArtigoEDICS reg in arq.Lines)
  24:     {
  25:         DataRow linha = tab.NewRow();
  26:         linha["NOME"] = reg.Nome;
  27:         linha["TP_PESSOA"] = reg.TipoPessoa;
  28:         linha["DT_NASC"] = reg.DataNascimento;
  29:         linha["DOCTO"] = reg.Documento;
  30:         linha["SALDO"] = reg.SaldoAtual;
  31:         tab.Rows.Add(linha);
  32:     }
  33:     BtPrimeiroClick(null,null);
  34: }

No programa, guardamos em um DataTable os registros adicionados através do formulário.

Para exportar este DataTable para texto, criamos uma instância de TArquivoArtigoEDI (variável arq), varremos as linhas do DataTable. Dentro do foreach criamos uma instância de TRegistroArquivoEDICS (variável reg) que será cada linha do arquivo. Atribuimos cada coluna da linha do DataTable nas propriedades correspondentes de reg e no final adicionamos-a em arq. Terminando a iteração, chamamos o método SaveToFile() de arq para gravar o arquivo no disco.

Na importação do arquivo, o processo é o seguinte: Criamos a instância da classe do arquivo (variável arq) e limpamos o DataTable. Fazemos uma iteração percorrendo cada item da propriedade Lines da variável arq. Dentro da iteração, é criado um DataRow no esquema de tab (o nosso DataTable) e adicionamos em cada coluna desta linha as propriedades correspondentes de reg. Por fim, adicionamos o DataRow ao nosso DataTable. Concluído o processo de carga, apenas chamamos o método para ir para o primeiro registro.

Ufa! Finalmente acabou. Com isto é possível implementar rapidamente um gerador/leitor de arquivos EDI, muito utilizados no nosso dia-a-dia (vide Associação Comercial e outras entidades de proteção ao crédito, bancos, seguradoras, etc).

Exemplo de projeto com Arquivo EDI (61,8 KiB)

Um abraço a todos! :-)

Leia o restante deste post...

quarta-feira, 24 de junho de 2009

Fazendo Arquivos EDI com C# aplicando POO Parte 3

Finalmente vamos à 3ª e penúltima parte desta série de artigos sobre EDI com POO. Sim, porque vamos descrever agora como implementar uma classe que representa um arquivo em si.

Como falamos em artigos passados, um arquivo EDI é uma coleção de registros EDI, não é?
Vou colocar a definição da classe base de um arquivo e vamos comentá-la para o assunto não ficar "maçante", como na primeira parte...

   1: public class TEDIFile
   2: {
   3:     #region Variáveis Privadas e Protegidas
   4:     #endregion
   5:  
   6:     #region Propriedades
   7:     public List<TRegistroEDI> Lines = new List<TRegistroEDI>();
   8:     #endregion
   9:  
  10:     #region Métodos Privados e Protegidos
  11:     /// <summary>
  12:     /// Decodifica a linha do registro EDI para os campos; O tipo de campo/registro EDI depende
  13:     /// do layout especificado.
  14:     /// </summary>
  15:     /// <param name="Line">Linha do arquivo a ser decodificada</param>
  16:     protected virtual void DecodeLine(string Line)
  17:     {
  18:     
  19:     }
  20:     #endregion
  21:  
  22:     #region Métodos Públicos
  23:     /// <summary>
  24:     /// Carrega um arquivo EDI
  25:     /// </summary>
  26:     /// <param name="FileName">Nome do arquivo a ser carregado</param>
  27:     public virtual void LoadFromFile(string FileName)
  28:     {
  29:         StreamReader sr = new StreamReader(FileName);
  30:         this.Lines.Clear();
  31:         while (!sr.EndOfStream)
  32:         {
  33:             this.DecodeLine(sr.ReadLine());
  34:         }
  35:         sr.Close();
  36:         sr.Dispose();
  37:     }
  38:  
  39:     public virtual void LoadFromStream(Stream s)
  40:     {
  41:         this.Lines.Clear();
  42:         StreamReader sr = new StreamReader(s);
  43:         while (!sr.EndOfStream)
  44:         {
  45:             this.DecodeLine(sr.ReadLine());
  46:         }
  47:         sr.Close();
  48:         sr.Dispose();
  49:     }
  50:  
  51:     /// <summary>
  52:     /// Grava um arquivo EDI em disco
  53:     /// </summary>
  54:     /// <param name="FileName">Nome do arquivo EDI a ser salvo</param>
  55:     public virtual void SaveToFile(string FileName)
  56:     {
  57:         StreamWriter sw = new StreamWriter(FileName);
  58:         foreach (TRegistroEDI linha in this.Lines)
  59:         {
  60:             linha.CodificarLinha();
  61:             sw.WriteLine(linha.LinhaRegistro);
  62:         }
  63:         sw.Close();
  64:         sw.Dispose();
  65:     }
  66:     #endregion
  67: }

Veja que como principal propriedade, temos um objeto List<TRegistroEDI>, que representa uma coleção de objetos TRegistroEDI. Nesta propriedade teremos acesso a todos os registros de um arquivo, ou seja, esta classe representa o arquivo em si.

Temos o método virtual DecodeLine, recebendo uma string como parâmetro. Esta string é uma linha do arquivo gravado/a ser gravado no disco. Notaram que este método não faz nada?

Calma lá! Lembre-se que esta é uma classe base, ou seja, o arquivo EDI em si é derivado dela, usaremos herança e sobreescrevemos o método DecodeLine de acordo com o tipo de layout!

Para adiantar um pouco, é nele que iremos atribuir a linha do arquivo na propriedade LinhaRegistro de um objeto TRegistroEDI e chamamos a rotina de decodificação do mesmo e em seguida é feita a adição do um objeto TRegistroEDI decodificado na propriedade Lines..

Os métodos LoadFromFile e LoadFromStream têm funcionamento semelhante (a diferença é que o LoadFromFile cria um stream dentro do método, e em seguida executa a mesma coisa que o LoadFromStream, recebendo diretamente um stream): É feita a leitura linha a linha do arquivo EDI, onde a mesma passa pelo método DecodeLine (sim, ele mesmo, o que não tem implementação na classe TEDIFile!).

O método SaveToFile possui funcionamento "inverso" dos métodos de carga: A propriedade Lines é lida elemento por elemento, e cada elemento é codificado peo método CodificarLinha da classe TRegistroEDI e é adicionado a um StreamWriter. Em seguida, este stream é salvo em um arquivo no disco.

Que tal alterarmos um pouco o nosso método de teste do artigo anterior?

   1: public void TesteLayout()
   2: {
   3:     //Teste de formatação de um campo EDI:
   4:     TCampoRegistroEDI campo1 = new TCampoRegistroEDI(TTiposDadoEDI.ediInteiro, 8, 0, 0, null, null, 1);
   5:     TCampoRegistroEDI campo2 = new TCampoRegistroEDI(TTiposDadoEDI.ediDDMMAA, 6, 0, 0, null, null, 9);
   6:    
   7:     campo1.ValorNatural = 12;
   8:     campo2.ValorNatural = DateTime.Now();
   9:  
  10:     //Veja o resultado das variáveis abaixo!
  11:     string campo1formatado1 = campo1.CodificarNaturalParaEDI();
  12:     string campo1formatado2 = campo2.CodificarNaturalParaEDI();
  13:  
  14:     //Teste de montagem de linha de registro
  15:     TRegistroEDI reg = new TRegistroEDI();
  16:     reg.CamposEDI.Add(campo1);
  17:     reg.CamposEDI.Add(campo2);
  18:     reg.CodificarLinha();
  19:     //Veja o resultado da variável abaixo!
  20:     string linha = reg.LinhaRegistro;
  21:  
  22:     //Testando o salvamento do arquivo
  23:     TEDIFile arq = new TEDIFile();
  24:     arq.Lines.Add(campo1);
  25:     arq.Lines.Add(campo2);
  26:     arq.SaveToFile("C:\MeuArq.txt");
  27:  
  28:     //Testando a carga do arquivo
  29:     TEDIFIle arq2 = new TEDIFile();
  30:     arq.LoadFromFile("C:\MeuArq.txt");
  31:     //agora, dê um "watch" na variável Arq e veja o conteúdo dela...
  32: }

No próximo artigo desta série, teremos uma aplicação completa de desenvolvimento de um layout, usando este conjunto de classes!

Até lá!

Leia o restante deste post...

  © Blogger templates ProBlogger Template by Ourblogtemplates.com 2008 - Editado e configurado por Leonel F.

Voltar ao TOPO