Sub-sistema para arquivos texto

Aqui você poderá oferecer suas Contribuições, Dicas e Tutoriais (Texto ou Vídeo) que sejam de interesse de todos.

Moderador: Moderadores

Avatar do usuário
Maligno
Membro Master
Membro Master
Mensagens: 6398
Registrado em: 06 Jul 2004 01:40
Localização: Londrina/PR

Sub-sistema para arquivos texto

Mensagem por Maligno »

Criei há algum tempo um sub-sistema de manipulação de arquivos a fim de tornar mais prática a leitura de arquivos texto. É sempre uma tarefa chata abrir arquivos, procurar pelo par CR/LF, contar, voltar o ponteiro, etc. É muito enfadonho.

Esse sistema trabalha não apenas com um mas com vários arquivos texto, que seguem o mesmo esquema de "aliases" do Clipper com arquivos DBF. Cada arquivo texto é indexado e seus "entry points" são armazenados, a fim de permitir o acesso randômico a qualquer linha, independentemente da largura que ela tenha. Além disso, há uma função que retorna o tamanho da maior linha existente, dispensando uma "varredura" extra, o que facilita a vida dos que pretenderem fabricar visualizadores de texto (ex: preview de relatórios).

Infelizmente, dada a falta de tempo, é um projeto inconcluso. Faltaram: a inserção de linhas e a criação de comandos, a fim de facilitar mais ainda o uso. Notem que existe uma função chamada txAddLine(), mas ela está pela metade, incluindo linha apenas no final do arquivo (append).

O arquivo de cabeçalho com algumas constantes:

Código: Selecionar todos

#define _kTXTSYS_NOERROR            0
#define _kTXTSYS_FILENOTFOUND       1
#define _kTXTSYS_OPENERROR          2
#define _kTXTSYS_CREATEERROR        3
#define _kTXTSYS_READERROR          4
#define _kTXTSYS_WRITEERROR         5
#define _kTXTSYS_ALREADYOPEN        6
#define _kTXTSYS_SELECTERROR        7
#define _kTXTSYS_ALIASNOTFOUND      8
#define _kTXTSYS_NOTSELECTED        9


// Modos de acesso e compartilhamento dos arquivos
#define _kTXTSYS_ACCESS_READ        0 // leitura
#define _kTXTSYS_ACCESS_WRITE       1 // escrita
#define _kTXTSYS_ACCESS_READWRITE   2 // leitura e escrita (default para a abertura)
                                      // 
#define _kTXTSYS_SHARING_EXCLUSIVE 16 // acesso exclusivo (default na criação do arquivo)
#define _kTXTSYS_SHARING_DENYWRITE 32 // proibição de escrita
#define _kTXTSYS_SHARING_DENYREAD  48 // proibição de leitura
#define _kTXTSYS_SHARING_DENYNONE  64 // compartilhamento total
O fonte do sistema:

Código: Selecionar todos

                                        // 
                                        //
                                        //              TXT Sub-System v1.0
                                        // --------------------------------
                                        // (c) 04/2004, Paulo Buzinello
                                        //              Londrina/PR
                                        //              Brasil
                                        //              paulo@buzinello.com
                                        //
                                        // ---------------------------------------------------------
                                        // Sub-sistema de controle de acesso, para leitura e escrita
                                        // em arquivos de texto, de forma semelhante ao sistema DBF,
                                        // por acessar randomicamente suas linhas através de índice.
                                        // ---------------------------------------------------------
                                        //
                                        //                Funções Públicas
                                        //                ------------------------------------------
                                        //  txAddLine() - adiciona uma linha no arquivo
                                        //    txClose() - fecha o arquivo do texto ativo ou indicado
                                        // txCloseAll() - fecha todos os arquivos, em todas as áreas
                                        //    txError() - informa qual o último erro registrado
                                        //  txGetLine() - lê uma linha inteira do texto ativo
                                        //    txLines() - informa a quantidade de linhas de um texto
                                        //   txMaxCol() - informa a largura da maior linha existente
                                        // txNextMark() - encontra a próxima marca de tabulação
                                        //     txOpen() - abre um arquivo texto, indexando-o
                                        //  txSetArea() - seleciona uma área de texto
                                        //     txSize() - informa o tamanho do texto ativo
                                        //
                                        // 
