Skip to content

Commit

Permalink
Support RCAL g1.3 et factorisation validation XSD
Browse files Browse the repository at this point in the history
  • Loading branch information
lucduron committed Jun 26, 2023
1 parent f1483b9 commit e72e4bb
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 60 deletions.
32 changes: 2 additions & 30 deletions crue10/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os.path
import xml.etree.ElementTree as ET

from crue10.utils import add_default_missing_metadata, DATA_FOLDER_ABSPATH, ExceptionCrue10, \
from crue10.utils import add_default_missing_metadata, check_xml_file, DATA_FOLDER_ABSPATH, ExceptionCrue10, \
ExceptionCrue10Grammar, JINJA_ENV, get_xml_root_from_file, logger, PREFIX, XSI_SCHEMA_LOCATION
from crue10.utils.settings import VERSION_GRAMMAIRE_COURANTE, VERSION_GRAMMAIRE_PRECEDENTE, XML_ENCODING

Expand Down Expand Up @@ -209,35 +209,7 @@ def _write_xml_file(self, xml, folder, **kwargs):
out.write(template_render)

def _check_xml_file(self, file_path):
logger.debug("Validation XSD (grammaire %s) de %s" % (self.version_grammaire, file_path))
errors_list = []
file_splitted = file_path.split('.')
if len(file_splitted) > 2:
xml_type = file_splitted[-2]
xsd_path = os.path.join(DATA_FOLDER_ABSPATH, self.version_grammaire, 'xsd',
'%s-%s.xsd' % (xml_type, self.version_grammaire))
xsd_tree = etree.parse(xsd_path)
xsd_tree.xinclude() # replace `xs:include` by its content

with open(file_path, 'r', encoding=XML_ENCODING) as in_xml:
content = '\n'.join(in_xml.readlines())
xmlschema = etree.XMLSchema(xsd_tree)
try:
xml_tree = etree.fromstring(content)
try:
xmlschema.assertValid(xml_tree)
except etree.DocumentInvalid:
for error in xmlschema.error_log:
error_str = "Invalid XML at line %i: %s" % (error.line, error.message)
if not isinstance(error_str, str): # Python2 fix: encode
error_str = error_str.encode('utf-8')
errors_list.append(error_str)
except etree.XMLSyntaxError as e:
error_str = "Error XML: %s" % e
if not isinstance(error_str, str): # Python2 fix: encode
error_str = error_str.encode('utf-8')
errors_list.append(error_str)
return errors_list
return check_xml_file(file_path, self.version_grammaire)

def check_xml_files(self, folder=None):
"""
Expand Down
48 changes: 47 additions & 1 deletion crue10/data/1.3/xsd/common-rbin-1.3.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,19 @@
</xs:complexType>
</xs:element>

<xs:element name="VariableResLoi">
<xs:element name="VariableResQregul">
<xs:complexType>
<xs:attribute name="NomRef" type="TypeForAttributeNomRefDistant" use="required" />
</xs:complexType>
</xs:element>

<xs:element name="VariableResZregul">
<xs:complexType>
<xs:attribute name="NomRef" type="TypeForAttributeNomRefDistant" use="required" />
</xs:complexType>
</xs:element>

<xs:element name="VariableResLoi">
<xs:complexType>
<xs:attribute name="NomRef" type="TypeForAttributeNomRefDistant" use="required" />
</xs:complexType>
Expand Down Expand Up @@ -260,6 +272,39 @@
</xs:complexType>
</xs:element>

<!-- les modèles -->
<xs:element name="Modele">
<xs:complexType>
<xs:attribute name="NomRef" type="TypeForAttributeNomRefModele" use="required" />
<xs:attribute name="NbrMot" type="xs:nonNegativeInteger" use="required" />
</xs:complexType>
</xs:element>

<xs:complexType name="TypeTypeEMHModele">
<xs:annotation>
<xs:documentation>
Type a utiliser pour tous les TypeEMH de CatEMHModele
</xs:documentation>
</xs:annotation>
<xs:sequence>
<!-- Attention l'ordre VariableRes, VariableResQregul puis VariableResZregul est figé -->
<xs:element ref="VariableRes" minOccurs="0" maxOccurs="unbounded"/>
<xs:element ref="VariableResQregul" minOccurs="0" maxOccurs="unbounded"/>
<xs:element ref="VariableResZregul" minOccurs="0" maxOccurs="unbounded"/>
<xs:element ref="Modele" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="NbrMot" type="xs:nonNegativeInteger" use="required" />
</xs:complexType>

<xs:element name="Modeles">
<xs:complexType>
<xs:sequence>
<xs:element name="ModeleRegul" type="TypeTypeEMHModele" />
</xs:sequence>
<xs:attribute name="NbrMot" type="xs:nonNegativeInteger" use="required" />
</xs:complexType>
</xs:element>

<!-- Structure des résultats par CatEMH et par TypeEMH -->
<xs:element name="StructureResultat">
<xs:complexType>
Expand All @@ -273,6 +318,7 @@
<xs:element ref="Casiers" />
<xs:element ref="Sections" />
<xs:element ref="Branches" />
<xs:element ref="Modeles" minOccurs="0" />
</xs:sequence>
<xs:attribute name="NbrMot" type="xs:nonNegativeInteger" use="required" />
</xs:complexType>
Expand Down
8 changes: 7 additions & 1 deletion crue10/data/1.3/xsd/frag-common-1.3.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,13 @@
<xs:attribute type="TypeForAttributeNomRefNoeud" name="NomRef" use="required" />
</xs:complexType>

<xs:simpleType name="TypeForAttributeNomRefNoeud">
<xs:simpleType name="TypeForAttributeNomRefModele">
<xs:restriction base="TypeForAttributeNomRefDistant">
<xs:pattern value="Mo_.*"></xs:pattern>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="TypeForAttributeNomRefNoeud">
<xs:restriction base="xs:token">
<xs:pattern value="Nd_.*"></xs:pattern>
</xs:restriction>
Expand Down
11 changes: 8 additions & 3 deletions crue10/run/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
from datetime import datetime
from glob import glob
from io import open
from lxml import etree
import os.path
import subprocess

from crue10.run.resultats_calcul import ResultatsCalcul
from crue10.utils.settings import CRUE10_EXE_PATH, CRUE10_EXE_OPTS
from crue10.utils.settings import CRUE10_EXE_PATH
from crue10.run.trace import Trace
from crue10.utils import add_default_missing_metadata, ExceptionCrue10, logger
from crue10.utils import add_default_missing_metadata, check_xml_file, DATA_FOLDER_ABSPATH, ExceptionCrue10, logger
from crue10.utils.crueconfigmetier import ENUM_SEVERITE
from crue10.utils.settings import GRAVITE_AVERTISSEMENT, GRAVITE_MAX, GRAVITE_MIN, \
GRAVITE_MIN_ERROR, GRAVITE_MIN_ERROR_BLK
GRAVITE_MIN_ERROR, GRAVITE_MIN_ERROR_BLK, XML_ENCODING


FMT_RUN_IDENTIFIER = "R%Y-%m-%d-%Hh%Mm%Ss"
Expand Down Expand Up @@ -319,6 +320,10 @@ def has_computation_traces(self):
"""A des traces de calculs"""
return len(self.traces['c']) != 0

def check_xml_rcal_file(self, version_grammaire):
file_path = get_path_file_unique_matching(self.run_mo_path, '*.rcal.xml')
return check_xml_file(file_path, version_grammaire)

def get_resultats_calcul(self):
"""
Obtenir une instance ResultatsCalcul pour post-traiter les résultats de calcul du Run.
Expand Down
39 changes: 33 additions & 6 deletions crue10/run/resultats_calcul.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ class ResultatsCalcul:
:vartype emh: OrderedDict(str)
:ivar variables: dictionnaires avec les types d'EMH secondaires donnant la liste des variables disponibles
:vartype variables: OrderedDict(str)
:ivar variables_Qregul: liste des variables Qregul
:vartype variables_Qregul: list(str)
:ivar variables_Zregul: liste des variables Zregul
:vartype variables_Zregul: list(str)
:ivar res_calc_pseudoperm: dictionnaires avec les métadonnées des calculs pseudo-permanents
:vartype res_calc_pseudoperm: OrderedDict(ResCalcPseudoPerm)
:ivar res_calc_trans: dictionnaires avec les métadonnées des calculs transitoires
Expand All @@ -171,7 +175,7 @@ class ResultatsCalcul:
:vartype _res_pattern: list(tuple)
"""
#: Noms des EMHs primaires
EMH_PRIMARY_TYPES = ['Noeud', 'Casier', 'Section', 'Branche']
EMH_PRIMARY_TYPES = ['Noeud', 'Casier', 'Section', 'Branche', 'Modele']

def __init__(self, rcal_path):
"""
Expand All @@ -184,6 +188,8 @@ def __init__(self, rcal_path):
self.emh_types = []
self.emh = OrderedDict()
self.variables = OrderedDict()
self.variables_Qregul = []
self.variables_Zregul = []
self.res_calc_pseudoperm = OrderedDict()
self.res_calc_trans = OrderedDict()

