mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 13:01:29 +00:00
M12: an XFA child named as a property shadows it for the whole expression
`row.border.edge[0]` with a field `border` in the row no longer falls back to the row's own border when the field has no `edge`; `row.#border` still names the property by class (XFA 3.3 p. 96). `namedItem` on a list of property objects reads each name as written in the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
This commit is contained in:
1 parent
5a6ab51bf9
commit
17ad260d71
2 files changed
+36
-10
No files matched your search
+10
-10
@@ -715,6 +715,8 @@ pub(crate) fn resolve_into_props(h: &XHost, from: usize, expr: &str) -> Vec<(usi
|
||||
let (Some(head), Some(rest)) = (segs.get(..i), segs.get(i..)) else { continue };
|
||||
let first = rest.first().map_or("", |s| s.trim());
|
||||
let first = first.split('[').next().unwrap_or(first);
|
||||
// `#border` names the property by its class (XFA 3.3 p. 96), past a child named `border`.
|
||||
let by_class = first.starts_with('#');
|
||||
let first = first.strip_prefix('#').unwrap_or(first);
|
||||
// Only an object's properties start the rest (the parser's property tags): the
|
||||
// prefix is resolved only then.
|
||||
@@ -731,12 +733,14 @@ pub(crate) fn resolve_into_props(h: &XHost, from: usize, expr: &str) -> Vec<(usi
|
||||
// empty, until a node's value is its value element.
|
||||
.filter(|&n| !(first == "value" && h.nodes.get(n).is_some_and(|x| x.kind == XfaKind::Draw)))
|
||||
.flat_map(|n| {
|
||||
let named = h.nodes.get(n).is_some_and(|x| x.children.iter().any(|&c| h.nodes.get(c).is_some_and(|c| !c.gone && c.name == first)));
|
||||
let children = if named { super::resolve_som(h, n, &rest) } else { Vec::new() };
|
||||
if children.is_empty() {
|
||||
resolve(h, n, "", &rest).into_iter().map(|p| (n, p)).collect::<Vec<_>>()
|
||||
let named = !by_class
|
||||
&& h.nodes.get(n).is_some_and(|x| x.children.iter().any(|&c| h.nodes.get(c).is_some_and(|c| !c.gone && c.name == first)));
|
||||
// The child shadows the property for the whole rest: what the child lacks names nothing
|
||||
// here (a property of the child is found where the head names the child).
|
||||
if named {
|
||||
super::resolve_som(h, n, &rest).into_iter().map(|c| (c, String::new())).collect()
|
||||
} else {
|
||||
children.into_iter().map(|c| (c, String::new())).collect()
|
||||
resolve(h, n, "", &rest).into_iter().map(|p| (n, p)).collect::<Vec<_>>()
|
||||
}
|
||||
})
|
||||
.take(MAX_RESOLVED)
|
||||
@@ -1442,11 +1446,7 @@ pub(crate) fn list_object(ctx: &mut Context, items: Vec<(usize, String)>) -> JsR
|
||||
let found = {
|
||||
let h = host(ctx)?.borrow();
|
||||
let named = |(n, p): &&(usize, String)| {
|
||||
if p.is_empty() {
|
||||
h.nodes.get(*n).is_some_and(|x| x.name == name)
|
||||
} else {
|
||||
template_prop(&h, *n, p).is_some_and(|e| e.attrs.iter().any(|(k, v)| k == "name" && *v == name))
|
||||
}
|
||||
if p.is_empty() { h.nodes.get(*n).is_some_and(|x| x.name == name) } else { attr(&h, *n, p, "name").is_some_and(|v| v == name) }
|
||||
};
|
||||
items.iter().find(named).or_else(|| items.iter().find(|(_, p)| !p.is_empty() && tag_of(p) == name)).cloned()
|
||||
};
|
||||
|
||||
@@ -445,6 +445,11 @@ fn named_children_class_names_and_validation_members_read_as_forms_use_them() {
|
||||
for (expr, want) in [
|
||||
("this.desc.vMandatory.value", "1"),
|
||||
("this.desc.nodes.namedItem('title').value", "Title"),
|
||||
// A name written in the run is the one `namedItem` finds.
|
||||
(
|
||||
"this.desc.nodes.namedItem('title').setAttribute('heading', 'name'), String(this.desc.nodes.namedItem('title')) + '|' + this.desc.nodes.namedItem('heading').value",
|
||||
"null|Title",
|
||||
),
|
||||
("this.extras.nodes.namedItem('note').value", "N"),
|
||||
("this.extras.note.value", "N"),
|
||||
("this.bind.ref", "$.a"),
|
||||
@@ -595,6 +600,27 @@ fn resolve_nodes_takes_a_child_over_a_property_instance_by_instance() {
|
||||
assert_eq!((o.error, o.result.as_deref()), (None, Some("2|field|border|hidden|field")));
|
||||
}
|
||||
|
||||
/// A child named as a property shadows that property for the rest of the expression too
|
||||
/// (`row.border.edge[0]` with a field `border`): the rest is resolved from the child, and when the
|
||||
/// child has no such property the expression names nothing; it does not fall back to the parent's
|
||||
/// property. `#border` names the property by class (XFA 3.3 p. 96), so it still reaches it.
|
||||
#[test]
|
||||
fn a_child_named_as_a_property_shadows_it_for_the_rest_of_the_expression() {
|
||||
let edges = |presence: &str| vec![p("edge", &[("presence", presence)], vec![])];
|
||||
let field =
|
||||
XfaNode { som: "form1[0].row[0].border[0]".into(), ..node("border", XfaKind::Field, &[], vec![p("border", &[], edges("invisible"))]) };
|
||||
let row = XfaNode {
|
||||
som: "form1[0].row[0]".into(),
|
||||
children: vec![field],
|
||||
..node("row", XfaKind::Subform, &[], vec![p("border", &[], edges("hidden"))])
|
||||
};
|
||||
let root = XfaNode { name: "form1".into(), som: "form1[0]".into(), kind: Some(XfaKind::Subform), children: vec![row], ..Default::default() };
|
||||
let script = "var r = function (s) { var o = xfa.resolveNode('xfa.form.form1.row.' + s); return o === null ? 'null' : o.presence; }; \
|
||||
[r('border.border.edge[0]'), r('border.edge[0]'), r('#border.edge[0]')].join('|')";
|
||||
let o = go_on(&root, script, "row");
|
||||
assert_eq!((o.error, o.result.as_deref()), (None, Some("invisible|null|hidden")));
|
||||
}
|
||||
|
||||
/// `keep` reads as the template writes it, else with its defaults (XFA 3.3 pp. 762-763):
|
||||
/// `intact` is `contentArea` for a draw and a positioned or row subform, `none` for a field and
|
||||
/// a flowing subform; `next` and `previous` are `none`. A write reads back in the run.
|
||||
|
||||
Reference in new issue
Block a user