sbml_rs/
lib.rs

1use std::collections::HashMap;
2use std::str;
3
4use mathml_rs::Apply;
5use mathml_rs::Ci;
6use mathml_rs::Op;
7use mathml_rs::OpNode;
8use quick_xml::events::Event;
9use quick_xml::Reader;
10use sbml_macros::{attach, attach_math, close};
11
12pub mod structs;
13pub use structs::compartments::*;
14pub use structs::function_definitions::*;
15pub use structs::initial_assignments::*;
16pub use structs::math::*;
17pub use structs::model::*;
18pub use structs::parameters::*;
19pub use structs::reactions::*;
20pub use structs::root::*;
21pub use structs::rules::*;
22pub use structs::species::*;
23pub use structs::tag::*;
24pub use structs::units::*;
25
26#[allow(unused_variables, unused_assignments, dead_code)]
27pub fn parse(filename: &str) -> Result<Model, Vec<String>> {
28    // read file
29    //let file = File::open().unwrap();
30    let mut reader = Reader::from_file(filename).expect("File error.");
31    reader.trim_text(true);
32    reader.expand_empty_elements(true);
33    let mut buf = Vec::new();
34
35    let mut stack: Vec<TagIndex> = Vec::new();
36    let mut nodes = Vec::new();
37    let mut nodes_len = 0;
38    let mut model_attrs = HashMap::new();
39
40    let root = Root::default();
41    nodes.push(Tag::Root(root));
42    nodes_len += 1;
43    let mut current = 0;
44    stack.push(current);
45
46    loop {
47        match reader.read_event(&mut buf) {
48            // for each starting tag
49            Ok(Event::Start(ref e)) => {
50                let mut new_tag = None;
51                match e.name() {
52                    b"sbml" => {}
53                    b"model" => {
54                        let attributes = e.attributes().map(|a| a.unwrap()).collect::<Vec<_>>();
55                        for attribute in attributes {
56                            let key = str::from_utf8(attribute.key).unwrap();
57                            let value = attribute.unescape_and_decode_value(&reader).unwrap();
58                            match key {
59                                "id" | "substanceUnits" | "timeUnits" | "extentUnits"
60                                | "volumeUnits" | "areaUnits" | "lengthUnits"
61                                | "conversionFactor" | "metaid" | "name" => {
62                                    model_attrs.insert(key.to_string(), value);
63                                }
64                                _ => panic!("Attribute {} not parsed for model.", key),
65                            }
66                        }
67                    }
68                    b"listOfUnitDefinitions" => attach!(ListOfUnitDefinitions to Root),
69                    b"unitDefinition" => attach!(UnitDefinition with
70                                                id as String
71                                            to ListOfUnitDefinitions),
72                    b"listOfUnits" => attach!(ListOfUnits to UnitDefinition),
73                    b"unit" => attach!(Unit with 
74                                        kind as String,
75                                        exponent as f64,
76                                        scale as i64,
77                                        multiplier as f64
78                                        to ListOfUnits),
79                    b"listOfCompartments" => attach!(ListOfCompartments to Root),
80                    b"compartment" => attach!(Compartment with
81                                                name as String,
82                                                id as String,
83                                                units as String,
84                                                constant as bool,
85                                                spatial_dimensions as f64,
86                                                sbo_term as String,
87                                                size as f64
88                                            to ListOfCompartments),
89                    b"listOfParameters" => attach!(ListOfParameters to Root),
90                    b"parameter" => attach!(Parameter with
91                                            id as String,
92                                            name as String,
93                                            value as f64,
94                                            units as String,
95                                            sbo_term as String,
96                                            constant as bool
97                                        to ListOfParameters),
98                    b"listOfSpecies" => attach!(ListOfSpecies to Root),
99                    b"species" => attach!(Species with
100                                            id as String,
101                                            name as String,
102                                            meta_id as String,
103                                            sbo_term as String,
104                                            compartment as String,
105                                            initial_concentration as f64,
106                                            initial_amount as f64,
107                                            substance_units as String,
108                                            has_only_substance_units as bool,
109                                            boundary_condition as bool,
110                                            constant as bool,
111                                            conversion_factor as String,
112                                    to ListOfSpecies),
113                    b"listOfReactions" => attach!(ListOfReactions to Root),
114                    b"reaction" => attach!(Reaction with
115                                             id as String,
116                                             reversible as bool,
117                                             compartment as String,
118                                             name as String,
119                                             sbo_term as String
120                                        to ListOfReactions),
121                    b"listOfReactants" => attach!(ListOfReactants to Reaction),
122                    b"listOfProducts" => attach!(ListOfProducts to Reaction),
123                    b"speciesReference" => attach!(SpeciesReference with
124                                                    id as String,
125                                                    name as String,
126                                                    species as String,
127                                                    constant as bool,
128                                                    sbo_term as String,
129                                                    stoichiometry as f64,
130                                        to ListOfReactants | ListOfProducts),
131                    b"listOfModifiers" => attach!(ListOfModifiers to Reaction),
132                    b"modifierSpeciesReference" => attach!(ModifierSpeciesReference with
133                                                    id as String,
134                                                    name as String,
135                                                    species as String,
136                                                    sbo_term as String,
137                                        to ListOfModifiers),
138                    b"kineticLaw" => attach!(KineticLaw with
139                                                    sbo_term as String,
140                                        to Reaction),
141                    b"listOfLocalParameters" => attach!(ListOfLocalParameters to KineticLaw),
142                    b"localParameter" => attach!(LocalParameter with
143                                            id as String,
144                                            value as f64,
145                                            units as String,
146                                            sbo_term as String,
147                                        to ListOfLocalParameters),
148                    b"math" => {
149                        let (math_nodes, returned_reader) = mathml_rs::parse_fragment(reader);
150                        reader = returned_reader;
151
152                        attach_math![
153                            KineticLaw,
154                            FunctionDefinition,
155                            InitialAssignment,
156                            AssignmentRule,
157                            RateRule,
158                        ];
159                    }
160                    b"listOfFunctionDefinitions" => attach!(ListOfFunctionDefinitions to Root),
161                    b"functionDefinition" => {
162                        attach!(FunctionDefinition with
163                                    id as String,
164                                    name as String,
165                                    sbo_term as String
166                                to ListOfFunctionDefinitions)
167                    }
168                    b"listOfInitialAssignments" => attach!(ListOfInitialAssignments to Root),
169                    b"initialAssignment" => {
170                        attach!(InitialAssignment with
171                                    id as String,
172                                    symbol as String,
173                                    sbo_term as String
174                                to ListOfInitialAssignments)
175                    }
176                    b"listOfRules" => attach!(ListOfRules to Root),
177                    b"assignmentRule" => {
178                        attach!(AssignmentRule with
179                                    id as String,
180                                    metaid as String,
181                                    variable as String,
182                                    sbo_term as String
183                                to ListOfRules)
184                    }
185                    b"rateRule" => {
186                        attach!(RateRule with
187                                    id as String,
188                                    metaid as String,
189                                    variable as String,
190                                    sbo_term as String
191                                to ListOfRules)
192                    }
193                    _ => {
194                        panic!("Tag not parsed: {}", str::from_utf8(e.name()).unwrap());
195                    }
196                }
197                if let Some(t) = new_tag {
198                    nodes.push(t);
199                    nodes_len += 1;
200                }
201            }
202            // for each closing tag
203            Ok(Event::End(ref e)) => match e.name() {
204                b"listOfUnitDefinitions" => close![ListOfUnitDefinitions],
205                b"unitDefinition" => close![UnitDefinition],
206                b"listOfUnits" => close![ListOfUnits],
207                b"unit" => close![Unit],
208                b"listOfCompartments" => close![ListOfCompartments],
209                b"compartment" => close![Compartment],
210                b"listOfParameters" => close![ListOfParameters],
211                b"parameter" => close![Parameter],
212                b"listOfSpecies" => close![ListOfSpecies],
213                b"species" => close![Species],
214                b"listOfReactions" => close![ListOfReactions],
215                b"reaction" => close![Reaction],
216                b"listOfReactants" => close![ListOfReactants],
217                b"listOfProducts" => close![ListOfProducts],
218                b"speciesReference" => close![SpeciesReference],
219                b"listOfModifiers" => close![ListOfModifiers],
220                b"modifierSpeciesReference" => close![ModifierSpeciesReference],
221                b"kineticLaw" => close![KineticLaw],
222                b"listOfLocalParameters" => close![ListOfLocalParameters],
223                b"localParameter" => close![LocalParameter],
224                b"math" => close![MathTag],
225                b"listOfFunctionDefinitions" => close![ListOfFunctionDefinitions],
226                b"functionDefinition" => close![FunctionDefinition],
227                b"listOfInitialAssignments" => close![ListOfInitialAssignments],
228                b"initialAssignment" => close![InitialAssignment],
229                b"listOfRules" => close![ListOfRules],
230                b"assignmentRule" => close![AssignmentRule],
231                b"rateRule" => close![RateRule],
232                _ => {}
233            },
234            // unescape and decode the text event using the reader encoding
235            Ok(Event::Text(e)) => {
236                let s = e.unescape_and_decode(&reader).unwrap();
237                panic!("Unknown text found in {:?}", nodes[current]);
238            }
239            Ok(Event::Eof) => break, // exits the loop when reaching end of file
240            Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
241            _ => (), // There are several other `Event`s we do not consider here
242        }
243    }
244
245    let model = Model::new(nodes, model_attrs);
246
247    Ok(model)
248}
249
250pub fn parse_with_converted_species(filename: &str) -> Result<Model, Vec<String>> {
251    let mut model = parse(filename)?;
252
253    let species = model.species();
254    let mut species_compartment_id = HashMap::<String, String>::new();
255    for sp in species {
256        if let Some(species_id) = sp.id {
257            if Some(false) == sp.has_only_substance_units {
258                if let Some(compartment_id) = sp.compartment {
259                    species_compartment_id.insert(species_id, compartment_id);
260                }
261            }
262        }
263    }
264    //for node in &model.nodes {
265    //if let Tag::MathTag(math_tag) = node {
266    //println!("{}", math_tag);
267    //}
268    //}
269
270    let mut new_nodes = model.nodes.clone();
271
272    for i in 0..model.nodes.len() {
273        match &model.nodes[i] {
274            // perform replacement in each MathTag
275            Tag::MathTag(math_tag) => {
276                for j in 0..math_tag.nodes.len() {
277                    match &math_tag.nodes[j] {
278                        // replace each Ci that refers to a species
279                        // with hasOnlySubstanceUnits = false
280                        MathNode::Ci(ci) => {
281                            if let Some(species_id) = &ci.name {
282                                // check if the species is in the hashmap made earlier
283                                if let Some(compartment) = species_compartment_id.get(species_id) {
284                                    // if it is, make changes to the copy
285                                    if let Tag::MathTag(math_tag_copy) = &mut new_nodes[i] {
286                                        // replace Species Ci node with an Apply node and insert
287                                        // Species Ci, Divide Op and Compartment Ci nodes at the end
288                                        // create nodes Apply, Divide and Compartment
289                                        let mut species_math_node = math_tag_copy.nodes[j].clone();
290                                        let mut apply = Apply::default();
291                                        let mut divide = OpNode::default();
292                                        divide.op = Some(Op::Divide);
293                                        let mut compartment = Ci::with_name(compartment.clone());
294
295                                        // set child and parent pointers
296                                        let length = math_tag_copy.nodes.len();
297                                        apply.parent = ci.parent;
298                                        apply.children = vec![length, length + 1, length + 2];
299                                        apply.operator = Some(length);
300                                        apply.operands = vec![length + 1, length + 2];
301                                        divide.parent = Some(j);
302                                        compartment.parent = Some(j);
303                                        if let MathNode::Ci(species) = &mut species_math_node {
304                                            species.parent = Some(j);
305                                        }
306
307                                        let apply_math_node = MathNode::Apply(apply);
308                                        let divide_math_node = MathNode::Op(divide);
309                                        let compartment_math_node = MathNode::Ci(compartment);
310                                        math_tag_copy.nodes[j] = apply_math_node;
311                                        math_tag_copy.nodes.push(divide_math_node);
312                                        math_tag_copy.nodes.push(species_math_node);
313                                        math_tag_copy.nodes.push(compartment_math_node);
314                                    }
315                                }
316                            }
317                        }
318                        _ => {}
319                    }
320                }
321            }
322            _ => {}
323        }
324    }
325
326    model.nodes = new_nodes;
327
328    //for node in &model.nodes {
329    //if let Tag::MathTag(math_tag) = node {
330    //println!("{}", math_tag);
331    //}
332    //}
333
334    Ok(model)
335}
336
337#[cfg(test)]
338mod tests {
339    use super::*;
340    #[test]
341    fn it_works() {
342        for n in 1..2 {
343            let filename = format!(
344                "../../testsuites/core-semantic/{:0>5}/{:0>5}-sbml-l3v2.xml",
345                n, n
346            );
347            println!("{}", filename);
348            let result = parse_with_converted_species(&filename);
349            match result {
350                Ok(model) => {
351                    let function_definitions = model.function_definitions();
352                    for function_definition in function_definitions {
353                        println!("{}", function_definition.math_tag(&model).unwrap());
354                    }
355                }
356                Err(errors) => {
357                    println!("{:?}", errors);
358                }
359            }
360        }
361    }
362}