# -*- coding: utf-8 -*-
# le code est sous GPL (car une partie de MediaWiki est reprise !)
# Copyright (C) 2011 Juju2004
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
# QUEL EST LE BUT DE CE SCRIPT
#*****************************
# Ce script a été créé dans le cadre de la "Discussion Wikipédia:Prise de décision/Adoption du balisage dfn dans les introductions
# d'articles". Le script ne concerne *que* la partie remplacement du titre en gras par une balise dfn. Je (Juju2004) ne suis pas dresseur
# de bots et ne suis donc pas concerné par le reste de l'opération. Je propose ici un algorithme pour une partie délicate cette opération.
# Ce script peut donc servir de boîte à idées pour les scripts des dresseurs de bots.
#
# LE LANGAGE PYTHON
#******************
# Le script ne fait pas usage de particularité du langage Python, afin de permettre l'adaptation dans tous les langages similaires :
# Donc :
# * pas de liste en intension (sauf deboguage)
# * pas de enumerate.
#
# COMMENT UTILISER CE SCRIPT ?
#*****************************
# En local, la ligne :
#----------------------------
# from botdfn import *
#----------------------------
# Puis la définition des paramètres :
#----------------------------
# if __name__=="__main__":
# dfnName = 'dfn'
# templatesToNeutralize = [...]
# templatesToExplore = [...]
# titleModifiers = [...]
#----------------------------
# Ensuite, le process du texte :
#----------------------------
# inserter = dfnInserter(dfnName, titleModifiers, templatesToNeutralize, templatesToExplore)
# text = inserter.process(text, title)
#----------------------------------------
#
# CE QUE GÈRE ET NE GÈRE PAS CE SCRIPT
#*************************************
# Le script gère :
# * le non remplacement dans les modèles ;
# * les gras et italiques comme MediaWiki ;
# * certains modèles typo qui pouvaient poser problème (ex. {{e}}) ;
# * les modèles dont il faut explorer les paramètres (ex. {{japonais}}) ;
# * les tableaux (merci Dr Brains).
# * les titres spécifiés (magic word DISPLAYTITLE et autres)
# * l'adaptation des modèles {{lang|...}} à la nouvelles syntaxe {{dfn|lang=...|...}}
# * les parenthèses d'homonymie
# * les italiques dans la balise
# * le cas particulier des gras autour du titre
# Le script ne gère pas :
# * une reconnaissance par comparaison du titre (par comparaison de deux chaînes de caractères par ex.)
# * la prise en compte de la présence d'une balise dfn dans l'article avant traitement
# * la gestion de plusieurs titres alternatifs à rechercher
#
# MODIFICATIONS
#**************
# 16/04/11 :
# - version initiale (début de doQuotes, pour fonctionner comme MediaWiki ; flagger les parties de texte à exporer, applatissement des modèles inutiles)
# 18/04/11 :
# - modèles typo et japonais
# - normalisation des paramètres dans la classe Params
# 21/04/11 :
# - gestion des titres mis en forme
# 25/04/11 :
# - passage à l'objet
# - la normalisation des paramètres est placée dans l'inserter
# - classe Template.
# 26/04/11 :
# - tri des clés sur makeDfn, afin d'avoir un résultat prévisible
# - nettoyage des parenthèses d'homonymie
# 03/05/11 :
# - NoTemplate (cf. Null Object)
# - les italiques dans makeDfn
# 07/05/11 :
# - ajout d'une classe PreLex
# - gestion des italiques dans le titre de l'article
# - switchVerbose
# - ajout des asserts
# - nouvelle erreur si les apostrophes ne sont pas équilibrées
# - correction d'un bug : '''x''' ne veut pas dire que x est en gras : il peut être entouré de gras (reprise de la fin de doQuotes)
# - cleanKey dans le langTemplate.
# 08/05/11 :
# - factorisation template (TemplateOneKey, getCleanKey)
# - correction bug mineur sur le cas : '''''x'''y''
# - réduction des preLexs au début du passage du lexer
#
# TODO
#*****
# Un peu de commentaire en plus...
import re
##############
# L'INSERTER #
##############
# Il se charge de l'insertion de la balise dfn dans un texte
class dfnInserter():
EMPTY_TEXT = 1
UNBALANCED_BRACES = 2
NO_TITLE = 4
PRESUMPTION_OF_NON_BALANCED_QUOTES = 8
# les paramètres lui sont passés
def __init__(self, dfnName, titleModifiers, templatesToNeutralize, templatesToExplore, verbose=False):
Template.dfnName = dfnName # variable de classe
newTitleModifers = []
for pattern in titleModifiers:
endPattern = pattern[-1]
if endPattern=='|': # un modèle normal
pattern = '\s*?['+pattern[0:1].upper()+pattern[0:1].lower()+']'+pattern[1:-1]+'\s*?\|\s*?'
elif endPattern==':': # un mot magique : toujours en majuscules
pattern = '\s*?'+pattern[0:-1].upper()+'\s*?:\s*?'
else: # autre : on suppose que c'est un modèle normal
pattern = '\s*?'+'['+pattern[0:1].upper()+pattern[0:1].lower()+']'+pattern[1:]+'\s*?\|\s*?'
newTitleModifers.append(pattern)
self.__titleRegexp = '('+'|'.join(newTitleModifers)+')(.*)'
self.__preLexer = PreLexer(templatesToNeutralize, templatesToExplore, verbose)
self.__lexer = Lexer()
self.__verbose = verbose
def switchVerbose(self):
self.__verbose = not self.__verbose
# ajoute la balise DFN au texte, si possible, et renvoie le texte
def process(self, text, title):
self.__errors = 0
if self.__verbose:
print ("Text = ", text)
preLexs = self.__preLexer.process(text)
if self.__verbose:
print ("PreLexs = ", [preLex.asStr() for preLex in preLexs])
assert(PreLex.list2str(preLexs)==text) # vérification de base : le texte n'est pas modifié ici
titleToFind = self.__getTitleToFind(preLexs, title) # s'il y a un titre à afficher qui est différent du titre de la page
if self.__verbose:
print ("Title to find = ", titleToFind)
lexs = self.__lexer.process(preLexs)
if self.__verbose:
print ("Lexs = ", [lex.asStr() for lex in lexs])
assert(PreLex.list2str(lexs)==text) # vérification de base : le texte n'est pas modifié ici
lexs = self.__insert(lexs, titleToFind)
result = self.__toText(lexs)
if self.__verbose:
print ('Result = ', result)
return result
# intermède : le titre fourni est-il le bon ? #
def __getTitleToFind(self, preLexs, title):
titleToFind = None
for i in range(len(preLexs)):
if i%2==0: # un morceau de textes
m = re.search(self.__titleRegexp, preLexs[i].string)
if m:
titleToFind = m.group(2)
break
if titleToFind==None: # on va au moins nettoyer le titre des parenthèses d'homonymie
indexBrace = title.find('(')
if indexBrace!=-1:
titleToFind = title[0:indexBrace].strip()
else:
titleToFind = title
return titleToFind
# l'insertion proprement dite dans la suite de lexèmes
def __insert(self, lexs, titleToFind):
inserted = False
i = self.__findTitle(lexs, titleToFind, 0) # trouve l'indice du titre (avec minuscules)
inserted = False
while i!=None: # titre trouvé
prevQuotes = lexs[i-1].string
nextQuotes = lexs[i+1].string
if (len(prevQuotes)==3 and len(nextQuotes)==3) or (len(prevQuotes)==5 and len(nextQuotes)==5):
inserted = True
if inserted:
if self.__verbose:
print ('Titre "'+titleToFind+'" trouvé dans "'+lexs[i].string+'"')
lexs[i-1].string = ''
lexs[i+1].string = ''
lexs[i].string = self.__cleanTitle(lexs[i], titleToFind) # un petit nettoyage si nécessaire
break
i = self.__findTitle(lexs, titleToFind, i) # trouve l'indice du titre (avec minuscules)
if not inserted:
self.__errors |= dfnInserter.NO_TITLE
return lexs
# trouve l'indice d'un lexème qui contient le titre, None sinon
def __findTitle(self, lexs, title, start=0):
for i in range(start+1, len(lexs)):
lex = lexs[i]
if lex.toExplore and self.__isTitle(lex.getCleanText(), title):
return i
return None
# le test peut être raffiné (accents, liens WP, etc.)
def __isTitle(self, string, title):
string = string.lower()
string = re.sub('\[\[', '', string) # on enlève les wikiliens pour la comparaison
string = re.sub('\]\]', '', string)
if string[0:2]=="''" and string[-2:]=="''":
string = string[2:-2] # on compare le titre sans les italiques autour
title = title.lower()
if title[0:2]=="''" and title[-2:]=="''":
title = title[2:-2] # on compare le titre sans les italiques autour
return string==title
def __cleanTitle(self, lex, title):
return lex.template.makeDfnTemplate().toText() # on se contente de tirer un dfnTemplate du template, et de le convertir en texte
# construit le texte de retour à partir des lexèmes
def __toText(self, lexs):
result = ''
for lex in lexs:
result += lex.string
return result
# toutes les erreurs sont converties en chaîne à afficher
def getErrorsStr(self):
self.__errors |= self.__preLexer.getErrors() | self.__lexer.getErrors()
errs = []
if self.__errors & dfnInserter.EMPTY_TEXT:
errs.append('Empty text')
if self.__errors & dfnInserter.UNBALANCED_BRACES:
errs.append('Unbalanced braces')
if self.__errors & dfnInserter.NO_TITLE:
errs.append('No title')
return ' & '.join(errs)
#############
# UN LEXÈME #
#############
# Le texte va être découpé en lexèmes qui seront utilisés pour la comparaison avec le titre et éventuellement insertion dfn
class PreLex():
def __init__(self, string, toExplore):
self.string = string
self.toExplore = toExplore
# le contenu textuel du lexème, pour comparaison.
def getCleanText(self):
return self.string
# conversion en chaine de caractères
def asStr(self): # en python pur, on écrirait __str__
return (self.string, self.toExplore)
def asLex(self, template=None):
if template==None:
template = TemplateGenerator.getInstance().getNoTemplate(self.string)
return Lex(self.string, self.toExplore, template)
def list2str(preLexs):
string = ''
for preLex in preLexs:
string += preLex.string
return string
class Lex():
def __init__(self, string, toExplore, template):
self.string = string
self.toExplore = toExplore
self.template = template
# le contenu textuel du lexème, pour comparaison.
def getCleanText(self):
return self.template.getCleanText()
# conversion en chaine de caractères
def asStr(self): # en python pur, on écrirait __str__
return (self.string, self.toExplore, self.template.asStr())
###############
# LE PRELEXER #
###############
# Il découpe le texte selon les doubles accolades, neutralise certains modèles, et définit les
# parties qui peuvent intéresser pour la comparaion de titre
class PreLexer():
def __init__(self, templatesToNeutralize, templatesToExplore, verbose=False):
self.__templatesToNeutralize = lowerList(templatesToNeutralize)
self.__templatesToExplore = lowerList(templatesToExplore)
self.__verbose = verbose
# construit une première suite de lexèmes à partir du texte
def process(self, text):
self.__errors = 0
preLexs = []
if text.strip() == '':
self.__errors = dfnInserter.EMPTY_TEXT
else:
strings = self.__splitAccoladesCapture(text) # découpe selon les modèles, avec neutralisation des modèles typo
preLexs = self.__flagStrings(strings) # on ajoute à chaque élément de la liste un flag (la liste peut-être modifiée ici)
return preLexs
# découpage selon les modèles avec neutralisation de certains modèles.
def __splitAccoladesCapture(self, text):
# liste de longueur 2n+1 est de la forme : [0:morceau 0, 1:delim 0, 2:morceau 1, ... , 2n-2:morceau n-1, 2n-1:delim n-1, 2n:morceau n]
strings = splitDelimCapture("\{\{|\{\||\}\}|\|\}", text) # découpe selon les modèles et les tableaux
if self.__verbose:
print('> Texte découpé selon les accolades (initial) : ', strings)
# on va traiter le cas des modèles de mise en forme, type {{e}}, qu'il faut simplement neutraliser (c'est du texte), ou les modèles de langues qui seront traités à la fin.
# on s'intéresse aux morceaux 1 à n-1 (on exclut les morceaux aux extrêmes), c'est-à-dire aux indices 2, 4, ... , 2n-2
i=2
while i<len(strings)-2: # une boucle while parce qu'on modifie la liste en cours de route
if self.__isTemplateBegin(strings, i):
(strings, i) = self.__tryToNeutralizeTemplate(strings, i) # neutraliser certains modèles
i+=1
if self.__verbose:
print('> Texte découpé selon les accolades (après neutralisation) : ', strings)
return strings
# on ne s'intéresse qu'aux textes qui suivent une accolade ouvrante
def __isTemplateBegin(self, strings, i):
return i%2==0 and i>1 and strings[i-1] == '{{'
# si on a un modèle, essaie de le neutraliser
# on peut faire mieux avec les arguments.
def __tryToNeutralizeTemplate(self, strings, iOdd): # 2 <= iOdd < len-2
string = strings[iOdd]
if matchBegin(string, self.__templatesToNeutralize): # {{à neutraliser|....}}
strings = self.__flattenTemplate(strings, iOdd) # ici, strings[iOdd] contient le texte entre accolades équilibrées
# attention : au fur et à mesure qu'on supprime, l'indice ne doit pas bouger
strings = pack(strings, iOdd-2, iOdd+3) # "xyz", "{{", "flat", "}}", "xyz" (1<= k < len+1)
iOdd -= 2 # on se recule sur le nouveau morceau de texte
return (strings, iOdd)
## Aplatit le contenu d'un modèle : on n'a plus qu'un texte entre deux accolades. ##
# aplatit i-1:"{{", i:"xyz", i+1:"{{", i+2:"xyz", i+3:"}}", i+4:"xyz", i+5:"}}" -> i-1:"{{", i:"xyz{{xyz}}xyz", i+1:"}}"
# Renvoie le texte et le nouvel indice.
def __flattenTemplate(self, strings, iOdd): # iOdd = le début du modèle
jEven = self.__findClosingAccolade(strings, iOdd-1) # j est l'indice de la fin du modèle à explorer (}}).
if self.__verbose:
print ('>> Aplatit ', strings[iOdd:jEven], end='')
strings = pack(strings, iOdd, jEven)
if self.__verbose:
print (' vers "'+strings[iOdd]+'"')
return strings
# renvoie un l'indice impair de l'accolade fermante pour une accolade ouvrante donnée
def __findClosingAccolade(self, textsAndDelims, startEven):
rec = 0
for i in range(startEven, len(textsAndDelims)):
if i%2==1: # un délimiteur
rec += self.__recDelta(textsAndDelims[i])
if rec==0:
return i
return len(textsAndDelims)
# convertit les morceaux de texte à considérer en lexèmes
def __flagStrings(self, strings):
rec = 0 # pour suivre la profondeur de récursion
i=0
preLexs = []
while i<len(strings): # strings est susceptible d'être modifiée en cours de route
string = strings[i]
if i%2==0: # i pair : morceau de texte
if rec==0:
preLexs.append(PreLex(string, True)) # hors modèle => on ajoute le morceau à explorer
elif self.__isTemplateToExploreBegin(rec, strings, i):
strings = self.__flattenTemplate(strings, i) # newTextPart = (par ex.) "japonais|xyz"
argsPreLexs = self.__getArgsPreLexs(strings[i])
preLexs.extend(argsPreLexs)
else:
preLexs.append(PreLex(string, False)) # un contenu de modèle non à explorer
else: # un délimiteur
preLexs.append(PreLex(string, False)) # un délimiteur n'est pas à explorer
rec += self.__recDelta(string) # plus ou moins
i+=1
if rec!=0:
self.__errors |= dfnInserter.UNBALANCED_BRACES
if self.__verbose:
print ('> Texte découpé avec flags : ', [preLex.asStr() for preLex in preLexs])
return preLexs
# début d'un modèle à explorer : début de modèle de non imbriqué dans un modèle, et qi correspond à un des modèles à explorer
def __isTemplateToExploreBegin(self, rec, strings, i):
return rec==1 and self.__isTemplateBegin(strings, i) and matchBegin(strings[i], self.__templatesToExplore)
# découpe selon la limite entre les arguments
def __getArgsPreLexs(self, string):
# insertion des arguments
args = splitDelimCapture('\|(.*?=)?', string)
argsPreLexs = [PreLex(args[0], False)] # on n'explore pas le nom du modèle
for i in range(1, len(args)):
argsPreLexs.append(PreLex(args[i], i%2==0))
return argsPreLexs
# calcule le delta qu'un délimiteur donne dans l'étude des récursions
def __recDelta(self, delim):
delta = 0
if delim== '{{':
delta = 1
elif delim=='{|':
delta = 1000 # pour s'assurer qu'un modèle ne ferme pas un tableau
elif delim== "}}":
delta = -1
elif delim=='|}':
delta = -1000 # pour s'assurer qu'un modèle ne ferme pas un tableau
return delta
def getErrors(self):
return self.__errors
############
# LE LEXER #
############
class Lexer():
def __init__(self, verbose=False):
self.__verbose = verbose
# la première suite de lexèmes est analysée pour produire une seconde suite plus précise
def process(self, preLexs):
self.__errors = 0
preLexs = self._packPreLexs(preLexs) # on rassemble les toExplore consécutifs d'une part, les not toExplore consécutifs d'autre part
lexs = []
for i in range(len(preLexs)):
preLex = preLexs[i]
if preLex.toExplore: # ces lexèmes seront découpés en nouveaux lexèmes plus précis
quoteStrings = self.__splitQuotesCapture(preLex.string) # on découpe selon les apostrophes
lexs.extend(self.__processQuoteStrings(quoteStrings)) # trouve les modèles contenus dans le texte
else:
lexs.append(preLex.asLex())
return lexs
# on rassemble les toExplore consécutifs d'une part, les not toExplore consécutifs d'autre
def _packPreLexs(self, preLexs):
prev=0
cur=1
while cur<len(preLexs):
if preLexs[cur].toExplore==preLexs[prev].toExplore:
preLexs[prev].string += preLexs[cur].string
preLexs.pop(cur)
else:
prev = cur
cur += 1
return preLexs
# version reprise de doQuotes de MediaWiki
# renvoie un texte sous forme de suite de ' et de morceaux de textes
# voir : http://svn.wikimedia.org/doc/Parser_8php_source.html#l01349
def __splitQuotesCapture(self, text):
arr = splitDelimCapture("''+", text)
if len(arr)== 1:
return arr
# First, do some preliminary work. This may shift some apostrophes from
# being mark-up to being text. It also counts the number of occurrences
# of bold and italics mark-ups.
numbold = 0
numitalics = 0
for i in range(len(arr)):
if (i%2) == 1: # c'est un délimiteur
# If there are ever four apostrophes, assume the first is supposed to
# be text, and the remaining three constitute mark-up for bold text.
if len(arr[i])== 4:
arr[i-1] += "'"
arr[i] = "'''"
elif len (arr[i])>5:
# If there are more than 5 apostrophes in a row, assume they're all
# text except for the last 5.
for j in range(len(arr[i]) - 5):
arr[i-1] += "'"
arr[i] = "'''''"
# Count the number of occurrences of bold and italics mark-ups.
# We are not counting sequences of five apostrophes.
if len(arr[i]) == 2:
numitalics += 1
elif len(arr[i]) == 3:
numbold += 1
elif len(arr[i]) == 5:
numitalics += 1
numbold += 1
# If there is an odd number of both bold and italics, it is likely
# that one of the bold ones was meant to be an apostrophe followed
# by italics. Which one we cannot know for certain, but it is more
# likely to be one that has a single-letter word before it.
if numbold%2 == 1 and numitalics%2 == 1:
i = 0
firstsingleletterword = -1
firstmultiletterword = -1
firstspace = -1
for r in arr:
if (i%2==1) and (len(r)==3): # séparateur '''
x1 = arr[i-1][-1:] # dernier car du bloc préc, substr( arr[i-1], -1 )
x2 = arr[i-1][-2:-1] # avant-dernier car du bloc préc, substr( arr[i-1], -2, 1 )
if x1 == ' ': # chaine = '...x '
if firstspace == -1:
firstspace = i
elif x2 == ' ': # chaine = '...x y'
if firstsingleletterword == -1:
firstsingleletterword = i
else: # chaine = "...xy"
if firstmultiletterword == -1:
firstmultiletterword = i
i+=1
# If there is a single-letter word, use it!
if firstsingleletterword > -1: #
arr[firstsingleletterword] = "''"
arr[firstsingleletterword-1] += "'"
elif firstmultiletterword > -1:
# If not, but there's a multi-letter word, use that one.
arr[firstmultiletterword] = "''"
arr[firstmultiletterword-1] += "'"
elif firstspace > -1:
# ... otherwise use the first one that has neither.
# (notice that it is possible for all three to be -1 if, for example,
# there is only one pentuple-apostrophe in the line)
arr[firstspace] = "''"
arr[firstspace-1] += "'"
return arr
# renvoie une suite de lexèmes en définissant éventuellement le modèle présent
# on va reconstituer les italiques en texte.
# suite de la reprise de doQuotes de MediaWiki
# voir : http://svn.wikimedia.org/doc/Parser_8php_source.html#l01349
def __processQuoteStrings(self, quoteStrings):
lexs = []
curString = ''
# Now let's actually convert our apostrophic mush to HTML!
state = '' # vaut both si ouvert par des ''''' <b><i>
for i in range(len(quoteStrings)):
string = quoteStrings[i]
toExplore = False
if i%2==0:
curString += string
newState = state
else:
if len(string) == 2: # ''
if state == 'i':
newState = ''
elif state == 'bi' or state=='ib':
newState = 'b'
elif state=='both': # both -> bi : '''''bold and italics'' bold ...'''
newState = 'b'
curString = "''"+curString
elif state=='b':
newState = 'bi'
else: # state can be 'b' or ''
state += 'i'
curString += string # on continue à enrichir le texte
elif len(string) == 3: # '''
if state == 'b':
newState = ''
elif state == 'bi' or state == 'ib':
newState = 'i'
elif state=='both': # both -> ib : '''''bold and italics''' italics ...'''
newState = 'i'
# on ajoute '' à la chaîne précédente : CHAINE PREC''!'''bold and italics''' italics ...'''
newString = lexs[-2].string+"''"
newBold = lexs[-2].toExplore
lexs[-2] = Lex(newString, newBold, TemplateGenerator.getInstance().getNoTemplate(newString))
else: # state can be 'i' or ''
newState = state+'b'
elif len(string) == 5: # '''''
if state == 'b':
newState = 'i' # fermeture du gras, ouverture des italiques
elif state == 'i':
newState = 'b'
elif state=='ib' or state == 'bi':
newState = ''
elif state == 'both':
newState = ''
curString = "''"+curString+"''"
else: # state == ''
newState = 'both'
if len(string)>=3: # changement de graisse : ce qui nous intéresse ici
lexs.append(self.__getCurLex(curString, state))
lexs.append(self.__getQuotesLex())
curString = '' # on vide la chaîne courante
state = newState
i+=1
# Now close all remaining tags. Notice that the order is important.
# on va juste générer des erreurs
if state != '':
self.__errors |= dfnInserter.PRESUMPTION_OF_NON_BALANCED_QUOTES
lexs.append(self.__getCurLex(curString, state))
return lexs
def __getCurLex(self, string, state):
bold = self.__isBold(state)
if bold:
template = TemplateGenerator.getInstance().fromText(string)
else:
template = TemplateGenerator.getInstance().getNoTemplate(string)
return Lex(string, bold, template)
def __isBold(self, state):
return state=='b' or state=='bi' or state=='ib' or state=='both'
def __getQuotesLex(self):
template = TemplateGenerator.getInstance().getNoTemplate("'''")
return Lex("'''", False, template)
# récupère le corps d'un modèle, None si pas un modèle.
def __getTemplateBody(self, string):
string = string.lower()
string = re.sub('\[\[', '', string) # wikiliens...
string = re.sub('\]\]', '', string)
string = re.sub('\'\'', '', string) # les italiques
if string[0:2] == '{{' and string[-2:] == '}}':
return string[2:-2]
else:
return None
# récupère les erreurs
def getErrors(self):
return self.__errors
##################################
# CLASSES POUR GERER LES MODELES #
##################################
# Une classe qui génére des modèles
class TemplateGenerator():
__instance = None
def getInstance():
if TemplateGenerator.__instance == None:
TemplateGenerator.__instance = TemplateGenerator()
return TemplateGenerator.__instance
def __init__(self):
self.__templates = {'lang':LangTemplate} # correspondance nom/sous-classe
def fromText(self, string):
if string[0:2] == "''" and string[-2:] == "''": # italiques
italics = True # pour les ajouter *dans* les paramètres du modèle
cleanString = string[2:-2]
else:
italics = False
cleanString = string
if cleanString[0:2] == '{{' and cleanString[-2:] == '}}':
(name, args) = self.__getNameArgs(cleanString[2:-2]) # on tire le modèle du texte
return self.__templates[name](name, args, italics)
else:
return TemplateGenerator.__instance.getNoTemplate(string)
# extrait un modèle d'un morceau de texte, et renvoie le template adapté
def __getNameArgs(self, templateText):
i=1
argsKV = templateText.split('|')
name = argsKV[0]
args = {}
argsKV.pop(0)
for argKV in argsKV:
index = argKV.find('=')
if index!=-1:
(argK, argV) = (argKV[0:index], argKV[index+1:])
else:
argK = str(i)
argV = argKV
i +=1
args[argK] = argV
return (name, args)
def getNoTemplate(self, string):
return NoTemplate('', {'1':string})
# class abstraite de modèle
class Template():
def __init__(self, name='', args = {}):
self._name = name
self._args = args
# renvoie la clé pour le contenu
def getCleanText(self):
raise Exception("Abstract function")
# convertit le modèle en texte, de manière prévisible
def toText(self):
text = '{{'+self._name+'|'
params = []
for k in sorted(self._args.keys()): # pour que le résultat soit prévisible
params.append(k+'='+str(self._args[k]))
text += '|'.join(params)+'}}'
return text
def __cmp(self, a, b):
if type(a)==int and type(b)==str:
return a<b
elif type(a)==int and type(b)==int:
return
# renvoie le contenu textuel du modèle.
def getCleanText(self):
raise Exception("Abstract function")
# renvoie l'interprétation dfn du modèle
def makeDfnTemplate(self):
raise Exception("Abstract function")
# en Python pur, __str__
def asStr(self):
return(self._name+' '+str(self._args))
# class abstraite de modèle, avec le texte contenu dans une seule valeur du tableau
class TemplateOneKey(Template):
def __init__(self, name='', args = {}, italics=False):
Template.__init__(self, name, args)
self._cleanKey = self._getCleanKey()
if italics:
self._args[self._cleanKey] = "''"+self._args[self._cleanKey]+"''"
# renvoie la clé qui contient l'information principale
def _getCleanKey(self):
raise Exception("Abstract function")
# renvoie le texte contenu dans la clé
def getCleanText(self):
return self._args[self._cleanKey]
# Un faux template, pour homogénéité
class NoTemplate(TemplateOneKey):
def _getCleanKey(self):
return '1'
def makeDfnTemplate(self):
return Template(Template.dfnName, self._args)
def asStr(self): # redef
return ''
# template de langue
class LangTemplate(TemplateOneKey):
def _getCleanKey(self):
if 'texte' in self._args.keys():
cleanKey = 'texte'
else:
if self._args['1'] == 'ltr' or self._args['1'] == 'rtl':
cleanKey = '3'
else:
cleanKey = '2'
return cleanKey
def makeDfnTemplate(self):
args = {}
if 'texte' in self._args.keys():
args['lang'] = self._args['1']
if 'dir' in self._args.keys():
args['dir'] = self._args['dir']
if 'trans' in self._args.keys():
args['trans'] = self._args['trans']
else:
if self._args['1'] == 'ltr' or self._args['1'] == 'rtl':
args['dir'] = self._args['1']
args['lang'] = self._args['2']
else:
args['lang'] = self._args['1']
args['1'] = self._args[self._cleanKey]
return Template(Template.dfnName, args)
#######################
# FONCTIONS GENERALES #
#######################
# agglutine les valeurs arr[i] à arr[j] (exclu) dans arr[i], et renvoie arr
def pack(arr, i, j):
if j>len(arr):
j=len(arr)
for k in range(i+1, j): # attention : au fur et à mesure qu'on supprime, l'indice du prochain à agglutiner est toujours le suivant
arr[i] += arr[i+1]
arr.pop(i+1)
return arr
# émulation Python du PHP preg_split( $pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE ) qui conserve les délimiteurs
# on obtient un tableau dont les indices pairs sont les morceaux découpés et les indices impairs sont les délimiteurs
# il y a toujours un nombre *impair* de valeurs : [morceau 1, delim 1, ... , delim n-1, morceau n-1]
# c'est un genre de lexer rudimentaire
def splitDelimCapture(pattern, text):
arr = []
match = re.search(pattern, text)
while match != None:
block = text[0:match.start()] # le morceau précédant le délimiteurr
delim = text[match.start():match.end()] # le délimiteur
arr.append(block) # qu'on stocke dans le tableau
arr.append(delim)
text = text[match.end():]
match = re.search(pattern, text)
arr.append(text) # le dernier morceau
return arr
# est-ce que le texte proposé à son début dans la liste ?
def matchBegin(text, beginList):
text = text.lower().strip()
for name in beginList:
if text[0:len(name)] == name:
return True
return False
# renvoie une liste en minuscules
def lowerList(l):
newL = []
for x in l:
newL.append(x.lower())
return newL