Nettoyer et remplacer les homographes (et homoglyphes) d’un texte en PHP

Mathieu Chartier Programmation 0 commentaire

Le traitement de chaînes de caractères est très courant en programmation, que ce soit en PHP ou dans un autre langage. Il n'est pas rare que les textes que nous souhaitons manipuler ne soient malheureusement pas aussi bien interprétés par l'informatique que par nos propres yeux. En effet, de nombreux caractères peuvent sembler équivalent à l’œil, mais sont pourtant encodés différemment par la machine, on parle alors d'homoglyphes dans ce cas (les homographes étant les mots complets qui se ressemblent mais qui s'encodent différemment).

Lorsque l'on rencontre des homoglyphes dans les textes et que l'on souhaite les manipuler, notamment en générant des remplacements de morceaux de chaînes, on se retrouve bloqué car la machine n'interprète pas ces caractères spéciaux tels que notre cerveau les décode. Souvent, il s'agit d'un simple petit problème d'encodage entre deux supports, mais cela peut malheureusement aller bien plus loin, surtout si vous traitez des corpus ou des volumes importants de texte.

Prenons quelques exemples simples :

  • Tous ces caractères représentent bien un "a", mais pourtant, la machine ne les comprend pas comme tel : '?', '?', '?', '?', '?', '⍺', '?', '?', 'а', '?', '?', '?', '?'...
  • Ces caractères correspondent au "Z" en majuscule : 'ℤ', 'Ζ', '?', '?', '?', '?', '?'...
  • Ces apostrophes sont similaires, mais informatiquement, elles diffèrent : '‘', '’' et '‛' ne sont pas "'".

Homoglyphes en PHP

Dès lors, vous comprenez l'enjeu de pouvoir facilement passer d'une liste de caractères mal encodés vers leurs équivalents mieux reconnus en informatique.

Les listes et programmes fournis par la suite sont spécifiques à PHP. J'ai en réalité effectué un travail similaire et plus avancé en Python dans le cadre de mes recherches, l'objectif ici est surtout de vous familiariser avec les homographes et homoglyphes que l'on peut rencontrer sur la Toile, et de savoir comment nettoyer facilement un texte. Les listes de caractères peuvent sûrement être complétées par d'autres signes dont j'ignore l'existence...

Voici un fichier PHP qui contient l'ensemble des codes et exemples qui suivront, cela pourra vous faciliter la vie plutôt que de copier/coller chaque portion de code.

Télécharger “homoglyphes.zip”homoglyphes.zip – Téléchargé 150 fois – 9,78 Ko

Créer des listes de correspondances entre caractères et homoglyphes

Dans un premier temps, l'idéal est de créer deux listes de caractères, afin d'avoir la liberté de passer d'un caractère reconnu à ses homoglyphes. J'ai fait le choix de créer une liste contenant les correspondances entre les caractères et les points de code Unicode (valeur numérique correspondante à un signe précis), que l'on peut facilement transformer en son caractère équivalent. La seconde liste est plus visuelle puisqu'elle établit la correspondance directe entre un caractère et ses homoglyphes.

Voici la première liste :

