Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2023年05月31日] [更新 2023年06月07日] Delphi 一般・その他
   
区点コードを Unicode のコードポイントに変換
動作確認等 Windows 10 ビルド 19043 Pro 64 bit + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 1220]







区点コードを Unicode のコードポイントに変換


当サイトに,JIS の区点コード表を掲載していますが,JIS の区点コードは,Windows に実装されている文字符号化方式ではありません.区点コードを Unicode のコードポイントに変換して,それを参照して文字を表示しています.その際に使用した,JIS の区点コードを Unicode のコードポイントに変換する手順です.



JIS で制定されている文字にはサロゲートペアの文字があります.HTML で,Unicode のコードポイントを参照して文字を表示する場合,認識してくれないサロゲートペアがあります.そこで,本ページのサンプルではその対策処理をしています.





01_JIS X 0208 の区点コードを Unicode のコードポイントに変換


JIS X 0208 の区点コードを Unicode のコードポイントに変換する例です.
区と点の値に $20 を加算した値に,エスケープシーケンスと呼ばれるバイトデータを追加して,コードページ 50222 のバイトデータを生成します.このバイトデータをエンコードして Unicode の文字とします.その文字のコードポイントを取得する関数を作成して使用しています.JIS X 0208 のエスケープシーケンスは $1B $24 $42 です.1 文字だけが対象なので,文字列終端のエスケープシーケンス $1B $28 $42 は省略しています.



下図が実行結果です.文字がない場合はコードポイントの値が $30FB となります.



図1
実行結果
  • 値は左から
    区点コード
    その区点コードの文字
    Unicode のコードポイント
  • 文字がない場合は $30FB

リスト1
implementation

uses
  MECSUtils;

{$R *.dfm}

var
  FEsc0208 : array [0..4] of Byte = ($1B, $24, $42, $00, $00);

//-----------------------------------------------------------------------------
//  JIS X 0208 の区点コードを Unicode のコードポイントに変換
//  変換できなかった時 ( 文字がない時 ) は $30FB 「・」を返す
//-----------------------------------------------------------------------------
function KuTen2CodePoint(AKuNo: Byte; ATenNo: Byte): Integer;

  // 全角チルダ  / 波ダッシュ問題の対応処理
  function Fix_JISX0208ToUnicode(const AUniCode: Integer): Integer;
  begin

    Result := AuniCode;

    case AUniCode of
      $2014: Result := $2015;
      $203E: Result := $FFE3;
      $2016: Result := $2225;
      $2212: Result := $FF0D;
      $301C: Result := $FF5E;
      $00A2: Result := $FFE0;
      $00A3:  REsult:= $FFE1;
      $00AC:  Result:= $FFE2;
    end;
  end;

var
  LStream   : TMemoryStream;
  LStrList  : TStringList;
  LUniStr   : string;
  Lucs4Text : UCS4String;
begin
  Result := $30FB;

  if (AKuNo <= 8) or (AKuNo >= 16) and ((AKuNo <= 84)) then begin
    FEsc0208[3] := AKuNo + $20;
    FEsc0208[4] := ATenNo + $20;;

    LStream  := TMemoryStream.Create;
    LStrList := TStringList.Create;
    try
      LStream.Write(FEsc0208[0], Length(FEsc0208));
      LStream.Position := 0;
      LStrList.LoadFromStream(LStream, TEncoding.GetEncoding(50222));

      // サロゲートペア対策.UTF-32 にして処理
      LUniStr := LStrList[0];
      Lucs4Text := MECSUtils.UTF16ToUTF32(LUniStr);
      Result := Fix_JISX0208ToUnicode(Lucs4Text[0]);
    finally
      FreeAndNil(LStream);
      FreeAndNil(LStrList);
    end;
  end;
end;

//=============================================================================
//  上の関数の使用例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LKuNo    : Integer;
  LTenNo   : Integer;
  LUniStr  : string;
  LCode    : Integer;
  LText    : string;
