use std::collections::HashMap;
use xfa_dom_resolver::data_dom::{DataDom, DataNodeId};
use xfa_dom_resolver::som::{
parse_som, resolve_data_path, SomExpression, SomIndex, SomRoot, SomSelector,
};
use xfa_layout_engine::form::{FormNodeId, FormNodeType, FormTree, GroupKind};
use super::RuntimeMetadata;
pub const MAX_MUTATIONS_PER_DOC: usize = 4096;
pub const MAX_INSTANCES_PER_SUBFORM: u32 = 256;
pub const MAX_ITEMS_PER_LISTBOX: u32 = 4096;
pub const MAX_RESOLVE_CALLS_PER_SCRIPT: u32 = 1024;
pub const MAX_RESOLVE_RESULTS: usize = 256;
pub const MAX_SOM_DEPTH: usize = 16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MutationLogEntry {
pub node_id: FormNodeId,
pub script_idx: usize,
pub before: String,
pub after: String,
}
#[derive(Debug)]
pub struct HostBindings {
form: *mut FormTree,
root_id: FormNodeId,
current_id: Option<FormNodeId>,
current_activity: Option<String>,
current_script_idx: usize,
next_script_idx: usize,
generation: u64,
mutation_log: Vec<MutationLogEntry>,
mutation_count_this_doc: usize,
resolve_count_this_script: u32,
metadata: RuntimeMetadata,
static_page_count: u32,
zero_instance_runs: HashMap<(FormNodeId, String), u64>,
data_dom: Option<*const DataDom>,
}
impl Default for HostBindings {
fn default() -> Self {
Self {
form: std::ptr::null_mut(),
root_id: FormNodeId(0),
current_id: None,
current_activity: None,
current_script_idx: 0,
next_script_idx: 0,
generation: 0,
mutation_log: Vec::new(),
mutation_count_this_doc: 0,
resolve_count_this_script: 0,
metadata: RuntimeMetadata::default(),
static_page_count: 0,
zero_instance_runs: HashMap::new(),
data_dom: None,
}
}
}
impl HostBindings {
pub fn new() -> Self {
Self::default()
}
pub fn set_form_handle(&mut self, form: *mut FormTree, root_id: FormNodeId) {
self.form = form;
self.root_id = root_id;
if form.is_null() {
self.current_id = None;
self.current_activity = None;
self.current_script_idx = 0;
self.zero_instance_runs.clear();
}
}
pub fn reset_per_document(&mut self) {
self.form = std::ptr::null_mut();
self.root_id = FormNodeId(0);
self.current_id = None;
self.current_activity = None;
self.current_script_idx = 0;
self.next_script_idx = 0;
self.generation = self.generation.wrapping_add(1);
self.mutation_log.clear();
self.mutation_count_this_doc = 0;
self.resolve_count_this_script = 0;
self.metadata = RuntimeMetadata::default();
self.static_page_count = 0;
self.zero_instance_runs.clear();
}
pub fn set_data_handle(&mut self, dom: *const DataDom) {
self.data_dom = Some(dom);
}
pub fn reset_per_script(&mut self, current_id: FormNodeId, activity: Option<&str>) {
self.current_id = Some(current_id);
self.current_activity = activity.map(str::to_string);
self.current_script_idx = self.next_script_idx;
self.next_script_idx = self.next_script_idx.saturating_add(1);
self.resolve_count_this_script = 0;
}
pub fn set_static_page_count(&mut self, page_count: u32) {
self.static_page_count = page_count;
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn current_node(&self) -> Option<FormNodeId> {
self.current_id
}
pub fn root_node(&mut self, generation: u64) -> Option<FormNodeId> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if generation != self.generation {
return None;
}
let Some(form) = self.form_ref() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
};
(self.root_id.0 < form.nodes.len()).then_some(self.root_id)
}
pub fn subform_scope_chain(&mut self, node_id: FormNodeId, generation: u64) -> Vec<String> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.handle_is_live(node_id, generation) {
return vec![];
}
let Some(form) = self.form_ref() else {
return vec![];
};
let root = self.root_id;
let mut names: Vec<String> = vec![];
let mut stack: Vec<(FormNodeId, usize, bool)> = vec![(root, 0, false)];
while let Some(&(current, child_idx, _pushed)) = stack.last() {
if current == node_id {
return names;
}
let children_len = form.get(current).children.len();
if child_idx >= children_len {
let (_, _, pushed) = stack.pop().unwrap();
if pushed {
names.pop();
}
continue;
}
let child_id = form.get(current).children[child_idx];
stack.last_mut().unwrap().1 += 1;
let child = form.get(child_id);
let is_scope = matches!(
child.node_type,
FormNodeType::Subform | FormNodeType::Area | FormNodeType::ExclGroup
);
let pushed = is_scope && !child.name.is_empty();
if pushed {
names.push(child.name.clone());
}
stack.push((child_id, 0, pushed));
}
vec![]
}
pub fn take_metadata(&mut self) -> RuntimeMetadata {
std::mem::take(&mut self.metadata)
}
pub fn mutation_log(&self) -> &[MutationLogEntry] {
&self.mutation_log
}
pub fn get_raw_value(&mut self, node_id: FormNodeId, generation: u64) -> Option<String> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.handle_is_live(node_id, generation) {
return None;
}
let Some(form) = self.form_ref() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
};
match &form.get(node_id).node_type {
FormNodeType::Field { value } => Some(value.clone()),
_ => None,
}
}
pub fn node_name(&self, node_id: FormNodeId, generation: u64) -> Option<String> {
if !self.handle_is_live(node_id, generation) {
return None;
}
let form = self.form_ref()?;
Some(form.get(node_id).name.clone())
}
pub fn get_occur_property(
&mut self,
node_id: FormNodeId,
generation: u64,
property: &str,
) -> Option<i32> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.handle_is_live(node_id, generation) {
return None;
}
let Some(form) = self.form_ref() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
};
let occur = &form.get(node_id).occur;
match property {
"min" => Some(clamp_occur_to_i32(occur.min)),
"max" => Some(occur.max.map(clamp_occur_to_i32).unwrap_or(-1)),
"initial" => Some(clamp_occur_to_i32(occur.initial)),
_ => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
None
}
}
}
pub fn set_occur_property(
&mut self,
node_id: FormNodeId,
generation: u64,
property: &str,
value: &str,
) -> bool {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.write_activity_allowed()
|| !self.handle_is_live(node_id, generation)
|| self.mutation_count_this_doc >= MAX_MUTATIONS_PER_DOC
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
}
enum OccurValue {
Nonnegative(u32),
Max(Option<u32>),
}
let parsed = match property {
"min" | "initial" => parse_nonnegative_occur_value(value).map(OccurValue::Nonnegative),
"max" => parse_max_occur_value(value).map(OccurValue::Max),
_ => None,
};
let Some(parsed) = parsed else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
};
let Some(form) = self.form_mut() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
};
let Some(node) = form.nodes.get_mut(node_id.0) else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
};
match (property, parsed) {
("min", OccurValue::Nonnegative(n)) => node.occur.min = n,
("initial", OccurValue::Nonnegative(n)) => node.occur.initial = n,
("max", OccurValue::Max(n)) => node.occur.max = n,
_ => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
}
}
self.mutation_count_this_doc = self.mutation_count_this_doc.saturating_add(1);
true
}
pub fn set_raw_value(&mut self, node_id: FormNodeId, value: String, generation: u64) -> bool {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.write_activity_allowed()
|| !self.handle_is_live(node_id, generation)
|| self.mutation_count_this_doc >= MAX_MUTATIONS_PER_DOC
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
}
let Some((before, after)) = self.write_field_value(node_id, value) else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
};
self.mutation_log.push(MutationLogEntry {
node_id,
script_idx: self.current_script_idx,
before,
after,
});
self.metadata.mutations = self.metadata.mutations.saturating_add(1);
self.mutation_count_this_doc = self.mutation_count_this_doc.saturating_add(1);
true
}
pub fn resolve_node(&mut self, path: &str) -> Option<FormNodeId> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
let nodes = match self.resolve_path(path) {
ResolveOutcome::Ok(nodes) => nodes,
ResolveOutcome::NoMatch => {
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
return None;
}
ResolveOutcome::BindingError => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
}
};
let Some(form) = self.form_ref() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
};
let found = nodes
.into_iter()
.find(|node_id| matches!(form.get(*node_id).node_type, FormNodeType::Field { .. }));
if found.is_none() {
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
}
found
}
pub fn resolve_nodes(&mut self, path: &str) -> Vec<FormNodeId> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
let nodes = match self.resolve_path(path) {
ResolveOutcome::Ok(nodes) => nodes,
ResolveOutcome::NoMatch => return Vec::new(),
ResolveOutcome::BindingError => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
return Vec::new();
}
};
let Some(form) = self.form_ref() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Vec::new();
};
nodes
.into_iter()
.filter(|node_id| matches!(form.get(*node_id).node_type, FormNodeType::Field { .. }))
.take(MAX_RESOLVE_RESULTS)
.collect()
}
pub fn resolve_implicit(&mut self, current_id: FormNodeId, name: &str) -> Option<FormNodeId> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
match self.resolve_implicit_inner(current_id, name) {
ResolveOutcome::Ok(nodes) => nodes.into_iter().next(),
ResolveOutcome::NoMatch => {
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
None
}
ResolveOutcome::BindingError => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
None
}
}
}
pub fn resolve_implicit_candidates(
&mut self,
current_id: FormNodeId,
name: &str,
) -> Vec<FormNodeId> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
match self.resolve_implicit_inner(current_id, name) {
ResolveOutcome::Ok(nodes) => nodes,
ResolveOutcome::NoMatch => {
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
Vec::new()
}
ResolveOutcome::BindingError => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
Vec::new()
}
}
}
pub fn resolve_child(&mut self, parent_id: FormNodeId, name: &str) -> Option<FormNodeId> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
match self.resolve_child_inner(parent_id, name) {
ResolveOutcome::Ok(nodes) => nodes.into_iter().next(),
ResolveOutcome::NoMatch => {
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
None
}
ResolveOutcome::BindingError => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
None
}
}
}
pub fn resolve_child_candidates(
&mut self,
parent_ids: &[FormNodeId],
name: &str,
) -> Vec<FormNodeId> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
match self.resolve_child_candidates_inner(parent_ids, name) {
ResolveOutcome::Ok(nodes) => nodes,
ResolveOutcome::NoMatch => {
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
Vec::new()
}
ResolveOutcome::BindingError => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
Vec::new()
}
}
}
pub fn resolve_scoped_candidates(
&mut self,
scope_ids: &[FormNodeId],
name: &str,
) -> Vec<FormNodeId> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
match self.resolve_scoped_candidates_inner(scope_ids, name) {
ResolveOutcome::Ok(nodes) => nodes,
ResolveOutcome::NoMatch => {
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
Vec::new()
}
ResolveOutcome::BindingError => {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
Vec::new()
}
}
}
pub fn instance_count(&mut self, parent_id: FormNodeId) -> u32 {
self.instance_count_inner(parent_id, None)
}
pub fn instance_count_for_handle(&mut self, parent_id: FormNodeId, generation: u64) -> u32 {
self.instance_count_inner(parent_id, Some(generation))
}
pub fn instance_index(&mut self, node_id: FormNodeId) -> u32 {
self.instance_index_inner(node_id, None)
}
pub fn instance_index_for_handle(&mut self, node_id: FormNodeId, generation: u64) -> u32 {
self.instance_index_inner(node_id, Some(generation))
}
pub fn has_zero_instance_run(
&mut self,
parent_id: FormNodeId,
generation: u64,
name: &str,
) -> bool {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
let name = name.trim();
if name.is_empty() || !self.consume_resolve_call() {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
}
let Some(form) = self.form_ref() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
};
if generation != self.generation || parent_id.0 >= form.nodes.len() {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return false;
}
self.zero_instance_runs
.contains_key(&(parent_id, name.to_string()))
}
#[allow(clippy::result_unit_err)]
pub fn instance_set(&mut self, parent_id: FormNodeId, n: u32) -> Result<u32, ()> {
self.instance_set_inner(parent_id, None, n)
}
#[allow(clippy::result_unit_err)]
pub fn instance_set_for_handle(
&mut self,
parent_id: FormNodeId,
generation: u64,
n: u32,
) -> Result<u32, ()> {
self.instance_set_inner(parent_id, Some(generation), n)
}
#[allow(clippy::result_unit_err)]
pub fn instance_add(&mut self, parent_id: FormNodeId) -> Result<FormNodeId, ()> {
self.instance_add_inner(parent_id, None)
}
#[allow(clippy::result_unit_err)]
pub fn instance_add_for_handle(
&mut self,
parent_id: FormNodeId,
generation: u64,
) -> Result<FormNodeId, ()> {
self.instance_add_inner(parent_id, Some(generation))
}
#[allow(clippy::result_unit_err)]
pub fn instance_remove(&mut self, parent_id: FormNodeId, index: u32) -> Result<(), ()> {
self.instance_remove_inner(parent_id, None, index)
}
#[allow(clippy::result_unit_err)]
pub fn instance_remove_for_handle(
&mut self,
parent_id: FormNodeId,
generation: u64,
index: u32,
) -> Result<(), ()> {
self.instance_remove_inner(parent_id, Some(generation), index)
}
#[allow(clippy::result_unit_err)]
pub fn list_clear(&mut self, field_id: FormNodeId) -> Result<(), ()> {
self.list_clear_inner(field_id, None)
}
#[allow(clippy::result_unit_err)]
pub fn list_clear_for_handle(
&mut self,
field_id: FormNodeId,
generation: u64,
) -> Result<(), ()> {
self.list_clear_inner(field_id, Some(generation))
}
#[allow(clippy::result_unit_err)]
pub fn list_add(
&mut self,
field_id: FormNodeId,
display: String,
save: Option<String>,
) -> Result<(), ()> {
self.list_add_inner(field_id, None, display, save)
}
#[allow(clippy::result_unit_err)]
pub fn list_add_for_handle(
&mut self,
field_id: FormNodeId,
generation: u64,
display: String,
save: Option<String>,
) -> Result<(), ()> {
self.list_add_inner(field_id, Some(generation), display, save)
}
pub fn bound_item_for_handle(
&mut self,
field_id: FormNodeId,
generation: u64,
display_value: String,
) -> String {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.handle_is_live(field_id, generation) {
return display_value;
}
let Some(form) = self.form_ref() else {
return display_value;
};
if !matches!(form.get(field_id).node_type, FormNodeType::Field { .. }) {
return display_value;
}
let meta = form.meta(field_id);
for (display, save) in &meta.runtime_listbox_items {
if display == &display_value {
return save.clone();
}
}
for (idx, display) in meta.display_items.iter().enumerate() {
if display == &display_value {
return meta
.save_items
.get(idx)
.cloned()
.unwrap_or_else(|| display_value.clone());
}
}
display_value
}
pub fn num_pages(&mut self) -> u32 {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
self.static_page_count
}
pub fn metadata_binding_error(&mut self) {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
}
pub fn metadata_resolve_failure(&mut self) {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
self.metadata.resolve_failures = self.metadata.resolve_failures.saturating_add(1);
}
fn consume_resolve_call(&mut self) -> bool {
if self.resolve_count_this_script >= MAX_RESOLVE_CALLS_PER_SCRIPT {
return false;
}
self.resolve_count_this_script = self.resolve_count_this_script.saturating_add(1);
true
}
fn resolve_path(&mut self, path: &str) -> ResolveOutcome {
if !self.consume_resolve_call() {
return ResolveOutcome::BindingError;
}
let Some(form) = self.form_ref() else {
return ResolveOutcome::BindingError;
};
let Some(current_id) = self.current_id else {
return ResolveOutcome::BindingError;
};
if current_id.0 >= form.nodes.len() || self.root_id.0 >= form.nodes.len() {
return ResolveOutcome::BindingError;
}
let normalized = normalize_resolve_path(path.trim());
let mut expr = match parse_som(&normalized) {
Ok(expr) => expr,
Err(_) => return ResolveOutcome::BindingError,
};
if expr.segments.len() > MAX_SOM_DEPTH {
return ResolveOutcome::BindingError;
}
if matches!(
expr.segments.last().map(|segment| &segment.selector),
Some(SomSelector::Name(name)) if name == "rawValue"
) {
expr.segments.pop();
}
let parents = build_parent_map(form, self.root_id);
let resolver = HostSomResolver {
form,
root_id: self.root_id,
parents: &parents,
current_id,
};
match resolver.resolve_expression(&expr) {
Some(nodes) if !nodes.is_empty() => ResolveOutcome::Ok(nodes),
_ => ResolveOutcome::NoMatch,
}
}
fn resolve_implicit_inner(&mut self, current_id: FormNodeId, name: &str) -> ResolveOutcome {
let name = name.trim();
if name.is_empty() || !self.consume_resolve_call() {
return ResolveOutcome::BindingError;
}
let Some(form) = self.form_ref() else {
return ResolveOutcome::BindingError;
};
if current_id.0 >= form.nodes.len() || self.root_id.0 >= form.nodes.len() {
return ResolveOutcome::BindingError;
}
let parents = build_parent_map(form, self.root_id);
resolve_implicit_candidates_in_scope(form, &parents, current_id, name)
}
fn resolve_child_inner(&mut self, parent_id: FormNodeId, name: &str) -> ResolveOutcome {
self.resolve_child_candidates_inner(&[parent_id], name)
}
fn resolve_child_candidates_inner(
&mut self,
parent_ids: &[FormNodeId],
name: &str,
) -> ResolveOutcome {
let name = name.trim();
if name.is_empty() || !self.consume_resolve_call() {
return ResolveOutcome::BindingError;
}
let Some(form) = self.form_ref() else {
return ResolveOutcome::BindingError;
};
if parent_ids.is_empty()
|| parent_ids
.iter()
.any(|node_id| node_id.0 >= form.nodes.len())
{
return ResolveOutcome::BindingError;
}
let mut direct = Vec::new();
for &parent_id in parent_ids {
for &child_id in &form.get(parent_id).children {
if form.get(child_id).name == name {
push_unique_candidate(&mut direct, child_id);
if direct.len() >= MAX_RESOLVE_CANDIDATES {
return ResolveOutcome::Ok(direct);
}
}
}
}
if !direct.is_empty() {
return ResolveOutcome::Ok(direct);
}
let mut descendants = Vec::new();
for &parent_id in parent_ids {
let mut local =
collect_named_descendant_candidates(form, parent_id, name, MAX_SOM_DEPTH);
order_candidates(form, &mut local);
for node_id in local {
push_unique_candidate(&mut descendants, node_id);
if descendants.len() >= MAX_RESOLVE_CANDIDATES {
return ResolveOutcome::Ok(descendants);
}
}
}
if descendants.is_empty() {
ResolveOutcome::NoMatch
} else {
ResolveOutcome::Ok(descendants)
}
}
fn resolve_scoped_candidates_inner(
&mut self,
scope_ids: &[FormNodeId],
name: &str,
) -> ResolveOutcome {
let name = name.trim();
if name.is_empty() || !self.consume_resolve_call() {
return ResolveOutcome::BindingError;
}
let Some(form) = self.form_ref() else {
return ResolveOutcome::BindingError;
};
if scope_ids.is_empty()
|| self.root_id.0 >= form.nodes.len()
|| scope_ids
.iter()
.any(|node_id| node_id.0 >= form.nodes.len())
{
return ResolveOutcome::BindingError;
}
let parents = build_parent_map(form, self.root_id);
let mut out = Vec::new();
for &scope_id in scope_ids {
if let ResolveOutcome::Ok(nodes) =
resolve_implicit_candidates_in_scope(form, &parents, scope_id, name)
{
for node_id in nodes {
push_unique_candidate(&mut out, node_id);
if out.len() >= MAX_RESOLVE_CANDIDATES {
return ResolveOutcome::Ok(out);
}
}
}
}
if out.is_empty() {
ResolveOutcome::NoMatch
} else {
ResolveOutcome::Ok(out)
}
}
fn instance_count_inner(&mut self, parent_id: FormNodeId, generation: Option<u64>) -> u32 {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
let Some(run) = self.read_instance_run(parent_id, generation) else {
return 0;
};
run.nodes.len() as u32
}
fn instance_index_inner(&mut self, node_id: FormNodeId, generation: Option<u64>) -> u32 {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
let Some(run) = self.read_instance_run(node_id, generation) else {
return 0;
};
run.nodes
.iter()
.position(|candidate| *candidate == node_id)
.unwrap_or(0) as u32
}
fn instance_set_inner(
&mut self,
parent_id: FormNodeId,
generation: Option<u64>,
n: u32,
) -> Result<u32, ()> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.write_activity_allowed()
|| self.mutation_count_this_doc >= MAX_MUTATIONS_PER_DOC
|| !self.consume_resolve_call()
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let Some(run) = self.live_instance_run(parent_id, generation) else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
let Some(target_count) = self.clamped_instance_count(run.prototype_id, n) else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
let target_count = target_count as usize;
let prototype_id = run.prototype_id;
let parent_id = run.parent_id;
let first_pos = run.first_position;
let Some(prototype_name) = self
.form_ref()
.and_then(|form| form.nodes.get(prototype_id.0))
.map(|node| node.name.clone())
else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
let remove_ids = run.nodes;
let mut new_ids = Vec::with_capacity(target_count);
if target_count > 0 {
self.normalize_instance_occurrence(prototype_id);
new_ids.push(prototype_id);
for _ in 1..target_count {
let cloned_id = self.clone_subtree(prototype_id)?;
new_ids.push(cloned_id);
}
}
let Some(form) = self.form_mut() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
let parent = form.get_mut(parent_id);
parent
.children
.retain(|child_id| !remove_ids.contains(child_id));
let insert_pos = first_pos.min(parent.children.len());
for (offset, node_id) in new_ids.iter().copied().enumerate() {
parent.children.insert(insert_pos + offset, node_id);
}
let key = (parent_id, prototype_name);
if target_count == 0 {
self.zero_instance_runs.insert(key, self.generation);
} else {
self.zero_instance_runs.remove(&key);
}
self.record_instance_write();
Ok(target_count as u32)
}
fn instance_add_inner(
&mut self,
parent_id: FormNodeId,
generation: Option<u64>,
) -> Result<FormNodeId, ()> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.write_activity_allowed()
|| self.mutation_count_this_doc >= MAX_MUTATIONS_PER_DOC
|| !self.consume_resolve_call()
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let Some(run) = self.live_instance_run(parent_id, generation) else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
let zero_key = self
.form_ref()
.and_then(|form| form.nodes.get(run.prototype_id.0))
.map(|node| (run.parent_id, node.name.clone()));
let Some(max_allowed) = self.max_instances_for(run.prototype_id) else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
if run.nodes.len() as u32 >= max_allowed {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let cloned_id = self.clone_subtree(run.prototype_id)?;
let Some(form) = self.form_mut() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
form.get_mut(run.parent_id)
.children
.insert(run.last_position + 1, cloned_id);
if let Some(key) = zero_key {
self.zero_instance_runs.remove(&key);
}
self.record_instance_write();
Ok(cloned_id)
}
fn instance_remove_inner(
&mut self,
parent_id: FormNodeId,
generation: Option<u64>,
index: u32,
) -> Result<(), ()> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.write_activity_allowed()
|| self.mutation_count_this_doc >= MAX_MUTATIONS_PER_DOC
|| !self.consume_resolve_call()
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let Some(run) = self.live_instance_run(parent_id, generation) else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
let zero_key = self
.form_ref()
.and_then(|form| form.nodes.get(run.prototype_id.0))
.map(|node| (run.parent_id, node.name.clone()));
let min_allowed = self
.form_ref()
.and_then(|form| form.nodes.get(run.prototype_id.0))
.map(|node| node.occur.min)
.unwrap_or(1);
if run.nodes.len() as u32 <= min_allowed {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let Some(remove_position) = run.positions.get(index as usize).copied() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
let Some(form) = self.form_mut() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
form.get_mut(run.parent_id).children.remove(remove_position);
if let Some(key) = zero_key {
if run.nodes.len() == 1 {
self.zero_instance_runs.insert(key, self.generation);
} else {
self.zero_instance_runs.remove(&key);
}
}
self.record_instance_write();
Ok(())
}
fn read_instance_run(
&mut self,
node_id: FormNodeId,
generation: Option<u64>,
) -> Option<InstanceRun> {
if !self.consume_resolve_call() {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
}
let Some(form) = self.form_ref() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
};
if generation.is_some_and(|value| value != self.generation) {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
}
if node_id.0 >= form.nodes.len() || self.root_id.0 >= form.nodes.len() {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
}
let parents = build_parent_map(form, self.root_id);
if !parents.contains_key(&node_id) {
if node_id == self.root_id {
return Some(InstanceRun {
parent_id: node_id,
positions: vec![0],
nodes: vec![node_id],
prototype_id: node_id,
first_position: 0,
last_position: 0,
});
}
return None;
}
build_instance_run(form, &parents, node_id)
}
fn live_instance_run(
&self,
node_id: FormNodeId,
generation: Option<u64>,
) -> Option<InstanceRun> {
let form = self.form_ref()?;
if generation.is_some_and(|value| value != self.generation)
|| node_id.0 >= form.nodes.len()
|| self.root_id.0 >= form.nodes.len()
|| !is_instance_node(&form.get(node_id).node_type)
{
return None;
}
let parents = build_parent_map(form, self.root_id);
build_instance_run(form, &parents, node_id)
}
fn clamped_instance_count(&self, prototype_id: FormNodeId, requested: u32) -> Option<u32> {
let min_allowed = self.form_ref()?.get(prototype_id).occur.min;
let max_allowed = self.max_instances_for(prototype_id)?;
if min_allowed > max_allowed {
return None;
}
Some(requested.clamp(min_allowed, max_allowed))
}
fn max_instances_for(&self, prototype_id: FormNodeId) -> Option<u32> {
let occur = &self.form_ref()?.get(prototype_id).occur;
let max = occur.max.unwrap_or(u32::MAX);
Some(max.min(MAX_INSTANCES_PER_SUBFORM))
}
fn clone_subtree(&mut self, source_id: FormNodeId) -> Result<FormNodeId, ()> {
let (mut new_node, mut new_meta, child_ids) = {
let Some(form) = self.form_ref() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
if source_id.0 >= form.nodes.len() {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
(
form.get(source_id).clone(),
form.meta(source_id).clone(),
form.get(source_id).children.clone(),
)
};
let mut new_children = Vec::with_capacity(child_ids.len());
for child_id in child_ids {
new_children.push(self.clone_subtree(child_id)?);
}
new_node.children = new_children;
new_node.occur.initial = 1;
new_meta.xfa_id = None;
let Some(form) = self.form_mut() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
Ok(form.add_node_with_meta(new_node, new_meta))
}
fn normalize_instance_occurrence(&mut self, node_id: FormNodeId) {
if let Some(form) = self.form_mut() {
if let Some(node) = form.nodes.get_mut(node_id.0) {
node.occur.initial = 1;
}
}
}
fn record_instance_write(&mut self) {
self.metadata.instance_writes = self.metadata.instance_writes.saturating_add(1);
self.mutation_count_this_doc = self.mutation_count_this_doc.saturating_add(1);
}
fn record_list_write(&mut self) {
self.metadata.list_writes = self.metadata.list_writes.saturating_add(1);
self.mutation_count_this_doc = self.mutation_count_this_doc.saturating_add(1);
}
fn list_clear_inner(
&mut self,
field_id: FormNodeId,
generation: Option<u64>,
) -> Result<(), ()> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.write_activity_allowed()
|| self.mutation_count_this_doc >= MAX_MUTATIONS_PER_DOC
|| !self.consume_resolve_call()
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let current_generation = self.generation;
let Some(form) = self.form_mut() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
if generation.is_some_and(|value| value != current_generation)
|| field_id.0 >= form.nodes.len()
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
if !matches!(form.get(field_id).node_type, FormNodeType::Field { .. }) {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
form.meta_mut(field_id).runtime_listbox_items.clear();
self.record_list_write();
Ok(())
}
fn list_add_inner(
&mut self,
field_id: FormNodeId,
generation: Option<u64>,
display: String,
save: Option<String>,
) -> Result<(), ()> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.write_activity_allowed()
|| self.mutation_count_this_doc >= MAX_MUTATIONS_PER_DOC
|| !self.consume_resolve_call()
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let current_generation = self.generation;
let Some(form) = self.form_mut() else {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
};
if generation.is_some_and(|value| value != current_generation)
|| field_id.0 >= form.nodes.len()
{
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
if !matches!(form.get(field_id).node_type, FormNodeType::Field { .. }) {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let meta = form.meta_mut(field_id);
if meta.runtime_listbox_items.len() >= MAX_ITEMS_PER_LISTBOX as usize {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Err(());
}
let save_value = save.unwrap_or_else(|| display.clone());
meta.runtime_listbox_items.push((display, save_value));
self.record_list_write();
Ok(())
}
fn write_activity_allowed(&self) -> bool {
matches!(
self.current_activity.as_deref(),
Some("initialize")
| Some("calculate")
| Some("validate")
| Some("docReady")
| Some("layoutReady")
)
}
fn handle_is_live(&self, node_id: FormNodeId, generation: u64) -> bool {
generation == self.generation
&& self
.form_ref()
.is_some_and(|form| node_id.0 < form.nodes.len())
}
fn write_field_value(
&mut self,
node_id: FormNodeId,
value: String,
) -> Option<(String, String)> {
let form = self.form_mut()?;
let node = form.nodes.get_mut(node_id.0)?;
let FormNodeType::Field { value: field_value } = &mut node.node_type else {
return None;
};
let before = field_value.clone();
*field_value = value;
Some((before, field_value.clone()))
}
fn form_ref(&self) -> Option<&FormTree> {
if self.form.is_null() {
None
} else {
unsafe { self.form.as_ref() }
}
}
fn form_mut(&mut self) -> Option<&mut FormTree> {
if self.form.is_null() {
None
} else {
unsafe { self.form.as_mut() }
}
}
fn data_dom_ref(&self) -> Option<&DataDom> {
self.data_dom.map(|ptr| unsafe { &*ptr })
}
pub fn data_children(&mut self, raw_id: usize) -> Vec<usize> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
let result = {
let Some(dom) = self.data_dom_ref() else {
return Vec::new();
};
let id = DataNodeId::from_raw(raw_id);
if dom.get(id).is_none() {
return Vec::new();
}
dom.children(id)
.iter()
.take(MAX_RESOLVE_RESULTS)
.map(|c| c.as_raw())
.collect::<Vec<_>>()
};
self.metadata.data_reads = self.metadata.data_reads.saturating_add(1);
result
}
pub fn data_value(&mut self, raw_id: usize) -> Option<String> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
let result = {
let dom = self.data_dom_ref()?;
let id = DataNodeId::from_raw(raw_id);
dom.get(id)?;
dom.value(id).ok().map(|s| s.to_owned())
};
if result.is_some() {
self.metadata.data_reads = self.metadata.data_reads.saturating_add(1);
}
result
}
pub fn data_child_by_name(&mut self, parent_raw: usize, name: &str) -> Option<usize> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
let result = {
let dom = self.data_dom_ref()?;
let parent = DataNodeId::from_raw(parent_raw);
dom.get(parent)?;
dom.children_by_name(parent, name)
.into_iter()
.next()
.map(|id| id.as_raw())
};
if result.is_some() {
self.metadata.data_reads = self.metadata.data_reads.saturating_add(1);
}
result
}
pub fn data_bound_record(
&mut self,
form_node_id: FormNodeId,
generation: u64,
) -> Option<usize> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if generation != self.generation {
return None;
}
let form = self.form_ref()?;
if form_node_id.0 >= form.nodes.len() {
return None;
}
form.meta(form_node_id).bound_data_node
}
pub fn data_resolve_node(&mut self, path: &str) -> Option<usize> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.consume_resolve_call() {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return None;
}
{
let dom = self.data_dom_ref()?;
resolve_data_path(dom, path, None)
.ok()?
.into_iter()
.next()
.map(|id| id.as_raw())
}
}
pub fn data_resolve_nodes(&mut self, path: &str) -> Vec<usize> {
self.metadata.host_calls = self.metadata.host_calls.saturating_add(1);
if !self.consume_resolve_call() {
self.metadata.binding_errors = self.metadata.binding_errors.saturating_add(1);
return Vec::new();
}
{
let Some(dom) = self.data_dom_ref() else {
return Vec::new();
};
resolve_data_path(dom, path, None)
.unwrap_or_default()
.into_iter()
.take(MAX_RESOLVE_RESULTS)
.map(|id| id.as_raw())
.collect()
}
}
}
enum ResolveOutcome {
Ok(Vec<FormNodeId>),
NoMatch,
BindingError,
}
#[derive(Debug, Clone)]
struct InstanceRun {
parent_id: FormNodeId,
positions: Vec<usize>,
nodes: Vec<FormNodeId>,
prototype_id: FormNodeId,
first_position: usize,
last_position: usize,
}
fn build_instance_run(
form: &FormTree,
parents: &HashMap<FormNodeId, FormNodeId>,
node_id: FormNodeId,
) -> Option<InstanceRun> {
let parent_id = parents.get(&node_id).copied()?;
let name = form.get(node_id).name.clone();
let parent = form.get(parent_id);
let mut positions = Vec::new();
let mut nodes = Vec::new();
for (position, child_id) in parent.children.iter().copied().enumerate() {
if form.get(child_id).name == name {
positions.push(position);
nodes.push(child_id);
}
}
if !nodes.contains(&node_id) {
return None;
}
Some(InstanceRun {
parent_id,
prototype_id: nodes[0],
first_position: positions[0],
last_position: *positions.last()?,
positions,
nodes,
})
}
fn is_instance_node(node_type: &FormNodeType) -> bool {
matches!(
node_type,
FormNodeType::Root
| FormNodeType::Subform
| FormNodeType::Area
| FormNodeType::ExclGroup
| FormNodeType::SubformSet
)
}
fn normalize_resolve_path(path: &str) -> String {
if let Some(rest) = path.strip_prefix("this.") {
format!("$.{rest}")
} else if path == "this" {
"$".to_string()
} else {
path.to_string()
}
}
struct HostSomResolver<'a> {
form: &'a FormTree,
root_id: FormNodeId,
parents: &'a HashMap<FormNodeId, FormNodeId>,
current_id: FormNodeId,
}
impl HostSomResolver<'_> {
fn resolve_expression(&self, expr: &SomExpression) -> Option<Vec<FormNodeId>> {
match expr.root {
SomRoot::Data | SomRoot::Record | SomRoot::Template => None,
SomRoot::CurrentContainer => {
if expr.segments.is_empty() {
Some(vec![self.current_id])
} else {
Some(self.follow_absolute(vec![self.current_id], &expr.segments))
}
}
SomRoot::Form => {
if expr.segments.is_empty() {
Some(vec![self.root_id])
} else {
Some(self.follow_absolute(vec![self.root_id], &expr.segments))
}
}
SomRoot::Xfa => {
let segments = strip_xfa_form_prefix(&expr.segments);
if segments.is_empty() {
Some(vec![self.root_id])
} else {
Some(self.follow_absolute(vec![self.root_id], segments))
}
}
SomRoot::Unqualified => {
if expr.segments.is_empty() {
Some(vec![self.current_id])
} else {
Some(self.follow_unqualified(&expr.segments))
}
}
}
}
fn follow_absolute(
&self,
mut current: Vec<FormNodeId>,
segments: &[xfa_dom_resolver::som::SomSegment],
) -> Vec<FormNodeId> {
for (idx, segment) in segments.iter().enumerate() {
let allow_self = idx == 0;
current = current
.into_iter()
.flat_map(|node_id| self.step_from_node(node_id, segment, allow_self))
.collect();
if current.is_empty() {
break;
}
}
current
}
fn follow_unqualified(
&self,
segments: &[xfa_dom_resolver::som::SomSegment],
) -> Vec<FormNodeId> {
let Some((first, rest)) = segments.split_first() else {
return vec![self.current_id];
};
let mut scope = Some(self.current_id);
while let Some(scope_id) = scope {
let anchors: Vec<_> = descendants_inclusive(self.form, scope_id)
.into_iter()
.filter(|node_id| self.node_matches_segment(*node_id, first))
.collect();
let matched = self.follow_remaining(anchors, rest);
if !matched.is_empty() {
return matched;
}
scope = self.parents.get(&scope_id).copied();
}
let anchors: Vec<_> = descendants_inclusive(self.form, self.root_id)
.into_iter()
.filter(|node_id| self.node_matches_segment(*node_id, first))
.collect();
self.follow_remaining(anchors, rest)
}
fn follow_remaining(
&self,
mut current: Vec<FormNodeId>,
segments: &[xfa_dom_resolver::som::SomSegment],
) -> Vec<FormNodeId> {
for segment in segments {
current = current
.into_iter()
.flat_map(|node_id| self.step_from_node(node_id, segment, false))
.collect();
if current.is_empty() {
break;
}
}
current
}
fn step_from_node(
&self,
node_id: FormNodeId,
segment: &xfa_dom_resolver::som::SomSegment,
allow_self: bool,
) -> Vec<FormNodeId> {
if let SomSelector::Name(name) = &segment.selector {
if name == ".." {
if let Some(&parent_id) = self.parents.get(&node_id) {
return apply_index_to_single(parent_id, segment.index);
}
return Vec::new();
}
}
if allow_self && self.node_matches_selector(node_id, &segment.selector) {
return apply_index_to_single(node_id, segment.index);
}
let matches: Vec<_> = self
.form
.get(node_id)
.children
.iter()
.copied()
.filter(|child_id| self.node_matches_selector(*child_id, &segment.selector))
.collect();
apply_index(matches, segment.index)
}
fn node_matches_segment(
&self,
node_id: FormNodeId,
segment: &xfa_dom_resolver::som::SomSegment,
) -> bool {
if !self.node_matches_selector(node_id, &segment.selector) {
return false;
}
match segment.index {
SomIndex::All => true,
SomIndex::None => self.sibling_position(node_id, &segment.selector) == Some(0),
SomIndex::Specific(idx) => {
self.sibling_position(node_id, &segment.selector) == Some(idx)
}
}
}
fn sibling_position(&self, node_id: FormNodeId, selector: &SomSelector) -> Option<usize> {
let Some(parent_id) = self.parents.get(&node_id).copied() else {
return self.node_matches_selector(node_id, selector).then_some(0);
};
self.form
.get(parent_id)
.children
.iter()
.copied()
.filter(|candidate| self.node_matches_selector(*candidate, selector))
.position(|candidate| candidate == node_id)
}
fn node_matches_selector(&self, node_id: FormNodeId, selector: &SomSelector) -> bool {
match selector {
SomSelector::Name(name) => self.form.get(node_id).name == *name,
SomSelector::Class(class_name) => self.node_matches_class(node_id, class_name),
SomSelector::AllChildren => true,
}
}
fn node_matches_class(&self, node_id: FormNodeId, class_name: &str) -> bool {
let class_name = class_name.to_ascii_lowercase();
match class_name.as_str() {
"subform" => matches!(
self.form.get(node_id).node_type,
FormNodeType::Root | FormNodeType::Subform
),
"pageset" => matches!(self.form.get(node_id).node_type, FormNodeType::PageSet),
"pagearea" => matches!(
self.form.get(node_id).node_type,
FormNodeType::PageArea { .. }
),
"field" => matches!(self.form.get(node_id).node_type, FormNodeType::Field { .. }),
"draw" => matches!(
self.form.get(node_id).node_type,
FormNodeType::Draw(_) | FormNodeType::Image { .. }
),
"exclgroup" => self.form.meta(node_id).group_kind == GroupKind::ExclusiveChoice,
_ => false,
}
}
}
fn strip_xfa_form_prefix(
segments: &[xfa_dom_resolver::som::SomSegment],
) -> &[xfa_dom_resolver::som::SomSegment] {
match segments.first() {
Some(segment)
if matches!(&segment.selector, SomSelector::Name(name) if name == "form")
&& matches!(segment.index, SomIndex::None) =>
{
&segments[1..]
}
_ => segments,
}
}
fn apply_index(matches: Vec<FormNodeId>, index: SomIndex) -> Vec<FormNodeId> {
match index {
SomIndex::None => matches.into_iter().take(1).collect(),
SomIndex::Specific(idx) => matches.get(idx).copied().into_iter().collect(),
SomIndex::All => matches,
}
}
fn apply_index_to_single(node_id: FormNodeId, index: SomIndex) -> Vec<FormNodeId> {
match index {
SomIndex::None | SomIndex::Specific(0) | SomIndex::All => vec![node_id],
SomIndex::Specific(_) => Vec::new(),
}
}
fn descendants_inclusive(form: &FormTree, root_id: FormNodeId) -> Vec<FormNodeId> {
let mut out = Vec::new();
collect_descendants(form, root_id, &mut out);
out
}
fn collect_descendants(form: &FormTree, node_id: FormNodeId, out: &mut Vec<FormNodeId>) {
out.push(node_id);
for &child_id in &form.get(node_id).children {
collect_descendants(form, child_id, out);
}
}
const MAX_RESOLVE_CANDIDATES: usize = 32;
fn collect_named_descendant_candidates(
form: &FormTree,
scope_id: FormNodeId,
name: &str,
max_depth: usize,
) -> Vec<FormNodeId> {
let mut candidates: Vec<FormNodeId> = Vec::new();
find_named_descendant_inner(form, scope_id, name, 0, max_depth, &mut candidates);
candidates
}
fn order_candidates(form: &FormTree, candidates: &mut [FormNodeId]) {
candidates.sort_by(|left, right| {
let left_is_field = matches!(form.get(*left).node_type, FormNodeType::Field { .. });
let right_is_field = matches!(form.get(*right).node_type, FormNodeType::Field { .. });
match (left_is_field, right_is_field) {
(true, false) => std::cmp::Ordering::Less,
(false, true) => std::cmp::Ordering::Greater,
(true, true) => std::cmp::Ordering::Equal,
(false, false) => form
.get(*right)
.children
.len()
.cmp(&form.get(*left).children.len()),
}
});
}
fn push_unique_candidate(candidates: &mut Vec<FormNodeId>, node_id: FormNodeId) {
if candidates.len() < MAX_RESOLVE_CANDIDATES && !candidates.contains(&node_id) {
candidates.push(node_id);
}
}
fn resolve_implicit_candidates_in_scope(
form: &FormTree,
parents: &HashMap<FormNodeId, FormNodeId>,
current_id: FormNodeId,
name: &str,
) -> ResolveOutcome {
let mut scope = Some(current_id);
let mut depth = 0usize;
while let Some(scope_id) = scope {
if depth > MAX_SOM_DEPTH {
return ResolveOutcome::BindingError;
}
if scope_id.0 >= form.nodes.len() {
return ResolveOutcome::BindingError;
}
let mut candidates =
collect_named_descendant_candidates(form, scope_id, name, MAX_SOM_DEPTH);
if !candidates.is_empty() {
order_candidates(form, &mut candidates);
if !form.get(candidates[0]).children.is_empty() {
return ResolveOutcome::Ok(candidates);
}
}
scope = parents.get(&scope_id).copied();
depth += 1;
}
let mut scope = Some(current_id);
let mut depth = 0usize;
while let Some(scope_id) = scope {
if depth > MAX_SOM_DEPTH {
return ResolveOutcome::BindingError;
}
if scope_id.0 >= form.nodes.len() {
return ResolveOutcome::BindingError;
}
if form.get(scope_id).name == name {
return ResolveOutcome::Ok(vec![scope_id]);
}
scope = parents.get(&scope_id).copied();
depth += 1;
}
let mut scope = Some(current_id);
let mut depth = 0usize;
while let Some(scope_id) = scope {
if depth > MAX_SOM_DEPTH {
return ResolveOutcome::BindingError;
}
if scope_id.0 >= form.nodes.len() {
return ResolveOutcome::BindingError;
}
let mut candidates =
collect_named_descendant_candidates(form, scope_id, name, MAX_SOM_DEPTH);
if !candidates.is_empty() {
order_candidates(form, &mut candidates);
return ResolveOutcome::Ok(candidates);
}
scope = parents.get(&scope_id).copied();
depth += 1;
}
ResolveOutcome::NoMatch
}
fn find_named_descendant_inner(
form: &FormTree,
node_id: FormNodeId,
name: &str,
depth: usize,
max_depth: usize,
candidates: &mut Vec<FormNodeId>,
) {
if depth >= max_depth || candidates.len() >= MAX_RESOLVE_CANDIDATES {
return;
}
for &child_id in &form.get(node_id).children {
if candidates.len() >= MAX_RESOLVE_CANDIDATES {
return;
}
if form.get(child_id).name == name {
candidates.push(child_id);
} else {
find_named_descendant_inner(form, child_id, name, depth + 1, max_depth, candidates);
}
}
}
fn build_parent_map(form: &FormTree, root_id: FormNodeId) -> HashMap<FormNodeId, FormNodeId> {
let mut parents = HashMap::new();
populate_parent_map(form, root_id, &mut parents);
parents
}
fn populate_parent_map(
form: &FormTree,
node_id: FormNodeId,
parents: &mut HashMap<FormNodeId, FormNodeId>,
) {
for &child_id in &form.get(node_id).children {
parents.insert(child_id, node_id);
populate_parent_map(form, child_id, parents);
}
}
fn parse_nonnegative_occur_value(value: &str) -> Option<u32> {
let parsed = value.trim().parse::<u64>().ok()?;
u32::try_from(parsed).ok()
}
fn parse_max_occur_value(value: &str) -> Option<Option<u32>> {
let trimmed = value.trim();
if trimmed == "-1" {
return Some(None);
}
parse_nonnegative_occur_value(trimmed).map(Some)
}
fn clamp_occur_to_i32(value: u32) -> i32 {
value.min(i32::MAX as u32) as i32
}
#[cfg(test)]
mod tests {
use super::*;
use xfa_layout_engine::form::{FormNode, Occur};
use xfa_layout_engine::text::FontMetrics;
use xfa_layout_engine::types::{BoxModel, LayoutStrategy};
fn add_node(tree: &mut FormTree, name: &str, node_type: FormNodeType) -> FormNodeId {
tree.add_node(FormNode {
name: name.to_string(),
node_type,
box_model: BoxModel::default(),
layout: LayoutStrategy::TopToBottom,
children: Vec::new(),
occur: Occur::once(),
font: FontMetrics::default(),
calculate: None,
validate: None,
column_widths: Vec::new(),
col_span: 1,
})
}
#[test]
fn raw_value_get_set_and_generation_guard() {
let mut tree = FormTree::new();
let root = add_node(&mut tree, "root", FormNodeType::Root);
let field = add_node(
&mut tree,
"Field1",
FormNodeType::Field {
value: "old".to_string(),
},
);
tree.get_mut(root).children = vec![field];
let mut host = HostBindings::new();
host.reset_per_document();
host.set_form_handle(&mut tree as *mut FormTree, root);
host.reset_per_script(field, Some("calculate"));
let generation = host.generation();
assert_eq!(
host.get_raw_value(field, generation),
Some("old".to_string())
);
assert!(host.set_raw_value(field, "new".to_string(), generation));
assert_eq!(
host.get_raw_value(field, generation),
Some("new".to_string())
);
host.reset_per_document();
assert_eq!(host.get_raw_value(field, generation), None);
}
#[test]
fn resolve_node_rejects_over_depth() {
let mut tree = FormTree::new();
let root = add_node(&mut tree, "root", FormNodeType::Root);
let field = add_node(
&mut tree,
"Field1",
FormNodeType::Field {
value: String::new(),
},
);
tree.get_mut(root).children = vec![field];
let mut host = HostBindings::new();
host.reset_per_document();
host.set_form_handle(&mut tree as *mut FormTree, root);
host.reset_per_script(root, Some("calculate"));
let long_path = (0..=MAX_SOM_DEPTH)
.map(|idx| format!("n{idx}"))
.collect::<Vec<_>>()
.join(".");
assert_eq!(host.resolve_node(&long_path), None);
assert_eq!(host.take_metadata().binding_errors, 1);
}
}