Expand All @@ -201,11 +207,24 @@ def run_id(self):
return os.path.basename(os.path.normpath(os.path.join(self.rcal_folder, '..')))

def _add_variables_names(self, elt, emh_sec):
"""
Ajout des variables pour ce type d'EMHs
:param elt: élément XML
:rtype elt: xml.etree.ElementTree.Element
:param emh_sec: type d'EMHs
:rtype emh_sec: str
"""
if emh_sec not in self.variables:
self.variables[emh_sec] = []
for sub_elt in elt:
varname = sub_elt.get('NomRef')
if sub_elt.tag.endswith('VariableRes'):
self.variables[emh_sec].append(sub_elt.get('NomRef'))
self.variables[emh_sec].append(varname)
elif sub_elt.tag.endswith('VariableResQregul'):
self.variables_Qregul.append(varname)
elif sub_elt.tag.endswith('VariableResZregul'):
self.variables_Zregul.append(varname)

def _add_emh_names(self, elt, emh_sec):
if int(elt.get('NbrMot')) > 0: # Avoid empty lists in self.emh
Expand All @@ -215,7 +234,8 @@ def _add_emh_names(self, elt, emh_sec):
self.emh_types.append(emh_sec)
self.emh[emh_sec] = []
for sub_elt in elt:
if not sub_elt.tag.endswith('VariableRes'):
if not sub_elt.tag.endswith('VariableRes') and not sub_elt.tag.endswith('VariableResQregul') \
and not sub_elt.tag.endswith('VariableResZregul'):
emh_name = sub_elt.get('NomRef')
self.emh[emh_sec].append(emh_name)

Expand Down Expand Up @@ -243,7 +263,10 @@ def _read_structure(self):
if not sub_elt.tag.endswith('VariableRes'):
self._add_emh_names(sub_elt, emh_subtype)
else:
if emh_type == 'Noeud':
if emh_type == 'Modele':
self._add_variables_names(emh_list.find(PREFIX + 'ModeleRegul'), 'Modele')
self._add_emh_names(emh_list.find(PREFIX + 'ModeleRegul'), 'Modele')
elif emh_type == 'Noeud':
self._add_variables_names(emh_list.find(PREFIX + 'NoeudNiveauContinu'), 'Noeud')
self._add_emh_names(emh_list.find(PREFIX + 'NoeudNiveauContinu'), 'Noeud')
elif emh_type == 'Casier':
Expand Down Expand Up @@ -272,7 +295,10 @@ def _set_res_pattern(self):
emh_types_with_res = []
for emh_type in self.emh_types:
emh_types_with_res.append(emh_type)
self._res_pattern.append((emh_type, (len(self.emh[emh_type]), len(self.variables[emh_type]))))
nbvar = len(self.variables[emh_type])
if emh_type == 'Modele':
nbvar += len(self.variables_Qregul) + len(self.variables_Zregul)
self._res_pattern.append((emh_type, (len(self.emh[emh_type]), nbvar)))
# Add emh_types which have no result data (because delimiter is still present)
for i, emh_type in enumerate(ResultatsCalcul.EMH_PRIMARY_TYPES[:-1]):
if emh_type not in emh_types_with_res:
Expand Down Expand Up @@ -339,7 +365,8 @@ def summary(self):
if len(self.emh[emh_type]) > 0 and len(self.variables[emh_type]) > 0:
text += "~> %i %s (avec %i variables)\n" % (len(self.emh[emh_type]), emh_type,
len(self.variables[emh_type]))
text += "=> %s calculs permanents et %i calculs transitoires\n" % (len(self.res_calc_pseudoperm), len(self.res_calc_trans))
text += "=> %s calculs permanents et %i calculs transitoires\n" \
% (len(self.res_calc_pseudoperm), len(self.res_calc_trans))
return text

