oox 0.1.0

Open Office XML file format deserializer
Documentation
use super::{document::BlockLevelElts, simpletypes::DecimalNumber};
use crate::{
    error::{LimitViolationError, MaxOccurs, MissingAttributeError},
    xml::XmlNode,
    xsdtypes::XsdChoice,
};

type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, EnumString)]
pub enum FtnEdnType {
    #[strum(serialize = "normal")]
    Normal,
    #[strum(serialize = "separator")]
    Separator,
    #[strum(serialize = "continuationSeparator")]
    ContinuationSeparator,
    #[strum(serialize = "continuationNotice")]
    ContinuationNotice,
}

#[derive(Debug, Clone, PartialEq)]
pub struct FtnEdn {
    pub ftn_edn_type: Option<FtnEdnType>,
    pub id: DecimalNumber,
    pub block_level_elements: Vec<BlockLevelElts>,
}

impl FtnEdn {
    pub fn from_xml_element(xml_node: &XmlNode) -> Result<Self> {
        let mut ftn_edn_type = None;
        let mut id = None;

        for (attr, value) in &xml_node.attributes {
            match attr.as_ref() {
                "w:type" => ftn_edn_type = Some(value.parse()?),
                "w:id" => id = Some(value.parse()?),
                _ => (),
            }
        }

        let id = id.ok_or_else(|| MissingAttributeError::new(xml_node.name.clone(), "w:id"))?;

        let block_level_elements = xml_node
            .child_nodes
            .iter()
            .filter_map(BlockLevelElts::try_from_xml_element)
            .collect::<Result<Vec<_>>>()?;

        if !block_level_elements.is_empty() {
            Ok(Self {
                ftn_edn_type,
                id,
                block_level_elements,
            })
        } else {
            Err(Box::new(LimitViolationError::new(
                xml_node.name.clone(),
                "BlockLevelElts",
                1,
                MaxOccurs::Unbounded,
                0,
            )))
        }
    }
}

#[derive(Debug, Clone, PartialEq, Default)]
pub struct Footnotes(pub Vec<FtnEdn>);

impl Footnotes {
    pub fn from_xml_element(xml_node: &XmlNode) -> Result<Self> {
        let footnotes = xml_node
            .child_nodes
            .iter()
            .filter(|child_node| child_node.local_name() == "footnote")
            .map(FtnEdn::from_xml_element)
            .collect::<Result<Vec<_>>>()?;

        Ok(Self(footnotes))
    }
}

#[cfg(test)]
mod tests {
    use super::super::document::{ContentBlockContent, P};
    use super::*;
    use std::str::FromStr;

    impl Footnotes {
        pub fn test_xml(node_name: &'static str) -> String {
            format!(
                r#"<{node_name}>
                {}
            </{node_name}>"#,
                FtnEdn::test_xml("w:footnote"),
                node_name = node_name,
            )
        }

        pub fn test_instance() -> Self {
            Self(vec![FtnEdn::test_instance()])
        }
    }

    #[test]
    pub fn test_footnotes_from_xml() {
        let xml = Footnotes::test_xml("w:footnotes");
        assert_eq!(
            Footnotes::from_xml_element(&XmlNode::from_str(xml.as_str()).unwrap()).unwrap(),
            Footnotes::test_instance(),
        );
    }

    impl FtnEdn {
        pub fn test_xml(node_name: &'static str) -> String {
            format!(
                r#"<{node_name} w:type="normal" w:id="1">
                {}
            </{node_name}>"#,
                P::test_xml("w:p"),
                node_name = node_name,
            )
        }

        pub fn test_instance() -> Self {
            Self {
                ftn_edn_type: Some(FtnEdnType::Normal),
                id: 1,
                block_level_elements: vec![BlockLevelElts::Chunk(ContentBlockContent::Paragraph(Box::new(
                    P::test_instance(),
                )))],
            }
        }
    }

    #[test]
    pub fn test_ftn_edn_from_xml() {
        let xml = FtnEdn::test_xml("w:ftnEdn");
        assert_eq!(
            FtnEdn::from_xml_element(&XmlNode::from_str(xml.as_str()).unwrap()).unwrap(),
            FtnEdn::test_instance(),
        );
    }
}