TStringListのName=Value形式を使って一時的に文字列を保持する
103 StringListNameValue 動作確認 Delphi2007 更新日 2010/07/04(日)

TStringListは項目に[Name=Value]の形式、
つまり[タイトル=値]の形式で文字列を格納する
独自の機能を持ちます。

値の取得、値の登録(上書き/追加)
タイトルのIndexを取得、Indexによるタイトルを取得
の機能があり、それぞれ動作確認してみました。

────────────────────
procedure VariantCheck(A, B: Variant);
begin
 if not(A = B) then
 begin
   Assert(False,
     'not equal' + #13#10 +
     'A=' + String(A) + #13#10 +
     'B=' + String(B));
 end;
end;

procedure Check(A, B: String); overload;
begin
 VariantCheck(A, B);
end;

procedure Check(A, B: Integer); overload;
begin
 VariantCheck(A, B);
end;

procedure TForm24.Button3Click(Sender: TObject);
var
  StringList1: TStringList;
begin
  StringList1 := TStringList.Create; try

  StringList1.Clear;
  StringList1.Add('タイトル1=値1');
  StringList1.Add('タイトル2=値2');
  StringList1.Add('タイトル3=値3');
  StringList1.Add('タイトル4=');    //空文字を登録した特殊項目
  StringList1.Add('タイトル5');     //Name=Value形式じゃない項目

  //・値を取得する
  Check('値1',  StringList1.Values['タイトル1']);
  Check('値2',  StringList1.Values['タイトル2']);
  Check('',     StringList1.Values['タイトル4']);
  Check('',     StringList1.Values['タイトル5']);
  //※タイトル4はName=空文字形式なので空文字
  //  タイトル5はName=Value形式ではないので空文字が返ります

  //・値を上書き登録する
  StringList1.Values['タイトル1'] := '値A';
  Check('値A', StringList1.Values['タイトル1']);
  Check('タイトル1=値A', StringList1.Strings[0]);
  //※該当タイトルの行が更新されます。

  //・値を追加登録する
  StringList1.Values['タイトル6'] := '値6';
  Check('値6', StringList1.Values['タイトル6']);
  Check('タイトル6=値6', StringList1.Strings[5]);
  //※新しいタイトルの場合は最下行に行が追加されます

  //・空文字の値を追加登録してみようとする。
  Check(6, StringList1.Count);
  StringList1.Values['タイトル7'] := '';
  Check('', StringList1.Values['タイトル7']);
  Check(6, StringList1.Count);
  //※Valueが空文字の場合は追加されません。

  //・空文字の値を上書き登録してみようとする。
  StringList1.Values['タイトル2'] := '';
  Check('', StringList1.Values['タイトル2']);
  Check(5, StringList1.Count);
  //※空文字を代入するとName=Valueの行全体が削除される

  //・タイトルからIndexを求める
  Check(0,      StringList1.IndexOfName('タイトル1'));
  Check(-1,     StringList1.IndexOfName('タイトル2'));
  Check(1,      StringList1.IndexOfName('タイトル3'));
  Check(2,      StringList1.IndexOfName('タイトル4'));
  Check(-1,     StringList1.IndexOfName('タイトル5'));
  Check(3,      StringList1.IndexOf    ('タイトル5'));
  Check(4,      StringList1.IndexOfName('タイトル6'));
  //※タイトル4はName=空文字形式だがIndexOfNameは有効
  //  タイトル5はName=Value形式ではないので無視されます


  //・値を追加登録する
  StringList1.Values['タイトル5'] := '値5';
  Check('値5', StringList1.Values['タイトル5']);
  Check('タイトル5',      StringList1.Strings[3]);
  Check('タイトル5=値5',  StringList1.Strings[5]);
  //※タイトル5の場合は、Name=Value形式ではないので
  //  新しいタイトルとみなされて行が追加されます

  //Indexによってタイトルを取得する
  Check('タイトル1',  StringList1.Names[0]);
  Check('タイトル3',  StringList1.Names[1]);
  Check('タイトル4',  StringList1.Names[2]);
  Check('',           StringList1.Names[3]);
  Check('タイトル6',  StringList1.Names[4]);
  Check('タイトル5',  StringList1.Names[5]);
  //※タイトル4はName=空文字形式だがNames[]は有効
  //  タイトル5はName=Value形式ではないので空文字が返ります

  Check('タイトル1=値A', StringList1[0]);
  Check('タイトル3=値3', StringList1[1]);
  Check('タイトル4=',    StringList1[2]);
  Check('タイトル5',     StringList1[3]);
  Check('タイトル6=値6', StringList1[4]);
  Check('タイトル5=値5', StringList1[5]);

  //空文字を取得しようとする
  Check(6, StringList1.Count);
  Check('',     StringList1.Values['']);
  Check(-1,     StringList1.IndexOfName(''));
  //※空文字が取得される

  StringList1.Add('');              //空行を追加
  StringList1.Add('タイトル7=値7');
  Check(8, StringList1.Count);

  //空行を検索
  Check('',     StringList1.Values['']);
  Check(-1,     StringList1.IndexOfName(''));
  Check('',     StringList1.Names[6]);
  Check('タイトル7',     StringList1.Names[7]);
  //※空行の検索はできない。

  //大小文字が異なる場合の動作
  StringList1.Values['titleA'] := 'ValueA';
  Check('ValueA', StringList1.Values['TITLea']);
  //※タイトルの大小文字が異なっていても値は取得できる

  finally StringList1.Free; end;