#include "__sysmac.ch"
#include "__txtsys.ch"

static aFCB  := {}                      // bloco de controle de arquivo (descrito abaixo)
static nArea := 0                       // área de texto atualmente selecionada

#define  _FCB_ALIAS     aFCB[nArea][ 1] // nome da área
#define _kFCB_ALIAS                  1  // 
#define  _FCB_FILENAME  aFCB[nArea][ 2] // nome completo do arquivo
#define  _FCB_FILEHAND  aFCB[nArea][ 3] // número da alça de manipulação
#define  _FCB_FILESIZE  aFCB[nArea][ 4] // tamanho do arquivo
#define  _FCB_PREGETLIN aFCB[nArea][ 5] // bloco de código de preparação de leitura de uma linha
#define  _FCB_PRESETLIN aFCB[nArea][ 6] // bloco de código de preparação de escrita de uma linha
#define  _FCB_LINS      aFCB[nArea][ 7] // quantidade de linhas (obtida na carga)
#define  _FCB_COLS      aFCB[nArea][ 8] // largura da linha mais extensa
#define  _FCB_FOCUS     aFCB[nArea][ 9] // número de ordem da linha atualmente focalizada
#define  _FCB_MARKS     aFCB[nArea][10] // matriz com os números de linhas marcadas para tabulação
#define  _FCB_POINTERS  aFCB[nArea][11] // string que armazenará os ponteiros de acesso
#define _kFCB_SIZE                  11  // 
                                        // ---------------------------------------------------------
                                        // Informações sobre os ponteiros de acesso rápido às linhas
                                        // ---------------------------------------------------------
                                        // A string _FCB_POINTERS conterá os ponteiros usados para o
                                        // acesso direto às linhas no arquivo. Esta será povoada com
                                        // a abertura do arquivo. Seu tamanho limite também limitará
                                        // a capacidade de leitura do arquivo. Cada ponteiro terá um
                                        // tamanho fixo, definido por _kPOINTER_LEN. Como a variável
                                        // Clipper está limitada em 64Kb, o maior arquivo que poderá
                                        // ser manipulado, terá sua quantidade de linhas limitada ao
                                        // resultado da divisão de 65535 por _kPOINTER_LEN. Caso seu
                                        // valor seja 4 (o default) por exemplo, teremos o máximo de
                                        // 16.383 linhas. Já que os ponteiros estarão armazenados no
                                        // formato alfadecimal super-extendido, o tamanho do arquivo
                                        // estará limitado a 78.074.895 bytes.
                                        // Com relação ao buffer de leitura: o tamanho de 4KB parece
                                        // ser apropriado, uma vez que dificilmente uma linha terá a
                                        // largura superior a 4.096 colunas.
                                        // Em resumo: os valores, relativamente grandes, deverão ser
                                        // suficientes para atender a grande maioria das aplicações.
                                        // ---------------------------------------------------------
                                        //
#define _kBUFFER_LEN     4096           // largura máxima de uma linha e também do buffer de leitura
#define _kPOINTER_LEN       4           // largura de cada ponteiro de acesso
                                        // tamanho máximo do texto: ~~~~(aSE) == 78.074.895(d) bytes


//**************************************************************************************************
static function BuildIndex()         // O objetivo desta função é calcular a quantidade de linhas do
local cBuffer := Space(_kBUFFER_LEN) // arquivo e montar um índice com os ponteiros de acesso rápido
local nFPoint := 0                   // a essas linhas, dentro do arquivo. Cada ponteiro é um número
local cRead   := ""                  // na base alfadecimal de largura fixa, definida pela constante
local i                              // _kPOINTER_LEN.
local n
//
// Inicialização do ponteiro do arquivo e variáveis
FSeek(_FCB_FILEHAND,0,0)
_FCB_LINS     := 0
_FCB_COLS     := 0
_FCB_MARKS    := {}
_FCB_POINTERS := ""
//
// Leitura integral do arquivo com o armazenamento do ponteiro de acesso a cada linha
while (n := FRead(_FCB_FILEHAND,@cBuffer,_kBUFFER_LEN)) > 0
   cRead += Left(cBuffer,n)
   while (i := At(EOL,cRead)) > 0
      _FCB_LINS++
      _FCB_POINTERS += Dec2AlfaSE(nFPoint,_kPOINTER_LEN)
      _FCB_COLS     := if(_FCB_COLS < i-1, i-1, _FCB_COLS)
      if Left(cRead,Len(PrtTabMark())) = PrtTabMark()
         AAdd(_FCB_MARKS,_FCB_LINS)
      end
      nFPoint += i+Len(EOL)-1
      cRead   := SubStr(cRead,i+2)
   end
   cBuffer := Space(_kBUFFER_LEN)
