Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿
返信数: 15

 キャメルケースリンクとは,アルファベットで記述された単語のうち,大文字から始まり小文字で続く文字列が複数個連結された文字列を自動的に意味のある単語と認識してリンクを生成する機能です.例えば,
  aaaa,aaaA,aaAa,aaAA,aAaa,aAaA,aAAa,aAAA
  Aaaa,AaaA,AaAa,AaAA,AAaa,AAaA,AAAa,AAAA
のような16パターンを考えると,このうち,AaAaのみにリンクが自動生成されます.もしAaAaという名前のページが存在しない場合,新規ページの作成を促すために,
  aaaa,aaaA,aaAa,aaAA,aAaa,aAaA,aAAa,aAAA
  Aaaa,AaaA,AaAa?,AaAA,AAaa,AAaA,AAAa,AAAA
のように,強調表示された上に,"?"記号が付加されます.

 たとえば,MechMoodleという単語が文章中に存在すると,これは”説明の必要がありそうな単語だ”と判断してくれることで,用語の説明を忘れずに済むというメリットはあるのですが,ソースリストの変数名の記述にこのスタイルは頻出するので,ソースリストをWiki中に示した場合は,かなり目障りです.

 Wikiの設定の中に,”Wikiオートリンクオプション”として,”キャメリケースリンクを無効にする”というチェックボックスがあります.このチェックをONにすることでキャメルケースリンクは無効化されます.

 ところが,キャメルケースリンクを無効にすると,副作用が生じることが分かってきました.キャメリケースリンクが有効な状態ではリンクが自動生成されない"++"が,無効にすると勝手にリンク生成されてしまいます.たとえば,
  for ($i = 0; $i < 100; $i++)
というソースコードあった場合,キャメルケースリンクが有効な場合はそのまま表示されるのですが,無効にすると,
  for ($i = 0; $i < 100; $i++?)
このように++が強調表示され,さらにその後ろに"?"記号が追加されます.

 当初,この現象はキャメルケースリンクが有効でも発生するものだろうと考えていました.[ ]で単語を括るとWikiページのリンクが生成されるのと同様に,++には何か意味がある(例えば +もじもじ+ とすると何か意味があるなど)のかと.しかしどうやらこれはバグのような予感がします.



キャメルケースリンクを無効にすると,wiki/ewiki/ewiki.phpの頭のところで,

    if ($moodle_disable_camel_case) {
        define("EWIKI_CHARS_L", "");
        define("EWIKI_CHARS_U", "");
    }
    else {
#### END MOODLE CHANGES
    define("EWIKI_CHARS_L", "a-z_??$\337-\377");
    define("EWIKI_CHARS_U", "A-Z0-9\300-\336");
#### BEGIN MOODLE CHANGES  
    }

というコードでEWIKI_CHARS_LとEWIKI_CHARS_Uを空にします.

 これらの定数は,たとえばfunction ewiki_render_wiki_links()関数などで用いられる

$link_regex = &$ewiki_config["wiki_link_regex"];
$o = preg_replace_callback($link_regex, "ewiki_link_regex_callback", $o);

preg_replace_callback()組込関数のコールバック関数ewiki_link_regex_callback()を呼ぶ際に用いられます.$lewiki_config["wiki_link_regex"]は,

            "wiki_link_regex" => "\007 [!~]?(
        \#?\[[^<>\[\]\n]+\] |
        \^[-".EWIKI_CHARS_U.EWIKI_CHARS_L."]{3,} |
        \b([\w]{3,}:)*([".EWIKI_CHARS_U."]+[".EWIKI_CHARS_L."]+){2,}\#?[\w\d]* |
        ([a-z]{2,9}://|mailto:)[^\s\[\]\'\"\)\,<]+ |
        \w[-_.+\w]+@(\w[-_\w]+[.])+\w{2,}   ) \007x",

と定義されています.私には意味不明です赤面

 また,以下のように使われている箇所もあります.

            $value = preg_replace_callback("/((\w+:)?([".EWIKI_CHARS_U."]+[".EWIKI_CHARS_L."]+){2,}[\w\d]*)/", "ewiki_link_regex_callback", $value);

