Automatically create nested table of contents based on title tags

Which of your cunning programmers can show me an elegant php encoded solution for automatically creating a nested table of contents based on the title tags on the page?

So I have an html document:

<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.

In particular, I want a related table of contents in the form of a nested list of heading links on one page:

Content (automatically generated by PHP code)

  • Animals
    • Mammals
      • Terrestrial_mammals
      • Marine_mammals
        • Whales
+1
source share
3 answers

I found this method, 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;

In HTML, headers should be written as:

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

, , ;)

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();
+6

Here is an example using a 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();
+4
source

All Articles