Une fonction Javascript pour éliminer accents et diacritiques

Et pour quoi faire d’abord?

Le français est une langue qui s’accentue, se trématise, se cédille, et qui pousse le vice jusqu’à l’OdansleEïsation. Et notre alphabet est loin d’être le plus riche : crochets, barres obliques, points souscrits, ogoneks, doubles accents, lettres inversées et autres zigouigouis font le charme (et l’utilité) des alphabets exotiques. Prenez l’alphabet vietnamien, ou Quốc ngữ : ne serait-ce pas un crime d’en faire un vulgaire Quoc ngu ?

C’est pourtant ce qu’on est parfois obligé de faire pour utiliser certains outils informatiques qui digèrent mal les accents et autres diacritiques, et ne supportent que les fades caractères ASCII (127 caractères, sans aucun accent).

C’est justement le cas d’IdRef, du moins dans les index de type mot. Or je suis toujours en train de bricoler le script décrit dans le dernier billet, et je ne désespère pas d’obtenir quelque chose de présentable, mais pour ce faire, j’avais besoin d’une fonction dépouillant une chaîne de ses accents et diacritiques afin de construire une requête compréhensible par l’API d’IdRef.

Après quelques recherches, il m’a semblé qu’une telle fonction existait dans d’autres langages de programmation, mais pas en Javascript. Peut-être me suis-je trompé, mais en tout cas cela m’aura permis de découvrir plein de langues rigolotes rares. J’avais d’abord recopié une fonction trouvé sur un forum, mais elle ne me satisfaisait pas, car elle ne traitait que les 256 caractères les plus usuels (correspondant à l’ASCII étendu). J’ai donc essayé de construire une fonction qui puisse également traiter des caractères plus rares, et qui soit facilement paramétrable pour s’adapter à divers besoins.

Pour ne rien vous cacher, ma fonction s’avère finalement inadaptée à IdRef, du moins dans son état actuel (je reviendrai sur le sujet), mais elle peut être reprise dans d’autres contextes et modifiée pour répondre à des besoins différents.

Le code de la fonction est en ligne ici. Je la recopie sans les commentaires initiaux:

Sous le code, l’unicode…

Comment ça marche ?

Concrètement, nous avons besoin d’une fonction qui transforme une chaîne telle que « œil école Điện słowiańskie » en « oeil ecole Dien slowianskie » (c’est du franco-polono-vietnamien), et plus généralement les 526 caractères « ªÁáÀàĂăẮắẰằẴẵẲẳÂâẤấẦầẪẫẨẩǍǎÅåǺǻÄäÃãĄąĀāẢảȀȁȂȃẠạẶặẬậḀḁÆæǼǽẚȺḂḃḄḅḆḇĆćĈĉČčĊċÇçḈḉȻȼĎďḊḋḐḑḌḍḒḓḎḏĐđÐðDZDzdzDŽDždžÉéÈèĔĕÊêẾếỀềỄễỂểĚěËëẼẽĖėȨȩḜḝĘęĒēḖḗḔḕẺẻȄȅȆȇẸẹỆệḘḙḚḛḞḟƒǴǵĞğĜĝĠġĢģḠḡĤĥḦḧḢḣḨḩḤḥḪḫẖĦħÍíÌìĬĭÎîǏǐÏïḮḯĨĩİĮįĪīỈỉȈȉȊȋỊịḬḭIJijıĴĵḰḱĶķḲḳḴḵĹ弾ĻļḶḷḸḹḼḽḺḻŁłĿŀLJLjljȽḾḿṀṁṂṃŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉNJNjnjºÓóÒòŎŏÔôỐốỒồỖỗỔổǑǒÖöȪȫŐőÕõṌṍṎṏȬȭȮȯȰȱØøǾǿŌōṒṓṐṑỎỏȌȍȎȏƠơỚớỜờỠỡỞởỢợỌọỘộŒœṔṕṖṗŔŕŘřṘṙŖŗȐȑȒȓṚṛṜṝṞṟŚśṤṥŜŝŠšṦṧṠṡŞşṢṣṨṩȘșſẛßẞȿŤťẗṪṫŢţṬṭȚțṰṱṮṯŦŧȾÚúÙùŬŭÛûǓǔŮůÜüǗǘǛǜǙǚǕǖŰűŨũṸṹŲųŪūṺṻỦủȔȕȖȗƯưỨứỪừỮữỬửỰựỤụṲṳṶṷṴṵṼṽṾṿẂẃẀẁŴŵẘẄẅẆẇẈẉẌẍẊẋÝýỲỳŶŷẙÿŸỸỹẎẏȲȳỶỷỴỵŹźẐẑŽžŻżẒẓẔẕɀÞþ »

