mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
M6: doc comments on the functions the formattedValue change touches
This commit is contained in:
@@ -4279,6 +4279,8 @@ fn xfa_choice_lists_signature_and_picture_fields_open_fill_script_and_save() {
|
||||
assert!(taken[0].contains("langs") && !taken[0].contains("country"), "{}", taken[0]);
|
||||
}
|
||||
|
||||
/// A form's data exports, imports and takes pictures through the session, and what it holds
|
||||
/// comes back the same.
|
||||
#[test]
|
||||
fn xfa_data_exports_imports_and_pictures_round_trip_through_the_session() {
|
||||
use pdfcraft_xfa::som_to_path;
|
||||
@@ -4360,6 +4362,8 @@ fn xfa_data_exports_imports_and_pictures_round_trip_through_the_session() {
|
||||
assert_eq!(d2.page_images(0).len(), 2);
|
||||
}
|
||||
|
||||
/// Importing data into a static form replaces every value, emptying the fields the file
|
||||
/// doesn't name, and pictures chosen for it reach its data.
|
||||
#[test]
|
||||
fn xfa_data_import_on_a_static_form_replaces_every_value_and_pictures_reach_its_data() {
|
||||
let original = Arc::new(pdfcraft_xfa::fixtures::static_shell("<form1><page1><name>Ada</name><agree>1</agree></page1></form1>"));
|
||||
@@ -4380,6 +4384,8 @@ fn xfa_data_import_on_a_static_form_replaces_every_value_and_pictures_reach_its_
|
||||
assert!(xml.contains("<name>Bob</name>") && !xml.contains("<agree>1</agree>"), "{xml}");
|
||||
}
|
||||
|
||||
/// Importing data into a dynamic form adds and removes the rows of its repeating subform to
|
||||
/// match, and the calculations over them run again.
|
||||
#[test]
|
||||
fn xfa_data_import_adds_and_removes_rows_of_a_dynamic_form() {
|
||||
let bytes = Arc::new(pdfcraft_xfa::fixtures::shell(&pdfcraft_xfa::fixtures::scripted_template()));
|
||||
@@ -4412,6 +4418,8 @@ fn xfa_data_import_adds_and_removes_rows_of_a_dynamic_form() {
|
||||
assert!(String::from_utf8_lossy(&out).contains("<item>only</item>"));
|
||||
}
|
||||
|
||||
/// Hundreds of calculations, in JavaScript and FormCalc, run on opening in one engine within
|
||||
/// the opening's budget, and a change recalculates them within the event's.
|
||||
#[test]
|
||||
fn hundreds_of_xfa_calculations_run_in_one_engine_within_the_open_budget() {
|
||||
let (sections, rows) = (10, 20);
|
||||
@@ -4442,6 +4450,9 @@ fn hundreds_of_xfa_calculations_run_in_one_engine_within_the_open_budget() {
|
||||
eprintln!("recalculated after a change in {:?}", started.elapsed());
|
||||
}
|
||||
|
||||
/// The scripts of one event share the form as the engine holds it: a failed script's
|
||||
/// half-done writes reach nobody, and a script reads the saved value a choice list was given
|
||||
/// by its shown text.
|
||||
#[test]
|
||||
fn xfa_scripts_of_one_event_see_the_form_as_the_engine_holds_it() {
|
||||
// In document order: `a` holds 1; `b`'s calculate writes `a` then fails; `c`'s calculate
|
||||
@@ -4483,6 +4494,9 @@ fn xfa_date_arithmetic_reads_the_date_as_its_display_picture_shows_it() {
|
||||
assert_eq!(value(&s, "to"), ["20261022"], "{:?}", out.errors);
|
||||
}
|
||||
|
||||
/// A scripted form end to end: initialize and calculate on open, recalculation after a change,
|
||||
/// a failing validation's message, buttons that add and remove rows and show messages, a check
|
||||
/// box that shows a subform (undoable), FormCalc, and a save and reopen that keep it all.
|
||||
#[test]
|
||||
fn xfa_scripts_initialize_calculate_validate_toggle_and_add_rows() {
|
||||
let bytes = Arc::new(pdfcraft_xfa::fixtures::shell(&pdfcraft_xfa::fixtures::scripted_template()));
|
||||
|
||||
@@ -129,6 +129,7 @@ pub(crate) struct XfaRun<'a> {
|
||||
pub ran_away: bool,
|
||||
}
|
||||
|
||||
/// The live form node `n`, with its descendants, as the script engine's snapshot of it.
|
||||
fn to_js(n: &FormNode) -> XfaNode {
|
||||
XfaNode {
|
||||
name: n.name.clone(),
|
||||
|
||||
@@ -1097,6 +1097,8 @@ impl Interp<'_> {
|
||||
self.charge(1)
|
||||
}
|
||||
|
||||
/// Property `name` of form object `i` as FormCalc reads it: its value, attributes,
|
||||
/// relatives and children by name.
|
||||
fn node_member(&mut self, i: usize, name: &str) -> R<Val> {
|
||||
let Some(n) = self.node(i) else { return Ok(Val::Null) };
|
||||
Ok(match name {
|
||||
|
||||
@@ -380,12 +380,15 @@ mod xfa_model {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the JavaScript `script` for `activity` on the test form, from the node at `target`.
|
||||
fn go(script: &str, target: &str, activity: &str) -> XfaOutcome {
|
||||
let ev = XfaEvent { activity: activity.into(), target: target.into(), ..Default::default() };
|
||||
let doc = XfaDoc { file_name: "f.pdf".into(), page: 1, page_count: 3 };
|
||||
run_xfa(script, &ev, &doc, &form(), Limits::default())
|
||||
}
|
||||
|
||||
/// A calculate script finds its siblings by name and yields its last expression, and a
|
||||
/// value it sets comes back as an effect.
|
||||
#[test]
|
||||
fn calculate_scripts_see_siblings_by_name_and_yield_their_last_expression() {
|
||||
let o = go("qty.rawValue * price.rawValue", "form1[0].page1[0].total[0]", "calculate");
|
||||
@@ -427,6 +430,7 @@ mod xfa_model {
|
||||
assert_eq!(o.result.as_deref(), Some("2026-10-21"));
|
||||
}
|
||||
|
||||
/// Long values a script sets, and long results, are cut where they are stored, with a note.
|
||||
#[test]
|
||||
fn long_values_and_results_are_cut_where_they_are_stored() {
|
||||
// The value kept for later reads is the one the effect saves.
|
||||
@@ -654,6 +658,7 @@ mod formcalc {
|
||||
assert_eq!(o.result.as_deref(), Some("2026-10-21"));
|
||||
}
|
||||
|
||||
/// FormCalc arithmetic, string conversion and comparisons follow FormCalc's own rules.
|
||||
#[test]
|
||||
fn arithmetic_strings_and_comparisons_follow_formcalc_rules() {
|
||||
assert_eq!(calc("1 + 2 * 3"), "7");
|
||||
|
||||
@@ -363,10 +363,13 @@ impl XHost {
|
||||
|
||||
type Shared = RefCell<XHost>;
|
||||
|
||||
/// The form host of the script running in `ctx`.
|
||||
fn host(ctx: &Context) -> JsResult<&Shared> {
|
||||
ctx.get_data::<Shared>().ok_or_else(|| error("the XFA script host is not set up"))
|
||||
}
|
||||
|
||||
/// The snapshot `n` and its descendants flattened into `out` under `parent` (to [`MAX_DEPTH`]
|
||||
/// and [`MAX_NODES`]): the index of `n`'s node, `None` past the limits.
|
||||
fn flatten(n: &XfaNode, parent: Option<usize>, out: &mut Vec<HNode>, depth: usize) -> Option<usize> {
|
||||
if depth > MAX_DEPTH || out.len() >= MAX_NODES {
|
||||
return None;
|
||||
@@ -826,6 +829,8 @@ pub(crate) fn resolve_som(h: &XHost, from: usize, expr: &str) -> Vec<usize> {
|
||||
cur
|
||||
}
|
||||
|
||||
/// The live descendants of node `from` named `name` (`from..name` in a SOM path), in document
|
||||
/// order, to [`MAX_DEPTH`].
|
||||
pub(crate) fn descendants_named(h: &XHost, from: usize, name: &str, depth: usize) -> Vec<usize> {
|
||||
let mut out = Vec::new();
|
||||
if depth > MAX_DEPTH {
|
||||
@@ -846,6 +851,8 @@ pub(crate) fn descendants_named(h: &XHost, from: usize, name: &str, depth: usize
|
||||
out
|
||||
}
|
||||
|
||||
/// A property read on a form node's Proxy: its value (and `formattedValue` as its picture shows
|
||||
/// it), attributes, relatives, methods, instance managers and children by name.
|
||||
fn node_get(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
|
||||
let idx = node_index(&arg(args, 0), ctx)?;
|
||||
let key = arg(args, 1);
|
||||
|
||||
@@ -419,6 +419,8 @@ pub fn formatted_value(picture: &str, raw: &str) -> Option<String> {
|
||||
(shown != raw).then_some(shown)
|
||||
}
|
||||
|
||||
/// The native PDF action a button's click script amounts to (a reset, print, save or link),
|
||||
/// else the script itself to run; `None` without a click script.
|
||||
fn button_action(scripts: &[Script]) -> Option<Action> {
|
||||
let s = scripts.iter().find(|s| s.activity == "click")?;
|
||||
let t = &s.text;
|
||||
|
||||
@@ -407,6 +407,8 @@ fn parse_items(n: XmlNode, warnings: &mut Vec<String>) -> (Vec<String>, Vec<Stri
|
||||
(shown, saved)
|
||||
}
|
||||
|
||||
/// A `<field>` element as the template model: its widget, caption, value, items, pictures,
|
||||
/// access and scripts.
|
||||
fn parse_field(n: XmlNode, warnings: &mut Vec<String>) -> Field {
|
||||
let ui = child(n, "ui");
|
||||
let ui_el = ui.and_then(|u| u.children().find(|c| c.is_element() && c.tag_name().name() != "extras" && c.tag_name().name() != "picture"));
|
||||
|
||||
@@ -188,6 +188,8 @@ impl Walker<'_> {
|
||||
true
|
||||
}
|
||||
|
||||
/// Note the scripted events of the object at `som`: its event scripts, its calculation and
|
||||
/// its validation (with the message a failing one shows).
|
||||
fn push_events(&mut self, som: &str, scripts: &[Script], calculate: Option<&Script>, validate: Option<&Script>, message: Option<&str>) {
|
||||
if self.events.len() >= MAX_EVENTS {
|
||||
self.truncated = true;
|
||||
@@ -229,14 +231,18 @@ impl Walker<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The presence of the object at `som`: a script's override, else the template's `p`.
|
||||
fn presence(&self, som: &str, p: Presence) -> String {
|
||||
self.ov.presence.get(som).cloned().unwrap_or_else(|| presence_name(p).to_string())
|
||||
}
|
||||
|
||||
/// The access of the object at `som`: a script's override, else the template's `a`.
|
||||
fn access(&self, som: &str, a: Access) -> String {
|
||||
self.ov.access.get(som).cloned().unwrap_or_else(|| access_name(a).to_string())
|
||||
}
|
||||
|
||||
/// The live node of field `f`, instance `index` under `parent_som`, valued from the data or
|
||||
/// its template default (`radio`: the value its exclusion group chose), with its events noted.
|
||||
fn field(&mut self, f: &Field, parent_som: &str, index: usize, radio: Option<&str>) -> FormNode {
|
||||
let som = child_som(parent_som, f.common.name.as_deref().or(Some("field")), index);
|
||||
let data_value = self.data.and_then(|d| d.value_at(&data_path(self.tpl, &som))).map(|t| t.trim().to_string()).filter(|t| !t.is_empty());
|
||||
@@ -277,6 +283,8 @@ impl Walker<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The live nodes of template `nodes` under `parent_som`: as many instances of a subform as
|
||||
/// the data or its occurrence asks for, areas, fields, exclusion groups and draws.
|
||||
fn children(&mut self, nodes: &[Node], parent_som: &str, depth: usize) -> Vec<FormNode> {
|
||||
let mut out = Vec::new();
|
||||
if depth > MAX_DEPTH {
|
||||
|
||||
@@ -310,6 +310,7 @@ fn jpeg_headers_give_the_size() {
|
||||
|
||||
const DATA: &str = "<form><head><familyName>Singh</familyName><agree>1</agree><born>2001-02-03</born><answer>N</answer></head><table><row><from0>2019</from0><what0>Studied</what0><where0>Delhi</where0></row><row><from0>2021</from0><what0>Worked</what0><where0>Toronto</where0></row><row><from0>2023</from0><what0>Moved</what0><where0>Ottawa</where0></row></table></form>";
|
||||
|
||||
/// The datasets packet parses into a tree whose nodes the fields' data paths find.
|
||||
#[test]
|
||||
fn datasets_parse_into_a_tree_and_som_paths_find_their_nodes() {
|
||||
let d = parse_datasets(&template_with_data(1, DATA)).expect("datasets");
|
||||
@@ -323,6 +324,8 @@ fn datasets_parse_into_a_tree_and_som_paths_find_their_nodes() {
|
||||
assert!(parse_datasets("<xdp:xdp xmlns:xdp=\"x\"/>").is_none());
|
||||
}
|
||||
|
||||
/// ISO dates convert to and from the date patterns fields show; text that isn't a date is left
|
||||
/// alone or refused.
|
||||
#[test]
|
||||
fn dates_convert_between_iso_and_acrobat_patterns() {
|
||||
assert_eq!(iso_to_pattern("2001-02-03", "yyyy-mm-dd"), "2001-02-03");
|
||||
@@ -362,6 +365,7 @@ fn display_pictures_are_parsed_apart_from_edit_pictures() {
|
||||
assert_eq!(days.display_picture, None);
|
||||
}
|
||||
|
||||
/// The datasets fill the fields they bind to and add the rows the data holds.
|
||||
#[test]
|
||||
fn data_fills_the_fields_and_adds_rows() {
|
||||
let form = layout_xml(&template_with_data(1, DATA)).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user