diff --git a/Cargo.toml b/Cargo.toml index ce83eba..f6baf2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ strum_macros = "0.26" regex = "1.10.3" biodivine-xml-doc = "0.3.0" sbml-macros = { path = "sbml-macros" } +embed-doc-image = "0.1.4" [dev-dependencies] sbml-test-suite = { path = "sbml-test-suite" } \ No newline at end of file diff --git a/README.md b/README.md index a33ce17..7d6133b 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ fn main() { let compartment_id = compartments.get(0).id().get(); let s = Species::new(model.document(), &species_id, &compartment_id); let species_name = "MySpecies".to_string(); - s.name().set(Some(&species_name)); + s.name().set_some(&species_name); // Then, add it to the current list of species. species.push(s); diff --git a/docs-images/uml-model.png b/docs-images/uml-model.png new file mode 100644 index 0000000..2f3a95a Binary files /dev/null and b/docs-images/uml-model.png differ diff --git a/docs-images/uml-sbml-container.png b/docs-images/uml-sbml-container.png new file mode 100644 index 0000000..2f287c9 Binary files /dev/null and b/docs-images/uml-sbml-container.png differ diff --git a/src/constants/element.rs b/src/constants/element.rs index 8d761c4..f06df52 100644 --- a/src/constants/element.rs +++ b/src/constants/element.rs @@ -120,13 +120,13 @@ pub const ALLOWED_CHILDREN: Map<&str, &[&str]> = phf_map! { "listOfUnitDefinitions" => extended_sbase_children!("unitDefinition"), "unitDefinition" => extended_sbase_children!("listOfUnits"), "listOfUnits" => extended_sbase_children!("unit"), - "unit" => extended_sbase_children!(), + "unit" => ALLOWED_SBASE_CHILDREN, "listOfCompartments" => extended_sbase_children!("compartment"), - "compartment" => extended_sbase_children!(), + "compartment" => ALLOWED_SBASE_CHILDREN, "listOfSpecies" => extended_sbase_children!("species"), - "species" => extended_sbase_children!(), + "species" => ALLOWED_SBASE_CHILDREN, "listOfParameters" => extended_sbase_children!("parameter"), - "parameter" => extended_sbase_children!(), + "parameter" => ALLOWED_SBASE_CHILDREN, "listOfInitialAssignments" => extended_sbase_children!("initialAssignment"), "initialAssignment" => extended_sbase_children!("math"), "listOfRules" => extended_sbase_children!("algebraicRule", "assignmentRule", "rateRule"), @@ -139,12 +139,12 @@ pub const ALLOWED_CHILDREN: Map<&str, &[&str]> = phf_map! { "reaction" => extended_sbase_children!("listOfReactants", "listOfProducts", "listOfModifiers", "kineticLaw"), "listOfReactants" => extended_sbase_children!("speciesReference"), "listOfProducts" => extended_sbase_children!("speciesReference"), - "speciesReference" => extended_sbase_children!(), + "speciesReference" => ALLOWED_SBASE_CHILDREN, "listOfModifiers" => extended_sbase_children!("modifierSpeciesReference"), - "modifierSpeciesReference" => extended_sbase_children!(), + "modifierSpeciesReference" => ALLOWED_SBASE_CHILDREN, "kineticLaw" => extended_sbase_children!("math", "listOfLocalParameters"), "listOfLocalParameters" => extended_sbase_children!("localParameter"), - "localParameter" => extended_sbase_children!(), + "localParameter" => ALLOWED_SBASE_CHILDREN, "listOfEvents" => extended_sbase_children!("event"), "event" => extended_sbase_children!("trigger", "priority", "delay", "listOfEventAssignments"), "trigger" => extended_sbase_children!("math"), @@ -299,30 +299,37 @@ pub const MATHML_BINARY_OPERATORS: &[&str] = &[ "outerproduct", ]; -// source: https://www.w3.org/TR/MathML2/chapter4.html#contm.funopqual -pub const MATHML_NARY_OPERATORS: &[&str] = &[ - "plus", - "times", - "max", - "min", - "gcd", - "lcm", - "mean", - "sdev", - "variance", - "median", - "mode", - "union", - "intersect", - "cartesianproduct", - "selector", - "and", - "or", - "xor", - "eq", - "neq", - "leq", - "lt", - "geq", - "gt", -]; +/* + + This is currently unused, but will become relevant once we start implementing + the MathML abstraction. + + // source: https://www.w3.org/TR/MathML2/chapter4.html#contm.funopqual + pub const MATHML_NARY_OPERATORS: &[&str] = &[ + "plus", + "times", + "max", + "min", + "gcd", + "lcm", + "mean", + "sdev", + "variance", + "median", + "mode", + "union", + "intersect", + "cartesianproduct", + "selector", + "and", + "or", + "xor", + "eq", + "neq", + "leq", + "lt", + "geq", + "gt", + ]; + +*/ diff --git a/src/constants/mod.rs b/src/constants/mod.rs index 9b8b421..64d009f 100644 --- a/src/constants/mod.rs +++ b/src/constants/mod.rs @@ -1,3 +1,3 @@ -pub mod document; -pub mod element; -pub mod namespaces; +pub(crate) mod document; +pub(crate) mod element; +pub(crate) mod namespaces; diff --git a/src/constants/namespaces.rs b/src/constants/namespaces.rs index 034f6d5..25a3e1a 100644 --- a/src/constants/namespaces.rs +++ b/src/constants/namespaces.rs @@ -12,16 +12,19 @@ pub const URL_HTML: &str = "http://www.w3.org/1999/xhtml"; pub const URL_MATHML: &str = "http://www.w3.org/1998/Math/MathML"; /// The URL of the "default" empty namespace. +#[cfg(test)] pub const URL_EMPTY: &str = ""; /// The "core" SBML namespace. Default prefix for this namespace is empty. pub const NS_SBML_CORE: Namespace = ("", URL_SBML_CORE); /// The "core" HTML namespace. Default prefix for this namespace is empty. +#[cfg(test)] pub const NS_HTML: Namespace = ("", URL_HTML); /// The MathML namespace. Default prefix for this namespace is empty. pub const NS_MATHML: (&str, &str) = ("", URL_MATHML); /// The "default" empty namespace. Default prefix for this namespace is empty. +#[cfg(test)] pub const NS_EMPTY: (&str, &str) = ("", URL_EMPTY); diff --git a/src/core/model.rs b/src/core/model.rs index 424dfd8..197a9a9 100644 --- a/src/core/model.rs +++ b/src/core/model.rs @@ -1,3 +1,8 @@ +use std::ops::Deref; + +use embed_doc_image::embed_doc_image; +use sbml_macros::{SBase, XmlWrapper}; + use crate::core::sbase::SbmlUtils; use crate::core::{ AbstractRule, AlgebraicRule, AssignmentRule, Compartment, Constraint, Event, @@ -8,11 +13,214 @@ use crate::xml::{ OptionalChild, OptionalProperty, OptionalXmlChild, OptionalXmlProperty, RequiredXmlProperty, XmlDefault, XmlDocument, XmlElement, XmlList, XmlSupertype, XmlWrapper, }; -use sbml_macros::{SBase, XmlWrapper}; - -use std::ops::Deref; -/// A type-safe representation of an SBML \ element. +/// The SBML model object +/// (Section 4.2; [specification](https://raw.githubusercontent.com/combine-org/combine-specifications/main/specifications/files/sbml.level-3.version-2.core.release-2.pdf)). +/// +/// +/// ## 4.2 Model +/// +/// +/// +/// +/// ![UML][sbml-model] +/// +/// Only one instance of a [`Model`] object is allowed per instance of an SBML Level 3 Version 2 +/// Core document or data stream, and it must be located inside the ` ... ` element +/// as described in Section 4.1. +/// +/// Model serves as a container for components of classes +/// [`FunctionDefinition`], [`UnitDefinition`], [`Compartment`], [`Species`], [`Parameter`], +/// [`InitialAssignment`], [`Rule`], [`Constraint`], [`Reaction`] and [`Event`]. Instances of the +/// classes are placed inside instances of classes `ListOfFunctionDefinitions`, +/// `ListOfUnitDefinitions`, `ListOfCompartments`, `ListOfSpecies`, `ListOfParameters`, +/// `ListOfInitialAssignments`, `ListOfRules`, `ListOfConstraints`, `ListOfReactions`, and +/// `ListOfEvents` (here represented using [`XmlList`]). All the lists are optional, and, +/// if present, may be empty; this is semantically equivalent to omitting the list. +/// The resulting XML data object for a full model containing every possible list would have +/// the following form: +/// +/// ```xml +/// +/// +/// +/// +/// zero or more ... elements +/// +/// +/// zero or more ... elements +/// +/// +/// zero or more ... elements +/// +/// +/// zero or more ... elements +/// +/// +/// zero or more ... elements +/// +/// +/// zero or more ... elements +/// +/// +/// zero or more elements of subclasses of Rule +/// +/// +/// zero or more ... elements +/// +/// +/// zero or more ... elements +/// +/// +/// zero or more ... elements +/// +/// +/// +/// ``` +/// +/// Although the lists are optional, there are dependencies between SBML components such that +/// defining some components requires defining others. For example, defining a species requires +/// defining a compartment, and defining a species reference in a reaction requires defining a +/// species. Such dependencies are explained throughout specification. +/// +/// ### 4.2.1 The `sboTerm` attribute +/// +/// [`Model`] inherits an optional `sboTerm` attribute of type `SBOTerm` from its parent class [`SBase`] (see Section 3.1.12 +/// and Section 5). When a value is given to this attribute in a [`Model`] instance, it should be +/// an SBO identifier belonging to the branch for type [`Model`] indicated in Table 6 on p. 98. +/// The relationship is of the form “the model definition is-a X”, where X is the SBO term. +/// The term chosen should be the most precise (narrow) one that captures the overall process or +/// phenomenon represented by the overall SBML model. As discussed in Section 5 on p. 91, SBO +/// labels are optional information on a model. Applications are free to ignore `sboTerm` values. +/// A model must be interpretable without the benefit of SBO labels. +/// +/// ### 4.2.2 The `substanceUnits` attribute +/// +/// The `substanceUnits` attribute is used to specify the unit of measurement associated with +/// substance quantities of [`Species`] objects that do not specify units explicitly. The +/// attribute’s value must be of type `UnitSIdRef` (Section 3.1.10 on p. 13). A list of recommended +/// units is given in Section 8.2.1 on p. 148. If a given [`Species`] object definition does not +/// specify its unit of substance quantity via the `substanceUnits` attribute on [`Species`] +/// (described in Section 4.6 on p. 49), then the species inherits the value of the [`Model`] +/// `substanceUnits` attribute. If the [`Model`] does not define a value for this attribute, then +/// there is no unit to inherit, and all species that do not specify individual `substanceUnits` +/// attribute values then have no declared units for their quantities. Section 4.6.4 provides more +/// information about the units of species quantities. Note that when the identifier of a species +/// appears in a model’s mathematical expressions, the unit of measurement associated with that +/// identifier is not solely determined by setting `substanceUnits` on [`Model`] or [`Species`]. +/// Section 4.6.5 and Section 4.6.8 explain this point in more detail. +/// +/// ### 4.2.3 The `timeUnits` attribute +/// +/// The `timeUnits` attribute is used to specify the unit in which time is measured in the model. +/// The value of this attribute must be of type `UnitSIdRef` (Section 3.1.10 on p. 13). A list of +/// recommended units is given in Section 8.2.1 on p. 148. +/// +/// This attribute on [`Model`] is the only way to specify a unit for time in a model. It is a +/// global attribute; time is measured in the model everywhere in the same way. This is +/// particularly relevant to [`Reaction`] and [`RateRule`] objects in a model: all [`Reaction`] +/// and [`RateRule`] objects in SBML define per-time values, and the unit of time is given by +/// the `timeUnits` attribute on the [`Model`] object instance. If the Model [`timeUnits`] +/// attribute has no value, it means that the unit of time is not defined for the model’s +/// reactions and rate rules. Leaving it unspecified in an SBML model does not result in an +/// invalid model; however, as a matter of best practice, we strongly recommend that all +/// models specify units of measurement for time. +/// +/// ### 4.2.4 The `volumeUnits`, `areaUnits` and `lengthUnits` attributes +/// +/// The attributes `volumeUnits`, `areaUnits` and `lengthUnits` together are used to set the units +/// of measurements for the sizes of [`Compartment`] objects in the model when those objects +/// do not otherwise specify units. The three attributes correspond to the most common cases +/// of compartment dimensions: `volumeUnits` for compartments having attribute value +/// `spatialDimensions=“3”`, `areaUnits` for compartments having `spatialDimensions=“2”`, and +/// `lengthUnits` for compartments having `spatialDimensions=“1”`. The values of these attributes +/// must be of type `UnitSIdRef` (Section 3.1.10 on p. 13). A list of recommended units is given +/// in Section 8.2.1 on p. 148. The attributes are not applicable to compartments whose +/// `spatialDimensions` attribute values are not one of `1`, `2` or `3`. +/// +/// If a given [`Compartment`] object instance does not provide a value for its `units` attribute, +/// then the unit of measurement of that compartment’s size is inherited from the value specified +/// by the Model `volumeUnits`, `areaUnits` or `lengthUnits` attribute, as appropriate based on +/// the [`Compartment`] object’s `spatialDimensions` attribute value. If the [`Model`] object does +/// not define the relevant attribute, then there are no units to inherit, and all compartments +/// that do not set a value for their `units` attribute then have no units associated with their +/// compartment sizes. Section 4.5.4 provides more information about units of compartment sizes. +/// +/// The use of three separate attributes is a carry-over from SBML Level 2. Note that it is +/// entirely possible for a model to define a value for two or more of the attributes `volumeUnits`, +/// `areaUnits` and `lengthUnits` simultaneously, because SBML models may contain compartments +/// with different numbers of dimensions. +/// +/// ### 4.2.5 The `extentUnits` attribute +/// +/// Reactions are processes that occur over time. These processes involve events of some sort, +/// where a single “reaction event” is one in which some set of entities (known as reactants, +/// products and modifiers in SBML) interact, once. The extent of a reaction is a measure of how +/// many times the reaction has occurred, while the time derivative of the extent gives the +/// instantaneous rate at which the reaction is occurring. Thus, what is colloquially referred +/// to as the “rate of the reaction” is in fact equal to the rate of change of reaction extent. +/// +/// The combination of `extentUnits` and `timeUnits` defines the units of kinetic laws in SBML +/// and establishes how the numerical value of each KineticLaw’s mathematical formula +/// (Section 4.11.5 on p. 74) is meant to be interpreted in a model. The units of the kinetic laws +/// are taken to be `extentUnits` divided by `timeUnits`. A list of recommended units is given in +/// Section 8.2.1 on p. 148. +/// +/// Note that this embodies an important principle in SBML models: all reactions in an SBML model +/// must have the same units for the rate of change of extent. In other words, the units of all +/// reaction rates in the model must be the same. There is only one global value for `extentUnits` +/// and one global value for `timeUnits`. +/// +/// ### 4.2.6 The `conversionFactor` attribute +/// +/// The attribute `conversionFactor` defines a global value inherited by all [`Species`] object +/// instances that do not define separate values for their `conversionFactor` attributes. The value +/// of this attribute must be of type `SIdRef` (Section 3.1.8 on p. 13) and refer to a +/// [`Parameter`] object instance defined in the model. The [`Parameter`] object in question must +/// be a constant; i.e., it must have its constant attribute value set to `true`. +/// +/// If a given [`Species`] object definition does not specify a conversion factor via the +/// `conversionFactor` attribute on [`Species`] (described in Section 4.6 on p. 49), then the +/// species inherits the conversion factor specified by the [`Model`] `conversionFactor` attribute. +/// If the [`Model`] does not define a value for this attribute, then there is no conversion +/// factor to inherit. Section 4.11.7 on p. 77 describes how to interpret the effects of +/// reactions on species in that situation. More information about conversion factors in SBML +/// is provided in Section 4.6 on p. 49 and Section 4.11 on p. 68. +/// +/// ### 4.2.7 The `ListOf` container classes +/// +/// The various `ListOf___` classes are merely containers used for organizing the main components +/// of an SBML document. `ListOfFunctionDefinitions`, `ListOfUnitDefinitions`, +/// `ListOfCompartments`, `ListOfSpecies`, `ListOfParameters`, `ListOfInitialAssignments`, +/// `ListOfRules`, `ListOfConstraints`, `ListOfReactions`, and `ListOfEvents` are all derived +/// from the abstract class [`SBase`] (Section 3.2 on p. 14), and inherit `SBase`’s various +/// attributes and sub-elements. The `ListOf___` classes do not add any attributes of their own. +/// +/// There are several motivations for grouping SBML components within XML elements with names of +/// the form `listOfClassNames` rather than placing all the components directly at the top level. +/// First, the fact that the container classes are derived from [`SBase`] means that software +/// tools can add information about the lists themselves into each list container’s [`Annotation`], +/// a feature that a number of today’s software tools exploit. Second, we believe the grouping +/// leads to a more modular structure that is helpful when working with elements from multiple +/// SBML Level 3 packages. Third, we believe that it makes visual reading of models in XML easier, +/// for situations when humans must inspect and edit SBML directly. +/// +/// Lists are allowed to be empty for two reasons. First, this allows model writers to add +/// [`Annotation`] and [`Notes`] objects to a given list even when the list is empty in a model; +/// this can be useful, for instance, to let a modeler explain why the components are absent +/// from the model. Second, it enables SBML Level 3 package specifications to define new elements +/// to be children of these lists, and for these child elements to be used without the +/// requirement that at least one SBML Level 3 Core element always be present. +/// +#[embed_doc_image("sbml-model", "docs-images/uml-model.png")] #[derive(Clone, Debug, XmlWrapper, SBase)] pub struct Model(XmlElement); @@ -22,18 +230,8 @@ impl XmlDefault for Model { } } -/// Public functions to manipulate with the contents of SBML [Model] -/// i.e., optional lists inside SBML model +/// The SBML-defined components of the [`Model`] class. impl Model { - /// Try to find an instance of a [Model] element for the given child element. - /// - /// The child can be any SBML tag, as long as it appears in an SBML model (i.e. one of - /// its transitive parents is a [Model] element). If this is not satisfied, the method - /// returns `None`. - pub fn for_child_element(child: &XmlElement) -> Option { - Self::search_in_parents(child, "model") - } - pub fn substance_units(&self) -> OptionalProperty { self.optional_sbml_property("substanceUnits") } @@ -101,6 +299,18 @@ impl Model { pub fn events(&self) -> OptionalChild> { self.optional_sbml_child("listOfEvents") } +} + +/// Other methods for creating and manipulating SBML [`Model`]. +impl Model { + /// Try to find an instance of a [Model] element for the given child element. + /// + /// The child can be any SBML tag, as long as it appears in an SBML model (i.e. one of + /// its transitive parents is a [Model] element). If this is not satisfied, the method + /// returns `None`. + pub fn for_child_element(child: &XmlElement) -> Option { + Self::search_in_parents(child, "model") + } /// Returns a vector of [FunctionDefinition] identifiers (attribute **id**). Function definitions /// without IDs are not included in the output. diff --git a/src/lib.rs b/src/lib.rs index 3b01259..1a5a97f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,102 @@ +//! +//! This crate provides a Rust interface for reading, editing, and validating Systems Biology +//! Markup Language (SBML) files. Main features: +//! +//! - Complete support for the SBML Level 3 Version 2 core specification. +//! - Validation of the *required* SBML conformance rules, including validation of proper namespace usage. +//! - Ability to (safely) edit invalid or partially corrupted files (e.g. to fix errors). +//! - Full access to the raw underlying XML document through the `xml-doc` interface. +//! - `Annotation`, `Notes` and other custom XML/HTML elements are fully accessible as raw `XmlElement` objects. +//! - Unofficial or unsupported features can be accessed using `DynamicProperty` or `DynamicChild` wrappers. +//! +//! For basic usage, explore the documentation of SBML objects declared in the [core] +//! module. More advanced operations can be then performed using the XML abstraction layer +//! described in module [xml]. +//! +//! Note that primary documentation of the SBML objects is adapted from the SBML specification, +//! including figures. +//! +//! ### Example +//! +//! ```rust +//! use biodivine_lib_sbml::*; +//! use biodivine_lib_sbml::core::*; +//! use biodivine_lib_sbml::xml::*; +//! +//! let doc = Sbml::read_path("./test-inputs/model.sbml") +//! .expect("This document is not valid XML."); +//! +//! // First, we want to know if the document we +//! // just read is valid SBML.//! +//! let issues = doc.validate(); +//! if issues.len() > 0 { +//! // Note that these could be just warnings/notes. //! +//! // You can check `SbmlIssue::severity` of each item +//! // to detect fatal errors. +//! eprintln!("This document has issues:"); +//! for issue in issues { +//! eprintln!("{:?}", issue); +//! } +//! } +//! +//! let model = doc +//! .model() +//! .get() +//! // This is strange but allowed by the specification. +//! .expect("This document does not contain any model."); +//! +//! // Note that individual lists of model components +//! // are also optional in the specification. Here, +//! // an empty list is created if it does not exist. +//! let species = model.species().get_or_create(); +//! let compartments = model.compartments().get_or_create(); +//! println!( +//! "This model has {} compartments and {} species.", +//! compartments.len(), +//! species.len(), +//! ); +//! +//! // We can use `DynamicProperty` and `DynamicChild` to access +//! // items that are not in the SBML core specification. +//! let qual_namespace = "http://www.sbml.org/sbml/level3/version1/qual/version1"; +//! +//! // For example, here, we are reading the list of qualitative species defined +//! // in the sbml-qual package as a "generic" list of `XmlElement` objects. +//! let qual_species: OptionalDynamicChild> = +//! model.optional_child("listOfQualitativeSpecies", qual_namespace); +//! println!( +//! "This model has {} qualitative species.", +//! qual_species.get_or_create().len(), +//! ); +//! +//! // We can also modify the model. +//! +//! // First, create a new instance of a `Species` object. +//! let species_id = "sp-1".to_string(); +//! let compartment_id = compartments.get(0).id().get(); +//! let s = Species::new(model.document(), &species_id, &compartment_id); +//! let species_name = "MySpecies".to_string(); +//! s.name().set_some(&species_name); +//! +//! // Then, add it to the current list of species. +//! species.push(s); +//! assert_eq!(species.get(0).name().get(), Some(species_name)); +//! +//! // Finally, we can print the model back as XML: +//! let xml_string = doc.to_xml_string() +//! .expect("Encoding error."); +//! +//! println!("{} ... ", &xml_string[..200]); +//! ``` +//! + use std::collections::HashSet; use std::ops::Deref; use std::str::FromStr; use std::sync::{Arc, RwLock}; use biodivine_xml_doc::{Document, Element, ReadOptions}; +use embed_doc_image::embed_doc_image; use xml::{OptionalChild, RequiredProperty}; @@ -16,44 +109,103 @@ use crate::core::validation::{ use crate::core::{Model, SBase}; use crate::xml::{OptionalXmlChild, OptionalXmlProperty, XmlDocument, XmlElement, XmlWrapper}; -/// A module with useful types that are not directly part of the SBML specification, but help -/// us work with XML documents in a sane and safe way. In particular: -/// - [XmlDocument] | A thread and memory safe reference to a [Document]. -/// - [XmlElement] | A thread and memory safe reference to an [Element]. -/// - [XmlWrapper] | A trait with utility functions for working with types -/// derived from [XmlElement]. -/// - [xml::XmlDefault] | An extension of [XmlWrapper] which allows creation of "default" -/// value for the derived type. -/// - [xml::XmlProperty] and [xml::XmlPropertyType] | Traits providing an abstraction for -/// accessing properties stored in XML attributes. Implementation can be generated using a derive -/// macro. -/// - [xml::XmlChild] and [xml::XmlChildDefault] | Trait abstraction for accessing singleton -/// child tags. Implementation can be generated using a derive macro. -/// - [xml::XmlList] | A generic implementation of [XmlWrapper] which represents -/// a typed list of elements. -/// - [xml::DynamicChild] and [xml::DynamicProperty] | Generic implementations of -/// [xml::XmlProperty] and [xml::XmlChild] that can be used when the name of the property/child -/// is not known at compile time. -pub mod xml; - +/// Defines [`Model`], [`Species`][core::Species], [`Compartment`][core::Compartment], +/// [`FunctionDefinition`][core::FunctionDefinition] and other data objects prescribed +/// by the SBML core specification. pub mod core; -pub mod constants; +/// Defines [`XmlDocument`], [`XmlElement`], [`XmlWrapper`], [`XmlProperty`][xml::XmlProperty], +/// [`XmlChild`][xml::XmlChild] and other utility types or traits that can be used to safely +/// manipulate the underlying XML document. +pub mod xml; + +/// **(internal)** An internal module which defines constant values relevant for SBML, such as +/// namespace URLs or mappings assigning elements their allowed attributes. +pub(crate) mod constants; +/// **(test)** A helper module for executing the syntactic SBML test suite as part of the +/// standard unit tests. #[cfg(test)] pub mod test_suite; -/// The object that "wraps" an XML document in a SBML-specific API. +/// The SBML container object +/// (Section 4.1; [specification](https://raw.githubusercontent.com/combine-org/combine-specifications/main/specifications/files/sbml.level-3.version-2.core.release-2.pdf)). +/// +/// ## 4.1 The SBML Container +/// +/// +/// +/// +/// ![UML][sbml-container] +/// +/// Following the XML declaration, the outermost portion of a model expressed in Level 3 consists +/// of an object of class [`Sbml`]. This class contains three required attributes +/// (*level*, *version* and *xmlns*), and an optional *model* element. +/// +/// The [`Sbml`] class defines the structure and content of the `sbml` outermost element in an SBML +/// file. The following is an abbreviated example of [`Sbml`] class object translated into XML +/// form for an SBML Level 3 Version 2 Core document. Here, ellipses (“...”) are used to indicate +/// content elided from this example: /// -/// This is mostly just the place where you can specify what SBML version and -/// what SBML extensions are being used. The actual content of the SBML model is -/// then managed through the `SbmlModel` struct. +/// ```xml +/// +/// +/// ... +/// ... +/// +/// ``` +/// +/// The attribute `xmlns` declares the XML namespace used within the `sbml` element. The URI for +/// SBML Level 3 Version 2 Core is `http://www.sbml.org/sbml/level3/version2/core`. All SBML Level +/// 3 Version 2 Core elements and attributes must be placed in this namespace either by assigning +/// the default namespace as shown in the example above, or using a tag prefix on every element. +/// The `sbml` element may contain additional attributes, in particular, attributes to support the +/// inclusion of SBML Level 3 packages; see Section 4.1.3. For purposes of checking conformance +/// to the SBML Level 3 Core specification, only the elements and attributes in the SBML Level +/// 3 Core XML namespace are considered. +/// +/// ### 4.1.1 The `id` and `name` attributes +/// Because SBML inherits from [`SBase`], it has optional `id`, `name`, `sboTerm` and `metaid` +/// attributes. SBML Level 3 Version 2 Core does not define a purpose for these attributes; +/// moreover, being outside the [`Model`] namespace, the `id` attribute is not subject to the +/// uniqueness constraints of `SId` values inside [`Model`] objects. +/// +/// ### 4.1.2 The `model` element +/// The actual model contained within an SBML document is defined by an instance of the [`Model`] +/// class element. The structure of this object and its use are described in Section 4.2. +/// An SBML document may contain at most one model definition. +/// +#[embed_doc_image("sbml-container", "docs-images/uml-sbml-container.png")] #[derive(Clone, Debug)] pub struct Sbml { xml: XmlDocument, sbml_root: XmlElement, } +/// The SBML-defined components of the [`Sbml`] container class. +impl Sbml { + pub fn model(&self) -> OptionalChild { + OptionalChild::new(&self.sbml_root, "model", URL_SBML_CORE) + } + + pub fn level(&self) -> RequiredProperty { + RequiredProperty::new(&self.sbml_root, "level") + } + + pub fn version(&self) -> RequiredProperty { + RequiredProperty::new(&self.sbml_root, "version") + } +} + +/// Other methods for creating and manipulating [`Sbml`] container. impl Sbml { pub fn read_str(file_contents: &str) -> Result { // Only accept documents that are using UTF-8. @@ -110,18 +262,6 @@ impl Sbml { } } - pub fn model(&self) -> OptionalChild { - OptionalChild::new(&self.sbml_root, "model", URL_SBML_CORE) - } - - pub fn level(&self) -> RequiredProperty { - RequiredProperty::new(&self.sbml_root, "level") - } - - pub fn version(&self) -> RequiredProperty { - RequiredProperty::new(&self.sbml_root, "version") - } - /// Perform a basic type checking procedure. If this procedure passes without issues, /// the document is safe to work with. If some issues are found, working with the document /// can cause the program to panic. diff --git a/src/xml/xml_element.rs b/src/xml/xml_element.rs index fe9b29a..e810477 100644 --- a/src/xml/xml_element.rs +++ b/src/xml/xml_element.rs @@ -4,7 +4,7 @@ use biodivine_xml_doc::Element; use std::ops::DerefMut; use std::sync::Arc; -/// An [XmlElement] maintains a single thread-safe reference to an [Element] of a [xml_doc::Document]. +/// An [XmlElement] maintains a single thread-safe reference to an [Element] of a [biodivine_xml_doc::Document]. /// /// Internally, this is achieved through a reference counted [std::sync::RwLock] (see [XmlDocument]). ///