UTF-8文字からUTF-16LEに変換する関数の実装 ~Delphiソースコード集
UTF-8文字列のバイト配列からDelphiのString型(UTF-16LE)に変換するには、
System.SysUtilsユニットのTEncoding.UTF8.GetString(バイト配列)関数を使えば可能ですが、
バイト配列に不正な値が1ビットでも存在すると、一切変換してくれません。
変換可能な個所だけでも変換した値がほしいので自作します。
Unicodeについて
ユニコードは「U+10FFFF」のように表現される(21ビット)最初の5ビットが「面」(0~16)、次の8ビットが「句」(0~255)、次の8ビットが「点」(0~255)と言われているそうです。
UMamUnicode.pasファイル
unit UMamUnicode; interface uses System.SysUtils; function MamU8ToU16(b:TBytes):TBytes; //UTF8→UTF16LEへの変換関数 function MamU32ToU16(b:TBytes):TBytes; //Unicode→UTF16LEへの変換関数 function MamU8ToU32(b:TBytes):TBytes; //UTF8→Unicodeへの変換関数 implementation function MamU8ToU16(b:TBytes):TBytes; var b1:TBytes; begin b1:=MamU8ToU32(b); result:=MamU32ToU16(b1); end; function MamU32ToU16(b:TBytes):TBytes; var i,bl,rl:integer; begin //「𠮷」UTF32(UNICODE)でU+20BB7 の場合 //b[0]=$02,b[1]=$0B,b[2]=$B7 //b[0]が1以上だとサロゲートペアになる //サロゲートペアの場合、 // ①b[0]:=b[0]-1;を行う // ② // b[0] b[1] b[2] // 0001 0000 1011 1011 0111 // ~~~~ ~~~~ ~~ LEなので //w1= 1101 10 0001 0000 10 ↓ ↓ = D842 → 42 D8 //w2= 1101 11 11 1011 0111 = DFB7 → B7 DF rl:=0; SetLength(result,rl); bl:=Length(b); i:=0; while (i+2)<bl do begin if b[i]<=0 then begin //サロゲートペアではない inc(rl,2); setLength(result,rl); result[rl-1]:=b[i+1]; result[rl-2]:=b[i+2]; end else begin //サロゲートペアの場合 b[i]:=b[i]-1; inc(rl,2); setLength(result,rl); result[rl-1]:=$D8 or ((b[i] and $0C) shr 2); result[rl-2]:=((b[i] and $03) shl 6) or ((b[i+1] and $FC) shr 2); inc(rl,2); setLength(result,rl); result[rl-1]:=$DC or (b[i+1] and $03); result[rl-2]:=b[i+2]; end; inc(i,3); end; end; function MamU8ToU32(b:TBytes):TBytes; var i:integer; bl,rl:Integer; begin //例えば「𠮷」はUTF8でF0A0AEB7 でバイト型配列で届く //b[0]=$F0,b[1]=$A0,b[2]=$AE,b[3]=$B7 //この関数は「𠮷」をUNICODEでU+20BB7 として3バイトで値を返すものとする //result[0]=$02,result[1]=$0B,result[2]=$B7 //ユニコードの各21ビットを //wwwww xxxxxyyy yzzzzzzz //とする場合 //ユニコード UTF-8 // 1Byte目 2Byte目 3Byte目 4Byte目 // 0~ 7F 0zzzzzzz 1バイト型UTF8 // 80~ 7FF 110yyyyz 10zzzzzz 2バイト型UTF8 // 800~ FFFF 1110xxxx 10xyyyyz 10zzzzzz 3バイト型UTF8 //10000~10FFFF 11110www 10wwxxxx 10xyyyyz 10zzzzzz 4バイト型UTF8 // 赤字の個所の値 がすべて 0 は不正な文字として扱う bl:=Length(b); rl:=0; SetLength(result,rl); i:=0; while i<bl do begin if b[i]<$80 then begin //1Byte inc(rl,3); SetLength(result,rl); result[rl-1]:=b[i]; result[rl-2]:=0; result[rl-3]:=0; inc(i); end else if (b[i]>=$C2) and (b[i]<$E0) then begin //2Byte if ((i+1)<bl) and (b[i+1]>=$80) and (b[i+1]<$C0) and ((b[0] and $1E)<>0) then begin inc(rl,3); SetLength(result,rl); result[rl-1]:=(b[i+1] and $3F) OR ((b[i] and $03) shl 6); result[rl-2]:=(b[i] and $1C) shr 2; result[rl-3]:=0; inc(i,2); end else begin //不正な値なのでスキップする inc(i); end; end else if (b[i]>=$E0) and (b[i]<$F0) then begin //3Byte if ((i+2)<bl) and (b[i+1]>=$80) and (b[i+1]<$C0) and (b[i+2]>=$80) and (b[i+2]<$C0) and (((b[i] and $0F)<>0) or ((b[i+1] and $20)<>0)) then begin inc(rl,3); SetLength(result,rl); result[rl-1]:=(b[i+2] and $3F) OR ((b[i+1] and $03) shl 6); result[rl-2]:=((b[i+1] and $3C) shr 2) + ((b[i] and $0F) shl 4); result[rl-3]:=0; inc(i,3); end else begin //不正な値なのでスキップする inc(i); end; end else if (b[i]>=$F0) and (b[i]<$F8) then begin //4Byte if ((i+3)<bl) and (b[i+1]>=$80) and (b[i+1]<$C0) and (b[i+2]>=$80) and (b[i+2]<$C0) and (b[i+3]>=$80) and (b[i+3]<$C0) and (((b[i] and $07)<>0) or ((b[i+1] and $30)<>0)) then begin inc(rl,3); SetLength(result,rl); result[rl-1]:=(b[i+3] and $3F) OR ((b[i+2] and $03) shl 6); result[rl-2]:=((b[i+2] and $3C) shr 2) + ((b[i+1] and $0F) shl 4); result[rl-3]:=((b[i+1] and $30) shr 4) + ((b[i] and $07) shl 4); inc(i,4); end else begin //不正な値なのでスキップする inc(i); end; end else begin //不正な値なのでスキップする inc(i); end; end; end; end.
使用例
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Net.URLClient, System.Net.HttpClient, System.Net.HttpClientComponent, UMamUnicode; type TForm1 = class(TForm) Button1: TButton; Memo1: TMemo; NetHTTPClient1: TNetHTTPClient; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var b:TBytes; http:TNetHTTPClient; res:IHTTPResponse; begin http:=TNetHTTPClient.Create(nil); try res:=http.Get('https://mam-mam.net/'); SetLength(b,res.ContentStream.Size); res.ContentStream.Position:=0; res.ContentStream.Read(b,Length(b)); b:=MamU8ToU16(b); //UTF8のバイト型配列 ⇒ UTF16LEバイト型配列 Memo1.Lines.Add( TEncoding.Unicode.GetString(b) //UTF16LEバイト型配列からString(文字列)型に変換 ); finally http.Free; end; end; end.