10 |
raphael |
1 |
/*
|
|
|
2 |
Surligneur de mot (glossaire)
|
|
|
3 |
|
|
|
4 |
0) tout d'abord la liste des mots est récupée (getMotsADefinitions) et stockée dans motsAyantDefinition
|
|
|
5 |
|
|
|
6 |
1) chaque mot de la liste subie une transformation progressive en pattern:
|
|
|
7 |
* les mots de moins de 3 caractères sont inchangés (exemple: "nu")
|
|
|
8 |
* les mots finissant en "eux/euse" sont généricisés
|
|
|
9 |
* les autres mots se voit ajoutée la possibilité de matcher sur \\w{0,2}
|
|
|
10 |
|
|
|
11 |
2) puis plusieurs RegExp sont baties à partir de la liste de pattern préliminaires
|
|
|
12 |
* regexp_complete permet d'assurer que le mot:
|
|
|
13 |
** commence en début de ligne
|
|
|
14 |
** suit un espace
|
|
|
15 |
** ne suit pas un caractère
|
|
|
16 |
[ mais ne s'assure pas que le mot ne suit PAS un caractère accentué, cf notes à propos de \\b et l'UTF-8 ]
|
|
|
17 |
|
|
|
18 |
* regexp_thin permet de s'assurer que le mot:
|
|
|
19 |
** ne se trouve pas déjà encapsulé dans un <span rel="" class="definition_term">X</span>
|
|
|
20 |
** ne se trouve pas comme attribut "rel" d'un <span rel="X" class="definition_term"></span>
|
|
|
21 |
** n'est pas directement précédé d'un accent (parmis ceux utilisés dans la BDD, cf exclureSpan)
|
|
|
22 |
** n'est pas directement précédé d'un caractère alphabétique
|
|
|
23 |
Et tente d'extraire une partie de contexte significative (jusqu'à 24 caractère car initialement le
|
|
|
24 |
test d'inclusion dans .definition_term était faire plus tardivement.
|
|
|
25 |
|
|
|
26 |
3) remplacerDefinitions() prend la liste des nodes susceptibles de nécessiter un traitement
|
|
|
27 |
et ignore ceux ayant déjà été traités [max_passages] fois (4 par défaut)
|
|
|
28 |
|
|
|
29 |
4) pour chaque node:
|
|
|
30 |
* le compteur de passage est incrémenté
|
16 |
aurelien |
31 |
* necessiteSubstitution() est appellé
|
10 |
raphael |
32 |
|
16 |
aurelien |
33 |
== necessiteSubstitution() ==:
|
10 |
raphael |
34 |
** Utilise regexp_thin pour extraire les mots restant à définir ainsi qu'une partie de leur contexte.
|
|
|
35 |
** Utilise regexp_complete pour extraire les mots au plus proche de leur bodures avec un moindre risque d'erreur
|
|
|
36 |
** exlue les faux positifs possibles de regexp_thin
|
|
|
37 |
** renvoi les matches de regexp_thin (qui présente plus de contexte)
|
|
|
38 |
=============================
|
|
|
39 |
|
16 |
aurelien |
40 |
* pour chaque correspondance retournée par necessiteSubstitution(),
|
10 |
raphael |
41 |
le mot d'origine (tel que dans motsAyantDefinition)
|
|
|
42 |
est déterminé en utilisant le levenstein (un cache est mis en place, cf texte_to_mot() dans helpers.js).
|
|
|
43 |
|
|
|
44 |
* rechercherEtRemplacerMotParDefinition() est appelée pour effectuer la substitution dans le node
|
|
|
45 |
|
|
|
46 |
|
|
|
47 |
Contraintes rencontrées:
|
|
|
48 |
1) exclure les mots déjà défini via la regexp général plutôt qu'au coup par coup.
|
|
|
49 |
2) vérifier les bordures
|
|
|
50 |
3) vérifier bordure d'accents:
|
|
|
51 |
\\b(é) dans une regexp ne peut matcher une chaîne JS UTF-8
|
|
|
52 |
http://stackoverflow.com/questions/2881445/utf-8-word-boundary-regex-in-javascript
|
|
|
53 |
*/
|
|
|
54 |
|
1 |
aurelien |
55 |
var dictionnaire = new Array();
|
3 |
aurelien |
56 |
var motsAyantDefinition = null;
|
1 |
aurelien |
57 |
var mouseX = null;
|
|
|
58 |
var mouseY = null;
|
3 |
aurelien |
59 |
var active = false;
|
22 |
delphine |
60 |
var set = '#zone-droite p, #zone-droite span:not(.definition_term), #zone-droite td, #zone-droite pre, #zone-droite div, #zone-droite li, .layout-content, .layout-content p, .layout-content span:not(.definition_term), .layout-content td, .layout-content pre, .layout-content div, .layout-content li';
|
16 |
aurelien |
61 |
var urlPage = window.location.href;
|
1 |
aurelien |
62 |
|
10 |
raphael |
63 |
// Note: utiliser \\b plutôt que \\W pour matcher les bordures de mots
|
|
|
64 |
// en incluant début et fin de ligne
|
|
|
65 |
var regexp_complete = null;
|
|
|
66 |
var regexp_thin = null;
|
|
|
67 |
|
|
|
68 |
// nombre maximum de passages de la détection sur un node donné
|
|
|
69 |
var max_passages = 4;
|
|
|
70 |
|
|
|
71 |
// echo $(mysql<<<"SELECT cle FROM definitions"|tr "[a-z,-]" o|sed -e 's/o//g' -e "s/\(.\)/\1\n/g"|sort -u)
|
|
|
72 |
// ç é è ê î ï ô (2013/07/03)
|
|
|
73 |
// TODO: http://xregexp.com/plugins/
|
|
|
74 |
var exclureSpan = '([^' +
|
|
|
75 |
'(?:class="definition_term">)' + // mot contextualisé déjà dictionnarisé
|
|
|
76 |
'(?:rel=")' + // la valeur du rel="" d'un mot déjà dictionnarisé
|
|
|
77 |
'çéèêîïô' + // les accents, non-traités dans la regexp générale et qui ne doivent pas
|
|
|
78 |
// être pris pour des bordures de mots
|
|
|
79 |
'\\w' +
|
|
|
80 |
']|^){1,24}';
|
|
|
81 |
|
19 |
raphael |
82 |
// sert à filtrer plus rapidement les nodes inutiles pour l'application
|
|
|
83 |
// du dictionnaire
|
|
|
84 |
var exclureBlancs = new RegExp('^\\s*$');
|
10 |
raphael |
85 |
|
3 |
aurelien |
86 |
function afficherLienDefinitions() {
|
|
|
87 |
html = '<div id="conteneur_activation_definition"><a href="#">rechercher les définitions</a></div>';
|
23 |
killian |
88 |
$('#conteneur_activation_definition').on('click', function(event) {
|
3 |
aurelien |
89 |
event.preventDefault();
|
10 |
raphael |
90 |
//supprimerToutesDefinitions();
|
3 |
aurelien |
91 |
if(motsAyantDefinition == null) {
|
|
|
92 |
getMotsADefinitions();
|
10 |
raphael |
93 |
ajouterListenerDefinitions();
|
3 |
aurelien |
94 |
} else {
|
7 |
raphael |
95 |
$(set).remplacerDefinitions(motsAyantDefinition);
|
3 |
aurelien |
96 |
}
|
|
|
97 |
});
|
|
|
98 |
$('body').append(html);
|
|
|
99 |
}
|
|
|
100 |
|
|
|
101 |
$.fn.remplacerDefinitions = function(mots) {
|
10 |
raphael |
102 |
this.each(function() {
|
|
|
103 |
$(this).contents().filter(function() {
|
|
|
104 |
return (this.nodeType == 3 && ! (($(this).data("count") || 0) > max_passages));
|
|
|
105 |
}).each(function() {
|
19 |
raphael |
106 |
// évite les nodes dont le texte est undefined (dumb example <script>)
|
|
|
107 |
if(!$(this).text()) return true;
|
|
|
108 |
// évite les nodes dont le texte est vide ou blanc
|
20 |
aurelien |
109 |
ttexte = texte = $(this).text();
|
19 |
raphael |
110 |
if(exclureBlancs.test(texte)) return true;
|
|
|
111 |
|
|
|
112 |
// incrémente le compteur de passage
|
|
|
113 |
/* TODO: cette ligne cause un problème à IE-8
|
|
|
114 |
(peut-être uniquement relié au onChange() en cas de passage sur
|
|
|
115 |
des composants ajoutés postérieurement au chargement initial de la
|
|
|
116 |
page via AJAX. à tester. */
|
10 |
raphael |
117 |
$(this).data("count", ($(this).data("count") || 0) + 1);
|
16 |
aurelien |
118 |
matches_list = necessiteSubstitution(texte)[0];
|
10 |
raphael |
119 |
if(!matches_list) return true; // TODO: data("count") = max_passages ?
|
|
|
120 |
|
|
|
121 |
for(i in matches_list) {
|
|
|
122 |
couple = matches_list[i];
|
|
|
123 |
mot_contextualise = couple[2];
|
|
|
124 |
cle = texte_to_mot(mot_contextualise);
|
|
|
125 |
|
|
|
126 |
if(! (ttexte = rechercherEtRemplacerMotParDefinition(texte, cle, couple))) break;
|
|
|
127 |
texte = ttexte;
|
|
|
128 |
}
|
|
|
129 |
$(this).replaceWith(texte);
|
|
|
130 |
});
|
|
|
131 |
});
|
|
|
132 |
return this;
|
1 |
aurelien |
133 |
}
|
|
|
134 |
|
10 |
raphael |
135 |
function rechercherEtRemplacerMotParDefinition(texte, mot, couple) {
|
|
|
136 |
var full_contexte = couple[0],
|
|
|
137 |
prefix_solo = couple[1],
|
|
|
138 |
mot_contextualise = couple[2];
|
3 |
aurelien |
139 |
|
10 |
raphael |
140 |
// cf exclureSpan
|
|
|
141 |
if(new RegExp('((?:class="definition_term">)|(?:rel=")|(?:[çéèêîïô]))(' + mot + "\\w{0,2}" + ')' + "\\b", "ig").test(full_contexte)) return;
|
|
|
142 |
|
|
|
143 |
templateMotADefinition = formaterTemplateMotADefinition(mot_contextualise, mot);
|
|
|
144 |
bloc_replace = full_contexte.replace(mot_contextualise, templateMotADefinition);
|
|
|
145 |
return texte.replace(full_contexte, bloc_replace);
|
3 |
aurelien |
146 |
}
|
|
|
147 |
|
10 |
raphael |
148 |
|
16 |
aurelien |
149 |
function necessiteSubstitution(texte) {
|
10 |
raphael |
150 |
var liste = [], liste_complete = [];
|
|
|
151 |
|
|
|
152 |
// regexp d'extraction du contexte, sans limite aux bords de "mot",
|
|
|
153 |
// peu amener des faux positifs, et notamment ne pas matcher au mieux.
|
|
|
154 |
// Eg: subsessiles match "sessile" et non "subsessile".
|
11 |
raphael |
155 |
while( (matches_c = regexp_thin.exec(texte)) ) liste.push(matches_c);
|
10 |
raphael |
156 |
if(liste.length == 0) return [null];
|
|
|
157 |
|
|
|
158 |
// regexp de validation des bordure de mots, ne conserve que des matches correct
|
|
|
159 |
// vis-à-vis de la bordure gauche
|
11 |
raphael |
160 |
while( (matches_c = regexp_complete.exec(texte)) ) liste_complete.push(matches_c[1]);
|
10 |
raphael |
161 |
|
|
|
162 |
// si une match n'est pas dans le tableau des mots autorisés (indexOf)
|
|
|
163 |
for(var i = 0; i < liste.length; i++) {
|
|
|
164 |
if($.inArray(liste[i][2], liste_complete) == -1) liste.splice(i, 1);
|
|
|
165 |
}
|
|
|
166 |
|
|
|
167 |
return [liste, liste_complete]
|
|
|
168 |
}
|
|
|
169 |
|
3 |
aurelien |
170 |
function getMotsADefinitions() {
|
1 |
aurelien |
171 |
$.ajax({
|
10 |
raphael |
172 |
url: URL_BASE_SERVICE+'mots/', // provient de dictionnaire.config.js
|
1 |
aurelien |
173 |
success: function(data) {
|
|
|
174 |
motsAyantDefinition = data;
|
10 |
raphael |
175 |
batirRegexp(motsAyantDefinition);
|
7 |
raphael |
176 |
$(set).remplacerDefinitions(motsAyantDefinition);
|
1 |
aurelien |
177 |
},
|
|
|
178 |
dataType: "JSON",
|
|
|
179 |
global: false
|
|
|
180 |
});
|
|
|
181 |
}
|
|
|
182 |
|
16 |
aurelien |
183 |
function getMotsADefinitionsEtRemplacer() {
|
|
|
184 |
if(motsAyantDefinition == null) {
|
|
|
185 |
getMotsADefinitions();
|
|
|
186 |
} else {
|
|
|
187 |
if(regexp_complete == null || regexp_thin == null) {
|
|
|
188 |
batirRegexp(motsAyantDefinition);
|
|
|
189 |
}
|
|
|
190 |
$(set).remplacerDefinitions(motsAyantDefinition);
|
|
|
191 |
}
|
|
|
192 |
}
|
|
|
193 |
|
17 |
raphael |
194 |
// update definitions set cle = trim(cle);
|
|
|
195 |
// select cle from definitions where cle like '%é' COLLATE 'utf8_bin';
|
|
|
196 |
// select cle from definitions where cle like '% ou %' COLLATE 'utf8_bin';
|
10 |
raphael |
197 |
function batirRegexp(mots) {
|
|
|
198 |
var restr = '';
|
|
|
199 |
$.each(mots, function(index, mot) {
|
|
|
200 |
if(restr != '') restr += '|';
|
|
|
201 |
|
17 |
raphael |
202 |
if(mot == 'ph') {
|
|
|
203 |
restr += 'ph[^.]';
|
|
|
204 |
}
|
|
|
205 |
else if(mot.length < 3) {
|
10 |
raphael |
206 |
restr += mot;
|
|
|
207 |
}
|
17 |
raphael |
208 |
// confluent => confluentes
|
|
|
209 |
else if(mot.substr(-2) == 'ts') {
|
|
|
210 |
restr += mot.slice(0, -1) + "e?s?";
|
|
|
211 |
}
|
|
|
212 |
else if(mot.substr(-3) == 'ore') {
|
|
|
213 |
restr += mot + "s?";
|
|
|
214 |
}
|
10 |
raphael |
215 |
// gestion masculin/féminin en "eux/euse"
|
|
|
216 |
else if(mot.substr(-1) == 'x') {
|
|
|
217 |
restr += mot.slice(0, -1) + "(?:x|se|ses)";
|
|
|
218 |
}
|
|
|
219 |
else if(mot.substr(-4) == 'euse') { // TODO: fix DB
|
|
|
220 |
restr += mot.slice(0, -2) + "(?:x|se|ses)";
|
|
|
221 |
}
|
17 |
raphael |
222 |
else if(mot.substr(-2) == 'ée') {
|
|
|
223 |
restr += mot.slice(0, -1) + "e?s?";
|
|
|
224 |
}
|
|
|
225 |
else if(mot.substr(-1) == 'é') {
|
|
|
226 |
restr += mot + "e?s?";
|
|
|
227 |
}
|
|
|
228 |
// verbe vs nom
|
|
|
229 |
else if(mot.substr(-4) == 'orme') {
|
|
|
230 |
restr += mot + "s?";
|
|
|
231 |
}
|
|
|
232 |
// verbe 3è p.pluriel en DB: ne devraient pas exister
|
|
|
233 |
// mais rampant => rampante
|
|
|
234 |
else if(mot.substr(-2) == 'nt') {
|
|
|
235 |
restr += mot + "e?s?";
|
|
|
236 |
}
|
|
|
237 |
else {
|
|
|
238 |
// moins de faux positifs:
|
|
|
239 |
// restr += mot + "(?:[ées]){0,2}";
|
|
|
240 |
// autrement:
|
10 |
raphael |
241 |
restr += mot + "\\w{0,2}";
|
17 |
raphael |
242 |
}
|
10 |
raphael |
243 |
});
|
|
|
244 |
|
|
|
245 |
regexp_complete = new RegExp("(?:\\s|^|\\W)(" + restr + ")\\b", "ig"); // accents
|
|
|
246 |
regexp_thin = new RegExp(exclureSpan + "(" + restr + ")\\b", "ig");
|
|
|
247 |
}
|
|
|
248 |
|
|
|
249 |
function formaterTemplateMotADefinition(motOriginal, motSimplifie) {
|
1 |
aurelien |
250 |
definitionHtml = '<span rel="'+motSimplifie+'" class="definition_term">'
|
10 |
raphael |
251 |
+motOriginal+
|
|
|
252 |
'</span>';
|
1 |
aurelien |
253 |
return definitionHtml;
|
|
|
254 |
}
|
|
|
255 |
|
|
|
256 |
function ajouterListenerDefinitions() {
|
23 |
killian |
257 |
$('.definition_term').on('mouseover mouseout', function(event) {
|
10 |
raphael |
258 |
if (event.type == 'mouseover') {
|
|
|
259 |
event.preventDefault();
|
|
|
260 |
afficherDefinition($(this));
|
|
|
261 |
} else {
|
|
|
262 |
cacherPopupsDefinitions();
|
|
|
263 |
}
|
1 |
aurelien |
264 |
});
|
|
|
265 |
}
|
|
|
266 |
|
|
|
267 |
function afficherDefinition(element) {
|
|
|
268 |
mot = element.attr('rel');
|
|
|
269 |
if(dictionnaire[mot] != null) {
|
3 |
aurelien |
270 |
element.after(formaterDefinition(element));
|
1 |
aurelien |
271 |
afficherPopupDefinition();
|
|
|
272 |
} else {
|
|
|
273 |
chargerDefinitionDistante(element);
|
|
|
274 |
}
|
|
|
275 |
}
|
|
|
276 |
|
3 |
aurelien |
277 |
function chargerDefinitionDistante(element) {
|
|
|
278 |
date = new Date();
|
5 |
aurelien |
279 |
mot = element.attr('rel');
|
1 |
aurelien |
280 |
$.ajax({
|
10 |
raphael |
281 |
url: URL_BASE_SERVICE+'def/'+mot, // provient de dictionnaire.config.js
|
1 |
aurelien |
282 |
success: function(data) {
|
|
|
283 |
retour = data;
|
|
|
284 |
definition = retour.valeur;
|
|
|
285 |
dictionnaire[mot] = definition;
|
3 |
aurelien |
286 |
element.after(formaterDefinition(element));
|
1 |
aurelien |
287 |
afficherPopupDefinition();
|
|
|
288 |
},
|
|
|
289 |
dataType: "JSON",
|
11 |
raphael |
290 |
global: false,
|
|
|
291 |
async: false
|
1 |
aurelien |
292 |
});
|
|
|
293 |
}
|
|
|
294 |
|
|
|
295 |
function afficherPopupDefinition() {
|
|
|
296 |
$(".definition_container").css({'top':mouseY + 20,'left':mouseX - 10}).fadeIn('slow');
|
|
|
297 |
}
|
|
|
298 |
|
|
|
299 |
function cacherPopupsDefinitions() {
|
|
|
300 |
$(".definition_container").remove();
|
|
|
301 |
}
|
|
|
302 |
|
|
|
303 |
function formaterDefinition(element) {
|
|
|
304 |
mot = element.attr('rel');
|
|
|
305 |
data = dictionnaire[mot];
|
|
|
306 |
defHtml = '<div class="definition_container">'+
|
10 |
raphael |
307 |
'<span class="definition_container_fleche"></span>'+
|
|
|
308 |
'<span class="definition">'+data+'</span>'+
|
|
|
309 |
'</div>';
|
1 |
aurelien |
310 |
return defHtml;
|
|
|
311 |
}
|
|
|
312 |
|
|
|
313 |
function supprimerToutesDefinitions() {
|
|
|
314 |
$('.definition_term').each(function() {
|
|
|
315 |
$(this).replaceWith($(this).html());
|
|
|
316 |
});
|
|
|
317 |
cacherPopupsDefinitions();
|
|
|
318 |
}
|
|
|
319 |
|
|
|
320 |
$(document).bind('mousemove', function(e){
|
|
|
321 |
mouseX = e.pageX;
|
|
|
322 |
mouseY = e.pageY - $(window).scrollTop();
|
|
|
323 |
});
|
|
|
324 |
|
14 |
aurelien |
325 |
$(document).ajaxStop(function() {
|
16 |
aurelien |
326 |
urlPage = $(this)[0].URL;
|
|
|
327 |
getMotsADefinitionsEtRemplacer();
|
14 |
aurelien |
328 |
});
|
|
|
329 |
|
1 |
aurelien |
330 |
$(document).ready(function() {
|
16 |
aurelien |
331 |
$.ajax({
|
|
|
332 |
url: URL_BASE_SERVICE + "../../../scripts/helpers.js",
|
|
|
333 |
dataType: "script",
|
|
|
334 |
global: false
|
|
|
335 |
});
|
|
|
336 |
getMotsADefinitionsEtRemplacer();
|
24 |
killian |
337 |
|
|
|
338 |
// I'm a dirty nasty hack, you should fix me hard
|
|
|
339 |
setTimeout(function() {ajouterListenerDefinitions()}, 2000);
|
1 |
aurelien |
340 |
});
|