Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
PHP Table Of Content extracting
<?php
require_once('./url_title.php');
require_once('./xss_clean.php');
if (!function_exists('extract_toc')) {
function extract_toc(DOMDocument $dom, int $max_level = 6) {
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('html', 'http://www.w3.org/1999/xhtml');
$max_level = min(max($max_level, 1), 6); // Les titres en HTML vont de h1 à h6 maximum
$headings_queries = [];
foreach (range(1, $max_level) as $h) {
$headings_queries[] = 'self::h'.$h; // Les requêtes XPath partent du nœud courant, pour chercher les balises `hX`
}
$query_headings = implode(' or ', $headings_queries); // On sélectionne toutes les balise `h1` ou `h2` ou `h3`, etc.
$query = '//*['.$query_headings.']'; // On part de la racine pour chercher les balises en question
$headings = $xpath->query($query);
$toc = '';
if (count($headings)) {
$current_level = 0;
$items = 0;
foreach ($headings as $n_i => $node){
$level = (int) $node->tagName{1};
$node_id = $node->getAttribute('id');
if (empty($node_id)) {
// On profite de l'occasion pour ajouter un ID à notre titre directement dans le DOM, pour pouvoir utiliser des ancres
$node_id = 'toc_'.url_title(strip_tags($node->textContent), '-', TRUE);
$node->setAttribute('id', $node_id);
}
// On crée un lien vers le titre en question
$new_toc = '<a href="#'.$node_id.'">'.xss_clean($node->textContent).'</a>';
if ($level > $current_level) {
// On monte d'un ou plusieurs niveaux, on crée une nouvelle liste
for ($a = 0; $a < $level-$current_level; $a++) {
$toc .= "\n".str_repeat("\t", $current_level * 2).'<ol class="toc-level-'.$level.'">'."\n".str_repeat("\t", ($current_level * 2) + 1).'<li>';
}
$toc .= $new_toc;
$items = 1;
}
elseif ($level === $current_level) {
$toc .= ($items ? '</li>'."\n" : '').str_repeat("\t", ($level * 2) - 1).'<li>'.$new_toc;
$items++;
}
else {
// On descend d'un niveau, on ferme la liste
for ($a = 0; $a < $current_level-$level; $a++) {
$toc .= "\n".str_repeat("\t", ($level * 2) + 1).'</li>'.
"\n".str_repeat("\t", $level * 2).'</ol>';
}
$toc .= "\n".str_repeat("\t", ($level * 2) - 1).'</li>'."\n".str_repeat("\t", ($level * 2) - 1).'<li>'.$new_toc;
$items = 0;
}
$current_level = $level;
}
// Enfin, on n'oublie pas de fermer les listes ouvertes
for ($a = $level - 1; $a >= 0; $a--) {
$toc .= "\n".str_repeat("\t", ($a * 2) + 1).'</li>'.
"\n".str_repeat("\t", $a * 2).'</ol>';
}
}
return $toc;
}
}
<?php
require_once('./extract_toc.php');
$page_html = file_get_contents('text.html');
$page_html = mb_convert_encoding($page_html, 'HTML-ENTITIES', 'UTF-8');
$page_dom = new DOMDocument('2.0', 'UTF-8'); // Mon fichier HTML étant encodé en UTF-8, je peux préciser cet encodage au parseur
$page_dom->loadHTML($page_html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$page_toc = extract_toc($page_dom);
$page_html = $page_dom->saveHTML(); // On peut sauvegarder le DOM mis à jour par la fonction `extrat_toc` pour bénéficier des ancres
print $page_toc; // On affiche notre sommaire
print "\n".'<hr>'."\n";
print $page_html; // On affiche le contenu de notre HTML
<p>Un texte d'intro</p>
<h1>Un premier titre</h1>
<h2>Un sous-titre</h2>
<p>Du texte</p>
<h2>Un autre sous-titre</h2>
<h3>Le PHP c'est génial</h3>
<p>Encore du texte</p>
<h3>On peut faire plein de choses !</h3>
<p>Toujours du texte</p>
<h1>Un second titre</h1>
<h2>Encore un sous-titre</h2>
<h3>Le PHP c'est vraiment génial</h3>
<p>Encore plus de texte</p>
<h3>On peut faire tout plein de choses !</h3>
<p>Toujours plus de texte</p>
<h2>Encore un autre sous-titre</h2>
<p>Vous reprendrez bien un peu de texte ?</p>
<?php
if (!function_exists('url_title')) {
function url_title($string, $separator, $lowercase) {
$result = implode($separator ?? '-',explode(" ", $string));
return $lowercase ? strtolower($result) : $result;
}
}
<?php
if (!function_exists('xss_clean')) {
function xss_clean($string) {
return htmlspecialchars($string);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment