1use std::fmt;
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct InstanceId(Box<str>);
15
16impl InstanceId {
17 #[must_use]
19 pub fn new(value: &str) -> Option<Self> {
20 (!value.is_empty() && value.bytes().all(|byte| byte.is_ascii_digit()))
21 .then(|| Self(value.into()))
22 }
23
24 #[must_use]
26 pub fn as_str(&self) -> &str {
27 &self.0
28 }
29}
30
31impl From<u64> for InstanceId {
32 fn from(value: u64) -> Self {
33 Self(value.to_string().into())
34 }
35}
36
37impl fmt::Display for InstanceId {
38 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(formatter, "#{}", self.0)
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub enum Parameter<S = String> {
46 Null,
48 Derived,
50 Bool(bool),
52 LogicalUnknown,
54 Integer(S),
56 Real(S),
58 Text(S),
60 Binary(S),
62 Enum(S),
64 Ref(InstanceId),
66 List(Vec<Self>),
68 Typed {
70 type_name: S,
72 value: Box<Self>,
75 },
76}
77
78impl<S> Parameter<S> {
79 #[must_use]
81 pub fn as_reference(&self) -> Option<InstanceId> {
82 match self {
83 Self::Ref(id) => Some(id.clone()),
84 _ => None,
85 }
86 }
87
88 #[must_use]
90 pub fn as_list(&self) -> Option<&[Self]> {
91 match self {
92 Self::List(items) => Some(items),
93 _ => None,
94 }
95 }
96
97 #[must_use]
99 pub fn unwrap_typed(&self) -> &Self {
100 match self {
101 Self::Typed { value, .. } => value.unwrap_typed(),
102 value => value,
103 }
104 }
105}
106
107impl<S: AsRef<str>> Parameter<S> {
108 #[must_use]
110 pub fn as_text(&self) -> Option<&str> {
111 match self {
112 Self::Text(text) => Some(text.as_ref()),
113 _ => None,
114 }
115 }
116
117 #[must_use]
119 pub fn as_f64(&self) -> Option<f64> {
120 match self {
121 Self::Integer(value) | Self::Real(value) => value.as_ref().parse().ok(),
122 _ => None,
123 }
124 }
125}
126
127#[derive(Debug, Clone, PartialEq)]
129pub struct HeaderRecord<S = String> {
130 pub name: S,
132 pub parameters: Vec<Parameter<S>>,
134}
135
136#[derive(Debug, Clone, PartialEq)]
138pub struct Record<S = String> {
139 pub name: S,
141 pub parameters: Vec<Parameter<S>>,
143}
144
145#[derive(Debug, Clone, PartialEq)]
147pub struct DataRecord<S = String> {
148 pub id: InstanceId,
150 pub records: Vec<Record<S>>,
152}
153
154impl<S> DataRecord<S> {
155 #[must_use]
157 pub fn simple(id: InstanceId, name: S, parameters: Vec<Parameter<S>>) -> Self {
158 Self {
159 id,
160 records: vec![Record { name, parameters }],
161 }
162 }
163
164 #[must_use]
166 pub fn as_simple(&self) -> Option<&Record<S>> {
167 (self.records.len() == 1).then(|| &self.records[0])
168 }
169}
170
171#[derive(Debug, Clone, Default, PartialEq)]
173pub struct HeaderSection<S = String> {
174 pub records: Vec<HeaderRecord<S>>,
176}
177
178#[derive(Debug, Clone, Default, PartialEq, Eq)]
183pub struct StandardHeader {
184 pub description: Option<Vec<String>>,
186 pub implementation_level: Option<String>,
188 pub name: Option<String>,
190 pub time_stamp: Option<String>,
192 pub author: Option<Vec<String>>,
194 pub organization: Option<Vec<String>>,
196 pub preprocessor_version: Option<String>,
198 pub originating_system: Option<String>,
200 pub authorization: Option<String>,
202 pub schema: Option<Vec<String>>,
204}
205
206impl<S: AsRef<str>> HeaderSection<S> {
207 #[must_use]
209 pub fn standard(&self) -> StandardHeader {
210 let mut header = StandardHeader::default();
211 for record in &self.records {
212 match record.name.as_ref().to_ascii_uppercase().as_str() {
213 "FILE_DESCRIPTION" => {
214 header.description = text_list(record.parameters.first());
215 header.implementation_level = text(record.parameters.get(1));
216 }
217 "FILE_NAME" => {
218 header.name = text(record.parameters.first());
219 header.time_stamp = text(record.parameters.get(1));
220 header.author = text_list(record.parameters.get(2));
221 header.organization = text_list(record.parameters.get(3));
222 header.preprocessor_version = text(record.parameters.get(4));
223 header.originating_system = text(record.parameters.get(5));
224 header.authorization = text(record.parameters.get(6));
225 }
226 "FILE_SCHEMA" => header.schema = text_list(record.parameters.first()),
227 _ => {}
228 }
229 }
230 header
231 }
232}
233
234fn text<S: AsRef<str>>(parameter: Option<&Parameter<S>>) -> Option<String> {
235 parameter?.as_text().map(ToOwned::to_owned)
236}
237
238fn text_list<S: AsRef<str>>(parameter: Option<&Parameter<S>>) -> Option<Vec<String>> {
239 Some(
240 parameter?
241 .as_list()?
242 .iter()
243 .filter_map(Parameter::as_text)
244 .map(ToOwned::to_owned)
245 .collect(),
246 )
247}
248
249#[derive(Debug, Clone, Default, PartialEq)]
251pub struct DataSection<S = String> {
252 pub records: Vec<DataRecord<S>>,
254}
255
256impl<S> DataSection<S> {
257 #[must_use]
259 pub fn get(&self, id: &InstanceId) -> Option<&DataRecord<S>> {
260 self.records.iter().find(|record| &record.id == id)
261 }
262}
263
264#[derive(Debug, Clone, Default, PartialEq)]
266pub struct Exchange<S = String> {
267 pub header: HeaderSection<S>,
269 pub data: DataSection<S>,
271}