end
//
// O último ponteiro (extra) será necessário apenas para o cálculo do tamanho da última linha
if Empty(cRead)
   _FCB_POINTERS += Dec2AlfaSE(nFPoint-2         ,_kPOINTER_LEN)
else
   _FCB_LINS++
   _FCB_POINTERS += Dec2AlfaSE(nFPoint           ,_kPOINTER_LEN)
   _FCB_POINTERS += Dec2AlfaSE(nFPoint+Len(cRead),_kPOINTER_LEN)
end
return VOID


//**************************************************************************************************
static function GetIndex(cAlias)
return AScan(aFCB,{|aFile|aFile[_kFCB_ALIAS]==Upper(cAlias)})


//**************************************************************************************************
function txAddLine(cLine,nOrder,xAlias)
local nLast := nArea
local lRet
*
default xAlias to nArea
default nOrder to 0
*
txError()
if (lRet := txSetArea(xAlias))
   if nOrder = 0 .or. nOrder > _FCB_LINS
      // Adicionar a nova linha no final do arquivo
      _FCB_LINS++
      _FCB_POINTERS += Dec2AlfaSE(nFPoint           ,_kPOINTER_LEN)
      _FCB_POINTERS += Dec2AlfaSE(nFPoint+Len(cRead),_kPOINTER_LEN)
      
   else
      //
      // Adicionar a nova linha numa determinada posição do arquivo,
      // deslocando todas as linhas já existentes para o final deste
      
   end
end
return lRet


//**************************************************************************************************
function txClose(xAlias)
local lRet
*
if (lRet := txSetArea(if(xAlias=VOID,nArea,xAlias)))
   FClose(_FCB_FILEHAND)
   if Len(aFCB) = 1
      aFCB := {}
   else
      ADel(aFCB,nArea)
      ASize(aFCB,Len(aFCB)-1)
      nArea := RetMin(nArea,Len(aFCB))
   end
   GarbagColl()
end
return lRet


//**************************************************************************************************
function txCloseAll()
while txClose(1)
end
txError()
return VOID


//**************************************************************************************************
function txError(nError)
static nLstError := _kTXTSYS_NOERROR
if nError  = VOID
   nError := _kTXTSYS_NOERROR
   Exchange(@nLstError,@nError)
else
   nLstError := nError
end
return nError


//**************************************************************************************************
function txFHandle(xAlias)
return if(txSetArea(if(xAlias=VOID,nArea,xAlias)), _FCB_FILEHAND, -1)


//**************************************************************************************************
function txGetLine(cLine,nOrder,xAlias) // A variável cLine deverá ser passada por referência pois o
local nLast := nArea                    // retorno da função é o valor lógico resultante da leitura.
local nSLen                             // Nota: xAlias poderá conter o nome ou o número da área que
local nIPtr                             // será acessada.
local lRet                              //
local i                                 //
default xAlias to nArea
*
if (lRet := txSetArea(xAlias))
   default nOrder to _FCB_FOCUS
   *
   if (lRet := (nOrder > 0 .and. nOrder <= _FCB_LINS))
      i     := (nOrder-1)*_kPOINTER_LEN+1
      nIPtr := AlfaSE2Dec(SubStr(_FCB_POINTERS,i              ,_kPOINTER_LEN))
      nSLen := AlfaSE2Dec(SubStr(_FCB_POINTERS,i+_kPOINTER_LEN,_kPOINTER_LEN))-nIPtr - if(nOrder=_FCB_LINS,0,2)
      cLine := Space(nSLen)
      *
      if nSLen > 0
         FSeek(_FCB_FILEHAND,nIPtr,0)
         if (lRet := (nSLen := FRead(_FCB_FILEHAND,@cLine,nSLen)) > 0)
            cLine := Left(cLine,nSLen)
         end
      end
      cLine := Eval(_FCB_PREGETLIN,cLine)
      *
      if lRet
         _FCB_FOCUS := nOrder+1
      end
   end
   *
   if !lRet
      txError(_kTXTSYS_READERROR)
   end
