【実務・中級編】TRANSLATE関数による文字変換テーブルの適用とマッピング – PL/Iの基本構文とデータ制御実践ガイド

PL/IのTRANSLATE関数:EBCDIC環境での文字コード変換をマスターする

おい、諸君!今日はPL/Iのちょっとした、でも非常に強力な機能、「TRANSLATE関数」について、現場で役立つ話をしようじゃないか。特に我々が日常的に触れているEBCDIC環境での文字コード変換、これはもう避けて通れない道だ。長年メインフレームの保守や改修に携わってきた経験から、このTRANSLATE関数を使いこなすことが、いかにバッチ処理の安定稼働や、将来的なマイグレーション作業の助けになるかを、実体験を交えながら、じっくりと解説していくから、しっかりついてきてくれよ。

なぜTRANSLATE関数が重要なのか?

皆もご存知の通り、メインフレームの世界ではEBCDICコードが主流だ。ASCII環境で育った連中から見れば、あの文字コード表は「一体どうなってんだ?」と頭を抱えたくなる代物だろう。しかし、我々はこのEBCDICを理解し、時にはそれを加工する必要がある。例えば、

  • 外部システムとのデータ連携: ASCIIやUTF-8で送られてきたデータをEBCDICに変換したり、逆にEBCDICのデータを外部に渡すために変換したり。
  • データクレンジング: 特定の記号や制御文字を別の文字に置き換えたり、全角英数字を半角にしたり。
  • 帳票やレポート作成: 表示用に特定の文字にマッピングしたり、カナを漢字に変換したり(これはTRANSLATE関数だけでは難しいが、一部分の処理には使える)。

こうした場面で、TRANSLATE関数は非常に効率的で、かつ可読性の高いコードを書くための強力な武器となるんだ。ONユニットやレコード入出力、VSAMアクセスといった、我々が普段扱っているPL/Iの基幹処理と組み合わせて使うことで、その威力を発揮する。

TRANSLATE関数の基本構文と引数

TRANSLATE関数は、第一引数で指定された文字列を、第二引数と第三引数で定義された変換テーブルに基づいて変換してくれる、非常に便利な組み込み関数だ。

TRANSLATE(string, to_string, from_string)

  • `string`: 変換したい元の文字列。これは固定長でも可変長でも構わない。
  • `to_string`: 変換後の文字を定義する文字列。`from_string`の各文字に対応する変換後の文字が格納される。
  • `from_string`: 変換元の文字を定義する文字列。この文字列の各文字が、`to_string`の対応する位置の文字に置き換えられる。

ここで重要なのは、`to_string`と`from_string`の長さの関係だ。

  • `to_string`の長さが`from_string`の長さと同じ場合: `from_string`のi番目の文字は、`to_string`のi番目の文字に変換される。
  • `to_string`の長さが`from_string`の長さより短い場合: `from_string`のi番目の文字は、`to_string`のi番目の文字に変換される。ただし、`from_string`の長さより多くの文字を変換しようとした場合、`to_string`の最後の文字でパディングされる。
  • `to_string`の長さが`from_string`の長さより長い場合: `from_string`のi番目の文字は、`to_string`のi番目の文字に変換される。`to_string`の余った部分は無視される。
  • `to_string`が省略された場合: `from_string`の各文字は、その文字自体に変換される(つまり、実質的に何も変換されない)。これはあまり実用的ではないが、構文としては存在する。

変換テーブルの定義方法:EBCDIC環境での注意点

さて、ここからが本番だ。EBCDIC環境での文字コード変換では、この変換テーブルの定義が肝となる。PL/Iでは、文字列リテラルや変数を定義して、これを`to_string`と`from_string`に指定する。

例えば、EBCDICで「あ」を「ア」に変換したい場合を考えてみよう。PL/Iでこれをどう表現するか。

/ EBCDIC環境での文字コード変換例 /
DCL SRC_STRING CHAR(50) VARYING;
DCL DEST_STRING CHAR(50) VARYING;
DCL FROM_CHARS CHAR(3) INIT(‘あ’); / 変換したい文字 /
DCL TO_CHARS CHAR(3) INIT(‘ア’); / 変換後の文字 /

/ 処理の途中 /
SRC_STRING = ‘これは、あいうえおテストです。’;

/ TRANSLATE関数で変換を実行 /
DEST_STRING = TRANSLATE(SRC_STRING, TO_CHARS, FROM_CHARS);

/ DEST_STRINGには「これは、アいうえおテストです。」が格納される /
PUT SKIP LIST(DEST_STRING);

しかし、ここには落とし穴がある。PL/Iのソースコードは、デフォルトではEBCDICで記述されることがほとんどだ。そして、EBCDICの「あ」や「ア」という文字は、ソースコード上で直接記述しても、コンパイラが正しく認識してくれるとは限らない。特に、異なるコードページ間でのやり取りや、ソースコードの編集環境によっては、文字化けの原因になる。

そこで、安全策として、十六進数リテラルを使うのが定石だ。EBCDICの「あ」や「ア」のコードポイントを、公式マニュアルや、よく知った仲間から教えてもらったコード表で確認し、それを十六進数で指定する。

例えば、EBCDICの「あ」が`X’8260’`、「ア」が`X’8341’`だと仮定しよう(実際のコードは環境やフォントによって異なる場合があるので、必ず確認すること!)。

/ 十六進数リテラルを使ったEBCDIC文字コード変換 /
DCL SRC_STRING CHAR(50) VARYING;
DCL DEST_STRING CHAR(50) VARYING;
DCL FROM_CHARS CHAR(2) INIT(X’8260′); / EBCDICの「あ」 /
DCL TO_CHARS CHAR(2) INIT(X’8341′); / EBCDICの「ア」 /

/ 処理の途中 /
SRC_STRING = ‘これは、あいうえおテストです。’;

/ TRANSLATE関数で変換を実行 /
DEST_STRING = TRANSLATE(SRC_STRING, TO_CHARS, FROM_CHARS);

/ DEST_STRINGには「これは、アいうえおテストです。」が格納される /
PUT SKIP LIST(DEST_STRING);

この方法なら、ソースコードの編集環境やコードページに依存することなく、確実にEBCDICの文字コードを指定できる。これは、レガシーシステムの保守において、非常に重要なプラクティスだ。

複数の文字を一度に変換する

TRANSLATE関数の真価は、複数の文字を一度に変換できることにある。

/ 複数の文字を一度に変換する例 /
DCL SRC_STRING CHAR(100) VARYING;
DCL DEST_STRING CHAR(100) VARYING;

DCL FROM_CHARS CHAR(10) INIT(‘abcdefghij’); / 変換元の文字 /
DCL TO_CHARS CHAR(10) INIT(‘ABCDEFGHIJ’); / 変換後の文字 /

/ 処理の途中 /
SRC_STRING = ‘This is a test string with abcdefghij.’;

/ TRANSLATE関数で変換を実行 /
DEST_STRING = TRANSLATE(SRC_STRING, TO_CHARS, FROM_CHARS);

/ DEST_STRINGには「This is a test string with ABCDEFGHIJ.」が格納される /
PUT SKIP LIST(DEST_STRING);

この例では、`’abcdefghij’`という小文字のアルファベットを、対応する大文字の`’ABCDEFGHIJ’`に変換している。

EBCDIC環境で、例えば全角英数字を半角英数字に変換したい場合も、同様に定義できる。

/ 全角英数字を半角英数字に変換する例 (EBCDICコードは仮定) /
DCL SRC_STRING CHAR(100) VARYING;
DCL DEST_STRING CHAR(100) VARYING;

DCL FROM_CHARS CHAR(26 2 + 10) INITIAL; / 全角英大文字・小文字・数字 /
DCL TO_CHARS CHAR(26 2 + 10) INITIAL; / 半角英大文字・小文字・数字 /

/ EBCDICコード表を参照して、FROM_CHARSとTO_CHARSを定義する /
/ (ここでは簡略化のため、具体的なコードは省略。実際には十六進数リテラルで埋める) /
/ 例: FROM_CHARS = X’8341′ || X’8342′ || … ; TO_CHARS = X’41’ || X’42’ || … ; /

/ 処理の途中 /
SRC_STRING = ‘これは全角テスト:ABC 123。’;

