1use crate::types::{FieldSchema, FieldType, FormSchema};
7use indexmap::IndexMap;
8use xfa_layout_engine::form::{FormNodeId, FormNodeType, FormTree};
9
10pub fn export_schema(tree: &FormTree, root: FormNodeId) -> FormSchema {
15 let mut fields = IndexMap::new();
16 let node = tree.get(root);
17
18 match &node.node_type {
19 FormNodeType::Root | FormNodeType::PageSet | FormNodeType::PageArea { .. } => {
20 for &child_id in &node.children {
21 walk_schema(tree, child_id, "", false, &mut fields);
22 }
23 }
24 _ => {
25 walk_schema(tree, root, "", false, &mut fields);
26 }
27 }
28
29 FormSchema { fields }
30}
31
32fn walk_schema(
34 tree: &FormTree,
35 node_id: FormNodeId,
36 parent_path: &str,
37 parent_repeatable: bool,
38 fields: &mut IndexMap<String, FieldSchema>,
39) {
40 let node = tree.get(node_id);
41 let path = if parent_path.is_empty() {
42 node.name.clone()
43 } else {
44 format!("{}.{}", parent_path, node.name)
45 };
46
47 let is_repeatable = parent_repeatable || node.occur.is_repeating();
48
49 match &node.node_type {
50 FormNodeType::Field { value } => {
51 let field_type = infer_field_type(value);
52 fields.insert(
53 path.clone(),
54 FieldSchema {
55 som_path: path,
56 field_type,
57 required: node.occur.min > 0,
58 repeatable: parent_repeatable,
59 max_occurrences: node.occur.max,
60 calculate: node.calculate.clone(),
61 validate: node.validate.clone(),
62 },
63 );
64 }
65 FormNodeType::Draw(..) | FormNodeType::Image { .. } => {
66 fields.insert(
67 path.clone(),
68 FieldSchema {
69 som_path: path,
70 field_type: FieldType::Static,
71 required: false,
72 repeatable: parent_repeatable,
73 max_occurrences: Some(1),
74 calculate: None,
75 validate: None,
76 },
77 );
78 }
79 FormNodeType::Subform
81 | FormNodeType::Area
82 | FormNodeType::ExclGroup
83 | FormNodeType::SubformSet
84 | FormNodeType::Root
85 | FormNodeType::PageSet
86 | FormNodeType::PageArea { .. } => {
87 for &child_id in &node.children {
88 walk_schema(tree, child_id, &path, is_repeatable, fields);
89 }
90 }
91 }
92}
93
94fn infer_field_type(value: &str) -> FieldType {
96 let trimmed = value.trim();
97
98 if trimmed.is_empty() {
99 return FieldType::Text; }
101
102 match trimmed.to_ascii_lowercase().as_str() {
103 "true" | "false" | "0" | "1" => return FieldType::Boolean,
104 _ => {}
105 }
106
107 if trimmed.parse::<f64>().is_ok() {
108 return FieldType::Numeric;
109 }
110
111 FieldType::Text
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use xfa_layout_engine::form::{FormNode, Occur};
118 use xfa_layout_engine::text::FontMetrics;
119 use xfa_layout_engine::types::{BoxModel, LayoutStrategy};
120
121 fn make_field(
122 tree: &mut FormTree,
123 name: &str,
124 value: &str,
125 calculate: Option<&str>,
126 validate: Option<&str>,
127 ) -> FormNodeId {
128 tree.add_node(FormNode {
129 name: name.to_string(),
130 node_type: FormNodeType::Field {
131 value: value.to_string(),
132 },
133 box_model: BoxModel::default(),
134 layout: LayoutStrategy::Positioned,
135 children: vec![],
136 occur: Occur::once(),
137 font: FontMetrics::default(),
138 calculate: calculate.map(|s| s.to_string()),
139 validate: validate.map(|s| s.to_string()),
140 column_widths: vec![],
141 col_span: 1,
142 })
143 }
144
145 fn make_subform(
146 tree: &mut FormTree,
147 name: &str,
148 children: Vec<FormNodeId>,
149 occur: Occur,
150 ) -> FormNodeId {
151 tree.add_node(FormNode {
152 name: name.to_string(),
153 node_type: FormNodeType::Subform,
154 box_model: BoxModel::default(),
155 layout: LayoutStrategy::TopToBottom,
156 children,
157 occur,
158 font: FontMetrics::default(),
159 calculate: None,
160 validate: None,
161 column_widths: vec![],
162 col_span: 1,
163 })
164 }
165
166 fn make_root(tree: &mut FormTree, children: Vec<FormNodeId>) -> FormNodeId {
167 tree.add_node(FormNode {
168 name: "Root".to_string(),
169 node_type: FormNodeType::Root,
170 box_model: BoxModel::default(),
171 layout: LayoutStrategy::TopToBottom,
172 children,
173 occur: Occur::once(),
174 font: FontMetrics::default(),
175 calculate: None,
176 validate: None,
177 column_widths: vec![],
178 col_span: 1,
179 })
180 }
181
182 #[test]
183 fn schema_captures_field_types() {
184 let mut tree = FormTree::new();
185 let name = make_field(&mut tree, "Name", "Acme", None, None);
186 let amount = make_field(&mut tree, "Amount", "42.50", None, None);
187 let active = make_field(&mut tree, "Active", "true", None, None);
188
189 let form = make_subform(
190 &mut tree,
191 "form1",
192 vec![name, amount, active],
193 Occur::once(),
194 );
195 let root = make_root(&mut tree, vec![form]);
196
197 let schema = export_schema(&tree, root);
198
199 assert_eq!(
200 schema.fields.get("form1.Name").unwrap().field_type,
201 FieldType::Text
202 );
203 assert_eq!(
204 schema.fields.get("form1.Amount").unwrap().field_type,
205 FieldType::Numeric
206 );
207 assert_eq!(
208 schema.fields.get("form1.Active").unwrap().field_type,
209 FieldType::Boolean
210 );
211 }
212
213 #[test]
214 fn schema_includes_scripts() {
215 let mut tree = FormTree::new();
216 let tax = make_field(
217 &mut tree,
218 "Tax",
219 "0",
220 Some("Subtotal * 0.21"),
221 Some("Tax >= 0"),
222 );
223 let form = make_subform(&mut tree, "form1", vec![tax], Occur::once());
224 let root = make_root(&mut tree, vec![form]);
225
226 let schema = export_schema(&tree, root);
227 let tax_schema = schema.fields.get("form1.Tax").unwrap();
228
229 assert_eq!(tax_schema.calculate, Some("Subtotal * 0.21".to_string()));
230 assert_eq!(tax_schema.validate, Some("Tax >= 0".to_string()));
231 }
232
233 #[test]
234 fn schema_marks_repeatable_fields() {
235 let mut tree = FormTree::new();
236 let desc = make_field(&mut tree, "Description", "Item", None, None);
237 let item = make_subform(&mut tree, "Item", vec![desc], Occur::repeating(0, None, 1));
238 let form = make_subform(&mut tree, "form1", vec![item], Occur::once());
239 let root = make_root(&mut tree, vec![form]);
240
241 let schema = export_schema(&tree, root);
242 let desc_schema = schema.fields.get("form1.Item.Description").unwrap();
243
244 assert!(desc_schema.repeatable);
245 }
246
247 #[test]
248 fn schema_required_field() {
249 let mut tree = FormTree::new();
250 let req = make_field(&mut tree, "Required", "x", None, None);
251 let form = make_subform(&mut tree, "form1", vec![req], Occur::once());
252 let root = make_root(&mut tree, vec![form]);
253
254 let schema = export_schema(&tree, root);
255 assert!(schema.fields.get("form1.Required").unwrap().required);
256 }
257
258 #[test]
259 fn infer_field_type_works() {
260 assert_eq!(infer_field_type("hello"), FieldType::Text);
261 assert_eq!(infer_field_type("42"), FieldType::Numeric);
262 assert_eq!(infer_field_type("3.14"), FieldType::Numeric);
263 assert_eq!(infer_field_type("true"), FieldType::Boolean);
264 assert_eq!(infer_field_type("0"), FieldType::Boolean);
265 assert_eq!(infer_field_type(""), FieldType::Text);
266 }
267}