携帯サイトの厄介な文字化け with JpMobile
あるサイトで、リンクをクリックすると、そのアンカーテキストで検索をかけ、その結果を表示
するプログラムを作成した。そしたら、結果の部分が文字化けする。しかも、getで投げた、
アンカーのテキストが。
それは、なぜか2回UTF8変換が起こっている。
対処として、文字コード変換を行っているmobile_filter(JpMilbleの機能)を外すとドコモでは
問題なくなる。
だが、Ezでは、文字コード全体をSJISと認識してしまうため、他の部分含めすべてバグル。
この点、ドコモとSBはUTF8と認識してくれるので、上手く行ってしまう。
■再現
mobile_filterをexceptして以下を実行すると同じになる。やはりutf8がutf8で変換されている
@school = NKF::nkf("-S -w",params[:school])。-Sとなっているが、実は入ってくる文字はUTF8。
他との違いはなんだろうか。何故かこのメソッドの場合のみ上手くいかない。
そういえば、ここの文字は直にDBから引いたものをパラメタとして渡してる。
(後に、これは勘違いだと知る)
<%= link_to #{h @user.address}, :action => "search_by_addr", :address => @user.address %>
ここは他と違うところだ。
ユーザーからの入力でも、コントローラやモデルで引いたものでもなく、DBから直に引いたものを使っている。
確かにDBはUTF8でそれを直に使った場合に何かあるかも。
そこをtosjisしたらできた。
<%= link_to #{h @user.address}, :action => "search_by_addr", :address => @user.address.tosjiss %>
あ、できた。これならmobile_filterを有効にしても大丈夫。
おそらく、ビューでクエリを発行したからDB呼んで直に飛ばしたので、utf8でとんだ。それが、
utf8変換されたっぽい。
・・と思ったら、今度はDocomo、EzはOKだが、バンクがだめ。
げげ。モバイルフィルタがVodaにはUtf8で渡すからだめだ。
根本的に、getクエリに、DBから直に持ってきた日本語を入れるのが問題。
よって、@user.address.idのようにして、日本語を渡さないようにする。
そして、それを受けたコントローラでfindかければOK。
原則
★日本語でDBからひいたものをパラメータに渡さなない使わない!☆
これが今回の教訓になった。
しかし、これでも駄目なことに気がついた。先ほどは、直接DBから引いているから駄目と
書いたが、元々はmobile_filterを使って、Controllerで作られたインスタンス変数(中身は日本語)
をgetでのパラメータを送ると、見事に2回変換がかかり、失敗してしまう。
例えば次のコード。これは、最初の検索はいいのだが、ページングで失敗する。最初のget時に、
params[:keyword]はきちんと文字コード変換が行なわれる。
しかし、その後、この、既にエンコードされた値をそのままビューのgetパラメータとして使うので、
エンコードする必要のないものをエンコードしてしまう。
■Userを名前で検索する
Controller
def search if params[:keyword] @keyword = params[:keyword] @pages, @users = paginate( :users, :conditions => ["name is not NULL and status = 1 and name like ?","%#{@keyword}%"], :order => "id DESC", :per_page => LIMIT_PER_PAGE ) flash.now[:notice] = "#{@pages.item_count}件ヒットしました。" render :action => "search_result" else render :action => "search_boxes" end end
View
<% @title = "名前検索" -%> <%= @pages.item_count %>件中 <%= (@pages.item_count != 0 ? @pages.current.first_item : 0)%> - <%= @pages.current.last_item %>件目 <% unless @pages.item_count == 0 -%> <%= separator %> <p> <%= separator %> <% for user in @users do -%> <%= name_address(user) %><br /> <%= separator %> <% end -%> </p> <div align="center" style="text-align:center;"> <% if (@pages.current.previous || @pages.current.next) -%> <%= separator %> <br /> <%=link_to "前へ",:keyword => @keyword, :page => @pages.current.previous if @pages.current.previous %><%=" | " if (@pages.current.previous && @pages.current.next)%> <%=link_to "次へ",:keyword => @keyword, :page => @pages.current.next if @pages.current.next %> <% end -%> </div> <% end -%> <%=separator%> <div style="text-align:left; font-size:small; background-color:<%= mypage_bg_color(get_user_from_session) %>;color:<%= @color %>; text-align:left;"> ■<%=link_to "検索画面へ", :action => "search_boxes" %><br /> <%=mypage_separator(get_user_from_session)%> </div>
この問題を防ぐ方法は次のようになる。
「:keyword => @keywordの値を変換前、つまり携帯側の文字コードに戻す」。ただ、単純に戻すと、
Softbant/Vodafoneの場合も変換されてしまうので注意。このキャリアはmobile_filterの変換を
受けないので、今回の問題には関係ない。しかし、解決策の点では関係ある。
解決策は次の通り。
View
:keyword => reencode_to_mobile_code(@keyword)
Helper
def reencode_to_mobile_code(str) filter = Jpmobile::Filter::Sjis.new if str && filter.apply_incoming?(controller) filter.to_external(str) else str end end