simple_html_dom.phpの質問です。


1.h2,h3.h4タグ,ul,liの一番深いものではないタグ
2.一番深いタグ
に分けて表示させたいのですが、プログラムが書けません。

うまく説明ができないので、説明用のファイルをアップしてあります。

http://1811way.com/work008/sample09.html
のhtmlファイルがあります。

このファイルに対して、simple_html_dom.php
を使って、
http://1811way.com/work008/sample09kekka.html
のように表示させたいです。

一番深いliタグの内容を[Contents]、それ以外を[Subject]としてひとまとめに表示させたいのですが、
できません。

できないというのは、アルゴリズムが組み立てられない、という事です。

参考までに、僕が書いたプログラムです。
これを元に、
sample09kekka.html
のように表示させるプログラムのアドバイス、考え方、もしくはソースを書いてくれると
大変助かります。


$html = file_get_html('sample09.html');
$i = 0;
foreach ($html->find('ul,h4,h3,h2') as $ul) {
$i++;
$test01 = $ul->find('h4',0);
echo $i . '番目の' . 'h4です:' . $test01 . '<br />';
}

以上、よろしくお願いします。

回答の条件
  • 1人1回まで
  • 登録:
  • 終了:2015/11/02 12:18:08
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:a-kuma3 No.1

回答回数4973ベストアンサー獲得回数2154

ポイント400pt

考え方でも良いということなので、javascript でやってみました(php は不得手です :-)。
jsFiddle で試したのがこちらです。
https://jsfiddle.net/a_kuma3/Lvm5vey1/

元になる HTML では SubjectNo2 と No3 の見出しタグの閉じタグが開始タグと一致していなかったので、そこは修正しています。

探索しているコードはこんな感じです。

// 探索ロジックはここから
var ul_list = document.body.querySelectorAll("UL");
Array.prototype.forEach.call(ul_list, function(ul) {
    if (! ul.querySelector("UL")) {
        var li_list = ul.querySelectorAll("LI");
        Array.prototype.forEach.call(li_list, function(item) {
            var bottom_content = item;
            var route = [];
            var LIMIT = 100;    // 無限ループが恐いので...
            while (item.tagName == "LI" && LIMIT > 0) {
                LIMIT = LIMIT - 1;
                // LI の親要素
                var p = item.parentNode;        // 多分 UL
                if (p.tagName == "BODY") {      // 念のため
                    break;
                }
                // UL よりも前にある要素を探す
                e = p.previousSibling;
                while (e.nodeType != 1) {
                    e = e.previousSibling;
                }
                // 見出し要素ならパンくずの道筋
                if (/^H[1234]/.test(e.tagName)) {
                    route.push(e);
                }
                // 見出しの親要素で続ける
                item = e.parentNode;            // 多分 LI
            }
            // 逆にしたのがパンくず
            var breadcrumb = route.reverse();

            // printout
            printout(breadcrumb, bottom_content);

            n += 1;
        });
    }
});

// 表示用
var n = 1;
function printout(breadcrumb, bottom_content) {
    var bc_text = breadcrumb.map(function(e) { return e.textContent; }).join(" > ");
    console.log(n + ".[Subect]:" + bc_text);
    console.log(n + ".[Contents]:" + bottom_content.textContent);

    var msg = document.getElementById("output");
    msg.value += n + ".[Subect]:" + bc_text + "\n";
    msg.value += n + ".[Contents]:" + bottom_content.textContent + "\n";
}

「一番深い LI タグ」は、「一番深い UL を見つけて、その下にある LI」という探し方をしています。
「一番深い UL」とは、「全ての UL のうち、その子供に UL がない UL」です。

パンくずは、HTML の構造に依存します。
「一番深い LI」のそれぞれについて、以下のロジックで探します
・LI の親要素を見つける(UL のはず)
・その UL と同じ階層にあって、それよりも前にある要素を探す
・その要素のタグが見出しタグなら、パンくずの候補に入れる
・その見出しタグの親(LI のはず)をターゲットにして繰り返す
・見出しタグの親要素が LI じゃなかったら探索の終了
・パンくずの候補は深い方から浅い方向の順番なので、配列をひっくり返す

javascript と php の Simple HTML DOM の差異について書いておきます。
ほとんどはメソッド・プロパティの読み替えだけで済むと思いますが、一点だけ大きく違うところがあります。

querySelectorAll → find

タグで要素を探すのに querySelectorAll というメソッドを使っています。
Simple HTML DOM では、find メソッドに相当します。

parentNode → parent

親の要素を取得します。

previousSibling → prev_sibling

その要素と同じ階層で、ひとつ前の要素を取得します。
ここが大きく違います。
javascript(というか、普通の DOM)では、このメソッドは Node を返します。
Node は、HTML のタグを表すもの(要素:Element)だけではなく、テキストやコメントなども表します。
先のコードで e.nodeType != 1 というループがあるのは、要素を探しています。

    // UL よりも前にある要素を探す
    e = p.previousSibling;
    while (e.nodeType != 1) {
        e = e.previousSibling;
    }

タグの間にある空白や改行も javascript の DOM では Node として取得されるので、nodeType プロパティを見て、要素が見つかるまで前に前にと探します。

