1use roxmltree::Node;
23
24use xfa_layout_engine::form::{
25 Access, AnchorType, ContentArea, DrawContent, EventScript, FieldKind, FormNode, FormNodeId,
26 FormNodeMeta, FormNodeStyle, FormNodeType, FormTree, GroupKind, Occur, Presence,
27 ScriptLanguage,
28};
29use xfa_layout_engine::text::{FontFamily, FontMetrics};
30use xfa_layout_engine::types::{
31 BoxModel, Caption, CaptionPlacement, Insets, LayoutStrategy, Measurement, TextAlign,
32 VerticalAlign,
33};
34
35use crate::error::{Result, XfaError};
36
37pub fn parse_template(xml: &str, datasets_xml: Option<&str>) -> Result<(FormTree, FormNodeId)> {
45 let doc = roxmltree::Document::parse(xml)
46 .map_err(|e| XfaError::ParseFailed(format!("template XML parse error: {e}")))?;
47
48 let root_elem = doc.root_element();
49 let template_elem = if root_elem.tag_name().name() == "template" {
52 root_elem
53 } else {
54 find_first_child_by_name(root_elem, "template")
56 .ok_or_else(|| XfaError::PacketNotFound("no <template> element found".to_string()))?
57 };
58
59 let mut tree = FormTree::new();
60 let (root_id, _trailing) = parse_node(&mut tree, template_elem, true)?;
61
62 if let Some(ds_xml) = datasets_xml {
64 if let Ok(ds_doc) = roxmltree::Document::parse(ds_xml) {
65 if let Some(data_root) = find_data_root(ds_doc.root_element()) {
66 bind_data(&mut tree, root_id, &data_root, &data_root);
67 }
68 }
69 }
70
71 Ok((tree, root_id))
72}
73
74fn parse_node(
85 tree: &mut FormTree,
86 elem: Node<'_, '_>,
87 is_root: bool,
88) -> Result<(FormNodeId, (bool, Option<String>))> {
89 let tag = elem.tag_name().name();
90
91 let (node, trailing_info) = match tag {
92 "template" => {
93 let mut n = parse_root_node(tree, elem)?;
94 let ti = add_children(tree, &mut n, elem)?;
95 (n, ti)
96 }
97 "subform" | "exclGroup" | "area" => {
101 let mut n = parse_subform_node(tree, elem, is_root)?;
102 let ti = add_children(tree, &mut n, elem)?;
103 if tag == "exclGroup" {
105 for &child_id in &n.children {
106 let child_meta = tree.meta_mut(child_id);
107 if child_meta.field_kind == FieldKind::Checkbox {
108 child_meta.field_kind = FieldKind::Radio;
109 }
110 }
111 }
112 (n, ti)
113 }
114 "field" => (parse_field(tree, elem)?, (false, None)),
115 "draw" => (parse_draw(tree, elem)?, (false, None)),
116 "pageSet" => (parse_page_set(tree, elem)?, (false, None)),
117 "pageArea" => (parse_page_area(tree, elem)?, (false, None)),
118 _ => {
119 let mut n = blank_node(tag);
120 let ti = add_children(tree, &mut n, elem)?;
121 (n, ti)
122 }
123 };
124
125 let mut meta = parse_node_meta(elem);
126 if tag == "field" {
134 meta.style.inset_top_pt = Some(node.box_model.margins.top);
135 meta.style.inset_bottom_pt = Some(node.box_model.margins.bottom);
136 meta.style.inset_left_pt = Some(node.box_model.margins.left);
137 meta.style.inset_right_pt = Some(node.box_model.margins.right);
138 }
139 Ok((tree.add_node_with_meta(node, meta), trailing_info))
140}
141
142fn parse_root_node(_tree: &mut FormTree, _elem: Node<'_, '_>) -> Result<FormNode> {
144 Ok(FormNode {
145 name: "root".to_string(),
146 node_type: FormNodeType::Root,
147 box_model: BoxModel {
148 max_width: f64::MAX,
149 max_height: f64::MAX,
150 ..Default::default()
151 },
152 layout: LayoutStrategy::TopToBottom,
153 children: Vec::new(),
154 occur: Occur::once(),
155 font: FontMetrics::default(),
156 calculate: None,
157 validate: None,
158 column_widths: Vec::new(),
159 col_span: 1,
160 })
161}
162
163fn parse_subform_node(
165 _tree: &mut FormTree,
166 elem: Node<'_, '_>,
167 _is_root: bool,
168) -> Result<FormNode> {
169 let name = attr(elem, "name").unwrap_or("").to_string();
170 let layout = parse_layout_attr(elem);
171 let mut bm = parse_box_model(elem);
172 if layout == LayoutStrategy::TopToBottom && bm.width.is_none() {
175 bm.width = Some(612.0);
176 }
177
178 Ok(FormNode {
179 name,
180 node_type: FormNodeType::Subform,
181 box_model: bm,
182 layout,
183 children: Vec::new(),
184 occur: parse_occur(elem),
185 font: FontMetrics::default(),
186 calculate: None,
187 validate: None,
188 column_widths: Vec::new(),
189 col_span: 1,
190 })
191}
192
193fn parse_field(tree: &mut FormTree, elem: Node<'_, '_>) -> Result<FormNode> {
194 let name = attr(elem, "name").unwrap_or("").to_string();
195 let bm = parse_box_model(elem);
196
197 let value = extract_value_text(elem).unwrap_or_default();
203
204 let mut bm_with_caption = bm.clone();
206 if !is_hidden(elem) {
207 if let Some(cap) = parse_caption(elem) {
208 bm_with_caption.caption = Some(cap);
209 }
210 }
211
212 let mut font = parse_font_metrics(elem);
213 if let Some(html_size) = extract_exdata_font_size(elem) {
215 font.size = html_size;
216 }
217
218 let node = FormNode {
219 name,
220 node_type: FormNodeType::Field { value },
221 box_model: bm_with_caption,
222 layout: LayoutStrategy::Positioned,
223 children: Vec::new(),
224 occur: Occur::once(),
225 font,
226 calculate: None,
227 validate: None,
228 column_widths: Vec::new(),
229 col_span: parse_col_span(elem),
230 };
231 let _ = tree; Ok(node)
234}
235
236fn parse_draw(tree: &mut FormTree, elem: Node<'_, '_>) -> Result<FormNode> {
237 let name = attr(elem, "name").unwrap_or("").to_string();
238 let bm = parse_box_model(elem);
239
240 if let Some(draw_content) = extract_draw_content(elem) {
242 let node = FormNode {
243 name,
244 node_type: FormNodeType::Draw(draw_content),
245 box_model: bm,
246 layout: LayoutStrategy::Positioned,
247 children: Vec::new(),
248 occur: Occur::once(),
249 font: FontMetrics::default(),
250 calculate: None,
251 validate: None,
252 column_widths: Vec::new(),
253 col_span: 1,
254 };
255 let _ = tree;
256 return Ok(node);
257 }
258
259 if let Some((image_data, mime_type)) = extract_value_image(elem) {
260 let node = FormNode {
261 name,
262 node_type: FormNodeType::Image {
263 data: image_data,
264 mime_type,
265 },
266 box_model: bm,
267 layout: LayoutStrategy::Positioned,
268 children: Vec::new(),
269 occur: Occur::once(),
270 font: FontMetrics::default(),
271 calculate: None,
272 validate: None,
273 column_widths: Vec::new(),
274 col_span: 1,
275 };
276 let _ = tree;
277 return Ok(node);
278 }
279
280 let mut font = parse_font_metrics(elem);
281 if let Some(html_size) = extract_exdata_font_size(elem) {
285 font.size = html_size;
286 }
287
288 let content = extract_value_text(elem).unwrap_or_default();
290
291 let node = FormNode {
292 name,
293 node_type: FormNodeType::Draw(DrawContent::Text(content)),
294 box_model: bm,
295 layout: LayoutStrategy::Positioned,
296 children: Vec::new(),
297 occur: Occur::once(),
298 font,
299 calculate: None,
300 validate: None,
301 column_widths: Vec::new(),
302 col_span: 1,
303 };
304 let _ = tree;
305 Ok(node)
306}
307
308fn is_hidden(elem: Node<'_, '_>) -> bool {
312 matches!(
313 attr(elem, "presence"),
314 Some("hidden") | Some("invisible") | Some("inactive")
315 )
316}
317
318fn parse_font_size(s: &str) -> Option<f64> {
324 if let Ok(v) = s.trim().parse::<f64>() {
326 return if v > 0.0 { Some(v) } else { None };
327 }
328 Measurement::parse(s).map(|m| m.to_points())
330}
331
332fn parse_percentage(s: &str) -> Option<f64> {
335 let s = s.trim();
336 let num_str = s.strip_suffix('%')?;
337 let v: f64 = num_str.trim().parse().ok()?;
338 Some(v / 100.0)
339}
340
341fn parse_letter_spacing(s: &str, font_size_pt: f64) -> Option<f64> {
347 let s = s.trim();
348 if s == "0" {
349 return Some(0.0);
350 }
351 if let Some(num_str) = s.strip_suffix("em") {
353 let v: f64 = num_str.trim().parse().ok()?;
354 return Some(v * font_size_pt);
355 }
356 Measurement::parse(s).map(|m| m.to_points())
358}
359
360fn parse_font_metrics(elem: Node<'_, '_>) -> FontMetrics {
375 let font_elem = find_first_child_by_name(elem, "font");
376 let size = font_elem
377 .and_then(|f| attr(f, "size"))
378 .and_then(parse_font_size)
379 .unwrap_or(FontMetrics::default().size);
380 let generic_family_str = font_elem.and_then(|f| attr(f, "genericFamily"));
384 let typeface = if let Some(gf) = generic_family_str {
385 FontFamily::from_generic_family(gf)
386 } else {
387 font_elem
388 .and_then(|f| attr(f, "typeface"))
389 .map(FontFamily::from_typeface)
390 .unwrap_or_default()
391 };
392 let text_align = find_first_child_by_name(elem, "para")
395 .and_then(|p| attr(p, "hAlign"))
396 .map(|a| match a {
397 "center" => TextAlign::Center,
398 "right" => TextAlign::Right,
399 "justify" | "justifyAll" => TextAlign::Justify,
400 _ => TextAlign::Left,
402 })
403 .unwrap_or_default();
404 FontMetrics {
405 size,
406 text_align,
407 typeface,
408 ..FontMetrics::default()
409 }
410}
411
412fn parse_page_set(tree: &mut FormTree, elem: Node<'_, '_>) -> Result<FormNode> {
413 let name = attr(elem, "name").unwrap_or("pageSet").to_string();
414 let mut node = FormNode {
415 name,
416 node_type: FormNodeType::PageSet,
417 box_model: BoxModel {
418 max_width: f64::MAX,
419 max_height: f64::MAX,
420 ..Default::default()
421 },
422 layout: LayoutStrategy::TopToBottom,
423 children: Vec::new(),
424 occur: Occur::once(),
425 font: FontMetrics::default(),
426 calculate: None,
427 validate: None,
428 column_widths: Vec::new(),
429 col_span: 1,
430 };
431 for child in elem.children().filter(|n| n.is_element()) {
433 if child.tag_name().name() == "pageArea" {
434 let (child_id, _) = parse_node(tree, child, false)?;
435 node.children.push(child_id);
436 }
437 }
438 Ok(node)
439}
440
441fn parse_page_area(tree: &mut FormTree, elem: Node<'_, '_>) -> Result<FormNode> {
442 let name = attr(elem, "name").unwrap_or("").to_string();
443
444 let (page_w, page_h) = read_medium(elem);
446
447 let content_areas = read_content_areas(elem, page_w, page_h);
449
450 let bm = BoxModel {
451 width: Some(page_w),
452 height: Some(page_h),
453 ..Default::default()
454 };
455
456 let mut node = FormNode {
457 name,
458 node_type: FormNodeType::PageArea { content_areas },
459 box_model: bm,
460 layout: LayoutStrategy::Positioned,
461 children: Vec::new(),
462 occur: Occur::once(),
463 font: FontMetrics::default(),
464 calculate: None,
465 validate: None,
466 column_widths: Vec::new(),
467 col_span: 1,
468 };
469 add_children(tree, &mut node, elem)?;
471 Ok(node)
472}
473
474fn parse_node_meta(elem: Node<'_, '_>) -> FormNodeMeta {
480 let tag = elem.tag_name().name();
481
482 let presence = match attr(elem, "presence") {
488 Some("hidden") => Presence::Hidden,
489 Some("invisible") => Presence::Invisible,
490 Some("inactive") => Presence::Inactive,
491 _ => Presence::Visible,
492 };
493
494 let (page_break_before, break_before_target) = detect_page_break_before(elem);
496 let (page_break_after, break_after_target) = detect_page_break_after(elem);
497 let content_area_break = detect_content_area_break(elem);
498
499 let break_target = break_before_target.or(break_after_target);
501
502 let event_scripts = collect_event_scripts(elem);
504
505 let (keep_next_content_area, keep_previous_content_area, keep_intact_content_area) =
507 parse_keep(elem);
508
509 let (overflow_leader, overflow_trailer) = parse_overflow(elem);
511 let (bookend_leader, bookend_trailer) = parse_bookend(elem);
512
513 let group_kind = if tag == "exclGroup" {
515 GroupKind::ExclusiveChoice
516 } else {
517 GroupKind::None
518 };
519
520 let item_value = if tag == "field" {
522 parse_item_value(elem)
523 } else {
524 None
525 };
526
527 let (display_items, save_items) = if tag == "field" {
529 parse_items_lists(elem)
530 } else {
531 (Vec::new(), Vec::new())
532 };
533
534 let xfa_id = attr(elem, "id").map(|s| s.to_string());
536
537 let field_kind = detect_field_kind(elem);
539
540 let style = parse_node_style(elem);
542 let (data_bind_ref, data_bind_none) = parse_bind(elem);
543 let anchor_type = parse_anchor_type(elem);
544
545 let access = attr(elem, "access").map(Access::parse);
553 let multiline = crate::merger::parse_multiline(elem);
554 let required = crate::merger::parse_required(elem);
555
556 FormNodeMeta {
557 xfa_id,
558 presence,
559 access,
560 multiline,
561 required,
562 page_break_before,
563 page_break_after,
564 break_target,
565 content_area_break,
566 overflow_leader,
567 overflow_trailer,
568 bookend_leader,
569 bookend_trailer,
570 keep_next_content_area,
571 keep_previous_content_area,
572 keep_intact_content_area,
573 event_scripts,
574 data_bind_ref,
575 data_bind_none,
576 group_kind,
577 item_value,
578 field_kind,
579 style,
580 display_items,
581 save_items,
582 anchor_type,
583 ..Default::default()
584 }
585}
586
587fn parse_bind(elem: Node<'_, '_>) -> (Option<String>, bool) {
588 let Some(bind) = find_first_child_by_name(elem, "bind") else {
589 return (None, false);
590 };
591
592 let bind_none = attr(bind, "match") == Some("none");
593 let bind_ref = if bind_none {
594 None
595 } else {
596 attr(bind, "ref").map(|s| s.trim().to_string())
597 };
598 (bind_ref, bind_none)
599}
600
601fn parse_node_style(elem: Node<'_, '_>) -> FormNodeStyle {
606 let mut style = FormNodeStyle {
607 check_button_mark: parse_check_button_mark(elem),
608 ..Default::default()
609 };
610
611 if let Some(fill) = find_first_child_by_name(elem, "fill") {
614 if !is_hidden(fill) {
615 if let Some(color) = find_first_child_by_name(fill, "color") {
616 if let Some(rgb) = parse_xfa_color(color) {
617 style.bg_color = Some(rgb);
618 }
619 }
620 if style.bg_color.is_none() {
622 if let Some(solid) = find_first_child_by_name(fill, "solid") {
623 if let Some(color) = find_first_child_by_name(solid, "color") {
624 if let Some(rgb) = parse_xfa_color(color) {
625 style.bg_color = Some(rgb);
626 }
627 }
628 }
629 }
630 }
631 }
632
633 let border = find_first_child_by_name(elem, "border").or_else(|| {
636 let ui = find_first_child_by_name(elem, "ui")?;
637 ui.children()
638 .filter(|c| c.is_element() && c.tag_name().name() != "border")
639 .find_map(|widget| find_first_child_by_name(widget, "border"))
640 });
641 if let Some(border) = border {
642 let edges: Vec<_> = border
644 .children()
645 .filter(|c| c.is_element() && c.tag_name().name() == "edge")
646 .collect();
647 let first_visible = edges
649 .iter()
650 .find(|e| !is_hidden(**e))
651 .or_else(|| edges.first());
652 if let Some(edge) = first_visible {
653 if let Some(color) = find_first_child_by_name(*edge, "color") {
654 if let Some(rgb) = parse_xfa_color(color) {
655 style.border_color = Some(rgb);
656 }
657 }
658 let stroke = attr(*edge, "stroke").unwrap_or("solid");
659 if stroke != "none" {
660 let thickness = attr(*edge, "thickness")
664 .and_then(Measurement::parse)
665 .map(|m| m.to_points())
666 .unwrap_or(1.0);
667 if thickness > 0.0 {
668 style.border_width_pt = Some(thickness);
669 }
670 }
671 }
672 let edge_visible = |e: &roxmltree::Node| -> bool {
674 !is_hidden(*e) && attr(*e, "stroke").unwrap_or("solid") != "none"
675 };
676 style.border_edges = match edges.len() {
677 0 => [true, true, true, true],
678 1 => {
679 let v = edge_visible(&edges[0]);
680 [v, v, v, v]
681 }
682 2 => {
683 let even = edge_visible(&edges[0]);
684 let odd = edge_visible(&edges[1]);
685 [even, odd, even, odd]
686 }
687 3 => {
688 let top = edge_visible(&edges[0]);
689 let rl = edge_visible(&edges[1]);
690 let bot = edge_visible(&edges[2]);
691 [top, rl, bot, rl]
692 }
693 _ => [
694 edge_visible(&edges[0]),
695 edge_visible(&edges[1]),
696 edge_visible(&edges[2]),
697 edge_visible(&edges[3]),
698 ],
699 };
700 let default_thickness = style.border_width_pt.unwrap_or(0.5);
701 let edge_thickness = |edge: roxmltree::Node<'_, '_>| -> f64 {
702 attr(edge, "thickness")
703 .and_then(Measurement::parse)
704 .map(|m| m.to_points())
705 .unwrap_or(default_thickness)
706 };
707 let per_edge_widths = match edges.len() {
708 0 | 1 => None,
709 2 => Some([
710 edge_thickness(edges[0]),
711 edge_thickness(edges[1]),
712 edge_thickness(edges[0]),
713 edge_thickness(edges[1]),
714 ]),
715 3 => Some([
716 edge_thickness(edges[0]),
717 edge_thickness(edges[1]),
718 edge_thickness(edges[2]),
719 edge_thickness(edges[1]),
720 ]),
721 _ => Some([
722 edge_thickness(edges[0]),
723 edge_thickness(edges[1]),
724 edge_thickness(edges[2]),
725 edge_thickness(edges[3]),
726 ]),
727 };
728 if let Some(widths) = per_edge_widths {
729 if !(widths[0] == widths[1] && widths[1] == widths[2] && widths[2] == widths[3]) {
730 style.border_widths = Some(widths);
731 }
732 }
733 if style.bg_color.is_none() {
736 if let Some(fill) = find_first_child_by_name(border, "fill") {
737 if !is_hidden(fill) {
738 if let Some(color) = find_first_child_by_name(fill, "color") {
739 if let Some(rgb) = parse_xfa_color(color) {
740 style.bg_color = Some(rgb);
741 }
742 }
743 }
744 }
745 }
746 }
747
748 if style.border_color.is_none() {
752 if let Some(value) = find_first_child_by_name(elem, "value") {
753 if let Some(rect) = find_first_child_by_name(value, "rectangle") {
754 let edges: Vec<_> = rect
755 .children()
756 .filter(|c| c.is_element() && c.tag_name().name() == "edge")
757 .collect();
758 if let Some(edge) = edges.first() {
759 if let Some(color) = find_first_child_by_name(*edge, "color") {
760 if let Some(rgb) = parse_xfa_color(color) {
761 style.border_color = Some(rgb);
762 }
763 }
764 if style.border_width_pt.is_none() {
765 let thickness = attr(*edge, "thickness")
766 .and_then(Measurement::parse)
767 .map(|m| m.to_points())
768 .unwrap_or(1.0);
769 if thickness > 0.0 {
770 style.border_width_pt = Some(thickness);
771 }
772 }
773 }
774 }
775 }
776 }
777
778 if style.border_width_pt.is_none() {
783 if let Some(line) = find_first_child_by_name(elem, "value")
784 .and_then(|value| find_first_child_by_name(value, "line"))
785 {
786 if let Some(edge) = find_first_child_by_name(line, "edge") {
787 if attr(edge, "stroke").unwrap_or("solid") != "none" {
788 if let Some(thickness) = attr(edge, "thickness")
789 .and_then(Measurement::parse)
790 .map(|m| m.to_points())
791 {
792 if thickness > 0.0 {
793 style.border_width_pt = Some(thickness);
794 }
795 }
796 if style.border_color.is_none() {
797 if let Some(rgb) =
798 find_first_child_by_name(edge, "color").and_then(parse_xfa_color)
799 {
800 style.border_color = Some(rgb);
801 }
802 }
803 }
804 }
805 }
806 }
807
808 if let Some(font) = find_first_child_by_name(elem, "font") {
815 if let Some(typeface) = attr(font, "typeface") {
816 style.font_family = Some(typeface.to_string());
817 }
818 if let Some(gf) = attr(font, "genericFamily") {
820 style.generic_family = Some(gf.to_string());
821 }
822 if let Some(size_str) = attr(font, "size") {
823 if let Some(m) = Measurement::parse(size_str) {
824 style.font_size = Some(m.to_points());
825 }
826 }
827 if let Some(weight) = attr(font, "weight") {
828 style.font_weight = Some(weight.to_string());
829 }
830 if let Some(posture) = attr(font, "posture") {
831 style.font_style = Some(posture.to_string());
832 }
833 if let Some(fill) = find_first_child_by_name(font, "fill") {
835 if let Some(color) = find_first_child_by_name(fill, "color") {
836 if let Some(rgb) = parse_xfa_color(color) {
837 style.text_color = Some(rgb);
838 }
839 }
840 }
841 if style.text_color.is_none() {
843 if let Some(color_str) = attr(font, "color") {
844 if let Some(rgb) = parse_font_color_attr(color_str) {
845 style.text_color = Some(rgb);
846 }
847 }
848 }
849 if let Some(scale_str) = attr(font, "fontHorizontalScale") {
851 if let Some(v) = parse_percentage(scale_str) {
852 style.font_horizontal_scale = Some(v);
853 }
854 }
855 if let Some(ls_str) = attr(font, "letterSpacing") {
857 if let Some(v) = parse_letter_spacing(ls_str, style.font_size.unwrap_or(10.0)) {
858 style.letter_spacing_pt = Some(v);
859 }
860 }
861 if let Some(underline_str) = attr(font, "underline") {
863 style.underline = underline_str == "1" || underline_str == "2";
864 }
865 if let Some(line_through_str) = attr(font, "lineThrough") {
867 style.line_through = line_through_str == "1";
868 }
869 }
870
871 if let Some(para) = find_first_child_by_name(elem, "para") {
879 if let Some(v) = attr(para, "spaceAbove").and_then(Measurement::parse) {
880 style.space_above_pt = Some(v.to_points());
881 }
882 if let Some(v) = attr(para, "spaceBelow").and_then(Measurement::parse) {
883 style.space_below_pt = Some(v.to_points());
884 }
885 if let Some(v) = attr(para, "marginLeft").and_then(Measurement::parse) {
886 style.margin_left_pt = Some(v.to_points());
887 }
888 if let Some(v) = attr(para, "marginRight").and_then(Measurement::parse) {
889 style.margin_right_pt = Some(v.to_points());
890 }
891 if let Some(v) = attr(para, "lineHeight").and_then(Measurement::parse) {
893 style.line_height_pt = Some(v.to_points());
894 }
895 if let Some(v) = attr(para, "textIndent").and_then(Measurement::parse) {
896 style.text_indent_pt = Some(v.to_points());
897 }
898 if let Some(va) = attr(para, "vAlign") {
899 style.v_align = Some(match va {
900 "middle" => VerticalAlign::Middle,
901 "bottom" => VerticalAlign::Bottom,
902 _ => VerticalAlign::Top,
903 });
904 }
905 if let Some(ha) = attr(para, "hAlign") {
909 style.h_align = Some(match ha {
910 "center" => TextAlign::Center,
911 "right" => TextAlign::Right,
912 _ => TextAlign::Left,
913 });
914 }
915 }
916
917 if let Some(border) = border {
919 if let Some(corner) = find_first_child_by_name(border, "corner") {
920 if let Some(v) = attr(corner, "radius").and_then(Measurement::parse) {
921 style.border_radius_pt = Some(v.to_points());
922 }
923 }
924 if let Some(edge) = find_first_child_by_name(border, "edge") {
925 if let Some(stroke) = attr(edge, "stroke") {
926 if stroke != "none" {
927 style.border_style = Some(stroke.to_string());
928 }
929 }
930 }
931 }
932
933 if let Some(format) = find_first_child_by_name(elem, "format") {
935 if let Some(picture) = find_first_child_by_name(format, "picture") {
936 if let Some(text) = picture.text() {
937 let trimmed = text.trim();
938 if !trimmed.is_empty() {
939 style.format_pattern = Some(trimmed.to_string());
940 }
941 }
942 }
943 }
944
945 style
946}
947
948fn parse_check_button_mark(elem: Node<'_, '_>) -> Option<String> {
949 let ui = find_first_child_by_name(elem, "ui")?;
950 let check_button = ui
951 .children()
952 .find(|n| n.is_element() && n.tag_name().name() == "checkButton")?;
953 let mark = attr(check_button, "mark")?.to_ascii_lowercase();
954 match mark.as_str() {
955 "check" | "circle" | "cross" | "diamond" | "square" | "star" => Some(mark),
956 _ => None,
957 }
958}
959
960fn parse_xfa_color(color_node: Node<'_, '_>) -> Option<(u8, u8, u8)> {
962 let value = attr(color_node, "value")?;
963 let parts: Vec<&str> = value.split(',').collect();
964 if parts.len() >= 3 {
965 let r = parts[0].trim().parse::<u8>().ok()?;
966 let g = parts[1].trim().parse::<u8>().ok()?;
967 let b = parts[2].trim().parse::<u8>().ok()?;
968 Some((r, g, b))
969 } else {
970 None
971 }
972}
973
974fn parse_font_color_attr(s: &str) -> Option<(u8, u8, u8)> {
981 let s = s.trim();
982 if let Some(hex) = s.strip_prefix('#') {
983 match hex.len() {
984 6 => {
985 let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
986 let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
987 let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
988 Some((r, g, b))
989 }
990 3 => {
991 let r = u8::from_str_radix(&hex[0..1], 16).ok()?;
992 let g = u8::from_str_radix(&hex[1..2], 16).ok()?;
993 let b = u8::from_str_radix(&hex[2..3], 16).ok()?;
994 Some((r * 17, g * 17, b * 17))
995 }
996 _ => None,
997 }
998 } else {
999 let parts: Vec<&str> = s.split(',').collect();
1001 if parts.len() >= 3 {
1002 let r = parts[0].trim().parse::<u8>().ok()?;
1003 let g = parts[1].trim().parse::<u8>().ok()?;
1004 let b = parts[2].trim().parse::<u8>().ok()?;
1005 Some((r, g, b))
1006 } else {
1007 None
1008 }
1009 }
1010}
1011
1012fn detect_field_kind(elem: Node<'_, '_>) -> FieldKind {
1019 let Some(ui) = find_first_child_by_name(elem, "ui") else {
1020 return FieldKind::Text;
1021 };
1022 for child in ui.children().filter(|n| n.is_element()) {
1023 let tag = child.tag_name().name();
1024 match tag {
1025 "checkButton" => {
1026 let shape = attr(child, "shape").unwrap_or("square");
1028 return if shape == "round" {
1029 FieldKind::Radio
1030 } else {
1031 FieldKind::Checkbox
1032 };
1033 }
1034 "choiceList" => return FieldKind::Dropdown,
1035 "button" => return FieldKind::Button,
1036 "dateTimeEdit" => return FieldKind::DateTimePicker,
1037 "numericEdit" => return FieldKind::NumericEdit,
1038 "passwordEdit" => return FieldKind::PasswordEdit,
1039 "imageEdit" => return FieldKind::ImageEdit,
1040 "signature" => return FieldKind::Signature,
1041 "barcode" => return FieldKind::Barcode,
1042 _ => {}
1043 }
1044 }
1045 FieldKind::Text
1046}
1047
1048fn detect_page_break_before(elem: Node<'_, '_>) -> (bool, Option<String>) {
1057 for child in elem.children().filter(|n| n.is_element()) {
1058 let tag = child.tag_name().name();
1059 if matches!(tag, "subform" | "field" | "draw" | "exclGroup") {
1060 break;
1061 }
1062 if tag == "breakBefore" && attr(child, "targetType") == Some("pageArea") {
1063 return (true, attr(child, "target").map(|s| s.to_string()));
1064 }
1065 if tag == "break" && attr(child, "before") == Some("pageArea") {
1066 return (true, attr(child, "target").map(|s| s.to_string()));
1070 }
1071 }
1072 (false, None)
1073}
1074
1075fn detect_page_break_after(elem: Node<'_, '_>) -> (bool, Option<String>) {
1079 let mut last_content_idx = 0;
1080 let children: Vec<_> = elem.children().filter(|n| n.is_element()).collect();
1081 for (i, child) in children.iter().enumerate() {
1082 let tag = child.tag_name().name();
1083 if matches!(tag, "subform" | "field" | "draw" | "exclGroup") {
1084 last_content_idx = i;
1085 }
1086 }
1087
1088 for child in children.iter().skip(last_content_idx) {
1090 let tag = child.tag_name().name();
1091 if tag == "breakAfter" && attr(*child, "targetType") == Some("pageArea") {
1092 return (true, attr(*child, "target").map(|s| s.to_string()));
1093 }
1094 if tag == "break" && attr(*child, "after") == Some("pageArea") {
1095 return (true, attr(*child, "target").map(|s| s.to_string()));
1096 }
1097 }
1098 (false, None)
1099}
1100
1101fn detect_content_area_break(elem: Node<'_, '_>) -> bool {
1109 for child in elem.children().filter(|n| n.is_element()) {
1110 let tag = child.tag_name().name();
1111 if tag == "breakBefore" && attr(child, "targetType") == Some("contentArea") {
1112 return true;
1113 }
1114 }
1115 false
1116}
1117
1118fn collect_event_scripts(elem: Node<'_, '_>) -> Vec<EventScript> {
1120 let mut scripts = Vec::new();
1121 for child in elem.children().filter(|n| n.is_element()) {
1122 let child_tag = child.tag_name().name();
1123 if child_tag == "event" {
1124 let activity = attr(child, "activity");
1126 let event_ref = attr(child, "ref");
1127 if activity == Some("ready") && event_ref == Some("$layout") {
1128 continue;
1129 }
1130 if let Some(script_elem) = find_first_child_by_name(child, "script") {
1132 if let Some(script) =
1133 build_event_script(script_elem, activity, event_ref, attr(script_elem, "runAt"))
1134 {
1135 scripts.push(script);
1136 }
1137 }
1138 } else if child_tag == "calculate" {
1139 if let Some(script_elem) = find_first_child_by_name(child, "script") {
1141 if let Some(script) = build_event_script(
1142 script_elem,
1143 Some("calculate"),
1144 None,
1145 attr(script_elem, "runAt"),
1146 ) {
1147 scripts.push(script);
1148 }
1149 }
1150 }
1151 }
1152 scripts
1153}
1154
1155fn build_event_script(
1156 script_elem: Node<'_, '_>,
1157 activity: Option<&str>,
1158 event_ref: Option<&str>,
1159 run_at: Option<&str>,
1160) -> Option<EventScript> {
1161 let text = script_elem.text()?.trim();
1162 if text.is_empty() {
1163 return None;
1164 }
1165
1166 Some(EventScript::new(
1167 text.to_string(),
1168 detect_script_language(attr(script_elem, "contentType")),
1169 activity.map(str::to_string),
1170 event_ref.map(str::to_string),
1171 run_at.map(str::to_string),
1172 ))
1173}
1174
1175fn detect_script_language(content_type: Option<&str>) -> ScriptLanguage {
1176 match content_type.map(|value| value.trim().to_ascii_lowercase()) {
1177 None => ScriptLanguage::FormCalc,
1178 Some(value) if value == "application/x-formcalc" || value.ends_with("/x-formcalc") => {
1179 ScriptLanguage::FormCalc
1180 }
1181 Some(value)
1182 if value == "application/x-javascript"
1183 || value == "application/javascript"
1184 || value == "text/javascript"
1185 || value.ends_with("/x-javascript") =>
1186 {
1187 ScriptLanguage::JavaScript
1188 }
1189 Some(_) => ScriptLanguage::Other,
1190 }
1191}
1192
1193fn parse_keep(elem: Node<'_, '_>) -> (bool, bool, bool) {
1198 if let Some(keep) = find_first_child_by_name(elem, "keep") {
1199 let next = attr(keep, "next") == Some("contentArea");
1200 let prev = attr(keep, "previous") == Some("contentArea");
1201 let intact = attr(keep, "intact") == Some("contentArea");
1202 (next, prev, intact)
1203 } else {
1204 (false, false, false)
1205 }
1206}
1207
1208fn parse_bookend(elem: Node<'_, '_>) -> (Option<String>, Option<String>) {
1218 match find_first_child_by_name(elem, "bookend") {
1219 Some(bookend) => (
1220 attr(bookend, "leader").map(|s| s.to_string()),
1221 attr(bookend, "trailer").map(|s| s.to_string()),
1222 ),
1223 None => (None, None),
1224 }
1225}
1226
1227fn parse_overflow(elem: Node<'_, '_>) -> (Option<String>, Option<String>) {
1228 if let Some(overflow) = find_first_child_by_name(elem, "overflow") {
1229 let leader = attr(overflow, "leader").map(|s| s.to_string());
1230 let trailer = attr(overflow, "trailer").map(|s| s.to_string());
1231 (leader, trailer)
1232 } else {
1233 (None, None)
1234 }
1235}
1236
1237fn parse_item_value(elem: Node<'_, '_>) -> Option<String> {
1239 let items = find_first_child_by_name(elem, "items")?;
1240 let text_elem = find_first_child_by_name(items, "text")?;
1241 let text = text_elem.text()?.trim();
1242 if text.is_empty() {
1243 None
1244 } else {
1245 Some(text.to_string())
1246 }
1247}
1248
1249fn collect_items_texts(items_elem: Node<'_, '_>) -> Vec<String> {
1251 items_elem
1252 .children()
1253 .filter(|n| n.is_element())
1254 .filter_map(|child| {
1255 let txt = child.text().unwrap_or("").trim().to_string();
1256 if txt.is_empty() {
1257 None
1258 } else {
1259 Some(txt)
1260 }
1261 })
1262 .collect()
1263}
1264
1265fn parse_items_lists(elem: Node<'_, '_>) -> (Vec<String>, Vec<String>) {
1267 let items_elems: Vec<_> = elem
1268 .children()
1269 .filter(|n| n.is_element() && n.tag_name().name() == "items")
1270 .collect();
1271 match items_elems.len() {
1272 0 => (Vec::new(), Vec::new()),
1273 1 => {
1274 let vals = collect_items_texts(items_elems[0]);
1275 (vals, Vec::new())
1276 }
1277 _ => {
1278 let first = items_elems[0];
1279 let second = items_elems[1];
1280 let first_is_save = attr(first, "save") == Some("1");
1281 if first_is_save {
1282 (collect_items_texts(second), collect_items_texts(first))
1283 } else {
1284 (collect_items_texts(first), collect_items_texts(second))
1285 }
1286 }
1287 }
1288}
1289
1290fn find_data_root<'a, 'input>(root: Node<'a, 'input>) -> Option<Node<'a, 'input>> {
1297 for child in root.children().filter(|n| n.is_element()) {
1299 if child.tag_name().name() == "data" {
1300 return child.children().find(|n| n.is_element()).or(Some(child));
1302 }
1303 }
1304 if root.tag_name().name() == "data" {
1306 return root.children().find(|n| n.is_element()).or(Some(root));
1307 }
1308 root.children().find(|n| n.is_element())
1310}
1311
1312fn bind_data(
1314 tree: &mut FormTree,
1315 node_id: FormNodeId,
1316 data_root: &Node<'_, '_>,
1317 data_node: &Node<'_, '_>,
1318) {
1319 let name = tree.get(node_id).name.clone();
1320 let children: Vec<FormNodeId> = tree.get(node_id).children.clone();
1321 let meta = tree.meta(node_id).clone();
1322 let group_kind = meta.group_kind;
1323
1324 if group_kind == GroupKind::ExclusiveChoice && !name.is_empty() {
1326 let data_value = lookup_bound_text(data_root, data_node, &meta, &name);
1327 let child_item_vals: Vec<(FormNodeId, Option<String>)> = children
1329 .iter()
1330 .map(|&cid| (cid, tree.meta(cid).item_value.clone()))
1331 .collect();
1332 for (child_id, item_val) in child_item_vals {
1333 if let FormNodeType::Field { ref mut value } = tree.get_mut(child_id).node_type {
1334 if let Some(ref dv) = data_value {
1335 if item_val.as_deref() == Some(dv.as_str()) {
1336 *value = dv.clone();
1337 } else {
1338 *value = String::new();
1339 }
1340 } else {
1341 *value = String::new();
1344 }
1345 }
1346 }
1347 return;
1348 }
1349
1350 if let FormNodeType::Field { ref mut value } = tree.get_mut(node_id).node_type {
1352 if let Some(dv) = lookup_bound_text(data_root, data_node, &meta, &name) {
1353 *value = dv;
1354 }
1355 return; }
1357
1358 let bound_nodes = resolve_bound_nodes(data_root, data_node, &meta, &name);
1362 let effective_data = bound_nodes.first().copied().unwrap_or(*data_node);
1363
1364 let occur = tree.get(node_id).occur.clone();
1366 let max_instances = occur.max.map(|max| max as usize).unwrap_or(usize::MAX);
1367 if max_instances > 1 && !meta.data_bind_none {
1368 let data_instances: Vec<_> = if let Some(bind_ref) = meta.data_bind_ref.as_deref() {
1369 resolve_bind_nodes(data_root, data_node, bind_ref)
1370 } else if !name.is_empty() {
1371 data_node
1372 .children()
1373 .filter(|child| child.is_element() && child.tag_name().name() == name)
1374 .collect()
1375 } else {
1376 Vec::new()
1377 };
1378
1379 if !data_instances.is_empty() {
1380 tree.get_mut(node_id).occur.initial = 1;
1381 }
1382
1383 if data_instances.len() > 1 {
1384 bind_data_children(tree, node_id, &children, data_root, &data_instances[0]);
1386
1387 let parent_id = tree
1389 .nodes
1390 .iter()
1391 .enumerate()
1392 .find(|(_, n)| n.children.contains(&node_id))
1393 .map(|(i, _)| FormNodeId(i));
1394 let mut insert_pos = parent_id.and_then(|pid| {
1395 tree.get(pid)
1396 .children
1397 .iter()
1398 .position(|&c| c == node_id)
1399 .map(|pos| (pid, pos + 1))
1400 });
1401
1402 for data_inst in &data_instances[1..data_instances.len().min(max_instances)] {
1403 let cloned_id = clone_subtree(tree, node_id);
1404 tree.get_mut(cloned_id).occur.initial = 1;
1405 bind_data_children(
1406 tree,
1407 cloned_id,
1408 &tree.get(cloned_id).children.clone(),
1409 data_root,
1410 data_inst,
1411 );
1412 if let Some((pid, pos)) = insert_pos.as_mut() {
1414 let parent = tree.get_mut(*pid);
1415 parent.children.insert(*pos, cloned_id);
1416 *pos += 1;
1417 }
1418 }
1419 return;
1420 }
1421 }
1422
1423 bind_data_children(tree, node_id, &children, data_root, &effective_data);
1424}
1425
1426fn bind_data_children(
1428 tree: &mut FormTree,
1429 _parent_id: FormNodeId,
1430 children: &[FormNodeId],
1431 data_root: &Node<'_, '_>,
1432 data_node: &Node<'_, '_>,
1433) {
1434 for &child_id in children {
1435 bind_data(tree, child_id, data_root, data_node);
1436 }
1437}
1438
1439fn clone_subtree(tree: &mut FormTree, source_id: FormNodeId) -> FormNodeId {
1441 let source = tree.get(source_id).clone();
1442 let source_meta = tree.meta(source_id).clone();
1443
1444 let new_children: Vec<FormNodeId> = source
1446 .children
1447 .iter()
1448 .map(|&child_id| clone_subtree(tree, child_id))
1449 .collect();
1450
1451 let mut new_node = source;
1452 new_node.children = new_children;
1453 tree.add_node_with_meta(new_node, source_meta)
1454}
1455
1456fn lookup_data_text(data_node: &Node<'_, '_>, name: &str) -> Option<String> {
1458 let child = find_child_element_by_name(data_node, name)?;
1459 child.text().map(|s| s.to_string())
1460}
1461
1462fn lookup_bound_text(
1463 data_root: &Node<'_, '_>,
1464 data_node: &Node<'_, '_>,
1465 meta: &FormNodeMeta,
1466 fallback_name: &str,
1467) -> Option<String> {
1468 if meta.data_bind_none {
1469 return None;
1470 }
1471
1472 if let Some(bind_ref) = meta.data_bind_ref.as_deref() {
1473 return resolve_bind_nodes(data_root, data_node, bind_ref)
1474 .into_iter()
1475 .next()
1476 .and_then(|node| node.text().map(|s| s.to_string()));
1477 }
1478
1479 if fallback_name.is_empty() {
1480 None
1481 } else {
1482 lookup_data_text(data_node, fallback_name)
1483 }
1484}
1485
1486fn resolve_bound_nodes<'a, 'input>(
1487 data_root: &Node<'a, 'input>,
1488 data_node: &Node<'a, 'input>,
1489 meta: &FormNodeMeta,
1490 fallback_name: &str,
1491) -> Vec<Node<'a, 'input>> {
1492 if meta.data_bind_none {
1493 return Vec::new();
1494 }
1495
1496 if let Some(bind_ref) = meta.data_bind_ref.as_deref() {
1497 return resolve_bind_nodes(data_root, data_node, bind_ref);
1498 }
1499
1500 if fallback_name.is_empty() {
1501 Vec::new()
1502 } else {
1503 find_child_element_by_name(data_node, fallback_name)
1504 .into_iter()
1505 .collect()
1506 }
1507}
1508
1509fn resolve_bind_nodes<'a, 'input>(
1510 data_root: &Node<'a, 'input>,
1511 data_node: &Node<'a, 'input>,
1512 bind_ref: &str,
1513) -> Vec<Node<'a, 'input>> {
1514 let mut path = bind_ref.trim();
1515 if path.is_empty() {
1516 return Vec::new();
1517 }
1518
1519 let mut current = if let Some(rest) = path.strip_prefix("$record.") {
1520 path = rest;
1521 vec![*data_root]
1522 } else if path == "$record" {
1523 return vec![*data_root];
1524 } else if let Some(rest) = path.strip_prefix("$.") {
1525 path = rest;
1526 vec![*data_node]
1527 } else {
1528 vec![*data_node]
1529 };
1530
1531 let segments: Vec<&str> = path
1532 .split('.')
1533 .map(str::trim)
1534 .filter(|segment| !segment.is_empty())
1535 .collect();
1536 if segments.is_empty() {
1537 return current;
1538 }
1539
1540 for segment in segments {
1541 let (name, selector) = parse_bind_segment(segment);
1542 if name.is_empty() {
1543 continue;
1544 }
1545
1546 let mut next = Vec::new();
1547 for node in current {
1548 let matches: Vec<Node<'a, 'input>> = node
1549 .children()
1550 .filter(|child| child.is_element() && child.tag_name().name() == name)
1551 .collect();
1552 match selector {
1553 BindSelector::First => {
1554 if let Some(first) = matches.into_iter().next() {
1555 next.push(first);
1556 }
1557 }
1558 BindSelector::All => next.extend(matches),
1559 BindSelector::Index(idx) => {
1560 if let Some(found) = matches.into_iter().nth(idx) {
1561 next.push(found);
1562 }
1563 }
1564 }
1565 }
1566 current = next;
1567 if current.is_empty() {
1568 break;
1569 }
1570 }
1571
1572 current
1573}
1574
1575#[derive(Clone, Copy)]
1576enum BindSelector {
1577 First,
1578 All,
1579 Index(usize),
1580}
1581
1582fn parse_bind_segment(segment: &str) -> (&str, BindSelector) {
1583 let Some(start) = segment.find('[') else {
1584 return (segment, BindSelector::First);
1585 };
1586 let name = &segment[..start];
1587 let index = segment[start + 1..]
1588 .strip_suffix(']')
1589 .unwrap_or_default()
1590 .trim();
1591 match index {
1592 "*" => (name, BindSelector::All),
1593 "" => (name, BindSelector::First),
1594 _ => index
1595 .parse::<usize>()
1596 .map(|idx| (name, BindSelector::Index(idx)))
1597 .unwrap_or((name, BindSelector::First)),
1598 }
1599}
1600
1601fn find_child_element_by_name<'a, 'input>(
1603 node: &Node<'a, 'input>,
1604 name: &str,
1605) -> Option<Node<'a, 'input>> {
1606 node.children()
1607 .filter(|n| n.is_element())
1608 .find(|n| n.tag_name().name() == name)
1609}
1610
1611fn add_children(
1625 tree: &mut FormTree,
1626 node: &mut FormNode,
1627 elem: Node<'_, '_>,
1628) -> std::result::Result<(bool, Option<String>), crate::error::XfaError> {
1629 let mut pending_break = false;
1630 let mut pending_break_target = None;
1631 let mut pending_ca_break = false;
1632 for child in elem.children().filter(|n| n.is_element()) {
1633 let tag = child.tag_name().name();
1634 match tag {
1635 "subform" | "field" | "draw" | "pageSet" | "pageArea" | "exclGroup" | "area" => {
1637 let (child_id, (trailing_break, trailing_target)) = parse_node(tree, child, false)?;
1638 if pending_break {
1639 let meta = tree.meta_mut(child_id);
1640 meta.page_break_before = true;
1641 if meta.break_target.is_none() {
1642 meta.break_target = pending_break_target.take();
1643 }
1644 pending_break = false;
1645 }
1646 if pending_ca_break {
1647 tree.meta_mut(child_id).content_area_break = true;
1648 pending_ca_break = false;
1649 }
1650 node.children.push(child_id);
1651 if trailing_break {
1652 pending_break = true;
1653 pending_break_target = trailing_target;
1654 }
1655 }
1656 "breakBefore" => {
1657 let target_type = attr(child, "targetType");
1658 if target_type == Some("pageArea") {
1659 pending_break = true;
1660 pending_break_target = attr(child, "target").map(|s| s.to_string());
1661 } else if target_type == Some("contentArea") {
1662 pending_ca_break = true;
1663 }
1664 }
1665 "break"
1667 if attr(child, "before") == Some("pageArea")
1668 && attr(child, "targetType") == Some("pageArea") =>
1669 {
1670 pending_break = true;
1671 pending_break_target = attr(child, "target").map(|s| s.to_string());
1672 }
1673 "caption" | "value" | "ui" | "font" | "border" | "margin" | "para" | "format"
1678 | "items" | "medium" | "contentArea" | "desc" | "occur" | "event" | "bind"
1679 | "calculate" | "validate" | "assist" | "toolTip" | "fill" | "edge" | "corner"
1680 | "linear" | "radial" | "pattern" | "stipple" | "color" | "extras" | "traversal"
1681 | "proto" | "overflow" => {
1682 }
1684 _ => {
1685 }
1687 }
1688 }
1689 Ok((pending_break, pending_break_target))
1690}
1691
1692fn blank_node(tag: &str) -> FormNode {
1693 FormNode {
1694 name: tag.to_string(),
1695 node_type: FormNodeType::Subform,
1696 box_model: BoxModel {
1697 max_width: f64::MAX,
1698 max_height: f64::MAX,
1699 ..Default::default()
1700 },
1701 layout: LayoutStrategy::TopToBottom,
1702 children: Vec::new(),
1703 occur: Occur::once(),
1704 font: FontMetrics::default(),
1705 calculate: None,
1706 validate: None,
1707 column_widths: Vec::new(),
1708 col_span: 1,
1709 }
1710}
1711
1712fn parse_layout_attr(elem: Node<'_, '_>) -> LayoutStrategy {
1718 match attr(elem, "layout").unwrap_or("") {
1719 "tb" => LayoutStrategy::TopToBottom,
1720 "lr-tb" => LayoutStrategy::LeftToRightTB,
1721 "rl-tb" => LayoutStrategy::RightToLeftTB,
1722 "table" => LayoutStrategy::Table,
1723 "row" => LayoutStrategy::Row,
1724 "paginate" => LayoutStrategy::TopToBottom, "position" => LayoutStrategy::Positioned,
1726 _ => LayoutStrategy::Positioned,
1727 }
1728}
1729
1730fn parse_anchor_type(elem: Node<'_, '_>) -> AnchorType {
1735 match attr(elem, "anchorType").unwrap_or("") {
1736 "topCenter" => AnchorType::TopCenter,
1737 "topRight" => AnchorType::TopRight,
1738 "middleLeft" => AnchorType::MiddleLeft,
1739 "middleCenter" => AnchorType::MiddleCenter,
1740 "middleRight" => AnchorType::MiddleRight,
1741 "bottomLeft" => AnchorType::BottomLeft,
1742 "bottomCenter" => AnchorType::BottomCenter,
1743 "bottomRight" => AnchorType::BottomRight,
1744 _ => AnchorType::TopLeft,
1745 }
1746}
1747
1748fn parse_box_model(elem: Node<'_, '_>) -> BoxModel {
1757 let w = attr(elem, "w").and_then(parse_dim);
1758 let h = attr(elem, "h").and_then(parse_dim);
1759 let x = attr(elem, "x").and_then(parse_dim).unwrap_or(0.0);
1760 let y = attr(elem, "y").and_then(parse_dim).unwrap_or(0.0);
1761 let min_h = attr(elem, "minH").and_then(parse_dim).unwrap_or(0.0);
1762 let min_w = attr(elem, "minW").and_then(parse_dim).unwrap_or(0.0);
1763 let max_h = attr(elem, "maxH").and_then(parse_dim).unwrap_or(f64::MAX);
1764 let max_w = attr(elem, "maxW").and_then(parse_dim).unwrap_or(f64::MAX);
1765 let margins = parse_margin(elem);
1766
1767 BoxModel {
1768 width: w,
1769 height: h,
1770 x,
1771 y,
1772 margins,
1773 min_width: min_w,
1774 max_width: max_w,
1775 min_height: min_h,
1776 max_height: max_h,
1777 ..Default::default()
1778 }
1779}
1780
1781fn parse_margin(elem: Node<'_, '_>) -> Insets {
1782 if let Some(margin) = find_first_child_by_name(elem, "margin") {
1783 Insets {
1784 top: attr(margin, "topInset").and_then(parse_dim).unwrap_or(0.0),
1785 bottom: attr(margin, "bottomInset")
1786 .and_then(parse_dim)
1787 .unwrap_or(0.0),
1788 left: attr(margin, "leftInset").and_then(parse_dim).unwrap_or(0.0),
1789 right: attr(margin, "rightInset")
1790 .and_then(parse_dim)
1791 .unwrap_or(0.0),
1792 }
1793 } else {
1794 Insets::default()
1795 }
1796}
1797
1798fn parse_dim(s: &str) -> Option<f64> {
1800 if s.trim().parse::<f64>().is_ok() {
1802 return Measurement::parse(&format!("{s}in")).map(|m| m.to_points());
1803 }
1804 Measurement::parse(s).map(|m| m.to_points())
1805}
1806
1807fn parse_occur(elem: Node<'_, '_>) -> Occur {
1812 if let Some(occur) = find_first_child_by_name(elem, "occur") {
1813 let min: u32 = attr(occur, "min").and_then(|s| s.parse().ok()).unwrap_or(1);
1814 let max: Option<u32> = attr(occur, "max")
1815 .map(|s| if s == "-1" { None } else { s.parse().ok() })
1816 .unwrap_or(Some(1));
1817 let initial: u32 = attr(occur, "initial")
1821 .and_then(|s| s.parse().ok())
1822 .unwrap_or(min.max(1));
1823 Occur::repeating(min, max, initial)
1824 } else {
1825 Occur::once()
1826 }
1827}
1828
1829fn parse_col_span(elem: Node<'_, '_>) -> i32 {
1831 attr(elem, "colSpan")
1832 .and_then(|s| s.parse().ok())
1833 .unwrap_or(1)
1834}
1835
1836fn read_medium(page_area: Node<'_, '_>) -> (f64, f64) {
1838 if let Some(m) = find_first_child_by_name(page_area, "medium") {
1839 let short = attr(m, "short").and_then(parse_dim).unwrap_or(612.0);
1841 let long_ = attr(m, "long").and_then(parse_dim).unwrap_or(792.0);
1842 (short, long_)
1843 } else {
1844 (612.0, 792.0)
1845 }
1846}
1847
1848fn read_content_areas(
1850 page_area: Node<'_, '_>,
1851 page_width: f64,
1852 page_height: f64,
1853) -> Vec<ContentArea> {
1854 let mut areas = Vec::new();
1855 for child in page_area.children().filter(|n| n.is_element()) {
1856 if child.tag_name().name() == "contentArea" {
1857 let x = attr(child, "x").and_then(parse_dim).unwrap_or(0.0);
1862 let y = attr(child, "y").and_then(parse_dim).unwrap_or(0.0);
1863 let w = attr(child, "w").and_then(parse_dim).unwrap_or(page_width);
1868 let h = attr(child, "h").and_then(parse_dim).unwrap_or(page_height);
1869 areas.push(ContentArea {
1870 name: attr(child, "name").unwrap_or("").to_string(),
1871 x,
1872 y,
1873 width: w,
1874 height: h,
1875 leader: None,
1876 trailer: None,
1877 });
1878 }
1879 }
1880 if areas.is_empty() {
1881 areas.push(ContentArea {
1882 name: String::new(),
1883 x: 0.0,
1884 y: 0.0,
1885 width: page_width,
1886 height: page_height,
1887 leader: None,
1888 trailer: None,
1889 });
1890 }
1891 areas
1892}
1893
1894fn extract_value_image(elem: Node<'_, '_>) -> Option<(Vec<u8>, String)> {
1910 let value = find_first_child_by_name(elem, "value")?;
1911 let image = find_first_child_by_name(value, "image")?;
1912 let content_type = attr(image, "contentType")
1913 .unwrap_or("image/png")
1914 .to_string();
1915 let data = image.text().unwrap_or_default();
1916 let decoded = base64_decode(data);
1917
1918 if decoded.starts_with(b"BM") || content_type == "image/bmp" {
1921 if let Some(png_data) = bmp_to_png(&decoded) {
1922 return Some((png_data, "image/png".to_string()));
1923 }
1924 log::warn!("BMP to PNG conversion failed; skipping image");
1926 return None;
1927 }
1928
1929 Some((decoded, content_type))
1930}
1931
1932fn bmp_to_png(bmp_data: &[u8]) -> Option<Vec<u8>> {
1934 let img = image::load_from_memory_with_format(bmp_data, image::ImageFormat::Bmp).ok()?;
1935 let mut buf = Vec::new();
1936 img.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)
1937 .ok()?;
1938 Some(buf)
1939}
1940
1941fn extract_value_text(elem: Node<'_, '_>) -> Option<String> {
1942 let value = find_first_child_by_name(elem, "value")?;
1943 for tag in &["text", "float", "integer", "date", "dateTime", "decimal"] {
1945 if let Some(child) = find_first_child_by_name(value, tag) {
1946 let text = child.text().unwrap_or("");
1947 let trimmed = text.trim_start_matches(|c: char| c.is_whitespace() && c != '\n');
1948 let trimmed = trimmed.trim_end_matches(|c: char| c.is_whitespace() && c != '\n');
1949 if !trimmed.is_empty() {
1950 return Some(trimmed.to_string());
1951 }
1952 }
1953 }
1954 if let Some(ex) = find_first_child_by_name(value, "exData") {
1957 let text = extract_text_from_descendants(ex);
1958 if !text.is_empty() {
1959 return Some(text);
1960 }
1961 }
1962 None
1963}
1964
1965fn extract_draw_content(elem: Node<'_, '_>) -> Option<DrawContent> {
1969 let value = find_first_child_by_name(elem, "value")?;
1970
1971 if let Some(line) = find_first_child_by_name(value, "line") {
1972 let x1 = attr_as_f64(line, "x1").unwrap_or(0.0);
1973 let y1 = attr_as_f64(line, "y1").unwrap_or(0.0);
1974 let x2 = attr_as_f64(line, "x2").unwrap_or(0.0);
1975 let y2 = attr_as_f64(line, "y2").unwrap_or(0.0);
1976 return Some(DrawContent::Line { x1, y1, x2, y2 });
1977 }
1978
1979 if let Some(rect) = find_first_child_by_name(value, "rectangle") {
1980 let x = attr_as_f64(rect, "x").unwrap_or(0.0);
1981 let y = attr_as_f64(rect, "y").unwrap_or(0.0);
1982 let w = attr_as_f64(rect, "w").unwrap_or(attr_as_f64(rect, "width").unwrap_or(0.0));
1983 let h = attr_as_f64(rect, "h").unwrap_or(attr_as_f64(rect, "height").unwrap_or(0.0));
1984 let radius =
1985 attr_as_f64(rect, "r").unwrap_or(attr_as_f64(rect, "cornerRadius").unwrap_or(0.0));
1986 return Some(DrawContent::Rectangle { x, y, w, h, radius });
1987 }
1988
1989 if let Some(arc) = find_first_child_by_name(value, "arc") {
1990 let x = attr_as_f64(arc, "x").unwrap_or(0.0);
1991 let y = attr_as_f64(arc, "y").unwrap_or(0.0);
1992 let w = attr_as_f64(arc, "w").unwrap_or(attr_as_f64(arc, "width").unwrap_or(0.0));
1993 let h = attr_as_f64(arc, "h").unwrap_or(attr_as_f64(arc, "height").unwrap_or(0.0));
1994 let start_angle = attr_as_f64(arc, "startAngle").unwrap_or(0.0);
1995 let sweep_angle = attr_as_f64(arc, "sweepAngle").unwrap_or(0.0);
1996 return Some(DrawContent::Arc {
1997 x,
1998 y,
1999 w,
2000 h,
2001 start_angle,
2002 sweep_angle,
2003 });
2004 }
2005
2006 None
2007}
2008
2009fn extract_text_from_descendants(node: Node<'_, '_>) -> String {
2014 let block_tags = [
2015 "p", "div", "h1", "h2", "h3", "h4", "h5", "h6", "li", "tr", "br",
2016 ];
2017 let mut result = String::new();
2018 let mut last_was_block = false;
2019
2020 for desc in node.descendants() {
2021 let is_block = desc.is_element() && block_tags.contains(&desc.tag_name().name());
2022
2023 if is_block && !result.is_empty() {
2024 result.push('\n');
2025 last_was_block = true;
2026 }
2027
2028 if desc.is_text() {
2029 if let Some(t) = desc.text() {
2030 let t = t.trim();
2031 if !t.is_empty() {
2032 if last_was_block || result.is_empty() {
2033 result.push_str(t);
2034 } else {
2035 result.push(' ');
2036 result.push_str(t);
2037 }
2038 last_was_block = false;
2039 }
2040 }
2041 }
2042 }
2043
2044 result.trim().to_string()
2045}
2046
2047fn base64_decode(input: &str) -> Vec<u8> {
2048 use base64::Engine;
2049 base64::engine::general_purpose::STANDARD
2050 .decode(input.trim())
2051 .unwrap_or_default()
2052}
2053
2054fn extract_exdata_font_size(elem: Node<'_, '_>) -> Option<f64> {
2060 let value = find_first_child_by_name(elem, "value")?;
2061 let ex = find_first_child_by_name(value, "exData")?;
2062 for desc in ex.descendants() {
2064 if !desc.is_element() {
2065 continue;
2066 }
2067 let style = desc
2068 .attribute("style")
2069 .or_else(|| desc.attribute("Style"))?;
2070 for part in style.split(';') {
2072 let part = part.trim();
2073 if let Some(val) = part
2074 .strip_prefix("font-size:")
2075 .or_else(|| part.strip_prefix("font-size :"))
2076 {
2077 let val = val.trim();
2078 if let Some(pt) = val.strip_suffix("pt") {
2079 if let Ok(size) = pt.trim().parse::<f64>() {
2080 if size > 0.0 {
2081 return Some(size);
2082 }
2083 }
2084 }
2085 }
2086 }
2087 }
2088 None
2089}
2090
2091fn parse_caption(elem: Node<'_, '_>) -> Option<Caption> {
2098 let cap_elem = find_first_child_by_name(elem, "caption")?;
2099 if is_hidden(cap_elem) {
2101 return None;
2102 }
2103 let text = extract_value_text(cap_elem)?;
2104 if text.is_empty() {
2105 return None;
2106 }
2107 let placement = match attr(cap_elem, "placement") {
2108 Some("right") => CaptionPlacement::Right,
2109 Some("top") => CaptionPlacement::Top,
2110 Some("bottom") => CaptionPlacement::Bottom,
2111 Some("inline") => CaptionPlacement::Inline,
2112 _ => CaptionPlacement::Left,
2113 };
2114 let reserve = attr(cap_elem, "reserve")
2115 .and_then(Measurement::parse)
2116 .map(|m| m.to_points());
2117 Some(Caption {
2118 placement,
2119 reserve,
2120 text,
2121 font_family: None,
2122 font_size: None,
2123 })
2124}
2125
2126fn attr<'a>(elem: Node<'a, '_>, name: &str) -> Option<&'a str> {
2128 elem.attributes()
2129 .find(|a| a.name() == name)
2130 .map(|a| a.value())
2131}
2132
2133fn attr_as_f64(elem: Node<'_, '_>, name: &str) -> Option<f64> {
2134 attr(elem, name)?.parse().ok()
2135}
2136
2137fn find_first_child_by_name<'a, 'input>(
2139 elem: Node<'a, 'input>,
2140 name: &str,
2141) -> Option<Node<'a, 'input>> {
2142 elem.children()
2143 .filter(|n| n.is_element())
2144 .find(|n| n.tag_name().name() == name)
2145}
2146
2147#[cfg(test)]
2152mod tests {
2153 use super::*;
2154
2155 const SIMPLE_TEMPLATE: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
2156<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
2157<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2158 <subform name="form1" layout="paginate">
2159 <pageSet>
2160 <pageArea name="Page1">
2161 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2162 <medium stock="default" short="8.5in" long="11in"/>
2163 </pageArea>
2164 </pageSet>
2165 <subform name="section" layout="tb" w="7.5in">
2166 <field name="firstName" w="3.5in" h="0.3in">
2167 <caption><value><text>First Name</text></value></caption>
2168 <ui><textEdit/></ui>
2169 <value><text/></value>
2170 </field>
2171 <field name="lastName" w="3.5in" h="0.3in">
2172 <caption><value><text>Last Name</text></value></caption>
2173 <ui><textEdit/></ui>
2174 <value><text>Default</text></value>
2175 </field>
2176 </subform>
2177 </subform>
2178</template>
2179</xdp:xdp>"#;
2180
2181 #[test]
2182 fn parse_simple_form() {
2183 let (tree, root_id) = parse_template(SIMPLE_TEMPLATE, None).unwrap();
2184 let root = tree.get(root_id);
2185 assert!(!root.children.is_empty(), "root has no children");
2187 }
2188
2189 #[test]
2190 fn field_with_default_value() {
2191 let (tree, root_id) = parse_template(SIMPLE_TEMPLATE, None).unwrap();
2192 let found = find_node_by_name(&tree, root_id, "lastName");
2194 assert!(found.is_some(), "lastName field not found");
2195 if let Some(n) = found {
2196 match &n.node_type {
2197 FormNodeType::Field { value } => assert_eq!(value, "Default"),
2198 other => panic!("expected Field, got {other:?}"),
2199 }
2200 }
2201 }
2202
2203 #[test]
2204 fn unlimited_occur_expands_all_dataset_instances_in_order() {
2205 let template = r#"<?xml version="1.0" encoding="UTF-8"?>
2206<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2207 <subform name="form1" layout="paginate">
2208 <pageSet>
2209 <pageArea name="Page1">
2210 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2211 </pageArea>
2212 </pageSet>
2213 <subform name="items" layout="tb" w="7in">
2214 <subform name="row" layout="tb" w="7in">
2215 <occur min="0" max="-1"/>
2216 <field name="value" w="2in" h="0.3in">
2217 <ui><textEdit/></ui>
2218 <value><text/></value>
2219 </field>
2220 </subform>
2221 </subform>
2222 </subform>
2223</template>"#;
2224 let datasets = r#"<?xml version="1.0" encoding="UTF-8"?>
2225<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
2226 <xfa:data>
2227 <form1>
2228 <items>
2229 <row><value>A</value></row>
2230 <row><value>B</value></row>
2231 <row><value>C</value></row>
2232 </items>
2233 </form1>
2234 </xfa:data>
2235</xfa:datasets>"#;
2236
2237 let (tree, root_id) = parse_template(template, Some(datasets)).unwrap();
2238 let items_id = find_node_id_by_name(&tree, root_id, "items").unwrap();
2239 let row_ids = tree.get(items_id).children.clone();
2240
2241 assert_eq!(row_ids.len(), 3);
2242 assert!(row_ids
2243 .iter()
2244 .all(|&row_id| tree.get(row_id).occur.count() == 1));
2245
2246 let values: Vec<String> = row_ids
2247 .iter()
2248 .map(|&row_id| {
2249 let field_id = tree.get(row_id).children[0];
2250 match &tree.get(field_id).node_type {
2251 FormNodeType::Field { value } => value.clone(),
2252 other => panic!("expected Field, got {other:?}"),
2253 }
2254 })
2255 .collect();
2256
2257 assert_eq!(values, vec!["A", "B", "C"]);
2258 }
2259
2260 #[test]
2261 fn explicit_dataref_bind_repeats_subform_instances() {
2262 let template = r#"<?xml version="1.0" encoding="UTF-8"?>
2263<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2264 <subform name="form1" layout="paginate">
2265 <pageSet>
2266 <pageArea name="Page1">
2267 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2268 </pageArea>
2269 </pageSet>
2270 <subform name="items" layout="tb" w="7in">
2271 <subform name="entryRow" layout="tb" w="7in">
2272 <occur min="0" max="-1"/>
2273 <bind match="dataRef" ref="$.item[*]"/>
2274 <field name="label" w="2in" h="0.3in">
2275 <bind match="dataRef" ref="$.value"/>
2276 <ui><textEdit/></ui>
2277 <value><text/></value>
2278 </field>
2279 </subform>
2280 </subform>
2281 </subform>
2282</template>"#;
2283 let datasets = r#"<?xml version="1.0" encoding="UTF-8"?>
2284<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
2285 <xfa:data>
2286 <form1>
2287 <items>
2288 <item><value>One</value></item>
2289 <item><value>Two</value></item>
2290 <item><value>Three</value></item>
2291 </items>
2292 </form1>
2293 </xfa:data>
2294</xfa:datasets>"#;
2295
2296 let (tree, root_id) = parse_template(template, Some(datasets)).unwrap();
2297 let items_id = find_node_id_by_name(&tree, root_id, "items").unwrap();
2298 let row_ids = tree.get(items_id).children.clone();
2299
2300 assert_eq!(row_ids.len(), 3);
2301 assert!(row_ids
2302 .iter()
2303 .all(|&row_id| tree.get(row_id).occur.count() == 1));
2304
2305 let values: Vec<String> = row_ids
2306 .iter()
2307 .map(|&row_id| {
2308 let field_id = tree.get(row_id).children[0];
2309 match &tree.get(field_id).node_type {
2310 FormNodeType::Field { value } => value.clone(),
2311 other => panic!("expected Field, got {other:?}"),
2312 }
2313 })
2314 .collect();
2315
2316 assert_eq!(values, vec!["One", "Two", "Three"]);
2317 }
2318
2319 #[test]
2320 fn dimension_parsing() {
2321 assert!((parse_dim("0.5in").unwrap() - 36.0).abs() < 0.01);
2322 assert!((parse_dim("72pt").unwrap() - 72.0).abs() < 0.01);
2323 assert!((parse_dim("1in").unwrap() - 72.0).abs() < 0.01);
2324 assert!((parse_dim("8.5in").unwrap() - 612.0).abs() < 0.1);
2325 assert!((parse_dim("11in").unwrap() - 792.0).abs() < 0.1);
2326 }
2327
2328 #[test]
2329 fn layout_attr_parsing() {
2330 assert_eq!(parse_layout_str("tb"), LayoutStrategy::TopToBottom);
2331 assert_eq!(parse_layout_str("lr-tb"), LayoutStrategy::LeftToRightTB);
2332 assert_eq!(parse_layout_str("paginate"), LayoutStrategy::TopToBottom);
2333 assert_eq!(parse_layout_str("position"), LayoutStrategy::Positioned);
2334 }
2335
2336 fn parse_layout_str(s: &str) -> LayoutStrategy {
2337 let xml = format!(
2338 r#"<?xml version="1.0"?><template xmlns="http://www.xfa.org/schema/xfa-template/3.3/"><subform layout="{s}"/></template>"#
2339 );
2340 let doc = roxmltree::Document::parse(&xml).unwrap();
2341 let root = doc.root_element();
2342 let subform = root.children().find(|n| n.is_element()).unwrap();
2343 parse_layout_attr(subform)
2344 }
2345
2346 fn find_node_by_name<'a>(
2347 tree: &'a FormTree,
2348 id: FormNodeId,
2349 name: &str,
2350 ) -> Option<&'a FormNode> {
2351 let node = tree.get(id);
2352 if node.name == name {
2353 return Some(node);
2354 }
2355 for &child_id in &node.children {
2356 if let Some(found) = find_node_by_name(tree, child_id, name) {
2357 return Some(found);
2358 }
2359 }
2360 None
2361 }
2362
2363 fn find_node_id_by_name(tree: &FormTree, id: FormNodeId, name: &str) -> Option<FormNodeId> {
2364 if tree.get(id).name == name {
2365 return Some(id);
2366 }
2367 for &child_id in &tree.get(id).children.clone() {
2368 if let Some(found) = find_node_id_by_name(tree, child_id, name) {
2369 return Some(found);
2370 }
2371 }
2372 None
2373 }
2374
2375 #[test]
2378 fn draw_exdata_html_text_extracted() {
2379 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
2380<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2381 <subform name="form1" layout="paginate">
2382 <pageSet>
2383 <pageArea name="Page1">
2384 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2385 <medium stock="default" short="8.5in" long="11in"/>
2386 </pageArea>
2387 </pageSet>
2388 <subform name="body" layout="tb" w="7.5in">
2389 <draw name="instructions" w="7in" h="1in">
2390 <value>
2391 <exData contentType="text/html">
2392 <body xmlns="http://www.w3.org/1999/xhtml">
2393 <p>Do <span>not</span> file this form.</p>
2394 </body>
2395 </exData>
2396 </value>
2397 </draw>
2398 </subform>
2399 </subform>
2400</template>"#;
2401 let (tree, root_id) = parse_template(xml, None).unwrap();
2402 let node =
2403 find_node_by_name(&tree, root_id, "instructions").expect("instructions draw not found");
2404 match &node.node_type {
2405 FormNodeType::Draw(DrawContent::Text(content)) => {
2406 assert!(
2407 content.contains("not") && content.contains("file"),
2408 "expected HTML text extracted, got: {content:?}"
2409 );
2410 }
2411 other => panic!("expected Draw, got {other:?}"),
2412 }
2413 }
2414
2415 #[test]
2419 fn hidden_elements_have_empty_content() {
2420 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
2421<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2422 <subform name="form1" layout="paginate">
2423 <pageSet>
2424 <pageArea name="Page1">
2425 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2426 <medium stock="default" short="8.5in" long="11in"/>
2427 </pageArea>
2428 </pageSet>
2429 <subform name="body" layout="tb" w="7.5in">
2430 <draw name="visible_draw" w="7in" h="0.5in">
2431 <value><text>Visible text</text></value>
2432 </draw>
2433 <draw name="hidden_draw" w="7in" h="0.5in" presence="hidden">
2434 <value><text>DRAFT</text></value>
2435 </draw>
2436 <field name="hidden_field" w="3in" h="0.3in" presence="hidden">
2437 <value><text>secret</text></value>
2438 </field>
2439 </subform>
2440 </subform>
2441</template>"#;
2442 let (tree, root_id) = parse_template(xml, None).unwrap();
2443
2444 let visible = find_node_by_name(&tree, root_id, "visible_draw").unwrap();
2446 match &visible.node_type {
2447 FormNodeType::Draw(DrawContent::Text(content)) => assert_eq!(content, "Visible text"),
2448 other => panic!("expected Draw, got {other:?}"),
2449 }
2450
2451 let hidden_draw = find_node_by_name(&tree, root_id, "hidden_draw").unwrap();
2454 match &hidden_draw.node_type {
2455 FormNodeType::Draw(DrawContent::Text(content)) => assert_eq!(content, "DRAFT"),
2456 other => panic!("expected Draw, got {other:?}"),
2457 }
2458 let hidden_draw_id = find_node_id_by_name(&tree, root_id, "hidden_draw").unwrap();
2459 assert!(tree.meta(hidden_draw_id).presence.is_not_visible());
2460
2461 let hidden_field = find_node_by_name(&tree, root_id, "hidden_field").unwrap();
2464 match &hidden_field.node_type {
2465 FormNodeType::Field { value } => assert_eq!(value, "secret"),
2466 other => panic!("expected Field, got {other:?}"),
2467 }
2468 let hidden_field_id = find_node_id_by_name(&tree, root_id, "hidden_field").unwrap();
2469 assert!(tree.meta(hidden_field_id).presence.is_not_visible());
2470 }
2471
2472 #[test]
2476 fn font_size_bare_number_is_points() {
2477 assert_eq!(parse_font_size("10"), Some(10.0));
2479 assert_eq!(parse_font_size("8"), Some(8.0));
2480 assert_eq!(parse_font_size("12"), Some(12.0));
2481 assert!((parse_font_size("10pt").unwrap() - 10.0).abs() < 0.01);
2483 assert_eq!(parse_font_size("0"), None);
2485 }
2486
2487 #[test]
2491 fn para_halign_parsed_into_font_metrics() {
2492 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
2493<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2494 <subform name="form1" layout="paginate">
2495 <pageSet>
2496 <pageArea name="Page1">
2497 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2498 <medium stock="default" short="8.5in" long="11in"/>
2499 </pageArea>
2500 </pageSet>
2501 <subform name="body" layout="tb" w="7.5in">
2502 <draw name="left_draw" w="7in" h="0.5in">
2503 <value><text>Left</text></value>
2504 <para hAlign="left"/>
2505 </draw>
2506 <draw name="center_draw" w="7in" h="0.5in">
2507 <value><text>Centered</text></value>
2508 <para hAlign="center"/>
2509 </draw>
2510 <draw name="right_draw" w="7in" h="0.5in">
2511 <value><text>Right</text></value>
2512 <para hAlign="right"/>
2513 </draw>
2514 </subform>
2515 </subform>
2516</template>"#;
2517 let (tree, root_id) = parse_template(xml, None).unwrap();
2518
2519 let left = find_node_by_name(&tree, root_id, "left_draw").unwrap();
2520 assert_eq!(
2521 left.font.text_align,
2522 TextAlign::Left,
2523 "left_draw should be Left"
2524 );
2525
2526 let center = find_node_by_name(&tree, root_id, "center_draw").unwrap();
2527 assert_eq!(
2528 center.font.text_align,
2529 TextAlign::Center,
2530 "center_draw should be Center"
2531 );
2532
2533 let right = find_node_by_name(&tree, root_id, "right_draw").unwrap();
2534 assert_eq!(
2535 right.font.text_align,
2536 TextAlign::Right,
2537 "right_draw should be Right"
2538 );
2539 }
2540
2541 #[test]
2544 fn bmp_image_converted_to_png() {
2545 let bmp_bytes: [u8; 58] = [
2547 0x42, 0x4D, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00,
2563 0x00, ];
2565 use base64::Engine;
2566 let b64 = base64::engine::general_purpose::STANDARD.encode(bmp_bytes);
2567
2568 let xml = format!(
2569 r#"<?xml version="1.0" encoding="UTF-8"?>
2570<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2571 <subform name="form1" layout="paginate">
2572 <pageSet>
2573 <pageArea name="Page1">
2574 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2575 <medium stock="default" short="8.5in" long="11in"/>
2576 </pageArea>
2577 </pageSet>
2578 <subform name="body" layout="tb" w="7.5in">
2579 <draw name="barcode_img" w="2in" h="0.5in">
2580 <value>
2581 <image contentType="image/bmp">{b64}</image>
2582 </value>
2583 </draw>
2584 </subform>
2585 </subform>
2586</template>"#
2587 );
2588 let (tree, root_id) = parse_template(&xml, None).unwrap();
2589 let node = find_node_by_name(&tree, root_id, "barcode_img").expect("barcode_img not found");
2590 match &node.node_type {
2591 FormNodeType::Image { data, mime_type } => {
2592 assert_eq!(mime_type, "image/png", "BMP should be converted to PNG");
2593 assert!(
2595 data.starts_with(&[0x89, 0x50, 0x4E, 0x47]),
2596 "expected PNG magic bytes, got {:?}",
2597 &data[..4.min(data.len())]
2598 );
2599 }
2600 other => panic!("expected Image, got {other:?}"),
2601 }
2602 }
2603
2604 #[test]
2605 fn parse_percentage_values() {
2606 assert!((parse_percentage("96%").unwrap() - 0.96).abs() < 1e-10);
2607 assert!((parse_percentage("110%").unwrap() - 1.10).abs() < 1e-10);
2608 assert!((parse_percentage("100%").unwrap() - 1.0).abs() < 1e-10);
2609 assert!((parse_percentage("50%").unwrap() - 0.50).abs() < 1e-10);
2610 assert!(parse_percentage("notanumber%").is_none());
2611 assert!(parse_percentage("96").is_none()); }
2613
2614 #[test]
2615 fn parse_letter_spacing_values() {
2616 let font_size = 10.0;
2617 let v = parse_letter_spacing("-0.018em", font_size).unwrap();
2619 assert!((v - (-0.018 * 10.0)).abs() < 1e-10);
2620 let v = parse_letter_spacing("0.1em", font_size).unwrap();
2621 assert!((v - 1.0).abs() < 1e-10);
2622 assert_eq!(parse_letter_spacing("0", font_size), Some(0.0));
2624 let v = parse_letter_spacing("0.5pt", font_size).unwrap();
2626 assert!((v - 0.5).abs() < 0.01);
2627 }
2628
2629 #[test]
2630 fn parse_font_color_attr_hex6() {
2631 assert_eq!(parse_font_color_attr("#000080"), Some((0, 0, 128)));
2632 assert_eq!(parse_font_color_attr("#FF0000"), Some((255, 0, 0)));
2633 assert_eq!(parse_font_color_attr("#00ff00"), Some((0, 255, 0)));
2634 assert_eq!(parse_font_color_attr("#ABCDEF"), Some((0xAB, 0xCD, 0xEF)));
2635 }
2636
2637 #[test]
2638 fn parse_font_color_attr_hex3() {
2639 assert_eq!(parse_font_color_attr("#F00"), Some((255, 0, 0)));
2641 assert_eq!(parse_font_color_attr("#0F0"), Some((0, 255, 0)));
2642 assert_eq!(parse_font_color_attr("#00F"), Some((0, 0, 255)));
2643 assert_eq!(parse_font_color_attr("#ABC"), Some((0xAA, 0xBB, 0xCC)));
2644 }
2645
2646 #[test]
2647 fn parse_font_color_attr_decimal_csv() {
2648 assert_eq!(parse_font_color_attr("0,0,128"), Some((0, 0, 128)));
2649 assert_eq!(parse_font_color_attr("255, 128, 0"), Some((255, 128, 0)));
2650 }
2651
2652 #[test]
2653 fn parse_font_color_attr_invalid() {
2654 assert_eq!(parse_font_color_attr(""), None);
2655 assert_eq!(parse_font_color_attr("#GG0000"), None);
2656 assert_eq!(parse_font_color_attr("#12345"), None);
2657 assert_eq!(parse_font_color_attr("not_a_color"), None);
2658 }
2659
2660 #[test]
2663 fn font_color_attribute_parsed() {
2664 let xml = r##"<?xml version="1.0" encoding="UTF-8"?>
2665<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2666 <subform name="form1" layout="paginate">
2667 <pageSet>
2668 <pageArea name="Page1">
2669 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2670 <medium stock="default" short="8.5in" long="11in"/>
2671 </pageArea>
2672 </pageSet>
2673 <subform name="body" layout="tb" w="7.5in">
2674 <draw name="blue_text" w="7in" h="0.5in">
2675 <value><text>Navy blue</text></value>
2676 <font typeface="Arial" size="10pt" color="#000080"/>
2677 </draw>
2678 </subform>
2679 </subform>
2680</template>"##;
2681 let (tree, root_id) = parse_template(xml, None).unwrap();
2682 let id = find_node_id_by_name(&tree, root_id, "blue_text").unwrap();
2683 let style = &tree.meta(id).style;
2684 assert_eq!(
2685 style.text_color,
2686 Some((0, 0, 128)),
2687 "font color=#000080 should parse to (0, 0, 128)"
2688 );
2689 }
2690
2691 #[test]
2692 fn font_horizontal_scale_and_letter_spacing_parsed() {
2693 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
2694<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2695 <subform name="form1" layout="paginate">
2696 <pageSet>
2697 <pageArea name="Page1">
2698 <contentArea x="0.5in" y="0.5in" w="7.5in" h="10in"/>
2699 <medium stock="default" short="8.5in" long="11in"/>
2700 </pageArea>
2701 </pageSet>
2702 <subform name="body" layout="tb" w="7.5in">
2703 <draw name="scaled_text" w="7in" h="0.5in">
2704 <value><text>Scaled</text></value>
2705 <font typeface="Arial" size="10pt" fontHorizontalScale="96%" letterSpacing="-0.018em"/>
2706 </draw>
2707 </subform>
2708 </subform>
2709</template>"#;
2710 let (tree, root_id) = parse_template(xml, None).unwrap();
2711 let id = find_node_id_by_name(&tree, root_id, "scaled_text").unwrap();
2712 let style = &tree.meta(id).style;
2713 assert!(
2714 (style.font_horizontal_scale.unwrap() - 0.96).abs() < 1e-10,
2715 "expected 0.96, got {:?}",
2716 style.font_horizontal_scale
2717 );
2718 assert!(
2719 (style.letter_spacing_pt.unwrap() - (-0.18)).abs() < 0.01,
2720 "expected -0.18pt, got {:?}",
2721 style.letter_spacing_pt
2722 );
2723 }
2724
2725 #[test]
2726 fn choice_list_items_parsed() {
2727 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
2728<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2729 <subform name="root" layout="tb">
2730 <pageSet>
2731 <pageArea name="Page1">
2732 <contentArea w="8in" h="10in"/>
2733 </pageArea>
2734 </pageSet>
2735 <field name="country" w="3in" h="0.3in">
2736 <ui><choiceList/></ui>
2737 <value><text>US</text></value>
2738 <items>
2739 <text>United States</text>
2740 <text>United Kingdom</text>
2741 <text>Canada</text>
2742 </items>
2743 <items save="1">
2744 <text>US</text>
2745 <text>UK</text>
2746 <text>CA</text>
2747 </items>
2748 </field>
2749 <field name="single_items" w="3in" h="0.3in">
2750 <ui><choiceList/></ui>
2751 <value><text>Red</text></value>
2752 <items>
2753 <text>Red</text>
2754 <text>Green</text>
2755 <text>Blue</text>
2756 </items>
2757 </field>
2758 </subform>
2759</template>"#;
2760
2761 let (tree, _pages) = parse_template(xml, None).unwrap();
2762
2763 let country = tree
2765 .nodes
2766 .iter()
2767 .enumerate()
2768 .find(|(_, n)| n.name == "country")
2769 .map(|(i, _)| FormNodeId(i))
2770 .expect("country field not found");
2771 let meta = tree.meta(country);
2772 assert_eq!(meta.field_kind, FieldKind::Dropdown);
2773 assert_eq!(
2774 meta.display_items,
2775 vec!["United States", "United Kingdom", "Canada"]
2776 );
2777 assert_eq!(meta.save_items, vec!["US", "UK", "CA"]);
2778
2779 let single = tree
2781 .nodes
2782 .iter()
2783 .enumerate()
2784 .find(|(_, n)| n.name == "single_items")
2785 .map(|(i, _)| FormNodeId(i))
2786 .expect("single_items field not found");
2787 let meta_s = tree.meta(single);
2788 assert_eq!(meta_s.display_items, vec!["Red", "Green", "Blue"]);
2789 assert!(meta_s.save_items.is_empty());
2790 }
2791
2792 #[test]
2793 fn content_area_without_xy_defaults_to_origin() {
2794 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
2795<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2796 <subform name="root" layout="tb">
2797 <pageSet>
2798 <pageArea name="Page1">
2799 <contentArea w="8in" h="10in"/>
2800 </pageArea>
2801 </pageSet>
2802 </subform>
2803</template>"#;
2804
2805 let (tree, root_id) = parse_template(xml, None).unwrap();
2806 let page_area_id = find_node_id_by_name(&tree, root_id, "Page1").unwrap();
2807 let page_area = tree.get(page_area_id);
2808
2809 match &page_area.node_type {
2810 FormNodeType::PageArea { content_areas } => {
2811 assert_eq!(content_areas.len(), 1);
2812 assert_eq!(content_areas[0].x, 0.0);
2813 assert_eq!(content_areas[0].y, 0.0);
2814 assert!((content_areas[0].width - 576.0).abs() < 0.01);
2815 assert!((content_areas[0].height - 720.0).abs() < 0.01);
2816 }
2817 other => panic!("expected PageArea, got {other:?}"),
2818 }
2819 }
2820
2821 #[test]
2822 fn check_button_mark_parsed_into_style() {
2823 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
2824<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2825 <subform name="root" layout="tb">
2826 <pageSet>
2827 <pageArea name="Page1">
2828 <contentArea w="8in" h="10in"/>
2829 </pageArea>
2830 </pageSet>
2831 <field name="agree" w="0.3in" h="0.3in">
2832 <ui><checkButton mark="circle"/></ui>
2833 <value><text>1</text></value>
2834 </field>
2835 </subform>
2836</template>"#;
2837
2838 let (tree, root_id) = parse_template(xml, None).unwrap();
2839 let id = find_node_id_by_name(&tree, root_id, "agree").unwrap();
2840 let meta = tree.meta(id);
2841 assert_eq!(meta.field_kind, FieldKind::Checkbox);
2842 assert_eq!(meta.style.check_button_mark.as_deref(), Some("circle"));
2843 }
2844
2845 #[test]
2846 fn border_widths_parsed_from_per_edge_template_border() {
2847 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
2848<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
2849 <subform name="root" layout="tb">
2850 <pageSet>
2851 <pageArea name="Page1">
2852 <contentArea w="8in" h="10in"/>
2853 </pageArea>
2854 </pageSet>
2855 <field name="amount" w="2in" h="0.3in">
2856 <ui><textEdit/></ui>
2857 <value><text>42</text></value>
2858 <border>
2859 <edge thickness="1pt"/>
2860 <edge thickness="2pt"/>
2861 <edge thickness="3pt"/>
2862 <edge thickness="4pt"/>
2863 </border>
2864 </field>
2865 </subform>
2866</template>"#;
2867
2868 let (tree, root_id) = parse_template(xml, None).unwrap();
2869 let id = find_node_id_by_name(&tree, root_id, "amount").unwrap();
2870 let style = &tree.meta(id).style;
2871
2872 assert_eq!(style.border_width_pt, Some(1.0));
2873 assert_eq!(style.border_widths, Some([1.0, 2.0, 3.0, 4.0]));
2874 }
2875}
2876
2877#[cfg(test)]
2878mod bookend_parser_tests {
2879 use roxmltree::Document;
2885
2886 fn first_subform(xml: &str) -> String {
2887 let doc = Document::parse(xml).expect("valid XML");
2888 let subform = doc
2889 .descendants()
2890 .find(|n| n.has_tag_name("subform"))
2891 .expect("a subform");
2892 let (leader, trailer) = super::parse_bookend(subform);
2893 format!("{leader:?}/{trailer:?}")
2894 }
2895
2896 #[test]
2897 fn reads_leader_and_trailer() {
2898 let xml = r#"<template><subform name="a">
2899 <bookend leader="Head" trailer="Foot"/>
2900 </subform></template>"#;
2901 assert_eq!(first_subform(xml), r#"Some("Head")/Some("Foot")"#);
2902 }
2903
2904 #[test]
2905 fn either_of_the_two_may_be_absent() {
2906 let xml = r#"<template><subform name="a">
2907 <bookend leader="Head"/>
2908 </subform></template>"#;
2909 assert_eq!(first_subform(xml), r#"Some("Head")/None"#);
2910 }
2911
2912 #[test]
2913 fn without_a_bookend_nothing() {
2914 let xml = r#"<template><subform name="a"/></template>"#;
2915 assert_eq!(first_subform(xml), "None/None");
2916 }
2917
2918 #[test]
2922 fn overflow_is_not_a_bookend() {
2923 let xml = r#"<template><subform name="a">
2924 <overflow leader="OverflowHead" trailer="OverflowFoot"/>
2925 </subform></template>"#;
2926 assert_eq!(
2927 first_subform(xml),
2928 "None/None",
2929 "parse_bookend reads an <overflow> as though it were a <bookend>"
2930 );
2931 }
2932
2933 #[test]
2934 fn the_two_side_by_side_stay_separate() {
2935 let xml = r#"<template><subform name="a">
2936 <overflow leader="OHead" trailer="OFoot"/>
2937 <bookend leader="BHead" trailer="BFoot"/>
2938 </subform></template>"#;
2939 let doc = Document::parse(xml).unwrap();
2940 let subform = doc
2941 .descendants()
2942 .find(|n| n.has_tag_name("subform"))
2943 .unwrap();
2944 assert_eq!(
2945 super::parse_bookend(subform),
2946 (Some("BHead".into()), Some("BFoot".into()))
2947 );
2948 assert_eq!(
2949 super::parse_overflow(subform),
2950 (Some("OHead".into()), Some("OFoot".into()))
2951 );
2952 }
2953}