Subversion Repositories Applications.dictionnaire

Rev

Rev 15 | Rev 17 | 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
 
10 raphael 181
function batirRegexp(mots) {
182
	var restr = '';
183
	$.each(mots, function(index, mot) {
184
		if(restr != '') restr += '|';
185
 
186
		if(mot.length < 3) {
187
			restr += mot;
188
		}
189
		// gestion masculin/féminin en "eux/euse"
190
		else if(mot.substr(-1) == 'x') {
191
			restr += mot.slice(0, -1) + "(?:x|se|ses)";
192
		}
193
		else if(mot.substr(-4) == 'euse') { // TODO: fix DB
194
			restr += mot.slice(0, -2) + "(?:x|se|ses)";
195
		}
196
		else
197
			restr += mot + "\\w{0,2}";
198
	});
199
 
200
	regexp_complete = new RegExp("(?:\\s|^|\\W)(" + restr + ")\\b", "ig"); // accents
201
	regexp_thin = new RegExp(exclureSpan + "(" + restr + ")\\b", "ig");
202
}
203
 
204
function formaterTemplateMotADefinition(motOriginal, motSimplifie) {
1 aurelien 205
	definitionHtml = '<span rel="'+motSimplifie+'" class="definition_term">'
10 raphael 206
		+motOriginal+
207
		'</span>';
1 aurelien 208
	return definitionHtml;
209
}
210
 
211
function ajouterListenerDefinitions() {
212
	$('.definition_term').live('mouseover mouseout', function(event) {
10 raphael 213
		if (event.type == 'mouseover') {
214
			event.preventDefault();
215
			afficherDefinition($(this));
216
		} else {
217
			cacherPopupsDefinitions();
218
		}
1 aurelien 219
	});
220
}
221
 
222
function afficherDefinition(element) {
223
	mot = element.attr('rel');
224
	if(dictionnaire[mot] != null) {
3 aurelien 225
		element.after(formaterDefinition(element));
1 aurelien 226
		afficherPopupDefinition();
227
	} else {
228
		chargerDefinitionDistante(element);
229
	}
230
}
231
 
3 aurelien 232
function chargerDefinitionDistante(element) {
233
	date = new Date();
5 aurelien 234
	mot = element.attr('rel');
1 aurelien 235
	$.ajax({
10 raphael 236
		url: URL_BASE_SERVICE+'def/'+mot, // provient de dictionnaire.config.js
1 aurelien 237
		success: function(data) {
238
			retour = data;
239
			definition = retour.valeur;
240
			dictionnaire[mot] = definition;
3 aurelien 241
			element.after(formaterDefinition(element));
1 aurelien 242
			afficherPopupDefinition();
243
		},
244
		dataType: "JSON",
11 raphael 245
		global: false,
246
		async: false
1 aurelien 247
	});
248
}
249
 
250
function afficherPopupDefinition() {
251
	$(".definition_container").css({'top':mouseY + 20,'left':mouseX - 10}).fadeIn('slow');
252
}
253
 
254
function cacherPopupsDefinitions() {
255
	$(".definition_container").remove();
256
}
257
 
258
function formaterDefinition(element) {
259
	mot = element.attr('rel');
260
	data = dictionnaire[mot];
261
	defHtml = '<div class="definition_container">'+
10 raphael 262
		'<span class="definition_container_fleche"></span>'+
263
		'<span class="definition">'+data+'</span>'+
264
		'</div>';
1 aurelien 265
	return defHtml;
266
}
267
 
268
function supprimerToutesDefinitions() {
269
	$('.definition_term').each(function() {
270
		$(this).replaceWith($(this).html());
271
	});
272
	cacherPopupsDefinitions();
273
}
274
 
275
$(document).bind('mousemove', function(e){
276
	mouseX = e.pageX;
277
    mouseY = e.pageY - $(window).scrollTop();
278
});
279
 
14 aurelien 280
$(document).ajaxStop(function() {
16 aurelien 281
	urlPage = $(this)[0].URL;
282
	getMotsADefinitionsEtRemplacer();
14 aurelien 283
});
284
 
1 aurelien 285
$(document).ready(function() {
16 aurelien 286
	$.ajax({
287
		url: URL_BASE_SERVICE + "../../../scripts/helpers.js",
288
		dataType: "script",
289
		global: false
290
	});
291
	getMotsADefinitionsEtRemplacer();
10 raphael 292
	ajouterListenerDefinitions();
1 aurelien 293
});