HTML分割
週末にNike+の箱に収まって旅にでていくものあり。
まぁこれについてはまた後日。。。
さておき、PHPでテキストデータが長過ぎる場合に文字数に合わせて折り畳む処理を書くことになりまして、まぁこれだけならなんてことはないのだけど、そこにHTMLタグが少し入る場合があったので、ちょっと悩みつつもとりあえずこれで良いだろう的なものを書き上げたわけだ。
で、これはこれで完了したからよいものの、どうせならもう少し後々使えるものにしておいたら良いかもなと思いはじめたわけです。
・文字数ではなくてバイト数で分割して、順番に放り込んだ配列をはきだす。
・HTMLの切れたところのタグを補充する。
・単語の途中で切らない。
こんな感じのものを考えたのだけど、でもこれって既にありそう。。。
で、検索してみたらPerlだけどかなり近いものを発見。こちら [
link] 。
PHPではそれらしきものが発見できなかったので、ちょっと作ってみることにした。
ついでに追加の条件として以下のものを考えてみる。
・タグを補充した上で指定したバイト数におさめる。
・英語、日本語ともに単語が切れないようにする。
問題は日本語の単語の解析なのだけど、そんなにばっちり解析できなくてもよくて、アバウトでもいいからとりあえずさくっと単語の前後に切れ目を入れたいなぁと、あれこれ探してみましたらば、こちら [
link] の正規表現がすばらしくナイスなので使わせていただくことにしました。
以下のようなテキストを100バイト指定で分割すると、
<html>
    <body>
        <h1>test html data</h1>
        <p id="a">テストHTMLデータ</p>
        <p id="b">HTMLタグを含むテキストを指定したバイト数で分割して配列でかえします。</p>
        <a href="#">
        <p id="c">ただし、そのまま分割すると酷いことになるので、</p>
        <p id="d">タグの途中では切らないようにしたり、タグを補充したりします。</p>
        <p id="e">またタグ以外でも単語の途中で分割されないようになっています。</p>
        <p id="f">なので、すべてが指定したバイト数ぴったりには分割されません。</p>
        </a>
        <p id="g">指定したバイト数ぴったりか少し小さく分割されます。</p>
    </body>
