forked from ScoDoc/DocScoDoc
Ajout image de fond sur PV jury et lettres individuelles de décision.
This commit is contained in:
parent
439571862b
commit
6160d48e9e
@ -69,7 +69,7 @@ def SU(s):
|
|||||||
# Remplace caractères composés
|
# Remplace caractères composés
|
||||||
# eg 'e\xcc\x81' COMBINING ACUTE ACCENT par '\xc3\xa9' LATIN SMALL LETTER E WITH ACUTE
|
# eg 'e\xcc\x81' COMBINING ACUTE ACCENT par '\xc3\xa9' LATIN SMALL LETTER E WITH ACUTE
|
||||||
# car les "combining accents" ne sont pas traités par ReportLab mais peuvent
|
# car les "combining accents" ne sont pas traités par ReportLab mais peuvent
|
||||||
# nous être envoyés par certains navigaters ou imports
|
# nous être envoyés par certains navigateurs ou imports
|
||||||
u = unicodedata.normalize("NFC", unicode(s, SCO_ENCODING, "replace"))
|
u = unicodedata.normalize("NFC", unicode(s, SCO_ENCODING, "replace"))
|
||||||
return u.encode("utf8")
|
return u.encode("utf8")
|
||||||
|
|
||||||
@ -203,6 +203,7 @@ class ScolarsPageTemplate(PageTemplate):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
canvas.saveState()
|
canvas.saveState()
|
||||||
|
# ---- Logo: a small image, positionned at top left of the page
|
||||||
if self.logo is not None:
|
if self.logo is not None:
|
||||||
# draws the logo if it exists
|
# draws the logo if it exists
|
||||||
((width, height), image) = self.logo
|
((width, height), image) = self.logo
|
||||||
|
@ -786,6 +786,77 @@ vu la délibération de la commission %(Type)s en date du %(Date)s présidée pa
|
|||||||
"category": "pvpdf",
|
"category": "pvpdf",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"PV_WITH_BACKGROUND",
|
||||||
|
{
|
||||||
|
"initvalue": 0,
|
||||||
|
"title": "Mettre l'image de fond sur les PV de jury (paysage)",
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"labels": ["non", "oui"],
|
||||||
|
"category": "pvpdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"PV_WITH_HEADER",
|
||||||
|
{
|
||||||
|
"initvalue": 1, # legacy
|
||||||
|
"title": "Ajouter l'en-tête sur les PV (paysage)",
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"labels": ["non", "oui"],
|
||||||
|
"category": "pvpdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"PV_WITH_FOOTER",
|
||||||
|
{
|
||||||
|
"initvalue": 1, # legacy
|
||||||
|
"title": "Ajouter le pied de page sur les PV (paysage)",
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"labels": ["non", "oui"],
|
||||||
|
"category": "pvpdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
# marges PV paysages (en millimètres)
|
||||||
|
(
|
||||||
|
"pv_left_margin",
|
||||||
|
{
|
||||||
|
"initvalue": 0,
|
||||||
|
"size": 10,
|
||||||
|
"title": "Marge gauche PV en mm",
|
||||||
|
"type": "float",
|
||||||
|
"category": "pvpdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pv_top_margin",
|
||||||
|
{
|
||||||
|
"initvalue": 23,
|
||||||
|
"size": 10,
|
||||||
|
"title": "Marge haute PV",
|
||||||
|
"type": "float",
|
||||||
|
"category": "pvpdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pv_right_margin",
|
||||||
|
{
|
||||||
|
"initvalue": 0,
|
||||||
|
"size": 10,
|
||||||
|
"title": "Marge droite PV",
|
||||||
|
"type": "float",
|
||||||
|
"category": "pvpdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"pv_bottom_margin",
|
||||||
|
{
|
||||||
|
"initvalue": 5,
|
||||||
|
"size": 10,
|
||||||
|
"title": "Marge basse PV",
|
||||||
|
"type": "float",
|
||||||
|
"category": "pvpdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"PV_LETTER_DIPLOMA_SIGNATURE",
|
"PV_LETTER_DIPLOMA_SIGNATURE",
|
||||||
{
|
{
|
||||||
@ -872,11 +943,21 @@ s'est réuni le %(date_jury)s.
|
|||||||
"category": "pvpdf",
|
"category": "pvpdf",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"PV_LETTER_WITH_BACKGROUND",
|
||||||
|
{
|
||||||
|
"initvalue": 0,
|
||||||
|
"title": "Mettre l'image de fond sur les lettres individuelles de décision",
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"labels": ["non", "oui"],
|
||||||
|
"category": "pvpdf",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"PV_LETTER_WITH_HEADER",
|
"PV_LETTER_WITH_HEADER",
|
||||||
{
|
{
|
||||||
"initvalue": 0,
|
"initvalue": 0,
|
||||||
"title": "Imprimer le logo en tête des lettres individuelles de décision",
|
"title": "Ajouter l'en-tête sur les lettres individuelles de décision",
|
||||||
"input_type": "boolcheckbox",
|
"input_type": "boolcheckbox",
|
||||||
"labels": ["non", "oui"],
|
"labels": ["non", "oui"],
|
||||||
"category": "pvpdf",
|
"category": "pvpdf",
|
||||||
@ -886,7 +967,7 @@ s'est réuni le %(date_jury)s.
|
|||||||
"PV_LETTER_WITH_FOOTER",
|
"PV_LETTER_WITH_FOOTER",
|
||||||
{
|
{
|
||||||
"initvalue": 0,
|
"initvalue": 0,
|
||||||
"title": "Imprimer le pied de page sur les lettres individuelles de décision",
|
"title": "Ajouter le pied de page sur les lettres individuelles de décision",
|
||||||
"input_type": "boolcheckbox",
|
"input_type": "boolcheckbox",
|
||||||
"labels": ["non", "oui"],
|
"labels": ["non", "oui"],
|
||||||
"category": "pvpdf",
|
"category": "pvpdf",
|
||||||
@ -1633,8 +1714,7 @@ class sco_base_preferences:
|
|||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""Load all preferences from db
|
"""Load all preferences from db"""
|
||||||
"""
|
|
||||||
log("loading preferences")
|
log("loading preferences")
|
||||||
try:
|
try:
|
||||||
GSL.acquire()
|
GSL.acquire()
|
||||||
@ -2063,12 +2143,14 @@ def doc_preferences(context):
|
|||||||
L = []
|
L = []
|
||||||
for cat, cat_descr in PREF_CATEGORIES:
|
for cat, cat_descr in PREF_CATEGORIES:
|
||||||
L.append([""])
|
L.append([""])
|
||||||
L.append(["## " + cat_descr.get("title", "") ])
|
L.append(["## " + cat_descr.get("title", "")])
|
||||||
L.append([""])
|
L.append([""])
|
||||||
L.append( ["Nom", " ", " " ] )
|
L.append(["Nom", " ", " "])
|
||||||
L.append( ["----", "----", "----"] )
|
L.append(["----", "----", "----"])
|
||||||
for pref_name, pref in PREFS:
|
for pref_name, pref in PREFS:
|
||||||
if pref["category"] == cat:
|
if pref["category"] == cat:
|
||||||
L.append(['`'+pref_name+'`', pref["title"], pref.get("explanation", "")])
|
L.append(
|
||||||
|
["`" + pref_name + "`", pref["title"], pref.get("explanation", "")]
|
||||||
|
)
|
||||||
|
|
||||||
return "\n".join([" | ".join(x) for x in L])
|
return "\n".join([" | ".join(x) for x in L])
|
||||||
|
78
sco_pvpdf.py
78
sco_pvpdf.py
@ -94,7 +94,9 @@ def pageFooter(canvas, doc, logo, preferences, with_page_numbers=True):
|
|||||||
("RIGHTPADDING", (-1, 0), (-1, 0), 1 * cm),
|
("RIGHTPADDING", (-1, 0), (-1, 0), 1 * cm),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
elems = [p, logo]
|
elems = [p]
|
||||||
|
if logo:
|
||||||
|
elems.append(logo)
|
||||||
colWidths = [None, LOGO_FOOTER_WIDTH + 2 * mm]
|
colWidths = [None, LOGO_FOOTER_WIDTH + 2 * mm]
|
||||||
if with_page_numbers:
|
if with_page_numbers:
|
||||||
elems.append(np)
|
elems.append(np)
|
||||||
@ -124,14 +126,14 @@ def pageHeader(canvas, doc, logo, preferences, only_on_first_page=False):
|
|||||||
id="monheader",
|
id="monheader",
|
||||||
showBoundary=0,
|
showBoundary=0,
|
||||||
)
|
)
|
||||||
|
if logo:
|
||||||
canvas.saveState() # is it necessary ?
|
canvas.saveState() # is it necessary ?
|
||||||
head.addFromList([logo], canvas)
|
head.addFromList([logo], canvas)
|
||||||
canvas.restoreState()
|
canvas.restoreState()
|
||||||
|
|
||||||
|
|
||||||
class CourrierIndividuelTemplate(PageTemplate):
|
class CourrierIndividuelTemplate(PageTemplate):
|
||||||
"""Template pour courrier avisant des decisions de jury (1 page /etudiant)
|
"""Template pour courrier avisant des decisions de jury (1 page /etudiant)"""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -156,6 +158,13 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||||||
self.preferences = preferences
|
self.preferences = preferences
|
||||||
self.force_header = force_header
|
self.force_header = force_header
|
||||||
self.force_footer = force_footer
|
self.force_footer = force_footer
|
||||||
|
self.with_footer = (
|
||||||
|
self.force_footer or self.preferences["PV_LETTER_WITH_HEADER"]
|
||||||
|
)
|
||||||
|
self.with_header = (
|
||||||
|
self.force_header or self.preferences["PV_LETTER_WITH_FOOTER"]
|
||||||
|
)
|
||||||
|
self.with_page_background = self.preferences["PV_LETTER_WITH_BACKGROUND"]
|
||||||
self.with_page_numbers = False
|
self.with_page_numbers = False
|
||||||
self.header_only_on_first_page = False
|
self.header_only_on_first_page = False
|
||||||
# Our doc is made of a single frame
|
# Our doc is made of a single frame
|
||||||
@ -175,13 +184,29 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||||||
|
|
||||||
PageTemplate.__init__(self, template_name, [content])
|
PageTemplate.__init__(self, template_name, [content])
|
||||||
|
|
||||||
|
self.background_image_filename = None
|
||||||
|
self.logo_footer = None
|
||||||
|
self.logo_header = None
|
||||||
|
for suffix in LOGOS_IMAGES_ALLOWED_TYPES:
|
||||||
|
if template_name == "PVJuryTemplate":
|
||||||
|
fn = image_dir + "/pvjury_background" + "." + suffix
|
||||||
|
else:
|
||||||
|
fn = image_dir + "/letter_background" + "." + suffix
|
||||||
|
if os.path.exists(fn):
|
||||||
|
self.background_image_filename = fn
|
||||||
|
|
||||||
|
fn = image_dir + "/logo_footer" + "." + suffix
|
||||||
|
if os.path.exists(fn):
|
||||||
self.logo_footer = Image(
|
self.logo_footer = Image(
|
||||||
image_dir + "/logo_footer.jpg",
|
fn,
|
||||||
height=LOGO_FOOTER_HEIGHT,
|
height=LOGO_FOOTER_HEIGHT,
|
||||||
width=LOGO_FOOTER_WIDTH,
|
width=LOGO_FOOTER_WIDTH,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fn = image_dir + "/logo_header" + "." + suffix
|
||||||
|
if os.path.exists(fn):
|
||||||
self.logo_header = Image(
|
self.logo_header = Image(
|
||||||
image_dir + "/logo_header.jpg",
|
fn,
|
||||||
height=LOGO_HEADER_HEIGHT,
|
height=LOGO_HEADER_HEIGHT,
|
||||||
width=LOGO_HEADER_WIDTH,
|
width=LOGO_HEADER_WIDTH,
|
||||||
)
|
)
|
||||||
@ -201,8 +226,15 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||||||
txt = SU(bm)
|
txt = SU(bm)
|
||||||
canvas.bookmarkPage(key)
|
canvas.bookmarkPage(key)
|
||||||
canvas.addOutlineEntry(txt, bm)
|
canvas.addOutlineEntry(txt, bm)
|
||||||
if self.force_footer or self.preferences["PV_LETTER_WITH_HEADER"]:
|
|
||||||
# --- Add header
|
# ---- Background image
|
||||||
|
if self.background_image_filename and self.with_page_background:
|
||||||
|
canvas.drawImage(
|
||||||
|
self.background_image_filename, 0, 0, doc.pagesize[0], doc.pagesize[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---- Header/Footer
|
||||||
|
if self.with_header:
|
||||||
pageHeader(
|
pageHeader(
|
||||||
canvas,
|
canvas,
|
||||||
doc,
|
doc,
|
||||||
@ -210,8 +242,7 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||||||
self.preferences,
|
self.preferences,
|
||||||
self.header_only_on_first_page,
|
self.header_only_on_first_page,
|
||||||
)
|
)
|
||||||
if self.force_footer or self.preferences["PV_LETTER_WITH_FOOTER"]:
|
if self.with_footer:
|
||||||
# --- Add footer
|
|
||||||
pageFooter(
|
pageFooter(
|
||||||
canvas,
|
canvas,
|
||||||
doc,
|
doc,
|
||||||
@ -222,8 +253,7 @@ class CourrierIndividuelTemplate(PageTemplate):
|
|||||||
|
|
||||||
|
|
||||||
class PVTemplate(CourrierIndividuelTemplate):
|
class PVTemplate(CourrierIndividuelTemplate):
|
||||||
"""Template pour les pages des PV de jury
|
"""Template pour les pages des PV de jury"""
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -231,10 +261,17 @@ class PVTemplate(CourrierIndividuelTemplate):
|
|||||||
author=None,
|
author=None,
|
||||||
title=None,
|
title=None,
|
||||||
subject=None,
|
subject=None,
|
||||||
margins=(0, 23, 0, 5), # additional margins in mm (left,top,right, bottom)
|
margins=None, # additional margins in mm (left,top,right, bottom)
|
||||||
image_dir="",
|
image_dir="",
|
||||||
preferences=None, # dictionnary with preferences, required
|
preferences=None, # dictionnary with preferences, required
|
||||||
):
|
):
|
||||||
|
if margins is None:
|
||||||
|
margins = (
|
||||||
|
preferences["pv_left_margin"],
|
||||||
|
preferences["pv_top_margin"],
|
||||||
|
preferences["pv_right_margin"],
|
||||||
|
preferences["pv_bottom_margin"],
|
||||||
|
)
|
||||||
CourrierIndividuelTemplate.__init__(
|
CourrierIndividuelTemplate.__init__(
|
||||||
self,
|
self,
|
||||||
document,
|
document,
|
||||||
@ -250,6 +287,9 @@ class PVTemplate(CourrierIndividuelTemplate):
|
|||||||
)
|
)
|
||||||
self.with_page_numbers = True
|
self.with_page_numbers = True
|
||||||
self.header_only_on_first_page = True
|
self.header_only_on_first_page = True
|
||||||
|
self.with_header = self.preferences["PV_WITH_HEADER"]
|
||||||
|
self.with_footer = self.preferences["PV_WITH_FOOTER"]
|
||||||
|
self.with_page_background = self.preferences["PV_WITH_BACKGROUND"]
|
||||||
|
|
||||||
def afterDrawPage(self, canvas, doc):
|
def afterDrawPage(self, canvas, doc):
|
||||||
"""Called after all flowables have been drawn on a page"""
|
"""Called after all flowables have been drawn on a page"""
|
||||||
@ -437,9 +477,13 @@ def pdf_lettre_individuelle(sem, decision, etud, params, signature=None, context
|
|||||||
s = "s"
|
s = "s"
|
||||||
else:
|
else:
|
||||||
s = ""
|
s = ""
|
||||||
params["autorisations_txt"] = (
|
params[
|
||||||
"""Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>"""
|
"autorisations_txt"
|
||||||
% (etud["ne"], s, s, decision["autorisations_descr"])
|
] = """Vous êtes autorisé%s à continuer dans le%s semestre%s : <b>%s</b>""" % (
|
||||||
|
etud["ne"],
|
||||||
|
s,
|
||||||
|
s,
|
||||||
|
decision["autorisations_descr"],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
params["autorisations_txt"] = ""
|
params["autorisations_txt"] = ""
|
||||||
@ -538,9 +582,6 @@ def _make_signature_image(signature, leftindent, formsemestre_id, context=None):
|
|||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------
|
# ----------------------------------------------
|
||||||
# PV complet, tableau en format paysage
|
|
||||||
|
|
||||||
|
|
||||||
def pvjury_pdf(
|
def pvjury_pdf(
|
||||||
context,
|
context,
|
||||||
dpv,
|
dpv,
|
||||||
@ -554,6 +595,7 @@ def pvjury_pdf(
|
|||||||
anonymous=False,
|
anonymous=False,
|
||||||
):
|
):
|
||||||
"""Doc PDF récapitulant les décisions de jury
|
"""Doc PDF récapitulant les décisions de jury
|
||||||
|
(tableau en format paysage)
|
||||||
dpv: result of dict_pvjury
|
dpv: result of dict_pvjury
|
||||||
"""
|
"""
|
||||||
if not dpv:
|
if not dpv:
|
||||||
|
20
sco_utils.py
20
sco_utils.py
@ -156,15 +156,14 @@ def fmt_note(val, note_max=None, keep_numeric=False):
|
|||||||
|
|
||||||
|
|
||||||
def fmt_coef(val):
|
def fmt_coef(val):
|
||||||
"""Conversion valeur coefficient (float) en chaine
|
"""Conversion valeur coefficient (float) en chaine"""
|
||||||
"""
|
|
||||||
if val < 0.01:
|
if val < 0.01:
|
||||||
return "%g" % val # unusually small value
|
return "%g" % val # unusually small value
|
||||||
return "%g" % round(val, 2)
|
return "%g" % round(val, 2)
|
||||||
|
|
||||||
|
|
||||||
def fmt_abs(val):
|
def fmt_abs(val):
|
||||||
""" Conversion absences en chaine. val est une list [nb_abs_total, nb_abs_justifiees
|
"""Conversion absences en chaine. val est une list [nb_abs_total, nb_abs_justifiees
|
||||||
=> NbAbs / Nb_justifiees
|
=> NbAbs / Nb_justifiees
|
||||||
"""
|
"""
|
||||||
return "%s / %s" % (val[0], val[1])
|
return "%s / %s" % (val[0], val[1])
|
||||||
@ -194,7 +193,7 @@ GSL = thread.allocate_lock() # Global ScoDoc Lock
|
|||||||
|
|
||||||
if "INSTANCE_HOME" in os.environ:
|
if "INSTANCE_HOME" in os.environ:
|
||||||
# ----- Repertoire "var" (local)
|
# ----- Repertoire "var" (local)
|
||||||
SCODOC_VAR_DIR = os.path.join( os.environ["INSTANCE_HOME"], "var", "scodoc" )
|
SCODOC_VAR_DIR = os.path.join(os.environ["INSTANCE_HOME"], "var", "scodoc")
|
||||||
# ----- Version information
|
# ----- Version information
|
||||||
SCODOC_VERSION_DIR = os.path.join(SCODOC_VAR_DIR, "config", "version")
|
SCODOC_VERSION_DIR = os.path.join(SCODOC_VAR_DIR, "config", "version")
|
||||||
# ----- Repertoire tmp
|
# ----- Repertoire tmp
|
||||||
@ -210,7 +209,6 @@ if "INSTANCE_HOME" in os.environ:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ----- Lecture du fichier de configuration
|
# ----- Lecture du fichier de configuration
|
||||||
SCO_SRCDIR = os.path.split(VERSION.__file__)[0]
|
SCO_SRCDIR = os.path.split(VERSION.__file__)[0]
|
||||||
if SCO_SRCDIR:
|
if SCO_SRCDIR:
|
||||||
@ -231,7 +229,9 @@ except:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
if hasattr(CONFIG, "CODES_EXPL"):
|
if hasattr(CONFIG, "CODES_EXPL"):
|
||||||
CODES_EXPL.update(CONFIG.CODES_EXPL) # permet de customiser les explications de codes
|
CODES_EXPL.update(
|
||||||
|
CONFIG.CODES_EXPL
|
||||||
|
) # permet de customiser les explications de codes
|
||||||
|
|
||||||
|
|
||||||
if CONFIG.CUSTOM_HTML_HEADER:
|
if CONFIG.CUSTOM_HTML_HEADER:
|
||||||
@ -296,6 +296,8 @@ PDF_MIMETYPE = "application/pdf"
|
|||||||
XML_MIMETYPE = "text/xml"
|
XML_MIMETYPE = "text/xml"
|
||||||
JSON_MIMETYPE = "application/json"
|
JSON_MIMETYPE = "application/json"
|
||||||
|
|
||||||
|
LOGOS_IMAGES_ALLOWED_TYPES = ("jpg", "png") # remind that PIL does not read pdf
|
||||||
|
|
||||||
|
|
||||||
class DictDefault(dict): # obsolete, use collections.defaultdict
|
class DictDefault(dict): # obsolete, use collections.defaultdict
|
||||||
"""A dictionnary with default value for all keys
|
"""A dictionnary with default value for all keys
|
||||||
@ -462,8 +464,7 @@ ALPHANUM_EXP = re.compile(r"^[\w-]+$", re.UNICODE)
|
|||||||
|
|
||||||
|
|
||||||
def is_valid_code_nip(s):
|
def is_valid_code_nip(s):
|
||||||
"""True si s peut être un code NIP: au moins 6 chiffres décimaux
|
"""True si s peut être un code NIP: au moins 6 chiffres décimaux"""
|
||||||
"""
|
|
||||||
if not s:
|
if not s:
|
||||||
return False
|
return False
|
||||||
return re.match(r"^[0-9]{6,32}$", s)
|
return re.match(r"^[0-9]{6,32}$", s)
|
||||||
@ -747,8 +748,7 @@ from sgmllib import SGMLParser
|
|||||||
|
|
||||||
|
|
||||||
class html2txt_parser(SGMLParser):
|
class html2txt_parser(SGMLParser):
|
||||||
"""html2txt()
|
"""html2txt()"""
|
||||||
"""
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""reset() --> initialize the parser"""
|
"""reset() --> initialize the parser"""
|
||||||
|
Loading…
Reference in New Issue
Block a user