Juman × PyKNPで解析結果から引用符(")が消える。

~~~bash:実行環境
$ python3 -V
Python 3.6.4

$ pip3 show pyknp
Name: pyknp
Version: 0.4.1

$ juman -v
juman 7.01
~~~

## 引用符が消える

以下のようなPythonスクリプトで分かち書きを行なった。

~~~Python:tokenizer.py
from pyknp import Juman
t = Juman(jumanpp=False)
text = "彼は\"Hello!\"と言った。"
print("Input : {}".format(text))
tokens = [token.midasi for token in t.analysis(text).mrph_list()]
print("Tokenized : {}".format(tokens))
~~~

~~~bash:実行結果
$ python3 tokenizer.py 
Input : 彼は"Hello!"と言った。
Tokenized : ['彼', 'は', 'Hello!', 'と', '言った', '。']
~~~

よく見るとHello!を囲っていた引用符(")がなくなっている。

レアな現象ではあるもの、オフセットがずれる原因になっていた。

## 一時的な対策
アポストロフィーなら消えないようなので置換する。

~~~Python:tokenizer.pyの一部
text = "彼は\"Hello!\"と言った。".replace("\"","'")
~~~

~~~bash:実行結果
$ python3 tokenizer.py
Input : 彼は'Hello!'と言った。
Tokenized : ['彼', 'は', "'Hello!'", 'と', '言った', '。']
~~~

引用符にこだわりがないならこれで良い。

## コードから原因を推測して直す。

pyknpの中にある[pyknp/juman/morpheme.py](https://github.com/ku-nlp/pyknp/blob/master/pyknp/juman/morpheme.py)において引用符が閉じている場合、引用符を意図的に消していることがわかった。

これは、コマンドで実行したjumanの解析結果のパースに絡んだ処理のよう。
コマンドでjumanを実行すると以下のようになる。

~~~bash:実行結果
$ juman
彼は"Hello!"と言った。
彼 かれ 彼 名詞 6 普通名詞 1 * 0 * 0 "代表表記:彼/かれ 漢字読み:訓 カテゴリ:人"
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
"Hello!" "Hello!" "Hello!" 未定義語 15 その他 1 * 0 * 0 NIL
と と と 助詞 9 格助詞 1 * 0 * 0 NIL
言った いった 言う 動詞 2 * 0 子音動詞ワ行 12 タ形 10 "代表表記:言う/いう 補文ト"
。 。 。 特殊 1 句点 1 * 0 * 0 NIL
EOS
~~~

代表表記の部分を見ると引用符で囲まれているのがわかる。 
引用符内は空白が使用可能であるため、パースをする際引用符で囲まれた範囲内の空白を無視する必要がある。 
そんなわけで引用符で囲まれた範囲を抽出可能なよう設計してあり、また抽出後に引用符は取り除かれるようになっている。
そのため、見出し語の引用符も消されてしまっている。 

[pyknp/juman/morpheme.py](https://github.com/ku-nlp/pyknp/blob/master/pyknp/juman/morpheme.py)の一部のif文を次のように直す。

~~~Python:pyknp/juman/morpheme.pyの一部
if part != "" and char == ' ' and not inside_quotes:
    #if part.startswith('"') and part.endswith('"') and len(part) > 1:
    if part.startswith('"') and part.endswith('"') and len(part) > 1 and len(parts) > 2:
        parts.append(part[1:-1])
    else:
        parts.append(part)
~~~

これで"Hello!" "Hello!" "Hello!"の部分の引用符は消されなくなった。

~~~bash:実行結果
$ python3 tokenizer.py 
Input : 彼は"Hello!"と言った。
Tokenized : ['彼', 'は', '"Hello!"', 'と', '言った', '。']
~~~

治った。やったね。

## ちなみに引用符がらみで他の問題もある。
~~~bash:実行結果
$ python3 tokenizer.py 
Input : 彼は" Hello!" と言った。
Tokenized : ['彼', 'は', '"', '\\ ', 'Hello!" Hello!"', '\\ ', 'と', '言った', '。']
~~~

Hello! Hello!とテンション高い人みたいになってしまった。 
コマンドでjumanを実行した場合はこう。

~~~bash:実行結果
juman
彼は" Hello!" と言った。
彼 かれ 彼 名詞 6 普通名詞 1 * 0 * 0 "代表表記:彼/かれ 漢字読み:訓 カテゴリ:人"
は は は 助詞 9 副助詞 2 * 0 * 0 NIL
" " " 未定義語 15 その他 1 * 0 * 0 NIL
  \  \  特殊 1 空白 6 * 0 * 0 NIL
Hello!" Hello!" Hello!" 未定義語 15 その他 1 * 0 * 0 NIL
  \  \  特殊 1 空白 6 * 0 * 0 NIL
と と と 助詞 9 格助詞 1 * 0 * 0 NIL
言った いった 言う 動詞 2 * 0 子音動詞ワ行 12 タ形 10 "代表表記:言う/いう 補文ト"
。 。 。 特殊 1 句点 1 * 0 * 0 NIL
EOS
~~~

お分りいただけただろうか。

~~~bash:実行結果の一部
Hello!" Hello!" Hello!" 未定義語 15 その他 1 * 0 * 0 NIL
~~~

引用符が来ると次の引用符まで問答無用でまとめられるので、 
Hello!" Hello!" 
Hello!" 未定義語 15 その他 1 * 0 * 0 NIL 
にしかパースされなくなってしまう。 
だからpyknpでの解析結果がおかしくなっている。

これは以下のように[pyknp/juman/morpheme.py](https://github.com/ku-nlp/pyknp/blob/master/pyknp/juman/morpheme.py)の1行を修正する。

~~~Python:pyknp/juman/morpheme.pyの一部
#if inside_quotes and char == ' ' and part == '"':
if inside_quotes and char == ' ' and part[-1] == '"':
    inside_quotes = False
~~~

今までは引用符単体の次に空白がこないと機能しなかったが、引用符の次に空白がこれば良いようにした。

~~~bash:実行結果
$ python3 tokenizer.py 
Input : 彼は" Hello!" と言った。
Tokenized : ['彼', 'は', '"', '\\ ', 'Hello!"', '\\ ', 'と', '言った', '。']
~~~

治った。やったね。

## githubに置いておく。

取り急ぎ[k141303/juman_temporarily](https://github.com/k141303/juman_temporarily)にJumanを継承する形で書いた修正コードを挙げておきます。

~~あとで本家にプルリクしたいと思います。~~
=> お粗末ですが修正させていただきプルリク[Parse bug fixes for Juman analysis results. ](https://github.com/ku-nlp/pyknp/pull/17)しました。

以上。