catalogue/
classification.rs

1use parse::UpperName;
2use parse::xml::*;
3use roxmltree::Node;
4use std::collections::BTreeMap;
5use std::ops::Range;
6
7use super::Error;
8
9#[derive(Clone)]
10#[derive(PartialEq)]
11#[repr(transparent)]
12pub struct ClassificationId(usize);
13
14impl Id for ClassificationId {
15    fn zero() -> Self {
16        Self(0)
17    }
18
19    fn succ(&mut self) {
20        self.0 += 1;
21    }
22
23    fn index(&self) -> usize {
24        self.0
25    }
26}
27
28#[derive(Clone)]
29pub struct Classification {
30    pub folders: Vec<ClassificationFolder>,
31    pub has_archive: bool,
32    pub has_description: bool,
33    pub has_national: bool,
34    pub has_national_limit: Option<UpperName>,
35    pub has_national_set: Option<UpperName>,
36    pub has_not_specified: bool,
37    pub has_special: bool,
38    pub has_specific: bool,
39    pub has_state: bool,
40    pub length: usize,
41    pub zero_padded: bool,
42}
43
44#[derive(Clone)]
45pub struct ClassificationFolder {
46    pub can_select: bool,
47    pub length: usize,
48    pub name: UpperName,
49    pub run_pretty: bool,
50    pub zero_select: bool,
51}
52
53struct Folder0 {
54    can_select: bool,
55    length: Parsed<usize>,
56    name: Parsed<UpperName>,
57    run_pretty: bool,
58    zero_select: bool,
59}
60
61pub (crate) struct Limit {
62    pub (crate) range: Range<usize>,
63    pub (crate) limit: UpperName,
64    pub (crate) national: UpperName,
65}
66
67impl Classification {
68    pub (crate) fn parse<'input>(
69        classifications: &mut ParsingMap<UpperName, Classification, ClassificationId>,
70        limits: &mut Vec<Limit>,
71        parent: Node<'input, 'input>,
72        errors: &mut Vec<Error>,
73    ) {
74        let mut file = Once::new("file");
75        let mut folders0 = Vec::new();
76        let mut has_archive = Once::new("archive");
77        let mut has_description = Once::new("description");
78        let mut has_national = Once::new("national");
79        let mut has_national_set = Once::new("national-set");
80        let mut has_not_specified = Once::new("not-specified");
81        let mut has_special = Once::new("special");
82        let mut has_specific = Once::new("specific");
83        let mut has_state = Once::new("state");
84        let mut maximum_length = Once::new("length");
85        let mut name = Once::new("name");
86        let mut title = Once::new("title");
87        let mut zero_padded = Once::new("zero-padded");
88
89        for attribute in parent.attributes() {
90            match attribute.name() {
91                "archive" =>
92                    has_archive.boolean_attribute(attribute, errors),
93
94                "description" =>
95                    has_description.boolean_attribute(attribute, errors),
96
97                "length" =>
98                    maximum_length.length_attribute(1..=10, attribute, errors),
99
100                "name" =>
101                    name.upper_name_attribute(attribute, errors),
102
103                "national" =>
104                    has_national.boolean_attribute(attribute, errors),
105
106                "national-set" =>
107                    has_national_set.boolean_attribute(attribute, errors),
108
109                "not-specified" =>
110                    has_not_specified.boolean_attribute(attribute, errors),
111
112                "special" =>
113                    has_special.boolean_attribute(attribute, errors),
114
115                "specific" =>
116                    has_specific.boolean_attribute(attribute, errors),
117
118                "state" =>
119                    has_state.boolean_attribute(attribute, errors),
120
121                "zero-padded" =>
122                    zero_padded.boolean_attribute(attribute, errors),
123
124                _ =>
125                    unexpected_attribute(attribute, errors),
126            }
127        }
128
129        for element in parent.children() {
130            let Some(name) = element_name(&element, errors) else {
131                continue;
132            };
133
134            match name {
135                "file" =>
136                    file.string_element(element, errors),
137
138                "folder" =>
139                    Folder0::parse(&mut folders0, element, errors),
140
141                "title" =>
142                    title.string_element(element, errors),
143
144                _ =>
145                    unexpected_element(element, errors),
146            }
147        }
148
149        let Some(inserted) = classifications.insert(name, &parent, errors) else {
150            return;
151        };
152
153        let Some(maximum_length) = maximum_length.required_attribute(&parent, errors) else {
154            return;
155        };
156
157        let mut failure = false;
158        let mut folders = Vec::new();
159        let mut folder_names: BTreeMap<UpperName, Range<usize>> = BTreeMap::new();
160        let mut minimum_length = 1;
161
162        for folder0 in folders0 {
163            let Some(folder0) = folder0 else {
164                return;
165            };
166
167            if let Some(existing_range) = folder_names.get(&folder0.name.value) {
168                duplicate_attribute(
169                    "name",
170                    folder0.name.range,
171                    existing_range.clone(),
172                    errors,
173                );
174
175                failure = true;
176            } else {
177                folder_names.insert(folder0.name.value.clone(), folder0.name.range);
178            }
179
180            if folder0.length.value < minimum_length || folder0.length.value > maximum_length {
181                errors.push(Error::Xml(ParseError::LengthRange {
182                    min: minimum_length,
183                    max: maximum_length,
184                    value: folder0.length.range.into(),
185                }));
186
187                failure = true;
188            } else {
189                minimum_length = folder0.length.value + 1;
190            }
191
192            folders.push(ClassificationFolder {
193                can_select: folder0.can_select,
194                length: folder0.length.value,
195                name: folder0.name.value,
196                run_pretty: folder0.run_pretty,
197                zero_select: folder0.zero_select,
198            });
199        }
200
201        if failure {
202            return;
203        }
204
205        let has_state = has_state.default(false);
206        let has_national_set = if let Some(range) = has_national_set.name_range_if_true() {
207            let national = inserted.name().append("National");
208
209            limits.push(Limit {
210                range,
211                limit: inserted.name().clone(),
212                national: national.clone(),
213            });
214
215            Some(national)
216        } else {
217            None
218        };
219
220        inserted.value(Classification {
221            folders,
222            has_archive: has_archive.default(has_state),
223            has_description: has_description.default(false),
224            has_national: has_national.default(false),
225            has_national_limit: None,
226            has_national_set,
227            has_not_specified: has_not_specified.default(false),
228            has_special: has_special.default(false),
229            has_specific: has_specific.default(false),
230            has_state,
231            length: maximum_length,
232            zero_padded: zero_padded.default(false),
233        });
234    }
235}
236
237impl Folder0 {
238    pub fn parse<'input>(
239        folders: &mut Vec<Option<Self>>,
240        parent: Node<'input, 'input>,
241        errors: &mut Vec<Error>,
242    ) {
243        let mut can_select = Once::new("select");
244        let mut length = Once::new("length");
245        let mut name = Once::new("name");
246        let mut run_pretty = Once::new("pretty");
247        let mut zero_select = Once::new("zero-select");
248
249        for attribute in parent.attributes() {
250            match attribute.name() {
251                "length" =>
252                    length.length_attribute(1..=10, attribute, errors),
253
254                "name" =>
255                    name.upper_name_attribute(attribute, errors),
256
257                "pretty" =>
258                    run_pretty.boolean_attribute(attribute, errors),
259
260                "select" =>
261                    can_select.boolean_attribute(attribute, errors),
262
263                "zero-select" =>
264                    zero_select.boolean_attribute(attribute, errors),
265
266                _ =>
267                    unexpected_attribute(attribute, errors),
268            }
269        }
270
271        for element in parent.children() {
272            unexpected_element(element, errors);
273        }
274
275        if let (
276            Some(length),
277            Some(name),
278        ) = (
279            length.required_parsed_attribute(&parent, errors),
280            name.required_parsed_attribute(&parent, errors),
281        ) {
282            folders.push(Some(Folder0 {
283                can_select: can_select.default(false),
284                length,
285                name,
286                run_pretty: run_pretty.default(false),
287                zero_select: zero_select.default(true),
288            }));
289        } else {
290            folders.push(None);
291        }
292    }
293}