#!/usr/bin/python # -*- coding: utf-8 -*- # # Pyromaths # Un programme en Python qui permet de créer des fiches d'exercices types de # mathématiques niveau collège ainsi que leur corrigé en LaTeX. # Copyright (C) 2006 -- Jérôme Ortais (jerome.ortais@pyromaths.org) # # 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 St, Fifth Floor, Boston, MA 02110-1301 USA # import sys, os, codecs from lxml import etree from lxml import _elementpath as DONTUSE # Astuce pour inclure lxml dans Py2exe from re import findall from .TexFiles import mise_en_forme from .Moodle_XML import moodle_xml from .HTML import quizs_html import gettext from glob import glob #============================================================== # Gestion des extensions de fichiers #============================================================== def supprime_extension(filename, ext): """supprime l'éventuelle extension ext du nom de fichier filename. ext est de la forme '.tex'""" if os.path.splitext(filename)[1].lower(): return os.path.splitext(filename)[0] return filename def ajoute_extension(filename, ext): """ajoute si nécessaire l'extension ext au nom de fichier filename. ext est de la forme '.tex'""" if os.path.splitext(filename)[1].lower() == ext: return filename return filename + ext #============================================================== # Gestion du fichier de configuration de Pyromaths #============================================================== def create_config_file(HOME, DIR_LANG, LANG_NOM, LANG): """Crée le fichier de configuration au format xml""" if LANG == "0": gettext_lang = gettext.NullTranslations() gettext_lang.install() else: gettext_lang = gettext.translation("pyromaths", DIR_LANG, languages=[LANG_NOM[int(LANG)][0]]) gettext_lang.install(unicode=True) from ..Values import VERSION, WEBSITE root = etree.Element("pyromaths") child = etree.SubElement(root, "options") etree.SubElement(child, "nom_fichier").text=_("exercices") etree.SubElement(child, "chemin_fichier").text="%s" % HOME etree.SubElement(child, "titre_fiche").text=_(u"Fiche de révisions") etree.SubElement(child, "corrige").text="False" etree.SubElement(child, "pdf").text="False" etree.SubElement(child, "unpdf").text="False" etree.SubElement(child, "moodle").text="False" etree.SubElement(child, "moodle_category").text=_(u'Sans catégorie') etree.SubElement(child, "html").text="True" etree.SubElement(child, "html_number").text=_(u"Seul un HTML") if LANG == "0": etree.SubElement(child, "modele").text="pyromaths.tex" else: etree.SubElement(child, "modele").text="pyromaths_%s.tex" %LANG_NOM[int(LANG)][0] etree.SubElement(child, "langue").text=str(LANG) child = etree.SubElement(root, "informations") etree.SubElement(child, "version").text=str(VERSION) etree.SubElement(child, "description").text=_(u"Pyromaths est un programme qui permet de générer des fiches d’exercices de mathématiques de collège ainsi que leur corrigé. Il crée des fichiers au format pdf qui peuvent ensuite être imprimés ou lus sur écran.") etree.SubElement(child, "icone").text="pyromaths.ico" subchild= etree.SubElement(child, "auteur") etree.SubElement(subchild, "nom").text=u"Jérôme Ortais" etree.SubElement(subchild, "email").text=u"jerome.ortais@pyromaths.org" etree.SubElement(subchild, "site").text=unicode(WEBSITE) return etree.tostring(root, pretty_print=True, encoding=unicode) def indent(elem, level=0): """Indente correctement les fichiers xml. By Filip Salomonsson; published on February 06, 2007. http://infix.se/2007/02/06/gentlemen-indent-your-xml""" i = "\n" + level*" " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " for e in elem: indent(e, level+1) if not e.tail or not e.tail.strip(): e.tail = i + " " if not e.tail or not e.tail.strip(): e.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i return elem def modify_config_file(file, HOME, CONFIGDIR, DIR_LANG, LANG_NOM): """Modifie le fichier de configuration si besoin, excepté les options utilisateur déjà configurées""" modifie = False oldtree = etree.parse(file) oldroot = oldtree.getroot() if oldroot.find('options').find('langue') == None: LANG = "0" else: LANG = oldroot.find('options').find('langue').text newroot = etree.XML(create_config_file(HOME, DIR_LANG, LANG_NOM, LANG)) for element in newroot.iter(tag=etree.Element): if not len(element): parents = [element] e = element.getparent() while e is not None: parents.insert(0,e) e = e.getparent() oldtag = oldroot for i in range(1, len(parents)): if oldtag.find(parents[i].tag) is None and i < len(parents) - 1 : if i > 1: etree.SubElement(oldroot.find(parents[i-1].tag), parents[i].tag) else: etree.SubElement(oldroot, parents[i].tag) oldtag = oldtag.find(parents[i].tag) else: oldtag = oldtag.find(parents[i].tag) if i == len(parents)-2: oldparent = oldtag if oldtag is None: # Ajoute un nouvel item dans le fichier xml modifie = True etree.SubElement(oldparent, element.tag).text = element.text elif oldtag.text != element.text and parents[1].tag != "options": # Modifie un item existant s'il ne s'agit pas des options modifie = True oldtag.text = element.text if modifie: f = codecs.open(os.path.join(CONFIGDIR, "pyromaths.xml"), encoding='utf-8', mode = 'w') f.write(etree.tostring(indent(oldroot), pretty_print=True, encoding=unicode)) f.close() if LANG == "0": gettext_lang = gettext.NullTranslations() gettext_lang.install() else: gettext_lang = gettext.translation("pyromaths", DIR_LANG, languages=[LANG_NOM[int(LANG)][0]]) gettext_lang.install(unicode=True) #============================================================== # Créer et lance la compilation des fichiers TeX #============================================================== def creation(parametres): """Création et compilation des fiches d'exercices. parametres = {'fiche_exo': f0, 'fiche_cor': f1, 'fiche_moodle': f2, 'fiche_html': f3, 'moodle_category': unicode(self.comboBox_moodle_category.currentText()), 'liste_exos': self.lesexos, 'creer_pdf': self.checkBox_create_pdf.checkState(), 'creer_moodle': self.checkBox_moodle.checkState(), 'creer_html': self.checkBox_html.checkState(), 'creer_unpdf': self.checkBox_unpdf.isChecked() and self.checkBox_unpdf.isEnabled(), 'titre': unicode(self.lineEdit_titre.text()), 'niveau': unicode(self.comboBox_niveau.currentText()), 'langue': self.LANG_NOM[int(self.LANG)][0] }""" exo = unicode(parametres['fiche_exo']) f0 = codecs.open(exo, encoding='utf-8', mode='w') f1 = None if parametres['corrige']: cor = unicode(parametres['fiche_cor']) f1 = codecs.open(cor, encoding='utf-8', mode='w') if parametres['creer_moodle']: moodle = unicode(parametres['fiche_moodle']) f2 = codecs.open(moodle, encoding='utf-8', mode='w') titre = parametres['titre'] fiche_metapost = os.path.splitext(exo)[0] + '.mp' quizs = [] isquizs = False if parametres['creer_moodle'] or parametres['creer_html']: isquizs = True copie_tronq_modele(f0, parametres, 'entete') if parametres['corrige'] and not parametres['creer_unpdf']: copie_tronq_modele(f1, parametres, 'entete') for exercice in parametres['liste_exos']: from ..sixiemes import sixiemes from ..cinquiemes import cinquiemes from ..quatriemes import quatriemes from ..troisiemes import troisiemes from ..lycee import lycee fonction = {0: sixiemes, 1: cinquiemes, 2: quatriemes, 3: troisiemes, 4: lycee} if exercice == (0, 1): quizs.append(fonction[exercice[0]].main(exercice[1], f0, f1, isquizs, parametres['langue'])) else: quizs.append(fonction[exercice[0]].main(exercice[1], f0, f1, isquizs)) if parametres['creer_unpdf']: f0.write("\\label{LastPage}\n") f0.write("\\newpage\n") f0.write(_(u"\\currentpdfbookmark{Le corrigé des exercices}{Corrigé}")) f0.write(_(u"\\lhead{\\textsl{\\footnotesize{Page \\thepage/ \\pageref{LastCorPage}}}}\n")) f0.write("\\setcounter{page}{1} ") f0.write("\\setcounter{exo}{0}\n") f1.write("\\label{LastCorPage}\n") copie_tronq_modele(f1, parametres, 'pied') else: f0.write("\\label{LastPage}\n") copie_tronq_modele(f0, parametres, 'pied') if parametres['corrige']: f1.write("\\label{LastPage}\n") copie_tronq_modele(f1, parametres, 'pied') f0.close() if parametres['corrige']: f1.close() if parametres['creer_unpdf']: f0 = codecs.open(exo, encoding='utf-8', mode='a') f1 = codecs.open(cor, encoding='utf-8', mode='r') for line in f1: f0.write(line) f0.close() f1.close() if parametres['creer_moodle']: quizs_xml = moodle_xml() if parametres['moodle_category'] != _(u"Sans catégorie"): quizs_xml.moodle_xml_category(parametres['moodle_category']) for q in quizs: if isinstance(q[0],list): for i in range(len(q)): for x in range(len(q[i])-1): quizs_xml.moodle_question(q[i][0], q[i][x+1][0], q[i][x+1][1], q[i][x+1][2]) else: for x in range(len(q)-1): quizs_xml.moodle_question(q[0], q[x+1][0], q[x+1][1], q[x+1][2]) quizs_xml.moodle_xml_fin(f2) f2.close() if parametres['creer_html']: # Nombre de la carpeta que será el del fichero dir_html = unicode(parametres['fiche_html']) + u'_html' # Nombre de la carpeta donde se guanda las imagenes dir_html_img = os.path.join(os.path.realpath(dir_html), 'img') # Comprueba si existe la carpeta (sino la crea y si existe borra las imagenes y html) if not os.path.isdir(dir_html): os.makedirs(dir_html) else: for i in glob(r'%s'%os.path.join(dir_html, '*.html')): os.remove(i) if not os.path.isdir(dir_html_img): os.makedirs(dir_html_img) else: for j in glob(r'%s'%os.path.join(dir_html_img, '*.gif')): os.remove(j) # Genera los enunciados, respuesta y su numero quizs_h = quizs_html(dir_html_img, parametres['datadir']) for q in quizs: if isinstance(q[0],list): for i in range(len(q)): for x in range(len(q[i])-1): quizs_h.html_question(q[i][0], q[i][x+1][0], q[i][x+1][1], q[i][x+1][2]) else: for x in range(len(q)-1): quizs_h.html_question(q[0], q[x+1][0], q[x+1][1], q[x+1][2]) q_exo, q_cor, q_num = quizs_h.html_fin() # Genera el número de páginas HTML try: q_num_box = int(parametres['html_number']) if q_num % q_num_box == 0: n_page = int(q_num/q_num_box) else: n_page = int(q_num/q_num_box)+1 except ValueError: q_num_box = q_num n_page = 1 for i in range(n_page): ## Nombre de la pagina HTML if i == 0: name_html = u'index.html' else: name_html = u'p%05d.html'%i f3 = codecs.open(os.path.join(dir_html,name_html), encoding='utf-8', mode='w') # Copia la 1 parte del index.html copie_tronq_modele_html(f3, parametres, "response") # Escribir las respuestas for line in q_cor[i*q_num_box:(i+1)*q_num_box]: f3.write(line) # Copia la 2 parte del index.html if q_num - i*q_num_box > q_num_box: q_num_page = q_num_box else: q_num_page = q_num - i*q_num_box copie_tronq_modele_html(f3, parametres, "question", str(q_num_page), str(i+1), str(n_page)) # Escribir las preguntas for line in q_exo[i*q_num_box:(i+1)*q_num_box]: f3.write(line) # Copia la parte antes del botón "prev" copie_tronq_modele_html(f3, parametres, "prev") if i == 1: prev_button = etree.Element("button", name="prev", onclick="location.href = \'index.html\'") prev_button.text = _("Page %s") % i etree.SubElement(prev_button, "img", alt="Prev", src="img/prev.gif") f3.write(etree.tounicode(prev_button)) elif i > 1: prev_button = etree.Element("button", name="prev", onclick="location.href = \'p%05d.html\'"%int(i-1)) prev_button.text = _("Page %s") % i etree.SubElement(prev_button, "img", alt="Prev", src="img/prev.gif") f3.write(etree.tounicode(prev_button)+"\n") # Copia la parte antes del botón "next" copie_tronq_modele_html(f3, parametres, "next") if (n_page > 0) and (i < n_page-1): next_button = etree.Element("button", name="next", onclick="location.href = \'p%05d.html\'"%int(i+1)) next_button.text = _(u"Page %s") % str(i+2) etree.SubElement(next_button, "img", alt="Next", src="img/next.gif") f3.write(etree.tounicode(next_button)) # Copia la 5 y última parte del index.html copie_tronq_modele_html(f3, parametres, "pie") f3.close() html = os.path.join(dir_html, u'index.html') # indentation des fichiers teX créés mise_en_forme(exo) if parametres['corrige'] and not parametres['creer_unpdf']: mise_en_forme(cor) # Dossiers et fichiers d'enregistrement, définitions qui doivent rester avant le if suivant. dir0=os.path.dirname(exo) if parametres['corrige']: dir1=os.path.dirname(cor) if parametres['creer_moodle']: dir2=os.path.dirname(moodle) if parametres['creer_html']: dir3=os.path.dirname(html) import socket if socket.gethostname() == "sd-20841.pyromaths.org": # Chemin complet pour Pyromaths en ligne car pas d'accents f0noext=os.path.splitext(exo)[0].encode(sys.getfilesystemencoding()) if parametres['corrige']: f1noext=os.path.splitext(cor)[0].encode(sys.getfilesystemencoding()) if parametres['creer_moodle']: f2noext=os.path.splitext(moodle)[0].encode(sys.getfilesystemencoding()) if parametres['creer_html']: f3noext=os.path.splitext(html)[0].encode(sys.getfilesystemencoding()) else: # Pas le chemin pour les autres, au cas où il y aurait un accent dans # le chemin (latex ne gère pas le 8 bits) f0noext=os.path.splitext(os.path.basename(exo))[0].encode(sys.getfilesystemencoding()) if parametres['corrige']: f1noext=os.path.splitext(os.path.basename(cor))[0].encode(sys.getfilesystemencoding()) if parametres['creer_moodle']: f2noext=os.path.splitext(os.path.basename(moodle))[0].encode(sys.getfilesystemencoding()) if parametres['creer_html']: f3noext=os.path.splitext(os.path.basename(html))[0].encode(sys.getfilesystemencoding()) if parametres['creer_unpdf']: os.chdir(dir1) os.remove('%s.tex' % f1noext) if parametres['creer_pdf']: from subprocess import call os.chdir(dir0) try: log = open('%s-pyromaths.log' % f0noext, 'w') for i in range(2): call(["latex", "-interaction=batchmode", "%s.tex" % f0noext], stdout=log) call(["dvips", "-q", "%s.dvi" % f0noext, "-o%s.ps" % f0noext], stdout=log) call(["ps2pdf", "-sPAPERSIZE#a4", "%s.ps" % f0noext, "%s.pdf" % f0noext], stdout=log) log.close() nettoyage(f0noext) if not "openpdf" in parametres or parametres["openpdf"]: if os.name == "nt": #Cas de Windows. os.startfile('%s.pdf' % f0noext) elif sys.platform == "darwin": #Cas de Mac OS X. os.system('open %s.pdf' % f0noext) else: os.system('xdg-open %s.pdf' % f0noext) except OSError: log.close() os.remove('%s-pyromaths.log' % f0noext) if parametres['corrige'] and not parametres['creer_unpdf']: os.chdir(dir1) try: log = open('%s-pyromaths.log' % f1noext, 'w') for i in range(2): call(["latex", "-interaction=batchmode", "%s.tex" % f1noext], stdout=log) call(["dvips", "-q", "%s.dvi" % f1noext, "-o%s.ps" % f1noext], stdout=log) call(["ps2pdf", "-sPAPERSIZE#a4", "%s.ps" % f1noext, "%s.pdf" % f1noext], stdout=log) log.close() nettoyage(f1noext) if not "openpdf" in parametres or parametres["openpdf"]: if os.name == "nt": #Cas de Windows. os.startfile('%s.pdf' % f1noext) elif sys.platform == "darwin": #Cas de Mac OS X. os.system('open %s.pdf' % f1noext) else: os.system('xdg-open %s.pdf' % f1noext) except OSError: log.close() os.remove('%s-pyromaths.log' % f1noext) # Para Abrir el *.moodle.xml tras crearlo if parametres['creer_moodle']: os.chdir(dir2) if os.name == "nt": #Cas de Windows. os.startfile('%s.xml' % f2noext) elif sys.platform == "darwin": #Cas de Mac OS X. os.system('open %s.xml' % f2noext) else: os.system('xdg-open %s.xml' % f2noext) # Para Abrir el *.html tras crearlo if parametres['creer_html']: os.chdir(dir3) if os.name == "nt": #Cas de Windows. os.startfile('%s.html' % f3noext) elif sys.platform == "darwin": #Cas de Mac OS X. os.system('open %s.html' % f3noext) else: os.system('xdg-open %s.html' % f3noext) def nettoyage(basefilename): """Supprime les fichiers temporaires créés par LaTeX""" try: for ext in ('.aux', '.dvi', '.out', '.ps'): os.remove(basefilename+ext) if os.path.getsize('%s.pdf' % basefilename) > 1000 : os.remove('%s.log' % basefilename) os.remove('%s-pyromaths.log' % basefilename) except OSError: pass #le fichier à supprimer n'existe pas et on s'en moque. def copie_tronq_modele(dest, parametres, master): """Copie des morceaux des modèles, suivant le schéma du master.""" master_fin = '% fin ' + master master = '% ' + master n = 0 ## Le fichier source doit être un modèle, donc il se trouve dans le dossier 'modeles' de pyromaths. source = parametres['modele'] if os.path.isfile(os.path.join(parametres['datadir'], 'templates',source)): source = os.path.join(parametres['datadir'], 'templates', source) elif os.path.isfile(os.path.join(parametres['datadir'], 'templates', source)): source = os.path.join(parametres['datadir'], 'templates', source) else: #TODO: Message d'erreur, le modèle demandé n'existe pas print(_(u"Le fichier modèle n'a pas été trouvé dans %s") % os.path.join(parametres['datadir'], 'templates')) ## Les variables à remplacer : titre = parametres['titre'] niveau = parametres['niveau'] if parametres['creer_unpdf']: bookmark=_(u"\\currentpdfbookmark{Les énoncés des exercices}{Énoncés}") else: bookmark="" if os.name == 'nt': os.environ['TEXINPUTS']= os.path.normpath(os.path.join(parametres['datadir'], 'packages')) tabvar = 'tabvar.tex' else: tabvar = os.path.normpath(os.path.join(parametres['datadir'], 'packages', 'tabvar.tex')) #rawstring pour \tabvar -> tab + abvarsous windows modele = codecs.open(source, encoding='utf-8', mode='r') for line in modele: if master_fin in line: break if n > 0: temp = findall('##{{[A-Z]*}}##',line) if temp: occ = temp[0][4:len(temp)-5].lower() #line = sub('##{{[A-Z]*}}##',eval(occ),line) line = line.replace(temp[0], eval(occ)) dest.write(line) if master in line: n = 1 modele.close() return def copie_tronq_modele_html(dest, parametres, master, nquestions = "", n_page = "", pages = ""): from time import localtime """Copie des morceaux des modèles, suivant le schéma du master.""" master_fin = u'" master = u'" n = 0 ## Le fichier source doit être un modèle, donc il se trouve dans le dossier 'modeles' de pyromaths. source = 'index_%s.html'%parametres['langue'] if os.path.isfile(os.path.join(parametres['datadir'], 'packages',source)): source = os.path.join(parametres['datadir'], 'packages', source) elif os.path.isfile(os.path.join(parametres['datadir'], 'packages', source)): source = os.path.join(parametres['datadir'], 'packages', source) else: #TODO: Message d'erreur, le modèle demandé n'existe pas print(_(u"Le fichier html n'a pas été trouvé dans %s") % os.path.join(parametres['datadir'], 'packages')) ## Les variables à remplacer : page = u"%s / %s" % (n_page, pages) titre = parametres['titre'] niveau = parametres['niveau'] t = localtime() if t.tm_mon > 8: year_text = _(u'Année ') + str(t.tm_year) + '/' + str(t.tm_year+1) else: year_text = _(u'Année ') + str(t.tm_year-1) + '/' + str(t.tm_year) age = year_text modele = codecs.open(source, encoding='utf-8', mode='r') for line in modele: if master_fin in line: break if n > 0: temp = findall('##{{[A-Z]*}}##',line) if temp: occ = temp[0][4:len(temp)-5].lower() line = line.replace(temp[0], eval(occ)) dest.write(line) if master in line: n = 1 modele.close() return