Chienomi

PureDocのエスケープ機能

有用なユーティリティコード

ずっと機能しない、と言ってきたPureDoc組み込みHTMLエスケープ機能だが、修正されて動くようになった。

問題のコードはこんな感じ。

STANDARD_ESCAPE_EX = /<(?![a-z/])|(?<![/"a-z])>|&(?![a-z0-9]+;|#[x0-9]+;)/ FORCE_ESCAPE_EX = /[<>"&]/ ESCAPE_INVOKE = ->(m, ptn) { return "" if m.nil? m.gsubz(ptn) do case m when "<" "&lt;" when ">" "&gt;" when '"' "&quot;" when "&" "&amp;" end end }

修正版はこうなった

STANDARD_ESCAPE_EX = /<(?:(?:/[a-z]+|[a-z]+(?: +[a-z]+="[^"]*"+)*)>)?|(?:<(?:/[a-z]+|[a-z]+(?: +[a-z]+="[^"]*"+)*))?>|&(?![a-z0-9]+;|#[x0-9]+;)/ FORCE_ESCAPE_EX = /[<>"&]/ ESCAPE_INVOKE = ->(m, ptn) { return "" if m.nil? m.gsub(ptn) do case $& when "<" "&lt;" when ">" "&gt;" when '"' "&quot;" when "&" "&amp;" else $& end end }

動かない原因は、caseでマッチする文字列が、正規表現のマッチ文字列ではなく元文字列になっていた、という単純なもので、気付くまでにデバッグに時間がかかったが、気付いてしまえばあっという間だった。

ところが、よく見るとSTANDARD_ESCAPE_EXがまるごと変わっている。これは、「<及び>がタグを構成する一部か」を判定する部分が甘かったからなのだが、ちょっとハマったのが、るりまには書かれていない「後読みアサーションに量指定子は使えない」という仕様だった。どうしても量指定子を使いたかったので、選択(?)でタグを構成する場合はタグ全体がマッチするようにした。このようにすると、case文で判定しているのは文字が含まれているかではなく、文字が===でマッチするかであるため(標準では文字列の同一性を見る)、タグ全体がマッチした場合はcase文でのマッチに失敗する。マッチに失敗した場合はelseでマッチ文字列全体を返すためString#grubの内容が唯一のcase文である時それにマッチしない文字列は変更されない。結果的に「タグを構成しているとみられる文字列は変更しない」となる。

なお、#escメソッドはこのように「タグ、もしくは文字参照と見られる文字列はエスケープしない」が、これは全ての要素メソッドが自動的に呼ぶためで、ユーザーが明示的にエスケープしたい場合は#esc!メソッドを呼ぶことで対応できる。これは、このような判定は一切行わず、全ての当該シンボルをエスケープする。

なお、PureDocは特殊文字入力にHTML/XMLの文字参照を使うことになっている。そのため文字参照のエスケープを回避しているのだが、HTML以外への編訳においては当然文字参照のアンエスケープまたは変換が必要となる。