【IRIS/Cache】UTF-8の文字列をバイト指定で切り取る

本記事は、UTF-8の文字列を指定されたバイト数で文字を切り取る方法と、一般的な文字の切り取り方法を合わせて解説します。

※この記事は下記の方向けになります。
  • UTF-8で出力する際、指定のバイト数で文字を切り取りたい方
  • オーソドックスな文字の切り取り方を確認したい方

はじめに

文字コードUTF-8で、CSVを出力する話になります。

そのCSVのとある列では、文字列を60バイト以内に収める仕様でした。

ご存じの通り、UTF-8では1文字あたり1~4バイトのふり幅があります。
日本語は、だいたい3バイトの文字幅ですよね。

この3バイトや4バイトの文字が存在する事で、60バイト幅以内で文字を切り取る方法に躓く事になりました。
今回は、反省と備忘を兼ねて記事にしたいと思います。

基本の振り返り

本題に入る前に、ObjectScriptで文字を切り取る方法を確認したいと思います。

$extract 単独使用

文字を切り取るコマンドとしては「$extract」になると思います。

// 文字列の切り出し
s val = $e(str, from, to)

// 文字列の置換 ※今回は割愛します
s $e(str, from ,to) = val

文字の切り取り方はこんな感じ。

toを省略したら、1文字のみの切り出しになります。
「*」は文字列の最後を示します。

s str = "abcあいう" // 全体で6文字
w $e(str) // a -- 最初の文字
w $e(str, 2) // b -- 2文字目
w $e(str, *) // う -- 最後の文字
w $e(str, *-3) // c -- (6-3)文字目
w $e(str, 2, 4) // bcあ -- 2文字目から4文字目
w $e(str, 4, *) // あいう -- 4文字目から最後まで
w $e(str, *-2, *) // あいう -- (6-2)文字目から最後まで

$extractは、バイト数ではなく「文字数をカウント」して切り取ります。

$wextract

$extractは、サロゲート・ペアを認識しません。
そのためサロゲート・ペアが含まれている場合は、$wextractを使用する必要があります。
 ※「w」が付きます。

使用感は$extractと変わりません。

サロゲート・ペアに関しては下記記事を参照してください。

$extract(str, from, $zposition(str, field, pitch))

$extractの「to」に$zpositionを設定する事で、指定のフィールド幅に収まるように切り取ります。

$zpositionのコマンドは、「field」に収める事ができる「str」の「文字数」を返します。
pitchの初期値=2

s num = $zposition(str, field, pitch)

$zpositionは、バイト数をカウントするのでは無く、「フィールド幅に合わせた文字数をカウント」して返します。

【文字の幅】
下記の様に、半角「a」はピッチ=1, 全角「あ」はピッチ=2になります。

下記例では、出力するフィールド幅が「5」の場合、2.5を返します。

s str = "あいうえお"
s filed = 5
w $zposition(str, filed) // 2.5 が返る

$extractと組み合わせると、フィールド幅=5に収まる文字列を取得することが出来ます。
 ※ from, toも整数しか処理しない

s str = "あいうえお"
s filed = 5
s num = $zposition(str, filed) // 2.5 が返る
w $e(str, 1, num) // あい

$zpositionと$extractの組み合わせは、指定の「フィールド幅に合わせた文字列」を返すので、本題である「バイト数」での切り取りとは異なります。

残念ながら、この手法は使えません。

$zwidth

$zpositionとセットで使用されるコマンドを、併せて紹介します。

$zwidthは、文字の合計幅を返します。

s str = "あいうえお"
w $zwidth(str) // 10 が返る

s str = "aあbいcう"
w $zwidth(str) // 9 が返る

本題

文字数、フィールド幅と振り返ってきましたが、目的はバイト数での切り取り方法になるため、今までの方法は使えません。

そこで今回使用するコマンドは、「$zconvert ($zcvt)」になります。

■サンプルPG

s str = "あいうえおかきくけこさしすせそたちつてとなに" // utf-8 66バイト

s cnvStr = $zcvt(str, "O", "UTF8")
w $l(cnvStr) // 66が返る

s cnvStr = $e(cnvStr, 1, 60) // 60文字での切り出し

w $zcvt(cnvStr, "I", "UTF8", handle) // あいうえおかきくけこさしすせそたちつてと
処理の流れ
  1. 3行目:UTF-8で出力エンコードを実行
  2. 6行目:1~60文字で切り取り
  3. 8行目:UTF-8で入力エンコードを実行
    ※この時、文字にならない半端者は「handle」に格納される

入力エンコードを実行した時、第4引数(handle)を指定しないと、末尾に変換しきれないゴミが付与される事になります。

s str = "あいうえお"

s cnvStr = $zcvt(str, "O", "UTF8")
s cnvStr = $e(cnvStr, 1, 7) // 「う」の途中で切り取る事になる

//---------------------------------------------
// handle未指定
w $zcvt(cnvStr, "I", "UTF8") // あい? > 中途半端なゴミ「?」が付く

// handle指定
w $zcvt(cnvStr, "I", "UTF8", handle) // あい
w handle // ã > 「う」の残骸

成程、$zconvertですかーーーーー!
盲点でした。

まだまだ精進が足りていません。

おわりに

いかがだったでしょうか。

今回の対応は、文字コードUTF-8にて「指定バイト数内に収めて出力する」という、一見シンプルながら奥の深い課題でした。

今回の対応を通じて、さまざまな学びがありました。

これらの文字列操作は、今後の開発でも役に立つ知見になりそうです。

この記事が、同様の問題に直面している方々の参考となり、少しでもお役に立てれば幸いです。
最後までお読みいただき、ありがとうございました。