共通するのは,([".EWIKI_CHARS_U."]+[".EWIKI_CHARS_L."]+)という表現です.そこでPerl互換正規表現に詳しい方に質問です.もしEWIKI_CHARS_U/Lが共に空の場合,この正規表現パターンは,([]+[]+)となります.これが"++"に反応してしまうということはあり得るのでしょうか? PHPの実装(当方はWindows)によるバグで,Linuxでは発生しないという可能性もあります. 


 いま,Linux上で動作しているMoodle1.7では上記現象が発生しないことを確認しました.Moodle1.8で発生する問題か,あるいはWindowsをサーバとする場合のみ発生する問題か,あるいは当方の改造したfs_moodle1.8.3+でのみ発生する問題か?

  • Linux上で動作するMoodle1.8で++がオートリンクされるか.
  • Windows上で動作する(無改造の)Moodle1.8で++がオートリンクされるか.

いずれかをチェックし,報告して頂けると助かります.

#また私の空騒ぎの可能性が出てきてしまいましたが...
#本文中の正規表現の中で3箇所,:)があります.そのままですとフェイスマークになってしまうので,:を全角に変えてあります.

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

あらららら,以下のPHPコードの実行結果は,

<?php
    $str = "for(\$i = 0; \$i < 100; \$i++)";
    echo "$str<BR>";
    $str = preg_replace("/[]+[]+/", "--",$str);
    echo "$str<BR>";
?>

以下のようになりました.

for($i = 0; $i < 100; $i++)
for($i = 0; $i < 100; $i--)

こういうものなのでしょうか? ともかくこれが原因のようですね.

このPHPコードはLinuxでも確認できると思いますので,実行結果を教えて頂けると助かります.でも,もしWindowsのPHPの実装の問題だと判明したとして,どうやって回避するのが安全なのだろうか.

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Haruhiko Okumura の投稿
問題をよく理解していませんが,+が1個以上あれば--に置き換えるということで,こういう動作で正しいのだろうと思います。
Haruhiko Okumura への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

 オリジナルのキャメルケース検出用のパターンを活用して,少しリアルな例にしてみました.

<?php
    echo "[CamelCase Enable]<BR>";
    $str = "for(\$i = 0; \$i < 100; \$i++)";
    echo "$str<BR>";
    $str = preg_replace("/([A-Z0-9\300-\336]+[a-z_??$\337-\377]+){2,}/", "CamelCase",$str);
    echo "$str<BR>";

    echo "<BR>";
    echo "[CamelCase Disable]<BR>";

    $str = "for(\$i = 0; \$i < 100; \$i++)";
    echo "$str<BR>";
    $str = preg_replace("/([]+[]+){2,}/", "CamelCase",$str);
    echo "$str<BR>";

    echo "<BR>";
    echo "[CamelCase Enable]<BR>";
    $str = "This is a AaAa++.";
    echo "$str<BR>";
    $str = preg_replace("/([A-Z0-9\300-\336]+[a-z_??$\337-\377]+){2,}/", "CamelCase",$str);
    echo "$str<BR>";

    echo "<BR>";
    echo "[CamelCase Disable]<BR>";

    $str = "This is a AaAa++.";
    echo "$str<BR>";
    $str = preg_replace("/([]+[]+){2,}/", "CamelCase",$str);
    echo "$str<BR>";

?>

この結果は,WindowsのPHPでは,

 [CamelCase Enable]
for($i = 0; $i < 100; $i++)
for($i = 0; $i < 100; $i++)

[CamelCase Disable]
for($i = 0; $i < 100; $i++)
for($i = 0; $i < 100; $iCamelCase)

[CamelCase Enable]
This is a AaAa++.
This is a CamelCase++.

[CamelCase Disable]
This is a AaAa++.
This is a AaAaCamelCase.

 です.

 気になるポイントは,+記号は1個以上の繰り返しを表す記号ですので,/[A-Z]+/は,AからZの範囲の文字で構成される1文字以上の文字にマッチする.ところが/[]+/のように文字集合が空っぽの場合(ewikiのMoodleによるキャメルケースリンク無効のための修正)は,/+/と同等に扱われてしまい,+記号とマッチしてしまっている.パターンの繰り返しを意味するメタ記号が,[]の後にあるにも関わらず単なる文字(もし/+/ならば正しいですね)になってしまっている.

#[A-Z0-9\300-\336]の部分は私,よく理解していません.A-Zと0-9は良いのですが,その後の\300-\336は8進数?
#{2,}という表現も初めて使いましたが,サブパターンが2回以上,という意味合いのようですが,これで理解は正しいのでしょうか.(Thisがマッチしないのはこれのお陰ですよね)

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

 全く読めないのですが,似たような箇所を議論しているフォーラム書き込みが本家Moodle内にあるようです.

