[Cascavel-pm] Qual a melhor estrutura pra lidar com o header de um arquivo de dados?

Rodrigo Torres motoster em gmail.com
Segunda Agosto 15 14:23:10 PDT 2005


Olá, monges.

É um prazer participar desta lista.

Estou escrevendo, em perl, um aplicativo pra extrair (dump) informações
contidas no header de um arquivo binário de dados.

O header é mais ou menos assim:

        00000000   4D 5A DD DD  04 08 0C 10  31 32 35 30  MZ......1250
        0000000C   35 32 30 30  35 30 38 31  39 30 32 30  520050819020
        00000018   30 30 30 32  30 30 30 32  36 36 39 37  000200026697
        00000024   31 4E 4D 41  36 39 30 50  52 45 53 49  1NMA690PRESI
        00000030   44 45 4E 54  45 20 4D 90  44 49 43 45  DENTE M.DICE
        0000003C   20 20 20 20  20 20 20 20  20 20 20 20
        00000048   20 50 52 45  46 45 49 54  55 52 41 20   PREFEITURA
        00000054   4D 55 4E 49  43 49 50 41  4C 20 44 45  MUNICIPAL DE
        00000060   20 50 52 45  53 49 44 45  4E 54 45 20   PRESIDENTE
        0000006C   4D 90 44 49  43 45 20 20  20 20 20 20  M.DICE
        00000078   20 20 20 20  20 20 20 20  20 20 20 20
        00000084   20 20 20 20  20 20 20 20  20 20 20 20
        00000090   20 20 20 20  20 20 20 20  20 20 20 20
        0000009C   20 20 20 20  20 20 20 20  20 20 20 20
        000000A8   20 20 20 20  20 20 20 20  20 20 20 20
        000000B4   20 20 20 20  20 20 20 20  20 20 20 20
        000000C0   20 20 20 20  20 20 20 20  20 20 20 20
        000000CC   20 20 20 20  20 20 20 20  20 20 20 20
        000000D8   20 20 20 20  20 20 20 20  20 20 20 20
        000000E4   20 20 20 20  20 20 20 20  20 20 20 20
        000000F0   20 20 20 20  20 20 20 20  20 30 30 30           000
        000000FC   30 30 30 30  30 33 36 33  30 31 32 30  000003630120
        00000108   30 35 30 30  31 31 39 33  00 00 00 00  05001193....

Como vocês já devem ter percebido, os campos desse header são strings
codificadas em IBM850.

0x0 até 0x3 é número mágico;
0x4 até 0x7 é reservado, e
o resto é informação em strings de tamanho fixo.

Eu sou novato em Perl, na verdade, sou novato em programação -- e já que
estou sendo sincero mesmo, sou novato em Linux também, hehe ;) , dei uma
pesquisada por aí mas não consegui encontrar nada muito direto ou
específico. 

Dei uma olhada no módulo Image::Exiftool, que é uma ferramenta para
extrair informações de EXIF de dentro de um arquivo de imagem, mas
aquilo é puro exoterismo pra mim. Do pouco que entendi, vi que ele tem
uma problema um pouco diferente do meu, uma vez que os campos, em sua
maioria, têm seus valores dentro de uma lista de pré-definidos, o que
não é meu caso, já que lido com datas, horas e strings arbitrárias.

Enfim, depois de matutar um pouquinho (Impaciência), ponderar num modo
elegante de usar as informações desse header (Presunção) e mudar a
estrutura sem quebrar demais o programa (Preguiça :) cheguei ao seguinte
resultado:

my %Header = (
    magic   => '4d5a' . 'dddd', # expressão regular  
    tamanho => 512,
    arquivo => undef,
    campos  => [
        { 'formato' => "a", 'valor' => undef, 'descricao' => "Versão do formato" },
        {
            'formato'   => "a8",
            'descricao' => "Data do backup",
            'valor'     => undef;
            'funcao'    => \&data
        },
        {
            'formato'   => "a6",
            'descricao' => "Hora do backup",
            'valor'     => undef;
            'funcao'    => \&hora
        },
        { 'formato' => "a5", 'descricao' => "Serial da instalação" },
        {
            'formato'   => "a9",
            'descricao' => "Tamanho do arquivo",
            'valor'     => undef;
            'funcao'    => \&tamanho
        },
        { 'formato' => "a",    'valor' => undef, 'descricao' => "Envio Orçamento" },
        { 'formato' => "a2",   'valor' => undef, 'descricao' => "UF" },
        { 'formato' => "a3",   'valor' => undef, 'descricao' => "Código da Empresa" },
        { 'formato' => "a30",  'valor' => undef, 'descricao' => "Cidade" },
        { 'formato' => "a50",  'valor' => undef, 'descricao' => "Empresa" },
        { 'formato' => "a126", 'valor' => undef, 'descricao' => "Mensagem" },
        { 'formato' => "a5",   'valor' => undef, 'descricao' => "Último xxpess->cdpess" },
        { 'formato' => "a6",   'valor' => undef, 'descricao' => "Último xxitem->cdprod" },
        { 'formato' => "a2",   'valor' => undef, 'descricao' => "Quantidade de exercícios" },
        { 'formato' => "a4",   'valor' => undef, 'descricao' => "Exercício 1" },
        { 'formato' => "a6",   'valor' => undef, 'descricao' => "emmext->seqve_ls 1" },

    )

##  A chave magic é uma expressão regular porque esse formato vai mudar
no futuro e eu posso checar ele com

        my $length = length $Header{magic};
        
        read( $fh , my $magic, $length );
        my $numbers = unpack( "H$length", $magic );
        print "Wow! Got another\n" if ( $numbers !~ /$Header{magic}/ )

##  A chave tamanho serve pra eu saber quantos bytes ler [1];
##  A chave arquivo é pra eu por o nome do arquivo que está sendo
processado no momento. 
##  A chave campos é um array (porque a ordem importa) contendo hashes,
cujas chaves são explicadas a seguir:
      * formato  : Formato que vai ser concatenado pra resultar no
        template do unpack como a seguir:

        my $Template = join " ", map $_->{formato}, @{ $Header{campos} };

      * descrição: A descrição do campo. Pensei nisso como uma forma de
        auto-documentação que ajuda muito na hora de gerar o relatório
        [2] ( Preguiça );
      * valor    : O que vai ser efetivamente extraído do header, e
      * funcao   : uma função[3] a ser chamada para formatar o campo
        para impressão, de forma que eu simplesmente faço

        foreach my $campo ( @{ $Header{campos} } ) {
            if ( defined $$campo{funcao} ) {
                $$campo{valor} = &{ $$campo{funcao} }( $$campo{valor} );
            }
        }

	antes de imprimir.

Pois vejam bem, monges, que eu já pensei em usar um array simplesmente e
decorar a posição de cada campo. Isso me pareceu rápido, mas confuso.
Gostaria, no entanto pedir uma ajuda de vocês na forma de uma pergunta:

Eu estou fazendo alguma coisa realmente idiota, poderia estar fazendo
muito melhor, ou já tá bom? Gostaria de sugestões. Se alguém quiser ver
o código todo e um arquivo de exemplo, entre em contato.

Humildemente, seu menor aprendiz,

motobói
	

1 -
    $bytes_read = read( $fh, my $dados, $Header{tamanho} )
      or ++$error;

    #VEJA A EXPLICAÇÃO SOBRE AS CHAVES PARA ENTENDER $Template
    @fields = map decode( "ibm850", $_ ), unpack( $Template, $dados );

    foreach ( my $i = 0 ; $i <= $#fields ; $i++ ) {
        $Header{campos}[$i]{valor} = $fields[$i];
    }

2 - 
    format = 
    @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    $campo,			      $valor
    ~~				     ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 
                                      $valor
.


    print ">>> $Header{arquivo}\n";

    foreach ( @{ $Header{campos} } ) {
        ( $campo, $valor ) = @{$_}{qw/descricao valor/}; #hash slices
        write();
    }

3 - EXEMPLO DE UMA FUNÇÃO DE FORMATAÇÃO:

    sub tamanho {

        #Formata o campo tamanho do arquivo.
        $_ = shift;
        return sprintf "%d Kbytes ( %d bytes)", $_ / 1024, $_;
    }




Mais detalhes sobre a lista de discussão Cascavel-pm