</html>
以下のようになる。
array(11) {
  [0]=>
  string(97) '<html><body><h1>test html data</h1><p id="a">テストHTMLデータ</p><p id="b">HTML</p></body></html>'
  [1]=>
  string(92) '<html><body><p id="b">タグを含むテキストを指定したバイト数で分割して配列で</p></body></html>'
  [2]=>
  string(94) '<html><body><p id="b">かえします。</p><a href="#"><p id="c">ただし、その</p></a></body></html>'
  [3]=>
  string(90) '<html><body><a href="#"><p id="c">まま分割すると酷いことになるので、</p></a></body></html>'
  [4]=>
  string(98) '<html><body><a href="#"><p id="d">タグの途中では切らないようにしたり、タグを</p></a></body></html>'
  [5]=>
  string(92) '<html><body><a href="#"><p id="d">補充したりします。</p><p id="e">また</p></a></body></html>'
  [6]=>
  string(98) '<html><body><a href="#"><p id="e">タグ以外でも単語の途中で分割されないように</p></a></body></html>'
  [7]=>
  string(92) '<html><body><a href="#"><p id="e">なっています。</p><p id="f">なので、</p></a></body></html>'
  [8]=>
  string(92) '<html><body><a href="#"><p id="f">すべてが指定したバイト数ぴったりには</p></a></body></html>'
  [9]=>
  string(94) '<html><body><a href="#"><p id="f">分割されません。</p></a><p id="g">指定した</p></body></html>'
  [10]=>
  string(82) '<html><body><p id="g">バイト数ぴったりか少し小さく分割されます。</p></body></html>'
}かなりゴリゴリと力技。そしてまだうまくまとまっていないんですが以下の通り。
var_dump(htmlsplit($txt, 100));
function htmlsplit($txt, $len) {
    // HTML整形
    // 改行とタブを削除
    $txt = mb_eregi_replace('[\n\t]',"",$txt);
    // 連続半角スペースを1つにする
    $txt = mb_eregi_replace('[ ]+'," ",$txt);
    // タグの前後に改行を挿入
    $txt = preg_replace('/<.*?>/',"\n\\0\n",$txt);
    // 改行区切りで配列化
    $txt_array = explode("\n", $txt);
    // タグでなければ単語でさらに分割
    $sub_array = array();
    for ($i=0; $i<count($txt_array); $i++) {
        if (substr($txt_array[$i], 0, 1) != "<") {
            $txt_p = mb_eregi_replace('/[一-龠々〆ヵヶ]+|[ぁ-ん]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[a-zA-Z0-9]+|[,.、。!!??()()「」『』]+|[  ]+/', "\\0\n", $txt_array[$i]);
            $txt_p = mb_eregi_replace('/でなければ|について|ならば|までを|までの|くらい|なのか|として|とは|なら|から|まで|して|だけ|より|ほど|など|って|では|は|で|を|の|が|に|へ|と|て/', "\\0\n", $txt_p);
            $sub_array = array_merge($sub_array, explode("\n", $txt_p));
        } else {
            $sub_array[] = $txt_array[$i];
        }
    }
    // 空の要素を削除
    $txt_array = preg_grep('/./', $sub_array);
    $txt_array = array_values($txt_array);
    // 分割処理に投げる
    $txt_array = htmlarraysplit($txt_array, $len);
    if (!$txt_array) {
        // falseが返ってきたらfalseを返す
        return false;
    }
    // 結合処理
    for ($i=0; $i<count($txt_array); $i++) {
        $txt_array[$i] = implode($txt_array[$i], '');
    }
    return $txt_array;
}
function htmlarraysplit($txt_array, $len) {
    // 分割位置をセット
    $cnt = 0;
    $cntend = 0;
    for ($i=0; $i<count($txt_array); $i++) {
        if (($cnt + ($txtlen = strlen($txt_array[$i]))) <= $len) {
            $cnt += $txtlen;
        } else {
            $cntend = $i;
            break;
        }
    }
    if ($cntend == 0) {
        // 分割なしであれば終了
        return array($txt_array);
    } else {
        $tagname = array();
        $data_a = array();
        $data_b = array();
        // 終了タグをチェック
        for ($i=count($txt_array)-1; $i>=$cntend; $i--) {
            if (preg_match('/^<\/([a-zA-Z]+)/', $txt_array[$i], $regs)) {
                $tagname[] = $regs[1];
            } elseif (preg_match('/^<([a-zA-Z]+)/', $txt_array[$i], $regs)) {
                if (end($tagname) == $regs[1]) {
                    array_pop($tagname);
                }
            }
        }
        // 終了タグのバイト数にあわせて分割位置を移動
        $taglen = count($tagname) * 3 + strlen(implode($tagname, ''));
        for ($i=$cntend-1; $i>=0; $i--) {
            // 移動にあわせて終了タグを追加修正
            if (preg_match('/^<\/([a-zA-Z]+)/', $txt_array[$i], $regs)) {
                $tagname[] = $regs[1];
            } elseif (preg_match('/^<([a-zA-Z]+)/', $txt_array[$i], $regs)) {
                if (end($tagname) == $regs[1]) {
                    array_pop($tagname);
                }
            }
            // タグの文字数チェック
            $taglen -= strlen($txt_array[$i]);
            if ($taglen <= 0) {
                $cntend = $i;
                break;
            }
        }
        // データを分割
        for ($i=0; $i<count($txt_array); $i++) {
            if ($i < $cntend) {
                $data_a[] = $txt_array[$i];
            } else {
                $data_b[] = $txt_array[$i];
            }
        }
        // 前後半それぞれの終了タグ処理
        for ($i=count($tagname)-1; $i>=0; $i--) {
            // 前半部分の末尾に終了タグ追加
            $data_a[] = '</'.$tagname[$i].'>';
            // 後半部分の先頭に開始タグ検索追加
            $distag = array();
            for ($j=$cntend-1; $j>=0; $j--) {
                if (preg_match('/^<\/('.$tagname[$i].')/', $txt_array[$j], $regs)) {
                    $distag[] = $regs[1];
                } elseif (preg_match('/^<('.$tagname[$i].')/', $txt_array[$j], $regs)) {
                    if (end($distag) == $regs[1]) {
                        array_pop($distag);
                    } else {
                        array_unshift($data_b, $txt_array[$j]);
                        break;
                    }
                }
            }
        }
        // 前半部分チェック
        $data_ch = false;
        for ($i=0; $i<count($data_a); $i++) {
            // タグ以外のデータがあるかどうか確認
            if (substr($data_a[$i], 0, 1) != "<") {
                $data_ch = true;
                break;
            }
        }
        if (!$data_ch) {
            // タグのみになってしまった場合はfalseを返す
            return false;
        }
        // 後半部分を再帰分割した後、結合
        $data_all = array($data_a);
        if ($data_b_re = htmlarraysplit($data_b, $len)) {
            $data_all = array_merge($data_all, $data_b_re);
        } else {
            return false;
        }
        return $data_all;
    }
}
this page url :