Skip to main content

xfa_json/
import.rs

1//! Import JSON data into a FormTree.
2//!
3//! Merges field values from a `FormData` structure back into an existing
4//! FormTree, updating field values by matching SOM-style paths.
5
6// Copyright (c) 2026 Innovation Trigger B.V.
7//
8// PDFluent is available under two licences, at your option: the GNU AGPLv3, or
9// the PDFluent Commercial Licence. See the LICENSE file in this repository --
10// that file travels with the copy you received, which a URL does not.
11
12use crate::types::{FieldValue, FormData};
13use indexmap::IndexMap;
14use xfa_layout_engine::form::{DrawContent, FormNodeId, FormNodeType, FormTree};
15
16/// Merge JSON field values into an existing FormTree.
17///
18/// Walks the tree starting at `root`, matching field paths from `data`
19/// and updating field values. Repeating sections are matched by array index.
20pub fn json_to_form_tree(data: &FormData, tree: &mut FormTree, root: FormNodeId) {
21    let node = tree.get(root);
22    let children: Vec<FormNodeId> = node.children.clone();
23
24    match &node.node_type {
25        FormNodeType::Root | FormNodeType::PageSet | FormNodeType::PageArea { .. } => {
26            for child_id in children {
27                merge_node(data, tree, child_id, "");
28            }
29        }
30        _ => {
31            merge_node(data, tree, root, "");
32        }
33    }
34}
35
36/// Recursively merge data into a subtree.
37fn merge_node(data: &FormData, tree: &mut FormTree, node_id: FormNodeId, parent_path: &str) {
38    let node = tree.get(node_id);
39    let name = node.name.clone();
40    let path = if parent_path.is_empty() {
41        name.clone()
42    } else {
43        format!("{parent_path}.{name}")
44    };
45
46    let node_type = node.node_type.clone();
47    let children: Vec<FormNodeId> = node.children.clone();
48    let is_repeating = node.occur.is_repeating();
49
50    match node_type {
51        FormNodeType::Field { .. } => {
52            if let Some(value) = data.fields.get(&path) {
53                let string_value = field_value_to_string(value);
54                tree.get_mut(node_id).node_type = FormNodeType::Field {
55                    value: string_value,
56                };
57            }
58        }
59        FormNodeType::Draw(..) => {
60            if let Some(value) = data.fields.get(&path) {
61                let string_value = field_value_to_string(value);
62                tree.get_mut(node_id).node_type =
63                    FormNodeType::Draw(DrawContent::Text(string_value));
64            }
65        }
66        FormNodeType::Image { .. } => {
67            // Images are static content - no data binding
68        }
69        // Area and ExclGroup behave like Subform for data import.
70        // SubformSet is transparent — recurse into children.
71        FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup => {
72            if is_repeating {
73                // Repeating subforms are handled by the parent via
74                // merge_children_with_repeating_groups — skip here.
75            } else {
76                merge_children_with_repeating_groups(data, tree, &children, &path);
77            }
78        }
79        FormNodeType::SubformSet
80        | FormNodeType::Root
81        | FormNodeType::PageSet
82        | FormNodeType::PageArea { .. } => {
83            merge_children_with_repeating_groups(data, tree, &children, &path);
84        }
85    }
86}
87
88/// Merge children, grouping same-name repeating siblings and assigning
89/// array indices so each sibling gets the correct element from the JSON array.
90fn merge_children_with_repeating_groups(
91    data: &FormData,
92    tree: &mut FormTree,
93    children: &[FormNodeId],
94    parent_path: &str,
95) {
96    // Track how many times we've seen each repeating name, so we can assign indices.
97    let mut repeating_counts: IndexMap<String, usize> = IndexMap::new();
98
99    for &child_id in children {
100        let child = tree.get(child_id);
101        let is_repeating = child.occur.is_repeating();
102
103        if is_repeating {
104            let name = child.name.clone();
105            let index = repeating_counts.get(&name).copied().unwrap_or(0);
106            repeating_counts.insert(name.clone(), index + 1);
107
108            let path = if parent_path.is_empty() {
109                name
110            } else {
111                format!("{parent_path}.{name}")
112            };
113
114            // Look up the array and apply the correct element by index.
115            if let Some(FieldValue::Array(instances)) = data.fields.get(&path) {
116                if let Some(instance_data) = instances.get(index) {
117                    merge_instance(tree, child_id, instance_data);
118                }
119            }
120        } else {
121            merge_node(data, tree, child_id, parent_path);
122        }
123    }
124}
125
126/// Apply a single instance map (from a JSON array element) to a repeating
127/// subform node, recursively handling nested subforms and draw nodes.
128fn merge_instance(
129    tree: &mut FormTree,
130    node_id: FormNodeId,
131    instance_data: &IndexMap<String, FieldValue>,
132) {
133    let children: Vec<FormNodeId> = tree.get(node_id).children.clone();
134
135    for &child_id in &children {
136        let child = tree.get(child_id);
137        let child_name = child.name.clone();
138        let child_type = child.node_type.clone();
139
140        match child_type {
141            FormNodeType::Field { .. } => {
142                if let Some(value) = instance_data.get(&child_name) {
143                    let string_value = field_value_to_string(value);
144                    tree.get_mut(child_id).node_type = FormNodeType::Field {
145                        value: string_value,
146                    };
147                }
148            }
149            FormNodeType::Draw(..) => {
150                if let Some(value) = instance_data.get(&child_name) {
151                    let string_value = field_value_to_string(value);
152                    tree.get_mut(child_id).node_type =
153                        FormNodeType::Draw(DrawContent::Text(string_value));
154                }
155            }
156            FormNodeType::Subform => {
157                // Nested non-repeating subform: look up dotted keys (e.g., "Address.Street")
158                let nested_prefix = format!("{child_name}.");
159                let nested_data: IndexMap<String, FieldValue> = instance_data
160                    .iter()
161                    .filter_map(|(k, v)| {
162                        k.strip_prefix(&nested_prefix)
163                            .map(|rest| (rest.to_string(), v.clone()))
164                    })
165                    .collect();
166                if !nested_data.is_empty() {
167                    merge_instance(tree, child_id, &nested_data);
168                }
169            }
170            _ => {}
171        }
172    }
173}
174
175/// Convert a FieldValue back to a string for storage in FormTree.
176fn field_value_to_string(value: &FieldValue) -> String {
177    match value {
178        FieldValue::Text(s) => s.clone(),
179        FieldValue::Number(n) => {
180            if *n == n.trunc() && n.abs() < 1e15 {
181                format!("{}", *n as i64)
182            } else {
183                n.to_string()
184            }
185        }
186        FieldValue::Boolean(b) => if *b { "1" } else { "0" }.to_string(),
187        FieldValue::Null => String::new(),
188        FieldValue::Array(_) => String::new(),
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::export::form_tree_to_json;
196    use indexmap::IndexMap;
197    use xfa_layout_engine::form::{FormNode, Occur};
198    use xfa_layout_engine::text::FontMetrics;
199    use xfa_layout_engine::types::{BoxModel, LayoutStrategy};
200
201    fn make_field(tree: &mut FormTree, name: &str, value: &str) -> FormNodeId {
202        tree.add_node(FormNode {
203            name: name.to_string(),
204            node_type: FormNodeType::Field {
205                value: value.to_string(),
206            },
207            box_model: BoxModel::default(),
208            layout: LayoutStrategy::Positioned,
209            children: vec![],
210            occur: Occur::once(),
211            font: FontMetrics::default(),
212            calculate: None,
213            validate: None,
214            column_widths: vec![],
215            col_span: 1,
216        })
217    }
218
219    fn make_subform(tree: &mut FormTree, name: &str, children: Vec<FormNodeId>) -> FormNodeId {
220        make_subform_with_occur(tree, name, children, Occur::once())
221    }
222
223    fn make_subform_with_occur(
224        tree: &mut FormTree,
225        name: &str,
226        children: Vec<FormNodeId>,
227        occur: Occur,
228    ) -> FormNodeId {
229        tree.add_node(FormNode {
230            name: name.to_string(),
231            node_type: FormNodeType::Subform,
232            box_model: BoxModel::default(),
233            layout: LayoutStrategy::TopToBottom,
234            children,
235            occur,
236            font: FontMetrics::default(),
237            calculate: None,
238            validate: None,
239            column_widths: vec![],
240            col_span: 1,
241        })
242    }
243
244    fn make_root(tree: &mut FormTree, children: Vec<FormNodeId>) -> FormNodeId {
245        tree.add_node(FormNode {
246            name: "Root".to_string(),
247            node_type: FormNodeType::Root,
248            box_model: BoxModel::default(),
249            layout: LayoutStrategy::TopToBottom,
250            children,
251            occur: Occur::once(),
252            font: FontMetrics::default(),
253            calculate: None,
254            validate: None,
255            column_widths: vec![],
256            col_span: 1,
257        })
258    }
259
260    #[test]
261    fn roundtrip_simple_form() {
262        let mut tree = FormTree::new();
263        let name = make_field(&mut tree, "Name", "Original");
264        let amount = make_field(&mut tree, "Amount", "100");
265        let form = make_subform(&mut tree, "form1", vec![name, amount]);
266        let root = make_root(&mut tree, vec![form]);
267
268        // Export → modify → import
269        let mut data = form_tree_to_json(&tree, root);
270        data.fields.insert(
271            "form1.Name".to_string(),
272            FieldValue::Text("Updated".to_string()),
273        );
274        data.fields
275            .insert("form1.Amount".to_string(), FieldValue::Number(200.0));
276
277        json_to_form_tree(&data, &mut tree, root);
278
279        // Verify updated values
280        let exported = form_tree_to_json(&tree, root);
281        assert_eq!(
282            exported.fields.get("form1.Name"),
283            Some(&FieldValue::Text("Updated".to_string()))
284        );
285        assert_eq!(
286            exported.fields.get("form1.Amount"),
287            Some(&FieldValue::Number(200.0))
288        );
289    }
290
291    #[test]
292    fn import_boolean_as_zero_one() {
293        let mut tree = FormTree::new();
294        let check = make_field(&mut tree, "Active", "0");
295        let form = make_subform(&mut tree, "form1", vec![check]);
296        let root = make_root(&mut tree, vec![form]);
297
298        let mut fields = IndexMap::new();
299        fields.insert("form1.Active".to_string(), FieldValue::Boolean(true));
300        let data = FormData { fields };
301
302        json_to_form_tree(&data, &mut tree, root);
303
304        // Boolean true → "1" in FormTree
305        match &tree.get(check).node_type {
306            FormNodeType::Field { value } => assert_eq!(value, "1"),
307            _ => panic!("Expected Field"),
308        }
309    }
310
311    #[test]
312    fn import_null_clears_field() {
313        let mut tree = FormTree::new();
314        let field = make_field(&mut tree, "Note", "something");
315        let form = make_subform(&mut tree, "form1", vec![field]);
316        let root = make_root(&mut tree, vec![form]);
317
318        let mut fields = IndexMap::new();
319        fields.insert("form1.Note".to_string(), FieldValue::Null);
320        let data = FormData { fields };
321
322        json_to_form_tree(&data, &mut tree, root);
323
324        match &tree.get(field).node_type {
325            FormNodeType::Field { value } => assert_eq!(value, ""),
326            _ => panic!("Expected Field"),
327        }
328    }
329
330    #[test]
331    // 3.14 is literal test input verifying number->string formatting, not an
332    // approximation of PI.
333    #[allow(clippy::approx_constant)]
334    fn field_value_to_string_formats() {
335        assert_eq!(field_value_to_string(&FieldValue::Number(42.0)), "42");
336        assert_eq!(field_value_to_string(&FieldValue::Number(3.14)), "3.14");
337        assert_eq!(field_value_to_string(&FieldValue::Boolean(true)), "1");
338        assert_eq!(field_value_to_string(&FieldValue::Boolean(false)), "0");
339        assert_eq!(field_value_to_string(&FieldValue::Null), "");
340        assert_eq!(
341            field_value_to_string(&FieldValue::Text("hi".to_string())),
342            "hi"
343        );
344    }
345
346    #[test]
347    fn import_repeating_by_index_not_always_first() {
348        // P1 regression: each repeating sibling must get the correct array element
349        let mut tree = FormTree::new();
350        let desc1 = make_field(&mut tree, "Desc", "old1");
351        let qty1 = make_field(&mut tree, "Qty", "0");
352        let item1 = make_subform_with_occur(
353            &mut tree,
354            "Item",
355            vec![desc1, qty1],
356            Occur::repeating(0, None, 2),
357        );
358
359        let desc2 = make_field(&mut tree, "Desc", "old2");
360        let qty2 = make_field(&mut tree, "Qty", "0");
361        let item2 = make_subform_with_occur(
362            &mut tree,
363            "Item",
364            vec![desc2, qty2],
365            Occur::repeating(0, None, 2),
366        );
367
368        let form = make_subform(&mut tree, "form1", vec![item1, item2]);
369        let root = make_root(&mut tree, vec![form]);
370
371        // Import: each array element should go to the correct sibling
372        let mut instance0 = IndexMap::new();
373        instance0.insert("Desc".to_string(), FieldValue::Text("Widget A".to_string()));
374        instance0.insert("Qty".to_string(), FieldValue::Number(10.0));
375        let mut instance1 = IndexMap::new();
376        instance1.insert("Desc".to_string(), FieldValue::Text("Widget B".to_string()));
377        instance1.insert("Qty".to_string(), FieldValue::Number(5.0));
378
379        let mut fields = IndexMap::new();
380        fields.insert(
381            "form1.Item".to_string(),
382            FieldValue::Array(vec![instance0, instance1]),
383        );
384        let data = FormData { fields };
385
386        json_to_form_tree(&data, &mut tree, root);
387
388        // Verify each sibling got the correct data
389        match &tree.get(desc1).node_type {
390            FormNodeType::Field { value } => assert_eq!(value, "Widget A"),
391            _ => panic!("Expected Field"),
392        }
393        match &tree.get(desc2).node_type {
394            FormNodeType::Field { value } => assert_eq!(value, "Widget B"),
395            _ => panic!("Expected Field"),
396        }
397        match &tree.get(qty1).node_type {
398            FormNodeType::Field { value } => assert_eq!(value, "10"),
399            _ => panic!("Expected Field"),
400        }
401        match &tree.get(qty2).node_type {
402            FormNodeType::Field { value } => assert_eq!(value, "5"),
403            _ => panic!("Expected Field"),
404        }
405    }
406
407    #[test]
408    fn import_nested_subform_inside_repeating() {
409        // P2 regression: nested structures inside repeated items must be applied
410        let mut tree = FormTree::new();
411
412        // Item[0] has a nested Address subform
413        let street1 = make_field(&mut tree, "Street", "old");
414        let addr1 = make_subform(&mut tree, "Address", vec![street1]);
415        let name1 = make_field(&mut tree, "Name", "old");
416        let item1 = make_subform_with_occur(
417            &mut tree,
418            "Item",
419            vec![name1, addr1],
420            Occur::repeating(0, None, 2),
421        );
422
423        let street2 = make_field(&mut tree, "Street", "old");
424        let addr2 = make_subform(&mut tree, "Address", vec![street2]);
425        let name2 = make_field(&mut tree, "Name", "old");
426        let item2 = make_subform_with_occur(
427            &mut tree,
428            "Item",
429            vec![name2, addr2],
430            Occur::repeating(0, None, 2),
431        );
432
433        let form = make_subform(&mut tree, "form1", vec![item1, item2]);
434        let root = make_root(&mut tree, vec![form]);
435
436        // Import with nested Address.Street keys
437        let mut inst0 = IndexMap::new();
438        inst0.insert("Name".to_string(), FieldValue::Text("Alice".to_string()));
439        inst0.insert(
440            "Address.Street".to_string(),
441            FieldValue::Text("123 Main St".to_string()),
442        );
443        let mut inst1 = IndexMap::new();
444        inst1.insert("Name".to_string(), FieldValue::Text("Bob".to_string()));
445        inst1.insert(
446            "Address.Street".to_string(),
447            FieldValue::Text("456 Oak Ave".to_string()),
448        );
449
450        let mut fields = IndexMap::new();
451        fields.insert(
452            "form1.Item".to_string(),
453            FieldValue::Array(vec![inst0, inst1]),
454        );
455        let data = FormData { fields };
456
457        json_to_form_tree(&data, &mut tree, root);
458
459        // Verify nested fields were applied
460        match &tree.get(name1).node_type {
461            FormNodeType::Field { value } => assert_eq!(value, "Alice"),
462            _ => panic!("Expected Field"),
463        }
464        match &tree.get(street1).node_type {
465            FormNodeType::Field { value } => assert_eq!(value, "123 Main St"),
466            _ => panic!("Expected Field"),
467        }
468        match &tree.get(name2).node_type {
469            FormNodeType::Field { value } => assert_eq!(value, "Bob"),
470            _ => panic!("Expected Field"),
471        }
472        match &tree.get(street2).node_type {
473            FormNodeType::Field { value } => assert_eq!(value, "456 Oak Ave"),
474            _ => panic!("Expected Field"),
475        }
476    }
477}