• fra.png

Need a short translation ? Go to the english page...

InDesignSecrets en parle... (Juil. 2011) — This script has been updated at Indiscripts.com

MISE À JOUR (01.07.11). — Les archives d’IndexBrutal (et IndexMatic1) restent accessibles dans le grenier d’Indiscripts.com. Toutefois, ces anciens scripts ont été définitivement supplantées par IndexMatic2 pour InDesign CS3/CS4/CS5+. IndexMatic2 offre le produit de toutes les fonctionnalités d’IndexBrutal et IndexMatic1 réunis, plus des options de filtrage, une flexibilité et des performances sans précédent : www.indiscripts.com/category/projects/IndexMatic.

NOTE CRUCIALE (12.09.08).IndexBrutal se prépare à muter définitivement vers CS3/CS4 en combinaison avec IndexMatic. La version archivée sur cette page doit donc être considérée comme transitoire. Il y reste un bug endémique portant sur le processus de nettoyage en amont du rechercher/remplacer. Sous certaines circonstances mal élucidées, vous devrez parfois réinitialiser manuellement les champs dans tous les onglets (« Text », « Grep »...) avant de lancer ou de relancer le script, spécialement si vous avez opéré des remplacements automatiques dans vos documents entre deux indexations. Enfin, pour parer à tout accident, n’oubliez jamais de sauvegarder vos fichiers avant d’exécuter IndexBrutal !


(MàJ du 29.02.08)      La version 2.1 d’IndexBrutal corrige un bug sévère du tri alphabétique et apporte un patch important pour les utilisateurs de CS3. Détail technique dans le fil de news...


Interface française sous Mac OS, InDesign CS Interface anglaise sous Windows, InDesign CS3


Mise en garde

Qu’il soit dit haut et fort que la production ex machina d’une table d’index (matières, mots-clés, auteurs cités, etc.) est une utopie. L’homonymie, la polysémie et une armada de pièges morphosyntaxiques font qu’il est impossible de « déshumaniser » l’extraction des termes pertinents d’un texte. Imaginez la situation suivante : vous bouclez la mise en page d’une encyclopédie historique de la médecine, du genre 1800 pages, et on vous demande à brûle-pourpoint d’indexer une liste de signifiants, tels que cœur ou casse (« pulpe de la gousse de cassier »). Vous voyez d’ici le bazar ! Le mot cœur va affleurer dans des tas de locutions parasites (par cœur, cœur du débat, en avoir le cœur net) et inversement, l’automate manquera la cible chaque fois qu’elle se cachera derrière un voisinage sémantique (cardiaque, myocarde, palpitation, infarctus...). Quant au terme casse, il est presque certain que 99 % des occurrences relevées ne correspondront en rien avec l’acception spécifiquement recherchée.

Bref, sauf à disposer d’outils linguistiques ultra-puissants, on n’automatise pas une indexation. On demande à l’auteur (ou à l’éditeur) de préparer le travail, d’étiqueter le texte fourni à la compo, puis on utilise le protocole prévu par InDesign pour transporter ce balisage dans le document de travail (création de références et de rubriques au fil du traitement). En fin de course, la seule opération vraiment automatique est la génération de la table de correspondances (via la palette Index) entre les entrées et les folios (numéros de page), assortie des éventuels renvois.

Le script IndexBrutal est donc une solution de fortune lorsqu’un client vous réclame un index a posteriori — et en catastrophe ! — alors que lui-même n’a pas anticipé cette tâche éditoriale et se repose sur les vertus présumées de la fonction « Rechercher... » :

le client. — Au fait, j’avais oublié de vous dire... euh... j’ai une petite liste de mots-clés qu’il faudrait indexer...

vous. — Heink ? !

le client. — Oui, juste une cinquantaine de mots... 70 maximum, j’ai pas compté... C’est possible de faire un index avec ça ? (Il guette votre réaction) Oh, un truc tout simple, hein ? Le mot et en face les pages correspondantes. Voyez ?

vous. — Ben c’est-à-dire qu’il y a quand même 240 pages à...

le client. — ...Ouais mais bon avec la fonction « Rechercher », vous devriez vous en tirer en une demi-journée, non ?

vous. — Je dirais plutôt deux jours ! Parce que 70 opérations de recherche sur 240 pages, vous comprenez, ça fait un peu de boulot, mine de rien. Va falloir que j’ajuste mon devis, là !

le client (grognon). — Bah, toute façon j’ai pas le choix, ça vient du directeur de département...

Ne dites jamais à vos clients que vous scriptez sous InDesign ! Facturez deux jours d’exé en supplément et lancez tranquillement IndexBrutal : il va faire le job en 15 secondes(1) !

