M12: a date is tried in the field's first pattern again only when its format refused it

This commit is contained in:
Eric Kaden
2026-10-22 22:18:11 -04:00
parent 91df3c01a8
commit dd6d7feadb
4 changed files with 32 additions and 7 deletions
+8 -5
View File
@@ -2534,13 +2534,16 @@ fn run_edit(doc: &mut pdfcraft_cos::Document, edit: &Edit, cx: &mut EditCtx) ->
Some(js) => pdfcraft_forms::set_value_with(doc, name, value, js),
None => pdfcraft_forms::set_value(doc, name, value),
};
if let Err(refused) = set(doc, value) {
// A date the field's pattern doesn't read may be typed by another of an XFA date
// field's patterns: it is kept in the first.
match xfa::typed_date(doc, cx.xfa.as_deref(), name, value) {
match set(doc, value) {
Ok(()) => {}
// A date the field's format doesn't read may be typed by another of an XFA date
// field's patterns: it is kept in the first. Only a format refusal is tried
// again: it comes before any of the field's scripts run, so they run once.
Err(refused @ pdfcraft_forms::FormError::Format(_)) => match xfa::typed_date(doc, cx.xfa.as_deref(), name, value) {
Some(typed) => set(doc, &typed)?,
None => return Err(refused.into()),
}
},
Err(refused) => return Err(refused.into()),
}
}
Edit::ApplyScriptChanges { changes } => match cx.js.as_mut() {
+18
View File
@@ -7305,3 +7305,21 @@ fn a_date_typed_by_another_of_an_xfa_fields_edit_patterns_is_kept_in_its_first()
let data = pdfcraft_xfa::parse_datasets(&pdfcraft_xfa::read_packets(&cos).unwrap().unwrap().xdp).expect("a datasets packet");
assert_eq!(data.text_at(&pdfcraft_xfa::som_to_path("form[0].leave[0]")), Some("2026-11-08"));
}
/// A date the field's own scripts refuse is not tried again in another of an XFA date field's
/// patterns: only a date its format refuses is, which happens before any script runs, so the
/// scripts run once. This validate script counts its runs in `name` and accepts on its second.
#[test]
fn a_date_its_validate_script_refuses_is_not_tried_again() {
let mut s = Session::new();
let id = s
.open("dates.pdf", None, Arc::new(pdfcraft_xfa::fixtures::shell(&pdfcraft_xfa::fixtures::date_alternatives_template())), None)
.expect("opens");
let script = Some("var n = Number(getField('name').value || 0) + 1; getField('name').value = String(n); event.rc = n >= 2;".to_string());
s.apply(id, Edit::SetFieldScript { name: "leave".into(), event: "validate".into(), script }).unwrap();
let value = |s: &Session, n: &str| s.get(id).unwrap().form.iter().find(|f| f.name == n).unwrap().value.clone();
// Its format reads 18OCT26, and so does its DDMMMYY pattern.
let refused = s.apply(id, Edit::SetFieldValue { name: "leave".into(), value: FieldValue::Text("18OCT26".into()) });
assert!(refused.is_err(), "the validate script refused it, once");
assert!(value(&s, "name").is_empty(), "nothing the refused run changed is kept: {:?}", value(&s, "name"));
}
+5 -1
View File
@@ -48,6 +48,10 @@ pub enum FormError {
ReadOnly(String),
#[error("{0}")]
Invalid(String),
/// The value doesn't fit the field's format (its keystroke check refused it), which is
/// checked before any of the field's scripts run.
#[error("{0}")]
Format(String),
#[error("{0}")]
Cos(#[from] pdfcraft_cos::CosError),
}
@@ -1044,7 +1048,7 @@ fn write_value(doc: &mut Document, f: &Field, value: &FieldValue, scripts: &mut
return invalid(format!("{:?} takes at most {max} characters", f.name));
}
// Keystroke (on commit) and Validate, as Acrobat runs them before accepting a value.
let t = &af::keystroke(&f.actions.format, &f.name, t).map_err(FormError::Invalid)?;
let t = &af::keystroke(&f.actions.format, &f.name, t).map_err(FormError::Format)?;
af::validate(&f.actions.validate, t).map_err(FormError::Invalid)?;
let t = &scripting::accept(doc, f, t, scripts)?;
doc.update_dict(f.obj, |d| {
+1 -1
View File
@@ -1270,7 +1270,7 @@ fn formats_validation_and_calculations_run_like_acrobat() {
// Keystroke and validation errors use Acrobat's messages and change nothing.
assert_eq!(
set_value(&mut doc, "Qty", &FieldValue::Text("lots".into())),
Err(FormError::Invalid("The value entered does not match the format of the field [ Qty ]".into()))
Err(FormError::Format("The value entered does not match the format of the field [ Qty ]".into()))
);
assert_eq!(
set_value(&mut doc, "Qty", &FieldValue::Text("120".into())),