根据标题标记自动生成嵌套的目录

时间:2011-02-06 08:06:50

标签: php dynamically-generated

哪位狡猾的程序员可以向我展示一个优雅的PHP编码解决方案,用于根据页面上的标题标签自动生成嵌套的目录?

所以我有一个html文件:

<h1> Animals </h1>

Some content goes here.
Some content goes here.

<h2> Mammals </h2>

Some content goes here.
Some content goes here.

<h3> Terrestrial Mammals </h3>
Some content goes here.
Some content goes here.

<h3> Marine Mammals </h3>
Some content goes here.
Some content goes here.

<h4> Whales </h4>
Some content goes here.
Some content goes here.

更具体地说,我想要一个链接的目录,其形式为同一页面上标题的嵌套链接列表:

目录(由PHP代码自动生成)

  1. 动物
    1. 哺乳动物
      1. Terrestrial_Mammals
      2. Marine_Mammals

5 个答案:

答案 0 :(得分:7)

我觉得它不优雅,但可能有助于大致了解如何创建一个;)

它使用simple_html_dom来查找和操作原始html中的元素

$htmlcode = <<< EOHTML
<h1> Animals </h1>
Some content goes here.
Some content goes here.
<h2> Mammals </h2>
Some content goes here.
Some content goes here.
<h3> Terrestrial Mammals </h3>
Some content goes here.
Some content goes here.
<h3> Marine Mammals </h3>
Some content goes here.
Some content goes here.
<h4> Whales </h4>
Some content goes here.
Some content goes here.
EOHTML;
// simpehtmldom or other dom manipulating library
require_once 'simple_html_dom.php';

$html = str_get_html($htmlcode);

$toc = '';
$last_level = 0;

foreach($html->find('h1,h2,h3,h4,h5,h6') as $h){
    $innerTEXT = trim($h->innertext);
    $id =  str_replace(' ','_',$innerTEXT);
    $h->id= $id; // add id attribute so we can jump to this element
    $level = intval($h->tag[1]);

    if($level > $last_level)
        $toc .= "<ol>";
    else{
        $toc .= str_repeat('</li></ol>', $last_level - $level);
        $toc .= '</li>';
    }

    $toc .= "<li><a href='#{$id}'>{$innerTEXT}</a>";

    $last_level = $level;
}

$toc .= str_repeat('</li></ol>', $last_level);
$html_with_toc = $toc . "<hr>" . $html->save();

答案 1 :(得分:6)

以下是使用DOMDocument的示例:

$doc = new DOMDocument();
$doc->loadHTML($code);

// create document fragment
$frag = $doc->createDocumentFragment();
// create initial list
$frag->appendChild($doc->createElement('ol'));
$head = &$frag->firstChild;
$xpath = new DOMXPath($doc);
$last = 1;

// get all H1, H2, …, H6 elements
foreach ($xpath->query('//*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6]') as $headline) {
    // get level of current headline
    sscanf($headline->tagName, 'h%u', $curr);

    // move head reference if necessary
    if ($curr < $last) {
        // move upwards
        for ($i=$curr; $i<$last; $i++) {
            $head = &$head->parentNode->parentNode;
        }
    } else if ($curr > $last && $head->lastChild) {
        // move downwards and create new lists
        for ($i=$last; $i<$curr; $i++) {
            $head->lastChild->appendChild($doc->createElement('ol'));
            $head = &$head->lastChild->lastChild;
        }
    }
    $last = $curr;

    // add list item
    $li = $doc->createElement('li');
    $head->appendChild($li);
    $a = $doc->createElement('a', $headline->textContent);
    $head->lastChild->appendChild($a);

    // build ID
    $levels = array();
    $tmp = &$head;
    // walk subtree up to fragment root node of this subtree
    while (!is_null($tmp) && $tmp != $frag) {
        $levels[] = $tmp->childNodes->length;
        $tmp = &$tmp->parentNode->parentNode;
    }
    $id = 'sect'.implode('.', array_reverse($levels));
    // set destination
    $a->setAttribute('href', '#'.$id);
    // add anchor to headline
    $a = $doc->createElement('a');
    $a->setAttribute('name', $id);
    $a->setAttribute('id', $id);
    $headline->insertBefore($a, $headline->firstChild);
}

// append fragment to document
$doc->getElementsByTagName('body')->item(0)->appendChild($frag);

// echo markup
echo $doc->saveHTML();

答案 2 :(得分:4)

我发现了这个方法,由Alex Freeman(http://www.10stripe.com/articles/automatically-generate-table-of-contents-php.php):

    preg_match_all('#<h[4-6]*[^>]*>.*?<\/h[4-6]>#',$html_string,$resultats);

    //reformat the results to be more usable
    $toc = implode("\n",$resultats[0]);
    $toc = str_replace('<a name="','<a href="#',$toc);
    $toc = str_replace('</a>','',$toc);
    $toc = preg_replace('#<h([4-6])>#','<li class="toc$1">',$toc);
    $toc = preg_replace('#<\/h[4-6]>#','</a></li>',$toc);

    //plug the results into appropriate HTML tags
    $toc = '<div id="toc"> 
    <p id="toc-header">Table des matières</p>
    <hr />
    <ul>
    '.$toc.'
    </ul>
    </div><br /><br />';

    return $toc;

在HTML中,标题必须写为:

<h2><a name="target"></a>Text</h2>

答案 3 :(得分:0)

看看TOC class。它允许从嵌套标题生成目录。 h1标签之后可以是任何较低级别的h标签。该类使用递归从文章文本中提取标题

答案 4 :(得分:0)

使用 SimpleHTMLDom 的简短解决方案:

public function getSummary($body) 
{
    $dom  = new Htmldom($body);
    $summ = "<ul>";
    $prev = 2;

    foreach($dom->find("h2,h3,h4") as $x => $htag) 
    {
        $curr = intval(substr($htag->tag, -1));

        $prev <= $curr ?: $summ .= "</ul>";
        $prev >= $curr ?: $summ .= "<ul>";

        $summ .= "<li>$htag->plaintext</li>";

        $prev = $curr;
    }

    $summ .= "</ul>";

    return $summ;
}