(1) Ou même beaucoup moins : on a observé des écarts très significatifs de performance entre InDesign CS et les versions ultérieures. Notez que la version actuelle du script, afin de conserver l’homogénéité avec les versions CS/CS2/CS3 d’InDesign, n’utilise pas la syntaxe de recherche introduite dans la CS3. Une instruction de compatibilité (app.scriptPreferences.version = 4.0) impose à ID CS3 d’interpréter le code comme du patois CS2. Cela n’empêche pas d’excellentes performances, tant sous CS2 que sous CS3. Seul ID CS vous semblera agoniser si vous soumettez à IndexBrutal un très gros document et beaucoup de clefs à rechercher. Ce sera l’occasion d’aller vous faire un petit jus...

Principes de l’indexation « brutale »

Comme son nom l’indique, le script mène ses investigations sans fioritures : il ignore les expressions régulières, se moque des flexions morphologiques, des homographes, etc.(2). Son boulot est de rechercher les mots-clefs d’une liste (fournie en entrée) au sein du ou des document(s) de travail. Il repère toutes les apparitions de chacun de ces mots-clefs et produit en sortie une table de correspondance terme — folio(s). Les versions successives d’IndexBrutal ont permis d’affiner les critères de recherche et de présentation des résultats : respect de la casse paramétrable, « mot entier » ou fragment, regroupement des folios contigus, regroupement de clefs sous un même terme...

(2) Dans les situations délicates où une discrimination sémantique est incontournable, le script vous permettra de dégrossir le terrain en collectant les apparitions de certains termes ou expressions, à charge pour vous de tamiser.

Voici le scénario de base :
      a) vous créez tout d’abord une liste d’entrées dans un fichier texte (elle contiendra les clefs à rechercher et divers arguments optionnels que nous allons examiner plus loin) ;
      b) vous ouvrez dans InDesign votre maquette (un document ou même plusieurs(3) rassemblés dans un livre) et vous lancez IndexBrutal ;
      c) vous indiquez au script l’emplacement du fichier d’entrées (l’indexeur) et vous réglez les paramètres de travail dans la boîte de dialogue ;
      d) le script recherche dans le ou les document(s) cible(s) les occurrences de chacune des clefs et mémorise dans une table le numéro des pages correspondantes (plus exactement la propriété Page::name) ;
      e) en fin de course, il crache la table d’index dans un fichier texte ou dans le presse-papier, selon l’option choisie.

(3) La vocation de ce script m’a conduit à le concevoir d’emblée dans une optique multi-documents, car les bons maquettistes traitent aujourd’hui les ouvrages d’épaisseur respectable grâce à la fonctionnalité livre, basée sur des documents multiples paginés automatiquement.

Figure 1 - Affichage du fichier d’index

La figure 1 donne en exemple un fichier d’index généré sur un ouvrage en cours de composition sous InDesign (Le rugbyman nomade, Richard Beard, à paraître aux éditions Michel Champendal). La douzaine de mots-clés fournis via le fichier d’entrées a été traquée dans les 376 pages du livre. Le générateur a été configuré ici de façon à regrouper les folios contigus, ainsi les repères du type 64-67 indiquent que la clef est présente aux pages 64, 65, 66 et 67. Pour les besoins de ma démonstration, j’ai ajouté la clef dentifrice, introuvable dans le texte, qui ressort alors avec un marquage spécial décidé par l’utilisateur (ici, un tiret cadratin). On pourrait également demander au script d’évacuer de la table les clefs introuvables (v. interface dialoguée).

Syntaxe des entrées

Le fichier d’entrées que vous créez avant de lancer IndexBrutal consiste pour l’essentiel en une liste de mots séparés par des retours-chariot. À l’origine, le script prenait ces entrées telles quelles, une par une, et menait une recherche insensible à la casse avec l’option « mot entier » par défaut. Il est vite apparu aux utilisateurs que figer ces principes nous privait de fonctionnalités importantes pour l’indexation. Ainsi ont été ajoutés des caractères de contrôle (appelés opérateurs) permettant de corriger ponctuellement la stratégie par défaut d’IndexBrutal.

Les trois opérateurs actuels sont :

Op.Nom usuelFonction
 | Barre verticaleOpérateur de correspondance clef/terme (utilisé pour renommer ou regrouper des clefs)
 > Supérieur àOpérateur de recherche partielle (inverse l'option "mot entier")
 ! Point d'exclamationActive ou désactive la sensibilité à la casse selon le contexte (v. plus bas)

1) Opérateur de correspondance clef|terme. — Par convention, j’appelle clef l’expression recherchée dans le document et terme l’expression effectivement affichée dans la table d’index pour une clef donnée. En général, la clef et le terme sont identiques. On cherche le mot bidule et on indexe le mot bidule. Optionnel, l’opérateur de correspondance (|) permet de rechercher le mot bidule et de lui substituer finalement un autre terme dans la table d’index, par exemple machin. Pour ce faire, vous saisirez l’entrée sous la forme clef|terme :

bidule|machin

Ce système offre une solution simple lorsque vous avez besoin de renommer des clefs fragmentaires (cf. opérateur de recherche partielle) ou encore de regrouper des clefs sous le même terme :

machin
bidule|machin
truc|machin

Les trois entrées ci-dessus spécifient trois clefs de recherche (machin, bidule et truc) mais le processus d’indexation rassemblera tous les résultats sous un seul terme (machin).

