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 />';
}
以上、よろしくお願いします。
考え方でも良いということなので、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 というメソッドを使っています。
Simple HTML DOM では、find メソッドに相当します。
親の要素を取得します。
その要素と同じ階層で、ひとつ前の要素を取得します。
ここが大きく違います。
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 がいきなり返ります。
タイプを見ながらさかのぼる必要はないと思います。
ある要素の下位にあるテキスト要素だけを取り出します。
見出しタグを含めて取り出したければ、innertext を使います。
<?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
一応もう少し考えてみます。(^^;
※だれか分かる人がいればいいですね。
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 階層のつながりを抽出したいと。
違います?
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
どちらも、わかった部分は修正しました。
よろしくお願いします。