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 workbook.close().map_err(|e| {
955 errors.push(e.to_string());
956 errors.clone()
957 })?;
958
959 Ok(())
960}