(4) L’opérateur de correspondance clef|terme est une innovation de la version 2 d’IndexBrutal. Cette fonctionnalité m’a été suggérée par un utilisateur, Teodor Borsa, qui avait customisé le script de façon à atteindre l’objectif. Je tiens ici à le remercier, car c’est à partir de son idée que j’ai généralisé la logique de cet opérateur et procédé à une refonte complète, débouchant sur la version 2.

2) Opérateur de recherche partielle. — Par défaut, les clefs sans espace ni ponctuation sont recherchées comme des mots entiers (politique d’origine du script). Mais il est quelquefois plus pertinent de cibler le radical d’un mot (ou un fragment suffisamment univoque) afin d’intégrer des flexions potentielles : constitution, constitutionnel, anticonstitutionnel, etc. Au lieu de taper toutes les clefs possibles, vous pouvez alors préfixer le mot de l’opérateur de recherche partielle (>) :

>constitution

La clef sera alors recherchée en tant que fragment. Et rien ne vous empêche, lorsque le besoin s’en fait sentir, de lui associer un terme distinct grâce à l’opérateur de correspondance :

>thérap|THÉRAPIE

L’entrée ci-dessus va capturer toutes les formes contenant la clef thérap (thérapies, thérapeutique, psychothérapie, kinésithérapeute, etc.) et le script les indexera toutes sous le terme générique THÉRAPIE.

3) Sensibilité à la casse.IndexBrutal essaie de gamberger : lorsqu’une clef est en minuscules, il considère que la recherche doit être insensible à la casse (la clef bidule sera capturée sous toutes ses formes : bidule, Bidule, BIDULE...). En revanche, si la clef comporte au moins une majuscule, il considère que c’est un geste délibéré de votre part et il recherche alors la forme exacte. Cela permet d’entrer naturellement des noms propres (Marguerite, Paris...) sans risquer de repêcher leurs homographes substantifs (une marguerite, des paris stupides...). Cette gestion par défaut peut néanmoins poser problème, auquel cas le commutateur ! inversera la logique de sensibilisation à la casse :

!Urssaf
!marguerite

La première ligne indexe malgré la majuscule toutes les formes de la clef (Urssaf, URSSAF...), alors que la seconde indexe, malgré l’absence de majuscule, la forme unique marguerite (excluant par conséquent Marguerite). Notez que vous pouvez combiner les opérateurs > et ! sous réserve de les saisir dans cet ordre.

Le tableau ci-dessous récapitule les comportements relatifs à la casse et à l’option « mot entier » :

EntréeStratégieExemples
amourRecherche les occurrences complètes de 'amour' indépendamment de la casseamour, Amour, AMOUR
AmourRecherche les occurrences complètes et exactes de 'Amour'Amour
AMOURRecherche les occurrences complètes et exactes de 'AMOUR'AMOUR
>amourRecherche les expressions contenant 'amour' indépendamment de la casseAMOUR, Amoureux, désamour
>AmourRecherche les expressions contenant exactement 'Amour'Amour, Amours, DésAmour
>AMOURRecherche les expressions contenant exactement 'AMOUR'AMOUR, AMOUREUX, GLAMOUR
!amourRecherche les occurrences complètes et exactes de 'amour'amour
!AmourRecherche les occurrences complètes de 'Amour' indépendamment de la casse (le terme indexé sera noté 'Amour')amour, Amour, AMOUR
!AMOURRecherche les occurrences complètes de 'AMOUR' indépendamment de la casse (le terme indexé sera noté 'AMOUR')amour, Amour, AMOUR
>!amourRecherche les expressions contenant exactement 'amour'amour, mamours, Samouraï
>!AmourRecherche les expressions contenant 'Amour' indépendamment de la casse (le terme indexé sera noté 'Amour')amour, Amourettes, DESAMOUR
>!AMOURRecherche les expressions contenant 'AMOUR' indépendamment de la casse (le terme indexé sera noté 'AMOUR')amour, Amourettes, DESAMOUR

Voici en guise d’exemple un fichier d’entrées utilisant les différents opérateurs :

cricket
>!SCUF
>football
Écosse
>écossais|Écosse
Irlande
>irlandais|Irlande
>zéland|Nouvelle-Zélande
Cambridge
Angleterre
France
>français|France
>!Suisse

Petit décryptage :

      — l’entrée >!SCUF permet de capturer les formes concurrentes SCUF et Scuf (ainsi que le gentilé éventuel scufiste) ;

      — l’entrée >football accepte toutes les variantes de casse ainsi que les excroissances (footballeur, footballistique...) ;

      — Écosse et >écossais|Écosse regroupent sous le terme Écosse à la fois son orthographe exacte (avec majuscule initiale) et les extensions possibles de l’adjectif écossais avec ou sans majuscule ;

      — même raisonnement pour Irlande et >irlandais|Irlande ;

      — l’entrée >zéland|Nouvelle-Zélande condense en une seule règle tout ce qui peut graviter autour du terme Nouvelle-Zélande (dont néo-zélandais) — remarquez que si le fragment zéland est opérationnel, il n’en serait pas allé de même de écoss et irland à cause de mots parasites tels que écosser (verbe) ou guirlande (substantif) ;

      — l’entrée >!Suisse permet, comme >!SCUF, d’attraper les variantes de casse, le pluriel de l’adjectif, le féminin suissesse, le seul intrus étant petit-suisse, mais nous décidons de négliger le risque que les rugbymen en consomment...