end
nArea := nLast
return lRet


//**************************************************************************************************
function txLines(xAlias)
local nLast := nArea
local nLins := -1
default xAlias to nArea
*
if txSetArea(xAlias)
   nLins := _FCB_LINS
end
nArea := nLast
return nLins


//**************************************************************************************************
function txMaxCol(xAlias)
local nLast := nArea
local nCols := -1
default xAlias to nArea
*
if txSetArea(xAlias)
   nCols := _FCB_COLS
end
nArea := nLast
return nCols


//**************************************************************************************************
function txNextMark(nInc,nStart,xAlias)
local nLast := nArea
local nLin  := 0
local i
default xAlias to nArea
default nStart to 1
*
if txSetArea(xAlias) .and. Len(_FCB_MARKS) > 0
   i := if(nInc>0, 1, Len(_FCB_MARKS))
   while TRUE
      if if(nInc>0, _FCB_MARKS[i] >= nStart, _FCB_MARKS[i] <= nStart)
         nLin := _FCB_MARKS[i]
         exit
      end
      i += if(nInc>0, 1, -1)
      if i=if(nInc>0, Len(_FCB_MARKS)+1, 0)
         exit
      end
   end
end
nArea := nLast
return nLin


//**************************************************************************************************
function txOpen(cFName  ,; // path+nome do arquivo
                cAlias  ,; // nome pelo qual esta área de dados poderá ser selecionada futuramente
                lCreate ,; // se verdadeiro e o arquivo não existir, será criado (inclusive o path)
                nMode   ,; // modo de acesso
                bPGetLin,; // bloco de código de preparação de leitura de uma linha
                bPSetLin ; // bloco de código de preparação de escrita de uma linha
                )
local aDir := Directory(cFName)
local nHnd
local lRet
*
default cAlias   to cFName
default nMode    to _kTXTSYS_ACCESS_READWRITE
default lCreate  to FALSE
default bPGetLin to {|c|c}
default bPSetLin to {|c|c}
*
txError()
if GetIndex(cAlias) != 0
   txError(_kTXTSYS_ALREADYOPEN)
   return FALSE
end
*
if (lRet := Len(aDir) > 0)
   if (lRet := (nHnd := FOpen(cFName,nMode)) > 0)
      AAdd(aFCB,Array(_kFCB_SIZE))
      nArea          := Len(aFCB)
      _FCB_ALIAS     := cAlias
      _FCB_FILENAME  := cFName
      _FCB_FILEHAND  := nHnd
      _FCB_FILESIZE  := aDir[1][2]
      _FCB_PREGETLIN := bPGetLin
      _FCB_PRESETLIN := bPSetLin
      *
      BuildIndex()
      GarbagColl()
   else
      txError(_kTXTSYS_OPENERROR)
   end
else
   if lCreate
      if !SeekFPath(FilePath(cFName))
          MakeFPath(FilePath(cFName))
      end
      if (lRet := (nHnd := FCreate(cFName,nMode)) > 0)
         AAdd(aFCB,Array(_kFCB_SIZE))
         nArea          := Len(aFCB)
         _FCB_ALIAS     := cAlias
         _FCB_FILENAME  := cFName
         _FCB_FILEHAND  := nHnd
         _FCB_FILESIZE  := 0
         _FCB_PREGETLIN := bPGetLin
         _FCB_PRESETLIN := bPSetLin
      else
         txError(_kTXTSYS_CREATEERROR)
      end
   else
      txError(_kTXTSYS_FILENOTFOUND)
   end
end
return lRet


//**************************************************************************************************
function txSetArea(xAlias) // xAlias poderá conter o número da área ou seu nome
local lRet                 //
local i                    //
*
txError()
if ValType(xAlias) = "N"
   if (lRet := xAlias > 0 .and. xAlias <= Len(aFCB))
      nArea := xAlias
   else
      txError(_kTXTSYS_SELECTERROR)
   end
