Vous avez reçu un message "Your GitLab account has been locked ..." ? Pas d'inquiétude : lisez cet article https://docs.gricad-pages.univ-grenoble-alpes.fr/help/unlock/

Commit 1a39e2c6 authored by Sylvain Coulange's avatar Sylvain Coulange
Browse files

Ajout fonctionnalité liaisons développées par ProPro IdL 2021

parent 4543214a
from . import textPhonographer
import spacy
derniere_lettre = ['s', 'x', 'z', 't', 'd', 'n', 'p', 'f', 'r', 'g']
# Cette fonction va rechercher dans un texte brut s'il y a une locution.
# Si oui, on examine chaque token du texte pour savoir à quel indice se trouve cette
# locution et à quel indice la liaison se trouve
# entrée : texte brut + texte spacy
# sortie : index de la locution + index de la liaison (ou None) + type de la liaison ('O' ou 'F')
def liaison_locution(text, nlpText):
# liste des locutions avec exceptions (à compléter)
# clé : la locution
# valeur : le mot après lequel se fait la liaison (0 est le premier mot, 1 le deuxième, etc)
locutions = {"comment allez vous" : (0, 'O'),
"quand est ce" : (0, 'O'),
"de mieux en mieux" : (1, 'O'),
"petit à petit" : (0, 'O'),
"plus ou moins" : (0, 'O'),
"bien au contraire" : (0, 'O'),
"de plus en plus" : (1, 'O'),
"de temps en temps" : (1, 'O'),
"mot à mot" : (0, 'O'),
"pas à pas" : (0, 'O'),
"plus avant" : (0, 'O'),
"qui plus est" : (1, 'O'),
"pret à porter" : (0, 'O'),
"sous entendu" : (0, 'O'),
"pot au feu" : (0, 'O'),
"avant hier" : (0, 'O'),
"pied à terre" : (0, 'O'),
"non avenu" : (0, 'O'),
"nuit et jour" : (0, 'O'),
"vis à vis" : (0, 'O'),
"états unis" : (0, 'O'),
"de but en blanc" : (1, 'O'),
"de fond en comble" : (1, 'O'),
"moyen âge" : (0, 'O'),
"moyen Orient" : (0, 'O'),
"les beaux arts" : (1, 'O'),
"arts et métiers" : (0, 'O'),
"c'est à dire" : (1, 'O'),
"tout à fait" : (0, 'O'),
"tout à coup" : (0, 'O'),
"en effet" : (0, 'O'),
"à tout hasard" : (1, 'O'),
"peut être" : (0, 'O'),
"au fait et au prendre" : (1, 'O'),
"bien avant" : (0, 'O'),
"but à but" : (0, 'O'),
"vous et moi" : (0, 'O'),
"corps et âme" : (0, 'O'),
"corps et bien" : (0, 'O'),
"de bas en haut" : (1, 'O'),
"de fond en comble" : (1, 'O'),
"de haut en bas" : (1, 'O'),
"de temps à autre" : (1, 'O'),
"de moins en moins" : (1, 'O'),
"dès à présent" : (0, 'O'),
"du tout au tout" : (1, 'O'),
"pieds et poings liés" : (0, 'O'),
"rien à" : (0, 'O'),
"quand et quand" : (0, 'O'),
"de but en blanc" : (1, 'O'),
"bien être" : (0, 'O'),
"faits et gestes" : (1, 'O'),
####FAC
"d'ores et déjà" : (1, 'F'),
"pas à pas" : (0, 'F'),
"dos à dos" : (0, 'F'),
"avec armes et bagages" : (1, 'F'),
"de part et d'autre" : (1, 'F'),
"d'un bout à l'autre" : (2, 'F'),
"par monts et par vaux" : (1, 'F')}
locution = None
## vérifier si une locution est présente
# voir la dependance des mots d'apres
for cle in locutions.keys():
if cle in text.lower():
locution = cle
testloc = ""
if locution != None :
for j, token in enumerate(nlpText):
locution2 = locution.replace(' ', '') # chaîne sans espace pour éviter les problèmes de tokenization
# vérifier si le premier mot correspond au début d'une locution
if token.text.lower() == locution[:len(token.text)] and testloc=="":
testloc+=token.text.lower() # si oui on l'ajoute dans testloc (sans espace)
index_locution = j # enregistre où commence la locution
elif token.text.lower() in locution and testloc!="":
testloc+=token.text.lower()
# si la variable de test correspond à la locution cherchée on retourne les indices
if testloc == locution2:
index_liaison = locutions[locution][0]
liaison = locutions[locution][1]
# cas de "comment allez-vous" suivi d'une prep ou d'un verbe : il n'y a pas de liaison
if len(nlpText) > 3 and locution == "comment allez vous" and nlpText[j+1].pos_ in ['VERB', 'ADP'] :
return None
return index_locution, index_liaison, liaison
else:
testloc=""
else:
return None
#Fonction pour tester la liaison d'une suite de 2 mots
#en entree : 2 mots
#en sortie : un tuple de 2 mots, sinon NONE
def obtenirM1M2(mot1, mot2):
lettre_voy = ['a','à','â','é','è','ê','ë','e','i','î','ï','o','ô','u','û','ù','ü','y','ÿ','œ']
word2trans = textPhonographer.word2transFr # dic
if mot1[-1] in lettre_voy :
return None
elif mot1[-1] in derniere_lettre :
# voir si la première lettre de mot2 est une lettre voyelle
if mot2[0].lower() in lettre_voy:
# avec les mots en 'y' on ne fait souvent pas la liaison ('un yaourt', 'un yacht', etc)
if mot2[0].lower() == 'y':
if mot2 in ['yeux','y']: # à compléter ?
return (mot1, mot2)
else:
return None
# print("Le mot2 qui commence par une lettre voyelle : "+mot2)
return (mot1, mot2)
# voir si la première lettre de mot2 est "h"
elif mot2[0].lower() == 'h' and mot2.lower() in word2trans and "*" not in word2trans[mot2.lower()][0] :
# print("Le mot2 qui commence par 'h' muet : " + mot2)
return (mot1, mot2)
else:
# print("Le mot2 qui n'a pas de liaison : " + mot2)
return None
#Fonction pour les exceptions
#en entree : 2 tokens
#en sortie : booleen
def exception(token1, token2):
mots = ["oui", "ouf"]
lettre = ['H','O','E','I','U','É', 'È','Œ','Â','Ê','Î','Ô','Û','Ä','Ë','Ï','Ö','Ü','Æ','Ù','â','é','è','ê','ë','e','i','î','ï','o','ô','u','û','ù','ü','ÿ','œ','æ']
if token2.text in lettre or token2.text.lower() in mots:
print ("Cas1 except : pas de liaison")
return False
elif token2.pos_ == "PROPN":
print ("Cas2 except : pas de liaison")
return False
elif token1.text.lower() == "quand" and token2.text.lower() == "est":
print ("Cas3 except : pas de liaison")
return False
elif token1.text.lower() == "comment" and token2.text.lower() == "allez":
print ("Cas4 except : pas de liaison")
return False
else:
return True
#Fonction pour les mots qui se terminent en g / f / r
#en entree : 1 mot
#en sortie : booleen
def gfr(mot1):
lettre_gfr = ['g', 'f', 'r']
if mot1.text[-1:] in lettre_gfr:
if mot1.text[-2:] == "er":
print("cas er")
return True
elif mot1.text.lower() == "neuf":
print("cas neuf")
return True
elif mot1.text.lower() == "long":
print("cas long")
return True
else:
return False
print("fini par gfr, mais pas le cas")
else:
return True
# C'est possible que le token1 ou le token2 est num, mais il n'est pas sûr que le pos de "num" sera identifié. donc on utilise la liate
# Par exemple, "un" est toujours "DET"
# Fonction pour identifier la liaison avec des nombres
# en entrée : 2 tokens
# en sortie : "O" (obligatoire), "F" (facultative), "N" (neuf) ou NONE
def liaison_nombre(token1,token2):
mot_excep = ['ans','heures','hommes','autres']
mois = ['avril', 'août', 'octobre']
# Quand le nombre se trouve devant le mois, on ne fait pas de liaison
# Exemple : le trois avril
if token2.text.lower() in mois:
print("Pas de liaison car 'mois'", token1.text, 'et', token2.text)
return None
# Devant "onze" ou "onzième", pas de liaison
elif token2.text.lower() == "onze" or token2.text.lower() == "onzième" or token2.text.lower() == "onzièmes":
print("Pas de liaison car 'onze'", token1.text, 'et', token2.text)
return None
# Après "vingt" ou "cent"
elif token1.text.lower() == 'vingts' or token1.text.lower() == 'vingt' or token1.text.lower() == 'cent' or token1.text.lower() == 'cents':
if token2.pos_ == "NOUN" or token2.text.lower() == "et":
print("Liaison ok nb1 ",token1.text,'et',token2.text)
return "O"
else:
print("Pas de liaison nb1",token1.text,'et',token2.text)
return None
# Après "un"
elif token1.text.lower() == 'un':
if token2.pos_ == 'NOUN' or token2.pos_ == 'ADJ':
if token2.text.lower() not in mois :
print("Liaison ok nb2 ", token1.text, 'et', token2.text)
return "O"
else:
print("Pas de liaison nb2 ", token1.text, 'et', token2.text)
return None
else:
print("Pas de liaison nb3 ", token1.text, 'et', token2.text)
return None
# Quand "neuf" rencontre les mots dans la liste, la prononciation sera changée
elif token1.text.lower() == "neuf" and token2.text.lower() in mot_excep:
print("La pronciation de "+token1.text+" est [nœv]")
return "N"
# Quand le chiffre est employé comme pronom, on fait pas de liaision
# EXemple : j'ai connu quatre personnes, et trois y demeurent toujours. dep_--> nsubj
# Exemple : mon fils a eu un deux en anglais pos_--> PRON
elif "subj" in token1.dep_ or token1.pos_ == "PRON" or "subj" in token2.dep_ or token2.pos_ == "PRON":
print("Pas de liaison car 'PRON'",token1.text,'et',token2.text)
return None
else:
if token1.text.lower() != "neuf":
type = obl_facul(token1,token2)
return type
# Fonction pour vérifier les règles grammaticales de liaison
# entrée : 2 tokens
# sortie : F ou O ou None
def obl_facul(token1, token2):
# règles obligatoires
reg_obl_cat = {'DET':['NOUN','ADJ','PRON'],'PRON':['PRON','VERB','AUX'],'ADJ':['NOUN'],'NUM':['NOUN','ADJ']}
reg_obl_mot = {'quand':['PRON'],'très':['ADJ'],'trop':['ADJ'],'tout':['ADJ','ADP']}
reg_obl_prep = ['dans','chez','en','sans','sous','dès','des','aux']
# règles facultatives
reg_fac_cat = {'NOUN':['ADJ'],'ADV':['ADJ'],'VERB':['VERB','ADJ','ADV','ADP','AUX']}
reg_fac_mot = ['pas','mais','avoir','être']
reg_fac_prep = ['après','avant','depuis','pendant']
if 'Tense=Pres|VerbForm=Part' in token1.tag_: # liaison après un participe présent
return "F"
elif token1.pos_ in reg_obl_cat.keys():
reg_obl_cat_lis = reg_obl_cat[token1.pos_] # la liste correspondante du tableau reg_obl_cat
if token2.pos_ in reg_obl_cat_lis:
print("Cas1 obligatoire : "+token1.pos_, token2.pos_)
return "O"
else:
print("Pas de liaison1 : "+token1.pos_, token2.pos_)
return None
elif token1.text.lower() in reg_obl_mot.keys():
reg_obl_mot_lis = reg_obl_mot[token1.text] # la liste correspondante du tableau reg_obl_mot
if token2.pos_ in reg_obl_mot_lis:
print("Cas2 obligatoire : "+token1.text, token2.pos_)
return "O"
else:
print("Pas de liaison2 : "+token1.pos_, token2.pos_)
return None
elif token1.pos_=='VERB' and token2.pos_ == 'PRON':
print("Cas3 obligatoire : "+token1.pos_, token2.pos_)
return "O"
elif token1.lemma_ in reg_fac_mot:
print("Cas1 facultatif : " + token1.pos_, token2.pos_)
return "F"
elif token1.pos_ in reg_fac_cat.keys():
if token1.pos_ == 'NOUN' and 'Number=Plur' in token1.tag_: # vérifier si le nom est au pluriel
reg_fac_cat_lis = reg_fac_cat[token1.pos_]
if token2.pos_ in reg_fac_cat_lis:
print("Cas2 facultatif : " + token1.pos_, token2.pos_)
return "F"
elif token1.pos_ != 'NOUN': # cas des verbes ou des adverbes
reg_fac_cat_lis = reg_fac_cat[token1.pos_]
if token2.pos_ in reg_fac_cat_lis:
print("Cas3 facultatif : " + token1.pos_, token2.pos_)
return "F"
else:
print("Pas de liaison3 : "+token1.pos_, token2.pos_)
return None
elif token1.pos_ == 'ADP': # cas des prepositions
if token1.text.lower() in reg_obl_prep:
print ("Cas4 obligatoire : "+token1.text, token2.text)
return "O"
elif token1.text.lower() in reg_fac_prep:
print ("Cas4 facultatif : "+token1.text, token2.text)
return "F"
else :
print("Pas de liaison4 : " + token1.text, token2.text)
return None
else:
print("Pas de liaison5 : "+token1.pos_, token2.pos_)
return None
# Fonction pour intégrer toutes les fonctions établies pour nous déterminer la liaison
# entrée : 2 tokens
# sortie : F ou O ou None
def verifliaison(token1, token2):
num = ['un', 'une', 'deux', 'trois', 'onze', 'onzième','onzièmes', 'neuf', 'vingt', 'vingts', 'cent', 'cents']
if token1.text.lower() in num or token2.text.lower() in num:
if exception(token1, token2):
if gfr(token1):
type = liaison_nombre(token1, token2)
else:
type = None
else:
type = None
else:
if exception(token1,token2):
if gfr(token1):
type = obl_facul(token1, token2)
else:
type = None
else:
type = None
return type
# Fonction pour determiner le phoneme de liaison
# en entree : une lettre
# en sortie : un str
def phon_liaison(lettre):
l_p = {'s':'phon_z', 'x':'phon_z', 'z':'phon_z', 't':'phon_t', 'd':'phon_t', 'n':'phon_n', 'p':'phon_p', 'r':'phon_r', 'g':'phon_g'}
if lettre in l_p.keys():
phon = l_p[lettre]
else:
phon = "rien"
return phon
# Fonction pour la denasalisation
#entree : la transcription du mot (str)
#sortie : la transcription à jour (str)
def denasal (trans):
if trans[-1] == '̃' :
trans = trans[:-1]
trans += 'n'
return trans
......@@ -10,6 +10,7 @@ pylib += [os.path.relpath(r'../phon2graph')]
from phon2graph_french import decoupage
from phon2graph_english import decoupageEn # ENGLISH
from phon2graph_mandarin import pinyin2phon # MANDARIN CHINESE
from .liaisons import *
# FICHIERS
phonColFile = "../phon2graph/data/api2class.json"
......@@ -179,7 +180,7 @@ with open(api2classFile) as inFile:
######### LISTE DES FONCTIONS ##########
########################################
def traitement(mot, lang):
def traitement(mot, lang, liaison): # LIAISON : avec le caractere liaison en argument ('O', 'F', 'N' ou None)
if lang == "fr":
word2trans = word2transFr
phon2graph = phon2graphFr
......@@ -204,7 +205,20 @@ def traitement(mot, lang):
# Si mot dans dictionnaire... decoupage(mot,transcription)
if re.match(r'^\W+$|^\d+$',mot):
print("'", mot, "' n'est pas un mot.")
result = [([('phon_neutre',mot)],"",mot,"")]
#verifier la liaison
if '‿' in mot or "-͜" in mot :
phon = phon_liaison(liaison[-2])
if liaison[-1] == "O" :
result = [([(phon, mot)], "", mot, "Liaison obligatoire")]
elif liaison[-1] == "F" :
result = [([(phon, mot)], "", mot, "Liaison facultative")]
elif liaison[-1] == "N" :
phon = "v"
result = [([(phon, mot)], "", mot, "Liaison obligatoire")]
else :
result = [([('phon_neutre',mot)],"",mot,"")]
elif mot in word2trans.keys():
print("'", mot, "' trouvé dans le dico !",word2trans[mot])
if lang == "fr":
......@@ -216,6 +230,16 @@ def traitement(mot, lang):
if len(trans)>0 and trans[0] == "/":
transList.append((trans.replace("/",""),loc))
result = []
############ partie d'appel de la fonction denasalisation
if liaison != None :
liste = ["aucun", "bien", "en", "on", "rien", "un", "non", "mon", "ton", "son","commun"]
if mot[-1] == 'n' and mot not in liste :
for i , trans in enumerate(transList):
trans = denasal(trans)
transList[i] = trans
############################################################
for trans in transList:
if lang == "fr":
res = decoupage(mot,trans,phon2graph,phon2class)
......
......@@ -2,10 +2,8 @@ from django.shortcuts import render, HttpResponseRedirect
from . import textPhonographer as txtphono
from .models import Phonograph, Data, Entree, commitData
from django.http import JsonResponse
import json
import spacy
import subprocess
import re
from .liaisons import *
import json, spacy, subprocess, re
nlpFr = spacy.load('fr_core_news_sm')
nlpEn = spacy.load("en_core_web_sm")
......@@ -30,9 +28,53 @@ def colorize(request):
nlpText = nlpEn(text)
elif lang == "zh":
nlpText = nlpZh(text)
######### traitement du tiret ############
if lang == "fr":
listeMots = []
for i, mot in enumerate(nlpText):
# Traiter les mots qui contiennent le '-'.
# Ex : "-il", "Peut-être"
if "-" in mot.text and len(mot.text) > 1 :
temp = ""
for l in mot.text :
if l != "-":
temp += l
else:
if temp == '':
listeMots.append("-")
else:
listeMots.append(temp)
listeMots.append("-")
temp = ""
if temp != "":
listeMots.append(temp)
# Traiter uniquement le tiret
else:
listeMots.append(mot.text)
tiret = {} # EX : Vas-y ! Pouvait-il partir ? ==> {'Vas': 1, 'Pouvait': 5}
for k,item in enumerate(listeMots):
if item == "-" and k > 0 :
tiret[listeMots[k-1]] = k
print(tiret)
# Remplacer tous les tirets dans le texte par le caractere espace
if "-" in listeMots:
text = text.replace('-', ' ')
nlpText = nlpFr(text)
#########################################
outText = []
for token in nlpText:
# appeler la fonction pour savoir s'il y a une locution
index = liaison_locution(text, nlpText)
print("Index", index)
for j, token in enumerate(nlpText):
sdl = re.findall(r'\r\n',token.text)
print("sdl =",sdl)
if len(sdl) > 0:
......@@ -42,7 +84,27 @@ def colorize(request):
else:
print("Mot en entrée :",token.text)
if lang == "fr" or lang == "en":
result = txtphono.traitement(token.text,lang)
liaison = None
## TRAITEMENT LIAISON POUR FR
if lang == "fr":
if j + 1 < len(nlpText):
# ici on met la liaison directement s'il s'agit d'une locution avec exception
if index != None and (index[0]+index[1]) == j:
bigram = (nlpText[j].text, nlpText[j+1].text)
liaison = index[2]
print("bigram de locution :",bigram)
# appel de la fonction obtenirM1M2
else:
bigram = obtenirM1M2(token.text, nlpText[j+1].text)
print("bigram normal :",bigram)
# vérifie les règles de liaisons et denasalisation
if bigram != None:
print("bigram normal :",token.pos_,nlpText[j+1].pos_)
liaison = verifliaison(token, nlpText[j+1])
print("type de liaison : ",liaison)
result = txtphono.traitement(token.text,lang,liaison) # LIAISON : ajoute le booléen liaison en argument
phonographieList = []
for r in result:
......@@ -55,6 +117,33 @@ def colorize(request):
phonographieList.append((phonographie,r[1],r[2],r[3]))
outText.append(phonographieList)
## SUITE LIAISON ##
if lang == 'fr':
## Affichage de la liaison hors du mot
juge = False
if len(tiret) > 0 and token.text in tiret.keys() :
if liaison != None :
result1 = txtphono.traitement("-͜", lang, token.text+liaison)
juge = True
elif liaison == None :
result1 = txtphono.traitement("-", lang, liaison)
juge = True
else:
if liaison != None:
result1 = txtphono.traitement("‿", lang, token.text+liaison)
juge = True
if juge == True :
phonographieList = []
for r in result1:
phonographie = []
for i in r[0]:
ph = {}
ph['phon'] = i[0]
ph['graph'] = i[1]
phonographie.append(ph)
phonographieList.append((phonographie, r[1], r[2], r[3]))
outText.append(phonographieList)
elif lang == "zh":
result = txtphono.traitementzh(token.text)
outText.append(result)
......
......@@ -188,7 +188,10 @@ async function getColorisation() {
for (h=0; h<outText[i][0][0][0].graph.length; h++) {
outputDiv.innerHTML = outputDiv.innerHTML + '<br>';
}
} else if (outText[i][0][0][0].graph.match(/^(,|\.|…|\)|\]|\}|%|>|»|”|-)$/)) {
} else if (outText[i][0][0][0].graph.match(/^‿$/)){
outputDiv.innerHTML = outputDiv.innerHTML + '<span class="liaison '+ outText[i][0][0][0].phon +'">'+ outText[i][0][0][0].graph +'</span>';
noSpace = true;
} else if (outText[i][0][0][0].graph.match(/^(,|\.|…|\)|\]|\}|%|>|»|”|-)$/)) {
outputDiv.innerHTML = outputDiv.innerHTML + '<span class="phon_neutre">'+ outText[i][0][0][0].graph +'</span>';
if (outText[i][0][0][0].graph.match(/^-$/)) noSpace = true; else noSpace = false;
} else if (outText[i][0][0][0].graph.match(/\(|\[|\{|<|«|“/)) {
......@@ -345,6 +348,7 @@ function unknownMark() {
newMark.classList = 'glyphicon glyphicon-edit unknownMark';
newMark.title = "Ajouter ce mot au dictionnaire";
newMark.value = unknowns[i].innerHTML;
newMark.innerHTML = "?";
newMark.onclick = function(){
dicoAddPop(this.value);
};
......@@ -416,10 +420,11 @@ function bugMark() {
for (i = 0; i < bugs.length; i++) {
var newMark = document.createElement('span');
newMark.classList = 'glyphicon glyphicon-flash bugMark';
newMark.innerHTML = ' X';
var thisMotId = bugs[i].parentNode.id;
console.log(thisMotId);
var infoMot = dicoTok[thisMotId][dicoId[thisMotId]];
newMark.title = '/' + infoMot[2] + '/: ' + infoMot[3];
newMark.title = 'Le Fidel utilisé ne permet pas l\'alignement de /' + infoMot[2] + '/: ' + infoMot[3];
bugs[i].parentNode.insertBefore(newMark,bugs[i].nextSibling);
};
}
......
......@@ -188,7 +188,7 @@
{% include 'footer.html' %}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<!-- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> -->
<script type="text/javascript" src="{% static 'languages/languages.js' %}"></script>
<script src="{% static 'scripts/loc2stand.js' %}"></script>
<script src="{% static 'scripts/bicol2colcol.js' %}"></script>
......