en leur équivalent simple : « aAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAeaeAeaeaABbBbBbCcCcCcCcCcCcCcDdDdDdDdDdDdDdDdDZDZdzDZDZdzEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeEeFffGgGgGgGgGgGgHhHhHhHhHhHhhHhIiIiIiIiIiIiIiIiIIiIiIiIiIiIiIiIJijiJjKkKkKkKkLlLlLlLlLlLlLlLlLlLJLjljLMmMmMmNnNnNnNnNnNnNnNnNnNJNjnjoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOeoePpPpRrRrRrRrRrRrRrRrRrSsSsSsSsSsSsSsSsSsSsssssSSsTttTtTtTtTtTtTtTtTUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuUuVvVvWwWwWwwWwWwWwXxXxYyYyYyyyYYyYyYyYyYyZzZzZzZzZzZzzThth »

Il faut savoir qu’en Javascript et dans tous les outils modernes, les chaînes de caractère sont encodées en Unicode, standard qui attribue à chaque caractère un nom, et surtout un identifiant unique codé au maximum sur 21 bits. Pour les caractères es plus usuels (et donc pour les langues qui nous intéressent), le codage se fait  sur 16 bits, c’est à dire deux octets, soit 65536 caractères possibles.

Pour assurer une compatibilité avec les standards « préhistoriques », les 256 premiers caractères correspondent aux caractères ASCII (pour la tranche 0-127) et aux caractères ISO 8859-1 (pour la tranche 128-255). Il faut juste ajouter un octet égal à 00 avant celui qui contient la valeur du caractère en ASCII ou en 8859-1. Le codepoint U+0065 correspond ainsi au caractère « e » (65 en ASCII, valeur hexadécimale), et le codepoint U+00E9 correspond au caractère « é » (E9 en ISO 8859-1, valeur hexadécimale).

Au delà, les caractères Unicode n’ont pas d’équivalent en ASCII ou en ISO 8859-1. C’est le cas par exemple du caractère « ł » utilisé en polonais, codé 0141 et dénommé « LATIN SMALL LETTER L WITH STROKE ».

Certains caractères sont des modificateurs : il y a souvent deux manières de coder une lettre modifiés par un diacritique : en utilisant un caractère unique comme U+00E9 pour « é » ou U+00F1 pour « ñ », ou en saisissant à la suite un caractère de base (par exemple U+006E  pour « n ») et un ou plusieurs modificateurs (par exemple U+0303 = « ◌̃ »).

Enfin, certains caractères unicode sont des ligatures de plusieurs lettres simples, comme « œ », qui représente « oe » en français.

Nous avons donc besoin d’une fonction qui :

  1. élimine les caractères modificateurs. Ex : n◌̃ –> n
  2. remplace tout caractère modifié par un diacritique par son équivalent simple. Ex : é –> e
  3. résolve les ligatures. Ex : œ –> oe
  4. ne s’intéresse qu’aux caractères latins, ignore les alphabets non latins (grec, cyrillique, arabe, hébreu, coréen…), et les écritures non alphabétiques (chinois…).
  5. et plus précisément soit valable pour le français, les langues européennes modernes, quelques langues exotiques (notamment le Vietnamien),  mais sans nécessairement prendre en compte les langues mortes ou rarissimes (alphabets expérimentaux africains, etc.), ni les caractères utilisés dans l’alphabet phonétique international, ce qui ne servirait qu’à ralentir le script

Fausse piste

L’élimination des modificateurs ne pose pas de problèmes : ils sont regroupés dans des « blocs » Unicode qu’il suffit de cibler, essentiellement le bloc « Combining Diacritical Marks » (U+0300–U+036F), et le bloc des suppléments (U+1DC0-U+1DFF). On peut facilement éliminer tous ces caractères en utilisant une fonction « replace ».

Le reste est un peu plus délicat : on peut récupérer sur le site du consortium Unicode un fichier listant tous les caractères, et qui indique les règles de « décomposition » d’un caractère complexe. La ligne

00EB;LATIN SMALL LETTER E WITH DIAERESIS;Ll;0;L;0065 0308;;;;N;LATIN SMALL LETTER E DIAERESIS;;00CB;;00CB

signifie que le caractère U+00EB dénommé « LATIN SMALL LETTER E WITH DIAERESIS » (ë) peut se décomposer en U+0065 (e) + U+0308 (¨), et que son équivalent en majuscule est U+00CB.

Je pensais utiliser ce fichier, mais il n’est malheureusement pas assez précis. Certains caractères que l’on peut légitimement considérer comme composés ne sont pas signalés comme tels. C’est le cas notamment du l barré polonais :

0141;LATIN CAPITAL LETTER L WITH STROKE;Lu;0;L;;;;;N;LATIN CAPITAL LETTER L SLASH;;;0142;

Tout ce que donne le fichier c’est un équivalent en majuscule…