Interface dialoguée

Les versions premières d’IndexBrutal étaient dépourvues d’interface personnalisée. La procédure consistait simplement à sélectionner le fichier indexeur (dialogue d’ouverture de la classe File, en Javascript) puis à sauvegarder la table générée (via le dialogue d’enregistrement de la même classe File). Dans l’intervalle, le script travaillait en mode silencieux et n’autorisait donc aucun contrôle du processus ou de la mise en forme.

IndexBrutal vous propose désormais un scénario plus élaboré :

1) Sélection du fichier indexeur. — Au lancement du programme, un fichier words.txt est recherché dans le dossier où réside le script lui-même. Si ce fichier canonique est trouvé, IndexBrutal vous demande de confirmer son utilisation :

Figure 2 - Confirmation d’usage du fichier indexeur par défaut

Si vous infirmez, ou si le fichier par défaut n’a pas été trouvé, le dialogue système d’ouverture de fichier entre en action. Vous localiserez et choisirez vous-même l’indexeur à utiliser. (N’oubliez pas qu’il doit s’agir d’un fichier texte brut listant les entrées conformément à la syntaxe exposée plus haut.)

2) Options de travail. — Une fois l’indexeur choisi, le script affiche le dialogue-utilisateur. Le panneau supérieur est en général purement informatif (rappel de l’indexeur, nombre d’entrées) :

Figure 3 - Options de travail

Cette zone vous permet également d’identifier la cible, c’est-à-dire le(s) document(s) InDesign devant passer à la moulinette. Si un seul document est ouvert, il n’y a pas d’ambiguïté. Lorsque plusieurs documents sont ouverts, la rubrique « Cible » vous propose une paire de boutons radio représentant l’alternative « Document actif » / « Tous les documents ». Quand vous indexez un livre (au sens InDesign), c’est cette seconde option que vous choisissez. Dans ce cas de figure, n’oubliez pas deux éléments importants :
      — IndexBrutal ne considère que les documents effectivement ouverts au moment de l’appel (il n’ouvrira pas lui-même les documents d’un livre qui manqueraient à l’appel) ;
      — l’indexation prend pour repère le numéro des pages exclusivement (tel qu’il serait affiché par la marque « Numéro de page active »), ainsi est-il nécessaire, pour que l’indexation ait un sens, que les pages des différents documents traités s’organisent de façon suivie et non redondante : si par exemple deux documents possèdent une page portant le numéro 3, alors le folio « 3 », en table d’index, désignera indistinctement la page 3 du premier ou du deuxième document !

3) Sortie. — Le panneau « Sortie » présente simplement l’alternative « Fichier texte » / « Presse-papier ».

La sortie en fichier texte correspond à la version inaugurale d’IndexBrutal : à la fin du traitement, vous indiquerez au programme un fichier de destination (texte brut) qui recevra la table d’index et sera ouvert automatiquement dans l’application système appropriée (en général : TextEdit sous Mac OS, NotePad sous Windows).

La sortie presse-papier, option ajoutée dans la version 2, permet de récupérer directement la table d’index en vue d’un coller vers l’application-cliente de votre choix, par exemple InDesign ! Cette fonctionnalité a été implémentée via InDesign lui-même car je n’ai pas trouvé de moyen simple d’attaquer directement le presse-papier. Ainsi, le contenu de la table est inséré dans un bloc temporaire sous InDi puis transporté dans le presse-papier grâce à la méthode Application::copy(). Ceci a une conséquence importante pour les utilisateurs : la table générée hérite de certaines mises en forme automatiques propres à InDesign, tout spécialement la conversion des guillemets Ascii en guillemets typographiques si cette préférence est activée au niveau de l’application.

4) Options de mise en forme. — Le panneau inférieur du dialogue comporte deux nouvelles rubriques qui m’ont été demandées à cor et à cri par les utilisateurs :

Figure 4 - Options de mise en forme

La première (« Signaler les entrées non trouvées par... ») permet de contrôler l’indexation des clefs introuvables (le mot dentifrice dans l’exemple de la figure 1). Si vous laissez vide le champ de saisie, les introuvables seront tout bonnement supprimés de la table. Mais si vous saisissez un ou plusieurs caractères, ces derniers seront employés comme marqueurs en regard de chaque terme non trouvé. L’intérêt de cette fonctionnalité est de révéler les échecs d’indexation, donc d’éventuelles erreurs de saisie dans le fichier d’entrées.

