1use crate::tree::{FieldTree, FieldType, FieldValue};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum FormKind {
18 AcroForm,
20 Xfa,
22 None,
24}
25
26#[derive(Debug, thiserror::Error)]
28pub enum FormError {
29 #[error("field not found: {0}")]
31 FieldNotFound(String),
32 #[error("read-only field: {0}")]
34 ReadOnly(String),
35 #[error("invalid value for field type")]
37 InvalidValue,
38}
39
40pub trait FormAccess {
45 fn form_type(&self) -> FormKind;
47
48 fn field_names(&self) -> Vec<String>;
50
51 fn get_value(&self, path: &str) -> Option<String>;
53
54 fn set_value(&mut self, path: &str, value: &str) -> Result<(), FormError>;
56}
57
58pub trait DocumentOps {
63 fn page_count(&self) -> usize;
65
66 fn form(&self) -> Option<&dyn FormAccess>;
68
69 fn form_mut(&mut self) -> Option<&mut dyn FormAccess>;
71}
72
73impl FormAccess for FieldTree {
74 fn form_type(&self) -> FormKind {
75 FormKind::AcroForm
76 }
77
78 fn field_names(&self) -> Vec<String> {
79 self.terminal_fields()
80 .into_iter()
81 .map(|id| self.fully_qualified_name(id))
82 .collect()
83 }
84
85 fn get_value(&self, path: &str) -> Option<String> {
86 let id = self.find_by_name(path)?;
87 let value = self.effective_value(id)?;
88 match value {
89 FieldValue::Text(s) => Some(s.clone()),
90 FieldValue::StringArray(arr) => Some(arr.join(", ")),
91 }
92 }
93
94 fn set_value(&mut self, path: &str, value: &str) -> Result<(), FormError> {
95 let id = self
96 .find_by_name(path)
97 .ok_or_else(|| FormError::FieldNotFound(path.to_string()))?;
98
99 let ft = self
100 .effective_field_type(id)
101 .ok_or(FormError::InvalidValue)?;
102
103 if ft == FieldType::Signature || self.effective_flags(id).read_only() {
105 return Err(FormError::ReadOnly(path.to_string()));
106 }
107
108 match ft {
109 FieldType::Text | FieldType::Button => {
110 self.get_mut(id).value = Some(FieldValue::Text(value.to_string()));
111 }
112 FieldType::Choice => {
113 self.get_mut(id).value = Some(FieldValue::StringArray(vec![value.to_string()]));
114 }
115 FieldType::Signature => unreachable!(),
116 }
117 Ok(())
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::flags::FieldFlags;
125 use crate::tree::{FieldNode, FieldTree, FieldType, FieldValue};
126
127 fn make_node(name: &str) -> FieldNode {
128 FieldNode {
129 partial_name: name.into(),
130 alternate_name: None,
131 mapping_name: None,
132 field_type: None,
133 flags: FieldFlags::empty(),
134 value: None,
135 default_value: None,
136 default_appearance: None,
137 quadding: None,
138 max_len: None,
139 options: vec![],
140 top_index: None,
141 rect: None,
142 appearance_state: None,
143 on_state: None,
144 page_index: None,
145 parent: None,
146 children: vec![],
147 object_id: None,
148 has_actions: false,
149 mk: None,
150 border_style: None,
151 }
152 }
153
154 fn sample_tree() -> FieldTree {
155 let mut tree = FieldTree::new();
156
157 let form_id = tree.alloc(make_node("form"));
159
160 let mut name_node = make_node("name");
162 name_node.field_type = Some(FieldType::Text);
163 name_node.value = Some(FieldValue::Text("Alice".to_string()));
164 name_node.parent = Some(form_id);
165 let name_id = tree.alloc(name_node);
166
167 let mut agree_node = make_node("agree");
169 agree_node.field_type = Some(FieldType::Button);
170 agree_node.value = Some(FieldValue::Text("true".to_string()));
171 agree_node.parent = Some(form_id);
172 let agree_id = tree.alloc(agree_node);
173
174 let mut country_node = make_node("country");
176 country_node.field_type = Some(FieldType::Choice);
177 country_node.value = Some(FieldValue::StringArray(vec!["NL".to_string()]));
178 country_node.parent = Some(form_id);
179 let country_id = tree.alloc(country_node);
180
181 let mut sig_node = make_node("sig");
183 sig_node.field_type = Some(FieldType::Signature);
184 sig_node.parent = Some(form_id);
185 let sig_id = tree.alloc(sig_node);
186
187 let mut empty_node = make_node("empty");
189 empty_node.field_type = Some(FieldType::Text);
190 empty_node.parent = Some(form_id);
191 let empty_id = tree.alloc(empty_node);
192
193 let mut locked_node = make_node("locked");
195 locked_node.field_type = Some(FieldType::Text);
196 locked_node.flags = FieldFlags::from_bits(1); locked_node.value = Some(FieldValue::Text("frozen".to_string()));
198 locked_node.parent = Some(form_id);
199 let locked_id = tree.alloc(locked_node);
200
201 let form = tree.get_mut(form_id);
203 form.children = vec![name_id, agree_id, country_id, sig_id, empty_id, locked_id];
204
205 tree
206 }
207
208 #[test]
209 fn field_names_returns_all() {
210 let tree = sample_tree();
211 let names = tree.field_names();
212 assert_eq!(names.len(), 6);
213 assert!(names.contains(&"form.name".to_string()));
214 assert!(names.contains(&"form.agree".to_string()));
215 }
216
217 #[test]
218 fn get_value_existing_text() {
219 let tree = sample_tree();
220 assert_eq!(tree.get_value("form.name"), Some("Alice".to_string()));
221 }
222
223 #[test]
224 fn get_value_returns_none_for_unknown() {
225 let tree = sample_tree();
226 assert_eq!(tree.get_value("nonexistent"), None);
227 }
228
229 #[test]
230 fn get_value_returns_none_for_empty() {
231 let tree = sample_tree();
232 assert_eq!(tree.get_value("form.empty"), None);
233 }
234
235 #[test]
236 fn set_value_updates_text() {
237 let mut tree = sample_tree();
238 tree.set_value("form.name", "Bob").unwrap();
239 assert_eq!(tree.get_value("form.name"), Some("Bob".to_string()));
240 }
241
242 #[test]
243 fn set_value_unknown_field_errors() {
244 let mut tree = sample_tree();
245 let err = tree.set_value("nonexistent", "x").unwrap_err();
246 assert!(matches!(err, FormError::FieldNotFound(_)));
247 }
248
249 #[test]
250 fn set_value_signature_errors() {
251 let mut tree = sample_tree();
252 let err = tree.set_value("form.sig", "x").unwrap_err();
253 assert!(matches!(err, FormError::ReadOnly(_)));
254 }
255
256 #[test]
257 fn set_value_readonly_field_errors() {
258 let mut tree = sample_tree();
259 let err = tree.set_value("form.locked", "new").unwrap_err();
260 assert!(matches!(err, FormError::ReadOnly(_)));
261 assert_eq!(tree.get_value("form.locked"), Some("frozen".to_string()));
263 }
264
265 #[test]
266 fn form_type_is_acroform() {
267 let tree = sample_tree();
268 assert_eq!(tree.form_type(), FormKind::AcroForm);
269 }
270
271 #[test]
272 fn object_safe() {
273 let tree = sample_tree();
274 let _dyn_ref: &dyn FormAccess = &tree;
275 assert_eq!(_dyn_ref.form_type(), FormKind::AcroForm);
276 }
277}