@@ -70,10 +70,10 @@ def print_biomodel(cls, xml_string: str) -> None:
7070class XMLVisitor :
7171 def visit (self , element : _Element , node : vc .VcmlNode ) -> None :
7272 method_name = "visit_" + strip_namespace (element .tag )
73- method = getattr (self , method_name , self .generic_visit )
73+ method = getattr (self , method_name , self .generic_visit_children )
7474 method (element = element , node = node )
7575
76- def generic_visit (self , element : _Element , node : vc .VcmlNode ) -> None :
76+ def generic_visit_children (self , element : _Element , node : vc .VcmlNode ) -> None :
7777 for child in element :
7878 # Best-effort parse: an unmodeled or malformed physiology subtree should
7979 # cost us that subtree, not abort the whole document (and in particular not
@@ -93,7 +93,7 @@ def __init__(self, document: vc.VCMLDocument) -> None:
9393 def visit_BioModel (self , element : _Element , node : vc .VCMLDocument ) -> None :
9494 name = element .get ("Name" , default = "unnamed" )
9595 node .biomodel = vc .Biomodel (name = name , version = self ._parse_version (element ))
96- self .generic_visit (element , node .biomodel )
96+ self .generic_visit_children (element , node .biomodel )
9797
9898 def _parse_version (self , element : _Element ) -> vc .Version | None :
9999 """Extract a Version child element, if present."""
@@ -118,21 +118,21 @@ def _parse_version(self, element: _Element) -> vc.Version | None:
118118 def visit_Model (self , element : _Element , node : vc .Biomodel ) -> None :
119119 name : str = element .get ("Name" , default = "unnamed" )
120120 node .model = vc .Model (name = name )
121- self .generic_visit (element , node .model )
121+ self .generic_visit_children (element , node .model )
122122
123123 def visit_SimpleReaction (self , element : _Element , node : vc .Model ) -> None :
124124 name : str = element .get ("Name" , default = "unnamed" )
125125 compartment_name : str = element .get ("Structure" , default = "unknown" )
126126 reaction = vc .Reaction (name = name , is_flux = False , compartment_name = compartment_name )
127127 node .reactions .append (reaction )
128- self .generic_visit (element , reaction )
128+ self .generic_visit_children (element , reaction )
129129
130130 def visit_FluxStep (self , element : _Element , node : vc .Model ) -> None :
131131 name : str = element .get ("Name" , default = "unnamed" )
132132 compartment_name : str = element .get ("Structure" , default = "unknown" )
133133 reaction = vc .Reaction (name = name , is_flux = True , compartment_name = compartment_name )
134134 node .reactions .append (reaction )
135- self .generic_visit (element , reaction )
135+ self .generic_visit_children (element , reaction )
136136
137137 def visit_Reactant (self , element : _Element , node : vc .Reaction ) -> None :
138138 compound_ref : str = element .get ("LocalizedCompoundRef" , default = "unknown" )
@@ -141,7 +141,7 @@ def visit_Reactant(self, element: _Element, node: vc.Reaction) -> None:
141141 name = compound_ref , stoichiometry = stoichiometry , species_ref_type = vc .SpeciesRefType .reactant
142142 )
143143 node .reactants .append (reaction )
144- self .generic_visit (element , reaction )
144+ self .generic_visit_children (element , reaction )
145145
146146 def visit_Product (self , element : _Element , node : vc .Reaction ) -> None :
147147 compound_ref : str = element .get ("LocalizedCompoundRef" , default = "unknown" )
@@ -150,7 +150,7 @@ def visit_Product(self, element: _Element, node: vc.Reaction) -> None:
150150 name = compound_ref , stoichiometry = stoichiometry , species_ref_type = vc .SpeciesRefType .product
151151 )
152152 node .products .append (reaction )
153- self .generic_visit (element , reaction )
153+ self .generic_visit_children (element , reaction )
154154
155155 def visit_Kinetics (self , element : _Element , node : vc .VcmlNode ) -> None :
156156 # Only attach kinetics to an actual reaction. A <Kinetics> reached with a
@@ -160,7 +160,7 @@ def visit_Kinetics(self, element: _Element, node: vc.VcmlNode) -> None:
160160 kinetics_type : str = element .get ("KineticsType" , default = "GeneralKinetics" )
161161 kinetics = vc .Kinetics (kinetics_type = kinetics_type )
162162 node .kinetics = kinetics
163- self .generic_visit (element , kinetics )
163+ self .generic_visit_children (element , kinetics )
164164
165165 def visit_Feature (self , element : _Element , node : vc .Model ) -> None :
166166 name = element .get ("Name" , default = "unnamed" )
@@ -209,15 +209,15 @@ def visit_Parameter(self, element: _Element, node: vc.VcmlNode) -> None:
209209 # A <Parameter> in a context the data model doesn't represent (rate rules,
210210 # structure/species-context mappings, electrical params, …) — skip it.
211211 return
212- self .generic_visit (element , parameter )
212+ self .generic_visit_children (element , parameter )
213213
214214 def visit_SimulationSpec (self , element : _Element , node : vc .Biomodel ) -> None :
215215 name : str = element .get ("Name" , default = "unnamed" )
216216 stochastic : bool = element .get ("Stochastic" , default = "false" ).lower () == "true"
217217 default_geometry = vcg .Geometry (name = "default" , dim = 3 )
218218 application = vc .Application (name = name , stochastic = stochastic , geometry = default_geometry )
219219 node .applications .append (application )
220- self .generic_visit (element , application )
220+ self .generic_visit_children (element , application )
221221
222222 def visit_Simulation (self , element : _Element , node : vc .Application ) -> None :
223223 name : str = element .get ("Name" , default = "unnamed" )
@@ -468,7 +468,7 @@ def visit_Geometry(self, element: _Element, node: vc.Application) -> None:
468468 dim = int (element .get ("Dimension" , default = "0" ))
469469 geometry = vcg .Geometry (name = name , dim = dim )
470470 node .geometry = geometry
471- self .generic_visit (element , geometry )
471+ self .generic_visit_children (element , geometry )
472472
473473 def visit_Extent (self , element : _Element , node : vcg .Geometry ) -> None :
474474 X = float (element .get ("X" , default = "1" ))
@@ -529,7 +529,7 @@ def visit_SubVolume(self, element: _Element, node: vcg.Geometry) -> None:
529529 name = name , handle = handle , subvolume_type = subvolume_type , image_pixel_value = image_pixel_value
530530 )
531531 node .subvolumes .append (subvolume )
532- self .generic_visit (element , subvolume )
532+ self .generic_visit_children (element , subvolume )
533533
534534 def visit_AnalyticExpression (self , element : _Element , node : vcg .SubVolume ) -> None :
535535 expr : str | None = element .text
@@ -554,7 +554,7 @@ def visit_FeatureMapping(self, element: _Element, node: vc.Application) -> None:
554554 size_exp = size_exp ,
555555 )
556556 node .compartment_mappings .append (mapping )
557- self .generic_visit (element , mapping )
557+ self .generic_visit_children (element , mapping )
558558
559559 def visit_MembraneMapping (self , element : _Element , node : vc .Application ) -> None :
560560 compartment_name : str = element .get ("Membrane" , default = "unknown" )
@@ -568,7 +568,7 @@ def visit_MembraneMapping(self, element: _Element, node: vc.Application) -> None
568568 size_exp = size ,
569569 )
570570 node .compartment_mappings .append (mapping )
571- self .generic_visit (element , mapping )
571+ self .generic_visit_children (element , mapping )
572572
573573 def visit_BoundariesTypes (self , element : _Element , node : vc .CompartmentMapping ) -> None :
574574 switch = {"Flux" : vc .BoundaryType .flux , "Value" : vc .BoundaryType .value }
@@ -584,7 +584,7 @@ def visit_LocalizedCompoundSpec(self, element: _Element, node: vc.Application) -
584584 species_name : str = element .get ("LocalizedCompoundRef" , default = "unnamed" )
585585 species_mapping = vc .SpeciesMapping (species_name = species_name )
586586 node .species_mappings .append (species_mapping )
587- self .generic_visit (element , species_mapping )
587+ self .generic_visit_children (element , species_mapping )
588588
589589 def visit_InitialConcentration (self , element : _Element , node : vc .SpeciesMapping ) -> None :
590590 text : str = element .text or "0"
@@ -620,12 +620,19 @@ def visit_Diffusion(self, element: _Element, node: vc.SpeciesMapping) -> None:
620620 value : str | float = float_or_formula (text )
621621 node .diff_coef = value
622622
623+ def visit_Velocity (self , element : _Element , node : vc .SpeciesMapping ) -> None :
624+ parent = element .getparent ()
625+ if parent is None or strip_namespace (parent .tag ) != "LocalizedCompoundSpec" :
626+ return
627+ values : list [float | str ] = [float_or_formula (element .get (dim , default = "0.0" )) for dim in ["X" , "Y" , "Z" ]]
628+ node .velocity_x , node .velocity_y , node .velocity_z = tuple (values )
629+
623630
624631class PrintVisitor (XMLVisitor ):
625632 def visit_root (self , element : _Element , node : vc .VcmlNode ) -> None :
626633 print (f"Visiting root: { element .tag } " )
627- self .generic_visit (element , node )
634+ self .generic_visit_children (element , node )
628635
629636 def visit_child (self , element : _Element , node : vc .VcmlNode ) -> None :
630637 print (f"Visiting child: { element .tag } " )
631- self .generic_visit (element , node )
638+ self .generic_visit_children (element , node )
0 commit comments