Skip to main content

xfa_json/
export.rs

1//! Export FormTree to JSON.
2//!
3//! Walks the FormTree recursively and produces a flat `FormData` with
4//! SOM-style dotted paths as keys and coerced typed values.
5
6use crate::coerce::coerce_value;
7use crate::types::{FieldValue, FormData};
8use indexmap::IndexMap;
9use xfa_layout_engine::form::{DrawContent, FormNodeId, FormNodeType, FormTree};
10
11/// Convert a FormTree into a JSON-friendly `FormData` structure.
12///
13/// Walks the tree starting at `root`, collecting field values keyed
14/// by their SOM-style dotted path (e.g., `"form1.Customer.Name"`).
15/// Repeating subforms become arrays.
16pub fn form_tree_to_json(tree: &FormTree, root: FormNodeId) -> FormData {
17    let mut fields = IndexMap::new();
18    let node = tree.get(root);
19
20    // Skip root-level structural nodes and dive into content
21    match &node.node_type {
22        FormNodeType::Root | FormNodeType::PageSet | FormNodeType::PageArea { .. } => {
23            for &child_id in &node.children {
24                walk_node(tree, child_id, "", &mut fields);
25            }
26        }
27        _ => {
28            walk_node(tree, root, "", &mut fields);
29        }
30    }
31
32    FormData { fields }
33}
34
35/// Convert a FormTree into a raw `serde_json::Value`.
36///
37/// Convenience wrapper that serializes `FormData` directly.
38pub fn form_tree_to_value(tree: &FormTree, root: FormNodeId) -> serde_json::Value {
39    let data = form_tree_to_json(tree, root);
40    serde_json::to_value(data).unwrap_or(serde_json::Value::Null)
41}
42
43/// Recursively walk the FormTree, collecting fields into the flat map.
44fn walk_node(
45    tree: &FormTree,
46    node_id: FormNodeId,
47    parent_path: &str,
48    fields: &mut IndexMap<String, FieldValue>,
49) {
50    let node = tree.get(node_id);
51    let path = if parent_path.is_empty() {
52        node.name.clone()
53    } else {
54        format!("{}.{}", parent_path, node.name)
55    };
56
57    match &node.node_type {
58        FormNodeType::Field { value } => {
59            fields.insert(path, coerce_value(value));
60        }
61        FormNodeType::Draw(DrawContent::Text(content)) => {
62            if !content.is_empty() {
63                fields.insert(path, FieldValue::Text(content.clone()));
64            }
65        }
66        FormNodeType::Draw(_) | FormNodeType::Image { .. } => {
67            // Non-text draws and images are static content - not exported as form data
68        }
69        // Area and ExclGroup behave like Subform for data export.
70        // SubformSet is transparent — recurse into children.
71        FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup => {
72            if node.occur.is_repeating() {
73                // Repeating subform: collect siblings with the same name as an array.
74                // The caller handles this via collect_repeating_siblings.
75                // For a single instance in a repeating group, still emit as array.
76                let mut instance = IndexMap::new();
77                for &child_id in &node.children {
78                    walk_node_into_map(tree, child_id, &mut instance);
79                }
80                // Merge into existing array if present, or create new one
81                match fields.get_mut(&path) {
82                    Some(FieldValue::Array(arr)) => {
83                        arr.push(instance);
84                    }
85                    _ => {
86                        fields.insert(path, FieldValue::Array(vec![instance]));
87                    }
88                }
89            } else {
90                // Non-repeating subform: recurse with extended path
91                for &child_id in &node.children {
92                    walk_node(tree, child_id, &path, fields);
93                }
94            }
95        }
96        FormNodeType::SubformSet
97        | FormNodeType::Root
98        | FormNodeType::PageSet
99        | FormNodeType::PageArea { .. } => {
100            for &child_id in &node.children {
101                walk_node(tree, child_id, &path, fields);
102            }
103        }
104    }
105}
106
107/// Walk a node into a flat map (used for repeating section instances).
108fn walk_node_into_map(
109    tree: &FormTree,
110    node_id: FormNodeId,
111    map: &mut IndexMap<String, FieldValue>,
112) {
113    let node = tree.get(node_id);
114
115    match &node.node_type {
116        FormNodeType::Field { value } => {
117            map.insert(node.name.clone(), coerce_value(value));
118        }
119        FormNodeType::Draw(DrawContent::Text(content)) => {
120            if !content.is_empty() {
121                map.insert(node.name.clone(), FieldValue::Text(content.clone()));
122            }
123        }
124        FormNodeType::Draw(_) | FormNodeType::Image { .. } => {
125            // Non-text draws and images are static content
126        }
127        FormNodeType::Subform => {
128            if node.occur.is_repeating() {
129                let mut instance = IndexMap::new();
130                for &child_id in &node.children {
131                    walk_node_into_map(tree, child_id, &mut instance);
132                }
133                match map.get_mut(&node.name) {
134                    Some(FieldValue::Array(arr)) => {
135                        arr.push(instance);
136                    }
137                    _ => {
138                        map.insert(node.name.clone(), FieldValue::Array(vec![instance]));
139                    }
140                }
141            } else {
142                // Nested non-repeating: prefix with subform name
143                for &child_id in &node.children {
144                    let child = tree.get(child_id);
145                    let key = format!("{}.{}", node.name, child.name);
146                    match &child.node_type {
147                        FormNodeType::Field { value } => {
148                            map.insert(key, coerce_value(value));
149                        }
150                        FormNodeType::Draw(DrawContent::Text(content)) => {
151                            if !content.is_empty() {
152                                map.insert(key, FieldValue::Text(content.clone()));
153                            }
154                        }
155                        _ => {
156                            walk_node_into_map(tree, child_id, map);
157                        }
158                    }
159                }
160            }
161        }
162        _ => {
163            for &child_id in &node.children {
164                walk_node_into_map(tree, child_id, map);
165            }
166        }
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use xfa_layout_engine::form::{FormNode, Occur};
174    use xfa_layout_engine::text::FontMetrics;
175    use xfa_layout_engine::types::{BoxModel, LayoutStrategy};
176
177    fn make_field(tree: &mut FormTree, name: &str, value: &str) -> FormNodeId {
178        tree.add_node(FormNode {
179            name: name.to_string(),
180            node_type: FormNodeType::Field {
181                value: value.to_string(),
182            },
183            box_model: BoxModel::default(),
184            layout: LayoutStrategy::Positioned,
185            children: vec![],
186            occur: Occur::once(),
187            font: FontMetrics::default(),
188            calculate: None,
189            validate: None,
190            column_widths: vec![],
191            col_span: 1,
192        })
193    }
194
195    fn make_subform(
196        tree: &mut FormTree,
197        name: &str,
198        children: Vec<FormNodeId>,
199        occur: Occur,
200    ) -> FormNodeId {
201        tree.add_node(FormNode {
202            name: name.to_string(),
203            node_type: FormNodeType::Subform,
204            box_model: BoxModel::default(),
205            layout: LayoutStrategy::TopToBottom,
206            children,
207            occur,
208            font: FontMetrics::default(),
209            calculate: None,
210            validate: None,
211            column_widths: vec![],
212            col_span: 1,
213        })
214    }
215
216    fn make_root(tree: &mut FormTree, children: Vec<FormNodeId>) -> FormNodeId {
217        tree.add_node(FormNode {
218            name: "Root".to_string(),
219            node_type: FormNodeType::Root,
220            box_model: BoxModel::default(),
221            layout: LayoutStrategy::TopToBottom,
222            children,
223            occur: Occur::once(),
224            font: FontMetrics::default(),
225            calculate: None,
226            validate: None,
227            column_widths: vec![],
228            col_span: 1,
229        })
230    }
231
232    #[test]
233    fn simple_form_export() {
234        let mut tree = FormTree::new();
235        let name = make_field(&mut tree, "Name", "Acme Corp");
236        let amount = make_field(&mut tree, "Amount", "42.50");
237        let active = make_field(&mut tree, "Active", "true");
238
239        let form = make_subform(
240            &mut tree,
241            "form1",
242            vec![name, amount, active],
243            Occur::once(),
244        );
245        let root = make_root(&mut tree, vec![form]);
246
247        let data = form_tree_to_json(&tree, root);
248
249        assert_eq!(
250            data.fields.get("form1.Name"),
251            Some(&FieldValue::Text("Acme Corp".to_string()))
252        );
253        assert_eq!(
254            data.fields.get("form1.Amount"),
255            Some(&FieldValue::Number(42.50))
256        );
257        assert_eq!(
258            data.fields.get("form1.Active"),
259            Some(&FieldValue::Boolean(true))
260        );
261    }
262
263    #[test]
264    fn nested_subforms() {
265        let mut tree = FormTree::new();
266        let street = make_field(&mut tree, "Street", "123 Main St");
267        let city = make_field(&mut tree, "City", "Springfield");
268
269        let address = make_subform(&mut tree, "Address", vec![street, city], Occur::once());
270        let form = make_subform(&mut tree, "form1", vec![address], Occur::once());
271        let root = make_root(&mut tree, vec![form]);
272
273        let data = form_tree_to_json(&tree, root);
274
275        assert_eq!(
276            data.fields.get("form1.Address.Street"),
277            Some(&FieldValue::Text("123 Main St".to_string()))
278        );
279        assert_eq!(
280            data.fields.get("form1.Address.City"),
281            Some(&FieldValue::Text("Springfield".to_string()))
282        );
283    }
284
285    #[test]
286    fn repeating_subforms_as_arrays() {
287        let mut tree = FormTree::new();
288
289        let desc1 = make_field(&mut tree, "Description", "Widget A");
290        let qty1 = make_field(&mut tree, "Qty", "10");
291        let item1 = make_subform(
292            &mut tree,
293            "Item",
294            vec![desc1, qty1],
295            Occur::repeating(0, None, 2),
296        );
297
298        let desc2 = make_field(&mut tree, "Description", "Widget B");
299        let qty2 = make_field(&mut tree, "Qty", "5");
300        let item2 = make_subform(
301            &mut tree,
302            "Item",
303            vec![desc2, qty2],
304            Occur::repeating(0, None, 2),
305        );
306
307        let form = make_subform(&mut tree, "form1", vec![item1, item2], Occur::once());
308        let root = make_root(&mut tree, vec![form]);
309
310        let data = form_tree_to_json(&tree, root);
311
312        let items = data.fields.get("form1.Item").unwrap();
313        match items {
314            FieldValue::Array(arr) => {
315                assert_eq!(arr.len(), 2);
316                assert_eq!(
317                    arr[0].get("Description"),
318                    Some(&FieldValue::Text("Widget A".to_string()))
319                );
320                assert_eq!(arr[0].get("Qty"), Some(&FieldValue::Number(10.0)));
321                assert_eq!(
322                    arr[1].get("Description"),
323                    Some(&FieldValue::Text("Widget B".to_string()))
324                );
325                assert_eq!(arr[1].get("Qty"), Some(&FieldValue::Number(5.0)));
326            }
327            _ => panic!("Expected Array, got {items:?}"),
328        }
329    }
330
331    #[test]
332    fn empty_fields_are_null() {
333        let mut tree = FormTree::new();
334        let empty = make_field(&mut tree, "Empty", "");
335        let form = make_subform(&mut tree, "form1", vec![empty], Occur::once());
336        let root = make_root(&mut tree, vec![form]);
337
338        let data = form_tree_to_json(&tree, root);
339        assert_eq!(data.fields.get("form1.Empty"), Some(&FieldValue::Null));
340    }
341
342    #[test]
343    fn form_tree_to_value_produces_valid_json() {
344        let mut tree = FormTree::new();
345        let name = make_field(&mut tree, "Name", "Test");
346        let form = make_subform(&mut tree, "form1", vec![name], Occur::once());
347        let root = make_root(&mut tree, vec![form]);
348
349        let value = form_tree_to_value(&tree, root);
350        assert!(value.is_object());
351
352        let fields = value.get("fields").unwrap();
353        assert_eq!(fields.get("form1.Name").unwrap(), "Test");
354    }
355
356    #[test]
357    fn type_coercion_in_export() {
358        let mut tree = FormTree::new();
359        let num = make_field(&mut tree, "Total", "112.50");
360        let flag = make_field(&mut tree, "Checked", "0");
361        let text = make_field(&mut tree, "Note", "See attached");
362        let null_field = make_field(&mut tree, "Blank", "");
363
364        let form = make_subform(
365            &mut tree,
366            "form1",
367            vec![num, flag, text, null_field],
368            Occur::once(),
369        );
370        let root = make_root(&mut tree, vec![form]);
371
372        let data = form_tree_to_json(&tree, root);
373
374        assert_eq!(
375            data.fields.get("form1.Total"),
376            Some(&FieldValue::Number(112.50))
377        );
378        assert_eq!(
379            data.fields.get("form1.Checked"),
380            Some(&FieldValue::Boolean(false))
381        );
382        assert_eq!(
383            data.fields.get("form1.Note"),
384            Some(&FieldValue::Text("See attached".to_string()))
385        );
386        assert_eq!(data.fields.get("form1.Blank"), Some(&FieldValue::Null));
387    }
388}