/*

 * Script table des matières. Ce script est publié sous licence GPL
 * article : http://giminik.developpez.com/articles/javascript-dom/table-des-matieres/
 * date : 2005-09-14
 * http://www.gnu.org/copyleft/gpl.html
 * Vous pouvez le modifier librement et le redistribuer.
 * Merci de m'indiquer tout bug, incompatibilité, amélioration 
 * à giminik   at   redaction-developpez.com
 */

/* Cette fonction permet d'afficher/cacher le contenu d'un élément dont on
 * connaît l'identifiant : containerId. Ici, on s'en sert pour cacher la 
 * liste de liens. En même temps, le nom de classe de l'élément titre de
 * la liste est modifié afin de pouvoir lui affecter un style CSS.
 * containerId : l'identifiant de l'élément html à afficher cacher.
 * classOpened : le nom de la classe à donner à l'élément html lorsqu'il est
 *               affiché.
 * classClosed : le nom de la classe à donner à l'élément html lorsqu'il est 
 *               caché.
 */ 
function TCSwap(containerId, classOpened, classClosed) {

  /* ici pas de problème avec firefox concernant firstChild
   * et lastChild car la fonction agit sur des noeuds générés
   * via le DOM. Il n'y a donc pas de noeud #text pour chaque
   * saut de ligne entre les différentes balises HTML. */

  // récupération de la liste de liens
  var linkList = document.getElementById(containerId).lastChild;

  // récupération du titre de la liste
  var listTitle = document.getElementById(containerId).firstChild;

  // si la liste n'est pas cachée
  if (linkList.style.display != 'none') {

    // on la cache
    linkList.style.display = 'none';

    // et on change la classe du titre (utile pour une feuille de style)
    listTitle.className = classClosed;

  }
  // si la liste n'est pas affichée
  else if (linkList.style.display != 'block') {

    // on l'affiche (type block)
    linkList.style.display = 'block';

    // et on change la classe du titre (utile pour une feuille de style)
    listTitle.className = classOpened;

  }

}


/* Cette fonction génère la table des matière. Elle construit les éléments
 * html et les insère dans l'arborescence du document.
 *
 * contentId : seuls les titres contenus dans l'élément (et ses sous éléments)
 *             ayant comme id contentId seront utilisés pour la table des matières.
 *             ce doit être un identifiant valide et existant.
 * insertBeforeId : la table des matières sera insérée juste avant l'élément
 *                  portant cet identifiant. ce doit être un identifiant
 *                  valide et existant.
 * containerId : le nom du conteneur sera celui passé en paramètre. cet
 *               identifiant ne doit pas déjà être utilisé dans la page.
 * minHead : par exemple 5 pour titre h5 : les titres hiérarchiquement inférieurs
 *           sont ignorés. doit être compris entre 1 et 6.
 * maxHead : par exemple 2 pour titre h2 : les titres hiérarchiquement supérieurs
 *           sont ignorés. doit être compris entre 1 et 6 et doit être inférieur
 *           à minHead.
 * tableHeadLevel : un titre est inséré pour annoncer la table des matières.
 *                  utilisez 3 pour que le titre de cette table des matières soit
 *                  h3. doit être compris entre 1 et 6.
 * clickable : booléen indique si la table des matières est rétractable sur
 *             l'évènement click. doit prendre comme valeur true ou false.
 */
function contentTable(contentId, insertBeforeId, containerId, minHead,
                      maxHead, tableHeadLevel, clickable) {

  // configuration, il est possible de les mettre en paramètre, mais je
  // préfère les laisser là pour ne pas surcharger le nombre d'arguments
  // à passer...

  // il s'agit du titre affiché avant la table.
  var contentTableTitle = 'Sommaire';

  // il s'agit du préfixe utilisé pour chaque ancre générée.
  var anchorName = 'tableDesMatieres';

  // il s'agit du suffixe utilisé pour chaque ancre générée. ce chiffre est
  // incrémenté pour chaque ancre. il s'agit ici de la valeur initiale du
  // compteur.
  var anchorsNumberingBeginning = 0;

  // nom de classe que prend le titre lorsque la liste est dépliée.
  // utile avec une feuille de style.
  var openedClass = 'ouvert';

  // nom de classe que prend le titre lorsque la liste est fermée.
  // utile avec une feuille de style.
  var closedClass = 'ferme';


  // pas de DOM, pas de table des matières
  if (!document.getElementById) return;


  // vérification de la plage des paramètres passés.
  // en cas d'erreur, utilisation des valeurs par défaut.

  if (!minHead || minHead < 1 || minHead > 6) {
    minHead = 6;
  }

  if (!maxHead || maxHead < 1 || maxHead > minHead) {
    maxHead = 1;
  }

  if (!tableHeadLevel || tableHeadLevel < 1 || tableHeadLevel > 6) {
    tableHeadLevel = 2;
  }


  // vérification de la non existence de l'identifiant, si incorrect, on affiche
  // un message d'erreur et on quitte ensuite.
  if (document.getElementById(containerId)) {

    alert(containerId + ' already exists in this page!');
    return;

  }
  else if (!document.getElementById(insertBeforeId)) {

    alert(insertBeforeId + ' is not an existing id!');
    return;

  }
  else if (!document.getElementById(contentId)) {

    alert(contentId + ' is not an existing id!');
    return;

  }
  else {
    // l'affichage de la table des matières ne se fait que si l'identifiant
    // est unique afin que le document soit bien formé et que l'identifiant
    // devant lequel on insère la table des matières existe.

    // création d'un conteneur pour la table des matières
    var TCContainer = document.createElement('div');

    // on lui affecte l'id passé en paramètre
    TCContainer.id = containerId;


    // l'élément contenant les titres à référencer
    var content = document.getElementById(contentId);


    // tableau contenant les noeuds titres pour un accès direct
    var chapters = Array();


    // remplissage récursif du tableau de noeuds titres
    headTag(content, chapters);


    // s'il y a moins de 2 titres, pas besoin de table des matières
    if (chapters.length < 2) return;


    // création d'un titre pour la table des matières
    var TCTitle = document.createElement('h' + tableHeadLevel);
    TCTitle.appendChild(document.createTextNode(contentTableTitle));

    // ajout du titre dans la table des matières
    TCContainer.appendChild(TCTitle);


    // création de la liste
    var theList = document.createElement('ul');


    if (clickable) {
        // si on indique que la table des matières doit se dérouler au click

      // appel de la fonction TCSwap en passant le paramètre conteneur.
      TCTitle.onclick = function() { TCSwap(containerId, openedClass, closedClass) };

      // on donne au titre le nom de la classe qui indique qu'il est déplié
      // cela permettra notamment pas l'intermédiaire d'une feuille de style
      // d'insérer une image...
      TCTitle.className = openedClass;

      // évènements onclick sur la liste
      theList.onclick = function() { TCSwap(containerId, openedClass, closedClass) };

    }

    // pour chaque chapitres
    for (var i = 0; i < chapters.length; i++) {


      // on connaît le numéro du titre (h1) 1, (h2) 2, (h3) 3, (h4) 4, (h5) 5 ou (h6) 6
      var titleNumber = parseInt(chapters[i].nodeName.charAt(1));


      // si le titre est bien dans la plage donnée par l'utilisateur
      if (titleNumber <= minHead && titleNumber >= maxHead) {

        // création d'un élément de liste
        var anItem = document.createElement('ul');


        // création d'un lien et affectation du contenu (textuel uniquement) du titre correspondant
        var aLink = document.createElement('a');
        aLink.appendChild(document.createTextNode(inText(chapters[i])));

        // pour pouvoir exploiter la liste avec une feuille de style
        // on donne comme nom de classe à chaque élément de liste, le type de titre pointé (h1 - h6)
        anItem.className = chapters[i].nodeName.toLowerCase();


        // préparation des liens
        // si le titre possède déjà un identifiant id, on l'utilise, sinon on en génère un unique
        if (chapters[i].id) { // si l'id existe

          // on fait pointer le lien vers l'identifiant du titre correspondant
          aLink.href = '#' + chapters[i].id;

        }
        else { // on va devoir générer un id pour le titre

          // on génère un identifiant unique
          // tant que l'identifiant existe déjà, on boucle
          do {

            anchorsNumberingBeginning++;

          } while (document.getElementById(anchorName + anchorsNumberingBeginning))


          // une fois ici, l'identifiant généré doit être unique
          chapters[i].id = anchorName + anchorsNumberingBeginning;

          // on fait pointer le lien vers l'identifiant de cette page
          aLink.href = '#' + chapters[i].id;

        }

        // ajout du lien dans l'élément de liste
        anItem.appendChild(aLink);

        // ajout de l'élément de liste dans la liste
        theList.appendChild(anItem);

      }

    }

    // ajout de la liste dans le conteneur de liste
    TCContainer.appendChild(theList);

    // récupération du parent contenant l'élément devant lequel on va insérer
    // la table des matières.
    var beforeElement = document.getElementById(insertBeforeId);

    // le parent dans lequel on va insérer la table des matières.
    var theParent = beforeElement.parentNode;

    // insertion de la table des matières.
    theParent.insertBefore(TCContainer, beforeElement);

  }

}




/* Cette fonction ajoute récursivement la liste des balises d'entêtes à l'intérieur 
 * d'un noeud dans le tableau passé en paramètres. Afin de conserver l'ordre et de 
 * prendre en compte tous les éléments d'un noeud, cette fonction est récursive.
 * node : Il s'agit du noeud dans lequel on recherche les éléments titre.
 * headArray : Il s'agit du tableau dans lequel on va ajouter les noeuds
 *             des éléments titre Hn.
 */
function headTag(node, headArray) {

  // le nombre de noeuds contenus dans le noeud passé en paramètre
  var childrenNumber = node.childNodes.length;

  // pour tous les noeuds enfants
  for (var i = 0; i < childrenNumber; i++) {

    // contient le noeud en cours
    var element = node.childNodes[i];

    // contient le nom du noeud en cours
    var elementName = element.nodeName.toLowerCase();

    // si c'est un titre hn
    if (elementName == 'h1' || elementName == 'h2' || elementName == 'h3' 
                            || elementName == 'h4' || elementName == 'h5' 
                            || elementName == 'h6') {

      // on ajoute le noeud titre dans le tableau
      // headArray.push(element); <-- non compatible avec IE 5 donc :
      headArray[headArray.length] = element;

      // un titre ne peut être contenu dans un autre titre, la récursivité s'arrête ici.

    }
    else {

      // appel récursif à la procédure
      headTag(element, headArray);

    }

  }

}


/* Cette fonction retourne le texte contenu dans un noeud, uniquement le texte.
 * Le texte est épuré de toutes les balises intermédiaires.
 * node : le noeud pour lequel on ne souhaite récupérer que la partie textuelle.
 */
function inText(node) {

  // le nombre de noeuds contenus dans le noeud passé en paramètre
  var childrenNumber = node.childNodes.length;

  // la chaîne (contenant le texte des balises) que l'on retourne
  var foundString = "";

  // récursivité : si pas de noeud on retourne le texte
  // sinon on retourne le résultat de la fonction exécuté sur tous les sous noeuds
  if (childrenNumber == 0) { // pas d'enfants dans le noeud
    // on renvoie la valeur textuelle contenue dans le noeud
    return node.nodeValue;
  }
  else { // il y a des enfants, on les parcourt tous

    // pour chaque noeud fils
    for (var i = 0; i < childrenNumber; i++) {

      // on concatène à ce qui a déjà été trouvée le résultat de la fonction appelée récursivement
      foundString += inText(node.childNodes[i]);

    }

    // on renvoie le résultat
    return foundString;

  }

}