Subversion Repositories Applications.dictionnaire

Compare Revisions

No changes between revisions

Ignore whitespace Rev 1 → Rev HEAD

/scripts/helpers.js
New file
0,0 → 1,87
// cache les association "mot dans le texte" => "mot du dictionnaire"
// obtenues à l'aide de levenshtein() et texte_to_mot()
var assoc_mot_clef = new Array();
 
function texte_to_mot(mot) {
if(assoc_mot_clef[mot]) return assoc_mot_clef[mot];
 
var proche = '';
var lev = 99;
$.each(motsAyantDefinition, function(i, item) {
x = levenshtein(mot, item);
if(x < lev) { lev = x; proche = item; }
});
assoc_mot_clef[mot] = proche;
return proche;
}
 
function levenshtein (s1, s2) {
// http://kevin.vanzonneveld.net
// + original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
// + bugfixed by: Onno Marsman
// + revised by: Andrea Giammarchi (http://webreflection.blogspot.com)
// + reimplemented by: Brett Zamir (http://brett-zamir.me)
// + reimplemented by: Alexander M Beedie
// * example 1: levenshtein('Kevin van Zonneveld', 'Kevin van Sommeveld');
// * returns 1: 3
if (s1 == s2) {
return 0;
}
 
var s1_len = s1.length;
var s2_len = s2.length;
if (s1_len === 0) {
return s2_len;
}
if (s2_len === 0) {
return s1_len;
}
 
// BEGIN STATIC
var split = false;
try {
split = !('0')[0];
} catch (e) {
split = true; // Earlier IE may not support access by string index
}
// END STATIC
if (split) {
s1 = s1.split('');
s2 = s2.split('');
}
 
var v0 = new Array(s1_len + 1);
var v1 = new Array(s1_len + 1);
 
var s1_idx = 0,
s2_idx = 0,
cost = 0;
for (s1_idx = 0; s1_idx < s1_len + 1; s1_idx++) {
v0[s1_idx] = s1_idx;
}
var char_s1 = '',
char_s2 = '';
for (s2_idx = 1; s2_idx <= s2_len; s2_idx++) {
v1[0] = s2_idx;
char_s2 = s2[s2_idx - 1];
 
for (s1_idx = 0; s1_idx < s1_len; s1_idx++) {
char_s1 = s1[s1_idx];
cost = (char_s1 == char_s2) ? 0 : 1;
var m_min = v0[s1_idx + 1] + 1;
var b = v1[s1_idx] + 1;
var c = v0[s1_idx] + cost;
if (b < m_min) {
m_min = b;
}
if (c < m_min) {
m_min = c;
}
v1[s1_idx + 1] = m_min;
}
var v_tmp = v0;
v0 = v1;
v1 = v_tmp;
}
return v0[s1_len];
}
/scripts/dictionnaire.config.defaut.js
New file
0,0 → 1,0
var URL_BASE_SERVICE = 'http://localhost/dictionnaire/services/0.1/dictionnaire/';
/scripts/dictionnaire.css
1,11 → 1,10
.definition_term {
text-decoration: underline;
border-bottom: 1px solid black;
border-bottom: 1px dashed black;
position: relative;
}
 
.definition_term:hover {
background-color: yellow;
border-bottom: 2px solid black;
cursor: help;
}
 
12,12 → 11,21
.definition_container {
position: fixed;
height:auto;
background-color:#AAAAAA;
background-color:#F8FBF2;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
font-family:Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
font-size: 1.2em;
color: #333333;
padding:5px;
padding-bottom: 20px;
padding-top: 0px;
border:1px solid black;
z-index: 220;
z-index: 1000;
text-decoration: none;
width: 250px;
opacity: 0.95;
}
 
.definition_container_fleche {
27,5 → 35,22
left: 10px;
position: relative;
top: -20px;
z-index: 240;
z-index: 999;
border
}
 
.definition {
border-bottom: none;
text-decoration: none;
}
 
