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! :-)
0 comentários:
Postar um comentário