# uniseg-python

## Unicode テキスト境界処理ライブラリ for Python

Unicode 文字列を単語や行として適切に分割する。

公開:
  2012-05-20

### 1. インストール

最新版は `https://bitbucket.org/emptypage/uniseg-python` から入手できます。

アーカイブを展開した後、コマンドラインから `python setup.py install` してください。

### 2. 用語

このモジュール（と Unicode）では、テキストの分割について次のような用語を使います。

`unicode` オブジェクト

  Python の Unicode 文字列です。

  その内部表現は Python インタプリタをビルドした条件によって異なり、narrow と wide とがあります。前者は Unicode 文字列を 16 ビット単位で、後者は 32 ビット単位でメモリに格納しています。

  Unicode に収録される文字の数は 16 ビットの範囲を超えています。このため、narrow ビルドでは 16 ビットの範囲外の文字はふたつの 16 ビット表現を組み合わせて表現します（サロゲート・ペア）。narrow ビルドでは、文字列に格納されている文字の数と、unicode オブジェクトとしての長さ（`len(s)` の値）が一致しないことがあります。たとえば U+20B9F は Python では `u'\U00020b9f'` ですが、narrow ビルドでは `len(u'\U00020b9f')` は `2` です。また `list(u'\U00020b9f')` の結果は `[u'\ud842', u'\udf9f']` になります。厳密には、narrow ビルドでは Unicode 文字列を一文字ずつ処理するのに `list()` や `for` 文をそのまま使っては不適切なのです（それで問題ない処理が多いことも事実ですが）。

  wide ビルドでは `len(u'\U00020b9f')` は `1` であり、`list(u'\U00020b9f')` の結果はそのまま `[u'\U00020b9f']` になります。

  自分の使っている Python インタプリタの Unicode 実装が narrow か wide かは `sys.maxunicode` の値を見ればわかります。narrow ビルドではこの値が 16 ビットの最大値である 65535 になっています。wide ビルドではこれより大きい値が格納されています。

コード・ポイント (code point)

  Python の unicode 文字列が上記のように実装によってその単位が異なる可能性があるのに対して、コード・ポイントというのは Unicode の文字セットに収録されている文字ひとつひとつに与えられている論理的な番号のことをいいます。コードポイントはふつう U+XXXX ないし U+XXXXX の形式で表記されます。

  コードポイントは論理的な概念なので、Python の Unicode 実装に関わらず、Unicode の文字と一対一で対応しています。`u'\U00020b9f'` は Python で `len(u'\U00020b9f')` がいくつであろうと、コード・ポイントでいえば U+20B9F というひとつのコード・ポイントです。

  Unicode 文字列の処理は、基本的にこのコード・ポイント単位になります。Unicode で「文字」といえば、単一のコード・ポイントを指すことがほとんどです。後述のグラフィム・クラスタとの混同を避けるために「文字」という言葉を使わずにこの「コード・ポイント」という用語をあえて使うこともあります。

  wide ビルドの Python では、長さ 1 の unicode オブジェクトがすなわち 1 コード・ポイントに対応しています。

グラフィム・クラスタ (grapheme cluster)

  Unicode の「文字」といえばふつう単一のコード・ポイントのことですが、コンピュータの利用者にとってはひとつのコード・ポイントが必ずしもひとつの「文字」であるとは限りません。

  たとえば、アイヌ語の表記につかわれる、小書きの「プ」に相当する文字があります。これは日本の規格である JIS X 0213 にはひとつの文字として他の文字と同様にひとつのコードが割り当てられていますが、Unicode ではこの文字を表す単一のコード・ポイントはありません。小書きの「フ」に相当する U+31F7 (KATAKANA LETTER SMALL HU) に U+309A (COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK) を続けて表します（個人的には、「プ」を小書きにするのと、小書きの「フ」に半濁点を付けるのとは等価でないような気がしますが、とにかくそういうことになっています）。しかし、テキストエディタなどの表示の上では、ユーザーはこれをひとつの「文字」と認識しています。デリート・キーによる削除や矢印キーによるカーソルの移動などでは、これらのコード・ポイントは一体のものとして扱われなければいけません。

  このような「利用者にとっての一文字」の単位に相当するのが「グラフィム・クラスタ」です。エディタの文字数のカウント機能などでは、このグラフィム・クラスタの数を数える必要があります。

  グラフィム・クラスタは言語によって解釈が異なる可能性があります。たとえば、チェコ語やスロバキア語ではアルファベットのひとつとして ‘ch’ を使います。

### 3. クラス