end;
────────────────────
Namesは読み取り専用プロパティなので、
Namesに代入してタイトルを変更することはできません。

テストコードを各仕様の詳細を知るために利用してください。
    Name=Valueではない単なる行の形式や
    Name=空文字の形式
    Value/IndexOfNameに空文字を代入した場合の動作、
    Namesで空行を指定した場合の動作、
	タイトルに大文字小文字が異なる値を使った場合の動作など
は動作確認しないとなかなかわかりにくい仕様です。

ともかく、StringListには、このような機能があるので、
名前と値を関連づけて管理することが容易になっています。

これを使って
一時的に文字列と値を保持するリストを作ってみましょう。

次のユニットを使うことで
    SetNameValue('タイトル1', '値1');
としておいて値を保持する事ができ、
    GetNameValue('タイトル1');
とすることで保持した値を取り出すことができます。

────────────────────
unit NameValue;

interface

uses
  Classes;

type TGetNameValueFlag = (gfRemove, gfNoRemove);

procedure SetNameValue(Title, Value: String);
function GetNameValue(Title: String; Flag: TGetNameValueFlag = gfRemove): String;

//テスト用
//function NameValueList: TStringList;

var uNameValueList: TStringList;

implementation

//var uNameValueList: TStringList;

function NameValueList: TStringList;
begin
  if not Assigned(uNameValueList) then
    uNameValueList := TStringList.Create;
  Result := uNameValueList;
end;

procedure SetNameValue(Title, Value: String);
begin
  if (Title = '') then
    raise Exception.Create('EmptyStr Error: SetNameValue');

  NameValueList.Values[Title] := Value;
end;

function GetNameValue(Title: String; Flag: TGetNameValueFlag = gfRemove): String;
var
  Index: Integer;
begin
  if (Title = '') then
    raise Exception.Create('EmptyStr Error: GetNameValue');

  Result := NameValueList.Values[Title];

  if Flag = gfRemove then
  begin
    Index := NameValueList.IndexOfName(Title);
    if Index <> -1 then
      NameValueList.Delete(Index);
  end;
end;

initialization
  uNameValueList := nil;

finalization
  if Assigned(uNameValueList) then
    uNameValueList.Free;
end.

{----------------------------------------
//テストコード

procedure testNameValue;
begin
  //いきなりGetする
  Check('', GetNameValue('test'));
  FreeAndNil(uNameValueList);

  //いきなりCountを求める
  Check(0, NameValueList.Count);
  FreeAndNil(uNameValueList);

  //値を取得
  SetNameValue('test1', 'A');
  SetNameValue('test2', 'B');
  Check(2, NameValueList.Count);
  Check('A', GetNameValue('test1'));

  //取得後Countが減っている事を確認
  Check(1, NameValueList.Count);

  //タイトルに無いものを取得した場合
  Check('', GetNameValue('test3'));
  Check(1, NameValueList.Count);
  //空文字が返り、Countの増減はない

  //空文字をValueとして登録する
  SetNameValue('test3', '');
  Check(1, NameValueList.Count);
  //Countは増えない

  SetNameValue('test1', 'A');
  SetNameValue('test2', 'B');
  SetNameValue('test3', 'C');
  Check(3, NameValueList.Count);

  //値を書き換える
  SetNameValue('test2', 'd');
  Check(3, NameValueList.Count);
  Check('d', GetNameValue('test2'));
  Check(2, NameValueList.Count);
  //カウントに変化はなく、値は変更されている

  //値を削除せずに取得する
  Check('A', GetNameValue('test1', gfNoRemove));
  Check(2, NameValueList.Count);

  //大文字でも取得できる
  Check('C', GetNameValue('TEST3', gfNoRemove));

  //空文字を代入する
  Check(2, NameValueList.Count);
  SetNameValue('TEST1', '');
  Check(1, NameValueList.Count);
  //登録が削除される

end;

//----------------------------------------}
────────────────────

GetNameValueにgfNoRemoveの引数を指定しなければ、
取り出した値はすぐに削除されます。

    SetNameValue('タイトル1', '');
このようにすると、値を削除することもできます。

テストコードを参考にしてください。


NameValueListというTStringListのSingletonオブジェクトを
生成して使っていますが、
関数呼び出しする側の利用者にはわからないようになっています。


参考────────────────────
Delphi Tips - TStrings オブジェクトの使用方法
http://support.codegear.com/article/35959/
    Strings を使用した関連する値の格納

Singleton オブジェクトの作り方
http://delfusa.main.jp/delfusafloor2/technic/technic/094_Singleton.html