/ TRANSLATE関数で変換を実行 /
DEST_STRING = TRANSLATE(SRC_STRING, TO_CHARS, FROM_CHARS);

/ DEST_STRINGには「これは半角テスト:ABC 123.」が格納される (変換が正しく定義されていれば) /
PUT SKIP LIST(DEST_STRING);

このように、変換したい文字の種類が増えれば増えるほど、`FROM_CHARS`と`TO_CHARS`を適切に定義することが重要になる。この定義が間違っていると、想定外の文字に変換されたり、文字化けしたりする原因となる。

ONユニットとの連携、そしてVSAMアクセス

TRANSLATE関数は、単体で使うだけでなく、ONユニットやレコード入出力、VSAMアクセスといった、PL/Iの基幹処理と組み合わせて使うことで、より実践的な価値を発揮する。

例えば、VSAMファイルから読み込んだレコードの特定フィールドの文字コードを変換してから、後続の処理に渡す場合を考えてみよう。

/ VSAMファイルからの読み込みとTRANSLATE関数、ONユニットの連携例 /
DCL EMPLOYEE_RECORD RECORD;
DCL 1 EMPLOYEE_DATA BASED(ADDR(EMPLOYEE_RECORD)),
2 EMPLOYEE_ID CHAR(10),
2 EMPLOYEE_NAME CHAR(50), / ここを変換したい /
2 EMPLOYEE_ADDRESS CHAR(100);

DCL INPUT_VSAM_FILE FILE RECORD SEQUENTIAL ENV(VSAM);
DCL OUTPUT_REPORT_FILE FILE STREAM OUTPUT;

DCL NAME_EBCDIC CHAR(50);
DCL NAME_ASCII CHAR(50); / 変換後の名前を格納 /

/ EBCDICからASCIIへの変換テーブル (仮定) /
DCL FROM_EBCDIC_CHARS CHAR(256) INIT(…); / EBCDIC全文字 /
DCL TO_ASCII_CHARS CHAR(256) INIT(…); / 対応するASCII文字 /

/ ファイルオープン /
OPEN INPUT INPUT_VSAM_FILE;
OPEN OUTPUT OUTPUT_REPORT_FILE;

/ ON ENDFILE ユニットでファイル終端を処理 /
ON ENDFILE(INPUT_VSAM_FILE) GO TO END_PROCESSING;

/ レコード読み込みループ /
READ FILE(INPUT_VSAM_FILE) INTO(EMPLOYEE_RECORD);
DO WHILE (1); / 無限ループ(ENDFILEで抜ける) /

/ 読み込んだレコードの氏名フィールドをEBCDICからASCIIに変換 /
NAME_EBCDIC = EMPLOYEE_NAME; / 念のためコピー /
NAME_ASCII = TRANSLATE(NAME_EBCDIC, TO_ASCII_CHARS, FROM_EBCDIC_CHARS);

/ 変換後の名前を使ってレポートファイルに書き出す /
PUT FILE(OUTPUT_REPORT_FILE) EDIT(EMPLOYEE_ID, NAME_ASCII, EMPLOYEE_ADDRESS)
(PAGE, X(5), F(10), X(5), A(50), X(5), A(100));

/ 次のレコードを読み込む /
READ FILE(INPUT_VSAM_FILE) INTO(EMPLOYEE_RECORD);
END;

END_PROCESSING:
/ ファイルクローズ /
CLOSE INPUT_VSAM_FILE;
CLOSE OUTPUT_REPORT_FILE;

/ 正常終了 /
RETURN;

この例では、VSAMファイルから読み込んだ`EMPLOYEE_NAME`フィールドを、事前に定義した`FROM_EBCDIC_CHARS`と`TO_ASCII_CHARS`を使ってASCIIに変換し、レポートファイルに出力している。`ON ENDFILE`ユニットでファイル終端を検知し、ループを抜ける処理も入っている。

ここで、`FROM_EBCDIC_CHARS`と`TO_ASCII_CHARS`の定義について、もう少し掘り下げてみよう。EBCDICの全文字(256文字)を網羅するのは大変だが、よく使う文字種(英数字、記号、カタカナなど)だけでも定義しておくと、非常に便利だ。

EBCDICからASCIIへの変換テーブルの例(一部抜粋):

