読者です 読者をやめる 読者になる 読者になる

zakihayaメモ

RubyとRailsのことが中心

タグを残して先頭からの表示文字数を指定する

デザイン担当者の要望で、HTMLのタグは残したまま表示する文字数を制限したいんだけど・・・という要望が。
いつも無茶ばっかだなあと思いながら実装方法はちょっと興味があったので考えてみた。

案1:正規表現
 →タグを消しちゃったらもとに戻せないので却下。

案2:1文字ずつループしながらチェック
 →できれば避けたい

案3:DOMツリーを上から見ながら、text部分の文字数をカウント
 →これでやってみる

以下、ソースです。
とりあえず作ったので無駄な部分が多いですが・・・
substr_with_tagの引数continue_linkは、続きを表示するためのリンクを渡します。

   # HTMLタグは残したままで、指定した文字数を抽出します
  def substr_with_tag(str, word_cnt, continue_link)
    ret = ""
  
    # XMLにパース
    doc = REXML::Document.new str 
    
    # 指定された文字数分表示されるように不要なエレメントを消す
    disp_txt = ""
    is_formated = format_xml(doc, disp_txt, word_cnt)
    ret = doc.to_s
    if is_formated
      ret = ret + continue_link
    end

    return ret
  end
  
  # substr_with_tagから再起呼び出しされてXMLを整形します
  def format_xml(xml_element, disp_txt, word_cnt)
    end_flg = false
  
    xml_element.elements.each do |e|
      if end_flg
        # 指定した文字数を超えた場合は要素を消す
        xml_element.delete e
      elsif e.type == REXML::Element
        # HTML要素の場合は再起呼び出し
        if !e.text.nil?
          disp_txt = disp_txt.concat(e.text)
        end
        if word_cnt <= disp_txt.split(//u).length
          end_flg = true
          # 指定文字数を超えたら、超過分をカット
          over_cnt = disp_txt.split(//u).length - word_cnt
          val = e.text.split(//u)
          e.text = val[0..(val.length - over_cnt)]
        else
          end_flg = format_xml(e, disp_txt, word_cnt)
        end
      elsif e.type == REXML::Text
        # Textの場合は表示される文字列に連結して、文字数チェック
        disp_txt = disp_txt.concat(e.value)
        if word_cnt <= disp_txt.split(//u).length
          end_flg = true
          # 指定文字数を超えたら、超過分をカット
          over_cnt = disp_txt.split(//u).length - word_cnt
          val = e.value.split(//u)
          e.value = val[0..(val.length - over_cnt)]
        end
      end
    end
    return end_flg
  end

確認してみると、うまくいく場合といかない場合がある。
リファレンスをよく見てみたら、REXML::Element#textは、最初の子テキストノードのvalueしか返さないらしい。
つまり、途中にbrタグやimgタグやaタグがあるとtextの途中までしかわからない。
textsというメソッドもあるけど、これだとテキストだけなので間のタグが取れない。


とりあえず一旦迷宮入りということで・・・
また余裕があったら考えよう