短期集中連載? LibreOfficeをWindowsで開発してみよう:その⑤ 不具合直して? みたよ
GWの間にLibO開発できる環境をWindowsで作ろうの連載5回目。さてGWやらも本日で終わり、なのでこの短期集中連載もさしあたりの最終回。
せっかく開発環境作ったのになんにも触らないのもアレなんで、適当なバグを直してみようと。ターゲットにしたのはこれ。
この不具合なんですが、平たく言うとExcelで作成した「セルの書式設定で大字(12345……を壱弐参四五……って書くやつ)を指定したセルが、LibO Calcで開くと大字にならないってものです。
Excelで大字表示ってどうやるんだろ……って調べてみると、たとえばこのサイトがヒットしたんですけど:
書式記号に [DBNum2]
って指定すればいいらしいです。
Excelの書式指定子(Modifier)、Calcでも指定できるので指定してみる……と、
ホントだ。大字にならないですね。ということでこれを追ってみることにします*1。
ソースを検索して該当部分を探す
最初はやみくもにソースを検索して該当部分を探してみましょう*2。
ソースの検索は OpenGrok でも git grep
でもよいですが、まあせっかくなのでVS2019上で検索してみることにします。
Ctrl-Shift-F を押してソリューション内検索ダイアログを開いて "DBNum2" を検索します。
見つかったのはテストコード、コメントを除くと /svl/source/numbers/zformat.cxx
だけ。名前からいってもそれっぽい。ということで中を覗いてみます。
デバッガを使いつつなんとなく眺める
パッと目に入るのは、 SvNumberformat
のコンストラクタ。
こいつもまあ相当長いメソッドなのですが、要は書式指定子の文字列をパースして [DBNum2] なら 2
を取り出して保持しておく処理っぽいですね。
あと DBNum というキーワードでなんとなくコードを眺めると:
sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate ) sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate )
というペアのメソッドが目につきます。なにやら内部的に DBNum と NatNum なるものを相互に読み替えているらしい。
試しにデバッガで前者にブレークポイント設定して*3 DBNum2
を設定してみたら呼ばれてますね。なるほど。
私はこんな風にブレークポイントしかけて確かにこれが見るべき処理かって確認するのよくやります。
机上で追い込むの苦手なので……。
そこで、おっと思ったのが、さっきのテストでセルの書式設定に [DBNum2]
とやったやつ。
あれを保存して再度読み込みなおすと、 [NatNum4]
という書式指定に代わってしまう。
ここでようやっと、仕様確認しようと思いつきました(遅い)。ヘルプを "DBNum2" で検索すると、このページがヒットしました。
「Displaying Numbers Using Native Characters」とあるとおり、NatNum
というのはNative Numberなんでしょうね。
LibOの書式指定のネイティブはこっち。
で、Excel(OOXML)からインポートしたり、手打ちで書式指定 DBNum2
とかした場合は、適切なNatNumに変換して内部的にはそれを使って処理する……と。なるほどね。
エクスポートするときには逆に NatNum から DBNum に書き戻すのでしょう。
さてヘルプを見ると、…… DBNum2
は NatNum4
に対応するということですね。
それは、ODSで保存して読み込み直すときの結果と符合しますね。なのでここに間違いはないと*4。
じゃあそうなると、書式指定をもとに実際の表示文字列を組み立てるところ、それが問題あるということでしょうか。 表示文字列を組み立てるところ、それって多分ですけど、普通の実装なら、 セルの内容を渡したら文字列が返ってくるメソッドを用意するんじゃないでしょうかね*5。
ということで、ソースコードをgrepして(ここはVS2019使わないんかい!) zformat.cxx
の中で戻り値が OUString
なものをgrepして、中のぞいて、あーそれっぽいと思ったのがこの二つ。
OUString SvNumberformat::impTransliterateImpl(const OUString& rStr, const SvNumberNatNum& rNum ) const { css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); return GetFormatter().GetNatNum()->getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(), rNum.GetParams()); } void SvNumberformat::impTransliterateImpl(OUStringBuffer& rStr, const SvNumberNatNum& rNum ) const { css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() ); OUString sTemp(rStr.toString()); sTemp = GetFormatter().GetNatNum()->getNativeNumberStringParams( sTemp, aLocale, rNum.GetNatNum(), rNum.GetParams()); rStr = sTemp; }
どっちも GetnativeNumberStringParams()
なるメソッドを読んでるわけですが、ここはVS2019でステップ実行でステップインして飛びますと、
最終的には i18npool/source/nativenumber/nativenumbersupplier.cxx
の NativeNumberSupplierService::getNativeNumberString()
メソッドに来るわけですね。
で、こいつをデバッグ実行しつつ追うと、
switch (nNativeNumberMode) { ... case NativeNumberMode::NATNUM4: // Text, Lower, Long number = &natnum4[langnum]; break;
という処理があって、ここで number
という変数に入ってくるのは、すでに大字ではない通常の漢数字……ふむぅ。……ん?
ここで仕様(というかヘルプ)を再度確認
はい、私のよくなかったところ。 おもむろにソースを見る前に、「解くべき問題は果たして何か(何が正しいふるまいか)」を確認する必要がありましたね。
で、ヘルプ再度確認すると、いやー間抜け:
NatNum4
は modern だって書いてあるじゃー、ないですか!
なのでヘルプの記述が仕様として正しいなら、今の動作は完全に正しい。そして、古いヘルプ:
を見ると、この動作は3.3から……というかOpenOffice.orgのころから変わっていない、と思われます。
でもおかしいですよね、Excelでは DBNum2
は大字なのだとしたら、当然LibOではそれに対応する NatNum5
じゃないと、バグ起票されたようにExcelとの互換性がないということになってしまう。うーん。わからん。
目をつぶって直す
まあいいや、もし仮にこいつ直すとしたらどうするか。
結果としては DBNumX
と NatNunX
の割り当てだけが問題ってことになったので、前述の:
sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate ) sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate )
この二つを直せばいいということになります。
本来ならすべてのマッピングを見直す方が適切なのかもわからないですが、さしあたりはこの問題だけ直してみます。 以下diffだけだと若干情報足りないので手で補った嘘diffです。
@@ -170,7 +170,7 @@ sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang switch ( nDBNum ) { ... case 2: if ( eLang == primary(LANGUAGE_CHINESE)) nNatNum = 5; else if ( eLang == primary(LANGUAGE_JAPANESE) ) - nNatNum = 4; + nNatNum = 5; else if ( eLang == primary(LANGUAGE_KOREAN) ) nNatNum = 2; break;
こちらは DBNum2
を NatNum4
にして、
@@ -238,7 +238,7 @@ sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLan switch ( nNatNum ) { ... case 4: if ( eLang == primary(LANGUAGE_CHINESE) ) nDBNum = 2; else if ( eLang == primary(LANGUAGE_JAPANESE) ) - nDBNum = 3; + nDBNum = 2; break; case 6: if ( eLang == primary(LANGUAGE_CHINESE) )
こちらは逆に NatNum4
を DBNum2
に戻しているだけ。あー、超簡単だ。
で、この変更を入れてビルドしたやつで、バグ報告についてたファイルを開いてみると……。
直ってますね!
もし……仮にもし、これをパッチに出すとすると、
- まずはあるべき仕様を日本コミュニティで議論(メーリングリストかな?)。
- 上記に基づき、
DBNum2
以外に対する処理も書く。 - ユニットテストでこの処理を確認しているものがあるげなので、直すなり、もしこのケースを確認してないなら追加するなりする。
- ヘルプも直す。
- コミット整えて、gerrit に push
ってな感じですかねー。
まあともかく、開発環境作ってリアルな問題を(少なくとも技術的には)解けるところまではいったので、 ゴールデンウイークとしてはまあまあの成果かな?
せっかくなので、これっきりにしないで定期的に開発にかかわっていきたいですね!