DCL FROM_EBCDIC_CHARS CHAR(256) INIT(
/ 00-1F: 制御文字 (無視または特定文字に置換) /
X’00’, X’01’, …, X’1F’,
/ 20-3F: 特殊文字 /
X’20’, / Space / X’21’, / ! / …, X’3F’, / ? /
/ 40-5F: 英大文字 (EBCDIC) /
X’40’, / @ / X’41’, / # / X’42’, / $ / X’43’, / /
X’44’, / ) / X’45’, / – / X’46’, / . / X’47’, / > /
X’48’, / ? / X’49’, / : / X’4A’, / # / X’4B’, / , /
X’4C’, / % / X’4D’, / _ / X’4E’, / > / X’4F’, / ? /
X’50’, / – / X’51’, / ) / X’52’, / ; / X’53’, / ‘ /
X’54’, / + / X’55’, / / / X’56’, / & / X’57’, / _ /
X’58’, / ! / X’59’, / = / X’5A’, / ” / X’5B’, / < / / 50-6F: EBCDICの英大文字 (A-Z) / X'C1', / A / X'C2', / B / ..., X'D9', / Z / / 70-7F: 特殊文字 / X'7F', / ` / X'5C', / \ / X'5E', / ^ / X'7C', / | / X'7E', / ~ / X'5F', / _ / X'3C', / < / X'3E', / > /
/ 80-9F: EBCDICの英小文字 (a-z) /
X’81’, / a / X’82’, / b / …, X’9A’, / z /
/ A0-BF: EBCDICの数字 (0-9) /
X’80’, / 0 / X’81’, / 1 / …, X’89’, / 9 /
/ C0-DF: EBCDICのカタカナ /
X’8E’, / カ / X’8F’, / キ / …, X’AF’, / ン /
/ E0-FF: その他の特殊文字や拡張文字 /

);

DCL TO_ASCII_CHARS CHAR(256) INIT(
/ 00-1F: 制御文字 (Spaceに置換など) /
X’00’, X’00’, …, X’00’,
/ 20-3F: 特殊文字 /
X’20’, / Space / X’21’, / ! / …, X’3F’, / ? /
/ 40-5F: ASCIIの英大文字 (A-Z) /
X’41’, / A / X’42’, / B / …, X’5A’, / Z /
/ 60-7F: ASCIIの英小文字 (a-z) /
X’61’, / a / X’62’, / b / …, X’7A’, / z /
/ 80-9F: ASCIIの数字 (0-9) /
X’30’, / 0 / X’31’, / 1 / …, X’39’, / 9 /
/ A0-BF: ASCIIのカタカナ (ここでは例として省略) /
/ E0-FF: その他の特殊文字 /

);

注意: 上記の`FROM_EBCDIC_CHARS`と`TO_ASCII_CHARS`の定義は、あくまで例であり、実際のEBCDICコードページや、変換したいASCIIコードページによって、正確な値は異なります。必ず、使用している環境のコード表を参照して、正確な十六進数値を定義してください。特に、カタカナや漢字の変換は、コードページによっては非常に複雑になるので、TRANSLATE関数だけで対応できない場合は、より高度なライブラリや手法を検討する必要があります。

まとめ

TRANSLATE関数は、PL/Iで文字コード変換を行う際の、非常に強力で柔軟なツールだ。特にEBCDIC環境では、その真価が発揮される場面が多い。

  • 変換テーブルの定義は、十六進数リテラルを使うのが安全で確実。
  • `from_string`と`to_string`の対応関係を正しく定義することが重要。
  • 複数の文字を一度に変換できるため、効率的なコードが書ける。
  • レコード入出力やVSAMアクセス、ONユニットと組み合わせることで、実用的な処理を実現できる。

このTRANSLATE関数をマスターすれば、データ連携処理の効率化、データクレンジングの自動化、そして将来的なマイグレーション作業の調査や実装においても、君の力は格段に増すはずだ。

今日の話はここまでだ。もし疑問があれば、いつでも声をかけてくれ。現場で培った経験を、惜しみなく共有しようじゃないか。さあ、諸君もTRANSLATE関数を使いこなし、メインフレームの世界で更なる活躍を期待しているぞ!

タイトルとURLをコピーしました