Шаг 49.
Структура текстового файла

    На этом шаге мы перечислим способы чтения текстового файла.

    Сейчас мы поговорим более подробно о структуре текстового файла. Рассмотрим возможные варианты работы с текстовыми файлами.

    Основным признаком текстового файла является то, что он состоит из строк разной длины. Строки отделены друг от друга разделителями. Чаще всего это последовательность двух кодов - 13 и 10. Возможны и другие варианты, например, некоторые DOS-редакторы отделяли строки только одним кодом 13.

    Построчное чтение текстового файла можно осуществить четырьмя наиболее очевидными способами.

  1. Побайтное чтение из файла. Как только достигаем символа-разделителя производим действие над считанной строкой и переходим к чтению следующей строки. При этом, разумеется, следует учесть, что на конце файла может не быть символа-разделителя. Если кто-то решит, что это слишком медленный способ, то заметим, что Windows неплохо кэширует диск, так что все выглядит не так уж плохо.
  2. Чтение в небольшой буфер, но так чтобы туда входила, по крайней мере, одна строка. Прочитав, находим в буфере конец строки и производим над ней какое-либо действие. Далее следует обратиться к файлу и передвинуть указатель так, чтобы он был в файле на начале следующей строки, и, разумеется, повторить действие.
  3. Чтение в буфер произвольной длины. После чтения производится поиск всех строк, попавших в буфер, и выполнение действий над ними. При этом с большой вероятностью должна возникнуть ситуация, когда одна из строк не умещается в буфере. Мы обязаны учесть такую возможность.
  4. Чтение в буфер, в который помещается весь файл. Это частный случай третьего подхода и наиболее простой с точки зрения программирования.

    В следующем примере реализован третий подход.

.386P
;Плоская модель памяти.
.MODEL FLAT, STDCALL
;Константы.
STD_OUTPUT_HANDLE  equ  -11
GENERIC_READ     equ 80000000h
GENERIC_WRITE    equ 40000000h
GEN  = GENERIC_READ or GENERIC_WRITE
SHARE = 0
OPEN_EXISTING    equ  3
;Прототипы внешних процедур.
EXTERN  ExitProcess@4:NEAR
EXTERN  GetCommandLineA@0: NEAR
EXTERN	CreateFileA@28:NEAR
EXTERN	CloseHandle@4:NEAR
EXTERN  WriteFile@20:NEAR
EXTERN  ReadFile@20:NEAR
EXTERN  CharToOemA@8:NEAR
;-----------------------------------------
;Директивы компоновщику для подключения библиотек 
includelib \masm32\lib\user32.lib 
includelib \masm32\lib\kernel32.lib
;-----------------------------------------
;Сегмент данных.
_DATA SEGMENT DWORD PUBLIC USE32 'DATA' 
      HANDL   DWORD  ?   ;Дескриптор консоли.
      HFILE   DWORD  ?   ;Дескриптор файла.
      BUF     DB  100   DUP(0) ;Буфер для параметров.
      BUFER   DB  1000  DUP(0) ;Буфер для файла.
      NAMEOUT DB  "CONOUT$"
      INDS    DD  0  ;Номер символа в строке.
      INDB    DD  0  ;Номер символа в буфере.
      NUMB    DD  ?
      NUMC    DD  ? 
      PRIZN   DD  0
      STROKA  DB  300  DUP(0) 
_DATA ENDS 
;Сегмент кода.
_TEXT SEGMENT  DWORD  PUBLIC USE32   'CODE' 
START: 
;Получить  HANDLE  вывода (консоли) как файла.
      PUSH    0
      PUSH    0
      PUSH    OPEN_EXISTING
      PUSH    0
      PUSH    0
      PUSH    GEN
      PUSH    OFFSET NAMEOUT
      CALL    CreateFileA@28
      MOV     HANDL,EAX 
;Получить количество параметров.
      CALL     NUMPAR
;Если параметр один, то искать в текущем каталоге.
      CMP      EAX,1
      JE       NO_PAR
;----------------------------------------
;Получить параметр c номером EDI.
      MOV      EDI,2
      LEA      EBX,BUF
      CALL     GETPAR
;Открыть файл.
      PUSH 0              ;Должен быть равен 0.
      PUSH 0	          ;Атрибут файла (если  создаем)
      PUSH OPEN_EXISTING  ;Как открывать.
      PUSH 0              ;Указатель на security attr.
      PUSH 0              ;Режим общего доступа.
      PUSH GEN            ;Режим доступа.
      PUSH OFFSET BUF     ;Имя файла.
      CALL CreateFileA@28
      CMP  EAX,-1
      JE   NO_PAR
      MOV HFILE,EAX 
LOO: 
;Читать 1000 байт.
      PUSH 0
      PUSH OFFSET NUMB
      PUSH 1000
      PUSH OFFSET BUFER
      PUSH HFILE
      CALL ReadFile@20 
      MOV  INDB,0
;Проверим, есть ли в буфере байты.
      CMP  NUMB,0
      JZ   _CLOSE 
;Заполняем строку. 
LOO1:
      MOV  EDI,INDS
      MOV  ESI,INDB
      MOV  AL,BYTE PTR BUFER[ESI]
      CMP  AL,13   
;Проверка на конец строки.
      JE   _ENDSTR
      MOV  BYTE PTR STROKA[EDI],AL
      INC  ESI
      INC  EDI
      MOV  INDS,EDI
      MOV  INDB,ESI
      CMP  NUMB,ESI ;Проверка на конец буфера.
      JNBE LOO1 
;Закончился буфер.
      MOV  INDS,EDI
      MOV  INDB,ESI
      JMP  LOO 
_ENDSTR: 
;Делаем что-то со строкой.
      CALL OUTST 
;Обнулить строку.
      MOV  INDS,0 
;Перейти к следующей строке в буфере.
      ADD  INDB,2 
;Не закончился ли буфер?
      MOV  ESI,INDB
      CMP  NUMB,ESI
      JAE  LOO1
      JMP  LOO 
;----------------------------------------
_CLOSE: 
;Проверим, не пустая ли строка.
      CMP  INDS,0
      JZ   CONT 
;Делаем что-то со строкой.
      CALL OUTST 
CONT: 
;Закрыть файлы.
      PUSH HFILE
      CALL CloseHandle@4 
;Конец работы программы.
NO_PAR:
      PUSH 0
      CALL ExitProcess@4 
;****************************
;Область процедур.
;****************************
;Процедура определения количества параметров в строке. 
;Определить количество параметров (->EAX) 
NUMPAR PROC
      CALL     GetCommandLineA@0
      MOV      ESI,EAX ;Указатель на строку.
      XOR      ECX,ECX ;Счетчик.
      MOV      EDX,1   ;Признак.
L1:
      CMP      BYTE PTR [ESI],0
      JE       L4
      CMP      BYTE PTR [ESI],32
      JE       L3
      ADD      ECX,EDX ;Номер параметра.
      MOV      EDX,0
      JMP      L2
L3:
      OR       EDX,1 
L2:
      INC      ESI
      JMP      L1 
L4:
      MOV      EAX,ECX
      RET
NUMPAR ENDP
;Получить параметр из командной строки. 
;ЕВХ - указывает на буфер, куда будет помещен параметр 
;В буфер помещается строка с нулем на конце. 
;EDI - номер параметра. 
GETPAR PROC
      CALL     GetCommandLineA@0
      MOV      ESI,EAX  ;Указатель  на строку.
      XOR      ECX,ECX  ;Счетчик.
      MOV      EDX,1    ;Признак. 
L1:
      CMP      BYTE  PTR [ESI], 0
      JE       L4		
      CMP      BYTE  PTR [ESI], 32
      JE       L3		
      ADD      ECX,EDX   ;Номер параметра.
      MOV      EDX,0		
      JMP      L2		
L3:			
      OR       EDX,1		
L2:			
      CMP      ECX,EDI		
      JNE      L5		
      MOV      AL, BYTE PTR [ESI]
      MOV      BYTE PTR [EBX], AL
      INC      EBX		
L5:
      INC      ESI		
      JMP      L1		
L4:
      MOV      BYTE  PTR   [EBX],0
      RET
GETPAR ENDP
;Вывести строку в консоль с разделителем.
OUTST  PROC
      MOV  EBX,INDS
      MOV  BYTE PTR STROKA[EBX],0
      PUSH OFFSET  STROKA
      PUSH OFFSET STROKA
      CALL CharToOemA@8 
;В конце строки - разделитель.
      MOV  BYTE PTR STROKA[EBX],6
      INC  INDS 
;Вывести строку.
      PUSH 0
      PUSH OFFSET NUMC
      PUSH INDS
      PUSH OFFSET STROKA
      PUSH HANDL
      CALL WriteFile@20
      RET
OUTST ENDP 
_TEXT ENDS 
      END START
Текст этой программы можно взять здесь.

    Приведенный текст программы демонстрирует один из возможных алгоритмов обработки текстового файла - построчное чтение текстового файла. Часть программы, занимающаяся чтением и анализом текстового файла, сосредоточена между метками LOO и CONT. Детально разберитесь в приведенной программе.

    Со следующего шага мы начнем рассматривать директивы и макросредства ассемблера.




Предыдущий шаг Содержание Следующий шаг