ここでは APIVCL の 第5章 文字列の操作 に続いて、筆者が日記の自動更新用のプログラムを作る際に必要になった HTML のタグなどを扱うための文字列操作のための関数などを記録しておく。順不同、脈絡なく挙げていき、また、随時追加してゆくことにする。なお、以下の関数では前回作った SearchStrUnit.pas を使うものがある。そのときは明示的に uses に示す。便宜のためにリンクを以下に示しておく。
それでは始めよう。
[特定の文字列で囲まれた部分文字列をとりだす]
List 1
function ExtractStringBetween(const Str,Pre,Post:string):string; var PrePos,PostPos:integer; begin result := 'Error'; PrePos := AnsiPos(Pre,Str); if PrePos = 0 then exit; PostPos := AnsiPos(Post,Copy(Str,PrePos+Length(Pre),Length(Str))); if PostPos = 0 then exit; result := Copy(Str,PrePos+Length(Pre),PostPos-1); end; procedure TForm1.Button1Click(Sender: TObject); var s:string; begin s := ExtractStringBetween(Memo1.Lines[0],'<B>','</B>'); Memo2.Lines.Add(s); s := ExtractStringBetween(Memo1.Lines[1],'ここから','ここまで'); Memo2.Lines.Add(s); s := ExtractStringBetween(Memo1.Lines[2],'$$$','$$$'); Memo2.Lines.Add(s); end;
実行して Button1 を押すと右図のようになる。
[特定の文字列で囲まれた部分文字列を削除する]
List 2
function DeleteStringBetween(const Str,Pre,Post:string):string; var PrePos,PostPos:integer; begin result := 'Error'; PrePos := AnsiPos(Pre,Str); if PrePos = 0 then exit; PostPos := AnsiPos(Post,Copy(Str,PrePos+Length(Pre),Length(Str))); if PostPos = 0 then exit; result := Copy(Str,1,PrePos+Length(Pre)-1)+ Copy(Str,PrePos+Length(Pre)+PostPos-1,Length(Str)); end; procedure TForm1.Button1Click(Sender: TObject); var s:string; begin s := DeleteStringBetween(Memo1.Lines[0],'<B>','</B>'); Memo2.Lines.Add(s); s := DeleteStringBetween(Memo1.Lines[1],'ここから','ここまで'); Memo2.Lines.Add(s); s := DeleteStringBetween(Memo1.Lines[2],'$$$','$$$'); Memo2.Lines.Add(s); end;
今度はまえのものとは反対に取り出すのではなく削除する。タグに挟まれた部分の削除に相当する。
[特定の文字列で囲まれた部分文字列を囲み文字列ごと削除する]
List 3
function DeleteStringBetweenWith(const Str,Pre,Post:string):string; var PrePos,PostPos:integer; begin result := 'Error'; PrePos := AnsiPos(Pre,Str); if PrePos = 0 then exit; PostPos := AnsiPos(Post,Copy(Str,PrePos+Length(Pre),Length(Str))); if PostPos = 0 then exit; result := Copy(Str,1,PrePos-1)+ Copy(Str,PrePos+Length(Pre)+PostPos+Length(Post)-1,Length(Str)); end; procedure TForm1.Button1Click(Sender: TObject); var s:string; begin s := DeleteStringBetweenWith(Memo1.Lines[0],'<B>','</B>'); Memo2.Lines.Add(s); s := DeleteStringBetweenWith(Memo1.Lines[1],'ここから','ここまで'); Memo2.Lines.Add(s); s := DeleteStringBetweenWith(Memo1.Lines[2],'$$$','$$$'); Memo2.Lines.Add(s); end;
こんどは囲んでいる文字列もいっしょに削除する。特定のタグで挟まれた部分をタグごと削除できる。
[特定の文字列で囲まれた部分に文字列を挿入する]
List 4
function InsertStringBetween(const Str,Pre,Post,InsertStr:string):string; var PrePos,InsertPos:integer; begin result := Str; PrePos := AnsiPos(Pre+Post,Str); if PrePos = 0 then exit; InsertPos := PrePos+Length(Pre); Insert(InsertStr,result,InsertPos); end; procedure TForm1.Button1Click(Sender: TObject); var s:string; begin s := InsertStringBetween(Memo1.Lines[0],'<B>','</B>','タグで囲まれた文字列'); Memo2.Lines.Add(s); s := InsertStringBetween(Memo1.Lines[1],'ここから','ここまで','日本語もOK'); Memo2.Lines.Add(s); s := InsertStringBetween(Memo1.Lines[2],'$$$','$$$','pre と post が同じでもOK'); Memo2.Lines.Add(s); end;
これは最初の関数の逆の操作である。
[特定の文字列で囲まれた部分の文字列を入れ換える]
List 5
function ExchangeStringBetween(const Str,Pre,Post,ExchangeStr:string):string; var ret:string; begin result := 'Error'; ret := DeleteStringBetween(Str,Pre,Post); if ret = 'Error' then exit; result := InsertStringBetween(ret,Pre,Post,ExchangeStr); end; procedure TForm1.Button1Click(Sender: TObject); var s:string; begin s := ExchangeStringBetween(Memo1.Lines[0],'<B>','</B>','入れ換えた文字列'); Memo2.Lines.Add(s); s := ExchangeStringBetween(Memo1.Lines[1],'ここから','ここまで','入れ換えた文字列'); Memo2.Lines.Add(s); s := ExchangeStringBetween(Memo1.Lines[2],'$$$','$$$','入れ換えた文字列'); Memo2.Lines.Add(s); end;
これは、上でつくった関数を二つ組み合わせたものである。
[HTML のタグの削除]
List 6
uses SearchStrUnit; function StripTag(const Str:string):string; var Start,Stop:TIntArray; Num,i:integer; begin result := Str; Num := SearchPos(Str,'<',Start); if Num = 0 then exit; if SearchPos(Str,'>',Stop) <> Num then exit; for i := Num downto 1 do Delete(result,Start[i]+1,Stop[i]-Start[i]+1); end; procedure TForm1.Button1Click(Sender: TObject); begin Memo2.Text := StripTag(Memo1.Text); end;
これは SearchPos() を二回つかってタグの範囲を調べ、後ろの方から削除している。
[任意のデリミタによる文字列の分離]
List 7
function IsDelimiter(const Delimiters,c:string):Boolean; begin result := StrScan(PChar(Delimiters),c[1]) <> nil; end; function Split(const Str,Delimiters:string; SL:TStrings):integer; var ss:WideString; i,St:integer; begin SL.Clear; result := 0; if Length(Str) < 1 then exit; ss := Str; St := -1; for i := 1 to Length(ss) do if IsDelimiter(Delimiters,ss[i]) then begin if St <> -1 then begin SL.Add(Copy(ss,St,i-St)); St := -1; end end else if St = -1 then St := i; if St <> -1 then SL.Add(Copy(ss,St,Length(Str))); result := SL.Count; end; procedure TForm1.Button1Click(Sender: TObject); begin Label1.Caption := IntToStr(Split(Edit1.Text,Edit2.Text,Memo1.Lines)); end;
デリミタには、任意の一バイト文字を複数指定できる。論理上、複数のデリミタが連続しているときは一つのデリミタとみなす。ここでは、半角空白をデリミタに加えているので、文字列の前後の空白はデリミタと見なされている。半角空白文字をデータ文字列の一部として取得したいときは、デリミタに加えず、取得した文字列の前後の空白文字は、VCL の Trim() を使って取り除くことができる。この関数は、分離した文字列の数を返す。
[ Split の応用 : 文字列の並べ替えと形式化]
List 8
function MyFormatDateTime(const Source:string):string; var SL:TStringList; Num:integer; begin result := 'Error'; SL := TStringList.Create; Num := Split(Source,': /',SL); if Num < 5 then begin SL.Free; exit; end; result := Format('%4:s年%3:s月%2:s日 %0:s時%1:s分', [SL[0],SL[1],SL[2],SL[3],SL[4]]); SL.Free; end; procedure TForm1.Button1Click(Sender: TObject); begin Edit2.Text := MyFormatDateTime(Edit1.Text); end;
TDateTime 型の日時が得られるときは VCL の FormatDateTime() を使って任意の形式化ができるが、すでに文字列となっている場合は、Split で分解して、組み立て直すことができる。上はその一例である。
[ Split の応用 : CSV ファイルの読み込み]
List 9
procedure TrimAllLines(SL:TStrings); var i:integer; begin if SL.Count < 1 then exit; for i := 0 to SL.Count-1 do SL[i] := Trim(SL[i]); end; procedure TForm1.Button1Click(Sender: TObject); var SL:TStringList; i,j,Num:integer; begin SL := TStringList.Create; try for i := 0 to Memo1.Lines.Count-1 do begin Num := Split(Memo1.Lines[i],Edit1.Text,SL); TrimAllLines(SL); for j := 0 to Num-1 do StringGrid1.Cells[j,i] := SL[j] end; finally SL.Free; end; end;
任意の一バイト文字で区切られた文字列データを読み込むのに Split() を使った例を示した。ここではデリミタに半角空白を指定していないので、半角空白文字だけのデータは空文字データとして生き残る。Trim() をつかって文字列の前後の空白文字は取り除いている。
[ デリミタで区切られた文字列のn番目を取り出す ]
List 10
function IsDelimiter(const Delimiters,c:string):Boolean; begin result := StrScan(PChar(Delimiters),c[1]) <> nil; end; function GetToken(const Str,Delimiters:string; indx:integer):string; var ss:WideString; i,j,St:integer; begin result := 'Error'; if Length(Str) < 1 then exit; ss := Str; St := -1; j := 0; for i := 1 to Length(ss) do if IsDelimiter(Delimiters,ss[i]) then begin if St <> -1 then begin Inc(j); if j = indx then begin result := Copy(ss,St,i-St); exit; end; St := -1; end end else if St = -1 then St := i; if St <> -1 then begin Inc(j); if j = indx then result := Copy(ss,St,Length(Str)); end; end; procedure TForm1.Button1Click(Sender: TObject); begin Edit2.Text := GetToken(Edit1.Text,'/ |',2); end;
これは、Split() と同じ論理で分離したものの最初(indx = 1)から indx 番目の文字列を返す。
以上、とりあえずここまでとするが、以降は随時追加の「予定」です。