Чтение текстового файла с полями фиксированной длины и записями в Delphi - PullRequest
1 голос
/ 08 июля 2010

Мне нужно прочитать данные из текстового файла, в котором длина полей и длина записи фиксированы. Поля заполнены нулями или пробелами, всегда отображаются в одном и том же порядке, и каждая запись заканчивается CRLF. Файл может иметь один из трех возможных типов записей, определяемых первым символом в записи.

Пока что я создал базовый класс для всех типов записей и дочерний класс для каждого типа записей.

type
  TRecordBase = class abstract
  public
    // Various common fields...
    function ToString: string; virtual; abstract;
    procedure Read(AString: string); virtual; abstract;
  end;

  TRecordType1 = class(TRecordBase)
  public
    //RecordType1 fields...
    function ToString: string; override;
    procedure Read(AString: string); override;
  end;

  TRecordType2 = class(TRecordBase)
  public
    //RecordType2 fields...
    function ToString: string; override;
    procedure Read(AString: string); override;
  end;

  TRecordType3 = class(TRecordBase)
  public
    //RecordType3 fields...
    function ToString: string; override;
    procedure Read(AString: string); override;
  end;

Затем я просто читаю каждую строку файла как строку, определяю его тип по первому символу, создаю соответствующий экземпляр класса и вызываю Read.

Идея состоит в том, что классы Record можно использовать как для чтения, так и для записи в строковое представление записи. Процедура Read должна разбить строку и назначить ее для открытых полей.

У меня есть два (или три) вопроса:

  • Это хороший подход для обработки файлов такого типа?
  • Если так, то как будет выглядеть ваша реализация процедуры Read? (Я имел дело с файлами с разделителями, но это мое первое знакомство с полями фиксированной длины)
  • Если нет, какой подход вы бы выбрали?

Update

Просто подумал, что заполню некоторые недостающие детали. Эти классы записей по сути являются DTO (объектами передачи данных). Поля объявлены как публичные, и единственные методы предназначены для преобразования в / из строки. Единственная проверка данных на полях - это проверка типа компилятора. Поля преобразуются в строку в необходимом порядке, используя TStringBuilder.AppendFormat. Это гарантирует, что поля дополняются и / или усекаются до нужной длины.

Я согласился с предложением Роба использовать Copy в сочетании с соответствующим StrTo* для получения данных из строки. Я также определил позиции и длины полей как константы класса, т.е.

const Field1Pos = 1;
const Field1Length = 1;
const Field2Pos = 2;
const Field2Length = 5;

Констант немного легче читать, чем "магические числа" при звонках на Copy.

Будем благодарны за любые другие предложения.

Ответы [ 4 ]

2 голосов
/ 08 июля 2010

Я бы изменил одну вещь: замените процедуру чтения конструктором Read, примерно так:

TRecordBase = class
public
  constructor CreateFromText(Text:string);virtual;abstract;
end;

TRecordType1 = class(TRecordBase)
public
  constructor CreateFromText(Text:string);override;
end;

В зависимости от того, что вы делаете со своими записями, это позволит сэкономить время при наборе текста и облегчит чтение кода:

var s:string; // string from stream or string-list
if s[1] = 'X'then DoSomethingWith(TRecordType1.Create(s));

Наличие виртуального конструктора также удобно, если число типов записей растет. Вы можете сделать что-то вроде этого:

// Define an class type
type TRecordBaseClass = class of TRecordBase;

// Using Delphi 2010? Use a dictionary to register (FirstChar, TRecordBaseClass) paris
var RecordClassDictionary = TDictionary<char, TRecordBaseClass>;

// Init the dictionary like this:
RecordClassDictionary.Add('1', TRecordType1);
RecordClassDictionary.Add('2', TRecordType2);
RecordClassDictionary.Add('3', TRecordType3);

// And use it like this:
var RecordBaseClass: TRecordBaseClass;
for line in TextToParse do
  if RecordClassDictionary.TryGetValue(line[1], RecordBaseClass) then
     // Read the record, do something with the record
     DoSomethingWithTheRecord(RecordBaseClass.CreateFromText(line))
  else
     raise Exception.Create('Unkown record type.');
1 голос
/ 08 июля 2010

Если длина поля и длина записи фиксированы, я бы использовал почти забытые записи с вариантной частью:

TRecord1 = packed record
  A: array[0..10] of char;
end;

TRecord2 = packed record
  B: array[0..20] of Byte;
  C: array[0..5] of Byte;
end;

TRecord3 = packed record
  D: array[0..10] of Byte;
  E: array[0..15] of Byte;
  F: array[0..1] of Byte;
end;


TMyRecord = packed record
  case RecordType: Char of
    '1': (Rec1: TRecord1);
    '2': (Rec2: TRecord2);
    '3': (Rec3: TRecord3);
end;

S := ReadLn;

with TMyRecord(S[1]) do
begin
  ...
end;

Если вы используете релиз Delphi, который поддерживает методы записи, вы можете использоватьих также для доступа к полям.

1 голос
/ 08 июля 2010

Выглядит нормально для меня.Для извлечения полей вы можете использовать стандартную функцию Copy.Задайте ему входную строку, индекс первого символа поля и количество символов, и он вернет эту часть в виде новой строки, которую затем можно назначить другой строковой переменной или передать другой функции для дальнейшего преобразованиянапример StrToInt.

0 голосов
/ 08 июля 2010

Я думаю, что ваш подход - очень элегантное решение.

Единственное, что вы не укажете, - как будут работать ваши поля.Поскольку они имеют фиксированную длину, я хотел бы рассмотреть вопрос об их свойствах, чтобы в методе Set свойства вы могли проверить длину.

...