Tripatouillage tabulé

J’ai donc privilégié une autre piste : recopier dans un fichier tabulé tous les caractères qui m’intéressaient, en utilisant les tableaux disponibles par wikipédia (mais les mêmes données sont aussi sur le site d’unicode) dans les pages décrivant les principaux blocs de caractères latins, et exploiter le nom de chaque caractère pour en déduire avec quelques formules son équivalent simple. Par exemple, « LATIN CAPITAL LETTER L WITH STROKE » désigne un L majuscule modifié par une barre. Son équivalent simple est donc « L ».

D’autre part, j’ai fait une sélection dans ce fichier pour ne pas tenir compte de quelques caractères extrêmement rares ou obsolètes, et surtout de lettres « exotiques » qui bien que faisant partie en théorie de l’alphabet « latin » au sens large, n’ont pas d’équivalent strict dans la série [a-z] : « glottal stop », « whynn » et autre « kra » (utilisé en Inuit)…

Les caractères conservés ont été recopié dans une nouvelle feuille de calcul, puis à l’aide d’un tableau croisés dynamique assorti de quelques formules de concaténation et de remplacement, j’ai pu obtenir une série d’expressions de ce genre :

[‘a’,/[\u00AA\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u0101\u0103\u0105\u01CE\u01FB\u0201\u0203\u1E01\u1E9A\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7]/g], // a

Ensuite, il me suffit de remplir un array Javascript avec toutes ces expressions :

[
[‘a’,/[\u00AA\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u0101\u0103\u0105\u01CE\u01FB\u0201\u0203\u1E01\u1E9A\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7]/g], // a

[‘Z’,/[\u0179\u017B\u017D\u1E90\u1E92\u1E94]/g], // Z
]

et de passer les éléments tableau en argument à une fonction « replace ».

J’ai travaillé dans Excel, mais j’ai recopié le fichier tabulé dans Google Docs (il a fallu refaire les tableaux dynamique, les filtres, etc.). Il est consultable ici.

Un traitement en deux étapes

Je me suis aperçu que ma fonction risquait de trop ralentir le script, même si ça se compte en millisecondes.

Donc, dans la mesure où dans 9 cas sur 10, les mots récupérés dans un catalogue de bibliothèque français ou dans IdRef ne comprennent que des caractères « ordinaires » (entendez par là, codés sur un octet), j’ai repris mon fichier tabulé pour créer deux autres onglets : un qui traite les caractères codés sur un octet (+3 caractères sur deux octets que l’on peut trouver en français :Ÿ Œ œ), et un second qui traite tous les autres caractères.

On peut donc faire un premier tableau javascript traitant les lettres susceptibles d’être rencontrées en français :

[‘a’,/[\u00AA\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5]/g], // a

et un second pour les lettres plus rares :

[‘a’,/[\u0101\u0103\u0105\u01CE\u01FB\u0201\u0203\u1E01\u1E9A\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7]/g], // a

A l’issu de la première phase du traitement, si on ne détecte aucun caractère codé sur deux octets, on peut sortir de la fonction. Dans le cas contraire, la fonction peut approfondir l’examen de la chaîne à traiter.

Finalement…

La fonction traite :

  1. tous les caractères latins codés sur 1 octet en Unicode (blocs C0 Controls and Basic Latin, also called Basic Latin et C1 Controls and Latin-1 Supplement), dont les lettres islandaises  Thorn et Eth, remplacées par « Th » et « D » (choix qui n’a aucune valeur officielle…)
  2. une sélection de caractères codés sur 2 octets en Unicode :

En plus des caractères modifiés par des diacritiques, elle « résout » les ligatures et digrammes suivants : SS/ss ; ae/Ae ; Oe/oe ; IJ/ij ; DZ/Dz/dz ; NJ/Nj/nj ; LJ/Lj/lj

Elle ne traite pas :

  1. les caractères de l’alphabet phonétique international, du bloc Latin Extended-C 2C60–2C7F et du bloc Extended-D A720–A7FF.
  2. les lettres « exotiques » qui bien que faisant partie de l’alphabet « latin » au sens large, n’ont pas d’équivalent strict dans la série [a-z] : « glottal stop », « whynn », « kra », etc., sauf Eth et Thorn

Quelques remarques :

  • En Unicode, les umlaut allemands sont considérés comme des trémas -> ä, ü et ö deviennent a, u, o et non ae, ue, oe
  • O et o barré sont remplacés par O et o, mais on pourrait les transcrire également par Oe/oe
  • les d/D barrés sont transcrits d/D : normal en Vietnamien, mais ils sont usuellement transcrit par Dj dans les langues balkaniques
http://unicode.org/charts/PDF/UA720.pdf

3 comments to Une fonction Javascript pour éliminer accents et diacritiques

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

  

  

  

*