begin
  Memo1.Lines.Clear;

  LKuNo  := 16;
  LTenNo := 11;
  LCode := KuTen2CodePoint(LKuNo, LTenNo);

  LUniStr := CodePointToUTF16(LCode);
  LText  := IntToStr(LKuNo) + '-' + IntToStr(LTenNo);
  LText  := LText + ' ' +  LUniStr + ' ' + 'U+' + IntToHex(LCode, 4);
  Memo1.Lines.Add(LText);


  LKuNo  := 16;
  LTenNo := 96;
  LCode := KuTen2CodePoint(LKuNo, LTenNo);

  LUniStr := CodePointToUTF16(LCode);
  LText  := IntToStr(LKuNo) + '-' + IntToStr(LTenNo);
  LText  := LText + ' ' +  LUniStr + ' ' + 'U+' + IntToHex(LCode, 4);
  Memo1.Lines.Add(LText);
end;


上のサンプルでは,TStringList にバイトデータを読み込んでいます.以下は,コードページ 50222 の文字列型の文字列のデータとしてバイトデータを読み込んで処理する例です.



リスト2
type
  F50222String = type AnsiString(50222);

//-----------------------------------------------------------------------------
//  JIS X 0208 の区点コードを Unicode のコードポイントに変換
//  変換できなかった時 ( 文字がない時 ) は $30FB 「・」を返す
//-----------------------------------------------------------------------------
function KuTen2CodePoint(AKuNo: Byte; ATenNo: Byte): Integer;

  // 全角チルダ  / 波ダッシュ問題の対応処理
  function Fix_JISX0208ToUnicode(const AUniCode: Integer): Integer;
  begin

    Result := AuniCode;

    case AUniCode of
      $2014: Result := $2015;
      $203E: Result := $FFE3;
      $2016: Result := $2225;
      $2212: Result := $FF0D;
      $301C: Result := $FF5E;
      $00A2: Result := $FFE0;
      $00A3:  REsult:= $FFE1;
      $00AC:  Result:= $FFE2;
    end;
  end;
var
  LStream   : TMemoryStream;
  L50222Str : F50222String;
  LUniStr   : string;
  Lucs4Text : UCS4String;
begin
  Result := $30FB;

  if (AKuNo <= 8) or (AKuNo >= 16) and ((AKuNo <= 84)) then begin
    FEsc0208[3] := AKuNo + $20;
    FEsc0208[4] := ATenNo + $20;;

    LStream  := TMemoryStream.Create;
    try
      LStream.Write(FEsc0208[0], Length(FEsc0208));
      SetLength(L50222Str, LStream.Size);
      LStream.Position := 0;
      LStream.Read(L50222Str[1], LStream.Size);

      // サロゲートペア対策.UTF-32 にして処理
      LUniStr := L50222Str;
      Lucs4Text := MECSUtils.UTF16ToUTF32(LUniStr);
      Result := Fix_JISX0208ToUnicode(Lucs4Text[0]);
    finally
      FreeAndNil(LStream);
    end;
  end;
end;


このサンプルでは,MECSUtils に MecsMappingFix_JISX0208ToUnicode という関数のコードを利用して「全角チルダ  / 波ダッシュ問題」の修正をしています.


修正前 修正後

U+2014

U+2015

U+203E

U+FFE3

U+2016

U+2225

U+2212

U+FF0D

U+301C

U+FF5E
¢
U+00A2

U+FFE0
£
U+00A3

U+FFE1
¬
U+00AC

U+FFE2




02_JIS X 0213 の区点コードを Unicode のコードポイントに変換


JIS X 0213 の区点コードの符号化は,ISO-2022-JP-2004 で制定されていますが,Windows には実装されていません. Python, PHP には実装されているようです.
以下のリンクに掲載している区点コード表は,ウィキペティアに掲載されている表をエクセルに取り込んで利用して作成しています.



次の最初のリンクの記事の方法は,ウェブページの表を指定して取り込みます.2 番目のリンクのサンプルプログラムは,ウェブページの表を全てエクセルのシートに取り込みます.



