oca_parser_xls/xls_parser/
data_entry.rs

1use isolang::Language;
2use oca_bundle_semantics::state::{
3    entries::EntriesElement, entry_codes::EntryCodes, oca::overlay,
4    oca::OCABundle,
5};
6use std::collections::{HashMap, HashSet};
7use std::convert::TryInto;
8use std::iter::FromIterator;
9use xlsxwriter::{prelude::*, worksheet::validation};
10
11pub fn generate(
12    oca_list: &[OCABundle],
13    filename: String,
14) -> Result<(), Vec<String>> {
15    let mut errors: Vec<String> = vec![];
16    let oca = oca_list.get(0).unwrap();
17
18    let workbook = Workbook::new(
19        format!("{filename}-data_entry.xlsx").as_str(),
20    )
21    .map_err(|e| {
22        errors.push(e.to_string());
23        errors.clone()
24    })?;
25    let mut format_header1 = Format::new();
26    format_header1
27        .set_font_size(10.)
28        .set_bold()
29        .set_text_wrap()
30        .set_vertical_align(FormatVerticalAlignment::VerticalTop)
31        .set_border_bottom(FormatBorder::Thin);
32    let mut format_header2 = Format::new();
33    format_header2
34        .set_font_size(10.)
35        .set_bold()
36        .set_text_wrap()
37        .set_vertical_align(FormatVerticalAlignment::VerticalTop)
38        .set_border_bottom(FormatBorder::Thin)
39        .set_border_right(FormatBorder::Thin);
40    let mut format_attr1 = Format::new();
41    format_attr1
42        .set_font_size(10.)
43        .set_text_wrap()
44        .set_vertical_align(FormatVerticalAlignment::VerticalTop);
45    let mut format_attr2 = Format::new();
46    format_attr2
47        .set_font_size(10.)
48        .set_text_wrap()
49        .set_vertical_align(FormatVerticalAlignment::VerticalTop)
50        .set_border_right(FormatBorder::Thin);
51
52    let mut format_data_header = Format::new();
53    format_data_header
54        .set_font_size(10.)
55        .set_bg_color(FormatColor::Custom(0xE7E6E6))
56        .set_vertical_align(FormatVerticalAlignment::VerticalTop)
57        .set_border_bottom(FormatBorder::Thin)
58        .set_border_right(FormatBorder::Thin);
59
60    let mut format_lookup_header = Format::new();
61    format_lookup_header
62        .set_font_size(10.)
63        .set_bold()
64        .set_bg_color(FormatColor::Custom(0xE7E6E6))
65        .set_text_wrap()
66        .set_vertical_align(FormatVerticalAlignment::VerticalTop);
67    let mut format_lookup_attr = Format::new();
68    format_lookup_attr
69        .set_font_size(10.)
70        .set_bold()
71        .set_text_wrap()
72        .set_vertical_align(FormatVerticalAlignment::VerticalTop);
73    let mut format_lookup_value = Format::new();
74    format_lookup_value
75        .set_font_size(10.)
76        .set_text_wrap()
77        .set_vertical_align(FormatVerticalAlignment::VerticalTop);
78
79    let mut sheet1 = workbook
80        .add_worksheet(Some("Schema Description"))
81        .map_err(|e| {
82            errors.push(e.to_string());
83            errors.clone()
84        })?;
85    sheet1.set_row(0, 35., None).map_err(|e| {
86        errors.push(e.to_string());
87        errors.clone()
88    })?;
89    sheet1.set_column(0, 0, 13., None).map_err(|e| {
90        errors.push(e.to_string());
91        errors.clone()
92    })?;
93    sheet1
94        .write_string(0, 0, "CB: Classification", Some(&format_header1))
95        .map_err(|e| {
96            errors.push(e.to_string());
97            errors.clone()
98        })?;
99    sheet1.set_column(1, 1, 17., None).map_err(|e| {
100        errors.push(e.to_string());
101        errors.clone()
102    })?;
103    sheet1
104        .write_string(0, 1, "CB: Attribute Name", Some(&format_header1))
105        .map_err(|e| {
106            errors.push(e.to_string());
107            errors.clone()
108        })?;
109    sheet1.set_column(2, 2, 12.5, None).map_err(|e| {
110        errors.push(e.to_string());
111        errors.clone()
112    })?;
113    sheet1
114        .write_string(0, 2, "CB: Attribute Type", Some(&format_header1))
115        .map_err(|e| {
116            errors.push(e.to_string());
117            errors.clone()
118        })?;
119    sheet1
120        .write_string(0, 3, "CB: Flagged Attribute", Some(&format_header2))
121        .map_err(|e| {
122            errors.push(e.to_string());
123            errors.clone()
124        })?;
125
126    let mut sheet2 = workbook.add_worksheet(Some("Data Entry")).map_err(|e| {
127        errors.push(e.to_string());
128        errors.clone()
129    })?;
130    let mut sheet3 = workbook
131        .add_worksheet(Some("schema conformant data"))
132        .map_err(|e| {
133            errors.push(e.to_string());
134            errors.clone()
135        })?;
136
137    let mut attributes_index: HashMap<String, u32> = HashMap::new();
138
139    for (i, (attr_name, attr_type)) in oca.capture_base.attributes.iter().enumerate() {
140        let attr_i: u32 = (i + 1).try_into().unwrap();
141        attributes_index.insert(attr_name.to_string(), attr_i);
142
143        sheet1
144            .write_string(
145                attr_i,
146                0,
147                &oca.capture_base.classification,
148                Some(&format_attr2),
149            )
150            .map_err(|e| {
151                errors.push(e.to_string());
152                errors.clone()
153            })?;
154        sheet1
155            .write_string(attr_i, 1, attr_name, Some(&format_attr2))
156            .map_err(|e| {
157                errors.push(e.to_string());
158                errors.clone()
159            })?;
160        sheet1
161            .write_string(attr_i, 2, &serde_json::to_string(attr_type).unwrap(), Some(&format_attr1))
162            .map_err(|e| {
163                errors.push(e.to_string());
164                errors.clone()
165            })?;
166        sheet1
167            .write_string(
168                attr_i,
169                3,
170                if oca.capture_base.flagged_attributes.contains(attr_name) {
171                    "Y"
172                } else {
173                    ""
174                },
175                Some(&format_attr2),
176            )
177            .map_err(|e| {
178                errors.push(e.to_string());
179                errors.clone()
180            })?;
181
182        sheet3
183            .write_string(
184                0,
185                i.try_into().unwrap(),
186                attr_name,
187                Some(&format_data_header),
188            )
189            .map_err(|e| {
190                errors.push(e.to_string());
191                errors.clone()
192            })?;
193        let letter = char::from_u32((65 + i).try_into().unwrap()).unwrap();
194        for r in 1..1001 {
195            let formula = format!(
196                "=IF(ISBLANK('Data Entry'!${}${}),\"\",'Data Entry'!${}${})",
197                letter,
198                r + 1,
199                letter,
200                r + 1
201            );
202            sheet3
203                .write_formula(r, i.try_into().unwrap(), &formula, None)
204                .map_err(|e| {
205                    errors.push(e.to_string());
206                    errors.clone()
207                })?;
208        }
209    }
210
211    sheet2
212        .set_column(
213            0,
214            (oca.capture_base.attributes.keys().count() - 1)
215                .try_into()
216                .unwrap(),
217            12.,
218            None,
219        )
220        .map_err(|e| {
221            errors.push(e.to_string());
222            errors.clone()
223        })?;
224    sheet3
225        .set_column(
226            0,
227            (oca.capture_base.attributes.keys().count() - 1)
228                .try_into()
229                .unwrap(),
230            12.,
231            None,
232        )
233        .map_err(|e| {
234            errors.push(e.to_string());
235            errors.clone()
236        })?;
237
238    let mut lang: Option<&Language> = None;
239    let mut skipped: usize = 0;
240    let mut lookup_entries: HashMap<String, &HashMap<String, String>> = HashMap::new();
241
242    let languages: Vec<Option<&Language>> = oca
243        .overlays
244        .iter()
245        .map(|o| o.language())
246        .collect::<HashSet<_>>()
247        .into_iter()
248        .collect();
249    for language_option in languages {
250        if let Some(l) = language_option {
251            if l.eq(&Language::Eng) {
252                lang = language_option;
253            }
254        }
255    }
256
257    for (i, o) in oca.overlays.iter().enumerate() {
258        if o.overlay_type().to_string().eq("CharacterEncoding") {
259            sheet1
260                .set_column(
261                    (i + 4 - skipped).try_into().unwrap(),
262                    (i + 4 - skipped).try_into().unwrap(),
263                    15.,
264                    None,
265                )
266                .map_err(|e| {
267                    errors.push(e.to_string());
268                    errors.clone()
269                })?;
270            sheet1
271                .write_string(
272                    0,
273                    (i + 4 - skipped).try_into().unwrap(),
274                    "OL: Character Encoding",
275                    Some(&format_header2),
276                )
277                .map_err(|e| {
278                    errors.push(e.to_string());
279                    errors.clone()
280                })?;
281
282            let overlay = o
283                .as_any()
284                .downcast_ref::<overlay::CharacterEncoding>()
285                .unwrap();
286
287            for j in 0..oca.capture_base.attributes.len() {
288                sheet1
289                    .write_blank(
290                        (j + 1).try_into().unwrap(),
291                        (i + 4 - skipped).try_into().unwrap(),
292                        Some(&format_attr2),
293                    )
294                    .map_err(|e| {
295                        errors.push(e.to_string());
296                        errors.clone()
297                    })?;
298            }
299            for (attr_name, encoding) in &overlay.attribute_character_encoding {
300                if let Ok(serde_json::Value::String(e)) = serde_json::to_value(encoding) {
301                    sheet1
302                        .write_string(
303                            *attributes_index.get(attr_name.clone().as_str()).unwrap(),
304                            (i + 4 - skipped).try_into().unwrap(),
305                            &e,
306                            Some(&format_attr2),
307                        )
308                        .map_err(|e| {
309                            errors.push(e.to_string());
310                            errors.clone()
311                        })?;
312                }
313            }
314        } else if o.overlay_type().to_string().eq("Cardinality") {
315            sheet1
316                .set_column(
317                    (i + 4 - skipped).try_into().unwrap(),
318                    (i + 4 - skipped).try_into().unwrap(),
319                    15.,
320                    None,
321                )
322                .map_err(|e| {
323                    errors.push(e.to_string());
324                    errors.clone()
325                })?;
326            sheet1
327                .write_string(
328                    0,
329                    (i + 4 - skipped).try_into().unwrap(),
330                    "OL: Cardinality",
331                    Some(&format_header2),
332                )
333                .map_err(|e| {
334                    errors.push(e.to_string());
335                    errors.clone()
336                })?;
337
338            let overlay = o.as_any().downcast_ref::<overlay::Cardinality>().unwrap();
339            for j in 0..oca.capture_base.attributes.len() {
340                sheet1
341                    .write_blank(
342                        (j + 1).try_into().unwrap(),
343                        (i + 4 - skipped).try_into().unwrap(),
344                        Some(&format_attr2),
345                    )
346                    .map_err(|e| {
347                        errors.push(e.to_string());
348                        errors.clone()
349                    })?;
350            }
351            for (attr_name, cardinality) in &overlay.attribute_cardinality {
352                sheet1
353                    .write_string(
354                        *attributes_index.get(attr_name.clone().as_str()).unwrap(),
355                        (i + 4 - skipped).try_into().unwrap(),
356                        cardinality,
357                        Some(&format_attr2),
358                    )
359                    .map_err(|e| {
360                        errors.push(e.to_string());
361                        errors.clone()
362                    })?;
363            }
364        } else if o.overlay_type().to_string().eq("Conformance") {
365            sheet1
366                .set_column(
367                    (i + 4 - skipped).try_into().unwrap(),
368                    (i + 4 - skipped).try_into().unwrap(),
369                    15.,
370                    None,
371                )
372                .map_err(|e| {
373                    errors.push(e.to_string());
374                    errors.clone()
375                })?;
376            sheet1
377                .write_string(
378                    0,
379                    (i + 4 - skipped).try_into().unwrap(),
380                    "OL: Conformance",
381                    Some(&format_header2),
382                )
383                .map_err(|e| {
384                    errors.push(e.to_string());
385                    errors.clone()
386                })?;
387
388            let overlay = o.as_any().downcast_ref::<overlay::Conformance>().unwrap();
389            for j in 0..oca.capture_base.attributes.len() {
390                sheet1
391                    .write_blank(
392                        (j + 1).try_into().unwrap(),
393                        (i + 4 - skipped).try_into().unwrap(),
394                        Some(&format_attr2),
395                    )
396                    .map_err(|e| {
397                        errors.push(e.to_string());
398                        errors.clone()
399                    })?;
400            }
401            for (attr_name, conformance) in &overlay.attribute_conformance {
402                sheet1
403                    .write_string(
404                        *attributes_index.get(attr_name.clone().as_str()).unwrap(),
405                        (i + 4 - skipped).try_into().unwrap(),
406                        conformance,
407                        Some(&format_attr2),
408                    )
409                    .map_err(|e| {
410                        errors.push(e.to_string());
411                        errors.clone()
412                    })?;
413            }
414        } else if o.overlay_type().to_string().eq("Conditional") {
415            sheet1
416                .set_column(
417                    (i + 4 - skipped).try_into().unwrap(),
418                    (i + 4 - skipped).try_into().unwrap(),
419                    15.,
420                    None,
421                )
422                .map_err(|e| {
423                    errors.push(e.to_string());
424                    errors.clone()
425                })?;
426            sheet1
427                .write_string(
428                    0,
429                    (i + 4 - skipped).try_into().unwrap(),
430                    "OL: Conditional [Condition]",
431                    Some(&format_header2),
432                )
433                .map_err(|e| {
434                    errors.push(e.to_string());
435                    errors.clone()
436                })?;
437            sheet1
438                .set_column(
439                    (i + 5 - skipped).try_into().unwrap(),
440                    (i + 4 - skipped).try_into().unwrap(),
441                    15.,
442                    None,
443                )
444                .map_err(|e| {
445                    errors.push(e.to_string());
446                    errors.clone()
447                })?;
448            sheet1
449                .write_string(
450                    0,
451                    (i + 5 - skipped).try_into().unwrap(),
452                    "OL: Conditional [Dependencies]",
453                    Some(&format_header2),
454                )
455                .map_err(|e| {
456                    errors.push(e.to_string());
457                    errors.clone()
458                })?;
459
460            let overlay = o.as_any().downcast_ref::<overlay::Conditional>().unwrap();
461            for j in 0..oca.capture_base.attributes.len() {
462                sheet1
463                    .write_blank(
464                        (j + 1).try_into().unwrap(),
465                        (i + 4 - skipped).try_into().unwrap(),
466                        Some(&format_attr2),
467                    )
468                    .map_err(|e| {
469                        errors.push(e.to_string());
470                        errors.clone()
471                    })?;
472                sheet1
473                    .write_blank(
474                        (j + 1).try_into().unwrap(),
475                        (i + 5 - skipped).try_into().unwrap(),
476                        Some(&format_attr2),
477                    )
478                    .map_err(|e| {
479                        errors.push(e.to_string());
480                        errors.clone()
481                    })?;
482            }
483            for (attr_name, condition) in &overlay.attribute_conditions {
484                sheet1
485                    .write_string(
486                        *attributes_index.get(attr_name.clone().as_str()).unwrap(),
487                        (i + 4 - skipped).try_into().unwrap(),
488                        condition,
489                        Some(&format_attr2),
490                    )
491                    .map_err(|e| {
492                        errors.push(e.to_string());
493                        errors.clone()
494                    })?;
495            }
496            for (attr_name, dependencies) in &overlay.attribute_dependencies {
497                sheet1
498                    .write_string(
499                        *attributes_index.get(attr_name.clone().as_str()).unwrap(),
500                        (i + 5 - skipped).try_into().unwrap(),
501                        &dependencies.join(","),
502                        Some(&format_attr2),
503                    )
504                    .map_err(|e| {
505                        errors.push(e.to_string());
506                        errors.clone()
507                    })?;
508            }
509
510            skipped -= 1;
511        } else if o.overlay_type().to_string().eq("Format") {
512            sheet1
513                .set_column(
514                    (i + 4 - skipped).try_into().unwrap(),
515                    (i + 4 - skipped).try_into().unwrap(),
516                    15.,
517                    None,
518                )
519                .map_err(|e| {
520                    errors.push(e.to_string());
521                    errors.clone()
522                })?;
523            sheet1
524                .write_string(
525                    0,
526                    (i + 4 - skipped).try_into().unwrap(),
527                    "OL: Format",
528                    Some(&format_header2),
529                )
530                .map_err(|e| {
531                    errors.push(e.to_string());
532                    errors.clone()
533                })?;
534
535            let overlay = o.as_any().downcast_ref::<overlay::Format>().unwrap();
536            for j in 0..oca.capture_base.attributes.len() {
537                sheet1
538                    .write_blank(
539                        (j + 1).try_into().unwrap(),
540                        (i + 4 - skipped).try_into().unwrap(),
541                        Some(&format_attr2),
542                    )
543                    .map_err(|e| {
544                        errors.push(e.to_string());
545                        errors.clone()
546                    })?;
547            }
548            for (attr_name, format) in &overlay.attribute_formats {
549                sheet1
550                    .write_string(
551                        *attributes_index.get(attr_name.clone().as_str()).unwrap(),
552                        (i + 4 - skipped).try_into().unwrap(),
553                        format,
554                        Some(&format_attr2),
555                    )
556                    .map_err(|e| {
557                        errors.push(e.to_string());
558                        errors.clone()
559                    })?;
560
561                if let "DateTime" = serde_json::to_string(oca.capture_base.attributes.get(attr_name).unwrap()).unwrap().as_str() {
562                    let mut format_attr = Format::new();
563                    format_attr.set_num_format(format);
564
565                    for r in 1..1001 {
566                        let col_i = *attributes_index.get(attr_name.clone().as_str()).unwrap() - 1;
567                        sheet2
568                            .write_blank(r, col_i.try_into().unwrap(), Some(&format_attr))
569                            .map_err(|e| {
570                                errors.push(e.to_string());
571                                errors.clone()
572                            })?;
573                        let letter = char::from_u32(65 + col_i).unwrap();
574                        let formula = format!(
575                            "=IF(ISBLANK('Data Entry'!${}${}),\"\",'Data Entry'!${}${})",
576                            letter,
577                            r + 1,
578                            letter,
579                            r + 1
580                        );
581                        sheet3
582                            .write_formula(
583                                r,
584                                col_i.try_into().unwrap(),
585                                &formula,
586                                Some(&format_attr),
587                            )
588                            .map_err(|e| {
589                                errors.push(e.to_string());
590                                errors.clone()
591                            })?;
592                    }
593                }
594            }
595        } else if o.overlay_type().to_string().eq("EntryCode") {
596            sheet1
597                .write_string(
598                    0,
599                    (i + 4 - skipped).try_into().unwrap(),
600                    "OL: Entry Code",
601                    Some(&format_header2),
602                )
603                .map_err(|e| {
604                    errors.push(e.to_string());
605                    errors.clone()
606                })?;
607
608            let overlay = o.as_any().downcast_ref::<overlay::EntryCode>().unwrap();
609            for j in 0..oca.capture_base.attributes.len() {
610                sheet1
611                    .write_blank(
612                        (j + 1).try_into().unwrap(),
613                        (i + 4 - skipped).try_into().unwrap(),
614                        Some(&format_attr2),
615                    )
616                    .map_err(|e| {
617                        errors.push(e.to_string());
618                        errors.clone()
619                    })?;
620            }
621            for (attr_name, entry_codes) in &overlay.attribute_entry_codes {
622                if let EntryCodes::Array(codes) = entry_codes {
623                    sheet1
624                        .write_string(
625                            *attributes_index.get(attr_name.clone().as_str()).unwrap(),
626                            (i + 4 - skipped).try_into().unwrap(),
627                            &codes.join("|"),
628                            Some(&format_attr2),
629                        )
630                        .map_err(|e| {
631                            errors.push(e.to_string());
632                            errors.clone()
633                        })?;
634                }
635            }
636        } else if o.overlay_type().to_string().eq("Label") {
637            if lang.is_none() {
638                lang = o.language()
639            }
640            if lang == o.language() {
641                sheet1
642                    .set_column(
643                        (i + 4 - skipped).try_into().unwrap(),
644                        (i + 4 - skipped).try_into().unwrap(),
645                        17.,
646                        None,
647                    )
648                    .map_err(|e| {
649                        errors.push(e.to_string());
650                        errors.clone()
651                    })?;
652                sheet1
653                    .write_string(
654                        0,
655                        (i + 4 - skipped).try_into().unwrap(),
656                        "OL: Label",
657                        Some(&format_header2),
658                    )
659                    .map_err(|e| {
660                        errors.push(e.to_string());
661                        errors.clone()
662                    })?;
663
664                let overlay = o.as_any().downcast_ref::<overlay::Label>().unwrap();
665
666                for j in 0..oca.capture_base.attributes.len() {
667                    sheet1
668                        .write_blank(
669                            (j + 1).try_into().unwrap(),
670                            (i + 4 - skipped).try_into().unwrap(),
671                            Some(&format_attr2),
672                        )
673                        .map_err(|e| {
674                            errors.push(e.to_string());
675                            errors.clone()
676                        })?;
677                }
678                for (attr_name, label) in &overlay.attribute_labels {
679                    sheet1
680                        .write_string(
681                            *attributes_index.get(attr_name.clone().as_str()).unwrap(),
682                            (i + 4 - skipped).try_into().unwrap(),
683                            label,
684                            Some(&format_attr2),
685                        )
686                        .map_err(|e| {
687                            errors.push(e.to_string());
688                            errors.clone()
689                        })?;
690                    sheet2
691                        .write_string(
692                            0,
693                            (*attributes_index.get(attr_name.clone().as_str()).unwrap() - 1)
694                                .try_into()
695                                .unwrap(),
696                            label,
697                            Some(&format_data_header),
698                        )
699                        .map_err(|e| {
700                            errors.push(e.to_string());
701                            errors.clone()
702                        })?;
703                }
704            } else {
705                skipped += 1;
706            }
707        } else if o.overlay_type().to_string().eq("Entry") {
708            if lang.is_none() {
709                lang = o.language()
710            }
711            if lang == o.language() {
712                sheet1
713                    .set_column(
714                        (i + 4 - skipped).try_into().unwrap(),
715                        (i + 4 - skipped).try_into().unwrap(),
716                        20.,
717                        None,
718                    )
719                    .map_err(|e| {
720                        errors.push(e.to_string());
721                        errors.clone()
722                    })?;
723                sheet1
724                    .write_string(
725                        0,
726                        (i + 4 - skipped).try_into().unwrap(),
727                        "OL: Entry",
728                        Some(&format_header2),
729                    )
730                    .map_err(|e| {
731                        errors.push(e.to_string());
732                        errors.clone()
733                    })?;
734
735                let overlay = o.as_any().downcast_ref::<overlay::Entry>().unwrap();
736                for j in 0..oca.capture_base.attributes.len() {
737                    sheet1
738                        .write_blank(
739                            (j + 1).try_into().unwrap(),
740                            (i + 4 - skipped).try_into().unwrap(),
741                            Some(&format_attr2),
742                        )
743                        .map_err(|e| {
744                            errors.push(e.to_string());
745                            errors.clone()
746                        })?;
747                }
748                for (attr_name, entries) in &overlay.attribute_entries {
749                    if let EntriesElement::Object(entries_map) = entries {
750                        lookup_entries.insert(attr_name.to_string(), entries_map);
751
752                        sheet1
753                            .write_string(
754                                *attributes_index.get(attr_name.clone().as_str()).unwrap(),
755                                (i + 4 - skipped).try_into().unwrap(),
756                                &Vec::from_iter::<Vec<String>>(
757                                    entries_map
758                                        .iter()
759                                        .map(|(k, v)| format!("{k}:{v}"))
760                                        .collect(),
761                                )
762                                .join("|"),
763                                Some(&format_attr2),
764                            )
765                            .map_err(|e| {
766                                errors.push(e.to_string());
767                                errors.clone()
768                            })?;
769                    }
770                }
771            } else {
772                skipped += 1;
773            }
774        } else if o.overlay_type().to_string().eq("Information") {
775            if lang.is_none() {
776                lang = o.language()
777            }
778            if lang == o.language() {
779                sheet1
780                    .set_column(
781                        (i + 4 - skipped).try_into().unwrap(),
782                        (i + 4 - skipped).try_into().unwrap(),
783                        20.,
784                        None,
785                    )
786                    .map_err(|e| {
787                        errors.push(e.to_string());
788                        errors.clone()
789                    })?;
790                sheet1
791                    .write_string(
792                        0,
793                        (i + 4 - skipped).try_into().unwrap(),
794                        "OL: Information",
795                        Some(&format_header2),
796                    )
797                    .map_err(|e| {
798                        errors.push(e.to_string());
799                        errors.clone()
800                    })?;
801
802                let overlay = o.as_any().downcast_ref::<overlay::Information>().unwrap();
803                for j in 0..oca.capture_base.attributes.len() {
804                    sheet1
805                        .write_blank(
806                            (j + 1).try_into().unwrap(),
807                            (i + 4 - skipped).try_into().unwrap(),
808                            Some(&format_attr2),
809                        )
810                        .map_err(|e| {
811                            errors.push(e.to_string());
812                            errors.clone()
813                        })?;
814                }
815                for (attr_name, info) in &overlay.attribute_information {
816                    sheet1
817                        .write_string(
818                            *attributes_index.get(attr_name.clone().as_str()).unwrap(),
819                            (i + 4 - skipped).try_into().unwrap(),
820                            info,
821                            Some(&format_attr2),
822                        )
823                        .map_err(|e| {
824                            errors.push(e.to_string());
825                            errors.clone()
826                        })?;
827                }
828            } else {
829                skipped += 1;
830            }
831        }
832    }
833
834    let mut lookup_table: HashMap<String, (usize, usize)> = HashMap::new();
835    let lookup_start = oca.capture_base.attributes.len() + 5;
836    sheet1
837        .write_string(
838            (lookup_start).try_into().unwrap(),
839            0,
840            "Lookup tables:",
841            Some(&format_lookup_header),
842        )
843        .map_err(|e| {
844            errors.push(e.to_string());
845            errors.clone()
846        })?;
847    sheet1
848        .write_blank(
849            (lookup_start).try_into().unwrap(),
850            1,
851            Some(&format_lookup_header),
852        )
853        .map_err(|e| {
854            errors.push(e.to_string());
855            errors.clone()
856        })?;
857    let mut offset = 0;
858    for (attr_name, entries_map) in lookup_entries {
859        sheet1
860            .write_string(
861                (lookup_start + 1 + offset).try_into().unwrap(),
862                0,
863                &attr_name,
864                Some(&format_lookup_attr),
865            )
866            .map_err(|e| {
867                errors.push(e.to_string());
868                errors.clone()
869            })?;
870        lookup_table.insert(
871            attr_name.to_string(),
872            (
873                lookup_start + 3 + offset,
874                lookup_start + 2 + offset + entries_map.len(),
875            ),
876        );
877        for (i, (k, v)) in entries_map.iter().enumerate() {
878            sheet1
879                .write_string(
880                    (lookup_start + 2 + offset + i).try_into().unwrap(),
881                    0,
882                    v,
883                    Some(&format_lookup_value),
884                )
885                .map_err(|e| {
886                    errors.push(e.to_string());
887                    errors.clone()
888                })?;
889            sheet1
890                .write_string(
891                    (lookup_start + 2 + offset + i).try_into().unwrap(),
892                    1,
893                    k,
894                    Some(&format_lookup_value),
895                )
896                .map_err(|e| {
897                    errors.push(e.to_string());
898                    errors.clone()
899                })?;
900        }
901        offset += entries_map.len() + 2;
902    }
903
904    for (attr_name, (start, end)) in &lookup_table {
905        let validation = validation::DataValidation::new(
906            validation::DataValidationType::ListFormula {
907                ignore_blank: true,
908                formula: format!("'Schema Description'!$A${start}:$A${end}"),
909            },
910            None,
911            Some(validation::ErrorAlertOptions {
912                style: validation::DataValidationErrorType::Stop,
913                title: "".to_string(),
914                message: "".to_string(),
915            }),
916        );
917        let col_i = *attributes_index.get(attr_name.clone().as_str()).unwrap() - 1;
918        let letter = char::from_u32(65 + col_i).unwrap();
919        sheet2
920            .data_validation_range(
921                1,
922                (*attributes_index.get(attr_name.clone().as_str()).unwrap() - 1)
923                    .try_into()
924                    .unwrap(),
925                1001,
926                (*attributes_index.get(attr_name.clone().as_str()).unwrap() - 1)
927                    .try_into()
928                    .unwrap(),
929                &validation,
930            )
931            .map_err(|e| {
932                errors.push(e.to_string());
933                errors.clone()
934            })?;
935
936        for r in 1..1001 {
937            let formula = format!("=IF(ISBLANK('Data Entry'!${}${}),\"\",VLOOKUP('Data Entry'!${}${},'Schema Description'!$A${}:$B${},2))", letter, r+1, letter, r+1, start, end);
938            sheet3
939                .write_formula(r, col_i.try_into().unwrap(), &formula, None)
940                .map_err(|e| {
941                    errors.push(e.to_string());
942                    errors.clone()
943                })?;
944        }
945    }
946
947    /*
948    let mut protection = Protection::new();
949    protection.no_select_locked_cells = false;
950    protection.no_select_unlocked_cells = false;
951    sheet1.protect("oca", &protection);
952    sheet3.protect("oca", &protection);
953    */
954    workbook.close().map_err(|e| {
955        errors.push(e.to_string());
956        errors.clone()
957    })?;
958
959    Ok(())
960}