HTMLを保持しつつ指定文字数で文章をカット

HTMLを切って短くするとき、mb_substrではHTMLタグの文字まで文字数にカウントされるので、単純には使えません。
たとえば、

こんにちは<img src="smilie.gif" alt="にこにこマーク" width="20" height="20" />

を20文字で切ると、

こんにちは<img src="smili

となります。このようにタグの途中で指定文字数に達する場合もあると、必要な文字数が取り出せない上、HTMLタグも壊れてしまいます。

HTMLタグが必要ない時はstrip_tagsでタグを削除すれば良いですが、
ではタグをキープしながら指定文字数で切りたい場合はどうしたらいいのか。

以下を実現できるか考えてみました。

  • HTMLを一定の長さで切る
  • HTMLは文字数に含めない
  • HTMLは保持する

次のようにすると、HTMLの入れ子は不完全ながら、必要な文(分)は取り出すことができます。

 
function mb_truncate_html($str, $limit, $count_break=false ) {
 $txts = $tags = array();
 $ord = 0;
 $str = preg_replace('/\s+/', ' ', $str);
 while(!empty($str)) {
  if (preg_match($re = '/^((?:\r\n|\r|\n)+)/', $str, $m)) {
   if ($count_break) $txts[] = $m[1];
   else $tags[] = $m[1];
   $str = preg_replace($re, '', $str);
  }
  else {
   $re = '/^(<.+?>)/';
   if (preg_match($re, $str, $m)) {
    $tags[$ord] = $m[1];
    $str = preg_replace($re, '', $str);
   }
   else if (mb_ereg('^([^<]+?)(?:<.*)?$', $str, $m)) {
    $txts[$ord] = $m[1];
    $str = mb_ereg_replace('^'.preg_quote($m[1]), '', $str);
   }
  };
  $ord++;
 }
 $out = array();
 $n = 0;
 for ($i = 0; $i <= $ord; $i++) {
  if (isset($tags[$i])) $out[] = $tags[$i];
  else {
   if (!isset($txts[$i])) continue;
   $a = $txts[$i];
   $b = mb_strlen($a); my_print_r($a);
   if ($n + $b > $limit) {
    $out[] = mb_substr($a, 0, $limit - $n);
    break;
   }
   else {
    $out[] = $a ;
    $n += $b;
   }
  }
 }
 return $out;
}

使用例

たとえば、

<div class="articleText">
<!-- google_ad_section_start(name=s1, weight=.9) -->
母の日のプレゼントで可愛いお花を貰いました
<img src="http://emoji.ameba.jp/img/user/ku/kusurisumimoto/4512510.gif" />
<img src="http://emoji.ameba.jp/img/user/no/noni0118/4530363.gif" />
<img src="http://emoji.ameba.jp/img/user/no/noni0118/4530363.gif" />
<img src="http://emoji.ameba.jp/img/user/no/noni0118/4530363.gif" />
<img src="http://emoji.ameba.jp/img/user/no/noni0118/4530363.gif" />
<img src="http://emoji.ameba.jp/img/user/ta/taiyouh43/4505724.gif" />
<img src="http://emoji.ameba.jp/img/user/so/solamoto/4507366.gif" />
<img src="http://emoji.ameba.jp/img/user/so/solamoto/4507366.gif" />
<img src="http://emoji.ameba.jp/img/user/bu/burgundy-cherry32/4507629.gif" />
<img src="http://emoji.ameba.jp/img/user/tw/twin-blade/4512851.gif" />
    <div id="{A27FB613-7BFE-4407-89AF-C0F82077D3EA:01}" style="text-align:left">
        <div>
            <a class="detailOn" href="http://ameblo.jp/tsuji-nozomi/image-12024865340-13302579215.html" id="i13302579215">
                 <img alt="{A27FB613-7BFE-4407-89AF-C0F82077D3EA:01}" border="0" height="380" src="http://stat.ameba.jp/user_images/20150510/13/tsuji-nozomi/53/ce/j/o0480048013302579215.jpg" width="380" />
            </a></div></div>
    <div id="{E7EF14EE-4575-4F8E-8365-B5C8B41C67B0:01}" style="text-align:left">
        お花の後にはボートがあってメッセージが</div>
<!-- google_ad_section_end(name=s1) --></div>

なんていうソースがあったとします。
(出典:辻希美オフィシャルブログ「のんピース」。検索キーワード「オフィシャルブログ」で2位(実質1位))

 
$str = [上記HTMLソース]
$out = mb_truncate_html($str, 48, 1);
$str_out = implode('', $out);

 母の日のプレゼントで可愛いお花を貰いました 
[画像]
お花の後にはボートがあってメッセ

まで取り出すことができます。

なお、HTML構造が、開始タグがあって終了タグがない可能性が大いにありますので、後処理をしてください。
WordPressだと force_balance_tags なんていう便利な関数があったりします。

Leave a Reply