ウェブページの表をエクセルに読み込んだら,名前を付けて保存します.カンマ区切りテキスト (CSV) として保存することも考えられます.CSV ファイルとして保存した場合,TStringList 等に読み込む時は UTF-8 でエンコードします.U+ がない行を削除すれば,区点コード情報のテキストになります.

JIS X 0213 には,サロゲートペアの文字,結合文字があります.ウィキペティアに掲載されている非漢字一覧の Unicode のコードポイントは,「全角チルダ / 波ダッシュ問題」対応した値にはなっていません.





03_JIS X 0212 の区点コードを Unicode のコードポイントに変換


JIS X 0212 の区点コードを Unicode のコードポイントに変換する例です.
区と点に $20 を加算した値に,エスケープシーケンスと呼ばれるバイトデータを追加して,コードページ 50222 のバイトデータを生成します.このバイトデータをエンコードして Unicode の文字とします.その文字のコードポイントを取得する関数を作成して使用しています.JIS X 0212 のエスケープシーケンスは $1B $24, $28 $44 です.1 文字だけが対象なので,文字列終端のエスケープシーケンス $1B $28 $42 は省略しています.



下図が実行結果です.文字がない場合はコードポイントの値が $30FB となります.



図2
実行結果
  • 値は左から
    区点コード
    その区点コードの文字
    Unicode のコードポイント
  • 文字がない場合は $30FB

リスト3
implementation

uses
  MECSUtils;

{$R *.dfm}

var
  FEsc0212 : array [0..5] of Byte = ($1B, $24, $28, $44, $00, $00);

//-----------------------------------------------------------------------------
//  JIS X 0212 の区点コードを Unicode のコードポイントに変換
//  変換できなかった時 ( 文字がない時 ) は $30FB 「・」を返す
//-----------------------------------------------------------------------------
function KuTen2CodePoint(AKuNo: Byte; ATenNo: Byte): Integer;
var
  LStream   : TMemoryStream;
  LStrList  : TStringList;
  LUniStr   : string;
  Lucs4Text : UCS4String;
begin
  FEsc0212[4] := AKuNo + $20;
  FEsc0212[5] := ATenNo + $20;;

  LStream  := TMemoryStream.Create;
  LStrList := TStringList.Create;
  try
    LStream.Write(FEsc0212[0], Length(FEsc0212));
    LStream.Position := 0;
    LStrList.LoadFromStream(LStream, TEncoding.GetEncoding(50222));

    // サロゲートペア対策.UTF-32 にして処理
    LUniStr := LStrList[0];
    Lucs4Text := MECSUtils.UTF16ToUTF32(LUniStr);
    Result := Lucs4Text[0];
  finally
    FreeAndNil(LStream);
    FreeAndNil(LStrList);
  end;
end;

//=============================================================================
//  上の関数の使用例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LKuNo    : Integer;
  LTenNo   : Integer;
  LUniStr  : string;
  LCode    : Integer;
  LText    : string;
begin
  Memo1.Lines.Clear;

  LKuNo  := 2;
  LTenNo := 78;
  LCode := KuTen2CodePoint(LKuNo, LTenNo);

  LUniStr := CodePointToUTF16(LCode);
  LText  := IntToStr(LKuNo) + '-' + IntToStr(LTenNo);
  LText  := LText + ' ' +  LUniStr + ' ' + 'U+' + IntToHex(LCode, 4);
  Memo1.Lines.Add(LText);


  LKuNo  := 2;
  LTenNo := 88;
  LCode := KuTen2CodePoint(LKuNo, LTenNo);

  LUniStr := CodePointToUTF16(LCode);
  LText  := IntToStr(LKuNo) + '-' + IntToStr(LTenNo);
  LText  := LText + ' ' +  LUniStr + ' ' + 'U+' + IntToHex(LCode, 4);
  Memo1.Lines.Add(LText);
end;




04_GB コードを Unicode のコードポイントに変換


GB コードを Unicode のコードポイントに変換する例です.
GB コードの値に,エスケープシーケンスと呼ばれるバイトデータを追加して,コードページ 50222 のバイトデータを生成します.このバイトデータをエンコードして Unicode の文字とします.その文字のコードポイントを取得する関数を作成して使用しています.GB コードのエスケープシーケンスは $1B $24 $29 $41 です.1 文字だけが対象なので,文字列終端のエスケープシーケンス $1B $28 $42 は省略しています.



下図が実行結果です.文字がない場合はコードポイントの値が $003F となります.



図3
実行結果
  • 値は左から
    GB コード
    その区点コードの文字
    Unicode のコードポイント
  • 文字がない場合は $003F

リスト4
implementation

uses
  MECSUtils;

{$R *.dfm}

var
  FEscGB : array [0..5] of Byte = ($1B, $24, $29, $41, $00, $00);

//-----------------------------------------------------------------------------
//  GB コードを Unicode のコードポイントに変換
//  変換できなかった時 ( 文字がない時 ) は $003F 「?」を返す
//-----------------------------------------------------------------------------
function GB2CodePoint(AGBCode: Integer): Integer;
var
  LStream   : TMemoryStream;
  LStrList  : TStringList;
  LUniStr   : string;
  Lucs4Text : UCS4String;
begin
  FEscGB[4] := Hi(AGBCode);
  FEscGB[5] := Lo(AGBCode);;

  LStream  := TMemoryStream.Create;
  LStrList := TStringList.Create;
  try
    LStream.Write(FEscGB[0], Length(FEscGB));
    LStream.Position := 0;
    LStrList.LoadFromStream(LStream, TEncoding.GetEncoding(50222));

    // サロゲートペア対策.UTF-32 にして処理
    LUniStr := LStrList[0];
    Lucs4Text := MECSUtils.UTF16ToUTF32(LUniStr);
    Result := Lucs4Text[0];
  finally
    FreeAndNil(LStream);
    FreeAndNil(LStrList);
  end;
end;

//=============================================================================
//  上の関数の使用例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LGBCode  : Integer;
  LUniStr  : string;
  LCode    : Integer;
  LText    : string;
begin
  Memo1.Lines.Clear;

  LGBCode := $3032;
  LCode := GB2CodePoint(LGBCode);

  LUniStr := CodePointToUTF16(LCode);
  LText  := '$' + IntToHex(LGBCode, 4)
          + ' ' +  LUniStr + ' ' + 'U+' + IntToHex(LCode, 4);
  Memo1.Lines.Add(LText);


  LGBCode := $2661;
  LCode := GB2CodePoint(LGBCode);

  LUniStr := CodePointToUTF16(LCode);
  LText  := '$' + IntToHex(LGBCode, 4)
          + ' ' +  LUniStr + ' ' + 'U+' + IntToHex(LCode, 4);
  Memo1.Lines.Add(LText);
end;




05_文字が環境依存かをチェック


ASCII 文字と JIS X 0208 で制定されている文字は,日本語を扱う処理系であれば全てが処理可能です.これらは環境に依存しない文字とされています.コードページ 50222 のバイナリデータを調べると,その文字が JIS X 0208 で制定されているかを調べることができます.

このサンプルは,コードページ 50222 の文字のエスケープシーケンスを調べて,文字が JIS X 0208 で制定されているかを判定します.JIS X 0208 で制定されていれば,区点コードを取得して表示します.ASCII 文字でもなく,JIS X 0208 にも制定されていない文字の場合は [環境依存] とします.

このサンプルは,MECSUtils の MecsMappingFix_UnicodeToJISX0208 関数を使用して「全角チルダ / 波ダッシュ問題」と「JIS2004 問題 (新常用漢字問題)」を修正しています.



図4
文字の区点コードと [環境依存]
  • 値は左から
    調査対象の文字
    その文字の区点コード

リスト5
implementation

uses
  MECSUtils;

{$R *.dfm}

type
  F50222String = type AnsiString(50222);

