diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index aec0db8f61..3f83e0188b 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -37,24 +37,16 @@ from hashlib import md5
import numbers
import os
import re
-import sys
+import six
import six.moves._thread
+import sys
import time
import types
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
import six.moves.urllib.request, six.moves.urllib.error, six.moves.urllib.parse
+from xml.etree.ElementTree import Element
-
-# XML generation package (apt-get install jaxml)
-import jaxml # XXX
-
-try:
- import six
-
- STRING_TYPES = six.string_types
-except ImportError:
- # fallback for very old ScoDoc instances
- STRING_TYPES = bytes
+STRING_TYPES = six.string_types
from PIL import Image as PILImage
@@ -876,9 +868,8 @@ def _sco_error_response(context, msg, format="html", REQUEST=None):
raise sco_exceptions.ScoValueError(msg)
elif format == "xml":
REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE)
- doc = jaxml.XML_document(encoding=SCO_ENCODING)
- doc.error(msg=msg)
- return repr(doc)
+ doc = ElementTree.Element("error", msg=msg)
+ return sco_xml.XML_HEADER + ElementTree.tostring(doc)
elif format == "json":
REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE)
return "undefined" # XXX voir quoi faire en cas d'erreur json
diff --git a/app/views/absences.py b/app/views/absences.py
index 295e2b0b23..343a7b1b3a 100644
--- a/app/views/absences.py
+++ b/app/views/absences.py
@@ -46,16 +46,16 @@ L'API de plus bas niveau est en gros:
"""
-import string
-import re
-import time
+import calendar
+import cgi
import datetime
import dateutil
import dateutil.parser
-import calendar
+import re
import six.moves.urllib.request, six.moves.urllib.parse, six.moves.urllib.error
-import cgi
-import jaxml
+import string
+import time
+from xml.etree import ElementTree
from flask import g
from flask import current_app
@@ -93,6 +93,7 @@ from app.scodoc import sco_groups
from app.scodoc import sco_groups_view
from app.scodoc import sco_moduleimpl
from app.scodoc import sco_preferences
+from app.scodoc import sco_xml
CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS
@@ -1505,22 +1506,22 @@ def XMLgetAbsEtud(context, beg_date="", end_date="", REQUEST=None):
Abs = sco_abs.ListeAbsDate(context, etud["etudid"], beg_date, end_date)
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
- doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
- doc.absences(etudid=etud["etudid"], beg_date=beg_date, end_date=end_date)
- doc._push()
+ doc = ElementTree.Element(
+ "absences", etudid=etud["etudid"], beg_date=beg_date, end_date=end_date
+ )
for a in Abs:
if a["estabs"]: # ne donne pas les justifications si pas d'absence
- doc._push()
- doc.abs(
- begin=a["begin"],
- end=a["end"],
- description=a["description"],
- justified=a["estjust"],
+ doc.append(
+ ElementTree.Element(
+ "abs",
+ begin=a["begin"],
+ end=a["end"],
+ description=a["description"],
+ justified=a["estjust"],
+ )
)
- doc._pop()
- doc._pop()
log("XMLgetAbsEtud (%gs)" % (time.time() - t0))
- return repr(doc)
+ return sco_xml.XML_HEADER + ElementTree.tostring(doc)
context.populate(globals())
diff --git a/app/views/notes.py b/app/views/notes.py
index d95d802063..62a33e5713 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -33,9 +33,9 @@ Emmanuel Viennet, 2021
import sys
import time
import datetime
-import jaxml
import pprint
from operator import itemgetter
+from xml.etree import ElementTree
from flask import url_for, g
from flask import current_app
@@ -128,6 +128,7 @@ from app.scodoc import sco_tag_module
from app.scodoc import sco_ue_external
from app.scodoc import sco_undo_notes
from app.scodoc import sco_users
+from app.scodoc import sco_xml
from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_pdf import PDFLOCK
from app.scodoc.sco_permissions import Permission
@@ -650,13 +651,11 @@ def XMLgetFormsemestres(context, etape_apo=None, formsemestre_id=None, REQUEST=N
args["formsemestre_id"] = formsemestre_id
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
- doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
- doc.formsemestrelist()
+ doc = ElementTree.Element("formsemestrelist")
for sem in sco_formsemestre.do_formsemestre_list(context, args=args):
- doc._push()
- doc.formsemestre(sem)
- doc._pop()
- return repr(doc)
+ doc.append("formsemestre", **sem)
+
+ return sco_xml.XML_HEADER + ElementTree.tostring(doc)
sco_publish(
diff --git a/app/views/users.py b/app/views/users.py
index 4d72c2936f..c57cbddc6d 100644
--- a/app/views/users.py
+++ b/app/views/users.py
@@ -34,7 +34,7 @@ Vues s'appuyant sur auth et sco_users
Emmanuel Viennet, 2021
"""
import re
-import jaxml
+from xml.etree import ElementTree
from flask import g
from flask_login import current_user
@@ -54,6 +54,7 @@ from app.decorators import (
from app.scodoc import html_sco_header
from app.scodoc import sco_users
from app.scodoc import sco_utils as scu
+from app.scodoc import sco_xml
from app.scodoc.notes_log import log
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError
from app.scodoc.sco_permissions_check import can_handle_passwd
@@ -341,7 +342,7 @@ def create_user_form(context, REQUEST, user_name=None, edit=0):
edit = 0
try:
force = int(vals["force"][0])
- except:
+ except (ValueError, TypeError):
force = 0
if edit:
@@ -471,13 +472,12 @@ def get_user_list_xml(dept=None, start="", limit=25, REQUEST=None):
]
if REQUEST:
REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE)
- doc = jaxml.XML_document(encoding=scu.SCO_ENCODING)
- doc.results()
+ doc = ElementTree.Element("results")
for user in userlist[:limit]:
- doc._push()
- doc.rs(user.get_nomplogin(), id=user.id, info="")
- doc._pop()
- return repr(doc)
+ x_rs = ElementTree.Element("rs", id=user.id, info="")
+ x_rs.text = user.get_nomplogin()
+ doc.append(x_rs)
+ return sco_xml.XML_HEADER + ElementTree.tostring(doc)
@bp.route("/form_change_password")
diff --git a/config/install_debian10.sh b/config/install_debian10.sh
index 9269af5426..fd80ef41e1 100755
--- a/config/install_debian10.sh
+++ b/config/install_debian10.sh
@@ -87,9 +87,8 @@ apt-get -y install postgresql
apt-get -y install graphviz
# ------------ INSTALL DES EXTENSIONS PYTHON (2.7)
-# XXX to fix: pip in our env
+# ScoDoc8 uses pip in our env
apt-get -y install python-docutils
-apt-get -y install python-jaxml
apt-get -y install python-psycopg2
apt-get -y install python-pyrss2gen
apt-get -y install python-pil python-reportlab
diff --git a/misc/iscid_create_formation_from_xls.py b/misc/iscid_create_formation_from_xls.py
index b41a9d9a16..4ab38a741b 100644
--- a/misc/iscid_create_formation_from_xls.py
+++ b/misc/iscid_create_formation_from_xls.py
@@ -6,37 +6,38 @@
# XXX TODO : a tester et moderniser (ects, verifier champs, python 3, importer codes depuis ScoDoc ?)
import os, sys, pdb, pprint
-from openpyxl import load_workbook # apt-get install python-openpyxl
-import jaxml
-SCO_ENCODING = 'utf-8'
+from openpyxl import load_workbook # apt-get install python-openpyxl
+from xml.etree import ElementTree
+
+SCO_ENCODING = "utf-8"
INPUT_FILENAME = "/tmp/Bachelor.xlsx"
-OUTPUT_FILENAME= os.path.splitext(INPUT_FILENAME)[0] + '.xml'
+OUTPUT_FILENAME = os.path.splitext(INPUT_FILENAME)[0] + ".xml"
-FIRST_SHEET_IDX=1 # saute première feuille du classeur
+FIRST_SHEET_IDX = 1 # saute première feuille du classeur
# Code de ScoDoc (sco_utils.py)
-UE_STANDARD = 0 # UE "fondamentale"
-UE_SPORT = 1 # bonus "sport"
-UE_STAGE_LP = 2 # ue "projet tuteuré et stage" dans les Lic. Pro.
-UE_ELECTIVE = 4 # UE "élective" dans certains parcours (UCAC?, ISCID)
-UE_PROFESSIONNELLE = 5 # UE "professionnelle" (ISCID, ...)
+UE_STANDARD = 0 # UE "fondamentale"
+UE_SPORT = 1 # bonus "sport"
+UE_STAGE_LP = 2 # ue "projet tuteuré et stage" dans les Lic. Pro.
+UE_ELECTIVE = 4 # UE "élective" dans certains parcours (UCAC?, ISCID)
+UE_PROFESSIONNELLE = 5 # UE "professionnelle" (ISCID, ...)
# Code du fichier Excel:
-UE_TYPE2CODE = { u'UE F' : UE_STANDARD, u'UE E' : UE_ELECTIVE }
+UE_TYPE2CODE = {u"UE F": UE_STANDARD, u"UE E": UE_ELECTIVE}
# Lecture du fichier Excel
UE = []
wb = load_workbook(filename=INPUT_FILENAME)
-#print wb.get_sheet_names()
+# print wb.get_sheet_names()
for sheet_name in wb.get_sheet_names()[FIRST_SHEET_IDX:]:
- print 'Importing sheet %s' % sheet_name
+ print "Importing sheet %s" % sheet_name
sheet = wb.get_sheet_by_name(sheet_name)
# Avance jusqu'à trouver le titre 'CODE' en premiere colonne
- i=0
- while i < len(sheet.rows) and sheet.rows[i][0].value != 'CODE':
+ i = 0
+ while i < len(sheet.rows) and sheet.rows[i][0].value != "CODE":
i = i + 1
i = i + 1
@@ -48,81 +49,93 @@ for sheet_name in wb.get_sheet_names()[FIRST_SHEET_IDX:]:
if ue:
UE.append(ue)
# creation UE
- acronyme = code # ici l'acronyme d'UE est le code du module
- if not acronyme and (i < len(sheet.rows)-1):
- acronyme = sheet.rows[i+1][0].value # code module sur ligne suivante
- #print acronyme
- if acronyme: # tres specifique: deduit l'acronyme d'UE du code module
- parts = acronyme.split(u'-')
- parts[-1] = parts[-1][-1] # ne garde que le dernier chiffre
- acronyme = u'-'.join(parts) # B1-LV1-EN1 -> B1-LV1-1
- #print '->', acronyme
+ acronyme = code # ici l'acronyme d'UE est le code du module
+ if not acronyme and (i < len(sheet.rows) - 1):
+ acronyme = sheet.rows[i + 1][0].value # code module sur ligne suivante
+ # print acronyme
+ if acronyme: # tres specifique: deduit l'acronyme d'UE du code module
+ parts = acronyme.split(u"-")
+ parts[-1] = parts[-1][-1] # ne garde que le dernier chiffre
+ acronyme = u"-".join(parts) # B1-LV1-EN1 -> B1-LV1-1
+ # print '->', acronyme
if not acronyme:
- acronyme = sheet.rows[i][3].value # fallback: titre
- ue = { 'acronyme' : acronyme,
- 'titre' : sheet.rows[i][3].value,
- 'ects' : sheet.rows[i][5].value or u"",
- 'type' : UE_TYPE2CODE[type_ue],
- 'numero' : (sheet.rows[i][1].value or 0)*1000 + i*10,
- 'modules' : []
- }
+ acronyme = sheet.rows[i][3].value # fallback: titre
+ ue = {
+ "acronyme": acronyme,
+ "titre": sheet.rows[i][3].value,
+ "ects": sheet.rows[i][5].value or u"",
+ "type": UE_TYPE2CODE[type_ue],
+ "numero": (sheet.rows[i][1].value or 0) * 1000 + i * 10,
+ "modules": [],
+ }
i_ue = i
if code:
- ue['modules'].append( {
- 'code' : code,
- 'heures_td' : sheet.rows[i_ue][4].value or u"",
- 'titre' : sheet.rows[i][3].value,
- 'semestre_id' : sheet.rows[i][1].value,
- 'numero' : i*10
- } )
+ ue["modules"].append(
+ {
+ "code": code,
+ "heures_td": sheet.rows[i_ue][4].value or u"",
+ "titre": sheet.rows[i][3].value,
+ "semestre_id": sheet.rows[i][1].value,
+ "numero": i * 10,
+ }
+ )
- i += 1 # next line
+ i += 1 # next line
if ue:
UE.append(ue)
def sstr(s):
- if type(s) is type(u''):
+ if type(s) is type(u""):
return s.encode(SCO_ENCODING)
else:
return str(s)
-# ----- Write to XML
-doc = jaxml.XML_document( encoding=SCO_ENCODING )
-doc._push()
-doc.formation( acronyme="Bachelor ISCID",
- code_specialite="",
- type_parcours="1001",
- titre_officiel="Bachelor ISCID",
- formation_code="FCOD4",
- version="1",
- titre="Bachelor ISCID",
- formation_id="FORM115"
- )
+# ----- Write to XML
+doc = ElementTree.Element(
+ "formation",
+ acronyme="Bachelor ISCID",
+ code_specialite="",
+ type_parcours="1001",
+ titre_officiel="Bachelor ISCID",
+ formation_code="FCOD4",
+ version="1",
+ titre="Bachelor ISCID",
+ formation_id="FORM115",
+)
for ue in UE:
- doc._push()
- doc.ue( acronyme=sstr(ue['acronyme']), ects=sstr(ue['ects']), titre=sstr(ue['titre']), numero=sstr(ue['numero']), type=sstr(ue['type']) )
- doc._push()
- doc.matiere( titre=sstr(ue['titre']) ) # useless but necessary
- for m in ue['modules']:
- doc._push()
- doc.module( coefficient="1.0", code=sstr(m['code']),
- heures_td=sstr(m['heures_td']),
- titre=sstr(m['titre']), abbrev=sstr(m['titre']),
- semestre_id=sstr(m['semestre_id']),
- numero=sstr(m['numero'])
- )
- doc._pop() # /module
- doc._pop() # /matiere
- doc._pop() # /ue
-
-doc._pop() # /formation
+ x_ue = ElementTree.Element(
+ "ue",
+ acronyme=sstr(ue["acronyme"]),
+ ects=sstr(ue["ects"]),
+ titre=sstr(ue["titre"]),
+ numero=sstr(ue["numero"]),
+ type=sstr(ue["type"]),
+ )
+ doc.append(ue)
+ x_mat = ElementTree.Element(
+ "matiere", titre=sstr(ue["titre"])
+ ) # useless but necessary
+ x_ue.append(x_mat)
+ for m in ue["modules"]:
+ x_mod = ElementTree.Element(
+ "module",
+ coefficient="1.0",
+ code=sstr(m["code"]),
+ heures_td=sstr(m["heures_td"]),
+ titre=sstr(m["titre"]),
+ abbrev=sstr(m["titre"]),
+ semestre_id=sstr(m["semestre_id"]),
+ numero=sstr(m["numero"]),
+ )
+ x_mat.append(x_mod)
-#---
-print 'Writing XML file: ', OUTPUT_FILENAME
-f = open(OUTPUT_FILENAME, 'w')
+# ---
+print "Writing XML file: ", OUTPUT_FILENAME
+f = open(OUTPUT_FILENAME, "w")
+f.write("""\n""")
f.write(str(doc))
f.close()
diff --git a/requirements-2.7.txt b/requirements-2.7.txt
index 5f6d9e96aa..4ce146c0be 100644
--- a/requirements-2.7.txt
+++ b/requirements-2.7.txt
@@ -27,7 +27,6 @@ icalendar==4.0.7
idna==2.10
isort==4.3.21
itsdangerous==1.1.0
-jaxml==3.2
Jinja2==2.11.2
lazy-object-proxy==1.6.0
Mako==1.1.4
diff --git a/tests/test_export_xml.py b/tests/test_export_xml.py
new file mode 100644
index 0000000000..11c438ce19
--- /dev/null
+++ b/tests/test_export_xml.py
@@ -0,0 +1,141 @@
+# -*- coding: UTF-8 -*
+
+"""Unit tests for XML exports
+
+Usage: python -m unittest tests.test_export_xml
+"""
+
+# ScoDoc7 utilisait jaxml, obsolete et non portée en python3
+# On teste ici les fionctions de remplacement, fournies par
+# notre nouveau module sco_xml.py
+
+from __future__ import print_function
+import os
+import re
+import sys
+import unittest
+
+sys.path.append("/mac/ScoDoc")
+
+from app.scodoc import sco_xml
+from app.scodoc.gen_tables import GenTable
+
+# Legacy function
+# import jaxml
+# from app.scodoc import sco_utils as scu
+
+# r = scu.simple_dictlist2xml([{"id": 1, "ues": [{"note": 10}, {}]}], tagname="infos")
+
+
+def xml_normalize(x):
+ "supprime espaces inutiles"
+ x = re.sub(r"\s+", " ", str(x)).strip().replace("> <", "><")
+
+
+def xmls_compare(x, y):
+ return xml_normalize(x) == xml_normalize(y)
+
+
+# expected_result est le résultat de l'ancienne fonction ScoDoc7:
+for (data, expected_result) in (
+ (
+ [{"id": 1, "ues": [{"note": 10}, {}, {"valeur": 25}]}, {"bis": 2}],
+ """
+