// Liste des valeurs hexadécimales des homoglyphes
$homoglyphs_hex = array(
    "'" => [8216, 8217, 8219],
    '"' => [8223, 8220, 8221, 171, 32, 171, 187],
    "-" => [1748, 11450, 65112, 727, 8722, 10134, 8209, 8259, 8210, 8211, 8208, 8212], 
	"-" => [1748, 11450, 65112, 727, 8722, 10134, 8209, 8259, 8210, 8211, 8208],
	"." => [1793, 1632, 8228, 1776, 42510, 42232, 1794, 68176, 65294, 119149],
	"0" => [65296, 119874, 120782, 120812, 120802, 120792, 119822, 2848, 120342, 120238, 4816, 120618, 1365, 3046, 120822],
	"1" => [120157, 120180, 119868, 406, 11599, 126464, 42226, 65165, 65356, 120417, 8739, 120496, 1030, 11410, 1472, 120813, 120554, 65297, 120336, 1503, 124, 120261, 120469, 65512, 1633, 119816, 120128, 1493, 120803, 120440, 93992, 120313, 120001, 120823, 120612, 120053, 8572, 120783, 120232, 119845, 1216, 66313, 120284, 66336, 119897, 125127, 9213, 921, 448, 120105, 119920, 120365, 1994, 65321, 8465, 8544, 65166, 126592, 8467, 66186, 120670, 8464, 120728, 130033, 120024, 1777, 119949, 120793, 120388, 1575, 120209, 5825],
	"2" => [42842, 5311, 1000, 120814, 42735, 423, 120794, 120804, 130034, 120784, 65298, 42564, 120824],
	"3" => [120805, 42858, 120825, 540, 119302, 11468, 1047, 1248, 120815, 439, 65299, 130035, 120795, 120785, 94011, 71882, 42923],
	"4" => [120796, 130036, 120786, 120816, 71855, 120806, 65300, 5070, 120826],
	"5" => [120817, 444, 71867, 130037, 120787, 65301, 120827, 120807, 120797],
	"6" => [120818, 120808, 65302, 130038, 120788, 71893, 11474, 1073, 5102, 120798, 120828],
	"7" => [120799, 66770, 65303, 71878, 130039, 120819, 120809, 119314, 120789, 120829],
	"8" => [120790, 120830, 2819, 125131, 546, 2538, 2666, 120820, 547, 65304, 66330, 120800, 130040, 120810],
	"9" => [120831, 130041, 42862, 71852, 2663, 120791, 71894, 65305, 120801, 120811, 2541, 71884, 3437, 11466, 2920, 120821],
	"A" => [120546, 120016, 120328, 42222, 120604, 119964, 120068, 43898, 120488, 120276, 120120, 120380, 120172, 120432, 913, 119860, 94016, 1040, 5573, 7424, 119808, 65313, 120662, 66208, 5034, 120224, 119912, 120720],
	"B" => [120121, 120017, 120433, 1042, 120277, 42932, 120721, 120173, 120663, 66178, 66209, 1074, 120547, 914, 120489, 5108, 5623, 5842, 66305, 120069, 119913, 120329, 120605, 119809, 8492, 119861, 5116, 120225, 120381, 42192, 65314, 665],
	"C" => [120434, 120226, 120330, 119862, 71913, 66581, 5087, 71922, 8493, 65315, 1017, 120018, 11428, 120382, 1057, 128844, 8557, 5205, 42202, 119914, 119966, 8834, 8450, 11814, 66306, 66844, 119810, 120174, 120278, 66210],
	"D" => [120071, 120383, 119967, 120435, 42195, 120331, 8517, 119915, 120279, 120123, 43888, 65316, 120227, 120019, 7429, 120175, 8558, 5024, 5610, 119863, 5598, 119811],
	"E" => [119916, 120492, 120124, 120176, 120280, 71846, 119812, 120550, 120072, 8959, 120436, 8496, 5036, 42224, 120724, 11577, 71854, 120384, 65317, 43900, 120020, 119864, 120228, 917, 120332, 120608, 1045, 7431, 120666, 66182],
	"F" => [120177, 8497, 42904, 119813, 42205, 71874, 120229, 120333, 71842, 5556, 120437, 120281, 119917, 120385, 66183, 66853, 120073, 65318, 119315, 120778, 120125, 120021, 119865, 66213, 988],
	"G" => [119970, 610, 5056, 42198, 119866, 120126, 120282, 1292, 120438, 120178, 120334, 120022, 5107, 120386, 120230, 119918, 43920, 1293, 120074, 119814, 5115, 65319],
	"H" => [8461, 11406, 43915, 119919, 65320, 1053, 120439, 668, 120552, 119867, 120023, 120283, 120179, 42215, 120610, 120387, 1085, 120231, 919, 120726, 5500, 119815, 66255, 8459, 8460, 5051, 120494, 120335, 120668],
	"I" => [120157, 120180, 119868, 406, 11599, 126464, 42226, 65165, 65356, 120417, 8739, 120496, 1030, 11410, 1472, 120813, 120554, 65297, 120336, 1503, 124, 120261, 120469, 65512, 1633, 119816, 120128, 1493, 120803, 120440, 93992, 120313, 120001, 120823, 120612, 120053, 8572, 120783, 120232, 119845, 1216, 66313, 120284, 66336, 119897, 125127, 9213, 921, 448, 120105, 119920, 120365, 1994, 65321, 8465, 8544, 65166, 126592, 8467, 66186, 120670, 8464, 120728, 130033, 120024, 1777, 119949, 120793, 120388, 1575, 120209, 5825],
	"J" => [1032, 42930, 120389, 120077, 120233, 120181, 120285, 43899, 119817, 7434, 5261, 65322, 120337, 119869, 120441, 42201, 119973, 895, 120129, 119921, 120025, 5035],
	"K" => [5845, 120390, 119974, 120234, 119870, 922, 120130, 42199, 120026, 120286, 120338, 120497, 66840, 120555, 120182, 1050, 120671, 65323, 5094, 120729, 120078, 120442, 119922, 119818, 120613, 11412, 8490],
	"L" => [11473, 120391, 119871, 120235, 120287, 43950, 120339, 65324, 119923, 120079, 66854, 120183, 120443, 66627, 42209, 93974, 8556, 5290, 11472, 71843, 120131, 671, 119819, 71858, 120027, 8466, 5086, 119338, 66587],
	"M" => [66224, 120028, 8559, 66321, 5616, 120236, 5846, 120340, 924, 120080, 120673, 120499, 120615, 119820, 119924, 120444, 120288, 5047, 119872, 1052, 8499, 42207, 120184, 65325, 120731, 1018, 120392, 120557, 120132, 11416],
	"N" => [119873, 120674, 11418, 120237, 120341, 119821, 628, 120500, 120185, 119977, 120393, 42208, 120616, 8469, 66835, 120289, 120029, 120732, 120081, 120558, 65326, 119925, 120445, 925],
	"O" => [119926, 119978, 120030, 3360, 3174, 66219, 66754, 120134, 927, 66194, 120734, 42227, 66564, 11604, 12295, 1505, 120394, 120676, 120446, 1054, 120560, 3302, 120792, 119822, 2848, 120342, 120238, 4816, 120186, 11422, 120290, 1984, 120502, 65327, 120082],
	"P" => [43954, 120291, 7465, 119979, 65328, 120395, 120239, 120083, 1056, 8473, 120447, 120031, 42193, 120504, 929, 120187, 120678, 120736, 66197, 7448, 119875, 5229, 119823, 11426, 120562, 120343, 5090, 120620, 119927],
	"Q" => [119980, 120188, 11605, 119928, 119876, 119824, 8474, 120084, 120396, 65329, 120344, 120240, 120448, 120292, 120032],
	"R" => [119929, 8476, 43889, 119318, 120293, 5511, 640, 120241, 119825, 5809, 422, 5025, 65330, 8475, 43938, 120397, 5074, 66740, 120189, 8477, 119877, 120449, 94005, 120033, 42211, 120345],
	"S" => [1359, 65331, 120034, 120190, 120242, 66198, 5082, 119930, 119878, 119982, 120346, 120398, 66592, 5077, 120294, 42210, 120086, 119826, 120138, 94010, 120450, 1029],
	"T" => [120533, 120451, 119931, 120139, 10201, 11430, 93962, 7451, 119827, 66199, 43890, 120035, 120765, 120347, 964, 120507, 120707, 8868, 1058, 1090, 128872, 120243, 120681, 120565, 119983, 120295, 120399, 932, 66225, 120087, 65332, 120739, 120649, 119879, 120591, 5026, 42196, 120623, 71868, 66325, 120191],
	"U" => [119880, 119828, 8899, 8746, 120244, 120088, 120192, 119932, 119984, 120400, 5196, 66766, 120036, 120296, 120348, 120452, 71864, 94018, 42228, 120140, 4608, 65333, 1357],
	"V" => [119829, 120453, 66845, 8548, 120193, 5081, 5167, 1140, 42719, 11576, 1783, 65334, 119309, 120141, 119881, 120349, 119985, 119933, 120245, 71840, 42214, 120089, 1639, 120037, 120297, 93960, 120401],
	"W" => [120454, 71910, 1308, 120402, 119934, 119986, 119830, 120038, 120298, 71919, 120090, 5076, 120246, 65335, 120142, 119882, 120194, 5043, 42218, 120350],
	"X" => [120247, 8553, 120742, 119987, 66338, 9587, 120351, 935, 120510, 66192, 66228, 120143, 66327, 42219, 11436, 119935, 1061, 65336, 120091, 120684, 119883, 71916, 5741, 119831, 120626, 11613, 66855, 120195, 120403, 120299, 120455, 120568, 42931, 120039, 5815],
	"Y" => [120196, 42220, 933, 119988, 120456, 119936, 66226, 120352, 120144, 120682, 120404, 5053, 120300, 11432, 120508, 5033, 120740, 120624, 119832, 1059, 120040, 71844, 65337, 1198, 94019, 120092, 978, 119884, 120566, 120248],
	"Z" => [119885, 66293, 120551, 120457, 65338, 8484, 918, 120405, 120493, 120353, 120609, 120667, 119937, 120197, 119833, 42204, 120249, 120041, 5059, 120725, 119989, 120301, 71849, 71909, 8488],
	"a" => [65345, 593, 945, 119834, 120406, 120688, 119938, 120458, 9082, 120746, 119990, 1072, 120094, 120302, 120354, 120146, 120250, 119886, 120572, 120514, 120042, 120630, 120198],
	"b" => [119939, 119835, 119991, 120251, 5551, 120199, 120355, 5071, 120043, 388, 120303, 120147, 1068, 120095, 119887, 65346, 120459, 120407, 5234],
	"c" => [120096, 119888, 120304, 8573, 120200, 7428, 66621, 43951, 120044, 120356, 119836, 120252, 120408, 1089, 120148, 1010, 11429, 120460, 119940, 119992, 65347],
	"d" => [120305, 5095, 119837, 119993, 8518, 42194, 1281, 65348, 120201, 120097, 120461, 120409, 120253, 5231, 119889, 120357, 8574, 120149, 119941, 120045],
	"e" => [8495, 120098, 1213, 65349, 120150, 8519, 120410, 8494, 43826, 119942, 120306, 119890, 120254, 120046, 120202, 120358, 120462, 1077, 119838],
	"f" => [120411, 119943, 383, 119995, 120099, 1412, 120779, 120307, 65350, 120463, 120203, 43829, 120047, 7837, 120151, 120255, 119891, 119839, 989, 120359, 42905],
	"g" => [120204, 119840, 8458, 120308, 120152, 120412, 609, 120100, 120464, 397, 1409, 120256, 120360, 65351, 119944, 7555, 120048, 119892],
	"h" => [119841, 119997, 65352, 120205, 120413, 120465, 120153, 120257, 120361, 5058, 1392, 120309, 120049, 8462, 119945, 120101, 1211],
	"i" => [120362, 8126, 8520, 120696, 120206, 119842, 1231, 890, 65353, 120258, 120638, 42567, 120310, 5029, 120414, 71875, 617, 119998, 120484, 9075, 120102, 119894, 953, 120050, 120522, 120754, 119946, 120466, 618, 120580, 731, 43893, 1110, 8560, 8505, 120154, 305, 237],
	"j" => [119843, 120259, 120467, 8521, 120155, 1011, 65354, 119947, 119895, 119999, 120415, 1112, 120103, 120363, 120311, 120051, 120207],
	"k" => [65355, 120156, 119896, 119844, 120416, 120468, 120208, 120260, 120312, 120104, 120364, 119948, 120052, 120000],
	"l" => [120157, 120180, 119868, 406, 11599, 126464, 42226, 65165, 65356, 120417, 8739, 120496, 1030, 11410, 1472, 120813, 120554, 65297, 120336, 1503, 124, 120261, 120469, 65512, 1633, 119816, 120128, 1493, 120803, 120440, 93992, 120313, 120001, 120823, 120612, 120053, 8572, 120783, 120232, 119845, 1216, 66313, 120284, 66336, 119897, 125127, 9213, 921, 448, 120105, 119920, 120365, 1994, 65321, 8465, 8544, 65166, 126592, 8467, 66186, 120670, 8464, 120728, 130033, 120024, 1777, 119949, 120793, 120388, 1575, 120209, 5825],
	"m" => [65357],
	"n" => [120107, 120211, 120263, 119899, 65358, 120315, 1400, 120367, 120055, 120419, 119951, 120003, 120159, 120471, 119847, 1404],
	"o" => [1413, 64426, 1726, 120590, 2534, 3330, 65257, 120368, 1729, 119900, 2662, 120764, 3074, 4351, 119952, 120264, 3458, 65359, 120644, 120316, 64422, 7441, 1637, 64427, 120528, 120760, 71880, 927, 65260, 120706, 120532, 1781, 64429, 65259, 65258, 4160, 64423, 120702, 119848, 2790, 120472, 66794, 3792, 120056, 1607, 3202, 2406, 3430, 64428, 66604, 120648, 8500, 2918, 43837, 1749, 120586, 3664, 120108, 120212, 963, 1086, 7439, 959, 120160, 3046, 120420, 64424, 64425, 11423, 4125],
	"p" => [119901, 120161, 120718, 1009, 120776, 120588, 65360, 11427, 120005, 120762, 119953, 120213, 120646, 119849, 120602, 120421, 120660, 120704, 120109, 120473, 961, 9076, 120265, 120544, 120317, 1088, 120369, 120530, 120057],
	"q" => [1307, 65361, 120058, 120266, 120110, 120162, 119902, 120318, 120422, 120214, 120474, 1379, 119954, 120370, 119850, 1382, 120006],
	"r" => [120215, 120059, 11397, 120267, 119903, 43847, 120475, 119851, 120423, 1075, 119955, 120007, 43848, 120319, 65362, 120111, 7462, 120163, 43905, 120371],
	"s" => [120476, 42801, 43946, 120320, 445, 1109, 119904, 71873, 120164, 120424, 120060, 119956, 120268, 120372, 119852, 66632, 120112, 120216, 65363, 120008],
	"t" => [119957, 120269, 120217, 120425, 120113, 120061, 120009, 119853, 120321, 119905, 120477, 120165, 65364, 120373],
	"u" => [119906, 66806, 120270, 120534, 7452, 65365, 119854, 120218, 120478, 120592, 120322, 120374, 119958, 120114, 120426, 71896, 965, 120766, 120010, 120062, 120166, 1405, 43854, 43858, 42911, 120708, 651, 120650],
	"v" => [120427, 120063, 1141, 120758, 65366, 119959, 120115, 120700, 120323, 120479, 119855, 7456, 120011, 120219, 1496, 8897, 120642, 120526, 71430, 957, 120584, 119907, 8744, 120375, 120271, 43945, 71872, 8564, 120167],
	"w" => [120324, 65367, 120272, 119960, 119856, 71439, 120376, 120428, 120220, 120116, 120064, 43907, 119908, 623, 71434, 1377, 120480, 120168, 7457, 120012, 1121, 71438, 1309],
	"x" => [119857, 119909, 10799, 120117, 120273, 1093, 5501, 120377, 120013, 119961, 8569, 10540, 120325, 215, 5742, 120481, 65368, 5441, 120169, 10539, 120221, 120065, 120429],
	"y" => [43866, 7935, 1091, 655, 120326, 8509, 120690, 1199, 4327, 120170, 120014, 120516, 120378, 65369, 120430, 120632, 611, 120748, 120066, 119910, 120482, 947, 120118, 7564, 119962, 71900, 119858, 120222, 120574, 120274],
	"z" => [119963, 119859, 120223, 120379, 120171, 120327, 120119, 7458, 120015, 43923, 119911, 120431, 120483, 71876, 120067, 120275, 65370]
);

