銀月の符号

Python 使い見習いの日記・雑記

Python レシピ追加、特定の文字コードで正規表現マッチを行う (鬼車のラッパー ponyguruma の紹介あり)

「029:特定の文字コードで正規表現マッチを行う」を追加。

正規表現に限らず、テキストを扱うにはユニコード文字列を使うようにするのがベター、といった内容。でも、これで終わるのもなんだし、ユニコード文字列への変換を行わずに何とかする方法も提示してみたかった。

ponyguruma (鬼車のラッパー)

そして、鬼車の Python ラッパー ponyguruma が見つかったのでメモ。

バイト列のまま、処理することが可能。

from ponyguruma import *
from ponyguruma.constants import ENCODING_UTF8
r = Regexp(u'文字コード'.encode('utf-8'), encoding=ENCODING_UTF8)
m = r.search(u'特定の文字コードで正規表現マッチを行う'.encode('utf-8'))
print repr(m)
<Match groups: 0, span: (9, 24)>

ユニコード文字列も当然処理できる。この場合は encoding の指定は不要。というか指定してはならない。 ponyguruma が環境に合った encoding 、 ONIG_ENCODING_UTF16_LE, ONIG_ENCODING_UTF32_LE などで処理してくれる*1

from ponyguruma import *
r = Regexp(u'文字コード')
m = r.search(u'特定の文字コードで正規表現マッチを行う')
print repr(m)
<Match groups: 0, span: (3, 8)>

JIS漢字コードの並び順に依存した [亜-腕] のような正規表現でも、エンコーディング次第で意図したとおりに動かすことが可能。これは re モジュールではできないことの 1 つ。

# coding: utf-8

from ponyguruma import *
from ponyguruma.constants import ENCODING_EUC_JP

# [亜-腕] が JIS 第一水準漢字にマッチすることを期待
r = Regexp(u'[亜-腕]'.encode('euc-jp'), encoding=ENCODING_EUC_JP)
m = r.search(u'阿'.encode('euc-jp')) # 第一水準漢字
print repr(m)
m = r.search(u'弌'.encode('euc-jp')) # 第二水準漢字
print repr(m)
<Match groups: 0, span: (0, 2)>
None
>>> import re
>>> re.search(u'[亜-腕]', u'阿')
>>> re.search(u'[亜-腕]', u'弌')
<_sre.SRE_Match object at 0x009EAC60>

この ponyguruma で使用される鬼車正規表現の文法のデフォルトは ONIG_SYNTAX_RUBY をベースにした OnigSyntaxPython 。実装はこうなっている。別のものを使いたければ Regexp の syntax 引数に constants.SYNTAX_RUBY などを渡せば変更可能。

/**
 * The oniguruma syntax for python
 */
static OnigSyntaxType OnigSyntaxPython;

static int
init_python_syntax(void)
{
	int behavior;
	onig_copy_syntax(&OnigSyntaxPython, ONIG_SYNTAX_RUBY);
	behavior = onig_get_syntax_behavior(&OnigSyntaxPython);

	/* use the ruby settings but disable the use of the same
	   name for multiple groups, disable warnings for stupid
	   escapes and capture named and position groups */
	onig_set_syntax_behavior(&OnigSyntaxPython, behavior & ~(
		ONIG_SYN_CAPTURE_ONLY_NAMED_GROUP |
		ONIG_SYN_ALLOW_MULTIPLEX_DEFINITION_NAME |
		ONIG_SYN_WARN_CC_OP_NOT_ESCAPED |
		ONIG_SYN_WARN_REDUNDANT_NESTED_REPEAT
	));
	/* sre like singleline */
	onig_set_syntax_options(&OnigSyntaxPython,
		ONIG_OPTION_NEGATE_SINGLELINE
	);
	return 0;
}

ponyguruma には Python 標準の正規表現モジュール re (sre) の API をまねる ponyguruma.sre が付属している。いつもの感覚で ponyguruma を使いたい場合はこちらで。

import ponyguruma.sre as re
r = re.compile(u'文字コード')
m = r.search(u'特定の文字コードで正規表現マッチを行う')
print repr(m)
<ponygurma.sre.SRE_Match object at 0xb04310>

Visual C++ 2008 用? パッチ

ponyguruma をビルドする際、 _lowlavel.c が Visual C++ 2008 で通らなかったので、微修正している。なので変更点を公開。変数の宣言を関数の頭でするようにしただけ。そもそも、 C って関数の途中で変数宣言できなかったような…。(2009/12/10 17:35 追記。コメントに指摘あり。 C99 規格では宣言できる。)

*** ponyguruma/lib/ponyguruma/_lowlevel_org.c	Tue Dec 08 12:33:31 2009
--- ponyguruma/lib/ponyguruma/_lowlevel.c	Tue Dec 08 12:34:19 2009
***************
*** 128,135 ****
  static int
  init_python_syntax(void)
  {
  	onig_copy_syntax(&OnigSyntaxPython, ONIG_SYNTAX_RUBY);
! 	int behavior = onig_get_syntax_behavior(&OnigSyntaxPython);
  
  	/* use the ruby settings but disable the use of the same
  	   name for multiple groups, disable warnings for stupid
--- 128,136 ----
  static int
  init_python_syntax(void)
  {
+ 	int behavior;
  	onig_copy_syntax(&OnigSyntaxPython, ONIG_SYNTAX_RUBY);
! 	behavior = onig_get_syntax_behavior(&OnigSyntaxPython);
  
  	/* use the ruby settings but disable the use of the same
  	   name for multiple groups, disable warnings for stupid
***************
*** 665,671 ****
  PyMODINIT_FUNC
  init_lowlevel(void)
  {
! 	PyObject *module;
  
  	if (init_python_syntax() < 0)
  		return;
--- 666,672 ----
  PyMODINIT_FUNC
  init_lowlevel(void)
  {
! 	PyObject *module, *version;
  
  	if (init_python_syntax() < 0)
  		return;
***************
*** 688,694 ****
  	Py_INCREF(&MatchStateType);
  	PyModule_AddObject(module, "MatchState", (PyObject *)&MatchStateType);
  
! 	PyObject *version = Py_BuildValue("(iii)", ONIGURUMA_VERSION_MAJOR,
  					  ONIGURUMA_VERSION_MINOR,
  					  ONIGURUMA_VERSION_TEENY);
  	PyModule_AddObject(module, "VERSION", (PyObject*)version);
--- 689,695 ----
  	Py_INCREF(&MatchStateType);
  	PyModule_AddObject(module, "MatchState", (PyObject *)&MatchStateType);
  
! 	version = Py_BuildValue("(iii)", ONIGURUMA_VERSION_MAJOR,
  					  ONIGURUMA_VERSION_MINOR,
  					  ONIGURUMA_VERSION_TEENY);
  	PyModule_AddObject(module, "VERSION", (PyObject*)version);

*1:_lowlevel.c の (UChar *) PyUnicode_AS_UNICODE(pattern) という箇所が不安だったので調査。 PyUnicode_AS_UNICODE マクロの戻り値は環境依存だが、 unsigned short *, unsigned long *, もしくは wchar * 。 これを UChar * 、 OnigUChar * つまり unsigned char * にキャストしてるんだよな。大丈夫なのか? えっと…。うーん…。前後のコードも落ち着いて読んでみたところ、意図したとおりに動く問題ないキャストに見える。杞憂でした。 C 言語忘れてる、まずいぜ。