携帯サイトの厄介な文字化け 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