use crate::extract::{extract_xfa_from_bytes, XfaPackets};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XfaType {
None,
Static,
Dynamic,
}
impl std::fmt::Display for XfaType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
XfaType::None => write!(f, "None"),
XfaType::Static => write!(f, "Static"),
XfaType::Dynamic => write!(f, "Dynamic"),
}
}
}
pub fn detect_xfa_type_from_packets(packets: &XfaPackets) -> XfaType {
if packets.packets.is_empty() && packets.full_xml.is_none() {
return XfaType::None;
}
let template_xml: Option<&str> = packets.template();
let full_xml: Option<&str> = packets.full_xml.as_deref();
let search_text: &str = template_xml.or(full_xml).unwrap_or("");
if search_text.contains(r#"baseProfile="interactiveForms""#) {
XfaType::Static
} else if !search_text.is_empty() || packets.packets.iter().any(|(name, _)| name == "template")
{
XfaType::Dynamic
} else {
XfaType::Dynamic
}
}
pub fn detect_xfa_type(pdf_bytes: &[u8]) -> XfaType {
match extract_xfa_from_bytes(pdf_bytes.to_vec()) {
Ok(packets) => detect_xfa_type_from_packets(&packets),
Err(_) => XfaType::None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extract::XfaPackets;
fn packets_from_xml(xml: &str) -> XfaPackets {
let mut p = XfaPackets::default();
p.full_xml = Some(xml.to_string());
if xml.contains("<template") {
let start = xml.find("<template").unwrap();
let end = xml
.find("</template>")
.map(|i| i + "</template>".len())
.or_else(|| {
Some(xml.len())
})
.unwrap();
p.packets
.push(("template".to_string(), xml[start..end].to_string()));
}
p
}
#[test]
fn empty_packets_returns_none() {
let p = XfaPackets::default();
assert_eq!(detect_xfa_type_from_packets(&p), XfaType::None);
}
#[test]
fn static_form_detected_via_base_profile() {
let xml = r#"<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/"><template xmlns="http://www.xfa.org/schema/xfa-template/3.3/" baseProfile="interactiveForms"><subform name="root"/></template></xdp:xdp>"#;
let p = packets_from_xml(xml);
assert_eq!(detect_xfa_type_from_packets(&p), XfaType::Static);
}
#[test]
fn dynamic_form_detected_when_no_base_profile() {
let xml = r#"<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/"><template xmlns="http://www.xfa.org/schema/xfa-template/3.3/"><subform name="root"><occur min="0" max="-1"/></subform></template></xdp:xdp>"#;
let p = packets_from_xml(xml);
assert_eq!(detect_xfa_type_from_packets(&p), XfaType::Dynamic);
}
#[test]
fn xfa_type_none_on_empty_pdf_bytes() {
assert_eq!(detect_xfa_type(&[]), XfaType::None);
}
#[test]
fn xfa_type_display() {
assert_eq!(XfaType::None.to_string(), "None");
assert_eq!(XfaType::Static.to_string(), "Static");
assert_eq!(XfaType::Dynamic.to_string(), "Dynamic");
}
#[test]
fn packets_with_only_full_xml_static() {
let xml = r#"<?xml version="1.0"?><xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/"><template baseProfile="interactiveForms"><subform/></template></xdp:xdp>"#;
let mut p = XfaPackets::default();
p.full_xml = Some(xml.to_string());
assert_eq!(detect_xfa_type_from_packets(&p), XfaType::Static);
}
#[test]
fn datasets_only_packet_treated_as_dynamic() {
let mut p = XfaPackets::default();
p.packets
.push(("datasets".to_string(), "<xfa:datasets/>".to_string()));
assert_eq!(detect_xfa_type_from_packets(&p), XfaType::Dynamic);
}
}