#conteneur_activation_definition {
position: fixed;
left: 0;
top: 0;
padding: 5px;
background-color: #EAEDCD;
border: 1px dotted black;
font-family: Arial,Helvetica,Verdana,sans-serif;
font-size: 0.85em;
}
/scripts/dictionnaire.js
1,114 → 1,266
/*
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 = new Array();
var noeudAyantsDefinition = new Array();
var motsAyantDefinition = null;
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;
 
function getUrlBaseService() {
return 'http://localhost/dictionnaire/services/0.1/dictionnaire/';
// 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 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;
};
$.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;
 
// 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)");
// 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;
}
 
function normaliserMotPourRecherche(str) {
str = supprimerAccents(str);
str = etendreChaineAccents(str);
return str;
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);
}
 
$.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 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]
}
 
function getMotADefinitions() {
function getMotsADefinitions() {
$.ajax({
url: getUrlBaseService()+'mots/',
url: URL_BASE_SERVICE+'mots/', // provient de dictionnaire.config.js
success: function(data) {
motsAyantDefinition = null;
motsAyantDefinition = data;
ajouterDefinitions(motsAyantDefinition);
batirRegexp(motsAyantDefinition);
$(set).remplacerDefinitions(motsAyantDefinition);
},
dataType: "JSON",
global: false
});
ajouterListenerDefinitions();
}
 
function ajouterDefinitions(motsAvecDefinitions) {
jQuery.each(motsAvecDefinitions, function(index) {
def = motsAvecDefinitions[index]['cle'];
$('p, span, td, th, div').remplacerDefinition(def);
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}";
}
});
 
regexp_complete = new RegExp("(?:\\s|^|\\W)(" + restr + ")\\b", "ig"); // accents
regexp_thin = new RegExp(exclureSpan + "(" + restr + ")\\b", "ig");
}
 
function formaterTemplateMotADefinition(motSimplifie, motOriginal) {
function formaterTemplateMotADefinition(motOriginal, motSimplifie) {
definitionHtml = '<span rel="'+motSimplifie+'" class="definition_term">'
+motOriginal+
'</span>';
+motOriginal+
'</span>';
return definitionHtml;
}
 
function ajouterListenerDefinitions() {
$('.definition_term').live('mouseover mouseout', function(event) {
if (event.type == 'mouseover') {
event.preventDefault();
afficherDefinition($(this));
} else {
cacherPopupsDefinitions();
}
$('.definition_term').on('mouseover mouseout', function(event) {
if (event.type == 'mouseover') {
event.preventDefault();
afficherDefinition($(this));
} else {
cacherPopupsDefinitions();
}
});
}
 
115,7 → 267,7
function afficherDefinition(element) {
mot = element.attr('rel');
if(dictionnaire[mot] != null) {
element.append(formaterDefinition(element));
element.after(formaterDefinition(element));
afficherPopupDefinition();
} else {
chargerDefinitionDistante(element);
122,18 → 274,21
}
}
 
function chargerDefinitionDistante(element) {
function chargerDefinitionDistante(element) {
date = new Date();
mot = element.attr('rel');
$.ajax({
url: getUrlBaseService()+'def/'+mot,
url: URL_BASE_SERVICE+'def/'+mot, // provient de dictionnaire.config.js
success: function(data) {
retour = data;
definition = retour.valeur;
dictionnaire[mot] = definition;
element.append(formaterDefinition(element));
element.after(formaterDefinition(element));
afficherPopupDefinition();
},
dataType: "JSON",
global: false
global: false,
async: false
});
}
 
149,9 → 304,9
mot = element.attr('rel');
data = dictionnaire[mot];
defHtml = '<div class="definition_container">'+
'<span class="definition_container_fleche"></span>'+
data+
'</div>';
'<span class="definition_container_fleche"></span>'+
'<span class="definition">'+data+'</span>'+
'</div>';
return defHtml;
}
 
162,21 → 317,24
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();
});
 
$(document).ajaxStop(function() {
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);
});
 
/scripts/fleche.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png