def get_data_pseudoperm(self, calc_name):
Expand Down
23 changes: 4 additions & 19 deletions crue10/scenario/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
import os.path
import re
import shutil
import tempfile
import time

from crue10.base import EnsembleFichiersXML
from crue10.modele import Modele
from crue10.run import get_run_identifier, Run
from crue10.utils import check_isinstance, check_preffix, duration_iso8601_to_seconds, duration_seconds_to_iso8601, \
from crue10.utils import check_isinstance, check_preffix, check_xml_content, \
duration_iso8601_to_seconds, duration_seconds_to_iso8601, \
ExceptionCrue10, extract_pdt_from_elt, get_optional_commentaire, get_xml_root_from_file, \
JINJA_ENV, logger, parse_loi, PREFIX, write_default_xml_file, write_xml_from_tree, DATA_FOLDER_ABSPATH
from crue10.utils.crueconfigmetier import CCM_FILE
Expand Down Expand Up @@ -1068,28 +1068,13 @@ def check_xml_scenario(self, folder):
xml_content = etree.tostring(xml_tree).decode('utf-8')
xml_content = re.sub(r' xml:base="file:/(.*?)"', '', xml_content)

tmp_path = tempfile.NamedTemporaryFile().name
with open(tmp_path, 'w') as out:
out.write(xml_content)

# Prepare XSD
xsd_path = os.path.join(DATA_FOLDER_ABSPATH, self.version_grammaire, 'xsd',
'%s-%s.xsd' % ('scenario', self.version_grammaire))
xsd_tree = etree.parse(xsd_path)
xmlschema = etree.XMLSchema(xsd_tree)
xsd_schema = etree.XMLSchema(xsd_tree)

# List errors
errors_list = []
try:
xml_tree = etree.fromstring(xml_content)
try:
xmlschema.assertValid(xml_tree)
except etree.DocumentInvalid:
for error in xmlschema.error_log:
errors_list.append("Invalid XML at line %i: %s" % (error.line, error.message))
except etree.XMLSyntaxError as e:
errors_list.append('Error XML: %s' % e)
return errors_list
return check_xml_content(xml_content, xsd_schema)

def log_check_xml_scenario(self, folder):
"""
Expand Down
35 changes: 35 additions & 0 deletions crue10/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,38 @@ def extract_pdt_from_elt(elt):
return res
else:
raise NotImplementedError("Pas de temps impossible à traiter")


def check_xml_content(xml_content, xsd_schema):
errors_list = []
try:
xml_tree = etree.fromstring(xml_content)
try:
xsd_schema.assertValid(xml_tree)
except etree.DocumentInvalid:
for error in xsd_schema.error_log:
error_str = "Invalid XML at line %i: %s" % (error.line, error.message)
if not isinstance(error_str, str): # Python2 fix: encode
error_str = error_str.encode('utf-8')
errors_list.append(error_str)
except etree.XMLSyntaxError as e:
error_str = "Error XML: %s" % e
if not isinstance(error_str, str): # Python2 fix: encode
error_str = error_str.encode('utf-8')
errors_list.append(error_str)
return errors_list


def check_xml_file(xml_path, version_grammaire):
logger.debug("Validation XSD (grammaire %s) de %s" % (version_grammaire, xml_path))
file_splitted = xml_path.split('.')
xml_type = file_splitted[-2]
xsd_path = os.path.join(DATA_FOLDER_ABSPATH, version_grammaire, 'xsd',
'%s-%s.xsd' % (xml_type, version_grammaire))
xsd_tree = etree.parse(xsd_path)
xsd_tree.xinclude() # replace `xs:include` by its content

with open(xml_path, 'r', encoding=XML_ENCODING) as in_xml:
content = '\n'.join(in_xml.readlines())
xsd_schema = etree.XMLSchema(xsd_tree)
return check_xml_content(content, xsd_schema)

0 comments on commit e72e4bb

Please sign in to comment.