3

アプリケーションを開発しており、CSV ファイルから DB テーブルにデータをアップロードする必要があります。問題は、CSV ファイルはありませんが、CSV に変換するフラット テキスト ファイルがあることです。追加の問題は、アプリケーションが異なるシステムを使用する複数の顧客によって使用されているため、異なるレイアウトの異なるフラット テキスト ファイルを持っていることです。

私が達成したいことは、特別なファイルから「ルール」をロードするアプリケーションを作成することです。これらのルールはフラット テキスト ファイルで処理され、CSV ファイルが生成されます。フラット ファイルから CSV に変換するアプリケーションは同じで、一連のルールが異なるだけです。

どうすればこれを達成できますか?あなたが推奨するベストプラクティスは何ですか?

4

2 に答える 2

7

ルールの複雑さによって異なります。唯一の変化する入力が列の名前と使用されるセパレーターである場合、それは非常に簡単ですが、完全に異なる形式 (XML など) も解析できるようにしたい場合は、別の話です。

私自身は、ファイルからレコードを読み取り、データセットまたは CSV に出力する「レコード」リーダーの基本クラスを実装することを選択します。次に、さまざまなソース形式の読み取りを実装する子クラスを実装できます。

必要に応じて、これらの形式に特定のルールを追加して、BaseReader から派生した汎用 XMLReader を作成できますが、これにより、構成可能な列名が可能になります。しかし、あなたが遭遇するかもしれないそれらのフォーマットのどの方言がより明確になるまで、私はあなたが得たフォーマットのハードコードされたリーダーの束から始めます.

編集:リクエストに応じて、どのように見えるかの例。

この例は理想とはかけ離れていることに注意してください。カスタム形式を読み取り、それを 1 つの特定のテーブル構造に転送して、それを CSV ファイルとして保存します。別のテーブル構造にコードを再利用できるように、もう少し分割することもできます。特にフィールド定義は、子孫クラスまたはファクトリ クラスで設定できるようにしたい場合があります。しかし、単純にするために、私はより厳密なアプローチを採用し、1 つの基底クラスに多すぎるインテリジェンスを配置しました。

基本クラスには、メモリ内データセットを作成するために必要なロジックがあります (私は TClientDataSet を使用しました)。ファイルを「移行」できます。実際には、これはファイルを読み取り、検証し、エクスポートすることを意味します。

読み取りは抽象的であり、子クラスで実装する必要があります。インメモリ データセットにデータを読み取る必要があります。これにより、クライアント データセットで必要なすべての検証を行うことができます。これにより、データベース/ファイル形式に依存しない方法で、必要に応じてフィールドの型とサイズを強制し、追加のチェックを行うことができます。

検証と書き込みは、データセット内のデータを使用して行われます。ソース ファイルがデータセットに解析された瞬間から、ソース ファイル形式に関する知識はもう必要ありません。

宣言: を使用することを忘れないでくださいDB, DBClient

type
  TBaseMigrator = class
  private
    FData: TClientDataset;
  protected
    function CSVEscape(Str: string): string;
    procedure ReadFile(AFileName: string); virtual; abstract;
    procedure ValidateData;
    procedure SaveData(AFileName: string);
  public
    constructor Create; virtual;
    destructor Destroy; override;

    procedure MigrateFile(ASourceFileName, ADestFileName: string); virtual;
  end;

実装:

{ TBaseReader }

constructor TBaseMigrator.Create;
begin
  inherited Create;
  FData := TClientDataSet.Create(nil);
  FData.FieldDefs.Add('ID', ftString, 20, True);
  FData.FieldDefs.Add('Name', ftString, 60, True);
  FData.FieldDefs.Add('Phone', ftString, 15, False);
  // Etc
end;

function TBaseMigrator.CSVEscape(Str: string): string;
begin
  // Escape the string to a CSV-safe format;
  // Todo: Check if this is sufficient!
  Result := '"' + StringReplace(Result, '"', '""', [rfReplaceAll]) + '"';
end;

destructor TBaseMigrator.Destroy;
begin
  FData.Free;
  inherited;
end;

procedure TBaseMigrator.MigrateFile(ASourceFileName, ADestFileName: string);
begin
  // Read the file. Descendant classes need to override this method.
  ReadFile(ASourceFileName);

  // Validation. Implemented in base class.
  ValidateData;

  // Saving/exporting. For now implemented in base class.
  SaveData(ADestFileName);
