unit uSetUtils;

(*
中村 拓男 さん
集合と文字列の相互変換
http://www.asahi-net.or.jp/~HA3T-NKMR/tips024.htm

modified by moriq:

StrToSet:
例外のメッセージ文字列を変更しました。
- '集合→文字列　変換エラー'
+ 'StrToSet convert error'
オリジナルは誤っています。
  '文字列→集合　変換エラー'
が正しいようです。

SetToStr:
ひとつも要素がなかったときに Result = '[]' ではなく
Result = ']' になってしまうバグを修正。
*)

interface

uses TypInfo;

// 集合→文字列変換
function SetToStr(SetType: PTypeInfo; const SetValue): string;
// 文字列→集合変換
procedure StrToSet(SetType: PTypeInfo; str: string; var SetVar);


implementation

uses Windows, SysUtils;

function SetToStr(SetType: PTypeInfo; const SetValue): string;
var
  pd: PTypeData;
  CompType: PTypeInfo;
  i: Integer;
  pi: PInteger;
begin
  pd := GetTypeData(SetType);
  Assert(pd <> Nil);
  CompType := pd.CompType^;
  Assert(CompType <> Nil);
  pd := GetTypeData(CompType);
  Assert(pd <> Nil);

  pi := @SetValue;

  Result := '';

  for i := pd.MinValue to pd.MaxValue do
    if (pi^ and (1 shl i)) <> 0 then
      if Result = '' then Result := GetEnumName(CompType, i)
                     else Result := Result + ', ' +
                                    GetEnumName(CompType, i);
  Result := '[' + Result + ']';
end;

procedure StrToSet(SetType: PTypeInfo; str: string; var SetVar);
var
  pd: PTypeData;
  CompType: PTypeInfo;
  i: Integer;
  pdw: PDWORD;
  pw: PWORD;
  pb: PByte;
  p: PChar;
  EnumName: string;
  ENumValue: Integer;
begin
  pd := GetTypeData(SetType);
  Assert(pd <> Nil);
  CompType := pd.CompType^;
  Assert(CompType <> Nil);
  pd := GetTypeData(CompType);
  Assert(pd <> Nil);

  pdw := @SetVar;
  pw  := @SetVar;
  pb  := @SetVar;

  case pd.OrdType of
  otSByte, otUByte: pb^ := 0;
  otSWord, otUWord: pw^ := 0;
  otSLong, otULong: pdw^ := 0;
  end;


  p := PChar(Str);

  // '[' と ' ' をスキップ
  while p^ in ['[',' '] do Inc(p);

  // ','  ' ' #0 ']' までを要素名として取り出す
  i := 0;
  while not (p[i] in [',', ' ', #0,']']) do Inc(i);
  SetString(EnumName, p, i);
  // 次の語の先頭までポインタを進める
  while p[i] in [',', ' ',']'] do Inc(i);
  Inc(p, i);


  while EnumName <> '' do
  begin
    EnumValue := GetEnumValue(CompType, EnumName);
    if EnumValue < 0 then
      raise Exception.Create('StrToSet convert error');
    case pd.OrdType of
    otSByte, otUByte: pb^ := pb^ or (1 shl EnumValue);
    otSWord, otUWord: pw^ := pw^ or (1 shl EnumValue);
    otSLong, otULong: pdw^ := pdw^ or (1 shl EnumValue);
    end;
    // ','  ' ' #0 ']' までを要素名として取り出す
    i := 0;
    while not (p[i] in [',', ' ', #0,']']) do Inc(i);
    SetString(EnumName, p, i);
    // 次の語の先頭までポインタを進める
    while p[i] in [',', ' ',']'] do Inc(i);
    Inc(p, i);
  end;
end;

end.
