Vim scriptで、文字が漢字かどうかを判定する方法

660 views
Skip to first unread message

KIHARA Hideto

unread,
May 15, 2013, 9:39:30 AM5/15/13
to vim...@googlegroups.com
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)

Yasuhiro MATSUMOTO

unread,
May 15, 2013, 10:20:45 AM5/15/13
to vim...@googlegroups.com
vim-jp.org の記事にしてはどうでしょうか?
> --
> このメールは Google グループのグループ「vim_jp」の登録者に送られています。
> このグループから退会し、メールの受信を停止するには、vim_jp+un...@googlegroups.com にメールを送信します。
> このグループに投稿するには、vim...@googlegroups.com にメールを送信してください。
> http://groups.google.com/group/vim_jp?hl=ja からこのグループにアクセスしてください。
> その他のオプションについては、https://groups.google.com/groups/opt_out にアクセスしてください。
>
>
>


--
- Yasuhiro Matsumoto

Taro MURAOKA

unread,
May 15, 2013, 8:14:47 PM5/15/13
to vim...@googlegroups.com
どうしても正規表現でやる必要がありますか?

もしも違うのであれば全漢字
(常用でも5000字くらいですから、たぶん1万字程度で十分)
を dict に突っ込んであげれば良いんじゃないでしょうか。

イメージ的にはこんな感じ。

