1use std::collections::HashMap;
35
36use lopdf::Document;
37use xfa_dom_resolver::data_dom::{DataDom, DataNode, DataNodeId};
38use xfa_layout_engine::form::{
39 Access, FieldKind, FormNodeId, FormNodeType, FormTree, GroupKind, Presence,
40};
41use xfa_layout_engine::layout::{LayoutContent, LayoutDom, LayoutEngine, LayoutNode};
42
43use crate::datasets_writeback::{self, DatasetsEdit, FormPacketEdit};
44use crate::dynamic::apply_dynamic_scripts;
45use crate::error::{Result, XfaError};
46use crate::extract::{extract_xfa_from_bytes, XfaPackets};
47use crate::flatten::{
48 apply_form_dom_presence, extract_embedded_images, inject_resolved_metrics,
49 resolve_template_fonts, XfaRenderingPolicy,
50};
51use crate::merger::FormMerger;
52
53#[derive(Debug, Clone, Copy)]
55pub enum XfaWriteValue<'a> {
56 Text(&'a str),
59 Checkbox(bool),
61 Radio(&'a str),
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum XfaFieldType {
69 Text,
71 Checkbox,
73 RadioGroup,
75 Button,
77 Dropdown,
79 Signature,
81 DateTime,
83 Numeric,
85 Password,
87 Image,
89 Barcode,
91}
92
93impl XfaFieldType {
94 fn from_kind(kind: FieldKind) -> Self {
95 match kind {
96 FieldKind::Text => XfaFieldType::Text,
97 FieldKind::Checkbox => XfaFieldType::Checkbox,
98 FieldKind::Radio => XfaFieldType::RadioGroup,
99 FieldKind::Button => XfaFieldType::Button,
100 FieldKind::Dropdown => XfaFieldType::Dropdown,
101 FieldKind::Signature => XfaFieldType::Signature,
102 FieldKind::DateTimePicker => XfaFieldType::DateTime,
103 FieldKind::NumericEdit => XfaFieldType::Numeric,
104 FieldKind::PasswordEdit => XfaFieldType::Password,
105 FieldKind::ImageEdit => XfaFieldType::Image,
106 FieldKind::Barcode => XfaFieldType::Barcode,
107 }
108 }
109
110 pub fn is_fillable(self) -> bool {
112 !matches!(
113 self,
114 XfaFieldType::Button
115 | XfaFieldType::Signature
116 | XfaFieldType::Image
117 | XfaFieldType::Barcode
118 )
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq)]
125pub struct XfaRect {
126 pub x: f64,
128 pub y: f64,
130 pub width: f64,
132 pub height: f64,
134}
135
136#[derive(Debug, Clone)]
138pub struct XfaWidget {
139 pub page: usize,
141 pub rect: XfaRect,
143 pub on_value: Option<String>,
145}
146
147#[derive(Debug, Clone)]
149pub struct XfaFieldOption {
150 pub display: String,
152 pub save: String,
155}
156
157#[derive(Debug, Clone)]
160pub struct XfaFieldModel {
161 pub name: String,
165 pub som_path: String,
168 pub field_type: XfaFieldType,
170 pub value: String,
173 pub read_only: bool,
176 pub required: bool,
178 pub multiline: bool,
180 pub hidden: bool,
183 pub options: Vec<XfaFieldOption>,
186 pub on_value: Option<String>,
188 pub off_value: Option<String>,
190 pub page: Option<usize>,
193 pub rect: Option<XfaRect>,
195 pub widgets: Vec<XfaWidget>,
197 pub bound_to_data: bool,
201 pub bind_none: bool,
204}
205
206#[derive(Debug, Clone)]
208pub struct XfaSetOutcome {
209 pub raw_value: String,
212 pub persisted_to_datasets: bool,
215}
216
217#[derive(Debug, Clone, Default)]
219pub struct XfaWritebackReport {
220 pub datasets_spliced: bool,
224 pub form_packet_values_updated: usize,
227}
228
229struct FieldEntry {
230 node: FormNodeId,
232 members: Vec<(FormNodeId, String)>,
234 read_only: bool,
236 segments: Vec<(String, usize)>,
238}
239
240pub struct XfaSession {
247 pdf_bytes: Vec<u8>,
248 packets: XfaPackets,
249 data_dom: DataDom,
250 tree: FormTree,
251 layout: LayoutDom,
252 fields: Vec<FieldEntry>,
253 models: Vec<XfaFieldModel>,
254 by_name: HashMap<String, usize>,
255 dirty_data: Vec<DataNodeId>,
257 created_data: Vec<DataNodeId>,
259 form_packet_sync: Vec<FormPacketEdit>,
261}
262
263impl XfaSession {
264 pub fn open(pdf_bytes: &[u8]) -> Result<XfaSession> {
272 let packets = extract_xfa_from_bytes(pdf_bytes.to_vec())?;
273 let template_xml = packets
274 .template()
275 .ok_or_else(|| XfaError::PacketNotFound("template".to_string()))?
276 .to_string();
277
278 let data_dom = match packets.datasets() {
282 Some(ds) => DataDom::from_xml(ds).unwrap_or_else(|e| {
283 log::warn!("XfaSession: datasets packet unparseable ({e}); starting empty");
284 DataDom::new()
285 }),
286 None => DataDom::new(),
287 };
288
289 let image_files = match Document::load_mem(pdf_bytes) {
290 Ok(doc) => extract_embedded_images(&doc),
291 Err(_) => HashMap::new(),
292 };
293
294 let merger = FormMerger::new(&data_dom).with_image_files(image_files);
295 let (mut tree, root_id) = merger
296 .merge(&template_xml)
297 .map_err(|e| XfaError::ParseFailed(format!("template merge: {e}")))?;
298
299 let _ = apply_dynamic_scripts(&mut tree, root_id)?;
303
304 if let Some(form_xml) = packets.get_packet("form") {
307 let _ = apply_form_dom_presence(
308 &mut tree,
309 root_id,
310 form_xml,
311 XfaRenderingPolicy::SavedStateFaithful,
312 true,
313 );
314 apply_form_dom_access(&mut tree, root_id, form_xml);
315 }
316
317 let resolved = resolve_template_fonts(&template_xml, pdf_bytes);
320 inject_resolved_metrics(&mut tree, &resolved);
321
322 let layout = LayoutEngine::new(&tree)
323 .layout(root_id)
324 .map_err(|e| XfaError::LayoutFailed(e.to_string()))?;
325
326 let mut session = XfaSession {
327 pdf_bytes: pdf_bytes.to_vec(),
328 packets,
329 data_dom,
330 tree,
331 layout,
332 fields: Vec::new(),
333 models: Vec::new(),
334 by_name: HashMap::new(),
335 dirty_data: Vec::new(),
336 created_data: Vec::new(),
337 form_packet_sync: Vec::new(),
338 };
339 session.enumerate_fields(root_id);
340 Ok(session)
341 }
342
343 pub fn page_count(&self) -> usize {
345 self.layout.pages.len()
346 }
347
348 pub fn page_size(&self, page: usize) -> Option<(f64, f64)> {
350 self.layout.pages.get(page).map(|p| (p.width, p.height))
351 }
352
353 pub fn fields(&self) -> &[XfaFieldModel] {
355 &self.models
356 }
357
358 pub fn field(&self, name: &str) -> Option<&XfaFieldModel> {
361 self.by_name.get(name).map(|&i| &self.models[i])
362 }
363
364 pub fn set_value(&mut self, name: &str, value: XfaWriteValue<'_>) -> Result<XfaSetOutcome> {
374 let idx = *self
375 .by_name
376 .get(name)
377 .ok_or_else(|| XfaError::FieldNotFound(name.to_string()))?;
378
379 if self.fields[idx].read_only {
380 return Err(XfaError::FieldReadOnly(name.to_string()));
381 }
382 let field_type = self.models[idx].field_type;
383 if !field_type.is_fillable() {
384 return Err(XfaError::InvalidFieldValue {
385 name: name.to_string(),
386 reason: format!("{field_type:?} fields are not fillable"),
387 });
388 }
389
390 let raw = self.normalize_value(idx, value, name)?;
391
392 self.write_tree_value(idx, &raw);
394
395 let persisted = self.write_data_value(idx, &raw)?;
397
398 if self.packets.get_packet("form").is_some() {
400 let segments = self.fields[idx].segments.clone();
401 self.form_packet_sync
404 .retain(|e| !e.segments.starts_with(&segments));
405 if self.fields[idx].members.is_empty() {
406 self.form_packet_sync.push(FormPacketEdit {
407 segments,
408 value: raw.clone(),
409 insert_if_missing: true,
410 });
411 } else {
412 for (member, on) in self.fields[idx].members.clone() {
417 let mut member_segments = segments.clone();
418 member_segments.push((self.tree.get(member).name.clone(), 0));
419 let selected = on == raw;
420 self.form_packet_sync.push(FormPacketEdit {
421 segments: member_segments,
422 value: if selected { raw.clone() } else { String::new() },
423 insert_if_missing: selected,
424 });
425 }
426 self.form_packet_sync.push(FormPacketEdit {
427 segments,
428 value: raw.clone(),
429 insert_if_missing: false,
430 });
431 }
432 }
433
434 self.models[idx].value = raw.clone();
436
437 Ok(XfaSetOutcome {
438 raw_value: raw,
439 persisted_to_datasets: persisted,
440 })
441 }
442
443 pub fn save_to_bytes(&self) -> Result<Vec<u8>> {
446 let mut doc = Document::load_mem(&self.pdf_bytes)
447 .map_err(|e| XfaError::LoadFailed(format!("reload for writeback: {e}")))?;
448 self.write_into_document(&mut doc)?;
449 let mut out = Vec::new();
450 doc.save_to(&mut out)
451 .map_err(|e| XfaError::WritebackFailed(format!("PDF save: {e}")))?;
452 Ok(out)
453 }
454
455 pub fn write_into_document(&self, doc: &mut Document) -> Result<XfaWritebackReport> {
459 let mut report = XfaWritebackReport::default();
460
461 let edits = self.collect_datasets_edits();
464 let original_datasets = self.packets.datasets();
465 let new_datasets = match original_datasets {
466 Some(orig) => match datasets_writeback::splice_datasets(orig, &edits) {
467 Some(spliced) => {
468 report.datasets_spliced = true;
469 spliced
470 }
471 None => datasets_writeback::regenerate_datasets(orig, &self.data_dom),
472 },
473 None => datasets_writeback::regenerate_datasets("", &self.data_dom),
474 };
475
476 let new_form = self.packets.get_packet("form").and_then(|orig| {
478 if self.form_packet_sync.is_empty() {
479 return None;
480 }
481 let (xml, updated) =
482 datasets_writeback::splice_form_packet(orig, &self.form_packet_sync);
483 report.form_packet_values_updated = updated;
484 if updated > 0 {
485 Some(xml)
486 } else {
487 None
488 }
489 });
490
491 datasets_writeback::write_packets_into_pdf(doc, &new_datasets, new_form.as_deref())?;
492 Ok(report)
493 }
494
495 pub fn is_dirty(&self) -> bool {
497 !self.dirty_data.is_empty()
498 || !self.created_data.is_empty()
499 || !self.form_packet_sync.is_empty()
500 }
501
502 fn normalize_value(&self, idx: usize, value: XfaWriteValue<'_>, name: &str) -> Result<String> {
505 let model = &self.models[idx];
506 match (model.field_type, value) {
507 (XfaFieldType::Checkbox, XfaWriteValue::Checkbox(on)) => Ok(if on {
508 model.on_value.clone().unwrap_or_else(|| "1".to_string())
509 } else {
510 model.off_value.clone().unwrap_or_else(|| "0".to_string())
511 }),
512 (XfaFieldType::Checkbox, XfaWriteValue::Text(s)) => {
513 let on_v = model.on_value.clone().unwrap_or_else(|| "1".to_string());
514 let off_v = model.off_value.clone().unwrap_or_else(|| "0".to_string());
515 if s == on_v || s.eq_ignore_ascii_case("true") || s == "1" {
516 Ok(on_v)
517 } else if s == off_v || s.eq_ignore_ascii_case("false") || s == "0" || s.is_empty()
518 {
519 Ok(off_v)
520 } else {
521 Err(XfaError::InvalidFieldValue {
522 name: name.to_string(),
523 reason: format!("checkbox accepts {on_v:?} or {off_v:?}, got {s:?}"),
524 })
525 }
526 }
527 (XfaFieldType::RadioGroup, XfaWriteValue::Radio(s))
528 | (XfaFieldType::RadioGroup, XfaWriteValue::Text(s)) => {
529 let known = self.fields[idx].members.iter().any(|(_, on)| on == s);
530 if known {
531 Ok(s.to_string())
532 } else {
533 let opts: Vec<&str> = self.fields[idx]
534 .members
535 .iter()
536 .map(|(_, on)| on.as_str())
537 .collect();
538 Err(XfaError::InvalidFieldValue {
539 name: name.to_string(),
540 reason: format!("radio accepts one of {opts:?}, got {s:?}"),
541 })
542 }
543 }
544 (XfaFieldType::Dropdown, XfaWriteValue::Text(s)) => {
545 if model.options.is_empty() {
546 return Ok(s.to_string());
547 }
548 if let Some(opt) = model.options.iter().find(|o| o.save == s) {
549 return Ok(opt.save.clone());
550 }
551 if let Some(opt) = model.options.iter().find(|o| o.display == s) {
552 return Ok(opt.save.clone());
553 }
554 Ok(s.to_string())
558 }
559 (_, XfaWriteValue::Text(s)) => Ok(s.to_string()),
560 (ft, v) => Err(XfaError::InvalidFieldValue {
561 name: name.to_string(),
562 reason: format!("{v:?} is not assignable to a {ft:?} field"),
563 }),
564 }
565 }
566
567 fn write_tree_value(&mut self, idx: usize, raw: &str) {
568 let entry = &self.fields[idx];
569 if entry.members.is_empty() {
570 let id = entry.node;
571 if let FormNodeType::Field { .. } = self.tree.get(id).node_type {
572 self.tree.get_mut(id).node_type = FormNodeType::Field {
573 value: raw.to_string(),
574 };
575 }
576 } else {
577 let members = entry.members.clone();
581 for (member, on_value) in members {
582 let new_value = if on_value == raw {
583 raw.to_string()
584 } else {
585 String::new()
586 };
587 if let FormNodeType::Field { .. } = self.tree.get(member).node_type {
588 self.tree.get_mut(member).node_type = FormNodeType::Field { value: new_value };
589 }
590 }
591 }
592 }
593
594 fn write_data_value(&mut self, idx: usize, raw: &str) -> Result<bool> {
597 let entry_node = self.fields[idx].node;
598 if self.tree.meta(entry_node).data_bind_none {
599 return Ok(false);
601 }
602
603 if let Some(raw_id) = self.tree.meta(entry_node).bound_data_node {
604 let id = DataNodeId::from_raw(raw_id);
605 match self.data_dom.get(id) {
606 Some(DataNode::DataValue { .. }) => {
607 self.data_dom
608 .set_value(id, raw.to_string())
609 .map_err(|e| XfaError::WritebackFailed(e.to_string()))?;
610 if !self.dirty_data.contains(&id) {
611 self.dirty_data.push(id);
612 }
613 return Ok(true);
614 }
615 Some(DataNode::DataGroup { .. }) => {
616 }
619 None => return Ok(false),
620 }
621 }
622
623 let created = self.create_data_node_for(idx, raw)?;
626 Ok(created)
627 }
628
629 fn create_data_node_for(&mut self, idx: usize, raw: &str) -> Result<bool> {
630 let field_node = self.fields[idx].node;
631 let data_name = self.tree.get(field_node).name.clone();
632 if data_name.is_empty() {
633 return Ok(false);
634 }
635
636 let mut parent_group: Option<DataNodeId> = None;
639 if let Some(raw_id) = self.tree.meta(field_node).bound_data_node {
641 let id = DataNodeId::from_raw(raw_id);
642 if matches!(self.data_dom.get(id), Some(DataNode::DataGroup { .. })) {
643 parent_group = Some(id);
650 }
651 }
652 if parent_group.is_none() {
653 let mut cursor = self.parent_of(field_node);
654 while let Some(p) = cursor {
655 if let Some(raw_id) = self.tree.meta(p).bound_data_node {
656 let id = DataNodeId::from_raw(raw_id);
657 if matches!(self.data_dom.get(id), Some(DataNode::DataGroup { .. })) {
658 parent_group = Some(id);
659 break;
660 }
661 }
662 cursor = self.parent_of(p);
663 }
664 }
665 if parent_group.is_none() {
666 if self.tree.meta(field_node).data_bind_ref.is_none() {
672 parent_group = self.synthesize_group_chain(idx)?;
673 }
674 }
675 let Some(group) = parent_group else {
676 return Ok(false);
677 };
678
679 let existing = self
681 .data_dom
682 .children_by_name(group, &data_name)
683 .into_iter()
684 .find(|&c| matches!(self.data_dom.get(c), Some(DataNode::DataValue { .. })));
685 let id = match existing {
686 Some(c) => {
687 self.data_dom
688 .set_value(c, raw.to_string())
689 .map_err(|e| XfaError::WritebackFailed(e.to_string()))?;
690 if !self.dirty_data.contains(&c) {
691 self.dirty_data.push(c);
692 }
693 self.tree.meta_mut(self.fields[idx].node).bound_data_node = Some(c.as_raw());
694 return Ok(true);
695 }
696 None => self
697 .data_dom
698 .create_value(group, &data_name, raw)
699 .map_err(|e| XfaError::WritebackFailed(e.to_string()))?,
700 };
701 self.created_data.push(id);
702 self.tree.meta_mut(self.fields[idx].node).bound_data_node = Some(id.as_raw());
703 Ok(true)
704 }
705
706 fn synthesize_group_chain(&mut self, idx: usize) -> Result<Option<DataNodeId>> {
712 let segments = self.fields[idx].segments.clone();
713 if segments.len() < 2 {
714 if segments.is_empty() {
716 return Ok(None);
717 }
718 }
719
720 let root = match self.data_dom.root() {
721 Some(r)
722 if self
723 .data_dom
724 .get(r)
725 .map(DataNode::is_group)
726 .unwrap_or(false) =>
727 {
728 r
729 }
730 _ => {
731 let r = self.data_dom.alloc(DataNode::DataGroup {
732 name: "data".to_string(),
733 namespace: None,
734 children: Vec::new(),
735 is_record: false,
736 parent: None,
737 });
738 self.data_dom.set_root(r);
739 r
740 }
741 };
742
743 let mut cursor = root;
744 for (name, index) in &segments[..segments.len() - 1] {
745 let groups: Vec<DataNodeId> = self
746 .data_dom
747 .children_by_name(cursor, name)
748 .into_iter()
749 .filter(|&c| {
750 self.data_dom
751 .get(c)
752 .map(DataNode::is_group)
753 .unwrap_or(false)
754 })
755 .collect();
756 cursor = match groups.get(*index) {
757 Some(&g) => g,
758 None if *index == 0 => self
759 .data_dom
760 .create_group(cursor, name)
761 .map_err(|e| XfaError::WritebackFailed(e.to_string()))?,
762 None => return Ok(None),
763 };
764 }
765 Ok(Some(cursor))
766 }
767
768 fn parent_of(&self, node: FormNodeId) -> Option<FormNodeId> {
769 for (i, n) in self.tree.nodes.iter().enumerate() {
771 if n.children.contains(&node) {
772 return Some(FormNodeId(i));
773 }
774 }
775 None
776 }
777
778 fn collect_datasets_edits(&self) -> Vec<DatasetsEdit> {
779 let mut edits = Vec::new();
780 for &id in &self.dirty_data {
781 if let Some(path) = self.data_path_of(id) {
782 if let Ok(value) = self.data_dom.value(id) {
783 edits.push(DatasetsEdit::SetValue {
784 path,
785 value: value.to_string(),
786 });
787 }
788 }
789 }
790 for &id in &self.created_data {
791 let Some(node) = self.data_dom.get(id) else {
792 continue;
793 };
794 let name = node.name().to_string();
795 let Some(parent) = node.parent() else {
796 continue;
797 };
798 let Some(parent_path) = self.data_path_of(parent) else {
799 continue;
800 };
801 if let Ok(value) = self.data_dom.value(id) {
802 edits.push(DatasetsEdit::CreateValue {
803 parent_path,
804 name,
805 value: value.to_string(),
806 });
807 }
808 }
809 edits
810 }
811
812 fn data_path_of(&self, id: DataNodeId) -> Option<Vec<(String, usize)>> {
817 let root = self.data_dom.root()?;
818 let mut rev = Vec::new();
819 let mut cursor = Some(id);
820 while let Some(c) = cursor {
821 let node = self.data_dom.get(c)?;
822 let name = node.name().to_string();
823 let at_root = c == root;
824 let parent = if at_root { None } else { node.parent() };
825 let index = match parent {
826 Some(p) => {
827 let mut k = 0usize;
831 let mut found = None;
832 for &sib in self.data_dom.children(p) {
833 let Some(sn) = self.data_dom.get(sib) else {
834 continue;
835 };
836 if sn.name() != name {
837 continue;
838 }
839 if is_metadata_value(sn) {
840 continue;
841 }
842 if sib == c {
843 found = Some(k);
844 break;
845 }
846 k += 1;
847 }
848 found?
849 }
850 None => 0,
851 };
852 rev.push((name, index));
853 if at_root {
854 cursor = None;
855 } else {
856 cursor = Some(node.parent()?);
860 }
861 }
862 rev.reverse();
863 Some(rev)
864 }
865
866 fn enumerate_fields(&mut self, root: FormNodeId) {
869 let mut geometry: HashMap<usize, Vec<(usize, XfaRect)>> = HashMap::new();
871 for (page_idx, page) in self.layout.pages.iter().enumerate() {
872 for node in &page.nodes {
873 collect_geometry(node, 0.0, 0.0, page_idx, &mut geometry);
874 }
875 }
876
877 let mut entries = Vec::new();
878 let mut segments: Vec<(String, usize)> = Vec::new();
879 let mut sibling_counters: Vec<HashMap<String, usize>> = vec![HashMap::new()];
880 let root_access = self.tree.meta(root).access.unwrap_or(Access::Open);
884 for &child in &self.tree.get(root).children.clone() {
885 self.walk_enumerate(
886 child,
887 root_access,
888 &mut segments,
889 &mut sibling_counters,
890 &mut entries,
891 );
892 }
893
894 let mut models = Vec::new();
895 let mut by_name: HashMap<String, usize> = HashMap::new();
896 for entry in entries {
897 let model = self.build_model(&entry, &geometry);
898 let idx = models.len();
899 by_name.entry(model.name.clone()).or_insert(idx);
902 by_name.entry(model.som_path.clone()).or_insert(idx);
903 models.push(model);
904 self.fields.push(entry);
905 }
906 self.models = models;
907 self.by_name = by_name;
908 }
909
910 #[allow(clippy::too_many_arguments)]
911 fn walk_enumerate(
912 &self,
913 node_id: FormNodeId,
914 inherited_access: Access,
915 segments: &mut Vec<(String, usize)>,
916 sibling_counters: &mut Vec<HashMap<String, usize>>,
917 out: &mut Vec<FieldEntry>,
918 ) {
919 let node = self.tree.get(node_id);
920 let meta = self.tree.meta(node_id);
921 let access = meta.access.unwrap_or(inherited_access);
922
923 let named = !node.name.is_empty();
924 if named {
925 let counter = sibling_counters
926 .last_mut()
927 .expect("sibling counter scope present");
928 let idx = *counter
929 .entry(node.name.clone())
930 .and_modify(|c| *c += 1)
931 .or_insert(0);
932 segments.push((node.name.clone(), idx));
933 }
934
935 let is_radio_group = matches!(node.node_type, FormNodeType::ExclGroup)
936 || meta.group_kind == GroupKind::ExclusiveChoice;
937 let is_field = matches!(node.node_type, FormNodeType::Field { .. });
938
939 if is_radio_group {
940 let mut members = Vec::new();
941 for &child in &node.children {
942 if let FormNodeType::Field { .. } = self.tree.get(child).node_type {
943 let cmeta = self.tree.meta(child);
944 let on = cmeta
945 .item_value
946 .clone()
947 .or_else(|| cmeta.style.check_button_on_value.clone())
948 .unwrap_or_else(|| self.tree.get(child).name.clone());
949 members.push((child, on));
950 }
951 }
952 if !members.is_empty() {
953 out.push(FieldEntry {
954 node: node_id,
955 members,
956 read_only: access.denies_writes(),
957 segments: segments.clone(),
958 });
959 }
960 } else if is_field {
961 out.push(FieldEntry {
962 node: node_id,
963 members: Vec::new(),
964 read_only: access.denies_writes(),
965 segments: segments.clone(),
966 });
967 }
968
969 if !is_field && !is_radio_group {
972 sibling_counters.push(HashMap::new());
973 for &child in &node.children {
974 self.walk_enumerate(child, access, segments, sibling_counters, out);
975 }
976 sibling_counters.pop();
977 }
978
979 if named {
980 segments.pop();
981 }
982 }
983
984 fn build_model(
985 &self,
986 entry: &FieldEntry,
987 geometry: &HashMap<usize, Vec<(usize, XfaRect)>>,
988 ) -> XfaFieldModel {
989 let node = self.tree.get(entry.node);
990 let meta = self.tree.meta(entry.node);
991
992 let name = segments_to_display_name(&entry.segments);
993 let som_path = segments_to_som_path(&entry.segments);
994
995 let field_type = if entry.members.is_empty() {
996 XfaFieldType::from_kind(meta.field_kind)
997 } else {
998 XfaFieldType::RadioGroup
999 };
1000
1001 let value = if entry.members.is_empty() {
1003 match &node.node_type {
1004 FormNodeType::Field { value } => value.clone(),
1005 _ => String::new(),
1006 }
1007 } else {
1008 entry
1009 .members
1010 .iter()
1011 .find_map(|(m, on)| match &self.tree.get(*m).node_type {
1012 FormNodeType::Field { value } if !value.is_empty() && value == on => {
1013 Some(on.clone())
1014 }
1015 _ => None,
1016 })
1017 .unwrap_or_default()
1018 };
1019
1020 let options = if !entry.members.is_empty() {
1022 entry
1023 .members
1024 .iter()
1025 .map(|(_, on)| XfaFieldOption {
1026 display: on.clone(),
1027 save: on.clone(),
1028 })
1029 .collect()
1030 } else {
1031 let displays = &meta.display_items;
1032 let saves = &meta.save_items;
1033 displays
1034 .iter()
1035 .enumerate()
1036 .map(|(i, d)| XfaFieldOption {
1037 display: d.clone(),
1038 save: saves.get(i).cloned().unwrap_or_else(|| d.clone()),
1039 })
1040 .collect()
1041 };
1042
1043 let on_value = meta
1044 .style
1045 .check_button_on_value
1046 .clone()
1047 .or_else(|| meta.item_value.clone());
1048 let off_value = meta.style.check_button_off_value.clone();
1049
1050 let mut widgets: Vec<XfaWidget> = Vec::new();
1052 if let Some(occ) = geometry.get(&entry.node.0) {
1053 for (page, rect) in occ {
1054 widgets.push(XfaWidget {
1055 page: *page,
1056 rect: *rect,
1057 on_value: None,
1058 });
1059 }
1060 }
1061 for (member, on) in &entry.members {
1062 if let Some(occ) = geometry.get(&member.0) {
1063 for (page, rect) in occ {
1064 widgets.push(XfaWidget {
1065 page: *page,
1066 rect: *rect,
1067 on_value: Some(on.clone()),
1068 });
1069 }
1070 }
1071 }
1072 widgets.sort_by(|a, b| {
1073 a.page.cmp(&b.page).then(
1074 a.rect
1075 .y
1076 .partial_cmp(&b.rect.y)
1077 .unwrap_or(std::cmp::Ordering::Equal),
1078 )
1079 });
1080 let page = widgets.first().map(|w| w.page);
1081 let rect = widgets.first().map(|w| w.rect);
1082
1083 let bound = meta
1084 .bound_data_node
1085 .map(|raw| self.data_dom.get(DataNodeId::from_raw(raw)).is_some())
1086 .unwrap_or(false);
1087
1088 XfaFieldModel {
1089 name,
1090 som_path,
1091 field_type,
1092 value,
1093 read_only: entry.read_only,
1094 required: meta.required,
1095 multiline: meta.multiline,
1096 hidden: meta.presence != Presence::Visible,
1097 options,
1098 on_value,
1099 off_value,
1100 page,
1101 rect,
1102 widgets,
1103 bound_to_data: bound,
1104 bind_none: meta.data_bind_none,
1105 }
1106 }
1107}
1108
1109fn is_metadata_value(node: &DataNode) -> bool {
1110 match node {
1111 DataNode::DataValue { contains, .. } => {
1112 *contains == xfa_dom_resolver::data_dom::DataContains::MetaData
1113 }
1114 DataNode::DataGroup { .. } => false,
1115 }
1116}
1117
1118fn segments_to_display_name(segments: &[(String, usize)]) -> String {
1119 segments
1120 .iter()
1121 .map(|(n, i)| {
1122 if *i == 0 {
1123 n.clone()
1124 } else {
1125 format!("{n}[{i}]")
1126 }
1127 })
1128 .collect::<Vec<_>>()
1129 .join(".")
1130}
1131
1132fn segments_to_som_path(segments: &[(String, usize)]) -> String {
1133 segments
1134 .iter()
1135 .map(|(n, i)| format!("{n}[{i}]"))
1136 .collect::<Vec<_>>()
1137 .join(".")
1138}
1139
1140fn collect_geometry(
1146 node: &LayoutNode,
1147 parent_x: f64,
1148 parent_y: f64,
1149 page_idx: usize,
1150 out: &mut HashMap<usize, Vec<(usize, XfaRect)>>,
1151) {
1152 let abs_x = node.rect.x + parent_x;
1153 let abs_y = node.rect.y + parent_y;
1154
1155 let record = matches!(node.content, LayoutContent::Field { .. })
1156 || matches!(
1157 node.content,
1158 LayoutContent::WrappedText {
1159 from_field: true,
1160 ..
1161 }
1162 );
1163 if record {
1164 out.entry(node.form_node.0).or_default().push((
1165 page_idx,
1166 XfaRect {
1167 x: abs_x,
1168 y: abs_y,
1169 width: node.rect.width,
1170 height: node.rect.height,
1171 },
1172 ));
1173 }
1174
1175 for child in &node.children {
1176 collect_geometry(child, abs_x, abs_y, page_idx, out);
1177 }
1178}
1179
1180fn apply_form_dom_access(tree: &mut FormTree, root_id: FormNodeId, form_xml: &str) {
1187 let Ok(doc) = roxmltree::Document::parse(form_xml) else {
1188 return;
1189 };
1190
1191 fn matches_node(tree: &FormTree, fid: FormNodeId, tag: &str, name: &str) -> bool {
1192 let node = tree.get(fid);
1193 match (tag, &node.node_type) {
1194 ("subform", FormNodeType::Subform | FormNodeType::Area) => node.name == name,
1195 ("exclGroup", FormNodeType::ExclGroup) => node.name == name,
1196 ("field", FormNodeType::Field { .. } | FormNodeType::ExclGroup) => node.name == name,
1198 _ => false,
1199 }
1200 }
1201
1202 fn walk(tree: &mut FormTree, fid: FormNodeId, xml: roxmltree::Node<'_, '_>) {
1203 if let Some(access) = xml.attribute("access") {
1204 tree.meta_mut(fid).access = Some(Access::parse(access));
1205 }
1206
1207 let xml_children: Vec<roxmltree::Node<'_, '_>> = xml
1208 .children()
1209 .filter(|c| {
1210 c.is_element() && matches!(c.tag_name().name(), "subform" | "field" | "exclGroup")
1211 })
1212 .collect();
1213 if xml_children.is_empty() {
1214 return;
1215 }
1216
1217 let tree_children = tree.get(fid).children.clone();
1218 let mut used = vec![false; tree_children.len()];
1219 for xc in xml_children {
1220 let tag = xc.tag_name().name();
1221 let name = xc.attribute("name").unwrap_or("");
1222 if name.is_empty() {
1223 continue;
1224 }
1225 for (i, &cid) in tree_children.iter().enumerate() {
1226 if used[i] || !matches_node(tree, cid, tag, name) {
1227 continue;
1228 }
1229 used[i] = true;
1230 walk(tree, cid, xc);
1231 break;
1232 }
1233 }
1234 }
1235
1236 let Some(form_root) = doc.root().children().find(|c| c.is_element()) else {
1241 return;
1242 };
1243 let root_children = tree.get(root_id).children.clone();
1246 let packet_subforms: Vec<roxmltree::Node<'_, '_>> = form_root
1247 .children()
1248 .filter(|c| c.is_element() && c.tag_name().name() == "subform")
1249 .collect();
1250 for xc in packet_subforms {
1251 let name = xc.attribute("name").unwrap_or("");
1252 for &cid in &root_children {
1253 if matches_node(tree, cid, "subform", name) {
1254 walk(tree, cid, xc);
1255 break;
1256 }
1257 }
1258 }
1259}