La seconde rubrique (« Regrouper les séquences de page avec... ») contrôle le processus d’assemblage des folios contigus. Initialement, IndexBrutal formatait systématiquement les séquences de pages comme des intervalles : 12, 13, 14,...22, 23, 24 se condensait en 12-24, usage bien pratique lorsqu’un terme revient sans cesse d’une page à l’autre. Vous pouvez désactiver cet automatisme en laissant vierge le champ de saisie, auquel cas tous les folios seront développés à la chaîne, sans regroupement par contiguïté. Sinon, les caractères saisis agiront comme séparateur pour les intervalles obtenus (on utilise habituellement le trait d’union, mais rien ne vous empêche d’essayer un tiret demi-cadratin ou une barre oblique...).

5) Génération de l’index. — Une fois le dialogue validé, IndexBrutal se lance dans une scrutation profonde du ou des document(s) cible(s). Le temps de traitement reste normalement assez court sous CS2 et CS3, mais tout dépend bien sûr de la quantité de clefs à rechercher, du nombre de pages et du nombre de documents. En sortie, les termes sont triés selon l’ordre alphabétique correct, ce qui n’était pas le cas dans les versions précédentes du script, Javascript se montrant particulièrement fallacieux en matière de tri.

IndexBrutal affiche le fruit(5) de son labeur (sortie fichier) ou confirme le bon déroulement des opérations (sortie presse-papier). Il vous restera bien sûr à mettre en page la table d’index à la fin du livre, mais ça c’est votre métier. Notre script s’est simplement occupé de ce qui n’était pas votre métier : rechercher et pointer des termes dans des documents InDesign ! Puisque vous venez de gagner des heures de manutention, offrez-vous une petite respiration en parcourant la section suivante (réservée aux javascripteurs).

(5) Notez toutefois que le script ne génère pas d’index si aucune des clefs n’a été trouvée, il affiche alors simplement un message de défaite.

Les dessous du script

Le script IndexBrutal a été codé à l’arrachée au tout début, puis raffiné ensuite. C’est pourquoi le code ne ressemble plus du tout à ce qu’il était dans la version d’origine. Des classes ont été introduites afin de rationaliser le processus et d’y injecter les nouvelles options :

Architecture du script

La figure 5 (et le PDF sous-jacent) n’expose que l’interface des classes. L’objet central du programme est bien sûr la classe TIndexList. Elle enveloppe les composants de l’indexation et possède tout l’équipement pour :
      — interpréter la syntaxe des entrées issues de l’indexeur : TIndexList::parseEntryLine(string) ;
      — informer son client (Application) de son état interne : TIndexList::isEmpty() et TIndexList::getSize() ;
      — conduire au sein de l’objet cible (Document ou Application(6)) la recherche exhaustive des clefs : TIndexList::scanPages(obj& target) ;
      — renvoyer au client, en fin d’opération, la table d’index complète, triée, sous forme de tableau de chaînes : TIndexList::getIndexLines(...).

(6) La cible (target) de la recherche est de type Document (app.activeDocument) si l’utilisateur a choisi d’indexer le document actif, elle est de type Application (app) en cas de recherche sur tous les documents ouverts. On utilise dans les deux cas la bonne vieille méthode .search(string word, bool wholeWord, bool caseSensitive) qui est disponible sous l’objet Application et sous l’objet Document dans l’architecture Javascript d’InDesign.

Dans l’intimité de TIndexList, deux propriétés considérées comme « privées », _Keys et _Terms représentent séparément le tableau des clefs et le tableau des entrées. Cette structure dédoublée est bien adaptée à nos contraintes algorithmiques. Chaque clef est implémentée comme un tableau à quatre cellules : le nom de la clef, l’option « mot entier » (booléen), l’option de sensibilité à la casse (booléen) et l’indice de l’entrée associée dans le tableau _Terms (sachant que plusieurs clefs peuvent conduire au même terme eu égard à l’opérateur de correspondance). Chaque terme (élément du tableau _Terms) est structuré sur deux cellules : le nom du terme et le tableau (Array) des folios obtenus pour toute clef associée à ce terme. Il serait sans profit, en effet, de stocker les numéros de pages au niveau des clefs puisque l’assemblage s’opère au niveau des termes indexés. Le tableau _Terms est donc profilé pour le tri et le traitement final, lequel ne fera plus apparaître les clefs.

Un schéma vaut mieux qu’un long discours :

Figure 6 - Structure des clefs et des termes au sein de TIndexList

Le tableau de clefs est profilé pour les recherches, il débouche sur un code très compact là où le programme est le plus consommateur de ressources, c’est-à-dire dans scanPages(). Lorsque le process atteint ce cap décisif, la méthode parseEntryLine (appelée en amont) a déjà fait son œuvre, c’est-à-dire que les entrées ont été disséquées (dissociation clef/terme) et que les opérateurs ont été interprétés de façon à produire les arguments finaux de la recherche sans nouveau traitement conditionnel :

