Fix bug with entities inside header text.
[xhtml-compiler.git] / XHTMLCompiler / DOMFilter / GenerateTableOfContents.php
blob581721519070125652ee3a25e1cfc808e8ac0aff
1 <?php
3 /**
4 * Generates a table of contents based on the heading structure of the
5 * document.
6 */
7 class XHTMLCompiler_DOMFilter_GenerateTableOfContents extends XHTMLCompiler_DOMFilter
10 protected $name = 'GenerateTableOfContents';
12 public function process(DOMDocument $dom, $page, $manager) {
14 // test for ToC container, if not present don't bother
15 // currently, only id="toc" is supported, which means there can
16 // only be one table of contents per page
17 $container = $this->query("//html:div[@id='toc']")->item(0);
18 if (!$container) return;
20 // grab all headings h2 and down from the document
21 $headings = array('h2', 'h3', 'h4', 'h5', 'h6');
22 foreach ($headings as $k => $v) $headings[$k] = "self::html:$v";
23 $query_headings = implode(' or ', $headings);
24 $query = "//*[$query_headings]"; // looks like "//*[self::html:h2 or ...]"
25 $headings = $this->query($query);
27 // setup the table of contents element
28 $toc = $dom->createElement('ul');
29 $toc->setAttribute('class', 'toc-base');
30 $container->appendChild($dom->createElement('h2', 'Table of Contents'));
31 $container->appendChild($toc);
33 // iterate through headings and build the table of contents
34 $current_level = 2;
35 $parents = array(false, $toc);
36 $indexes = array(0);
37 $i = 0;
38 foreach ($headings as $node) {
39 $level = (int) $node->tagName[1];
40 $name = $node->textContent; // no support for formatting
42 while ($level > $current_level) {
43 if (!$parents[$current_level-1]->lastChild) {
44 $parents[$current_level-1]->appendChild(
45 $dom->createElement('li')
48 $sublist = $dom->createElement('ul');
49 $parents[$current_level - 1]->lastChild->appendChild($sublist);
50 $parents[$current_level] = $sublist;
51 $current_level++;
52 $indexes[$current_level - 2] = 0;
55 while ($level < $current_level) {
56 unset($indexes[$current_level - 2]);
57 $current_level--;
60 $indexes[$current_level - 2]++;
63 $line = $dom->createElement('li');
64 $label = $dom->createElement('span', implode('.', $indexes) . '.');
65 $label->setAttribute('class', 'toc-label');
66 $line->appendChild($label);
67 $link = $dom->createElement('a', htmlspecialchars($name));
68 $line->appendChild($link);
69 $parents[$current_level-1]->appendChild($line);
71 // setup the anchors
72 $header_id = $node->getAttribute('id');
73 if (!$header_id) {
74 // automatically generate a header. These are volatile,
75 // so it's highly recommended that you manually specify
76 // them
77 // TODO: generate based on the contents of the header node
78 $header_id = 'toclink' . $i;
79 $node->setAttribute('id', $header_id);
80 $i++;
82 $link->setAttribute('href', '#' . $header_id);