##はじめに
前回の投稿で「異体字を簡単に入力する」方法を紹介しました。
しかし、異体字や旧字体の安易な使用は
・環境に依存する
・「異体字セレクタ」は発見や除去が難しい
・文字列処理でトラブルの原因になり得る
など、問題を起こす可能性も高いので使用の際は要注意です。
前回の投稿のカバーと注意喚起のため、自身が遭遇した実例を基に記事にまとめました。
##作業環境ほか
OS : Windows 10 Pro
Excel : Excel for Microsoft 365 MSO 32bit
PowerShell : Ver. 7.2.1
##Excelでの問題事例
###(1) LEN関数 および LENB関数での問題
Excelで異体字・旧字体を使用した場合に起きやすい「想定外(見た目と結果が異なる)」の事例です。
LEN
関数やLENB
関数は、それぞれ「文字数」や「バイト数(全角2バイト・半角1バイト)」を返すとの解説が一般的ですが・・・。
【図1】
B,C列はA列に対するLEN
関数およびLENB
関数の結果です。
「想定外」の部分には赤枠をつけています。
なお、A列のフォントは「游明朝」、2文字目(田・藤)は「通常の文字」です。
もちろん「空白文字」の混入は無く、編集時もカーソルは2文字分しか移動しません。
Google Drive にデータ(異体字問題.xlsx)を置いておきます。
Googleスプレッドシートが開きますが、ダウンロードは可能です。
(ちなみに、GoogleスプレッドシートはLENB
関数の結果が異なっています。)
さて、先頭文字が問題を起こしているので、CODE
関数とUNICODE
関数、念のため、VBAのLen
関数とLenB
関数の戻り値も確認してみます。(下図【図2(a)】)
セルA4,A5は、見た目が同じなのに文字コードは異なります。また、VBAのLenB
関数はワークシート関数のLENB
関数と異なる結果を返しますが、本題から逸れますのでコメントは控えます。
【図2(a)】 ※CODE
関数は、JISコードが存在しない場合 0x3F('?') を返します。(一部例外あり)
数式は以下のとおりです。
【図2(b)】 ※ F,G列は、次のユーザー定義関数です。
Function vba_len(c)
vba_len = Len(c.Value)
End Function
Function vba_lenb(c)
vba_lenb = LenB(c.Value)
End Function
先頭文字だけでは情報不足なので、セル内の全文字列をPowerShellで確認します。
セルA2:A14をデータをクリップボードにコピーして、次のワンライナーを実行します。
当記事に載せたPowerShellスクリプトの多くは、Windows PowerShell 5 で動作しません。
また、データの受け渡しにクリップボードを使用しますが、ExcelとPowerShellの相性問題(詳細は過去の投稿をご参照ください)から、PowerShell 7 の使用を前提とします。
【スクリプト1】(セル検査)
(gcb) -ne '' | %{($_.EnumerateRunes().Value | %{"U+{0:X4}" -f $_}) -join ' '} | scb
クリップボードの文字列をコードポイントで取得し、クリップボードに戻す単純なスクリプトです。String.EnumerateRunes
メソッドは .NET Core 以降の機能です。
スクリプト実行後、セルF2でペーストしたものが下図【図3】です。
【図3】
セルA3,A4,A7には異体字セレクタが使われていることがわかります。
結局、異体字セレクタの有無にかかわらず、LEN
関数およびLENB
関数(VBAも含め)は「想定外」となる可能性があり、あてにできません。
特に人名に使われる異体字・旧字体は問題が発生する可能性が高くなります。
ちなみに、下図【図4】を見ると「UnicodeがU+FFFFに収まっているか?」、「JISコードが割り当てられているか?」の2条件からLEN
関数およびLENB
関数の結果は推測できそうです。
【図4】
###(2) 印刷や表示が乱れる可能性がある。
下図【図5(a)】でB,C列はA列の値を参照し、配置やフォントを変更したものです。
一部のセルは影響を受けています。(赤枠部)
【図5(a)】
このシートの印刷プレビュー画面【図5(b)】でも、異常が確認でき、実際の印刷結果にも不具合が生じます。特に、異体字セレクタとして「字形選択子」(U+FE0X)を使った場合には、表示や印刷、pdf化などに問題が生じます。(【図5(c)】【図5(d)】)
このような問題は、原因の特定に苦労しそうです。
【図5(b)】 印刷プレビュー
【図5(c)】 印刷でpdf化「Microsoft Print to PDF」
【図5(d)】 保存でpdf化「名前をつけて保存 - ファイルの種類:PDF」
##対策について
上記の事例をベースにPowerShellを使った対応手段をいくつか示します。
###(1) 異体字セレクタの除去
例示するスクリプトは、人名データを想定して、漢字・ひらがな・カタカナ、長音記号「ー」・同音の繰り返し記号「ゝ」「ゞ」「々」や空白文字で構成されるデータを処理します。
Rune.Is~
メソッドを使った異体字セレクタ除去例です。
Excelでコピーしたセルから、「文字」「空白文字(改行文字含む)」以外を除去してクリップボードに戻します。
【スクリプト2】(異体字セレクタの除去)
-join ((gcb -raw).EnumerateRunes() | ?{[Text.Rune]::IsLetter($_) -or [Text.Rune]::IsWhiteSpace($_)} | %{$_.ToString()}) | scb
【図1】のセルA2:A14のデータで処理してB列にペーストしました。
結果を確認するため、あらためてB列(B2:B14)をコピーし、先の【スクリプト1】でデータを検査します。(C列)
【図3】と比較して、異体字セレクタが除去されています。
ちなみに、上記スクリプトは、㈱(U+3231)などの記号も除去します。
「㈱」を残したい場合は判断条件を追加します。
ついでに、「ゞ」は文字扱いであり、除去されないことも確認します。
# ㈱が除去される
-join ("いすゞ自動車㈱".EnumerateRunes() | ?{[Text.Rune]::IsLetter($_) -or [Text.Rune]::IsWhiteSpace($_)} | %{$_.ToString()})
# いすゞ自動車
# ㈱が除去されないようにする
-join ("いすゞ自動車㈱".EnumerateRunes() | ?{[Text.Rune]::IsLetter($_) -or [Text.Rune]::IsWhiteSpace($_) -or [Text.Rune]::IsSymbol($_)} | %{$_.ToString()})
# いすゞ自動車㈱
###(2) LEN関数の代替手段
LEN
関数について、先の推測が正しいとすれば、ワークシート関数やVBAで「正しい」文字数を得ることは可能かと思いますが、個人的には気乗りしません。
関数にこだわらなければ、PowerShellで見た目どおりの「正しい」文字数を取得できます。もちろん、異体字セレクタの有無に関係なく機能します。
【スクリプト3】(見た目どおりの文字数を取得)
(gcb) -ne '' | %{[Globalization.StringInfo]::new($_).LengthInTextElements} | scb
【図1】のデータに手を加えて、LEN
関数と上記スクリプトの結果(C列)を比較してみます。
C列は見た目どおりの文字数を返しています。
###(3) Unicode正規化
異体字・旧字体の中でも「CJK互換漢字」「CJK互換漢字補助」の範囲に割り当てられた文字は、Unicode正規化が可能です。(「﨑」(U+FA11)などの例外あり)
異体字が原因で検索や並べ替えに不都合が生じるような場合、一考の余地はあります。
【図1】のセルA5の「梅」(U+FA44)、A9の「塚」(U+FA10)などが該当します。
「梅」(U+6885)を検索するとA2~A4は引っかかりますが、A5はスルーされます。
.NETではString.Normalize
メソッドを使ってUnicode正規化を実現できるので、PowerShellで確認してみます。
([int][char]"`u{fa44}".Normalize()).ToString("X")
# 6885
([int][char]"`u{fa10}".Normalize()).ToString("X")
# 585A
梅(U+FA44) は 梅(U+6885) に、塚(U+FA10) は 塚(U+585A) に正規化されています。
必要に応じて.Normalize()
を追加するだけなので手間はかかりません。