let kanji['亜'] = 1
let kanji[`安'] = 1

判定はこんな感じですね。

if has_key(kanji, ch)
  echo "has kanji!"
endif

実際にはテキストファイル kanji.txt に1行1文字に買いといて

let kanji = {}
for ch in readline('kanji.txt')
  let kanji[ch] = 1
endfor

とすればよいでしょう。


どうしても正規表現ということであれば

> + ASCII・記号・ひらがな・カタカナ以外、という反転させた正規表現を使用 

がかける手間とやりたいことのバランスが一番取りやすいでしょう。


以上です。

KIHARA Hideto

unread,
May 16, 2013, 10:07:42 AM5/16/13
to vim...@googlegroups.com
On Wed, May 15, 2013 at 05:14:47PM -0700, <koron....@gmail.com> wrote:
> どうしても正規表現でやる必要がありますか?

無いです。

> もしも違うのであれば全漢字
> (常用でも5000字くらいですから、たぶん1万字程度で十分)
> を dict に突っ込んであげれば良いんじゃないでしょうか。

なるほど、この方法は思いつきませんでした。

ついでにDictionary構造の場合、他の情報も入れたくなりますね。
JIS第1/2/3/4水準とか、常用漢字かどうかとか、Unicode nameや、
訓読み、音読み、意味等の情報をUnicode Character Database等から入れて、
autoloadするファイルとして用意しておくといろいろ使えていいのかもしれません。
# FirefoxのCharacter Identifier addonや、
# EmacsのM-x describe-char(C-u C-x =)のような機能を作るために使ったり。

例えば、autoload/kanji.vimというファイルを作って、
各漢字のデータを以下のように登録。
let kanji#data['あ'] = { 'type': '2-byte Hiragana', 'jislevel': 0,
\ 'name': 'HIRAGANA LETTER A'}
let kanji#data['亜'] = { 'type': '2-byte Kanji', 'jislevel': 1, 'joyo': 1,
\ 'name': 'CJK IDEOGRAPH-4E9C', 'on': 'ア', 'kun': 'つぐ'}

On Wed, May 15, 2013 at 11:20:45PM +0900, <matt...@gmail.com> wrote:
> vim-jp.org の記事にしてはどうでしょうか?

かなりニッチな話だと思うので、
ぐぐれば見つかる形になる現状程度でいいかと思ってます。

# 経緯: 正規表現で漢字判定をやろうとしたら、E16エラーが出て、
# E16のヘルプを見ても理由がわからなくて、その時はあきらめ。
# 別プラグイン作成時に同じく漢字判定が必要になった時はchar2nr()で作成。
# 他にいい方法がないかそのうちMLあたりで聞いてみようと思いつつ、
# 別件でヘルプを見てる時に|/collection|の制約の記述に気付いたので、
# 同じことにはまる人がいるかもしれないと思って少し整理。

# その他漢字関係ネタ(今のところ作成予定無し。上のautoloadファイルと同じく):
# filetypeがmailの場合、機種依存文字などのISO-2022-JP以外の漢字があったら、
# syntax highlightで強調表示。
# (今どきutf-8でメールしてもほとんど問題ない気もしますが。)
# 常用漢字を色分け表示するJeditXエディタ用の設定例は以下にありました。
# http://d.hatena.ne.jp/n-yuji/20120229/p1

KIHARA Hideto

unread,
May 31, 2013, 7:53:29 PM5/31/13
to vim...@googlegroups.com
On Wed, May 15, 2013 at 10:39:30PM +0900, <de...@m1.interq.or.jp> wrote:
> # 参考: VimWiki「非アスキー文字を検索する」http://vimwiki.net/?RegexQA%2F2
> # では、[^\t -~]。[^[:print:]]と同じ。^L等も除く場合は[^[:print:][:cntrl:]]

ここは正しくなかったので訂正です。
[^[:print:]]と同じではありませんでした。
[^[:print:]]だと、一部の漢字/ひらがな/記号がマッチしないので。
# 逆に言うと、[[:print:]]で一部の漢字/ひらがな/記号にマッチするので、
# 困ることがありそうな気もします。

* &enc=utf-8環境だと、[^[:print:]]で以下の文字がマッチしない。
´(0xb4)¨(0xa8)±(0xb1)×(0xd7)÷(0xf7)°(0xb0)¢(0xa2)£(0xa3)
§(0xa7)¬(0xac)¶(0xb6)

* &enc=euc-jp環境だと、[^[:print:]]で以下の文字がマッチしない。
# 逆に言うと、[[:print:]]で以下の文字がマッチする。
■(0xa2a3)ぅ(0xa4a5)Η(0xa6a7)┤(0xa8a9)葦(0xb0b1)桶(0xb2b3)患(0xb4b5)
況(0xb6b7)弦(0xb8b9)沙(0xbabb)悉(0xbcbd)梢(0xbebf)請(0xc0c1)唾(0xc2c3)
津(0xc4c5)毒(0xc6c7)班(0xc8c9)碧(0xcacb)麺(0xcccd)力(0xcecf)佰(0xd0d1)
厶(0xd2d3)壞(0xd4d5)嶐(0xd6d7)慵(0xd8d9)无(0xdadb)槿(0xdcdd)渤(0xdedf)
珀(0xe0e1)矣(0xe2e3)粤(0xe4e5)肄(0xe6e7)蓍(0xe8e9)裨(0xeaeb)跋(0xeced)
鈿(0xeeef)韵(0xf0f1)鴦(0xf2f3)

* &enc=cp932環境だと、[^[:print:]]で以下の文字がマッチしない。
> # また、[^\x00-\xff]だと、&enc=cp932環境で、なぜか以下の文字がマッチしないので
> # ≠(0x8182)ヤ(0x8384)炎(0x898a)旧(0x8b8c)克(0x8d8e)署(0x8f90)葬(0x9192)
> # 灯(0x9394)楓(0x9596)利(0x9798)劒(0x999a)屆(0x9b9c)撼(0x9d9e)泛(0x9fa0)
> # 珮(0xe0e1)粤(0xe2e3)蒟(0xe4e5)跚(0xe6e7)韜(0xe8e9)

* 新正規表現エンジンの場合(Vim 7.3.970以降)
上記は&regexpengine=1の場合ですが、
&regexpengine=0として新正規表現エンジンを使った場合は動作が違って、
[^[:print:]]では漢字/ひらがなにマッチしなくなります。
[[:print:]]で非アスキー文字もマッチするので。
# この動作はこれでもいいのですが、
# 正規表現エンジンによって動作が変わると、修整の必要なscriptがあるかも。
## また、[[:graph:]]が非アスキー文字にマッチしないのは統一感がない気も。

Taro MURAOKA

unread,
Jun 10, 2013, 11:24:03 AM6/10/13
to vim...@googlegroups.com
vim-jp/issues に登録されました。

https://github.com/vim-jp/issues/issues/413
Reply all
Reply to author
Forward
0 new messages