TMyWStrings
ウムラウトのようなUnicode文字列をTStringsのようなリストで管理しようというクラス。
目次
ソースコード
今までUnicodeとAnsiStringの可逆変換関数を使ってUnicode文字列をリスト管理してきました。
TStringsのリストに入れるときにはgfnsWideToUtf7を使い、取り出すときにはgfnsUtf7ToWideを使います。
ちょっと煩わしいですし、どっちの関数を使うべきなのかなどという気も使わなければなりませんでした。
ということでいっそクラスにしてしまおうじゃないかと。
目標はTStringListのように単独で使えるだけでなく、TListBoxやTComboBoxなどにも使えるようにすることです。
まずはちょっと実験。
type
TMyStringList = class(TStringList)
public
function Add(const S: WideString): Integer;
end;
本当は
function Add(const S: WideString): Integer; override;
というようにAddメソッドをoverrideしたいのですが、「 'Add' の宣言がすでに定義されているものと異なります」というエラーメッセージが出てしまいコンパイルできません。
ということでoverride指令なしでやると警告は出ますがコンパイルはできます。
type
TMyStringList =
class(TStringList)
public
function Add(
const S:
WideString): Integer;
end;
function TMyStringList.Add(
const S:
WideString): Integer;
begin
Result := TStringList(Self).Add(
gfnsWideToUtf7(S));
end;
WideStringをUTF-7に変換してリストに追加するようにします。
これがTMyStringListのAddメソッドの内容です。
procedure TForm1.Button1Click(Sender: TObject);
var
List: TStringList; //-------------------- (1)
begin
List := TMyStringList.Create; //--------- (2)
try
List.Add('テスト'); //----------------- (3)
TMyStringList(List).Add('テスト'); //-- (4)
myDebug.gpcDebugAdd(List[0]);
myDebug.gpcDebugAdd(List[1]);
finally
List.Free;
end;
end;
Listは(1)でTStringListと宣言されています。
そして(2)でTMyStringListでCreateしています。
なので(3)のList.AddはTMyStringListのAddが呼ばれるのかと思うとそうではありません。TStringListのAddが呼ばれてしまいます。
リストにはUTF-7に変換された文字列ではなく、'テスト'という文字列がそのまま追加されます。
UTF-7に変換された文字列を追加するためにTMyStringListのAddメソッドを呼び出したければ(4)のようにわざわざTMyStringListでキャストしなければなりません。
TMyStringListのAddメソッドがoverrideしてあればどちらの場合でもTMyStringListのAddメソッドが呼ばれるのですが、残念ながら引数の型が違うのでoverrideできません。
結果
1行目はList.Add('テスト')としたもの。
2行目はTMyStringList(List).Add('テスト')としたもの。
この場合AddをWideString版にしただけなので取り出した文字列はUTF-7に変換されたままの文字列であって欲しいわけです。
ところが1行目はそうなっていません。
結局この場合List.AddとしたのではTMyStringListのAddメソッドは呼ばれずTStringListのAddメソッドが直接呼ばれてしまっています。
Addメソッド等をoverrideできないのであればAddWなどというような名前でWideString版のメソッドを実装してしまうという手もあります。InsertWとかIndexOfWなどなど。
けれども、それは実際使うとき(ほんのちょっとだけれども)ややこしいしできればAddやInsertやIndexOfのままで使いたいものだと。
そういうことで、じゃぁもうTStringsを継承するのはやめてしまおうと。同じ名前で(できるだけ)同じ内容のメソッドを実装してしまえばよいではないかという方向で作ったのがmyWStrings.pasです。
TMyWStrings = class(TObject)
private
...
protected
function WideToAnsi(sStr: WideString): AnsiString; virtual;
function AnsiToWide(sStr: AnsiString): WideString; virtual;
public
constructor Create; overload;
constructor Create(slList: TStrings); overload;
function Add(const sWStr: WideString): Integer; virtual;
procedure Insert(iIndex: Integer; sStr: WideString); virtual;
procedure Assign(slList: TStrings); overload; virtual;
procedure Assign(slList: TMyWStrings); overload; virtual;
property Strings[Index: Integer]: WideString read Get write Put; default;
property AnsiStrings: TStrings read F_slList; //生のStrings
end;
全部書き出すと長いのでかいつまんで。
AddやInsertやStringsなどはWideStringを直接受け渡しできます。
AssignはTStringsとTMyWStringsの二つを実装する必要があります。
TMyWStringsはTStringsから派生させていないのでこうなります。
TStrings版のAssignはTStringsから派生したリストをコピーする場合に必要になります。
TMyStrings版のAssignは、TMyWStringListやTMyUStringListなどとの間でリストをコピーするときに変換方式の違いに影響されずにコピーできます。
Assignの他にも一括してリストに追加するAddStrings等も同様にTStrings版とTMyWStrings版の二つを実装します。
AnsiStringsプロパティは変換された文字列(AnsiString型)にアクセスできます。
StringsはWideString型の文字列リストで、AnsiStringsは変換関数を使って変換したAnsiString型の文字列リストになります。
あとは必要なメソッドやプロパティを次々と実装していけばできあがります。
protectedにあるWideToAnsiとAnsiToWideがWideStringとAnsiStringの変換関数になります。
//------------------------------------------------------------------------------
{TMyWStringList}
//WideStringをUTF-7に変換してリストに持つ
type
TMyWStringList = class(TMyWStrings)
protected
function WideToAnsi(sStr: WideString): AnsiString; override;
function AnsiToWide(sStr: AnsiString): WideString; override;
end;
//------------------------------------------------------------------------------
{TMyUStringList}
//WideStringを直接リストに持つ
type
TMyUStringList = class(TMyWStrings)
protected
function WideToAnsi(sWStr: WideString): AnsiString; override;
function AnsiToWide(sStr: AnsiString): WideString; override;
end;
//------------------------------------------------------------------------------
{TMyWideStringList}
//WideStringをオリジナル変換関数で変換してリストに持つ
type
TMyWideStringList = class(TMyWStrings)
protected
function WideToAnsi(sStr: WideString): AnsiString; override;
function AnsiToWide(sStr: AnsiString): WideString; override;
end;
//------------------------------------------------------------------------------
{TMyWStringList}
function TMyWStringList.WideToAnsi(sStr:
WideString): AnsiString;
begin
Result :=
gfnsWideToUtf7(sStr);
end;
function TMyWStringList.AnsiToWide(
const sStr: AnsiString):
WideString;
begin
Result :=
gfnsUtf7ToWide(sStr);
end;
//------------------------------------------------------------------------------
{TMyUStringList}
function TMyUStringList.WideToAnsi(sStr:
WideString): AnsiString;
var
li_Len: Integer;
begin
li_Len := Length(sStr) + 1;
SetLength(Result, li_Len * 2);
lstrcpynw(PWideChar(PAnsiChar(Result)), PWideChar(sStr), li_Len);
end;
function TMyUStringList.AnsiToWide(sStr: AnsiString):
WideString;
var
lp_Src: PAnsiChar;
lp_Wide: PWideChar
absolute lp_Src;
begin
lp_Src := PAnsiChar(sStr);
Result :=
WideString(lp_Wide);
end;
//------------------------------------------------------------------------------
{TMyWideStringList}
function TMyWideStringList.WideToAnsi(sStr:
WideString): AnsiString;
begin
Result :=
gfnsWideToAnsiEx(sWStr);
end;
function TMyWideStringList.AnsiToWide(sStr: AnsiString):
WideString;
begin
Result :=
gfnsAnsiToWideEx(sStr);
end;
WideToAnsiやAnsiToWideはこのように対になる関数を指定します。
Createに二種類あるのはTLisbBoxやTComboBoxのItemなどを間接的にTMyWStringsで管理するためです。
constructor TMyWStrings.Create;
begin
Create(nil);
//↓のCreateを呼んでいる
end;
constructor TMyWStrings.Create(slList: TStrings);
begin
if (slList = nil) then begin
F_slList := TStringList.Create;
end else begin
F_slList := slList;
end;
使い方をみてみます。
var
List1, List2: TMyWStrings;
begin
List1 := TMyWStringList.Create;
List2 := TMyWStringList.Create(ListBox1.Items);
List1はそれ自身でリストを保持します。TMyWStringsのF_slListをTStringListでCreateしてリストを保持しています。
List2はListBox1のItemsにリストを保持します。List2を通してListBox1のItemsを操作する感じです。
List2のようにすればTListBoxやTComboBoxのItemsをTMyWStringsを通してWideStringのやり取りが直接できるので、わざわざ変換関数を使う手間が省けます。
この場合リストへの追加や削除はListBox1.Items.Add(sStr);などとするのではなくList2.Add(sStr);とします。List2を通して間接的にListBox1のItemsを操作するわけです。
ただ、使う前にTMyWStringsで変数を宣言してTMyWStringListでCreateして使い終わったらFreeしないといけないのでその分の手間は増えます。
基本的にTStringsやTStringListのプロパティやメソッドに準拠するように実装しています。
一部省略しているものや逆につけ足しているものもあります。
後ろに「※」印のついてるものがつけ足しであったり少し機能が違うものです。
- CaseSensitive
property CaseSensitive: Boolean;
文字列の比較の際に大文字と小文字を区別するかどうかのフラグ。
IndexOfとEqualsおよび各Sortに影響する。
- Capacity
property Capacity: Integer;
- CharCode ※
property CharCode: TMyCharCode;
LoadFromFileとSaveToFileで読み書きする時の文字コードを指定。
type
TMyCharCode = (
cdAnsi, //Shift-JIS
cdUnicodeLE, //UTF-16 LE (リトルエンディアン) BOMあり
cdUTF_16LE, //UTF-16 LE (リトルエンディアン) BOMなし
cdUnicodeBE, //UTF-16 BE (ビッグエンディアン) BOMあり
cdUTF_16BE, //UTF-16 BE (ビッグエンディアン) BOMなし
cdUTF_8, //UTF-8 BOMあり
cdUTF_8N, //UTF-8 BOMなし
cdUTF_7, //UTF-7
cdJIS, //JIS
cdEUC, //EUC-JP
cdBINDUMP, //バイナリダンプ
cdAuto //自動選択
);
JISはコードページ 50220 の半角カナ不可のもの。半角カナは全角に変換される。
EUCはコードページ 20932 のもの。
cdAutoは読み込みは文字コードを自動で判定。書き込み時はUnicode文字があればUTF-8で、なければShift-JISで書き込む。
- CommaText
property CommaText: WideString;
QuoteCharを二重引用符(")Delimiterをカンマ(,)にしたDelimitedText。
いわゆるCSV形式の文字列。
現状読み出しのみ。
- Count
property Count: Integer;
リストの行数。
読み出し専用。
- Delimiter
property Delimiter: WideString;
DelimitedTextの区切り子。
- DelimitedText
property DelimitedText: WideString;
リストの行を、各行の文字列を引用符(QuoteChar)で囲み区切り子(Delimiter)で繋いで一行の文字列に変換。
引用符で囲まれるのは行中にコントロールコード、半角スペース、引用符、区切り子がある場合でそれらがなければ囲まない。
現状読み出しのみ。
- Duplicates
property Duplicates: TDuplicates;
ソート済みリストに重複文字列を追加しようとしたときの動作。
type
TDuplicates = (
dupIgnore //重複文字列をリストに追加しない
dupAccept //重複文字列の追加を許可する
dupError //重複文字列をリストに追加せずメッセージを出す
);
- NewLine ※
property NewLine: TMyNewLine;
改行コードの指定。
type
TMyNewLine = (
nlCRLF, //CR + LF
nlCR, //CR
nlLF //LF
);
- QuoteChar
property QuoteChar: WideString;
DelimitedTextの引用符。
- Sorted
property Sorted: Boolean;
リストの文字列を自動的に並べ替えるかどうかの指定。
SortedをTrueにするとその時点でソートが行われる。
DuplicatesプロパティがdupAcceptだった場合重複文字列は削除されることに注意。
- Text
property Text: WideString;
読み込みはリストの各行をNewLineで指定した改行コードで連結した文字列にして返す。
書き込みは文字列をNewLineで指定した改行コードで分割してリストにセットする。
- TextEx
property TextEx: WideString;
Textの改行をDelimiterにしたもの。
読み込みはリストの各行をDelimiterで連結した文字列にして返す。
書き込みは文字列をDelimiterで分割してリストにセットする。
- AnsiStrings ※
property AnsiStrings: TStrings;
UnicodeをAnsiStringに変換した文字列。
Shift-JISで表せないUnicodeな文字は代替文字に変わるか'?'に変わってしまう。
- AnsiExStrings ※
property AnsiExStrings: TStrings;
Unicodeを自作関数gfnsWideToAnsiExを使ってAnsiStringに変換した文字列。
Shift-JISで表せない文字だけを $xxxx という'$'に続けてUnicodeのコード番号を4桁の16進数値に変換してAnsiStringに持たせる形式にした文字列。
- Utf7Strings ※
property Utf7Strings: TStrings;
UnicodeをUTF-7に変換した文字列。
- Utf8Strings ※
property Utf8Strings: TStrings;
UnicodeをUTF-8に変換した文字列。
- Names
property Names[iIndex: Integer]: WideString;
読み出しのみ。
- Objects
property Objects[iIndex: Integer]: TObject;
- Strings
property Strings[Index: Integer]: WideString; default;
Unicode対応の文字列。
- Values
property Values[sName: WideString]: WideString;
- OnAdd ※
property OnAdd: TMyWStringsOnAdd;
type
TMyWStringsOnAdd = procedure(Sender: TObject; iCount: Integer; iLength: Int64) of object;
- Create
constructor Create;
constructor Create(slList: TStrings); ※
通常は最初の構文を。
ListBoxやStringGridのTStringsを間接的に扱いたいときは二番目の構文を。
- Destroy
destructor Destroy;
- Attach ※
procedure Attach(slList: TStrings);
間接的に扱うTStringsの付け替え。
- Detach ※
procedure Detach;
間接的に扱うTStringsのとりやめ。
- Add
function Add(sStr: WideString): Integer;
リストに文字列を追加し、追加された文字列の0ベースのインデックスを返す。
- AddFile ※
procedure AddFile(sFile: WideString);
procedure AddFile(slList: TMyWStrings);
sFileをテキストファイルとして読み込んでリストに追加。
2番目の構文はslListの内容を複数のファイルとして扱いそれぞれの内容をすべて追加する。
- AddObject
function AddObject(S: WideString; AObject: TObject): Integer;
AddのObjectの紐付き版。
Sという文字列が追加されると同時にAObjectも追加され、そのインデックスを返す。
- AddName
function AddName(sName, sValue: WideString): Integer;
sName=sValue という形式でリストに追加。
INIファイル向け。
- AddStrings
procedure AddStrings(slList: TStrings);
procedure AddStrings(slList: TMyWStrings);
procedure AddStrings(iCount: Integer); ※
リストにslListの内容を一気に追加。
3番目の構文はiCountの空行を追加する。
- AddText ※
procedure AddText(sText: Widestring);
sTextを改行で分割してリストに追加する。
- Append
procedure Append(sStr: WideString);
リストに文字列を追加する。
Addより若干効率は良い。
- Assign
procedure Assign(slList: TStrings);
procedure Assign(slList: TMyWStrings);
リストの内容をslListに置き換える(リストのコピー)
- Clear
procedure Clear;
リストをクリアする。
- Delete
procedure Delete(iIndex: Integer);
リストのiIndex番目を削除する。
iIndex番目以降は一つずつ前にずれる。
- IndexOf
function IndexOf(sStr: WideString): Integer;
function IndexOf(sStr: WideString; iStart: Integer): Integer; ※
リスト内でsStrに一致する文字列の0ベースのインデックスを返す。
二番目の構文は開始インデックス指定版で、iStart番目以降(iStartも含む)の文字列と比較した結果を返す。
一致する文字列がなければ-1を返す。
CaseSensitiveプロパティの影響を受ける。
- IndexOfName
function IndexOfName(sName: WideString): Integer;
function IndexOfName(sName: WideString; iIndex: Integer): Integer;
「名前 = 値」という形式の項目のうち名前がsNameに等しい(大文字小文字は無視)最初のインデックスを返す。
INIファイル向け。
CaseSensitiveプロパティの影響は受けない。
- Insert
procedure Insert(iIndex: Integer; sStr: WideString);
リストのiIndex番目に文字列を挿入する。
iIndex番目以降は一つずつ後ろにずれる。
- InsertStrings ※
procedure InsertStrings(iIndex: Integer; slList: TStrings);
procedure InsertStrings(iIndex: Integer; slList: TMyWStrings);
リストのiIndex番目にslListの内容を一気挿入。
- InStringOf ※
function InStringOf(sStr: WideString): Integer;
function InStringOf(sStr: WideString; iStart: Integer): Integer;
リスト内でsStrを含む文字列の0ベースのインデックスを返す。
IndexOfとの違いは、文字列の一部にsStrがあれば一致するとみなす。
InHeadStringOfとの違いは、文字列の行頭だけでなくどこにsStrがあっても一致するとみなす。
二番目の構文は開始インデックス指定版で、iStart番目以降(iStartも含む)の文字列と比較した結果を返す。
sStrを含む文字列がなければ-1を返す。
CaseSensitiveプロパティの影響を受ける。
- InHeadStringOf ※
function InHeadStringOf(sStr: WideString): Integer;
function InHeadStringOf(sStr: WideString; iStart: Integer): Integer;
リスト内でsStrが行頭にある文字列の0ベースのインデックスを返す。
IndexOfとの違いは、文字列の一部にsStrがあれば一致するとみなす。
InStringOfとの違いは、sStrが行頭になければ一致するとはみなさい。
二番目の構文は開始インデックス指定版で、iStart番目以降(iStartも含む)の文字列と比較した結果を返す。
行頭にsStrを含む文字列がなければ-1を返す。
CaseSensitiveプロパティの影響を受ける。
- Equals
function Equals(slList: TMyWStrings): Boolean;
slListの内容と同じかどうかを返す。
CaseSensitiveプロパティの影響を受ける。
- Exchange
procedure Exchange(iIndex1, iIndex2: Integer);
リストの文字列の入れ替え。
- Find
function Find(sStr: WideString; var iIndex: Integer): Boolean;
- Move
procedure Move(iCurrentIndex, iNewIndex: Integer);
リストの文字列の移動。
iCurrentIndexの文字列をiNewIndexに移動する。
Exchangeと違い入れ替るのではなくiCurrentIndexの文字列を削除してからiNewIndexに挿入するようなもの。
- CustomSort
procedure CustomSort(fniCompare: TMyWStringsSortCompare);
TMyWStringsSortCompare型の比較関数を指定することで独自の並べ替えを行える。
TMyWStringsSortCompare = function(slList: TMyWStrings; iIndex1, iIndex2: Integer): Integer;
- FileSort ※
procedure FileSort;;
procedure FileSort(bUnique: Boolean);
procedure FileInsertionSort;
procedure FileInsertionSort(bUnique: Boolean);
ファイル用のソート。
エクスプローラの並び順に近いソート。
- QuickSort ※
procedure QuickSort(fniCompare: TMyWStringsSortCompare);
procedure QuickSort(fniCompare: TMyWStringsSortCompare; bUnique: Boolean);
クイックソート。
二番目の構文は重複する文字列を許可するかどうかを指定する。
bUniqueがTrueなら重複を許可しない。
- InsertionSort ※
procedure InsertionSort(fniCompare: TMyWStringsSortCompare);
procedure InsertionSort(fniCompare: TMyWStringsSortCompare; const bUnique: Boolean);
挿入ソート。
ソート済みのリストであればクイックソートよりは速い。
二番目の構文は重複する文字列を許可するかどうかを指定する。
bUniqueがTrueなら重複を許可しない。
- Reverse ※
procedure Reverse;
リストを逆に並び替える。
Sortした直後にこのReverseを呼べばリストは降順となる。
- Sort
procedure Sort;
リストの並べ替え。
マージソートで昇順で並べ替える。
CaseSensitiveの影響を受ける。
- LoadFromFile ※
procedure LoadFromFile(sFile: WideString);
procedure LoadFromFile(sFile: WideString; var cdCode: TMyCharCode);
procedure LoadFromFile(sFile: WideString; var cdCode: TMyCharCode; iByte: DWORD);
リストをファイルから読み込む。
最初の構文はファイルをShift-JISとして読み込む。
二番目の構文はBOMのないファイルをどのフォーマットで読み込むか指定。
cdAnsi Shift-JISとして読み込む。
cdUTF_16LE リトルエンディアンのUTF-16として読み込む。
cdUTF_16BE ビッグエンディアンのUTF-16として読み込む。
cdUTF_8N UTF-8として読み込む。
cdUTF_7 UTF-7として読み込む。
どちらの構文もファイルにBOMがあればBOMの値にしたがった文字コードで読み込む。
一番目の構文でも、二番目の構文でcdCodeを指定していてもファイルにBOMがあればcdCodeは無視されるということ。
- SaveToFile ※
procedure SaveToFile(sFile: WideString);
procedure SaveToFile(sFile: WideString; cdCode: TMyCharCode); ※
リストをファイルに保存。
最初の構文はファイルをUTF-8で書き込む。
二番目の構文はどの文字コードで書き込むか指定。
cdAnsiの場合ウムラウトのようなUnicode文字があれば代替文字に変わってしまう。
cdAnsi Shift-JISで書き込み。
cdUnicodeLE BOMありのリトルエンディアンのUTF-16で書き込み。
cdUnicodeBE BOMありのビッグエンディアンのUTF-16で書き込み。
cdUTF_16LE BOMなしのリトルエンディアンのUTF-16で書き込み。
cdUTF_16BE BOMなしのビッグエンディアンのUTF-16で書き込み。
cdUTF_8 BOMありのUTF-8で書き込み。
cdUTF_8N BOMなしのUTF-8で書き込み。
cdUTF_7 UTF-7で書き込み。
cdAuto リストにUnicode文字があればUTF-8で、なければShift-JISで書き込み。
- BeginUpdate
procedure BeginUpdate;
- EndUpdate
procedure EndUpdate;
2009-02-01:
SaveToFileの文字コード指定にcdAuto追加。
InStringOf, InHeadStringOfメソッド追加。
2008-07-16:
プロパティ、メソッドの一覧を追記。
2008-06-13: