Migaro. 技術Tips

                       

ミガロ. 製品の技術情報
IBMiの活用に役立つ情報を掲載!


【Delphi/400】CCSID 1208 の UTF-8 フィールドに値を読み書きする手順

IBM i で一般的に使用できるDBの文字コード(CCSID=5026、5035、拡張1399)は
EBCDICであり、元々Unicode依存文字にはほとんど対応していませんでした。

しかし、IBM i でもDDS等のフィールド定義において
CCSID=1208 と設定すると、UTF-8 が使用可能となっています。

今回のTipsでは、ひと手間かかるものの
Delphi/400 の CodePage にはない UTF-8 の文字を扱うためのトピックをご紹介します。

 

CCSID 1208 を含む検証用ファイルの作成

まず、UTF-8 値を格納できる
CCSID=1208 のフィールドを含む検証用ファイルをここでは作成します。

今回は以下の2つのフィールドを持つファイルを対象として処理を進めます。

ID(整数型、主キー)
MEMO(UTF-8 文字型300桁、CCSID=1208)

今回の検証用ファイルでは以下のDDSでファイルを生成します。
(SQLのCREATE TABLEでもCCSIDを指定すれば作成可能です。)

A*****************************************************************
A*          UTF8フィールドを含むDDS(ファイル名:UC1208P)
A*          CREATE: 2024/04/22
A*          UPDATE: XXXX/XX/XX
A*****************************************************************
A          R UC1208R                   TEXT('UTF8検証ワーク')
A            ID             8P 0       COLHDG('ID')
A*
A            MEMO         300A         CCSID(1208)
A                                      COLHDG('名称UTF8')
A          K ID
A*****************************************************************

 
以下の点がポイント(通常のEBCDIC準拠の全半角文字フィールドと異なる点)です。

  • 型は「A」を定義
  • 機能欄に「CCSID(1208)」と記述
  • UTF-8 の文字数に対して桁数が可変になるため、フィールドの桁数は長めに取っておく
    (基本的に半角文字は1文字1桁、全角文字は1文字3桁を使用。シフト文字は考慮不要。)

 


CCSID 1208 の検証用データの作成

<処理の流れ>
①:登録したい文字列をHEX(16進数文字)に変換する
②:登録SQLで対象フィールドに対してその旨を指定する

 
今回は以下の10行の文字列をDelphi/400を使用して、
データを登録する方法を考えてみます。

<登録する文字列サンプル>
①:2024 Migaro CO.,LTD.
②:株式会社ミガロ.
③:Delphi/400 ’11’ Alexandria
④:森鷗外
⑤:おはようございます☀ さわやかな朝✨
⑥:좋은 아침입니다 ☀ 상쾌한 아침입니다 ✨
⑦:早安☀一個清爽的早晨✨
⑧:สวัสดีตอนเช้า ☀ เช้าที่สดชื่น ✨
⑨:शुभ प्रभात ☀ एक स्फूर्तिदायी बिहान ✨
⑩:صباح الخير ☀ صباح منعش ✨

※①②は通常の文字列、③はシングルクォーテーション入り
 ④⑤はUnicode依存文字(鷗・絵文字)入り
 ⑥は⑤の韓国語翻訳、⑦は中国語訳、⑧タイ語訳、⑨ネパール語訳、⑩アラビア語訳

 
通常は
『 INSERT INTO TESTLIB/UC1208P (ID, MEMO) VALUES (~~, ‘~~~~’) 』
といったSQLを発行するかと思いますが、
そのまま発行するとUnicode依存文字の部分は正しく登録されません。

 
そこで、代わりにHEX(16進数文字)に変換した文字列を渡すことで
Unicode依存文字も化けずに登録されるようにしたいと思います。

 

①:登録したい文字列をHEX(16進数文字)に変換する

以下のような関数を作成して
『登録したい文字列 ⇒ Bytes ⇒ 16進数文字列』
に変換(エンコード)したものを用意します。

この関数を通すと、
例えば④行目の『森鷗外』は『68EE9DD75916』に変換されます。
(※「鷗」はUnicode依存で、従来は化けてしまっていた文字)
(※エンコード時はTEncoding.BigEndianUnicodeを使用します。)

この関数の記述場所は、後述の登録処理の直前でも、
別の共通処理用のユニット内でも構いません。

{*******************************************************************************
 目的: 文字列を16進数文字列に変換
 引数: str - 元の文字列
 戻値: 16進数にエンコードされた文字列
*******************************************************************************}
function UTF16StringToHex(const str: string): string;
var
  i: Integer;
  Bytes: TBytes;
begin
  // 文字列⇒Bytes
  Bytes := TEncoding.BigEndianUnicode.GetBytes(str);

  // Bytes⇒HEX文字列
  SetLength(Result, Length(Bytes) * 2);
  if Length(Bytes) > 0 then
  begin
    BinToHex(Bytes[0], PChar(Result), Length(Bytes));
  end;
end;

 

②:登録SQLで対象フィールドに対してその旨を指定する

登録や更新のSQLでは、値の前に「UX」をつけることでUnicodeの16進数を指定できます。

例えば先ほどの『森鷗外』の場合、
『 INSERT INTO TESTLIB/UC1208P (MEMO) VALUES (UX’68EE9DD75916′) 』
というSQLを実行すれば正しく登録することができます。

Delphi/400からは、先ほど作った関数「UTF16StringToHex」を
通した後の文字列を渡すことで実装が可能です。

例えば登録したい値がEdit1(ID)・Edit2(MEMO)に入っている場合は、
以下のようなロジックとなります。

{*******************************************************************************
 目的: レコード作成 ボタン押下時処理
 引数:
 戻値:
*******************************************************************************}
procedure TForm1.Button3Click(Sender: TObject);
var
  Hex1: string;
begin

  with Query do // ※TSQLQueryまたはTFDQuery
  begin
    Hex1     := UTF16StringToHex(Edit2.Text); // Edit2の値を16進数に変換
    SQL.Text := 'INSERT INTO TESTLIB/UC1208P '
                ' (ID, MEMO) VALUES (' + Edit1.Text + ', UX' + QuotedStr(Hex1) + ')';
    ExecSQL();  // SQL実行
  end;
  ShowMessage('レコードを追加しました。');
end;

※従来の CCSIDが 5026, 5035, 1399といったフィールドに対して、
 この方法で登録や更新をかけるとエラーになりますのでご注意ください。

※SQL文が長くなるため、複数行を一括登録する場合はSQL文の長さの上限にご注意ください。
 (BDE・dbExpress=約32000バイト、FireDAC=約16000バイト)
 バインド変数(ParamByName)を使用したSQL発行の方法は今のところ見つかっておりません。

 

登録したデータは下記のようになっています。

※「ID」には、今回のサンプル①~⑩に対応して1~10の整数値をセットしています。

 


Delphi/400でデータを画面に読み込む方法

次に、上記の処理で登録したUTF-8の項目を
Delphi/400 の画面上に元の文字列として表示する方法を考えてみます。

<処理の流れ>
①:SQLでHEX(16進数文字)としてデータを取得する
②:対象フィールドにOnGetTextイベントを割り当て、変換のロジックを記述する

 

①:SQLでHEX(16進数文字)としてデータを取得する

登録されたデータを読み取るにあたり、
SQLでそのまま『SELECT * FROM TESTLIB/UC1208P 』と発行しても
非対応の文字は化けて表示されてしまいます。

16進数の文字列で登録したフィールドを正しく読み取るには、
まずHEXでキャストすることで16進数の文字列を取得します。

<SQL記述>
『SELECT ID, HEX(MEMO) AS MEMO FROM TESTLIB/UC1208P 』

『HEX(MEMO) AS MEMO』として取得したフィールドは16進数文字列になっています。
これを後続の手順で変換(デコード)することで、登録した時点と同じ文字列を表示させます。

 

②:対象フィールドにOnGetTextイベントを割り当て、変換のロジックを記述する

設計画面のフィールドエディタから、
対象のフィールドにOnGetTextイベントを割り当てます。

OnGetTextイベントを使用すれば、画面に出力する文字列をカスタマイズできるため、
16進数文字列で取得した値をUTF-8文字列に変換した値に変換するロジックを組み込むことで
文字化けを解消する事ができます。
(※実際の内部データは16進数文字列のままです。)

設計画面にフィールドを定義していない場合は、
データセットをオープンした直後(QueryやCDSなどのAfterOpenイベント)に
以下のようにロジックでイベントを動的に割り当てることも可能です。
(今回の例ではイベント名は「Field1208_GetText」としています。)

  DBGrid1.DataSource.DataSet.
      FieldByName('MEMO').OnGetText := Field1208_GetText;

割り当てたイベントに、後述する文字変換を行うロジックを記述します。
これで先ほど登録した際と同じ文字列が表示されます。

 

こちらも先ほどのデータ登録時と同様に文字列変換の関数を作成し、
OnGetTextイベントからそれを呼び出すイメージです。
(記述場所はOnGetTextイベントの直前でも、別の共通処理用のユニット内でも構いません。)

なお登録時はTEncoding.BigEndianUnicodeを使いましたが、
読み取り時はTEncoding.UTF8を使用してデコードします。(詳細はロジック内を参照)

{*******************************************************************************
 目的: 16進数文字列を元の文字列(UTF-8)に変換
 引数: str - 16進数(HEX)の文字列
 戻値: 登録した元の文字列
*******************************************************************************}
function HexToUTF8String(const Hex: string): string;
var
  i: Integer;
  Bytes: TBytes;
begin
  // HEX文字列⇒Bytes
  SetLength(Bytes, Length(Hex) div 2);
  for I := 0 to Length(Bytes) - 1 do
  begin
    Bytes[I] := StrToInt('$' + Copy(Hex, I * 2 + 1, 2));
  end;

  // Bytes⇒UTF8文字列にデコード
  Result := TEncoding.UTF8.GetString(Bytes);
end;

 

<OnGetTextイベントの記述例>

{*******************************************************************************
 目的: 対象フィールドのGetTextイベント
 引数:
 戻値:
*******************************************************************************}
procedure TForm1.Field1208_GetText(Sender: TField; var Text: string;
  DisplayText: Boolean);
begin
  // HEXを元の文字列に変換して表示する
  Text := HexToUTF8String(Sender.AsString);
end;

 

 


補足:CCSID 1200 の UTF-16 フィールドの扱い方

今回は CCSID 1208UTF-8 フィールドについてご紹介しましたが、
同様に CCSID 1200UTF-16 フィールドについても読み書きすることが可能です。

今回紹介した UTF-8 での手順から、以下の部分をそれぞれ変更します。

 
 
<①:DDSファイルでのフィールド定義>

フィールド定義を行う箇所では、以下のように
型は「G」タイプを定義し、機能欄に「 CCSID(1200) 」と記述します。
桁数を長めに取っておく所は UTF-8 の場合と同様です。

A*UTF-16のDDS記述例
A            MEMO2        300G         CCSID(1200)
A                                      COLHDG('名称UTF16')

 
 
<②:CCSID 1200 の検証用データの作成>

CCSID 1208 の場合と変更なし

 
 
<③:Delphi/400でデータを画面に読み込む方法>

SQLでHEX(16進数文字)としてデータを取得したあと、
文字変換のデコード方法を変更します。

UTF-8 では「HexToUTF8String」という関数を作成しましたが、
今回は「HexToUTF16String」という別関数としています。
ここではデコードの際にも、エンコードで使用した TEncoding.BigEndianUnicode を使います。

OnGetText イベントから関数を呼び出す際も、ここで作成した HexToUTF16String を使用します。

{*******************************************************************************
 目的: 16進数文字列を元の文字列(UTF-16)に変換
 引数: str - 16進数(HEX)の文字列
 戻値: 登録した元の文字列
*******************************************************************************}
function HexToUTF16String(const Hex: string): string;
var
  i: Integer;
  Bytes: TBytes;
begin
  // HEX文字列⇒Bytes
  SetLength(Bytes, Length(Hex) div 2);
  for I := 0 to Length(Bytes) - 1 do
  begin
    Bytes[I] := StrToInt('$' + Copy(Hex, I * 2 + 1, 2));
  end;

  // Bytes⇒UTF16文字列にデコード
  Result := TEncoding.BigEndianUnicode.GetString(Bytes);
end;