Ajouter automatiquement une table des matières dans une page web

Fonction PHP permettant d'insérer une table des matières (ou sommaire, même si ce n'est pas tout à fait pareil) dans une page web.
Cette table des matières est générée automatiquement à partir des balises Hn de la page.

Requis

La page web doit être correctement structurée, à savoir par l'utilisation des balises h1, h2, h3, h4, h5, h6 (pas forcément toutes). h1, h2, h3 étant apparemment les plus importantes.

De plus une bonne structure, aide au référencement. Je ne suis pas du tout un expert, et je cherche pas à être connu. Donc, si vous voulez des infos voir https://www.webrankinfo.com/dossiers/techniques/balises-h1-h2-h3.
Entre autres, car il existe une multitudes de site web traitant de ce sujet. Google (ou un autre) sera votre ami !

Le sommaire est donc généré à partir de ces balises.

La balise h1 n'est logiquement pas prise en compte dans le sommaire, puisqu'il faut théoriquement qu'il en ait qu'une par page. Après si on veut l'inclure dans le sommaire, c'est possible.

Comment insérer un sommaire

C'est très simple. Dans la page où l'on veut afficher le sommaire, insérer ce code :

<?php
echo nwSommaire();
?>

C'est tout... c'est totomatique !

Après les css font le reste pour la mise en page et le responsive.
Pour ma part en version bureau le sommaire s'affichera dans la colonne droite et sur petit écran, il s'affichera en haut sous le titre h1 (il faudra toucher Sommaire pour l'afficher si besoin)

Code de la fonction PHP

<?php
/**
 * functions-sommaire.php
 *
 * Génération d'une table des matières (à partir des balises Hn contenues dans la page)
 * h1 est ignoré (théoriquement il doit y en avoir qu'un par page)
 *
 * www.nonoweb.net
 */

define('NTH_NIVEAU_MAX', 6);
define('NTH_TDM_SEPARE_NUMBER', '.');
define('NTH_TDM_AFTER_NUMBER', ' ');
define('NTH_TDM_FORMAT_INDEX', '-%s');
define('NTH_TDM_PATTERN', "/<h([2-".NTH_NIVEAU_MAX."])(.*?)>(.*?)<\/h([2-".NTH_NIVEAU_MAX."])>/i");

/*
function nthCleanTextLink($t) {
	// y a plus rien ici...
	return $t;
}
*/

$nth_ccc = 0;
function nthReplaceTag($matches) {
	global $tbl_index_anchor, $nth_ccc;
	$numerotation = ($GLOBALS['num_tag']) ? '<span class="numerotation-tag-h'.$matches[1].'">'.$tbl_index_anchor[$nth_ccc]['number'].'</span>' : '';
	$new_tag = '<h'.$matches[1].$matches[2].' id="'.$tbl_index_anchor[$nth_ccc]['anchor'].$tbl_index_anchor[$nth_ccc]['index'].'">'.$numerotation.$matches[3].'</h'.$matches[4].'>';
	$nth_ccc++;
	// test('<span style="white-space: pre-wrap;">'.htmlspecialchars($new_tag).'</span>', 't=$tdm&l='.basename(__FILE__).':'.__LINE__.' ');
	return $new_tag;
}

function addAnchorContent($content) {
	$content = preg_replace_callback(NTH_TDM_PATTERN, 'nthReplaceTag', $content);
	return $content;
}

/**
 * function nwSommaire
 * paramètres :
 * class    : (défaut '') class appliquées sur tag <nav> ex. class="toto titi"
 * style    : (défaut '') styles appliqués sur tag <nav> ex. style="width: 50%;"
 * num_link : (défaut 1) la numérotation (si définie) sera comprise dans le lien
 * num_tdm  : (défaut 1) numérotation table des matières
 * num_tag  : (défaut 0) numérotation devant les titres hx
 */
function nwSommaire($args = '') {
	global $content, $tbl_index_anchor;
	$default = array(
			'class'		=> '',
			'style'		=> '',
			'num_link'	=> 1,
			'num_tdm'	=> 1,
			'num_tag'	=> 0,
	);
    if ($args != '') {
        if (is_string($args))
			parse_str($args, $tbl_args);
        else
			$tbl_args = $args;
		$new = array_merge ($default, $tbl_args);
		extract($new, EXTR_SKIP);
    } else {
		extract($default, EXTR_SKIP);
    }
	// *******************************
	$GLOBALS['num_tag'] = $num_tag;
	// *******************************
	preg_match_all(NTH_TDM_PATTERN, $content, $matches);
	/*
	test($matches[1], 'h=200&t=$matches[1]&l='.basename(__FILE__).':'.__LINE__.' ');
	test($matches[2], 'h=200&t=$matches[2]&l='.basename(__FILE__).':'.__LINE__.' ');
	test($matches[3], 'h=200&t=$matches[3]&l='.basename(__FILE__).':'.__LINE__.' ');
	test($matches[4], 'h=200&t=$matches[4]&l='.basename(__FILE__).':'.__LINE__.' ');
	*/
	$tbl_index_anchor = array();
	foreach (array_reverse($matches[3]) as $c1 => $ti) {
		$tbl_index_anchor[$c1]['titre'] = $ti;
		// function sanitizeString
		// phpMyForm : Champ de type « text » -> phpmyform-champ-de-type-text
		$tbl_index_anchor[$c1]['anchor'] = sanitizeString(strip_tags($ti));
	}
	// test($tbl_index_anchor, 'h=200&t=$tbl_index_anchor&l='.basename(__FILE__).':'.__LINE__.' ');
	if (empty($tbl_index_anchor)) return false; // ajout 11-05-2023 (sinon erreur vers ligne 106 $new_array existe pas car pas de premier h2)
	/**
	 * ajout d'un index aux ancres si plusieures identiques (même si faut pas de hn identique en principe)
	 */
	foreach($tbl_index_anchor as $row) {
		$new_array[] = $row['anchor'];
	}
	$tbl_temp = array_count_values($new_array);
	// test($tbl_temp, 'h=200&t=$tbl_temp&l='.basename(__FILE__).':'.__LINE__.' ');

	foreach ($tbl_index_anchor as $x => $ttt) {
		if ($tbl_temp[$ttt['anchor']] == 1) {
			$tbl_index_anchor[$x]['index'] = '';
		}
		else {
			// plus d'une occurence du titre dans la page
			$tbl_temp[$ttt['anchor']] = $tbl_temp[$ttt['anchor']] - 1;
			$tbl_index_anchor[$x]['index'] = sprintf(NTH_TDM_FORMAT_INDEX, $tbl_temp[$ttt['anchor']]);
		}
	}
	unset($tbl_temp);
	$tbl_index_anchor = array_reverse($tbl_index_anchor);
	/**
	 * Génération numéro index
	 * Afficher dans la table des matières (paramètre shortcode num_tdm à 1)
	 * Afficher devant les titres Hx de la page (paramètre shortcode num_tag à 1)
	 */
	if ($num_tdm || $num_tag) {
		$format = array();
		$chaine = array();
		$tbl_args = array();

		for ($i = 0; $i < NTH_NIVEAU_MAX - 1; $i++) {
			$chaine[$i] = '%s';
			$format[$i] = implode(NTH_TDM_SEPARE_NUMBER, $chaine).NTH_TDM_AFTER_NUMBER;
			// test($format[$i], 't=$format[$i]');
			$tbl_niveau[$i] = 0;
		}
		foreach ($matches[3] as $h => $titre) {
			$idx = $matches[1][$h] - 2;
			$tbl_niveau[$idx]++;
			for ($j = 0; $j < $idx + 1; $j++) {
				$tbl_args[$j] = $tbl_niveau[$j];
			}
			$tbl_index_anchor[$h]['number'] = vsprintf($format[$idx], $tbl_args);
			if ($matches[1][$h] != NTH_NIVEAU_MAX) {
				if (isset($mem) && $mem != $matches[1][$h]) $tbl_niveau[$idx + 1] = 0;
				$mem = $matches[1][$h];
			}
		}
	}
	/**
	 * Génération de la table des matières
	 *
	 * <nav id="nth-sommaire" role="navigation" ... >
	 *   <ul>
	 *     <li class="nth-niveau-X">
	 *       <a href="#mon-titre">Mon titre</a>
	 *     </li>
	 *       ......
	 *   </ul>
	 * </nav>
	 */
	$tdm = '';
	$tbl_class = [];
	$chn_style = '';

	if (!empty($class)) $tbl_class[] = $class;
	// $tbl_class[] = 'margin-bottom';
	if (!empty($style)) $chn_style .= $style;
	// $chn_style .= 'border: 1px solid red;';
	$attr_nav = [];
	$attr_nav[] = 'id="nth-sommaire"';
	$attr_nav[] = 'role="navigation"';
	if (!empty($tbl_class)) $attr_nav[] = 'class="'.implode(' ', $tbl_class).'"';
	if (!empty($chn_style)) $attr_nav[] = 'style="'.$chn_style.'"';
	$tdm .= '<nav';
	if (!empty($attr_nav)) $tdm .= ' '.implode(' ', $attr_nav);
	$tdm .= '>'."\n";
	// #################################
	// #################################
	$tdm .= '<input id="toggle-tdm" type="checkbox"><label for="toggle-tdm"></label>'."\n";
	// #################################
	// #################################
	$tdm .= '<ul>';
	foreach ($matches[3] as $h => $titre) {
		$tdm .= '<li class="nth-niveau-'.$matches[1][$h].'">';
		$numerotation = ($num_tdm) ? '<span class="numerotation-tdm-'.$matches[1][$h].'">'.$tbl_index_anchor[$h]['number'].'</span>' : '';
		if (!$num_link) $tdm .= $numerotation;
		$tdm .= '<a href="#'.$tbl_index_anchor[$h]['anchor'].$tbl_index_anchor[$h]['index'].'">';
		if ($num_link) $tdm .= $numerotation;
		// $tdm .= strip_tags(nthCleanTextLink($tbl_index_anchor[$h]['titre'])).'</a>';
		$tdm .= strip_tags($tbl_index_anchor[$h]['titre']).'</a>';
		$tdm .= '</li>'."\n";
	} // end foreach ($matches[3] as $h => $titre)
	$tdm .= '</ul>';
	$tdm .= '</nav><!-- /nav#nth-sommaire -->';
	// test($tbl_index_anchor, 'h=200&t=$tbl_index_anchor&l='.basename(__FILE__).':'.__LINE__.' ');
	// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
	$content = addAnchorContent($content);
	// AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
	return $tdm;
}

Feuille de style CSS

/**
 * Styles sommaire
*/
nav#nth-sommaire {
	line-height: 1.5;
	outline: 1px solid gainsboro;
	background-color: #F9F9F9;
	width: 100%;
	border-radius: 5px;
}

nav#nth-sommaire ul {
	list-style: none;
	margin-bottom: 0;
	padding: 10px;
	max-height: 600px;
	overflow: auto;
	font-size: 95%;
}

nav#nth-sommaire ul li[class*="nth-niveau-"] a {text-decoration: none;}

nav#nth-sommaire ul li[class*="nth-niveau-"] a span[class*="numerotation-tdm-"] {}
span.numerotation-tdm-2 {}
span.numerotation-tdm-3 {}
span.numerotation-tdm-4 {}
span.numerotation-tdm-5 {}
span.numerotation-tdm-6 {}

span[class*="numerotation-tag-h"] {}
span.numerotation-tag-h2 {}
span.numerotation-tag-h3 {}
span.numerotation-tag-h4 {}
span.numerotation-tag-h5 {}
span.numerotation-tag-h6 {}

nav#nth-sommaire ul li[class*="nth-niveau-"] {}
nav#nth-sommaire ul li.nth-niveau-2 {margin-left: 0px;}
nav#nth-sommaire ul li.nth-niveau-3 {margin-left: 30px;}
nav#nth-sommaire ul li.nth-niveau-4 {margin-left: 50px;}
nav#nth-sommaire ul li.nth-niveau-5 {margin-left: 70px;}
nav#nth-sommaire ul li.nth-niveau-6 {margin-left: 90px;}

nav#nth-sommaire label {display: none;}
nav#nth-sommaire input#toggle-tdm {
	display: none;
	margin: 0;
	padding: 0;
	width: 100%;
	opacity: 0;
	cursor: default;
}

nav#nth-sommaire label:before {
	line-height: 1.3em;
	font-size: 1.3em;
	content: "\f196";
	font-family: FontAwesome;
	margin-right: 4px;
	vertical-align: -6%;
}

nav#nth-sommaire label:after {
	line-height: 1.3em;
	content: "Sommaire";
	font-family: inherit;
}

/*
caret-up : f0d8
caret-down : f0d7
plus-square-o : f196
minus-square-o : f147
*/

/* ***************************** */

@media screen and (max-width: 785px) {
	nav#nth-sommaire {}
} /* end @media screen and (max-width: 785px) */

@media (max-width: 767px) {
	nav#nth-sommaire label {
		margin-left: 5px;
		display: block;
		line-height: 2.2;
	}
	nav#nth-sommaire ul {display: none;}
	nav#nth-sommaire  input:checked + label {}
	nav#nth-sommaire  input:checked + label:before {
		line-height: 1.3em;
		font-size: 1.3em;
		content: "\f147";
		font-family: FontAwesome;
		margin-right: 4px;
		vertical-align: -6%;
	}
	nav#nth-sommaire  input:checked + label:after {
		line-height: 1.3em;
		content: "Fermer le sommaire";
		font-family: inherit;
	}
	nav#nth-sommaire input:checked ~ ul {
		display:block;
		max-height: 200px;
	}

} /* end @media */

@media (max-height: 350px) {
	nav#nth-sommaire ul {
		max-height: 300px;
	}
}

Script JavaScript facultatif

Pour un défilement fluide, plus agréable, du sommaire à la section, j'utilise le script javascript jQuery Smooth Scroll.

Voir tout ça en action, par exemple à cette longue page, où une table des matières me semble des plus appropriée. Sur une page courte comme celle sur laquelle on est actuellement, cela a moins d'utilité par contre.

Laisser un commentaire (Seuls le nom et le commentaire sont requis).
Les commentaires sont modérés avant publication.

 
Sélection smileys