Skip to main content

cimschema/
table.rs

1//! What a class table records, derived from a parsed specification.
2//!
3//! `cimgen` renders the tables as Rust source and `cimmodel` builds them at
4//! runtime from RDFS; both call these functions, so the two cannot disagree
5//! on how an attribute is classified, typed or namespaced.
6
7use crate::model::*;
8
9/// What an attribute's value denotes.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Kind {
12    Literal,
13    Association,
14    Enum,
15}
16
17pub fn attr_kind(a: &CimAttribute) -> Kind {
18    if a.is_enum_value {
19        Kind::Enum
20    } else if a.is_primitive || a.is_cim_datatype {
21        Kind::Literal
22    } else {
23        Kind::Association
24    }
25}
26
27/// The attribute's range: the class an association or enumeration points to,
28/// or the CIM datatype of a literal.
29pub fn attr_range(a: &CimAttribute) -> &str {
30    if a.rdf_range.is_empty() {
31        &a.cim_data_type
32    } else {
33        &a.rdf_range
34    }
35}
36
37/// The XSD datatype a literal attribute is written in, as the local name
38/// after `http://www.w3.org/2001/XMLSchema#`: `"double"` for `Float`, and so
39/// on. `""` for associations and enumerations.
40///
41/// CGMES `Float` is `double`, not `float`: values are read into `f64`, and
42/// claiming single precision would make a typed comparison disagree with the
43/// decoded value. `URI` (`md:Model.profile` and friends) is `anyURI`, as the
44/// SHACL files type those values. Anything unrecognised is `string`.
45pub fn xsd_type(a: &CimAttribute) -> &'static str {
46    if attr_kind(a) != Kind::Literal {
47        return "";
48    }
49    match a.data_type.as_str() {
50        DATA_TYPE_STRING => "string",
51        DATA_TYPE_INTEGER => "integer",
52        DATA_TYPE_BOOLEAN => "boolean",
53        DATA_TYPE_FLOAT => "double",
54        DATA_TYPE_DECIMAL => "decimal",
55        DATA_TYPE_DATE => "date",
56        DATA_TYPE_DATE_TIME => "dateTime",
57        DATA_TYPE_MONTH_DAY => "gMonthDay",
58        "URI" => "anyURI",
59        _ => "string",
60    }
61}
62
63/// The namespace an enumeration attribute's values live in: the decoder keeps
64/// a value after its `#`, and this is what rebuilds
65/// `http://iec.ch/TC57/CIM100#WindingConnection.D` from `WindingConnection.D`.
66/// `""` for any other attribute, or an enumeration the schema does not define.
67pub fn value_namespace<'a>(spec: &'a CimSpecification, a: &CimAttribute) -> &'a str {
68    if attr_kind(a) != Kind::Enum {
69        return "";
70    }
71    spec.enums.get(&a.rdf_range).map_or("", |e| e.namespace.as_str())
72}
73
74/// The family's enumerations, sorted by name; each one's values in
75/// vocabulary order.
76pub fn enums(spec: &CimSpecification) -> Vec<&CimEnum> {
77    let mut v: Vec<&CimEnum> = spec.enums.values().collect();
78    v.sort_by(|a, b| a.id.cmp(&b.id));
79    v
80}
81
82/// Profile code → profile URI (the `owl:versionIRI` a dataset declares), sorted
83/// by code.
84pub fn profile_uris(spec: &CimSpecification) -> Vec<(&str, &str)> {
85    let mut v: Vec<(&str, &str)> = spec
86        .ontologies
87        .iter()
88        .filter(|(_, o)| !o.owl_version_iri.is_empty())
89        .map(|(k, o)| (k.as_str(), o.owl_version_iri.as_str()))
90        .collect();
91    v.sort_unstable();
92    v
93}
94
95/// XML prefix → namespace IRI, as the family's vocabularies bind them, sorted
96/// by prefix.
97pub fn namespaces(spec: &CimSpecification) -> Vec<(&str, &str)> {
98    let mut v: Vec<(&str, &str)> =
99        spec.profile_namespaces.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect();
100    v.sort_unstable();
101    v
102}