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 page_index: None,
138 parent: None,
139 children: vec![],
140 object_id: None,
141 has_actions: false,
142 mk: None,
143 border_style: None,
144 }
145 }
146
147 fn sample_tree() -> FieldTree {
148 let mut tree = FieldTree::new();
149
150 let form_id = tree.alloc(make_node("form"));
152
153 let mut name_node = make_node("name");
155 name_node.field_type = Some(FieldType::Text);
156 name_node.value = Some(FieldValue::Text("Alice".to_string()));
157 name_node.parent = Some(form_id);
158 let name_id = tree.alloc(name_node);
159
160 let mut agree_node = make_node("agree");
162 agree_node.field_type = Some(FieldType::Button);
163 agree_node.value = Some(FieldValue::Text("true".to_string()));
164 agree_node.parent = Some(form_id);
165 let agree_id = tree.alloc(agree_node);
166
167 let mut country_node = make_node("country");
169 country_node.field_type = Some(FieldType::Choice);
170 country_node.value = Some(FieldValue::StringArray(vec!["NL".to_string()]));
171 country_node.parent = Some(form_id);
172 let country_id = tree.alloc(country_node);
173
174 let mut sig_node = make_node("sig");
176 sig_node.field_type = Some(FieldType::Signature);
177 sig_node.parent = Some(form_id);
178 let sig_id = tree.alloc(sig_node);
179
180 let mut empty_node = make_node("empty");
182 empty_node.field_type = Some(FieldType::Text);
183 empty_node.parent = Some(form_id);
184 let empty_id = tree.alloc(empty_node);
185
186 let mut locked_node = make_node("locked");
188 locked_node.field_type = Some(FieldType::Text);
189 locked_node.flags = FieldFlags::from_bits(1); locked_node.value = Some(FieldValue::Text("frozen".to_string()));
191 locked_node.parent = Some(form_id);
192 let locked_id = tree.alloc(locked_node);
193
194 let form = tree.get_mut(form_id);
196 form.children = vec![name_id, agree_id, country_id, sig_id, empty_id, locked_id];
197
198 tree
199 }
200
201 #[test]
202 fn field_names_returns_all() {
203 let tree = sample_tree();
204 let names = tree.field_names();
205 assert_eq!(names.len(), 6);
206 assert!(names.contains(&"form.name".to_string()));
207 assert!(names.contains(&"form.agree".to_string()));
208 }
209
210 #[test]
211 fn get_value_existing_text() {
212 let tree = sample_tree();
213 assert_eq!(tree.get_value("form.name"), Some("Alice".to_string()));
214 }
215
216 #[test]
217 fn get_value_returns_none_for_unknown() {
218 let tree = sample_tree();
219 assert_eq!(tree.get_value("nonexistent"), None);
220 }
221
222 #[test]
223 fn get_value_returns_none_for_empty() {
224 let tree = sample_tree();
225 assert_eq!(tree.get_value("form.empty"), None);
226 }
227
228 #[test]
229 fn set_value_updates_text() {
230 let mut tree = sample_tree();
231 tree.set_value("form.name", "Bob").unwrap();
232 assert_eq!(tree.get_value("form.name"), Some("Bob".to_string()));
233 }
234
235 #[test]
236 fn set_value_unknown_field_errors() {
237 let mut tree = sample_tree();
238 let err = tree.set_value("nonexistent", "x").unwrap_err();
239 assert!(matches!(err, FormError::FieldNotFound(_)));
240 }
241
242 #[test]
243 fn set_value_signature_errors() {
244 let mut tree = sample_tree();
245 let err = tree.set_value("form.sig", "x").unwrap_err();
246 assert!(matches!(err, FormError::ReadOnly(_)));
247 }
248
249 #[test]
250 fn set_value_readonly_field_errors() {
251 let mut tree = sample_tree();
252 let err = tree.set_value("form.locked", "new").unwrap_err();
253 assert!(matches!(err, FormError::ReadOnly(_)));
254 assert_eq!(tree.get_value("form.locked"), Some("frozen".to_string()));
256 }
257
258 #[test]
259 fn form_type_is_acroform() {
260 let tree = sample_tree();
261 assert_eq!(tree.form_type(), FormKind::AcroForm);
262 }
263
264 #[test]
265 fn object_safe() {
266 let tree = sample_tree();
267 let _dyn_ref: &dyn FormAccess = &tree;
268 assert_eq!(_dyn_ref.form_type(), FormKind::AcroForm);
269 }
270}