Simple HTML DOM ではドキュメントに以下のように記載されています。

element$e->prev_sibling () Returns the previous sibling of element, or null if not found.

リファレンスを見ても、Text Node に関する記載がないので、多分、要素:Element がいきなり返ります。
タイプを見ながらさかのぼる必要はないと思います。

textContent → plaintext

ある要素の下位にあるテキスト要素だけを取り出します。
見出しタグを含めて取り出したければ、innertext を使います。




追記です。
php でもやってみました。

<?php
require "simple_html_dom.php";

$html = ...

// 表示用
function text_content($e) {
    return $e->plaintext;
}

function printout($breadcrumb, $bottom_content, $n) {
    echo $n . '.[Subject]:' . implode(' > ', array_map("text_content", $breadcrumb)) . '<br>';
    echo $n . '.[Contents]:' . $bottom_content->plaintext . '<br>';
}

$n = 1;

// 探索ロジックはここから
$ul_list = $html->find('UL');
foreach ($ul_list as $ul) {
    $ul_child = $ul->find('UL');
    if (count($ul_child) == 0) {
        $li_list = $ul->find('LI');
        foreach ($li_list as $item) {
            $bottom_content = $item;
            $route = array();
            $limit = 100;       // 無限ループが恐いので
            while ($item->tag == 'li') {
                $limit = $limit - 1;
                // LI の親要素
                $p = $item->parent();       // 多分 UL
                if ($p->tag == "body") {    // 念のため
                    break;
                }
                // UL の前にある要素
                $e = $p->prev_sibling();
                // 見出し要素ならパンくずの道筋
                if (preg_match('/^H[1234]/i', $e->tag)) {
                    $route[] = $e;
                }
                // 見出しの親要素で続ける
                $item = $e->parent();       // 多分 LI
            }
            $breadcrumb = array_reverse($route);

            printout($breadcrumb, $bottom_content, $n);
            $n = $n + 1;
        }
    }
}
?>

Phpfiddle で試してみたのがこちらです。
http://phpfiddle.org/lite/code/qz9p-b0t6

他4件のコメントを見る
id:kohhi

できました。というかa-kuma3さんのコピペですがしっかり結果出ました。僕が実際考えても、できないか、かなりな時間がかかりましたね。 ありがとうございました。

2015/11/02 12:17:37
id:a-kuma3

ideone.com がときどきこけるので、その押さえくらいにと思ってた phpfiddle が file_get_html で別サイトをアクセスできたり、MySQL や SQLite が使えるっぽいということが分かって、ぼくも収穫ありでした :-)

2015/11/02 13:08:34
  • id:rouge_2008
    回答リクエストいただきましたが、私もまだアルゴリズムが思いつきませんので、回答できるかは分かりません。
    一応もう少し考えてみます。(^^;

    ※だれか分かる人がいればいいですね。
  • id:kohhi
    お手数おかけしてすいません。
  • id:a-kuma3
    sample09kekka.html って、正確です?
    HTML では、SubjectNo1-4th と SubjectNo1 5th が同じ深さなので、
    1|Sub1-Top : Sub1-2nd : Sub1-3rd : Sub1-5th
    1|Sub1-Content01
    2|Sub1-Top : Sub1-2nd : Sub1-3rd : Sub1-5th
    2|Sub1-Content02
    となるのかな、と。
    つまり、SubjectNo1 4th が表示されない。

    SubjectNo2 Content* については、No2 になってないのは打ち間違い?

    後、SubjectNo3 は、子供が二つあるので、
    5|Sub3-Top
    5|Content01
    6|Sub3-Top
    6|Content02
    と来るのかな、と思うんですが。


    パンくずみたいなことをやりたいんですよね?
    最下層の LI と、そこに至るまでの UL-LI 階層のつながりを抽出したいと。

    違います?
  • id:kohhi
    早速お返事ありがとうございます。

    1.--ご指摘1
    sample09kekka.html って、正確です?
    HTML では、SubjectNo1-4th と SubjectNo1 5th が同じ深さなので、
    1|Sub1-Top : Sub1-2nd : Sub1-3rd : Sub1-5th
    1|Sub1-Content01
    2|Sub1-Top : Sub1-2nd : Sub1-3rd : Sub1-5th
    2|Sub1-Content02
    となるのかな、と。
    つまり、SubjectNo1 4th が表示されない。
    --ご指摘1 以上

    おっしゃる通りですね。
    SubjectNo1 4th -> 削除
    SubjectNo1 5th -> SubjectNo1 4th
     = SubjectNo1 5th を無くした。



    2.--ご指摘2
    SubjectNo2 Content* については、No2 になってないのは打ち間違い?
    --ご指摘2 以上

    ちょっと考えます。



    3.--ご指摘3
    後、SubjectNo3 は、子供が二つあるので、
    5|Sub3-Top
    5|Content01
    6|Sub3-Top
    6|Content02
    と来るのかな、と思うんですが。
    --ご指摘3 以上

    おっしゃる通りですね。
    追加しておきました。


    sample09kekka.html 、sample09.html
    どちらも、わかった部分は修正しました。

    よろしくお願いします。

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません