人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

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 />';
}

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

●質問者: kohhi
●カテゴリ:ウェブ制作
○ 状態 :終了
└ 回答数 : 1/1件

▽最新の回答へ

1 ● a-kuma3
●400ポイント ベストアンサー

考え方でも良いということなので、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


kohhiさんのコメント
回答いただきまして、ありがとうございました。 javascriptは、よくわからないのですが、 これからPHPに変えてみます。 >元になる HTML では SubjectNo2 と >No3 の見出しタグの閉じタグが開始タグと一致していなかった すいません。なおしておきました。 取り急ぎご連絡まで。

a-kuma3さんのコメント
ぼくも余裕があったら、php で書いてみます。 ぼくも場合、配列に要素を追加したり、配列のループをしたり、などという辺りをいちいちマニュアルを見ないと書けないので、本質以外のところにとても時間がかかるので、ぱっと直せないんです (´・ω・`)

kohhiさんのコメント
ありがとうございます。マニュアル読んでやってます。難しいですね。

a-kuma3さんのコメント
php でもやってみました。 回答に追記してます。

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

a-kuma3さんのコメント
ideone.com がときどきこけるので、その押さえくらいにと思ってた phpfiddle が file_get_html で別サイトをアクセスできたり、MySQL や SQLite が使えるっぽいということが分かって、ぼくも収穫ありでした <tt>:-)</tt>
関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