1,266 → 1,114 |
/* |
Surligneur de mot (glossaire) |
|
0) tout d'abord la liste des mots est récupée (getMotsADefinitions) et stockée dans motsAyantDefinition |
|
1) chaque mot de la liste subie une transformation progressive en pattern: |
* les mots de moins de 3 caractères sont inchangés (exemple: "nu") |
* les mots finissant en "eux/euse" sont généricisés |
* les autres mots se voit ajoutée la possibilité de matcher sur \\w{0,2} |
|
2) puis plusieurs RegExp sont baties à partir de la liste de pattern préliminaires |
* regexp_complete permet d'assurer que le mot: |
** commence en début de ligne |
** suit un espace |
** ne suit pas un caractère |
[ mais ne s'assure pas que le mot ne suit PAS un caractère accentué, cf notes à propos de \\b et l'UTF-8 ] |
|
* regexp_thin permet de s'assurer que le mot: |
** ne se trouve pas déjà encapsulé dans un <span rel="" class="definition_term">X</span> |
** ne se trouve pas comme attribut "rel" d'un <span rel="X" class="definition_term"></span> |
** n'est pas directement précédé d'un accent (parmis ceux utilisés dans la BDD, cf exclureSpan) |
** n'est pas directement précédé d'un caractère alphabétique |
Et tente d'extraire une partie de contexte significative (jusqu'à 24 caractère car initialement le |
test d'inclusion dans .definition_term était faire plus tardivement. |
|
3) remplacerDefinitions() prend la liste des nodes susceptibles de nécessiter un traitement |
et ignore ceux ayant déjà été traités [max_passages] fois (4 par défaut) |
|
4) pour chaque node: |
* le compteur de passage est incrémenté |
* necessiteSubstitution() est appellé |
|
== necessiteSubstitution() ==: |
** Utilise regexp_thin pour extraire les mots restant à définir ainsi qu'une partie de leur contexte. |
** Utilise regexp_complete pour extraire les mots au plus proche de leur bodures avec un moindre risque d'erreur |
** exlue les faux positifs possibles de regexp_thin |
** renvoi les matches de regexp_thin (qui présente plus de contexte) |
============================= |
|
* pour chaque correspondance retournée par necessiteSubstitution(), |
le mot d'origine (tel que dans motsAyantDefinition) |
est déterminé en utilisant le levenstein (un cache est mis en place, cf texte_to_mot() dans helpers.js). |
|
* rechercherEtRemplacerMotParDefinition() est appelée pour effectuer la substitution dans le node |
|
|
Contraintes rencontrées: |
1) exclure les mots déjà défini via la regexp général plutôt qu'au coup par coup. |
2) vérifier les bordures |
3) vérifier bordure d'accents: |
\\b(é) dans une regexp ne peut matcher une chaîne JS UTF-8 |
http://stackoverflow.com/questions/2881445/utf-8-word-boundary-regex-in-javascript |
*/ |
|
var dictionnaire = new Array(); |
var motsAyantDefinition = null; |
var motsAyantDefinition = new Array(); |
var noeudAyantsDefinition = new Array(); |
var mouseX = null; |
var mouseY = null; |
var active = false; |
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'; |
var urlPage = window.location.href; |
|
// Note: utiliser \\b plutôt que \\W pour matcher les bordures de mots |
// en incluant début et fin de ligne |
var regexp_complete = null; |
var regexp_thin = null; |
|
// nombre maximum de passages de la détection sur un node donné |
var max_passages = 4; |
|
// echo $(mysql<<<"SELECT cle FROM definitions"|tr "[a-z,-]" o|sed -e 's/o//g' -e "s/\(.\)/\1\n/g"|sort -u) |
// ç é è ê î ï ô (2013/07/03) |
// TODO: http://xregexp.com/plugins/ |
var exclureSpan = '([^' + |
'(?:class="definition_term">)' + // mot contextualisé déjà dictionnarisé |
'(?:rel=")' + // la valeur du rel="" d'un mot déjà dictionnarisé |
'çéèêîïô' + // les accents, non-traités dans la regexp générale et qui ne doivent pas |
// être pris pour des bordures de mots |
'\\w' + |
']|^){1,24}'; |
|
// sert à filtrer plus rapidement les nodes inutiles pour l'application |
// du dictionnaire |
var exclureBlancs = new RegExp('^\\s*$'); |
|
function afficherLienDefinitions() { |
html = '<div id="conteneur_activation_definition"><a href="#">rechercher les définitions</a></div>'; |
$('#conteneur_activation_definition').on('click', function(event) { |
event.preventDefault(); |
//supprimerToutesDefinitions(); |
if(motsAyantDefinition == null) { |
getMotsADefinitions(); |
ajouterListenerDefinitions(); |
} else { |
$(set).remplacerDefinitions(motsAyantDefinition); |
} |
}); |
$('body').append(html); |
function getUrlBaseService() { |
return 'http://localhost/dictionnaire/services/0.1/dictionnaire/'; |
} |
|
$.fn.remplacerDefinitions = function(mots) { |
this.each(function() { |
$(this).contents().filter(function() { |
return (this.nodeType == 3 && ! (($(this).data("count") || 0) > max_passages)); |
}).each(function() { |
// évite les nodes dont le texte est undefined (dumb example <script>) |
if(!$(this).text()) return true; |
// évite les nodes dont le texte est vide ou blanc |
ttexte = texte = $(this).text(); |
if(exclureBlancs.test(texte)) return true; |
function supprimerAccents(str) { |
var rExps=[ |
{re:/[\xC0-\xC6]/g, ch:'A'}, |
{re:/[\xE0-\xE6]/g, ch:'a'}, |
{re:/[\xC8-\xCB]/g, ch:'E'}, |
{re:/[\xE8-\xEB]/g, ch:'e'}, |
{re:/[\xCC-\xCF]/g, ch:'I'}, |
{re:/[\xEC-\xEF]/g, ch:'i'}, |
{re:/[\xD2-\xD6]/g, ch:'O'}, |
{re:/[\xF2-\xF6]/g, ch:'o'}, |
{re:/[\xD9-\xDC]/g, ch:'U'}, |
{re:/[\xF9-\xFC]/g, ch:'u'}, |
{re:/[\xD1]/g, ch:'N'}, |
{re:/[\xF1]/g, ch:'n'} ]; |
for(var i=0, len=rExps.length; i<len; i++) |
str=str.replace(rExps[i].re, rExps[i].ch); |
return str; |
}; |
|
// incrémente le compteur de passage |
/* TODO: cette ligne cause un problème à IE-8 |
(peut-être uniquement relié au onChange() en cas de passage sur |
des composants ajoutés postérieurement au chargement initial de la |
page via AJAX. à tester. */ |
$(this).data("count", ($(this).data("count") || 0) + 1); |
matches_list = necessiteSubstitution(texte)[0]; |
if(!matches_list) return true; // TODO: data("count") = max_passages ? |
|
for(i in matches_list) { |
couple = matches_list[i]; |
mot_contextualise = couple[2]; |
cle = texte_to_mot(mot_contextualise); |
|
if(! (ttexte = rechercherEtRemplacerMotParDefinition(texte, cle, couple))) break; |
texte = ttexte; |
} |
$(this).replaceWith(texte); |
}); |
}); |
return this; |
// c'est moche mais le mauvais support de l'unicode dans |
// javascript ne me permet pas de faire mieux |
function etendreChaineAccents(str) { |
return str.replace('a',"(a|à|á|â|ã|ä|å)") |
.replace('e',"(e|è|é|ê|ë)") |
.replace('i',"(i|ì|í|î|ï)") |
.replace('o',"(o|ò|ó|ô|õ|ö)") |
.replace('u',"(u|ù|ú|û|ü)") |
.replace('y',"(ýÿ)") |
.replace('a',"(a|à|á|â|ã|ä|å)") |
.replace('æ',"(ae|æ)") |
.replace('ç',"(ç|c)") |
.replace('ñ',"(ñ|n)") |
.replace('œ',"(œ|oe)"); |
} |
|
function rechercherEtRemplacerMotParDefinition(texte, mot, couple) { |
var full_contexte = couple[0], |
prefix_solo = couple[1], |
mot_contextualise = couple[2]; |
|
// cf exclureSpan |
if(new RegExp('((?:class="definition_term">)|(?:rel=")|(?:[çéèêîïô]))(' + mot + "\\w{0,2}" + ')' + "\\b", "ig").test(full_contexte)) return; |
|
templateMotADefinition = formaterTemplateMotADefinition(mot_contextualise, mot); |
bloc_replace = full_contexte.replace(mot_contextualise, templateMotADefinition); |
return texte.replace(full_contexte, bloc_replace); |
function normaliserMotPourRecherche(str) { |
str = supprimerAccents(str); |
str = etendreChaineAccents(str); |
return str; |
} |
|
|
function necessiteSubstitution(texte) { |
var liste = [], liste_complete = []; |
|
// regexp d'extraction du contexte, sans limite aux bords de "mot", |
// peu amener des faux positifs, et notamment ne pas matcher au mieux. |
// Eg: subsessiles match "sessile" et non "subsessile". |
while( (matches_c = regexp_thin.exec(texte)) ) liste.push(matches_c); |
if(liste.length == 0) return [null]; |
|
// regexp de validation des bordure de mots, ne conserve que des matches correct |
// vis-à-vis de la bordure gauche |
while( (matches_c = regexp_complete.exec(texte)) ) liste_complete.push(matches_c[1]); |
|
// si une match n'est pas dans le tableau des mots autorisés (indexOf) |
for(var i = 0; i < liste.length; i++) { |
if($.inArray(liste[i][2], liste_complete) == -1) liste.splice(i, 1); |
} |
|
return [liste, liste_complete] |
$.fn.remplacerDefinition = function(mot) { |
|
motSimplifie = supprimerAccents(mot).toLowerCase(); |
mot = normaliserMotPourRecherche(mot); |
|
var regExp = new RegExp("("+mot+")", 'ig'); |
this.each(function() { |
$(this).contents().filter(function() { |
return this.nodeType == Node.TEXT_NODE; |
}).each(function() { |
termeDansTexte = regExp.exec($(this).text()); |
if(termeDansTexte != null && termeDansTexte.length > 0) { |
motOriginal = termeDansTexte[0]; |
templateMotADefinition = formaterTemplateMotADefinition(motSimplifie, motOriginal); |
output = $(this).text().replace(regExp, definitionHtml); |
$(this).replaceWith(output); |
noeudAyantsDefinition.push($(this)); |
} |
}); |
}); |
return this; |
} |
|
function getMotsADefinitions() { |
function getMotADefinitions() { |
$.ajax({ |
url: URL_BASE_SERVICE+'mots/', // provient de dictionnaire.config.js |
url: getUrlBaseService()+'mots/', |
success: function(data) { |
motsAyantDefinition = null; |
motsAyantDefinition = data; |
batirRegexp(motsAyantDefinition); |
$(set).remplacerDefinitions(motsAyantDefinition); |
ajouterDefinitions(motsAyantDefinition); |
}, |
dataType: "JSON", |
global: false |
}); |
|
ajouterListenerDefinitions(); |
} |
|
function getMotsADefinitionsEtRemplacer() { |
if(motsAyantDefinition == null) { |
getMotsADefinitions(); |
} else { |
if(regexp_complete == null || regexp_thin == null) { |
batirRegexp(motsAyantDefinition); |
} |
$(set).remplacerDefinitions(motsAyantDefinition); |
} |
} |
|
// update definitions set cle = trim(cle); |
// select cle from definitions where cle like '%é' COLLATE 'utf8_bin'; |
// select cle from definitions where cle like '% ou %' COLLATE 'utf8_bin'; |
function batirRegexp(mots) { |
var restr = ''; |
$.each(mots, function(index, mot) { |
if(restr != '') restr += '|'; |
|
if(mot == 'ph') { |
restr += 'ph[^.]'; |
} |
else if(mot.length < 3) { |
restr += mot; |
} |
// confluent => confluentes |
else if(mot.substr(-2) == 'ts') { |
restr += mot.slice(0, -1) + "e?s?"; |
} |
else if(mot.substr(-3) == 'ore') { |
restr += mot + "s?"; |
} |
// gestion masculin/féminin en "eux/euse" |
else if(mot.substr(-1) == 'x') { |
restr += mot.slice(0, -1) + "(?:x|se|ses)"; |
} |
else if(mot.substr(-4) == 'euse') { // TODO: fix DB |
restr += mot.slice(0, -2) + "(?:x|se|ses)"; |
} |
else if(mot.substr(-2) == 'ée') { |
restr += mot.slice(0, -1) + "e?s?"; |
} |
else if(mot.substr(-1) == 'é') { |
restr += mot + "e?s?"; |
} |
// verbe vs nom |
else if(mot.substr(-4) == 'orme') { |
restr += mot + "s?"; |
} |
// verbe 3è p.pluriel en DB: ne devraient pas exister |
// mais rampant => rampante |
else if(mot.substr(-2) == 'nt') { |
restr += mot + "e?s?"; |
} |
else { |
// moins de faux positifs: |
// restr += mot + "(?:[ées]){0,2}"; |
// autrement: |
restr += mot + "\\w{0,2}"; |
} |
function ajouterDefinitions(motsAvecDefinitions) { |
jQuery.each(motsAvecDefinitions, function(index) { |
def = motsAvecDefinitions[index]['cle']; |
$('p, span, td, th, div').remplacerDefinition(def); |
}); |
|
regexp_complete = new RegExp("(?:\\s|^|\\W)(" + restr + ")\\b", "ig"); // accents |
regexp_thin = new RegExp(exclureSpan + "(" + restr + ")\\b", "ig"); |
} |
|
function formaterTemplateMotADefinition(motOriginal, motSimplifie) { |
function formaterTemplateMotADefinition(motSimplifie, motOriginal) { |
definitionHtml = '<span rel="'+motSimplifie+'" class="definition_term">' |
+motOriginal+ |
'</span>'; |
+motOriginal+ |
'</span>'; |
return definitionHtml; |
} |
|
function ajouterListenerDefinitions() { |
$('.definition_term').on('mouseover mouseout', function(event) { |
if (event.type == 'mouseover') { |
event.preventDefault(); |
afficherDefinition($(this)); |
} else { |
cacherPopupsDefinitions(); |
} |
$('.definition_term').live('mouseover mouseout', function(event) { |
if (event.type == 'mouseover') { |
event.preventDefault(); |
afficherDefinition($(this)); |
} else { |
cacherPopupsDefinitions(); |
} |
}); |
} |
|
267,7 → 115,7 |
function afficherDefinition(element) { |
mot = element.attr('rel'); |
if(dictionnaire[mot] != null) { |
element.after(formaterDefinition(element)); |
element.append(formaterDefinition(element)); |
afficherPopupDefinition(); |
} else { |
chargerDefinitionDistante(element); |
274,21 → 122,18 |
} |
} |
|
function chargerDefinitionDistante(element) { |
date = new Date(); |
mot = element.attr('rel'); |
function chargerDefinitionDistante(element) { |
$.ajax({ |
url: URL_BASE_SERVICE+'def/'+mot, // provient de dictionnaire.config.js |
url: getUrlBaseService()+'def/'+mot, |
success: function(data) { |
retour = data; |
definition = retour.valeur; |
dictionnaire[mot] = definition; |
element.after(formaterDefinition(element)); |
element.append(formaterDefinition(element)); |
afficherPopupDefinition(); |
}, |
dataType: "JSON", |
global: false, |
async: false |
global: false |
}); |
} |
|
304,9 → 149,9 |
mot = element.attr('rel'); |
data = dictionnaire[mot]; |
defHtml = '<div class="definition_container">'+ |
'<span class="definition_container_fleche"></span>'+ |
'<span class="definition">'+data+'</span>'+ |
'</div>'; |
'<span class="definition_container_fleche"></span>'+ |
data+ |
'</div>'; |
return defHtml; |
} |
|
317,24 → 162,21 |
cacherPopupsDefinitions(); |
} |
|
function surFinRequeteAjax() { |
supprimerToutesDefinitions(); |
ajouterDefinitions(motsAyantDefinition); |
} |
|
$(document).bind('mousemove', function(e){ |
mouseX = e.pageX; |
mouseY = e.pageY - $(window).scrollTop(); |
}); |
|
$(document).ready(function() { |
getMotADefinitions(); |
}); |
|
$(document).ajaxStop(function() { |
urlPage = $(this)[0].URL; |
getMotsADefinitionsEtRemplacer(); |
t = setTimeout(function(){surFinRequeteAjax()},800) |
}); |
|
$(document).ready(function() { |
$.ajax({ |
url: URL_BASE_SERVICE + "../../../scripts/helpers.js", |
dataType: "script", |
global: false |
}); |
getMotsADefinitionsEtRemplacer(); |
|
// I'm a dirty nasty hack, you should fix me hard |
setTimeout(function() {ajouterListenerDefinitions()}, 2000); |
}); |