#### 3.1. `uniseg.TextWrapper` クラス

折り返しエンジンの基底クラス。

#### 3.2. `uniseg.TTWrapper` クラス

等幅フォントの折り返しエンジン。

### 4. 関数

#### 4.1. コード・ポイントを扱う関数

`uniseg.ord(c, index=None)`

  単一のコード・ポイントを表す Unicode 文字列 c に割り当てられた整数値を返す。組み込み関数 `ord()` と同様だが、c がサロゲート・ペアで表わされる U+10000 以上の文字の時、narrow ビルド環境（`len(c) == 2`）でも例外を起こさない。

  index が指定されると、第 1 引数 c は単一の Unicode コードポイントではなく、それ以上の長さを持ちうる Unicode 文字列と解釈され、`c[index]` に位置するコード・ポイントの値を返す。

`uniseg.unichr(cp)`

  整数値 cp に相当するコード・ポイントを表す Unicode 文字列を返す。組み込み関数 `unichr()` と同等だが、cp が 0x10000 以上でも例外を起こさない。

`uniseg.code_point(s, index=0)`

  Unicode 文字列 s について、`s[index]` の位置のコード・ポイント文字列を返す。単純に `s[index]` の値を使うと、narrow ビルド環境ではサロゲート・ペアで表されている U+10000 以上のコード・ポイントを分断してしまうおそれがある。

  index は省略すると 0 とみなされ、文字列先頭のコード・ポイントを返す。

`uniseg.code_points(s)`

  Unicode 文字列 s に含まれているコード・ポイントを列挙する（イテレータ）。narrow ビルドの Python インタプリタでは、`list(s)` はサロゲート・ペアで表現された U+10000 以上の文字をふたつの無効な文字に切り離してしまうが、この関数では適切に保たれる。

#### 4.2. 文字列を分割する関数

以下、説明が下っていくに従って、詳細でレイヤの低い処理になっていきます。基本的な処理であれば、「Unicode 文字列を分割する関数」「Unicode 文字列を分割する境界を取得する関数」のどちらかで十分なことも多いでしょう。

`uniseg.grapheme_clusters(s, tailor=None)`

  Unicode 文字列 s を grapheme cluster 単位に分割して列挙する（イテレータ）。

`uniseg.words(s, tailor=None)`

  Unicode 文字列 s を単語単位に分割して列挙する（イテレータ）。

`uniseg.sentences(s, tailor=None)`

  Unicode 文字列 s を文単位に分割して列挙する（イテレータ）。

`uniseg.line_break_units(s, legacy=False, tailor=None)`

  文字列 s を行分割可能な単位に分割して列挙する（イテレータ）。

  legacy を `True` にすると、旧来の文字コードでいわゆる全角文字として扱われていたギリシャ文字やキリル文字、一部の記号などを漢字と同等にみなし、これらの単語間での分割が許可されるようになります。

#### 4.3. 文字列を分割する境界を取得する関数

`uniseg.grapheme_cluster_boundaries(s, tailor=None)`

  Unicode 文字列 s の grapheme cluster 境界のインデックスを列挙する（イテレータ）。`0` から文字列末尾の境界（`== len(s)`）まで。

`uniseg.word_boundaries(s, tailor=None)`

  Unicode 文字列 s の単語境界のインデックスを列挙する（イテレータ）。`0` から文字列末尾の境界（`== len(s)`）まで。

`uniseg.sentence_boundaries(s, tailor=None)`

  Unicode 文字列 s の文境界のインデックスを列挙する（イテレータ）。`0` から文字列末尾の境界（`== len(s)`）まで。

`uniseg.line_break_boundaries(s, legacy=False, tailor=None)`

  行分割を行ってもよい境界のインデックスを列挙する（イテレータ）。`0` から文字列末尾の境界（`== len(s)`）まで。legacy の意味は `line_break_breakables()` に同じ。

#### 4.4. 分割境界のカスタマイズ

##### 4.4.1. 文字列の分割情報を取得する関数

`uniseg.grapheme_cluster_breakables(s)`

  Unicode 文字列 s のどこが grapheme cluster 境界として分割可能かを列挙する（イテレータ）。1 であれば分割可、0 であれば分割不可を表す。列挙する要素の数は `len(s)` に等しい。

`uniseg.word_breakables(s)`

  Unicode 文字列 s のどこが単語の境界として分割可能かを列挙する（イテレータ）。

`uniseg.sentence_breakables(s)`

  Unicode 文字列 s のどこが文の境界として分割可能かを列挙する（イテレータ）。

`uniseg.line_break_breakables(s, legacy=False)`

  Unicode 文字列 s のどこが行分割の境界として分割可能かを列挙する（イテレータ）。

##### 4.4.2. 分割情報を扱う関数

`boundaries(breakables)`

  breakables の情報を元に文字列の境界インデックスを列挙する（イテレータ）。境界は 0 から文字列末尾（== len(breakables)）まで。列挙されるアイテムの数は、`len(breakables) + 1` に等しい。

  `grapheme_cluster_boundaries(s)` は `boundaries(grapheme_cluster_breakables(s))` と同等の処理にあたる。`word_boundaries()`, `sentence_boundaries()`, `line_break_boundaries()` についても同様。

`break_units(s, breakables)`

  breakables の情報を元に文字列 s を分割し、各部分文字列を列挙する（イテレータ）。

  `grapheme_clusters(s)` は `break_units(grapheme_cluster_breakables(s))` と同等の処理にあたる。その他の `words()`, `sentences()`, `line_break_units()` についても同様。

#### 4.5. Unicode 分割属性

`uniseg.grapheme_cluster(c, index=0)`

  Unicode 文字 c の Grapheme_Cluster クラスを文字列で返す。戻り値は次のうちのいずれか： `'Other'`, `'CR'`, `'LF'`, `'Control'`, `'Extend'`, `'Prepend'`, `'SpacingMark'`, `'L'`, `'V'`, `'T'`, `'LV'`, `'LVT'`

  index の意味は `code_point()` 関数に同じ。

`uniseg.word_break(c, index=0)`

  Unicode 文字 c の Word_Break クラスを文字列で返す。戻り値は次のうちのいずれか： `'Other'`, `'CR'`, `'LF'`, `'Newline'`, `'Extend'`, `'format'`, `'Katakana'`, `'ALetter'`, `'MidNumLet'`, `'MidLetter'`, `'MidNum'`, `'Numeric'`, `'ExtendNumLet'`

  index の意味は `code_point()` 関数に同じ。

`uniseg.sentence_break(c, index=0)`

  Unicode 文字 c の Sentence_Break クラスを文字列で返す。戻り値は次のうちのいずれか： `'CR'`, `'LF'`, `'Extend'`, `'Sep'`, `'Format'`, `'Sp'`, `'Lower'`, `'Upper'`, `'OLetter'`, `'Numeric'`, `'ATerm'`, `'SContinue'`, `'STerm'`, `'Close'`

  index の意味は `code_point()` 関数に同じ。

`uniseg.line_break(c, index=0)`

  Unicode 文字 c の Line_Break クラスを文字列で返す。戻り値は次のうちのいずれか： `'BK'`, `'CR'`, `'LF'`, `'CM'`, `'NL'`, `'SG'`, `'WJ'`, `'ZW'`, `'GL'`, `'SP'`, `'B2'`, `'BA'`, `'BB'`, `'HY'`, `'CB'`, `'CL'`, `'CP'`, `'EX'`, `'IN'`, `'NS'`, `'OP'`, `'QU'`, `'IS'`, `'NU'`, `'PO'`, `'PR'`, `'SY'`, `'AI'`, `'AL'`, `'H2'`, `'H3'`, `'ID'`, `'JL'`, `'JV'`, `'JT'`, `'SA'`, `'XX'`

  index の意味は `code_point()` 関数に同じ。

### 5. スクリプト

本モジュールを使ったスクリプトを付属しています。いまのところサンプル・アプリケーション的な存在です。

#### `unibreak.py`

ファイルの文字列をコードポイント、文字（grapheme cluster）、単語、行分割単位で分割して一行ずつ出力します。

    usage: unibreak.py [-h] [-e ENCODING] [-l] [-m {c,g,l,s,w}] [-o OUTPUT] [file]

    positional arguments:
      file                  input text file

    optional arguments:
      -h, --help            show this help message and exit
      -e ENCODING, --encoding ENCODING
                            text encoding of the input <cp932>
      -l, --legacy          legacy mode (makes sense only with '--mode l')
      -m {c,g,l,s,w}, --mode {c,g,l,s,w}
                            breaking algorithm <w> (c: code points, g: grapheme
                            clusters, s: sentences l: line breaking units, w:
                            words)
      -o OUTPUT, --output OUTPUT
                            leave output to specified file

#### `uniwrap.py`