Et la seconde liste, avec la correspondance PHP entre les caractères connus et leurs homoglyphes :

// Liste des homoglyphes (sous forme de caractères)
$homoglyphs_chars = array(
    "'" => ['‘', '’', '‛'],
    '"' => ["‟", "“", "”", "« ", " »", "«", "»"],
    "-" => ['۔', 'Ⲻ', '﹘', '˗', '−', '➖', '‑', '⁃', '‒', '–', '‐', '—'],
	"." => ['܁', '٠', '․', '۰', '꘎', 'ꓸ', '܂', '?', '.', '?'],
	"0" => ['0', '?', '?', '?', '?', '?', '?', 'ଠ', '?', '?', 'ዐ', '?', 'Օ', '௦', '?'],
	"1" => ['?', '?', '?', 'Ɩ', 'ⵏ', '?', 'ꓲ', 'ﺍ', 'l', '?', '∣', '?', 'І', 'Ⲓ', '׀', '?', '?', '1', '?', 'ן', '|', '?', '?', '│', '١', '?', '?', 'ו', '?', '?', '?', '?', '?', '?', '?', '?', 'ⅼ', '?', '?', '?', 'Ӏ', '?', '?', '?', '?', '?', '⏽', 'Ι', 'ǀ', '?', '?', '?', 'ߊ', 'I', 'ℑ', 'Ⅰ', 'ﺎ', '?', 'ℓ', '?', '?', 'ℐ', '?', '?', '?', '۱', '?', '?', '?', 'ا', '?', 'ᛁ'],
	"2" => ['Ꝛ', 'ᒿ', 'Ϩ', '?', 'ꛯ', 'Ƨ', '?', '?', '?', '?', '2', 'Ꙅ', '?'],
	"3" => ['?', 'Ꝫ', '?', 'Ȝ', '?', 'Ⳍ', 'З', 'Ӡ', '?', 'Ʒ', '3', '?', '?', '?', '?', '?', 'Ɜ'],
	"4" => ['?', '?', '?', '?', '?', '?', '4', 'Ꮞ', '?'],
	"5" => ['?', 'Ƽ', '?', '?', '?', '5', '?', '?', '?'],
	"6" => ['?', '?', '6', '?', '?', '?', 'Ⳓ', 'б', 'Ꮾ', '?', '?'],
	"7" => ['?', '?', '7', '?', '?', '?', '?', '?', '?', '?'],
	"8" => ['?', '?', 'ଃ', '?', 'Ȣ', '৪', '੪', '?', 'ȣ', '8', '?', '?', '?', '?'],
	"9" => ['?', '?', 'Ꝯ', '?', '੧', '?', '?', '9', '?', '?', '৭', '?', '൭', 'Ⳋ', '୨', '?'],
	"A" => ['?', '?', '?', 'ꓮ', '?', '?', '?', 'ꭺ', '?', '?', '?', '?', '?', '?', 'Α', '?', '?', 'А', 'ᗅ', 'ᴀ', '?', 'A', '?', '?', 'Ꭺ', '?', '?', '?'],
	"B" => ['?', '?', '?', 'В', '?', 'Ꞵ', '?', '?', '?', '?', '?', 'в', '?', 'Β', '?', 'Ᏼ', 'ᗷ', 'ᛒ', '?', '?', '?', '?', '?', '?', 'ℬ', '?', 'ᏼ', '?', '?', 'ꓐ', 'B', 'ʙ'],
	"C" => ['?', '?', '?', '?', '?', '?', 'Ꮯ', '?', 'ℭ', 'C', 'Ϲ', '?', 'Ⲥ', '?', 'С', '?', 'Ⅽ', 'ᑕ', 'ꓚ', '?', '?', '⊂', 'ℂ', '⸦', '?', '?', '?', '?', '?', '?'],
	"D" => ['?', '?', '?', '?', 'ꓓ', '?', 'ⅅ', '?', '?', '?', 'ꭰ', 'D', '?', '?', 'ᴅ', '?', 'Ⅾ', 'Ꭰ', 'ᗪ', '?', 'ᗞ', '?'],
	"E" => ['?', '?', '?', '?', '?', '?', '?', '?', '?', '⋿', '?', 'ℰ', 'Ꭼ', 'ꓰ', '?', 'ⴹ', '?', '?', 'E', 'ꭼ', '?', '?', '?', 'Ε', '?', '?', 'Е', 'ᴇ', '?', '?'],
	"F" => ['?', 'ℱ', 'Ꞙ', '?', 'ꓝ', '?', '?', '?', '?', 'ᖴ', '?', '?', '?', '?', '?', '?', '?', 'F', '?', '?', '?', '?', '?', '?', 'Ϝ'],
	"G" => ['?', 'ɢ', 'Ꮐ', 'ꓖ', '?', '?', '?', 'Ԍ', '?', '?', '?', '?', 'Ᏻ', '?', '?', '?', 'ꮐ', 'ԍ', '?', '?', 'ᏻ', 'G'],
	"H" => ['ℍ', 'Ⲏ', 'ꮋ', '?', 'H', 'Н', '?', 'ʜ', '?', '?', '?', '?', '?', 'ꓧ', '?', '?', 'н', '?', 'Η', '?', 'ᕼ', '?', '?', 'ℋ', 'ℌ', 'Ꮋ', '?', '?', '?'],
	"I" => ['?', '?', '?', 'Ɩ', 'ⵏ', '?', 'ꓲ', 'ﺍ', 'l', '?', '∣', '?', 'І', 'Ⲓ', '׀', '?', '?', '1', '?', 'ן', '|', '?', '?', '│', '١', '?', '?', 'ו', '?', '?', '?', '?', '?', '?', '?', '?', 'ⅼ', '?', '?', '?', 'Ӏ', '?', '?', '?', '?', '?', '⏽', 'Ι', 'ǀ', '?', '?', '?', 'ߊ', 'I', 'ℑ', 'Ⅰ', 'ﺎ', '?', 'ℓ', '?', '?', 'ℐ', '?', '?', '?', '۱', '?', '?', '?', 'ا', '?', 'ᛁ'],
	"J" => ['Ј', 'Ʝ', '?', '?', '?', '?', '?', 'ꭻ', '?', 'ᴊ', 'ᒍ', 'J', '?', '?', '?', 'ꓙ', '?', 'Ϳ', '?', '?', '?', 'Ꭻ'],
	"K" => ['ᛕ', '?', '?', '?', '?', 'Κ', '?', 'ꓗ', '?', '?', '?', '?', '?', '?', '?', 'К', '?', 'K', 'Ꮶ', '?', '?', '?', '?', '?', '?', 'Ⲕ', 'K'],
	"L" => ['ⳑ', '?', '?', '?', '?', 'ꮮ', '?', 'L', '?', '?', '?', '?', '?', '?', 'ꓡ', '?', 'Ⅼ', 'ᒪ', 'Ⳑ', '?', '?', 'ʟ', '?', '?', '?', 'ℒ', 'Ꮮ', '?', '?'],
	"M" => ['?', '?', 'Ⅿ', '?', 'ᗰ', '?', 'ᛖ', '?', 'Μ', '?', '?', '?', '?', '?', '?', '?', '?', 'Ꮇ', '?', 'М', 'ℳ', 'ꓟ', '?', 'M', '?', 'Ϻ', '?', '?', '?', 'Ⲙ'],
	"N" => ['?', '?', 'Ⲛ', '?', '?', '?', 'ɴ', '?', '?', '?', '?', 'ꓠ', '?', 'ℕ', '?', '?', '?', '?', '?', '?', 'N', '?', '?', 'Ν'],
	"O" => ['?', '?', '?', 'ഠ', '౦', '?', '?', '?', 'Ο', '?', '?', 'ꓳ', '?', 'ⵔ', '〇', 'ס', '?', '?', '?', 'О', '?', '೦', '?', '?', 'ଠ', '?', '?', 'ዐ', '?', 'Ⲟ', '?', '߀', '?', 'O', '?'],
	"P" => ['ꮲ', '?', 'ᴩ', '?', 'P', '?', '?', '?', 'Р', 'ℙ', '?', '?', 'ꓑ', '?', 'Ρ', '?', '?', '?', '?', 'ᴘ', '?', 'ᑭ', '?', 'Ⲣ', '?', '?', 'Ꮲ', '?', '?'],
	"Q" => ['?', '?', 'ⵕ', '?', '?', '?', 'ℚ', '?', '?', 'Q', '?', '?', '?', '?', '?'],
	"R" => ['?', 'ℜ', 'ꭱ', '?', '?', 'ᖇ', 'ʀ', '?', '?', 'ᚱ', 'Ʀ', 'Ꭱ', 'R', 'ℛ', 'ꮢ', '?', 'Ꮢ', '?', '?', 'ℝ', '?', '?', '?', '?', 'ꓣ', '?'],
	"S" => ['Տ', 'S', '?', '?', '?', '?', 'Ꮪ', '?', '?', '?', '?', '?', '?', 'Ꮥ', '?', 'ꓢ', '?', '?', '?', '?', '?', 'Ѕ'],
	"T" => ['?', '?', '?', '?', '⟙', 'Ⲧ', '?', 'ᴛ', '?', '?', 'ꭲ', '?', '?', '?', 'τ', '?', '?', '⊤', 'Т', 'т', '?', '?', '?', '?', '?', '?', '?', 'Τ', '?', '?', 'T', '?', '?', '?', '?', 'Ꭲ', 'ꓔ', '?', '?', '?', '?'],
	"U" => ['?', '?', '⋃', '∪', '?', '?', '?', '?', '?', '?', 'ᑌ', '?', '?', '?', '?', '?', '?', '?', 'ꓴ', '?', 'ሀ', 'U', 'Ս'],
	"V" => ['?', '?', '?', 'Ⅴ', '?', 'Ꮩ', 'ᐯ', 'Ѵ', 'ꛟ', 'ⴸ', '۷', 'V', '?', '?', '?', '?', '?', '?', '?', '?', 'ꓦ', '?', '٧', '?', '?', '?', '?'],
	"W" => ['?', '?', 'Ԝ', '?', '?', '?', '?', '?', '?', '?', '?', 'Ꮤ', '?', 'W', '?', '?', '?', 'Ꮃ', 'ꓪ', '?'],
	"X" => ['?', 'Ⅹ', '?', '?', '?', '╳', '?', 'Χ', '?', '?', '?', '?', '?', 'ꓫ', 'Ⲭ', '?', 'Х', 'X', '?', '?', '?', '?', '᙭', '?', '?', 'ⵝ', '?', '?', '?', '?', '?', '?', 'Ꭓ', '?', 'ᚷ'],
	"Y" => ['?', 'ꓬ', 'Υ', '?', '?', '?', '?', '?', '?', '?', '?', 'Ꮍ', '?', 'Ⲩ', '?', 'Ꭹ', '?', '?', '?', 'У', '?', '?', 'Y', 'Ү', '?', '?', 'ϒ', '?', '?', '?'],
	"Z" => ['?', '?', '?', '?', 'Z', 'ℤ', 'Ζ', '?', '?', '?', '?', '?', '?', '?', '?', 'ꓜ', '?', '?', 'Ꮓ', '?', '?', '?', '?', '?', 'ℨ'],
	"a" => ['a', 'ɑ', 'α', '?', '?', '?', '?', '?', '⍺', '?', '?', 'а', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?'],
	"b" => ['?', '?', '?', '?', 'ᖯ', '?', '?', 'Ꮟ', '?', 'Ƅ', '?', '?', 'Ь', '?', '?', 'b', '?', '?', 'ᑲ'],
	"c" => ['?', '?', '?', 'ⅽ', '?', 'ᴄ', '?', 'ꮯ', '?', '?', '?', '?', '?', 'с', '?', 'ϲ', 'ⲥ', '?', '?', '?', 'c'],
	"d" => ['?', 'Ꮷ', '?', '?', 'ⅆ', 'ꓒ', 'ԁ', 'd', '?', '?', '?', '?', '?', 'ᑯ', '?', '?', 'ⅾ', '?', '?', '?'],
	"e" => ['ℯ', '?', 'ҽ', 'e', '?', 'ⅇ', '?', '℮', 'ꬲ', '?', '?', '?', '?', '?', '?', '?', '?', 'е', '?'],
	"f" => ['?', '?', 'ſ', '?', '?', 'ք', '?', '?', 'f', '?', '?', 'ꬵ', '?', 'ẝ', '?', '?', '?', '?', 'ϝ', '?', 'ꞙ'],
	"g" => ['?', '?', 'ℊ', '?', '?', '?', 'ɡ', '?', '?', 'ƍ', 'ց', '?', '?', 'g', '?', 'ᶃ', '?', '?'],
	"h" => ['?', '?', 'h', '?', '?', '?', '?', '?', '?', 'Ꮒ', 'հ', '?', '?', 'ℎ', '?', '?', 'һ'],
	"i" => ['?', 'ι', 'ⅈ', '?', '?', '?', 'ӏ', 'ͺ', 'i', '?', '?', 'ꙇ', '?', 'Ꭵ', '?', '?', 'ɩ', '?', '?', '⍳', '?', '?', 'ι', '?', '?', '?', '?', '?', 'ɪ', '?', '˛', 'ꭵ', 'і', 'ⅰ', 'ℹ', '?', 'ı', "í"],
	"j" => ['?', '?', '?', 'ⅉ', '?', 'ϳ', 'j', '?', '?', '?', '?', 'ј', '?', '?', '?', '?', '?'],
	"k" => ['k', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?'],
	"l" => ['?', '?', '?', 'Ɩ', 'ⵏ', '?', 'ꓲ', 'ﺍ', 'l', '?', '∣', '?', 'І', 'Ⲓ', '׀', '?', '?', '1', '?', 'ן', '|', '?', '?', '│', '١', '?', '?', 'ו', '?', '?', '?', '?', '?', '?', '?', '?', 'ⅼ', '?', '?', '?', 'Ӏ', '?', '?', '?', '?', '?', '⏽', 'Ι', 'ǀ', '?', '?', '?', 'ߊ', 'I', 'ℑ', 'Ⅰ', 'ﺎ', '?', 'ℓ', '?', '?', 'ℐ', '?', '?', '?', '۱', '?', '?', '?', 'ا', '?', 'ᛁ'],
	"m" => ['m'],
	"n" => ['?', '?', '?', '?', 'n', '?', 'ո', '?', '?', '?', '?', '?', '?', '?', '?', 'ռ'],
	"o" => ['օ', 'ﮪ', 'ھ', '?', '০', 'ം', 'ﻩ', '?', 'ہ', '?', '੦', '?', 'ం', 'ჿ', '?', '?', 'ං', 'o', '?', '?', 'ﮦ', 'ᴑ', '٥', 'ﮫ', '?', '?', '?', 'Ο', 'ﻬ', '?', '?', '۵', 'ﮭ', 'ﻫ', 'ﻪ', '၀', 'ﮧ', '?', '?', '૦', '?', '?', '໐', '?', 'ه', 'ಂ', '०', '൦', 'ﮬ', '?', '?', 'ℴ', '୦', 'ꬽ', 'ە', '?', '๐', '?', '?', 'σ', 'о', 'ᴏ', 'ο', '?', '௦', '?', 'ﮨ', 'ﮩ', 'ⲟ', 'ဝ'],
	"p" => ['?', '?', '?', 'ϱ', '?', '?', 'p', 'ⲣ', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 'ρ', '⍴', '?', '?', '?', 'р', '?', '?', '?'],
	"q" => ['ԛ', 'q', '?', '?', '?', '?', '?', '?', '?', '?', '?', 'գ', '?', '?', '?', 'զ', '?'],
	"r" => ['?', '?', 'ⲅ', '?', '?', 'ꭇ', '?', '?', '?', 'г', '?', '?', 'ꭈ', '?', 'r', '?', 'ᴦ', '?', 'ꮁ', '?'],
	"s" => ['?', 'ꜱ', 'ꮪ', '?', 'ƽ', 'ѕ', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 's', '?'],
	"t" => ['?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 't', '?'],
	"u" => ['?', '?', '?', '?', 'ᴜ', 'u', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', 'υ', '?', '?', '?', '?', 'ս', 'ꭎ', 'ꭒ', 'ꞟ', '?', 'ʋ', '?'],
	"v" => ['?', '?', 'ѵ', '?', 'v', '?', '?', '?', '?', '?', '?', 'ᴠ', '?', '?', 'ט', '⋁', '?', '?', '?', 'ν', '?', '?', '∨', '?', '?', 'ꮩ', '?', 'ⅴ', '?'],
	"w" => ['?', 'w', '?', '?', '?', '?', '?', '?', '?', '?', '?', 'ꮃ', '?', 'ɯ', '?', 'ա', '?', '?', 'ᴡ', '?', 'ѡ', '?', 'ԝ'],
	"x" => ['?', '?', '⨯', '?', '?', 'х', 'ᕽ', '?', '?', '?', 'ⅹ', '⤬', '?', '×', '᙮', '?', 'x', 'ᕁ', '?', '⤫', '?', '?', '?'],
	"y" => ['ꭚ', 'ỿ', 'у', 'ʏ', '?', 'ℽ', '?', 'ү', 'ყ', '?', '?', '?', '?', 'y', '?', '?', 'ɣ', '?', '?', '?', '?', 'γ', '?', 'ᶌ', '?', '?', '?', '?', '?', '?'],
	"z" => ['?', '?', '?', '?', '?', '?', '?', 'ᴢ', '?', 'ꮓ', '?', '?', '?', '?', '?', '?', 'z'],
);

Vous noterez que certains caractères s'affichent mal, cela provient des problèmes d'encodage, mais ces caractères sont pourtant bien existants...

Parcourir les listes de caractères et d'homoglyphes en PHP

Avant de passer au remplacement des homoglyphes, voyons rapidement comment parcourir les listes présentées ci-dessus. Il existe plusieurs manières de passer des points de code Unicode vers les caractères, ou inversement. Pour ce faire, nous pouvons par exemple utiliser les fonctions natives mb_chr() et mb_ord() de PHP 7 et plus qui effectuent automatiquement les transformations dans un encodage donné ("UTF-8" en règle générale dans notre cas).

Je vous ai mis 4 exemples de boucles pour obtenir les listes de caractères, avec leurs points de code Unicode et leurs homoglyphes équivalents. Cela peut s'avérer pratique si vous avez besoin de vérifier des listes de signes, notamment en cas de problème de remplacement (voir la fonction en fin d'article).

// Tables de correspondance
$chars = ["-", ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
$chars_hex = [0x2d, 0x2e, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a];

// Boucle la liste des caractères pour obtenir leurs correspondances directes
foreach($homoglyphs_chars as $sign => $chars) {
	echo($sign.' ==> ');
	foreach($chars as $char) {
		if(!in_array($char, $chars_hex)) {
			echo($char);
		}
	}
	echo "<br/>";
}

// Boucle la liste des points de code Unicode pour obtenir les codifications correspondantes
foreach($homoglyphs_hex as $sign => $chars_hex) {
	echo($sign.' ==> ');
	foreach($chars_hex as $char) {
		if(!in_array($char, $chars)) {
			echo(mb_chr($char, 'UTF-8'));
		}
	}
	echo "<br/>";
}

// Boucle la liste des caractères pour obtenir leurs points de code Unicode correspondants
foreach($homoglyphs_chars as $sign => $chars) {
	echo($sign.' ==> ');
	foreach($chars as $char) {
		if(!in_array($char, $chars_hex)) {
			echo(mb_ord($char, 'UTF-8').", ");
		}
	}
	echo "<br/>";
}

// Boucle pour obtenir la liste complète des correspondances
foreach($homoglyphs_chars as $sign => $chars) {
	echo($sign.' ==> ');
	foreach($chars as $char) {
		if(!in_array($char, $chars_hex)) {
			echo("[".$char.", ".mb_ord($char, 'UTF-8')."], ");
		}
	}
	echo "<br/>";
}

Remplacer les homoglyphes et homographes dans un texte avec PHP

L'étape la plus intéressante après tout ce travail préalable sur les homoglyphes est bien évidemment de pouvoir manipuler nos textes mal encodés en PHP pour récupérer un équivalent plus propre et compréhensible par la machine.

Prenons l'exemple de la phrase : "Lorⅇm i?sum dol?r sit ame?, co?sectetur adipisci?g elit, ?ed d? e?usmod tempor ?nciᑯidunt ut labore et ᑯolore magna ?liqu?.". On reconnait bien à l'oeil le début du classique Lorem ipsum utilisé par les webdesigners ou développeurs, mais on remarque clairement que certains caractères ne sont pas encodés comme il le faudrait. L'objectif serait de pouvoir transformer cette chaîne de caractère en "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".

La fonction suivante, qui s'appuie notamment sur la première liste fournit dans cet article, permet d'effectuer la conversion des homoglyphes en leur équivalent bien encodé (vous aurez compris que l'on peut adapter plusieurs fonctions selon nos envies et besoins avec la liste de notre choix, même si je préconise de passer par les points de code, qui ont l'avantage d'être différenciables facilement pour une machine).

function replace_homoglyphs($text = ""):string {
	# Récupère la liste des points de code Unicode des homoglyphes (variable globale)
	global $homoglyphs_chars;

	# Remplace les homoglyphes par leur caractère correspondant
	foreach($homoglyphs_chars as $homoglyph_letter => $homoglyphs_list) {
	    foreach($homoglyphs_list as $homoglyph) {
	        $text = str_replace($homoglyph, $homoglyph_letter, $text);
	    }
	}

	return $text;
}

$texte = "Lorⅇm i?sum dol?r sit ame?, co?sectetur adipisci?g elit, ?ed d? e?usmod tempor ?nciᑯidunt ut labore et ᑯolore magna ?liqu?.";

echo("<strong>Texte source :</strong><br/>");
echo($texte."<br/><br/>");

$texte_sans_homoglyphes = replace_homoglyphs($texte);

echo("<strong>Texte avec homoglyphes nettoyés :</strong><br/>");
echo($texte_sans_homoglyphes."<br/><br/>");

Ces quelques lignes suffisent à nettoyer le texte avec PHP, et à pouvoir obtenir quelque chose de propre en quelques secondes... :-)

Remplacement des homoglyphes et homographes en PHP dans un texte

Voilà, vous savez désormais comment vous débarrassez de ce type de problème relativement dérangeant lors du traitement de chaînes de caractères...