Subversion Repositories Applications.dictionnaire

Rev

Rev 16 | Rev 19 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
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;
12 raphael 60
var set = '#zone-droite p, #zone-droite span:not(.definition_term), #zone-droite td, #zone-droite pre, #zone-droite div, #zone-droite 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
 
82
 
3 aurelien 83
function afficherLienDefinitions() {
84
	html = '<div id="conteneur_activation_definition"><a href="#">rechercher les définitions</a></div>';
85
	$('#conteneur_activation_definition').live('click', function(event) {
86
		event.preventDefault();
10 raphael 87
		//supprimerToutesDefinitions();
3 aurelien 88
		if(motsAyantDefinition == null) {
89
			getMotsADefinitions();
10 raphael 90
			ajouterListenerDefinitions();
3 aurelien 91
		} else {
7 raphael 92
			$(set).remplacerDefinitions(motsAyantDefinition);
3 aurelien 93
		}
94
	});
95
	$('body').append(html);
96
}
97
 
98
$.fn.remplacerDefinitions = function(mots) {
10 raphael 99
    this.each(function() {
100
        $(this).contents().filter(function() {
101
        	return (this.nodeType == 3 && ! (($(this).data("count") || 0) > max_passages));
102
        }).each(function() {
103
			$(this).data("count", ($(this).data("count") || 0) + 1);
104
			ttexte = texte = $(this).text();
16 aurelien 105
			matches_list = necessiteSubstitution(texte)[0];
10 raphael 106
			if(!matches_list) return true; // TODO: data("count") = max_passages ?
107
 
108
			for(i in matches_list) {
109
				couple = matches_list[i];
110
				mot_contextualise = couple[2];
111
				cle = texte_to_mot(mot_contextualise);
112
 
113
				if(! (ttexte = rechercherEtRemplacerMotParDefinition(texte, cle, couple))) break;
114
				texte = ttexte;
115
			}
116
			$(this).replaceWith(texte);
117
		});
118
	});
119
	return this;
1 aurelien 120
}
121
 
10 raphael 122
function rechercherEtRemplacerMotParDefinition(texte, mot, couple) {
123
	var full_contexte = couple[0],
124
	prefix_solo = couple[1],
125
	mot_contextualise = couple[2];
3 aurelien 126
 
10 raphael 127
	// cf exclureSpan
128
	if(new RegExp('((?:class="definition_term">)|(?:rel=")|(?:[çéèêîïô]))(' + mot + "\\w{0,2}" + ')' + "\\b", "ig").test(full_contexte)) return;
129
 
130
	templateMotADefinition = formaterTemplateMotADefinition(mot_contextualise, mot);
131
	bloc_replace = full_contexte.replace(mot_contextualise, templateMotADefinition);
132
	return texte.replace(full_contexte, bloc_replace);
3 aurelien 133
}
134
 
10 raphael 135
 
16 aurelien 136
function necessiteSubstitution(texte) {
10 raphael 137
	var liste = [], liste_complete = [];
138
 
139
	// regexp d'extraction du contexte, sans limite aux bords de "mot",
140
	// peu amener des faux positifs, et notamment ne pas matcher au mieux.
141
	// Eg: subsessiles match "sessile" et non "subsessile".
11 raphael 142
	while( (matches_c = regexp_thin.exec(texte)) ) liste.push(matches_c);
10 raphael 143
	if(liste.length == 0) return [null];
144
 
145
	// regexp de validation des bordure de mots, ne conserve que des matches correct
146
	// vis-à-vis de la bordure gauche
11 raphael 147
	while( (matches_c = regexp_complete.exec(texte)) ) liste_complete.push(matches_c[1]);
10 raphael 148
 
149
	// si une match n'est pas dans le tableau des mots autorisés (indexOf)
150
	for(var i = 0; i < liste.length; i++) {
151
		if($.inArray(liste[i][2], liste_complete) == -1) liste.splice(i, 1);
152
	}
153
 
154
	return [liste, liste_complete]
155
}
156
 
3 aurelien 157
function getMotsADefinitions() {
1 aurelien 158
	$.ajax({
10 raphael 159
		url: URL_BASE_SERVICE+'mots/', // provient de dictionnaire.config.js
1 aurelien 160
		success: function(data) {
161
			motsAyantDefinition = data;
10 raphael 162
			batirRegexp(motsAyantDefinition);
7 raphael 163
			$(set).remplacerDefinitions(motsAyantDefinition);
1 aurelien 164
		},
165
		dataType: "JSON",
166
		global: false
167
	});
168
}
169
 
16 aurelien 170
function getMotsADefinitionsEtRemplacer() {
171
	if(motsAyantDefinition == null) {
172
		getMotsADefinitions();
173
	} else {
174
		if(regexp_complete == null || regexp_thin == null) {
175
			batirRegexp(motsAyantDefinition);
176
		}
177
		$(set).remplacerDefinitions(motsAyantDefinition);
178
	}
179
}
180
 
17 raphael 181
// update definitions set cle = trim(cle);
182
// select cle from definitions where cle like '%é' COLLATE 'utf8_bin';
183
// select cle from definitions where cle like '% ou %' COLLATE 'utf8_bin';
10 raphael 184
function batirRegexp(mots) {
185
	var restr = '';
186
	$.each(mots, function(index, mot) {
187
		if(restr != '') restr += '|';
188
 
17 raphael 189
		if(mot == 'ph') {
190
			restr += 'ph[^.]';
191
		}
192
		else if(mot.length < 3) {
10 raphael 193
			restr += mot;
194
		}
17 raphael 195
		// confluent => confluentes
196
		else if(mot.substr(-2) == 'ts') {
197
			restr += mot.slice(0, -1) + "e?s?";
198
		}
199
		else if(mot.substr(-3) == 'ore') {
200
			restr += mot + "s?";
201
		}
10 raphael 202
		// gestion masculin/féminin en "eux/euse"
203
		else if(mot.substr(-1) == 'x') {
204
			restr += mot.slice(0, -1) + "(?:x|se|ses)";
205
		}
206
		else if(mot.substr(-4) == 'euse') { // TODO: fix DB
207
			restr += mot.slice(0, -2) + "(?:x|se|ses)";
208
		}
17 raphael 209
		else if(mot.substr(-2) == 'ée') {
210
			restr += mot.slice(0, -1) + "e?s?";
211
		}
212
		else if(mot.substr(-1) == 'é') {
213
			restr += mot + "e?s?";
214
		}
215
		// verbe vs nom
216
		else if(mot.substr(-4) == 'orme') {
217
			restr += mot + "s?";
218
		}
219
		// verbe 3è p.pluriel en DB: ne devraient pas exister
220
		// mais rampant => rampante
221
		else if(mot.substr(-2) == 'nt') {
222
			restr += mot + "e?s?";
223
		}
224
		else {
225
			// moins de faux positifs:
226
			// restr += mot + "(?:[ées]){0,2}";
227
			// autrement:
10 raphael 228
			restr += mot + "\\w{0,2}";
17 raphael 229
		}
10 raphael 230
	});
231
 
232
	regexp_complete = new RegExp("(?:\\s|^|\\W)(" + restr + ")\\b", "ig"); // accents
233
	regexp_thin = new RegExp(exclureSpan + "(" + restr + ")\\b", "ig");
234
}
235
 
236
function formaterTemplateMotADefinition(motOriginal, motSimplifie) {
1 aurelien 237
	definitionHtml = '<span rel="'+motSimplifie+'" class="definition_term">'
10 raphael 238
		+motOriginal+
239
		'</span>';
1 aurelien 240
	return definitionHtml;
241
}
242
 
243
function ajouterListenerDefinitions() {
244
	$('.definition_term').live('mouseover mouseout', function(event) {
10 raphael 245
		if (event.type == 'mouseover') {
246
			event.preventDefault();
247
			afficherDefinition($(this));
248
		} else {
249
			cacherPopupsDefinitions();
250
		}
1 aurelien 251
	});
252
}
253
 
254
function afficherDefinition(element) {
255
	mot = element.attr('rel');
256
	if(dictionnaire[mot] != null) {
3 aurelien 257
		element.after(formaterDefinition(element));
1 aurelien 258
		afficherPopupDefinition();
259
	} else {
260
		chargerDefinitionDistante(element);
261
	}
262
}
263
 
3 aurelien 264
function chargerDefinitionDistante(element) {
265
	date = new Date();
5 aurelien 266
	mot = element.attr('rel');
1 aurelien 267
	$.ajax({
10 raphael 268
		url: URL_BASE_SERVICE+'def/'+mot, // provient de dictionnaire.config.js
1 aurelien 269
		success: function(data) {
270
			retour = data;
271
			definition = retour.valeur;
272
			dictionnaire[mot] = definition;
3 aurelien 273
			element.after(formaterDefinition(element));
1 aurelien 274
			afficherPopupDefinition();
275
		},
276
		dataType: "JSON",
11 raphael 277
		global: false,
278
		async: false
1 aurelien 279
	});
280
}
281
 
282
function afficherPopupDefinition() {
283
	$(".definition_container").css({'top':mouseY + 20,'left':mouseX - 10}).fadeIn('slow');
284
}
285
 
286
function cacherPopupsDefinitions() {
287
	$(".definition_container").remove();
288
}
289
 
290
function formaterDefinition(element) {
291
	mot = element.attr('rel');
292
	data = dictionnaire[mot];
293
	defHtml = '<div class="definition_container">'+
10 raphael 294
		'<span class="definition_container_fleche"></span>'+
295
		'<span class="definition">'+data+'</span>'+
296
		'</div>';
1 aurelien 297
	return defHtml;
298
}
299
 
300
function supprimerToutesDefinitions() {
301
	$('.definition_term').each(function() {
302
		$(this).replaceWith($(this).html());
303
	});
304
	cacherPopupsDefinitions();
305
}
306
 
307
$(document).bind('mousemove', function(e){
308
	mouseX = e.pageX;
309
    mouseY = e.pageY - $(window).scrollTop();
310
});
311
 
14 aurelien 312
$(document).ajaxStop(function() {
16 aurelien 313
	urlPage = $(this)[0].URL;
314
	getMotsADefinitionsEtRemplacer();
14 aurelien 315
});
316
 
1 aurelien 317
$(document).ready(function() {
16 aurelien 318
	$.ajax({
319
		url: URL_BASE_SERVICE + "../../../scripts/helpers.js",
320
		dataType: "script",
321
		global: false
322
	});
323
	getMotsADefinitionsEtRemplacer();
10 raphael 324
	ajouterListenerDefinitions();
1 aurelien 325
});