else
   if (lRet := xAlias != VOID .and. (i := GetIndex(xAlias)) > 0)
      nArea := i
   else
      txError(_kTXTSYS_ALIASNOTFOUND)
   end
end
return lRet


//**************************************************************************************************
function txSize(xAlias)
local nLast := nArea
local nSize := -1
local lRet
default xAlias to nArea
*
if (lRet := txSetArea(xAlias))
   nSize := _FCB_FILESIZE
end
nArea := nLast
return nSize
Exemplo de uso para leitura sequencial de um arquivo texto qualquer:

Código: Selecionar todos

function LerTxtTst()
if !txOpen("arquivo.txt","ALIAS",_kTXTSYS_ACCESS_READ) = _kTXTSYS_NOERROR
   // Um erro qualquer
   return .F.
end
*
cLine := ""
for i := 1 to txLines()
    txGetLine(@cLine,i)
    //
    // cLine contém a linha, sem o par CR/LF
    // Processe-a do jeito que precisar.
next
txClose()
return (i>1) // se .F., o arquivo estava vazio.
Usei pouco na verdade. Usei principalmente no meu preview de relatórios que, aliás, ficou diminuto por causa desse sub-sistema. No que me consta, não existem bugs. Mas caso alguém encontre algo errado, sinta-se à vontade para reclamar aqui mesmo. Pra tudo dá-se um jeito. :)
[]'s
Maligno
---
Não respondo questões técnicas através de MP ou eMail. Não insista.
As dúvidas devem ser postadas no fórum. Desta forma, todos poderão
se beneficiar das respostas.

---
Se um dia precisar de uma transfusão de sangue você perceberá como
é importante a figura do doador. Procure o hemocentro de sua cidade e
se informe sobre a doação de sangue, plaquetas e medula óssea. Doe!
Avatar do usuário
Maligno
Membro Master
Membro Master
Mensagens: 6398
Registrado em: 06 Jul 2004 01:40
Localização: Londrina/PR

Sub-sistema para arquivos texto

Mensagem por Maligno »

Complementando o post e corrigindo uma omissão, segue abaixo o conteúdo do arquivo de cabeçalho __SYSMAC.CH, mencionado no código principal. É uma adaptação do que tenho aqui. Apenas cortei o desnecessário ao código citado.

Código: Selecionar todos

#define ON         .t.
#define OFF        .f.
#define TRUE       .t.
#define FALSE      .f.
#define VOID       nil
#define SCROLLUP   0
#define SCROLLDOWN 1
#define bNIL       {||nil}
#define EOL        Chr(13)+Chr(10)

#xcommand DEFAULT <var> TO <defvl> => <var> := if(<var> = nil, <defvl>, <var>)
[]'s
Maligno
---
Não respondo questões técnicas através de MP ou eMail. Não insista.
As dúvidas devem ser postadas no fórum. Desta forma, todos poderão
se beneficiar das respostas.

---
Se um dia precisar de uma transfusão de sangue você perceberá como
é importante a figura do doador. Procure o hemocentro de sua cidade e
se informe sobre a doação de sangue, plaquetas e medula óssea. Doe!
Avatar do usuário
Pablo César
Usuário Nível 7
Usuário Nível 7
Mensagens: 5312
Registrado em: 31 Mai 2006 10:22
Localização: Curitiba - Paraná

Sub-sistema para arquivos texto

Mensagem por Pablo César »

Parabéns pelo seu trabalho Maligno, muito caprichado, rico em comentários e boa estruturação.
É muito bom ver a sua participação aqui de novo ! Espero que tudo esteja bem contigo.

Estou compilando o seu exemplo e acusa a falta das funções:
  • Dec2AlfaSE
    PrtTabMark
    RetMin
    GarbagColl
    Exchange
    AlfaSE2Dec
    SeekFPath
    FilePath
    MakeFPath
Procurei na internet para saber se eram de alguma biblioteca conhecida, mas não obtive sucesso.
Um clip-abraço !

Pablo César Arrascaeta
Compartilhe suas dúvidas e soluções com todos os colegas aqui do fórum.
Evite enviar as dúvidas técnicas por MPs ou eMails, assim todos iremos beneficiar-nos.
Responder