http://moodle.org/mod/forum/discuss.php?d=48881

 以下のURLを読むと,[]のような空の文字クラス(私は文字集合と書いていたかも知れませんが,文字クラスが正しい?)はエラーになる,ともあります.Windows版のPHPではエラーは出ていないようですが.

http://www.mobstyle.jp/manual/ruby/ja/_C0B5B5ACC9BDB8BD.html

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

 取り敢えず,苦肉の策で,以下のような修正を行いました.
 ポイントは,キャメルケースリンクを無効にした時に,
 [".EWIKI_CHARS_U."]+[".EWIKI_CHARS_L."]+
が,
 []+[]+
とならないように,”問題の無さそうな文字”を与えるという方針です.
(現在,本問題はWindows用PHPを使用しているMoodle1.8.3+でのみ発生が確認されています.Linux+Moodle1.7では問題が起きていません)

mod/wiki/ewiki/ewiki.phpの90行目近辺です.

     #-- allowed WikiPageNameCharacters

#### BEGIN MOODLE CHANGES - to remove auto-camelcase linking.  
    global $moodle_disable_camel_case;  
    if ($moodle_disable_camel_case) {
//      define("EWIKI_CHARS_L", "");
//      define("EWIKI_CHARS_U", "");
        define("EWIKI_CHARS_L", "\t");
        define("EWIKI_CHARS_U", "\r");
    }
    else {
#### END MOODLE CHANGES

    define("EWIKI_CHARS_L", "a-z_??$\337-\377");
    define("EWIKI_CHARS_U", "A-Z0-9\300-\336");

#### BEGIN MOODLE CHANGES  
    }
#### END MOODLE CHANGES
  
    define("EWIKI_CHARS", EWIKI_CHARS_L.EWIKI_CHARS_U);


 この指定では,[\r]+[\t]+ という正規表現になりますので,1個以上のキャリッジリターンコードに1個以上のタブコードが続く文字列(が1個以上連続する)をキャメルケースと判断するようになります.もしUTF-8に,0D 09 というコード(0D 0D 09も,0D 09 09...も)があればマッチしてしまいます.私の理解が正しければ,UTF-8ではUnicodeのU+0000からU+007Fまでは0xxxxxxx,U+0080からU+07FFまでは,110xxxxx 10xxxxxx,U+0800からU+FFFFまでは1110xxxx 10xxxxxx 10xxxxxxに,というように0D や 09を2オクテッド以降が含む恐れは無く,もしテイストデータ中に0D や 09を発見したら,それはキャリッジリターン,タブコードである(少し,自信ありません).そして,正常なテキスト文書であれば,1行の文字列にキャリッジリターンで始まってタブコードで終わるような単語は含まれて居ないだろう,という考えです.
([^\x00-\xFF]+[^\x00-\xFF]+というパターンも試してみましたが,エラーが出てしまいダメでした.”どんな文字でもマッチしない”のつもりです)

 いまのところ,この修正で ++ がキャメルケースが無効であるにも関わらずキャメルケースであると判断される問題は発生しなくなりました.

#しかし,/[]+[]+/のような正規表現パターンがLinuxのPHPでどのような挙動を示すのか(正しくはエラーを発生するらしい)に興味があります.先に示しましたスクリプトの実行結果を教えて頂けないでしょうか. 