TIndexList.prototype.scanPages = function(_target)
//----------------------------------------------------------
// Collecte les pages où les clefs apparaissent
//  Ret. FALSE si toutes les recherches ont echoué
// _target : cible de la recherche
{
var r = false;
var results = null;
var k = null;
var tf = null;
var pg = null;
 
var i,j;
 
for ( i=this._Keys.length-1; i >= 0; i-- )
      {
      k = this._Keys[i];
 
      results = _target.search(k[0],k[1],k[2]);
       
      if (results == null) continue;
      if (results.length == 0) continue;
       
      for ( j=results.length-1; j >= 0; j-- )
            {
            tf = results[j].getTextFrame();
            if ( tf == null ) continue;
 
            pg = tf.getPage();
            if ( pg == null ) continue;
             
            r = true;
            this._addPageName(k[3],pg.name);
            }
      }
return(r);
}

Qu’y a-t-il de captivant dans ce bout de script ? Pas grand-chose, sinon la simplicité de l’instruction de recherche pour la clef courante : results = _target.search(k[0],k[1],k[2]); (k[0] renferme la clef, k[1] l’option wholeWord, k[2] l’option caseSensitive). Tout se passe ici !

La boucle secondaire décrit alors les résultats de la recherche (results). Selon la documentation Javascript d’InDesign, il s’agit d’un tableau d’objets de type Text, ce qui est vrai sous CS et pas-tout-à-fait-vrai sous CS2 et CS3. En fait, les objets rapatriés sont généralement d’un type dérivé de Text (par exemple Word), mais cela revient toujours à une « portion » de texte. La propriété commune parentTextFrame[s] permet de récupérer le bloc-texte parent (objet TextFrame). Malheureusement, parentTextFrame n’a pas d’s dans la version CS (renvoi direct d’un bloc parent) tandis qu’il devient parentTextFrames sous CS2/CS3, visant un tableau de blocs-textes. Entre les deux versions, Adobe s’est aperçu qu’un Text pouvait avoir plusieurs blocs conteneurs (il suffit en effet de considérer une sélection chaînée sur plusieurs blocs). On doit alors extraire parentTextFrames[0] pour identifier le premier bloc où apparaît le texte... Toutes ces péripéties invitent à cuisiner une méthode ad hocgetTextFrame() — qui homogénéise le code dans une logique multi-versions. Une fois le bloc-texte connu (variable tf), il faut identifier sur quelle page il se trouve, ce qui est la mission de la méthode Object::getPage(), de peu d’intérêt(7), que je vous laisse découvrir dans le source du script.

(7) Encore qu’il me faille vous signaler un piège vicelard : un bloc-texte peut résider à l’extérieur d’une page, c’est-à-dire sur une portée (Spread). Auquel cas, impossible de lui attribuer un numéro de page. La méthode getPage() renvoie alors null et le résultat est ignoré. Concrètement, cela signifie qu’IndexBrutal ne considère que le texte présent sur les pages du document. On pourrait s’amuser à prévoir un traitement et une signalétique particulière pour les résultats hors-page, mais à quoi cela servirait-il donc ?

L’autre instruction intéressante de la méthode scanPages() est this._addPageName(k[3],pg.name);k[3] désigne l’indice du terme (dans _Terms) et pg.name le folio que l’on vient de trouver. La méthode interne _addPageName(indice,folio) ajoute le folio dans le tableau de pages administré par le terme dont l’indice est fourni (i.e. _Terms[indice][1]). Cette méthode est coûteuse, car _addPageName() vérifie que le folio n’est pas déjà enregistré pour le terme considéré (sur une page donnée, on peut trouver plusieurs occurrences de la même clef ou plusieurs clefs associées au même terme). Cette vérification oblige à traverser un tableau potentiellement nombreux et je finis par me demander s’il ne serait pas fructueux de reporter en aval, après les boucles harassantes de scanPages(), le nettoyage des doublons...

Pour l’heure, les folios posent d’autres problèmes qui méritent l’attention des javascripteurs. Dans InDesign, la notion de numéro de page est assez ardue. Elle dépend de conditions éclatées entre les préférences générales (mode absolu ou relatif), les options de numérotation au niveau des sections (« Numérotation automatique », « Inclure le préfixe lors de la numérotation »), sans parler des paramètres de pagination du livre si vous travaillez en multi-documents. L’objet Page connaît une propriété purement numérique (Page::documentOffset) qui indique le rang absolu d’une page dans le document hôte, mais elle ne présente pas d’utilité lorsqu’on cherche à connaître le folio effectif (la 1re page physique d’un document peut porter le numéro « 1 » comme n’importe quel autre, y compris le folio « xi » en numérotation romaine ou « B23 » en numérotation préfixée). Bref, seule la propriété Page::name incarne la matérialité des folios, mais c’est une chaîne de caractères. Il faut donc l’analyser comme telle et ne pas considérer comme acquis qu’elle représente un nombre. Vous trouverez dans la méthode TIndexList::_createIndex(...) la solution que j’ai adoptée pour trier les pages et effectuer le regroupement éventuel des folios mitoyens.

