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}