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