Je n’aborderai pas ici les traitements des fichiers (objet File) ni l’interface générale d’IndexBrutal. Sur ces deux chapitres, le code parle de lui-même et ne dit rien d’impérissable.

Le tri alphabétique en Javascript

J’aimerais en revanche vous entretenir du tri alphabétique que subit la table finale, car c’est un problème classique qui manque de solution classique. Mettons que vous disposez d’un tableau de chaînes. Si vous appelez la méthode Array::sort() sans argument, les chaînes sont comparées selon une fonction par défaut qui considère numériquement le code des caractères (au sens Unicode). Il s’ensuit que la lettre Z majuscule (U+005A) arrive bien avant la lettre a minuscule (U+0061), et que les lettres accentuées, ligaturées ou armées d’un diacritique (é, À, œ, ç...) se trouvent totalement marginalisées. Pour Javascript, la table ["Gâteau", "flan", "éclair"] est parfaitement triée ! Cette conception fâcheuse de l’ordonnancement alphabétique n’est évidemment pas supportable quand on entreprend de restituer à l’utilisateur une table d’index en bon ordre.

Heureusement, il est possible de fournir à Array::sort() un argument, en l’occurrence un objet Function (une fonction, si vous préférez) responsable de la comparaison de deux éléments du tableau. La fonction en question doit posséder deux arguments — disons argA et argB — et renvoyer -1, 0 (zéro) ou 1 selon que argA est considéré comme strictement inférieur, égal, ou strictement supérieur à argB. Cela revient donc à instituer votre propre relation d’ordre au sein du tableau à trier. Dans le cas qui nous occupe, nous allons créer un ordre remettant les caractères à leur place, donc gommant les disparités parasites. Il faut par exemple que les lettres à ou A soient équivalentes à la lettre a, mais aussi que le digramme ligaturé œ soit assimilé au digramme oe.

Pour mener à bien cette noble entreprise, j’ai d’abord créé une méthode String::toLowerAscii() chargée d’immerger n’importe quelle chaîne de caractères dans l’ensemble formé par l’alphabet Ascii minuscule et les chiffres. On aura par exemple : "Écœurer".toLowerAscii() == "ecoeurer". Ensuite, on définit sans difficulté notre fonction de comparaison smartAlphaComp(a, b), laquelle compare en réalité a.toLowerAscii() et b.toLowerAscii(). Vous pouvez alors trier correctement un tableau t en appelant t.sort(smartAlphaComp).

Voici le code de ce dispositif :

// On crée dans String un membre statique (DIAS) listant
// en une chaîne les minuscules accentuées et assimilées
// NB. j'utilise les rangs Unicode afin de maintenir le
// code dans l'espace Ascii (cf. mon dossier InDiCode)
 
String.DIAS = "\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00F2\u00F3\u00F4"
+ "\u00F5\u00F6\u00F8\u00E8\u00E9\u00EA\u00EB\u00E7\u00EC\u00ED\u00EE"
+ "\u00EF\u00F9\u00FA\u00FB\u00FC\u00FF\u00F1\u0153\u00E6";
 
// Tableau statique pour convertir les
// caractères de String.DIAS en minuscules Ascii
String.DIAS_TO_ASCII = ["a","a","a","a","a","a","o","o","o","o","o","o",
      "e","e","e","e","c","i","i","i","i","u","u","u","u","y","n","oe","ae"];
 
// Chaîne statique indiquant les caractères à conserver
String.KEEP_ASCII = "0123456789abcdefghijklmnopqrstuvwxyz";
 
String.prototype.toLowerAscii = function()
//----------------------------------------------------------
// Ret. le correspondant ascii bdc
// - les lettres accentuées sont desaccentuées
// - les digrammes ligaturés sont dissociés : oe, ae
// - les autres caractères non alphabétiques sont supprimés
{
var str = this.toLocaleLowerCase(); // on travaille avec des minuscules
var sz = str.length;
var r = "";
for(var i=0,c=null,p=null; i < sz; i++)
      {
      c = str.charAt(i);
      p = String.DIAS.indexOf(c);
      r += ( p >= 0 )? String.DIAS_TO_ASCII[p] :
            ( ( String.KEEP_ASCII.indexOf(c) >= 0 )? c : "" );
      }
return(r);
}
 
function smartAlphaComp(_a, _b)
//----------------------------------------------------------
// Utilitaire de comparaison pour le tri alphabétique
{
var a = _a.toLowerAscii();
var b = _b.toLowerAscii();
return( ( a < b )? -1 : ( ( a > b )? 1 : 0 ) );
}

Historique et remerciements

Parmi les scripts InDesign publiés dans cette rubrique, IndexBrutal est celui qui a suscité le plus de remaniements sous l’effet des suggestions et des feedbacks des internautes. Merci à eux.

jan 07 |      Olivier Berquin, AppleScripteur de son état, ouvre les hostilités en janvier 2007 en me signalant un bug mineur qui sera corrigé dans la version 1.0b.

Détails...

