forked from ScoDoc/ScoDoc
completed elimination of jaxml
This commit is contained in:
parent
9d6e882199
commit
5906ba6283
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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(
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
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)
|
||||
|
||||
doc._pop() # /formation
|
||||
|
||||
#---
|
||||
print 'Writing XML file: ', OUTPUT_FILENAME
|
||||
f = open(OUTPUT_FILENAME, 'w')
|
||||
# ---
|
||||
print "Writing XML file: ", OUTPUT_FILENAME
|
||||
f = open(OUTPUT_FILENAME, "w")
|
||||
f.write("""<?xml version="1.0" encoding="utf-8"?>\n""")
|
||||
f.write(str(doc))
|
||||
f.close()
|
||||
|
@ -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
|
||||
|
141
tests/test_export_xml.py
Normal file
141
tests/test_export_xml.py
Normal file
@ -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}],
|
||||
"""<?xml version="1.0" encoding="utf-8"?>
|
||||
<infos id="1">
|
||||
<ues note="10" />
|
||||
<ues />
|
||||
<ues valeur="25" />
|
||||
</infos>
|
||||
<infos bis="2" />
|
||||
""",
|
||||
),
|
||||
([], """"""),
|
||||
(
|
||||
["allo"],
|
||||
"""<?xml version="1.0" encoding="utf-8"?>
|
||||
<infos code="allo" />
|
||||
""",
|
||||
),
|
||||
(
|
||||
[{}],
|
||||
"""<?xml version="1.0" encoding="utf-8"?>
|
||||
<infos />
|
||||
""",
|
||||
),
|
||||
(
|
||||
[{"x": 1}],
|
||||
"""<?xml version="1.0" encoding="utf-8"?>
|
||||
<infos x="1" />
|
||||
""",
|
||||
),
|
||||
(
|
||||
[{"y": [1, 2, 3], "x": 1}],
|
||||
"""<?xml version="1.0" encoding="utf-8"?>
|
||||
<infos x="1">
|
||||
<y code="1" />
|
||||
<y code="2" />
|
||||
<y code="3" />
|
||||
</infos>
|
||||
""",
|
||||
),
|
||||
(
|
||||
[{"y": [{"x": 1}, {"y": [1, 2, 3]}], "x": 1}],
|
||||
"""<?xml version="1.0" encoding="utf-8"?>
|
||||
<infos x="1">
|
||||
<y x="1" />
|
||||
<y>
|
||||
<y code="1" />
|
||||
<y code="2" />
|
||||
<y code="3" />
|
||||
</y>
|
||||
</infos>
|
||||
""",
|
||||
),
|
||||
):
|
||||
# x = scu.simple_dictlist2xml(data, tagname="infos")
|
||||
y = sco_xml.simple_dictlist2xml(data, tagname="infos")
|
||||
assert xmls_compare(expected_result, y)
|
||||
# print("""({}, '''{}'''),""".format(data, str(x)))
|
||||
|
||||
# test du sendXML compatible ScoDoc7
|
||||
etuds = [{"x": 1, "etuds": ["allo", "mama"]}, {"x": 2, "etuds": ["un", "deux"]}]
|
||||
# Le résultat de l'ancien print(sendXML(None, etuds, tagname="etudiants"))
|
||||
expected_result = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<etudiants_list>
|
||||
<etudiants x="1">
|
||||
<etuds code="allo" />
|
||||
<etuds code="mama" />
|
||||
</etudiants>
|
||||
<etudiants x="2">
|
||||
<etuds code="un" />
|
||||
<etuds code="deux" />
|
||||
</etudiants>
|
||||
</etudiants_list>
|
||||
"""
|
||||
|
||||
assert xmls_compare(
|
||||
expected_result,
|
||||
sco_xml.simple_dictlist2xml([{"etudiant": etuds}], tagname="etudiant_list"),
|
||||
)
|
||||
|
||||
# ---- Tables
|
||||
T = GenTable(
|
||||
rows=[{"nom": "Toto", "age": 26}, {"nom": "Titi", "age": 21}],
|
||||
columns_ids=("nom", "age"),
|
||||
)
|
||||
print(T.xml())
|
||||
|
||||
expected_result = """
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<table origin="" caption="" id="gt_806883">
|
||||
<row>
|
||||
<nom value="Toto" />
|
||||
<age value="26" />
|
||||
</row>
|
||||
<row>
|
||||
<nom value="Titi" />
|
||||
<age value="21" />
|
||||
</row>
|
||||
</table>
|
||||
"""
|
Loading…
Reference in New Issue
Block a user