use serde::Deserialize;
use crate::docx::model::dimension::{Dimension, Twips};
use crate::docx::model::geometry::{EdgeInsets, PartialEdgeInsets};
#[derive(Clone, Copy, Debug, Default, Deserialize)]
pub(crate) struct EdgeInsetsTwipsXml {
#[serde(default)]
top: Option<SideXml>,
#[serde(default)]
bottom: Option<SideXml>,
#[serde(default, alias = "start")]
left: Option<SideXml>,
#[serde(default, alias = "end")]
right: Option<SideXml>,
}
#[derive(Clone, Copy, Debug, Deserialize)]
struct SideXml {
#[serde(rename = "@w", default)]
w: Option<Dimension<Twips>>,
}
impl From<EdgeInsetsTwipsXml> for EdgeInsets<Twips> {
fn from(x: EdgeInsetsTwipsXml) -> Self {
Self::new(
x.top.and_then(|s| s.w).unwrap_or_default(),
x.right.and_then(|s| s.w).unwrap_or_default(),
x.bottom.and_then(|s| s.w).unwrap_or_default(),
x.left.and_then(|s| s.w).unwrap_or_default(),
)
}
}
impl From<EdgeInsetsTwipsXml> for PartialEdgeInsets<Twips> {
fn from(x: EdgeInsetsTwipsXml) -> Self {
Self::new(
x.top.and_then(|s| s.w),
x.right.and_then(|s| s.w),
x.bottom.and_then(|s| s.w),
x.left.and_then(|s| s.w),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(xml: &str) -> EdgeInsets<Twips> {
let x: EdgeInsetsTwipsXml = quick_xml::de::from_str(xml).unwrap();
x.into()
}
#[test]
fn all_four_sides_captured() {
let e = parse(
r#"<tcMar>
<top w="100"/>
<bottom w="200"/>
<left w="50"/>
<right w="75"/>
</tcMar>"#,
);
assert_eq!(e.top.raw(), 100);
assert_eq!(e.bottom.raw(), 200);
assert_eq!(e.left.raw(), 50);
assert_eq!(e.right.raw(), 75);
}
#[test]
fn start_and_end_alias_left_right() {
let e = parse(
r#"<tcMar>
<start w="80"/>
<end w="120"/>
</tcMar>"#,
);
assert_eq!(e.left.raw(), 80);
assert_eq!(e.right.raw(), 120);
}
#[test]
fn missing_sides_default_to_zero() {
let e = parse(r#"<tcMar><top w="100"/></tcMar>"#);
assert_eq!(e.top.raw(), 100);
assert_eq!(e.right.raw(), 0);
assert_eq!(e.bottom.raw(), 0);
assert_eq!(e.left.raw(), 0);
}
#[test]
fn partial_tcmar_preserves_absent_sides_as_none() {
let xml = r#"<tcMar>
<top w="40"/>
</tcMar>"#;
let parsed: EdgeInsetsTwipsXml = quick_xml::de::from_str(xml).unwrap();
let p: PartialEdgeInsets<Twips> = parsed.into();
assert_eq!(p.top.map(|d| d.raw()), Some(40));
assert!(p.bottom.is_none(), "absent <w:bottom> must stay None");
assert!(p.left.is_none(), "absent <w:start>/<w:left> must stay None");
assert!(p.right.is_none(), "absent <w:end>/<w:right> must stay None");
let default = EdgeInsets::<Twips>::new(
Dimension::new(57),
Dimension::new(108),
Dimension::new(57),
Dimension::new(103),
);
let resolved = p.resolve_against(default);
assert_eq!(resolved.top.raw(), 40, "explicit override wins");
assert_eq!(resolved.bottom.raw(), 57, "absent side inherits");
assert_eq!(resolved.left.raw(), 103, "absent side inherits");
assert_eq!(resolved.right.raw(), 108, "absent side inherits");
}
#[test]
fn partial_tcmar_w_zero_is_explicit_zero_override() {
let xml = r#"<tcMar>
<top w="0"/>
<bottom w="0"/>
</tcMar>"#;
let parsed: EdgeInsetsTwipsXml = quick_xml::de::from_str(xml).unwrap();
let p: PartialEdgeInsets<Twips> = parsed.into();
assert_eq!(
p.top.map(|d| d.raw()),
Some(0),
"w=0 must be preserved as explicit Some(0), not silently dropped"
);
assert_eq!(p.bottom.map(|d| d.raw()), Some(0));
assert!(p.left.is_none());
assert!(p.right.is_none());
let default = EdgeInsets::<Twips>::new(
Dimension::new(57),
Dimension::new(108),
Dimension::new(57),
Dimension::new(103),
);
let resolved = p.resolve_against(default);
assert_eq!(resolved.top.raw(), 0, "explicit zero overrides default");
assert_eq!(resolved.bottom.raw(), 0, "explicit zero overrides default");
assert_eq!(resolved.left.raw(), 103, "absent side inherits default");
assert_eq!(resolved.right.raw(), 108, "absent side inherits default");
}
}