mars 07 |      Séverine F. (universitaire à Grenoble) remarque en mars 2007 l’absence de traitement des notes en bas de page (bug intrinsèque à InDesign CS2, donc irrévocable), peu avant qu’Alain Gilbert (M&G Éditions, Bourg-en-Bresse) me saisisse d’un bug étrange, lié au système de fichiers, sous Mac OS et InDesign CS. C’est jxswm (peuso imprononçable d’un participant du forum Adobe InDesign Scripting) qui apportera un patch salutaire quoique incompréhensible (version 1.0c).

Détails...

mai 07 |      Suite à des demandes convergentes de plusieurs internautes, la version 1.2 d’IndexBrutal introduit les deux opérateurs permettant d’optionnaliser la recherche par mot entier et la sensibilité à la casse.

Détails...

août 07 |       Gaëlle Le Roch (de la société multimédia Cyim) m’invite à rechercher la compatibilité du script avec InDesign CS3 (où les fonctions de recherche ont été complètement chamboulées).

Détails...

nov. 07 |      Benjamin Allard (animateur du blog IndesignBox) suggère d’optionnaliser le regroupement des folios contigus. Ce sera le point de départ de ma réflexion sur l’interface dialoguée, jusqu’alors inexistante. De son côté, Teodor Borsa (Typink.com) m’envoie par courriel une version remixée d’IndexBrutal qui introduit, l’air de rien, l’opérateur de correspondance clef|terme...

Détails...

fév. 08 |      Deux correctifs mineurs mais décisifs (version 2.1) après la découverte d’un bug dans la fonction de tri alphabétique et d’une mauvaise rétrocompatibilité d’InDesign CS3 pour la fonction de recherche.

Détails...

Installation et customisation

Les instructions d’installation d’IndexBrutal sont données en entête dans le script lui-même, IndexBrutal.js, inclus dans le fichier zip téléchargeable. Les anglophones noteront que le script commute automatiquement les messages d’interface en anglais si la langue locale du logiciel n’est pas le français.

Lors de l’utilisation du script, gardez à l’esprit les aspects suivants :

      — vous devez fournir un fichier d’entrées séparées par des retours-chariot ;

      — vous pouvez intégrer dans la liste : des locutions complexes, des chiffres, des caractères spéciaux et plus généralement toute suite de caractères qu’InDesign saurait interpréter comme une clef de recherche ;

      — si la numérotation des pages est bizarroïde, l’ordonnancement résultant le sera aussi ;

      — lorsque tous les documents ouverts sont scrutés par le script (v. options de travail dans l’interface dialoguée), assurez-vous qu’ils présentent une pagination suivie et différenciée ;

      — l’index résultant trie les termes indexés, chacun étant suivi d’une tabulation et des repères de pages avec virgules intercalaires ;

      — sauf heureuse exception, « IndexBrutal » ignore le texte des notes de bas de page. Cela résulte d’un bug inhérent au moteur de script d’InDesign CS2 quant au traitement desdites notes...

Customisation. — Certains paramètres, peu susceptibles d’évoluer, ont été figés dans le script au niveau de la rubrique intitulée SCRIPT GLOBAL SETTINGS & PREFERENCES (au début du code). Les bidouilleurs et les utilisateurs avertis — notamment s’ils souhaitent invoquer IndexBrutal depuis un processus englobant — peuvent redéfinir aisément quelques-unes des politiques par défaut :

      — pour changer le nom du fichier indexeur par défaut, modifiez la variable ENTRIES_FILE (initialisée à "words.txt") ;

      — pour empêcher le tri alphabétique en sortie, passez à FALSE la variable SORT_TERMS (initialisée à TRUE) ;

      — pour remplacer la tabulation séparant terme indexé et repères de page par une autre chaîne, réaffectez la variable TERM_SEPARATOR (initialisée à "\t") ;

      — pour modifier le séparateur de pages simples, réaffectez la variable PAGE_SEPARATOR (initialisée à ", ") ;

      — pour modifier un ou plusieurs des caractères-opérateurs > ! | , réaffectez au choix les variables NONWHOLE_WORD_OP (recherche partielle), INV_CASE_OP (commutateur de sensibilité à la casse), TERM_SEP_OP (correspondance clef/terme) — mais en prenant soin d’attribuer à chaque opérateur une chaîne mono-caractère non utilisée dans les clefs ;

      — pour modifier les options de traitement des clefs introuvables et des folios contigus (telles qu’elles sont proposées par défaut dans la boîte de dialogue), adaptez à vos besoins les variables NOTFOUNDS_MARK et INTERVAL_MARK (initialisées respectivement à "" et "-").

Ce programme est bien sûr en libre usage, mais n’oubliez pas d’indiquer la source et l’auteur si vous le mentionnez ou le réexploitez. Merci de signaler tout bug éventuel à l’adresse du rédacteur en chef.

BlogNot! est une émission produite par Marc Autret depuis 2004, à consommer de préférence en cuves acclimatées aux spécifications XHTML et CSS.
Pour harceler la rédaction : marcautret(at)free(point)fr