//-----------------------------------------------------------------------------
//  引数の文字の JIS X 0208 ( JIS 基本漢字 ) の区点コードを取得
//  戻り値の最下位バイトが点,その上のバイトが区の値
//  なければ 0 を返す
//-----------------------------------------------------------------------------
function Str2KuTenCode(AStr: string): Integer;
var
  LUniStr   : string;
  L50222Str : F50222String;
  LBytes    : TBytes;
  LkuNo     : Byte;
begin
  Result := 0;

  LUniStr := MecsMappingFix_UnicodeToJISX0208(AStr);

  L50222Str := LUniStr;
  LBytes := MecsBytesOf(L50222Str);
  if Length(LBytes) >= 8 then begin
    LKuNo := LBytes[3] - $20;
    if (LKuNo <= 8) or (LKuNo >= 16) and ((LKuNo <= 84)) then begin
      if (LBytes[0] = $1B) and (LBytes[1] = $24) and (LBytes[2] = $42) then begin
        Result := (LkuNo shl 8) + (LBytes[4] - $20);
      end;
    end;
  end;
end;

//=============================================================================
//  上の関数の使用例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LKuNo    : Integer;
  LTenNo   : Integer;
  LUniStr  : string;
  LCode    : Integer;
  LText    : string;
begin
  Memo1.Lines.Clear;

  LUniStr := '東';

  if Ord(LUniStr[1]) <= $7F then begin
    LText  := LUniStr + ' ' + 'ASCII';
  end else begin
    LCode := Str2KuTenCode(LUniStr);
    if LCode = 0 then begin
      LText  := LUniStr + ' ' + '[環境依存]';
    end else begin
      LKuNo  := Hi(LCode);
      LTenNo := Lo(LCode);
      LText  := LUniStr + ' ' + IntToStr(LKuNo) + '-' + IntToStr(LTenNo);
    end;
  end;
  Memo1.Lines.Add(LText);


  LUniStr := '髙';

  if Ord(LUniStr[1]) <= $7F then begin
    LText  := LUniStr + ' ' + 'ASCII';
  end else begin
    LCode := Str2KuTenCode(LUniStr);

    if LCode = 0 then begin
      LText  := LUniStr + ' ' + '[環境依存]';
    end else begin
      LKuNo  := Hi(LCode);
      LTenNo := Lo(LCode);
      LText  := LUniStr + ' ' + IntToStr(LKuNo) + '-' + IntToStr(LTenNo);
    end;
  end;
  Memo1.Lines.Add(LText);
end;




06_CP932 のコードポイントを Unicode のコードポイントに変換


一凛のコード表には Shift_JIS のコード表も掲載しています.
Shift_JIS の各文字のコードは,コードページ 932 のコードポイントです,そのコードポイントを Unicode のコードポイントに変換する例です.Shift_JIS の文字には,サロゲートペアの文字も結合文字もありません.全て,Unicode では 1 エレメント (2 バイト) の文字です.



JIS X 0208 の文字に,半角カタカナと以下のページに掲載している文字を追加したのが Shift_JIS の文字です.しかがって,上のサンプルの方法を使用すれば,文字がない猟奇を検出できます.ASCII 文字はどの符号化方式でも使用可能です.



図5
実行結果
  • $889F を Unicdoe のコードポイントに変換

リスト6
implementation

{$R *.dfm}

//=============================================================================
//  CP932 のコードポイント ( 文字コード ) を Unicode のコードポイントに変換
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LSjisCode : Integer;
  LSjisStr  : AnsiString;
  LUniStr   : string;
  LUniCode  : Integer;
  LText     : string;
begin
  Memo1.Lines.Clear;

  LSjisCode := $889F;

  if LSjisCode <= $FF then begin
    LSjisStr := AnsiChar(LSjisCode);
  end else begin
    LSjisStr := AnsiChar(Hi(LSjisCode)) + AnsiChar(Lo(LSjisCode));
  end;
  LUniStr  := string(LSjisStr);
  LUniCode := Ord(LUniStr[1]);

  LText := LSjisStr + ' ' + LUniStr
         + ' $' + IntToHex(LSjisCode, 2) + ' U+' + IntToHex(LUniCode, 4);
  Memo1.Lines.Add(LText);
end;