ファイルの文字列を指定された桁数で折り返し整形して出力します。漢字や仮名などはいわゆる全角文字としてアルファベットなどの倍の幅を持つものとして計算されます（早い話が日本語でも大丈夫ってことです）。

    usage: uniwrap.py [-h] [-e ENCODING] [-x] [-j] [-r] [-t TAB_WIDTH] [-l]
                      [-o OUTPUT] [-w WRAP_WIDTH] [-c]
                      [file]

    positional arguments:
      file                  input file

    optional arguments:
      -h, --help            show this help message and exit
      -e ENCODING, --encoding ENCODING
                            file encoding <cp932>
      -x, --expand-tabs     expand tabs to spaces
      -j, --justify         justify lines
      -r, --ruler           show ruler
      -t TAB_WIDTH, --tab-width TAB_WIDTH
                            tab width <8>
      -l, --legacy          treat ambiguous-width letters as wide
      -o OUTPUT, --output OUTPUT
                            leave output to specified file
      -w WRAP_WIDTH, --wrap-width WRAP_WIDTH
                            wrap width <60>
      -c, --char-wrap       wrap on grapheme boundaries instead of line break
                            boundaries

#### `wxwrapdemo.py`

プロポーショナルなフォントでの折り返し処理の例として、wxPython を使った GUI アプリケーションのサンプル・プログラムを同梱しています。

### 6. 履歴

0.5.1 (2012-06-02)
  モジュール名を `uniseg` に。
  `TextWrapper`、`TTWrapper` クラスをモジュール内に移動。あとでドキュメント化すること。
  `wxwrapdemo.py` 追加。
  `breakables` でなく `tailor` 関数でカスタマイズを指定するようにした。あとでドキュメント化すること。
0.5.0 (2012-05-27)
  Sentence boundaries 実装。
  `*_boundaries` は 0 から列挙するようにした。
  `*_breakables` 追加。
  `*_boundaries` および `grapheme_clusters`, `words`, `sentences`, `line_break_units` の結果をカスタマイズできるようにした。
  `ord` 関数に第 2 引数追加。
  Python インタプリタの Unicode 実装が narrow build でも wide build でも動く（はずな）ようにした。Wide build でテストできていません。
0.4.0 (2012-05-20)
  公開。

### 7. ToDos

以下の問題にたいして解決のめどが立ったらバージョン 1.0 にしたい。

- `uniwrap.py` に入っている折り返しエンジンをモジュール本体のほうに取り込み。
- grapheme cluster の分割動作をユーザーがカスタマイズできるように。
- word break の分割動作をユーザーがカスタマイズできるように。
- line break の分割動作をユーザーがカスタマイズできるように。
- sentence break 実装。
- よりよいモジュール名。
- サブモジュール廃止。
- `TextWrapper`、`TTWrapper` の仕様を整理。
- データベースのスリム化。
- Unicode 6.1.0 対応（現在は 5.2.0）。6.2.0 対応。
- `TextWrapper`、`TTWrapper`、`tailor` のカスタマイズ例をドキュメントに。
- より親切なドキュメント。（「Unicode テキスト境界処理の基本」）
- 遅い。現在の 2～10 倍程度の高速化。
- English documents.
- Python 3 対応。
- その他、思い出したら書き足す。

### 8. ライセンス

MIT ライセンスです。

    Copyright (c) 2012 Masaaki Shibata

    Permission is hereby granted, free of charge, to any person obtaining  a
    copy  of  this   software  and  associated   documentation  files   (the
    "Software"), to  deal in  the  Software without  restriction,  including
    without limitation  the rights  to use,  copy, modify,  merge,  publish,
    distribute, sublicense,  and/or  sell copies  of  the Software,  and  to
    permit persons to whom  the Software is furnished  to do so, subject  to
    the following conditions:

    The above copyright notice and this permission notice shall be  included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,  EXPRESS
    OR  IMPLIED,   INCLUDING  BUT   NOT  LIMITED   TO  THE   WARRANTIES   OF
    MERCHANTABILITY, FITNESS FOR A  PARTICULAR PURPOSE AND  NONINFRINGEMENT.
    IN NO EVENT  SHALL THE AUTHORS  OR COPYRIGHT HOLDERS  BE LIABLE FOR  ANY
    CLAIM, DAMAGES OR  OTHER LIABILITY,  WHETHER IN AN  ACTION OF  CONTRACT,
    TORT OR  OTHERWISE, ARISING  FROM,  OUT OF  OR  IN CONNECTION  WITH  THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

（※ 上記条文は `uniwrap.py` による整形例です。`-j -w 72` オプション。）

このページについてのご意見ご感想はゲストブックまたはメール `<mshibata at emptypage.jp>` まで。