Vim scriptで、文字が(記号・ひらがな・カタカナでなく)漢字かどうかを
判定したい場面があったのですが、単純な正規表現([亜-熙]等)では駄目だったので、
少し整理してみました。
# 昔、Vimの正規表現における日本語関係の制約についての記述を見た気が
# するのですが、今探しても見つけられなかったので。
* やりたかったこと
+ JavaScriptで記述された正規表現による漢字の判定処理を、Vim scriptに移植したい
# 文字種別(漢字やひらがな等)を使って区切り判定を行うTinySegmenter.jsを移植して
# 文節移動用プラグインjasegment.vimから使用したかった。
+ 辞典検索プラグインeblook.vimで、
指定された文字列そのままでは何も見つからなかった場合、
語尾補正をして検索し直す処理で、最終的には漢字だけにしたい。
「書いた」→「書く」→「書」
* 現象
&encがcp932やeuc-jpの場合は、正規表現で[亜-熙]ですが、
(ここでは単純化のためとりあえずJIS第3・第4水準漢字は考えないことにします。)
/[亜-熙]で検索しようとしても、E16エラーになります。
E16: Invalid range
ヘルプの|/collection|の以下の記述にひっかかるからだと思います。
| Non-ASCII characters can be
| used, but the character values must not be more than 256 apart.
# ヘルプの日本語訳は変なのでpull requestします。
&enc=utf-8の場合は、[一-龠]ですが、同じくE16エラーになります。
# 実際には[一-龠]だといろいろ問題があるようですが:
http://tama-san.com/?p=196
* 対処方法
以下の3つの方法があると思います。
もっといい方法があったら教えてください。
+ char2nr()した値で漢字の範囲かどうかを判定
+ 漢字範囲の正規表現を展開して、-で指定する間隔が256以下になるように変換
+ ASCII・記号・ひらがな・カタカナ以外、という反転させた正規表現を使用
** 例: char2nr()した値で漢字の範囲かどうかを判定
TinySegmenter.jsの移植では、以下のようにしました。
" for &encoding == 'utf-8'
let s:kanjimin = char2nr('一')
let s:kanjimax = char2nr('龠')
if &encoding == 'euc-jp' || &encoding == 'cp932'
" TODO: support JIS X 0213
let s:kanjimin = char2nr('亜')
let s:kanjimax = char2nr('熙')
endif
function s:getctype(str)
let nr = char2nr(a:str)
if nr >= s:kanjimin && nr <= s:kanjimax
return 'H' " 漢字
endif
return 'O'
endfunction
** 例: 漢字範囲の正規表現を展開して、-で指定する間隔が256以下になるように変換
[亜-熙]を以下に展開(euc-jpの場合。見やすいように改行入れてますが本当は1行)。
[亜-院陰-旺横-械海-瓦乾-汽畿-共凶-熊隈-絹県-工巧-昏昆-捌錆-蒔辞-臭舟-小
少-侵唇-整星-善漸-族続-坦担-調諜-典填-洞瞳-之埜-噺塙-氷漂-丙併-忙房-娘
冥-予余-両凌-録論-仞仭-兪兮-丗卉-喘喞-堋堙-嫖嫺-崙崘-彷徃-愴愽-拮拱-敲
數-柬枳-槎寨-殕殞-淮渭-瀟瀰-猥猾-疳痃-瞎瞋-穩龝-簫簽-縟縉-耿耻-舸舳-葷
葫-蛉蠣-袙袢-諂諚-賍贔-轎轗-釛釼-閙閠-鞐鞜-驕驍-鰲鱆-黹黻-熙]
cp932の場合は[亜-滌漾-熙]に分ける必要あり。
# 第1バイトが0x81-0x9fのエリアと0xe0-0xefのエリアに分かれてるので。
[亜-押旺-刈苅-僑兇-犬献-坤墾-痔磁-唱嘗-凄制-息捉-暢朝-陶頭-八鉢-吻噴-脈
妙-溜琉-舒弍-勹匆-垈坡-岾峇-愆惶-攀擽-楸楫-涎涕-滌漾-瓠瓣-磚磽-紕紊-腓
腑-薀薤-襴襷-蹤蹠-鍜鍠-飃飆-鶫鵯-熙]
utf-8の場合も同様に、[一-龠]を以下のExpandKanjiRegex()関数で展開。
" Insert modeで<C-R>=ExpandKanjiRegex('亜','熙')
function! ExpandKanjiRegex(k1, k2)
let k1n = char2nr(a:k1)
let k2n = char2nr(a:k2)
let arr = []
let n = k1n - 1
while n < k2n
let beg = n + 1
let n = beg + 256
if n > k2n
let n = k2n
endif
call add(arr, nr2char(beg) . '-' . nr2char(n))
"call add(arr, printf('\u%04x-\u%04x', beg, n))
endwhile
return join(arr, '')
endfunction
** 例: ASCII・記号・ひらがな・カタカナ以外、という反転させた正規表現を使用
euc-jp・cp932の場合、
[^\x00-\x7f -◇◆-◯0-zぁ-んァ-ヶΑ-ωА-я─-╂]
# [ -╂]だとE16エラーになるので、ひらがな・カタカナ等で指定。
# 本当は、丸付き数字等も外すようにする必要あり。
utf-8の場合(記号はJIS X 0208と0213に含まれる範囲のみ)、
[^\x00-\xff\u0100-\u01ff\u0250-\u02ff\u0300-\u03ff\u0400-\u04ff
\u2000-\u20ff\u2100-\u21ff\u2200-\u22ff\u2300-\u23ff\u2400-\u24ff
\u2500-\u25ff\u2600-\u26ff\u2700-\u27ff\u2900-\u29ff\u3000-\u30ff
\u3100-\u31ff\u3200-\u32ff\u3300-\u33ff\ufe45-\ufe46\uff00-\uffff]
ただし、utf-8で日本語以外も出現する可能性がある場合は、この方法では大変。
* 関連: マルチバイト文字の判定方法
[^\x00-\x7f]
# ASCII文字とマルチバイト文字の境界をtext object区切りとするプラグイン
# textobj-mbboundary.vim作成の際に必要になったので。
# 参考: VimWiki「非アスキー文字を検索する」
http://vimwiki.net/?RegexQA%2F2
# では、[^\t -~]。[^[:print:]]と同じ。^L等も除く場合は[^[:print:][:cntrl:]]
[^\x00-\xff]だと、&enc=utf-8環境で、JIS X 0208の一部記号
(´¨±×÷°¢£§¬¶)がマッチしないので、\xffでなく\x7fにしています。
# また、[^\x00-\xff]だと、&enc=cp932環境で、なぜか以下の文字がマッチしないので
# ≠(0x8182)ヤ(0x8384)炎(0x898a)旧(0x8b8c)克(0x8d8e)署(0x8f90)葬(0x9192)
# 灯(0x9394)楓(0x9596)利(0x9798)劒(0x999a)屆(0x9b9c)撼(0x9d9e)泛(0x9fa0)
# 珮(0xe0e1)粤(0xe2e3)蒟(0xe4e5)跚(0xe6e7)韜(0xe8e9)