1use parse::{
2 LowerName,
3 Map,
4 UpperName,
5};
6use parse::xml::*;
7
8use crate::pass0::{
9 Classification0,
10 ClassificationRestrict0,
11 Field0,
12 FieldKind0,
13 Framework0,
14 ReferRestrict0,
15 ReferRestrictTo0,
16 Summary0,
17 Table0,
18 Index0,
19};
20
21use crate::{
22 ChoiceId,
23 ClassificationId,
24 CustomSave,
25 Error,
26 FieldId,
27 IndexId,
28 TableId,
29};
30
31use super::{
32 DottedField,
33 Suggest,
34};
35
36pub struct Table {
37 pub abbreviation: String,
38 pub archived: bool,
39 pub custom_save: CustomSave,
40 pub memory: bool,
41 pub modified: bool,
42 pub name: UpperName,
43 pub singleton: bool,
44 pub title: String,
45
46 pub (crate) fields: Map<LowerName, Field, FieldId>,
47 pub (crate) indexes: Map<LowerName, Index, IndexId>,
48 pub (crate) refer_fields: Option<Summary>,
49 pub (crate) refer_summary: Option<Summary>,
50 pub (crate) suggest: Option<Suggest>,
51 pub (crate) summary: Option<Summary>,
52}
53
54pub struct Field {
55 pub abbreviation: String,
56 pub (crate) kind: FieldKind,
57 pub immutable: bool,
58 pub memory: bool,
59 pub name: LowerName,
60 pub null: bool,
61 pub title: String,
62}
63
64pub (crate) enum FieldKind {
65 Address(AddressField),
66 Alias(DottedField),
67 Binary,
68 Boolean(BooleanField),
69 Choice(ChoiceField),
70 Classification(ClassificationField),
71 Content,
72 Date,
73 Identifier(IdentifierField),
74 Formula,
75 EFTSL,
76 Memo,
77 Money,
78 Names,
79 Number(NumberField),
80 Percent,
81 Refer(ReferField),
82 Sequence,
83 String(StringField),
84 Time,
85}
86
87pub (crate) struct AddressField {
88 pub (crate) address1: FieldId,
89 pub (crate) address2: FieldId,
90 pub (crate) country: FieldId,
91 pub (crate) postcode: FieldId,
92 pub (crate) state: FieldId,
93 pub (crate) suburb: FieldId,
94}
95
96pub struct BooleanField {
97 pub display_as: Option<BooleanDisplayAs>,
98}
99
100#[derive(Clone)]
101pub struct BooleanDisplayAs {
102 pub no: String,
103 pub null: String,
104 pub yes: String,
105}
106
107pub struct ChoiceField {
108 pub display_null_as: Option<String>,
109 pub (crate) choice: ChoiceId,
110}
111
112pub (crate) struct ClassificationField {
113 pub (crate) classification: ClassificationId,
114 pub (crate) restrict: Option<ClassificationRestrict>,
115}
116
117pub (crate) enum ClassificationRestrict {
118 State(DottedField),
119 National(DottedField),
120}
121
122pub struct IdentifierField {
123 pub automate: bool,
124 pub length: usize,
125}
126
127pub struct NumberField {
128 pub range: Option<NumberRange>,
129}
130
131#[derive(Clone)]
132pub struct NumberRange {
133 pub minimum: usize,
134 pub maximum: usize,
135}
136
137pub struct ReferField {
138 pub display_null_as: Option<String>,
139 pub kind: ReferKind,
140 pub (crate) refer: TableId,
141 pub (crate) restrict: Option<ReferRestrict>,
142}
143
144#[derive(Clone)]
145pub enum ReferKind {
146 Container,
147 DropContainer,
148 DropExtend,
149 Extend,
150 Normal,
151 Super,
152}
153
154pub (crate) struct ReferRestrict {
155 pub (crate) field: DottedField,
156 pub (crate) value: ReferRestrictTo,
157}
158
159pub (crate) enum ReferRestrictTo {
160 Field(DottedField),
161 Table,
162}
163
164pub struct StringField {
165 pub kind: StringKind,
166 pub length: usize,
167}
168
169#[derive(Clone)]
170#[derive(Debug)]
171#[derive(PartialEq)]
172pub enum StringKind {
173 ABN,
174 CHESSN,
175 Email,
176 Normal,
177 LUI,
178 Phone,
179 Postcode,
180 Street,
181 Suburb,
182 TFN,
183 USI,
184 VSN,
185 Year,
186}
187
188pub (crate) struct Summary {
189 pub fields: Vec<FieldId>,
190}
191
192pub struct Index {
193 pub (crate) fields: Vec<FieldId>,
194 pub kind: IndexKind,
195 pub name: LowerName,
196}
197
198#[derive(Clone)]
199#[derive(Debug)]
200pub enum IndexKind {
201 Index,
202 Unique {
203 allow_nulls: bool,
204 },
205}
206
207impl Table {
208 pub (crate) fn map(
209 from: &ParsedEntry<UpperName, Table0, TableId>,
210 cx: &Framework0,
211 errors: &mut Vec<Error>,
212 ) -> Option<Self> {
213 let table0 = &from.value;
214
215 let Some(fields) = table0.fields.map_cx2(
216 Field::map,
217 from,
218 cx,
219 errors,
220 ) else {
221 return None;
222 };
223
224 let Some(indexes) = table0.indexes.map_cx(
225 Index::map,
226 table0,
227 errors,
228 ) else {
229 return None;
230 };
231
232 let marker = errors.marker();
233
234 let refer_fields = table0.refer_fields.as_ref().and_then(|summary0|
235 Summary::map(summary0, table0, errors)
236 );
237
238 let refer_summary = table0.refer_summary.as_ref().and_then(|summary0|
239 Summary::map(summary0, table0, errors)
240 );
241
242 let suggest = Suggest::map(&table0.suggest, from, cx, errors);
243
244 let summary = table0.summary.as_ref().and_then(|summary0|
245 Summary::map(summary0, table0, errors)
246 );
247
248 if errors.since(marker) {
249 return None;
250 }
251
252 Some(Table {
253 abbreviation: table0.abbreviation.clone(),
254 archived: table0.archived,
255 custom_save: table0.custom_save,
256 memory: table0.memory,
257 modified: table0.modified,
258 name: table0.name.clone(),
259 singleton: table0.singleton,
260 title: table0.title.clone(),
261
262 fields,
263 indexes,
264 refer_fields,
265 refer_summary,
266 suggest,
267 summary,
268 })
269 }
270
271 pub fn field_by_id(&self, id: &FieldId) -> &Field {
272 self.fields.vec.get(id.index())
273 .expect("Exist by construction")
274 }
275
276 pub fn field_by_name(&self, name: &LowerName) -> Option<(FieldId, &Field)> {
277 if let Some(id) = self.fields.names.get(name) {
278 let field = self.field_by_id(id);
279
280 Some((id.clone(), field))
281 } else {
282 None
283 }
284 }
285}
286
287impl ClassificationRestrict {
288 pub (crate) fn map(
289 table0: &ParsedEntry<UpperName, Table0, TableId>,
290 field0: &Field0,
291 classification0: &Classification0,
292 restrict0: &Option<ClassificationRestrict0>,
293 cx: &Framework0,
294 errors: &mut Vec<Error>,
295 ) -> Option<Self> {
296 if classification0.has_state {
297 if let Some(ClassificationRestrict0::State { state }) = restrict0 {
298 let Some(state) = DottedField::map_field(state, table0, cx, errors) else {
299 return None;
300 };
301 if !state.field.kind.is_avetmiss_state() {
302 errors.push(Error::FieldKindNot {
303 expecting: "choice AvetmissState",
304 found: state.field.kind.kind_string(),
305 value: state.range.clone().into(),
306 });
307 }
308 return Some(ClassificationRestrict::State(state.result));
309 } else {
310 errors.push(Error::RestrictExpectState {
311 field: field0.range.clone().into(),
312 });
313 }
314 } else if let Some(limit) = &classification0.has_national_limit {
315 if let Some(ClassificationRestrict0::National { national }) = restrict0 {
316 let Some(national) = DottedField::map_field(national, table0, cx, errors) else {
317 return None;
318 };
319 if !national.field.kind.is_classification(limit) {
320 errors.push(Error::FieldKindNot {
321 expecting: "national classification",
322 found: national.field.kind.kind_string(),
323 value: national.range.clone().into(),
324 });
325 }
326 return Some(ClassificationRestrict::National(national.result));
327 } else {
328 errors.push(Error::RestrictExpectNational {
329 field: field0.range.clone().into(),
330 });
331 }
332 } else {
333 if restrict0.is_some() {
334 errors.push(Error::RestrictExpectNone {
335 field: field0.range.clone().into(),
336 });
337 }
338 }
339
340 None
341 }
342}
343
344impl Field {
345 pub (crate) fn map(
346 from: &ParsedEntry<LowerName, Field0, FieldId>,
347 table0: &ParsedEntry<UpperName, Table0, TableId>,
348 cx: &Framework0,
349 errors: &mut Vec<Error>,
350 ) -> Option<Self> {
351 let field0 = &from.value;
352
353 let Some(kind) = FieldKind::map(table0, field0, &field0.kind, cx, errors) else {
354 return None;
355 };
356
357 Some(Field {
358 abbreviation: field0.abbreviation.clone(),
359 kind,
360 immutable: field0.immutable,
361 memory: field0.memory,
362 name: from.name.clone(),
363 null: field0.null,
364 title: field0.title.clone(),
365 })
366 }
367}
368
369impl FieldKind {
370 pub (crate) fn map(
371 table0: &ParsedEntry<UpperName, Table0, TableId>,
372 field0: &Field0,
373 kind0: &FieldKind0,
374 cx: &Framework0,
375 errors: &mut Vec<Error>,
376 ) -> Option<Self> {
377 match kind0 {
378 FieldKind0::Address { address1_name, address2_name, country_name, postcode_name, state_name, suburb_name } =>
379 Some(FieldKind::Address(AddressField {
380 address1: table0.value.fields.get_id(address1_name)
381 .expect("Exist by construction"),
382 address2: table0.value.fields.get_id(address2_name)
383 .expect("Exist by construction"),
384 country: table0.value.fields.get_id(country_name)
385 .expect("Exist by construction"),
386 postcode: table0.value.fields.get_id(postcode_name)
387 .expect("Exist by construction"),
388 state: table0.value.fields.get_id(state_name)
389 .expect("Exist by construction"),
390 suburb: table0.value.fields.get_id(suburb_name)
391 .expect("Exist by construction"),
392 })),
393
394 FieldKind0::Alias { alias } => {
395 let Some(alias) = DottedField::map_alias(alias, table0, cx, errors) else {
396 return None;
397 };
398
399 Some(FieldKind::Alias(alias))
400 }
401
402 FieldKind0::AvetmissRelease =>
403 Self::map_to_choice("AvetmissRelease", cx),
404
405 FieldKind0::AvetmissState =>
406 Self::map_to_choice("AvetmissState", cx),
407
408 FieldKind0::AvetmissSystem =>
409 Self::map_to_choice("AvetmissSystem", cx),
410
411 FieldKind0::Binary =>
412 Some(FieldKind::Binary),
413
414 FieldKind0::Boolean { display_as } =>
415 Some(FieldKind::Boolean(BooleanField {
416 display_as: display_as.clone(),
417 })),
418
419 FieldKind0::Choice { display_null_as, name } =>
420 if let Some(id) = cx.choices.get_id(&name.value) {
421 Some(FieldKind::Choice(ChoiceField {
422 display_null_as: display_null_as.clone(),
423 choice: id,
424 }))
425 } else {
426 errors.push(Error::ChoiceUnknown {
427 value: name.range.clone().into(),
428 });
429
430 None
431 }
432
433 FieldKind0::Classification { name, restrict } =>
434 if let Some((id, classification0)) = cx.classifications.get(&name.value) {
435 let restrict = ClassificationRestrict::map(
436 table0,
437 field0,
438 classification0,
439 restrict,
440 cx,
441 errors,
442 );
443
444 Some(FieldKind::Classification(ClassificationField {
445 classification: id,
446 restrict,
447 }))
448 } else {
449 errors.push(Error::ClassificationUnknown {
450 value: name.range.clone().into(),
451 });
452
453 None
454 }
455
456 FieldKind0::Country { range } => {
457 if let Some(id) = cx.classifications.get_id("Country") {
458 Some(FieldKind::Classification(ClassificationField {
459 classification: id,
460 restrict: None,
461 }))
462 } else {
463 errors.push(Error::ClassificationUnknown {
464 value: range.clone().into(),
465 });
466
467 None
468 }
469 }
470
471 FieldKind0::Content =>
472 Some(FieldKind::Content),
473
474 FieldKind0::Date =>
475 Some(FieldKind::Date),
476
477 FieldKind0::EFTSL =>
478 Some(FieldKind::EFTSL),
479
480 FieldKind0::Formula =>
481 Some(FieldKind::Formula),
482
483 FieldKind0::Identifier { automate, length } =>
484 Some(FieldKind::Identifier(IdentifierField {
485 automate: automate.clone(),
486 length: length.clone(),
487 })),
488
489 FieldKind0::Memo =>
490 Some(FieldKind::Memo),
491
492 FieldKind0::Money =>
493 Some(FieldKind::Money),
494
495 FieldKind0::Names =>
496 Some(FieldKind::Names),
497
498 FieldKind0::Number { range } =>
499 Some(FieldKind::Number(NumberField {
500 range: range.clone(),
501 })),
502
503 FieldKind0::Percent =>
504 Some(FieldKind::Percent),
505
506 FieldKind0::Refer { display_null_as, kind, refer, restrict } =>
507 if let Some((id, refer0)) = cx.tables.get_parsed(&refer.value) {
508 let restrict = if let Some(restrict0) = restrict {
509 ReferRestrict::map(
510 table0,
511 refer0,
512 restrict0,
513 cx,
514 errors,
515 )
516 } else {
517 None
518 };
519
520 Some(FieldKind::Refer(ReferField {
521 display_null_as: display_null_as.clone(),
522 kind: kind.clone(),
523 refer: id,
524 restrict,
525 }))
526 } else {
527 errors.push(Error::TableUnknown {
528 value: refer.range.clone().into(),
529 });
530
531 None
532 }
533
534 FieldKind0::Sequence =>
535 Some(FieldKind::Sequence),
536
537 FieldKind0::State =>
538 Self::map_to_choice("State", cx),
539
540 FieldKind0::String { kind, length } =>
541 Some(FieldKind::String(StringField {
542 kind: kind.clone(),
543 length: length.clone(),
544 })),
545
546 FieldKind0::Time =>
547 Some(FieldKind::Time),
548 }
549 }
550
551 fn map_to_choice(name: &'static str, cx: &Framework0) -> Option<Self> {
552 let choice = cx.choices.get_id(name)
553 .expect("Exist as a builtin");
554 Some(FieldKind::Choice(ChoiceField {
555 display_null_as: None,
556 choice,
557 }))
558 }
559}
560
561impl Index {
562 pub (crate) fn map(
563 from: &ParsedEntry<LowerName, Index0, IndexId>,
564 table0: &Table0,
565 errors: &mut Vec<Error>,
566 ) -> Option<Self> {
567 let index0 = &from.value;
568
569 let marker = errors.marker();
570
571 let mut fields = Vec::new();
572 for field0 in &index0.fields {
573 if let Some(id) = table0.fields.get_id(&field0.value) {
574 fields.push(id);
575 } else {
576 errors.push(Error::FieldUnknown {
577 table: table0.name.to_string(),
578 value: field0.range.clone().into(),
579 });
580 }
581 }
582
583 if errors.since(marker) {
584 return None;
585 }
586
587 Some(Index {
588 fields,
589 kind: index0.kind.clone(),
590 name: from.name.clone(),
591 })
592 }
593}
594
595impl ReferRestrict {
596 pub (crate) fn map(
597 table0: &ParsedEntry<UpperName, Table0, TableId>,
598 refer0: &ParsedEntry<UpperName, Table0, TableId>,
599 restrict0: &ReferRestrict0,
600 cx: &Framework0,
601 errors: &mut Vec<Error>,
602 ) -> Option<Self> {
603 let Some(field) = DottedField::map_field(&restrict0.field, refer0, cx, errors) else {
604 return None;
605 };
606
607 match &field.field.kind {
608 FieldKind0::Choice { .. } =>
609 match &restrict0.value {
610 ReferRestrictTo0::Field { field: value } => {
611 let Some(value) = DottedField::map_field(value, table0, cx, errors) else {
612 return None;
613 };
614 if value.field.kind.is_same_as(&field.field.kind) {
615 return Some(ReferRestrict {
616 field: field.result,
617 value: ReferRestrictTo::Field(value.result),
618 });
619 } else {
620 errors.push(Error::RestrictKinds {
621 field: field.range.clone().into(),
622 field_kind: field.field.kind.kind_string(),
623 value: value.range.clone().into(),
624 value_kind: value.field.kind.kind_string(),
625 });
626 }
627 }
628
629 ReferRestrictTo0::Table { range } =>
630 errors.push(Error::RestrictKinds {
631 field: field.range.clone().into(),
632 field_kind: field.field.kind.kind_string(),
633 value: range.clone().into(),
634 value_kind: format!("{} refer", refer0.name),
635 }),
636 }
637
638 FieldKind0::Refer { refer: field_refer, .. } =>
639 match &restrict0.value {
640 ReferRestrictTo0::Field { field: value } => {
641 let Some(value) = DottedField::map_field(value, table0, cx, errors) else {
642 return None;
643 };
644 if value.field.kind.is_same_as(&field.field.kind) {
645 return Some(ReferRestrict {
646 field: field.result,
647 value: ReferRestrictTo::Field(value.result),
648 });
649 } else {
650 errors.push(Error::RestrictKinds {
651 field: field.range.clone().into(),
652 field_kind: field.field.kind.kind_string(),
653 value: value.range.clone().into(),
654 value_kind: value.field.kind.kind_string(),
655 });
656 }
657 }
658
659 ReferRestrictTo0::Table { range } =>
660 if field_refer.value == table0.name {
661 return Some(ReferRestrict {
662 field: field.result,
663 value: ReferRestrictTo::Table,
664 });
665 } else {
666 errors.push(Error::RestrictKinds {
667 field: field.range.clone().into(),
668 field_kind: field.field.kind.kind_string(),
669 value: range.clone().into(),
670 value_kind: format!("{} refer", table0.name),
671 });
672 }
673 }
674
675 _ =>
676 errors.push(Error::FieldKindNot {
677 expecting: "choice or refer",
678 found: field.field.kind.kind_string(),
679 value: field.range.clone().into(),
680 }),
681 }
682
683 None
684 }
685}
686
687impl Summary {
688 pub fn map<'input>(
689 summary0: &Summary0,
690 table0: &Table0,
691 errors: &mut Vec<Error>,
692 ) -> Option<Self> {
693 let marker = errors.marker();
694
695 let mut fields = Vec::new();
696 for field0 in &summary0.fields {
697 if let Some(id) = table0.fields.get_id(&field0.value) {
698 fields.push(id);
699 } else {
700 errors.push(Error::FieldUnknown {
701 table: table0.name.to_string(),
702 value: field0.range.clone().into(),
703 });
704 }
705 }
706
707 if errors.since(marker) {
708 return None;
709 }
710
711 Some(Summary {
712 fields,
713 })
714 }
715}