やりたいこと
名前の環境依存文字を常用漢字に置換する処理の実装。
Shift-JISに存在しない(Unicodeだけに存在する)環境依存文字も対象に取る。
事前知識
Shift-JISの構造
Wikiからの参照
「全角文字は2バイト固定」で「バイトの先頭は81~9FまたはE0~EF」である。
JIS X 0201を1バイトで、JIS X 0208を2バイトで符号化する可変幅文字符号化方式。2バイト文字は、第1バイトに8116-9F16またはE016-EF16の47通り、第2バイトに4016-7E16または8016-FC16の188通りを用いる。
UTF-16の構造
こちらもWikiからの参照
- UTF-16は2バイト、または4バイトの値を取りうる。
- 2バイトの場合、バイトの先頭は「00~D7」または「E0~FF」
コードポイントは4桁の16進数で表される。(YYYY) - 4バイトの場合、1,2バイト目の先頭は「D8~DB」、3,4バイト目の「DC~DF」
コードポイントは5桁の16進数(XYYYY)
2文字を組み合わせて表現する手法をサロゲートペアと呼ぶ
BMPに含まれる
U+0000
–U+D7FF
とU+E000
–U+FFFF
は、そのまま符号単位1つで表す。BMP以外のU+10000
–U+10FFFF
は、表のようにビットを配分して、符号単位2つで表す。このとき使われる、
U+D800
–U+DFFF
の符号位置を、代用符号位置(Surrogate Code Point)と呼び、BMP外の1つの符号位置を表す連続した2つの代用符号位置のペアをサロゲートペアと呼ぶ。代用符号位置に使うため、BMPのこの領域には文字が収録されておらず、UTF-16以外のUTF-8、UTF-32では使用されない。
次に、4バイト文字(サロゲートペア)からなる漢字の一覧は以下の通り。
珍しい苗字もわずかながらに含まれる。
- CJK統合漢字拡張B(20000~2A6DF)【ex.𠀋、𡈽】
- CJK統合漢字拡張C(2A700~2B73F)
ソースコード
' 対応表のシート名および開始行
Private Const SHEET_NAME = "Master"
Private Const BEGIN_ROW As Long = 3
' 常用漢字と異体字の対応表
Private dic As Dictionary
' 対応表を初期化する
' 前提条件:A列に異体字、B列に常用漢字を入力したシートの用意
Private Sub InitMapping()
Dim sh As Worksheet
Set sh = ThisWorkbook.Worksheets(SHEET_NAME)
Dim last_row As Long
last_row = sh.Range("A" & BEGIN_ROW).End(xlDown).Row
Set dic = New Dictionary
Dim i As Long
For i = BEGIN_ROW To last_row
Dim c As String
Dim code As Long
c = sh.Cells(i, 1)
code = WorksheetFunction.Unicode(c)
Call dic.Add(code, sh.Cells(i, 2))
Next
End Sub
' 異体字を対応する常用漢字へ置換する
Public Function ConvertCommonKanji(name As String) As String
Const TEMP_FILL_CHAR = &H0
If (dic Is Nothing) Then
Call InitMapping
End If
Dim i As Long
For i = 1 To Len(name)
Dim c As String
Dim code As Long
c = Mid(name, i, 1)
code = AscW(c)
' 4バイト文字である場合 (上位サロゲートで判定)
If (&HD800 <= code And code <= &HDBFF) Then
' 4バイト文字の場合、AscW()では対応できないため別関数を使用する
code = WorksheetFunction.Unicode(Mid(name, i, 2))
' 2文字置換する
If (dic.Exists(code)) Then
' 文字数の一致する代入しかできないため、あとでReplaceで取り除く
Mid(name, i, 2) = dic.Item(code) & chr(TEMP_FILL_CHAR)
End If
i = i + 1
' 2バイト文字である場合
Else
' 1文字置換する
If (dic.Exists(code)) Then
Mid(name, i, 1) = dic.Item(code)
End If
End If
Next
name = Replace(name, chr(TEMP_FILL_CHAR), "")
ConvertCommonKanji = name
End Function
コード解説
AscWの代替
事前準備のところで、バイト数について言及したのには理由がある。
Shift-JISは2バイト固定だが、UTF-16の場合は4バイトを取る場合がある。
AscWを使うとUnicodeのコードポイントを取得することができるが、引数には -32768 から 32767までの65535通り(2バイトの範囲)でしか文字を取得できない。
そのため、例えば2バイトを超えた範囲-32900や40000のような値を渡すと実行時エラーが発生する。
一方で、シート関数で用意されているUnicode()であれば4バイト文字にも対応している。
そのため、4バイト文字のコードポイントを求める場面ではWorksheetFunction.Unicode()
を使用している。
4バイト文字の判定
エクセルではセルの文字列を取得する際、4バイト文字は自動的に2文字に分割して参照される。そのため、2バイトであれば1文字置換を、4バイトであれば2文字置換をと条件分岐が必要になる。
UTF-16の仕様として、先頭バイト「D8~DB」で判定を取ることができる。
実行速度(おまけ)
- 対応表の件数:300件
人名漢字の置換を想定しており、環境依存文字を全て網羅するわけでない。 - 入力件数:3,000,000行
- 入力文字長:10桁 / 行(置換される文字を必ず1桁持つ)
実行速度は3,000,000行で約10秒。
数百~数千程度であれば全然動く。