end;

procedure TBaseMigrator.SaveData(AFileName: string);
var
  Output: TFileStream;
  Writer: TStreamWriter;
  FieldIndex: Integer;
begin
  Output := TFileStream.Create(AFileName,fmCreate);
  Writer := TStreamWriter.Create(Output);
  try

    // Write the CSV headers based on the fields in the dataset
    for FieldIndex := 0 to FData.FieldCount - 1 do
    begin
      if FieldIndex > 0 then
        Writer.Write(',');
      // Column headers are escaped, but this may not be needed, since
      // they likely don't contain quotes, commas or line breaks.
      Writer.Write(CSVEscape(FData.Fields[FieldIndex].FieldName));
    end;
    Writer.WriteLine;

    // Write each row
    FData.First;
    while not FData.Eof do
    begin

      for FieldIndex := 0 to FData.FieldCount - 1 do
      begin
        if FieldIndex > 0 then
          Writer.Write(',');
        // Escape each value
        Writer.Write(CSVEscape(FData.Fields[FieldIndex].AsString));
      end;
      Writer.WriteLine;

      FData.Next
    end;

  finally
    Writer.Free;
    Output.Free;
  end;
end;

procedure TBaseMigrator.ValidateData;
begin
  FData.First;
  while not FData.Eof do
  begin
    // Validate the current row of FData
    FData.Next
  end;
end;

子クラスの例: TIniFileReader は、inifile セクションをデータベース レコードであるかのように読み取ります。ご覧のとおり、ファイルを読み取るためのロジックを実装するだけで済みます。

type
  TIniFileReader = class(TBaseMigrator)
  public
    procedure ReadFile(AFileName: string); override;
  end;

{ TIniFileReader }

procedure TIniFileReader.ReadFile(AFileName: string);
var
  Source: TMemIniFile;
  IDs: TStringList;
  ID: string;
  i: Integer;
begin
  // Initialize an in-memory dataset.
  FData.Close; // Be able to migrate multiple files with one instance.
  FData.CreateDataSet;

  // Parsing a weird custom format, where each section in an inifile is a
  // row. Section name is the key, section contains the other fields.
  Source := TMemIniFile.Create(AFileName);
  IDs := TStringList.Create;
  try
    Source.ReadSections(IDs);

    for i := 0 to IDs.Count - 1 do
    begin
      // The section name is the key/ID.
      ID := IDs[i];

      // Append a row.
      FData.Append;

      // Read the values.
      FData['ID'] := ID;
      FData['Name'] := Source.ReadString(ID, 'Name', '');
      // Names don't need to match. The field 'telephone' in this propriety
      // format maps to 'phone' in your CSV output.
      // Later, you can make this customizable (configurable) if you need to,
      // but it's unlikely that you encounter two different inifile-based
      // formats, so it's a waste to implement that until you need it.
      FData['Phone'] := Source.ReadString(ID, 'Telephone', '');

      FData.Post;
    end;

  finally
    IDs.Free;
    Source.Free;
  end;
end;
于 2012-09-17T14:05:47.300 に答える
0

これは、「スクリーン スクレーパー」が直面する問題とよく似ています。エンド ユーザーがこれを使用できるようにする場合は、(必要に応じて内部実装の詳細を除いて) 正規表現を使用せず、生の正規表現編集をエンド ユーザーに公開しません。

代わりに、データ ファイルのサンプルを読み込んで、ドラッグ アンド ドロップ スタイルでルールを視覚的に構築できるようにしました。

  1. 「テキストの一致」ボタンをクリックし、クリックしてドラッグし、画面上の長方形の領域を選択します。フォーマットが正確でない場合や再現性がない場合に、一定量だけ上下左右に移動できるようにするためのオプションがあります。元の箱の外に出られる限界を設定します。

  2. 「グラブ テキスト」ボタンをクリックし、クリックして画面上の長方形または非長方形 (フロー) 領域にドラッグします。フィールドで出力に名前を付け、タイプ (整数、文字列[x] など) を指定します。ステップ 1 と同様の制限が適用されます。

  3. [保存] をクリックすると、テンプレート ルールがディスクに書き込まれます。別のファイルをロードして、ルールが適切に適用されるかどうかを確認します。

関連するウィキペディアのトピック。

于 2012-09-17T18:05:59.153 に答える