(参考URL)
http://www.climb-net.com/espresschart/tips/Java_char.pdf

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

 嬉しい副作用です.

 上記修正を行うと,別の頭を悩ませていた問題も同時に解決しました.(ということは他に頭が痛くなる副作用を生む可能性がゼロではないのですが青あざ

 foo[] という配列があったとします.もしfoo[i]のような文字列がWiki中に書かれていた場合,fooi? と表示され,未記入の i という名前のページの作成を促されます.この場合はfoo![i]のように半角感嘆符(!)を[の前に付ける事でwikiのリンク作成を抑制できます.ところが,先の例,foo[],のようにカッコ内に文字が無い場合,foo[]は foo? のように”無”の名前のページを作成しようとします.では,foo![]とするとどうか.単に foo![] と表示されます...仕方がないので foo![ ]のように[と]の間に半角空白を一文字追加して誤魔化していました.

 ++の問題に対する先の修正を行うと,foo![]が正しくfoo[]と表示されるようになりました. ちなみにこの修正を行っても行わなくても, [] のように[の手前に文字列が存在しない場合は,半角空白をカッコ内に追加しないでも問題はありませんでした.

 キャメルケースを発見するための正規表現,意外と影響が大きかったようです.
 もしWindowsのPHPでのみ発生する問題であると判明したら,Moodle Trackerに報告しますので,御協力お願いします.

#当方はPHP5.2.0(Windows)を使用.

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Haruhiko Okumura の投稿
> #しかし,/[]+[]+/のような正規表現パターンがLinuxのPHPでどのような挙動を示すのか(正しくはエラーを発生するらしい)に興味があります.先に示しましたスクリプトの実行結果を教えて頂けないでしょうか.

すみません。上で報告したつもりになっていましたが,短すぎて意味不明になっていました。Linux上のPHPでもPerlでもやってみましたが,同じ動作です(1個以上の+)。
Haruhiko Okumura への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

 ありがとうございます!
 では,LinuxのPHPでもPerlでも,文字クラスが空白はNGである,NGな文字クラスは無視される,したがって/[]+[]+/は/++/と解釈される,ということで良さそうですね.では,Windowsだけの問題では無さそうです.

 なお,以前の書き込みで,「Linuxで動作するMoodle1.7ではキャメルケースを無効にしても++がキャメルリンクと勘違いされることは無かった」と報告しましたが,間違いでした.いまもう一度行ってみたところ,

[CamelCase Enable]
for($i = 0; $i < 100; $i++)
for($i = 0; $i < 100; $i++)

[CamelCase Disable]
for($i = 0; $i < 100; $i++)
for($i = 0; $i < 100; $iCamelCase)

[CamelCase Enable]
This is a AaAa++.
This is a CamelCase++.

[CamelCase Disable]
This is a AaAa++.
This is a AaAaCamelCase.

をWiki本文に書いたところ,キャメルケースを無効に設定すると,

TEST

CamelCase Enable?
for($i = 0; $i < 100; $i++?)
for($i = 0; $i < 100; $i++?)

CamelCase Disable?
for($i = 0; $i < 100; $i++?)
for($i = 0; $i < 100; $iCamelCase)

CamelCase Enable?
This is a AaAa++?.
This is a CamelCase++?.

CamelCase Disable?
This is a AaAa++?.
This is a AaAaCamelCase.

Windows同様に,++がキャメルケースと勘違いされます.Linux上でも全く同じでした.さらに,

a![]
a![1]

a![]
a[1]

になる問題も同じです.となると,これはMoodle Trackerに報告の必要アリですね.私の示した解決方法で大丈夫でしょうか...?

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Hidenori Sugiyama の投稿

はじめまして、杉山と申します。

> #しかし,/[]+[]+/のような正規表現パターンがLinuxのPHPでどのような挙動を示すのか(正しくはエラーを発生するらしい)に興味があります.先に示しましたスクリプトの実行結果を教えて頂けないでしょうか. 

面白い挙動ですので、正規表現ライブラリpcre(バージョン7.4)のソースコードを調べてみました。ざっくりと見た限りですが、[ を閉じるための ] は1文字目(ここでは[)を飛ばして、2文字目(ここでは+)以降から探すようになっています。そのため、この正規表現の意味は ]+[ のいずれかを1文字以上ということになるようです。

手元のPHP(5.1.6, Linux)でも試してみましたが、実際に [ だけや [] などにもマッチしました。

ただperlでも同じ挙動になるようですので、pcreの動作としてはこれが正しいのだとは思います。

Hidenori Sugiyama への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

 はじめまして!

 これで実験と理論の両面から現象の仕組みが解明できたようですね!

 確かに, aa]+[ も a++[] も a++][ もマッチしますね.

 仕組みが分かると,ホッとしますね.

 []+[]+ が,[ ]+[ ]+ とパージングされるとは予想外でした.

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Mikio Ikeda の投稿
四日市大学の Linux + PHP4.3.9 + MySQL 5.0.24 + Moodle 1.7.2+ では発生しませんが、

if ($moodle_disable_camel_case) {
define("EWIKI_CHARS_L", "");
define("EWIKI_CHARS_U", "");
define("CAMEL_CASE_REGEX", "");
}
else {
#### END MOODLE CHANGES

define("EWIKI_CHARS_L", "a-z_µ¤$\337-\377");
define("EWIKI_CHARS_U", "A-Z0-9\300-\336");
define("CAMEL_CASE_REGEX","[".EWIKI_CHARS_U."]+[".EWIKI_CHARS_L."]+");
}

のようにして、

"[".EWKIE_CHARS_U."]+[".EWIKI_CHARS_L."]+"

となっているパターンを全部(3箇所程度あるようです)

CAMEL_CASE_REGEX

に変更するのが、まっとうな解決策のような気がします。(未確認なので、直さないでください。)

Mikio Ikeda への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

 例えば,

"/((\w+:)?([".EWIKI_CHARS_U."]+[".EWIKI_CHARS_L."]+){2,}[\w\d]*)/"

"/((\w+:)?(".CAMEL_CASE_REGEX."){2,}[\w\d]*)/"

とするということですよね.この場合,CAMEL_CASE_REGEXが空の場合は,

"/((\w+:)?(){2,}[\w\d]*)/"

こうなりますよね.サブクラスを表す"("に対する")"を探すコードが,"["に対する"]"と同じように少なくとも一文字は存在するはず,ということで+2の場所から")"を探し始めると,"()"の")"を発見し損ねる恐れがありませんか?

 EWIKI_CHARS_U, EWIKI_CHARS_Lが意図しているのは,それぞれキャメルケースのUpper case文字とLower case文字であり,UltraSonicのような単語を発見したい時(オリジナルのewikiにはキャメルケースを無効にする機能が無かったと推測)には文字クラスを指定するが,無効にしたい時にはパターンマッチをしないのではなく,マッチしたくてもできないパターンを与えてしまえ,というMoodleの改造がお手軽だったと言えばお手軽です.それぞれEWIKI_CHARS_U, EWIKI_CHARS_Lに文字を入れないことで,「何にもマッチしないぞ」という方針だと解釈しました.この方針を引き継ぐと,副作用少なくマッチしない文字クラスを与えてあげるのが影響が最も少ないのではないかと考えました. 

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Mikio Ikeda の投稿
たしかにおっしゃるとおりだと思います。

1.7.2+ の Moodle には同じような正規表現マッチングが3箇所あります。
()の問題を含めて回避するには、さらに (と){2,} まで含めないとだめですね。ところが、3箇所のうちの1箇所はパターンが少し違っているので、前の(を含めることができません。

確かに1.7.2+ でも Camel Case を無効にしたときの、Wiki の動作が変です。Camel Case が排除しきれていません。もともとの Camel Case を除外するための対策が不十分な気がします。簡単なパターンではないようなので、少し調査してみます。

Mikio Ikeda への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Tatsuya Shirai の投稿

 正規表現をイジくるのではなく,本当は”キャメルケースリンク”している箇所は飛ばす,という対策をするべきなのかも知れませんが,意外と込み入っているようで,お手軽な修正に逃げました.やはり正規表現の部分をきちんと理解しないとダメですねぇ.

Tatsuya Shirai への返信

Re: Wikiでキャメルケースリンクを無効にすると"++"がリンクされてしまう問題

- Mikio Ikeda の投稿
少し試してみましたが、やはりうまくいきませんでした。先ほど提案した改造では、一致する文字列はないのですが、その分が空になり、結局他の部分が一致すると一致してしまいます。簡単に逃げる方法は、Shirai さんが提案したとおり、ありえない文字にむりやり一致させることです。

たとえば、以下のように改造して、さらに各々のパターンに一致する部分を入れ替えると、とりあえずはうまくいきます。しかし、これもやはり完全な方法ではありません。稀にあるかもしれない Never Match This String にマッチするかもしれません。

ちなみに、\337-\377 は ISO-8859-1 のアクセント記号付き文字(小文字)で、\300-\336は、同大文字だと思います。UTF-8 の場合は、最初の256文字は ISO-8859-1 と同じなので、とりあえずは大丈夫だとおもます。

#### BEGIN MOODLE CHANGES - to remove auto-camelcase linking.
global $moodle_disable_camel_case;
if ($moodle_disable_camel_case) {
define("EWIKI_CHARS_L", "");
define("EWIKI_CHARS_U", "");
define("EWIKI_CAMEL_REG0", "(Never Match This String)");
define("EWIKI_CAMEL_REGEX", EWIKI_CAMEL_REG0);
}
else {
#### END MOODLE CHANGES

define("EWIKI_CHARS_L", "a-z_µ¤$\337-\377");
define("EWIKI_CHARS_U", "A-Z0-9\300-\336");

#### BEGIN MOODLE CHANGES

define("EWIKI_CAMEL_REG0", "[".EWIKI_CHARS_U."]+[".EWIKI_CHARS_L."]+");
define("EWIKI_CAMEL_REGEX", "(".EWIKI_CAMEL_REG0."){2,}");
}
#### END MOODLE CHANGES