2020-12-24 16:02:20 +01:00
from typing import List
2019-11-23 08:17:35 +01:00
from . i18n import _
2020-07-24 14:59:09 +02:00
from . xmlreflector import XMLReflector
2019-11-23 08:17:35 +01:00
from . utils import normalize_family
2020-12-24 16:19:31 +01:00
from . error import SpaceObjShallNotBeUpdated , DictConsistencyError
2020-07-06 19:47:45 +02:00
from . path import Path
2020-08-12 08:23:38 +02:00
from . config import Config
2019-11-23 08:17:35 +01:00
2020-12-23 17:57:41 +01:00
# RougailObjSpace's elements that shall be forced to the Redefinable type
2020-07-06 19:47:45 +02:00
FORCE_REDEFINABLES = ( ' family ' , ' follower ' , ' service ' , ' disknod ' , ' variables ' )
2020-12-23 17:39:21 +01:00
# RougailObjSpace's elements that shall be forced to the UnRedefinable type
2020-07-07 18:12:16 +02:00
FORCE_UNREDEFINABLES = ( ' value ' , )
2020-12-23 17:57:41 +01:00
# RougailObjSpace's elements that shall not be modify
2020-07-16 09:50:01 +02:00
UNREDEFINABLE = ( ' multi ' , ' type ' )
2020-12-23 17:57:41 +01:00
# RougailObjSpace's elements that did not created automaticly
FORCE_ELEMENTS = ( ' choice ' , ' property_ ' , ' leadership ' )
# XML text are convert has name
2020-07-07 18:12:16 +02:00
FORCED_TEXT_ELTS_AS_NAME = ( ' choice ' , ' property ' , ' value ' , ' target ' )
2019-11-23 08:17:35 +01:00
# _____________________________________________________________________________
# special types definitions for the Object Space's internal representation
2020-12-23 17:39:21 +01:00
class RootRougailObject :
2020-11-11 16:24:06 +01:00
def __init__ ( self , xmlfiles ) :
if not isinstance ( xmlfiles , list ) :
xmlfiles = [ xmlfiles ]
self . xmlfiles = xmlfiles
2019-11-23 08:17:35 +01:00
2020-12-23 17:39:21 +01:00
class Atom ( RootRougailObject ) :
2020-12-23 17:36:50 +01:00
pass
2020-12-23 17:39:21 +01:00
class Redefinable ( RootRougailObject ) :
2020-12-23 17:36:50 +01:00
pass
2020-12-23 17:39:21 +01:00
class UnRedefinable ( RootRougailObject ) :
2020-12-23 17:36:50 +01:00
pass
2020-12-23 17:57:41 +01:00
class ObjSpace :
2019-11-23 08:17:35 +01:00
"""
2020-12-23 17:57:41 +01:00
Base object space
2019-11-23 08:17:35 +01:00
"""
2020-12-23 17:57:41 +01:00
pass
class RougailObjSpace :
2020-12-24 07:40:14 +01:00
""" Rougail ObjectSpace is an object ' s reflexion of the XML elements
2019-11-23 08:17:35 +01:00
"""
2020-12-24 16:19:31 +01:00
def __init__ ( self ,
xmlreflector : XMLReflector ,
) - > None :
2019-11-23 08:17:35 +01:00
self . index = 0
2020-12-24 16:19:31 +01:00
self . xmlreflector = xmlreflector
2019-11-23 08:17:35 +01:00
self . space = ObjSpace ( )
2020-07-06 19:47:45 +02:00
self . paths = Path ( )
2019-11-23 08:17:35 +01:00
self . redefine_variables = None
self . forced_text_elts = set ( )
2020-07-06 19:47:45 +02:00
self . forced_text_elts_as_name = set ( FORCED_TEXT_ELTS_AS_NAME )
2019-11-23 08:17:35 +01:00
self . list_conditions = { }
self . booleans_attributs = [ ]
2020-12-24 07:40:14 +01:00
self . make_object_space_classes ( )
2020-07-06 19:47:45 +02:00
2020-12-24 07:40:14 +01:00
def make_object_space_classes ( self ) :
""" Create Rougail ObjectSpace class types from DDT file
It enables us to create objects like :
2020-07-06 19:47:45 +02:00
File ( ) , Variable ( ) , Ip ( ) , Family ( ) , Constraints ( ) . . . and so on .
2020-12-24 07:40:14 +01:00
"""
2020-07-06 19:47:45 +02:00
for dtd_elt in self . xmlreflector . dtd . iterelements ( ) :
2019-11-23 08:17:35 +01:00
attrs = { }
2020-07-06 19:47:45 +02:00
if dtd_elt . name in FORCE_REDEFINABLES :
2020-12-23 17:36:50 +01:00
clstype = Redefinable
elif not dtd_elt . attributes ( ) and dtd_elt . name not in FORCE_UNREDEFINABLES :
clstype = Atom
2020-07-06 19:47:45 +02:00
else :
2020-12-23 17:36:50 +01:00
clstype = UnRedefinable
2020-07-06 19:47:45 +02:00
forced_text_elt = dtd_elt . type == ' mixed '
for dtd_attr in dtd_elt . iterattributes ( ) :
2020-12-23 17:36:50 +01:00
if set ( dtd_attr . itervalues ( ) ) == { ' True ' , ' False ' } :
2020-07-06 19:47:45 +02:00
# it's a boolean
self . booleans_attributs . append ( dtd_attr . name )
if dtd_attr . default_value :
# set default value for this attribute
default_value = dtd_attr . default_value
if dtd_attr . name in self . booleans_attributs :
2020-12-23 17:36:50 +01:00
default_value = self . convert_boolean ( default_value )
2020-07-06 19:47:45 +02:00
attrs [ dtd_attr . name ] = default_value
if dtd_attr . name == ' redefine ' :
# has a redefine attribute, so it's a Redefinable object
2020-12-23 17:36:50 +01:00
clstype = Redefinable
2020-07-06 19:47:45 +02:00
if dtd_attr . name == ' name ' and forced_text_elt :
# child.text should be transform has a "name" attribute
self . forced_text_elts . add ( dtd_elt . name )
2019-11-23 08:17:35 +01:00
forced_text_elt = False
if forced_text_elt is True :
2020-07-06 19:47:45 +02:00
self . forced_text_elts_as_name . add ( dtd_elt . name )
2019-11-23 08:17:35 +01:00
2020-07-06 19:47:45 +02:00
# create ObjectSpace object
setattr ( self , dtd_elt . name , type ( dtd_elt . name . capitalize ( ) , ( clstype , ) , attrs ) )
2020-12-23 17:57:41 +01:00
for elt in FORCE_ELEMENTS :
name = elt . capitalize ( )
if name . endswith ( ' _ ' ) :
name = name [ : - 1 ]
setattr ( self , elt , type ( name , ( RootRougailObject , ) , dict ( ) ) )
self . Leadership = self . leadership
2020-07-06 19:47:45 +02:00
2020-12-24 07:40:14 +01:00
def display_xmlfiles ( self ,
xmlfiles : list ,
) - > str :
if len ( xmlfiles ) == 1 :
return ' " ' + xmlfiles [ 0 ] + ' " '
return ' " ' + ' " , " ' . join ( xmlfiles [ : - 1 ] ) + ' " ' + ' and ' + ' " ' + xmlfiles [ - 1 ] + ' " '
2020-07-06 19:47:45 +02:00
def xml_parse_document ( self ,
2020-11-11 16:24:06 +01:00
xmlfile ,
2020-07-06 19:47:45 +02:00
document ,
space ,
namespace ,
) :
2020-12-24 07:40:14 +01:00
""" Parses a Rougail XML file and populates the RougailObjSpace
2020-07-06 19:47:45 +02:00
"""
2020-12-23 17:36:50 +01:00
# var to check unique family name in a XML file
2020-07-06 19:47:45 +02:00
family_names = [ ]
for child in document :
# this index enables us to reorder objects
if not isinstance ( child . tag , str ) :
2020-12-23 17:36:50 +01:00
# doesn't proceed the XML commentaries
2020-07-06 19:47:45 +02:00
continue
if child . tag == ' family ' :
if child . attrib [ ' name ' ] in family_names :
2020-12-24 12:41:10 +01:00
raise DictConsistencyError ( _ ( f ' Family " { child . attrib [ " name " ] } " is set several times in " { xmlfile } " ' ) , 44 )
2020-07-06 19:47:45 +02:00
family_names . append ( child . attrib [ ' name ' ] )
if child . tag == ' variables ' :
2020-12-23 17:36:50 +01:00
# variables has no name, so force namespace name
2020-07-06 19:47:45 +02:00
child . attrib [ ' name ' ] = namespace
2020-12-23 17:36:50 +01:00
if child . tag == ' value ' and child . text is None :
2020-12-24 07:40:14 +01:00
# remove empty value
2020-07-06 19:47:45 +02:00
continue
2020-07-06 20:58:11 +02:00
# variable objects creation
2020-07-06 19:47:45 +02:00
try :
2020-12-23 17:36:50 +01:00
variableobj = self . get_variableobj ( xmlfile ,
child ,
space ,
namespace ,
)
2020-07-06 19:47:45 +02:00
except SpaceObjShallNotBeUpdated :
continue
2020-12-23 17:36:50 +01:00
self . index + = 1
self . set_text ( child ,
variableobj ,
)
self . set_attributes ( xmlfile ,
child ,
variableobj ,
)
self . remove ( child ,
variableobj ,
)
self . set_path ( space ,
child ,
namespace ,
document ,
variableobj ,
)
2020-07-06 20:58:11 +02:00
self . add_to_tree_structure ( variableobj ,
2020-07-06 19:47:45 +02:00
space ,
child ,
2020-12-23 17:36:50 +01:00
namespace ,
2020-07-06 19:47:45 +02:00
)
if list ( child ) != [ ] :
2020-11-11 16:24:06 +01:00
self . xml_parse_document ( xmlfile ,
child ,
2020-07-06 20:58:11 +02:00
variableobj ,
2020-07-06 19:47:45 +02:00
namespace ,
)
2020-12-23 17:36:50 +01:00
def get_variableobj ( self ,
xmlfile : str ,
child : list ,
space ,
namespace ,
) :
2020-07-06 19:47:45 +02:00
"""
2020-12-24 07:40:14 +01:00
retrieves or creates Rougail Object Subspace objects
2020-07-06 19:47:45 +02:00
"""
2020-12-23 17:36:50 +01:00
obj = getattr ( self , child . tag )
if Redefinable in obj . __mro__ :
return self . create_or_update_redefinable_object ( xmlfile ,
child . attrib ,
space ,
child ,
namespace ,
)
elif Atom in obj . __mro__ :
if child . tag in vars ( space ) :
# Atom instance has to be a singleton here
# we do not re-create it, we reuse it
return getattr ( space , child . tag )
return obj ( xmlfile )
if child . tag not in vars ( space ) :
setattr ( space , child . tag , [ ] )
return obj ( xmlfile )
2020-07-06 19:47:45 +02:00
def create_or_update_redefinable_object ( self ,
2020-11-11 16:24:06 +01:00
xmlfile ,
2020-07-06 19:47:45 +02:00
subspace ,
space ,
child ,
namespace ,
) :
if child . tag in self . forced_text_elts_as_name :
name = child . text
2019-11-23 08:17:35 +01:00
else :
2020-07-06 19:47:45 +02:00
name = subspace [ ' name ' ]
2020-12-23 11:28:43 +01:00
if child . tag == ' family ' :
name = normalize_family ( name )
2020-12-23 17:36:50 +01:00
existed_var = self . get_existed_obj ( name ,
space ,
child ,
namespace ,
)
2020-11-11 16:24:06 +01:00
if existed_var :
2020-12-23 17:36:50 +01:00
# if redefine is set to object, default value is False
# otherwise it's always a redefinable object
2020-07-06 19:47:45 +02:00
default_redefine = child . tag in FORCE_REDEFINABLES
redefine = self . convert_boolean ( subspace . get ( ' redefine ' , default_redefine ) )
if redefine is True :
2020-11-11 16:24:06 +01:00
existed_var . xmlfiles . append ( xmlfile )
2020-12-23 17:36:50 +01:00
return existed_var
exists = self . convert_boolean ( subspace . get ( ' exists ' , True ) )
if exists is False :
2020-07-06 19:47:45 +02:00
raise SpaceObjShallNotBeUpdated ( )
2020-11-11 16:24:06 +01:00
xmlfiles = self . display_xmlfiles ( existed_var . xmlfiles )
2020-12-24 12:41:10 +01:00
raise DictConsistencyError ( _ ( f ' " { child . tag } " named " { name } " cannot be re-created in " { xmlfile } " , already defined in { xmlfiles } ' ) , 45 )
2020-12-23 17:36:50 +01:00
# if this object must only be modified if object already exists
2020-07-06 19:47:45 +02:00
exists = self . convert_boolean ( subspace . get ( ' exists ' , False ) )
2020-12-23 17:36:50 +01:00
if exists is True :
raise SpaceObjShallNotBeUpdated ( )
redefine = self . convert_boolean ( subspace . get ( ' redefine ' , False ) )
if redefine is False :
if child . tag not in vars ( space ) :
setattr ( space , child . tag , { } )
2020-11-11 16:24:06 +01:00
return getattr ( self , child . tag ) ( xmlfile )
2020-12-24 12:41:10 +01:00
raise DictConsistencyError ( _ ( f ' Redefined object in " { xmlfile } " : " { name } " does not exist yet ' ) , 46 )
2020-11-11 16:24:06 +01:00
2020-12-23 17:36:50 +01:00
def get_existed_obj ( self ,
name : str ,
space : str ,
child ,
namespace : str ,
) :
2020-12-24 07:40:14 +01:00
if isinstance ( space , self . family ) :
2020-08-12 08:23:38 +02:00
if namespace != Config [ ' variable_namespace ' ] :
2019-11-23 08:17:35 +01:00
name = space . path + ' . ' + name
2020-12-24 07:40:14 +01:00
if not self . paths . path_is_defined ( name ) :
return
old_family_name = self . paths . get_variable_family_name ( name )
if namespace != Config [ ' variable_namespace ' ] :
old_family_name = namespace + ' . ' + old_family_name
if space . path != old_family_name :
xmlfiles = self . display_xmlfiles ( space . xmlfiles )
2020-12-24 12:41:10 +01:00
raise DictConsistencyError ( _ ( f ' Variable was previously create in family " { old_family_name } " , now it is in " { space . path } " in { xmlfiles } ' ) , 47 )
2020-12-24 07:40:14 +01:00
return self . paths . get_variable_obj ( name )
2020-11-11 16:24:06 +01:00
children = getattr ( space , child . tag , { } )
2020-12-23 11:28:43 +01:00
if name in children :
return children [ name ]
2019-11-23 08:17:35 +01:00
2020-12-24 07:40:14 +01:00
def convert_boolean ( self , value ) :
2020-12-23 17:39:21 +01:00
""" Boolean coercion. The Rougail XML may contain srings like `True` or `False`
2020-07-06 19:47:45 +02:00
"""
if isinstance ( value , bool ) :
return value
if value == ' True ' :
return True
2020-12-24 07:40:14 +01:00
return False
2020-07-06 19:47:45 +02:00
2020-12-23 17:36:50 +01:00
def set_text ( self ,
child ,
variableobj ,
) :
if child . text is None :
return
text = child . text . strip ( )
if not text :
return
if child . tag in self . forced_text_elts_as_name :
variableobj . name = text
2019-11-23 08:17:35 +01:00
else :
2020-12-23 17:36:50 +01:00
variableobj . text = text
2019-11-23 08:17:35 +01:00
2020-12-23 17:36:50 +01:00
def set_attributes ( self ,
xmlfile ,
child ,
variableobj ,
) :
redefine = self . convert_boolean ( child . attrib . get ( ' redefine ' , False ) )
if redefine and child . tag == ' variable ' :
# delete old values
has_value = hasattr ( variableobj , ' value ' )
if has_value and len ( child ) != 0 :
del variableobj . value
for attr , val in child . attrib . items ( ) :
if redefine and attr in UNREDEFINABLE :
xmlfiles = self . display_xmlfiles ( variableobj . xmlfiles [ : - 1 ] )
2020-12-24 12:41:10 +01:00
raise DictConsistencyError ( _ ( f ' cannot redefine attribute " { attr } " for variable " { child . attrib [ " name " ] } " in " { xmlfile } " , already defined in { xmlfiles } ' ) , 48 )
2020-12-23 17:36:50 +01:00
if attr in self . booleans_attributs :
val = self . convert_boolean ( val )
if attr == ' name ' and getattr ( variableobj , ' name ' , None ) :
# do not redefine name
continue
setattr ( variableobj , attr , val )
2020-11-08 09:51:33 +01:00
2020-12-23 17:36:50 +01:00
def remove ( self ,
child ,
variableobj ,
) :
2020-12-23 17:39:21 +01:00
""" Rougail object tree manipulations
2020-12-23 17:36:50 +01:00
"""
if child . tag == ' variable ' :
if child . attrib . get ( ' remove_check ' , False ) :
self . remove_check ( variableobj . name )
if child . attrib . get ( ' remove_condition ' , False ) :
self . remove_condition ( variableobj . name )
if child . attrib . get ( ' remove_fill ' , False ) :
self . remove_fill ( variableobj . name )
if child . tag == ' fill ' and child . attrib [ ' target ' ] in self . redefine_variables :
self . remove_fill ( child . attrib [ ' target ' ] )
2020-11-08 09:51:33 +01:00
2020-12-24 07:40:14 +01:00
def remove_check ( self , name ) :
2019-11-23 08:17:35 +01:00
if hasattr ( self . space , ' constraints ' ) and hasattr ( self . space . constraints , ' check ' ) :
remove_checks = [ ]
2020-12-24 07:40:14 +01:00
for idx , check in enumerate ( self . space . constraints . check ) :
2019-11-23 08:17:35 +01:00
if hasattr ( check , ' target ' ) and check . target == name :
remove_checks . append ( idx )
remove_checks = list ( set ( remove_checks ) )
remove_checks . sort ( reverse = True )
for idx in remove_checks :
2020-12-24 07:40:14 +01:00
self . space . constraints . check . pop ( idx )
2020-07-06 19:47:45 +02:00
2020-12-24 07:40:14 +01:00
def remove_condition ( self , name ) :
2020-12-22 20:17:32 +01:00
remove_conditions = [ ]
2020-12-24 07:40:14 +01:00
for idx , condition in enumerate ( self . space . constraints . condition ) :
2020-12-22 20:17:32 +01:00
if condition . source == name :
remove_conditions . append ( idx )
for idx in remove_conditions :
del self . space . constraints . condition [ idx ]
2019-11-23 08:17:35 +01:00
2020-12-24 07:40:14 +01:00
def remove_fill ( self , name ) :
2020-12-23 17:36:50 +01:00
if hasattr ( self . space , ' constraints ' ) and hasattr ( self . space . constraints , ' fill ' ) :
remove_fills = [ ]
2020-12-24 07:40:14 +01:00
for idx , fill in enumerate ( self . space . constraints . fill ) :
2020-12-23 17:36:50 +01:00
if hasattr ( fill , ' target ' ) and fill . target == name :
remove_fills . append ( idx )
2019-11-23 08:17:35 +01:00
2020-12-23 17:36:50 +01:00
remove_fills = list ( set ( remove_fills ) )
remove_fills . sort ( reverse = True )
for idx in remove_fills :
2020-12-24 07:40:14 +01:00
self . space . constraints . fill . pop ( idx )
2019-11-23 08:17:35 +01:00
2020-12-23 17:36:50 +01:00
def set_path ( self ,
space ,
child ,
namespace ,
document ,
variableobj ,
2020-12-24 07:40:14 +01:00
) :
2020-07-06 19:47:45 +02:00
""" Fill self.paths attributes
2019-11-23 08:17:35 +01:00
"""
2020-07-06 19:47:45 +02:00
if child . tag == ' variable ' :
2020-12-23 11:28:43 +01:00
family_name = document . attrib [ ' name ' ]
family_name = normalize_family ( family_name )
2020-07-06 19:47:45 +02:00
self . paths . add_variable ( namespace ,
child . attrib [ ' name ' ] ,
family_name ,
document . attrib . get ( ' dynamic ' ) != None ,
2020-12-23 11:28:43 +01:00
variableobj ,
)
2020-07-06 19:47:45 +02:00
if child . attrib . get ( ' redefine ' , ' False ' ) == ' True ' :
2020-08-12 08:23:38 +02:00
if namespace == Config [ ' variable_namespace ' ] :
2020-07-06 19:47:45 +02:00
self . redefine_variables . append ( child . attrib [ ' name ' ] )
else :
self . redefine_variables . append ( namespace + ' . ' + family_name + ' . ' +
child . attrib [ ' name ' ] )
2019-11-23 08:17:35 +01:00
2020-07-06 19:47:45 +02:00
elif child . tag == ' family ' :
family_name = normalize_family ( child . attrib [ ' name ' ] )
2020-08-12 08:23:38 +02:00
if namespace != Config [ ' variable_namespace ' ] :
2020-07-06 19:47:45 +02:00
family_name = namespace + ' . ' + family_name
self . paths . add_family ( namespace ,
family_name ,
2020-07-06 20:58:11 +02:00
variableobj ,
2020-07-06 19:47:45 +02:00
)
2020-07-06 20:58:11 +02:00
variableobj . path = self . paths . get_family_path ( family_name , namespace )
2019-11-23 08:17:35 +01:00
2020-12-23 17:36:50 +01:00
def add_to_tree_structure ( self ,
variableobj ,
space ,
child ,
namespace ,
2020-12-24 07:40:14 +01:00
) :
2020-12-23 17:36:50 +01:00
if not hasattr ( variableobj , ' index ' ) :
variableobj . index = self . index
variableobj . namespace = namespace
if isinstance ( variableobj , Redefinable ) :
name = variableobj . name
if child . tag == ' family ' :
name = normalize_family ( name )
getattr ( space , child . tag ) [ name ] = variableobj
elif isinstance ( variableobj , UnRedefinable ) :
getattr ( space , child . tag ) . append ( variableobj )
else :
setattr ( space , child . tag , variableobj )