ooxml 0.1.1

An Office OpenXML parser writen in Rust
Documentation
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

use linked_hash_map::LinkedHashMap;
use serde::de::{Deserialize, Deserializer, MapAccess, Visitor};

use crate::error::OoxmlError;

pub type ContentType = String;

#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
struct Default {
    extension: String,
    content_type: ContentType,
}
#[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
#[serde(rename_all = "PascalCase")]
struct Override {
    part_name: String,
    content_type: ContentType,
}
// #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq)]
// #[serde(rename_all = "PascalCase", untagged)]
// enum ContentTypeItem {
//     Default(Default),
//     Override(Override),
// }

// impl ContentTypeItem {
//     pub fn new(content_type: &str) -> Self {
//         let mime: mime::Mime = content_type.parse().expect("unrecognized content type");
//         Self::new_default(mime.subtype().as_str(), content_type)
//     }
//     pub fn new_default(extension: &str, content_type: &str) -> Self {
//         ContentTypeItem::Default(Default {
//             extension: extension.into(),
//             content_type: content_type.into(),
//         })
//     }
//     pub fn new_override(part_name: &str, content_type: &str) -> Self {
//         ContentTypeItem::Override(Override {
//             part_name: part_name.into(),
//             content_type: content_type.into(),
//         })
//     }
// }
// #[test]
// fn test_content_type_serde() {
//     let default = ContentTypeItem::new("image/png");

//     let ser = quick_xml::se::to_string(&default).unwrap();
//     assert_eq!(ser, r#"<Default Extension="png" ContentType="image/png"/>"#);
//     let de: ContentTypeItem = quick_xml::de::from_str(&ser).unwrap();
//     assert_eq!(default, de);
//     println!("{:?}", de);
// }

#[derive(Debug, PartialEq, Default, Clone)]
pub struct ContentTypes {
    defaults: LinkedHashMap<String, ContentType>,
    overrides: LinkedHashMap<String, ContentType>,
}

struct ContentTypesVisitor;

impl<'de> Visitor<'de> for ContentTypesVisitor {
    // The type that our Visitor is going to produce.
    type Value = ContentTypes;

    // Format a message stating what data this Visitor expects to receive.
    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a very special map")
    }

    // Deserialize MyMap from an abstract "map" provided by the
    // Deserializer. The MapAccess input is a callback provided by
    // the Deserializer to let us see each entry in the map.
    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
    where
        M: MapAccess<'de>,
    {
        let mut types: ContentTypes = ContentTypes::default();
        while let Some(key) = access.next_key()? {
            let key: String = key;
            match &key {
                s if s == XMLNS_ATTRIBUTE_NAME => {
                    let _xmlns: String = access.next_value()?;
                }
                s if s == TYPES_TAG_NAME => {
                    //let v: Vec<String> = access.next_value()?;
                    unreachable!();
                }
                s if s == DEFAULT_TAG_NAME => {
                    let v: Default = access.next_value()?;
                    types.add_default_element(v.extension, v.content_type);
                }
                s if s == OVERRIDE_TAG_NAME => {
                    let v: Override = access.next_value()?;
                    types.add_override_element(v.part_name, v.content_type);
                }
                _ => {
                    unreachable!("content type unsupport!");
                }
            }
        }
        Ok(types)
    }
}
impl<'de> Deserialize<'de> for ContentTypes {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        // Instantiate our Visitor and ask the Deserializer to drive
        // it over the input data, resulting in an instance of MyMap.
        deserializer.deserialize_map(ContentTypesVisitor)
    }
}

pub const CONTENT_TYPES_FILE: &'static str = "[Content_Types].xml";
pub const TYPES_NAMESPACE_URI: &'static str =
    "http://schemas.openxmlformats.org/package/2006/content-types";
pub const TYPES_TAG_NAME: &'static str = "Types";
pub const DEFAULT_TAG_NAME: &'static str = "Default";
pub const OVERRIDE_TAG_NAME: &'static str = "Override";
pub const PART_NAME_ATTRIBUTE_NAME: &'static str = "PartName";
pub const EXTENSION_ATTRIBUTE_NAME: &'static str = "Extension";
pub const CONTENT_TYPE_ATTRIBUTE_NAME: &'static str = "ContentType";
pub const XMLNS_ATTRIBUTE_NAME: &'static str = "xmlns";

impl fmt::Display for ContentTypes {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut container = Vec::new();
        let mut cursor = std::io::Cursor::new(&mut container);
        self.write(&mut cursor).expect("write xml to memory error");
        let s = String::from_utf8_lossy(&container);
        write!(f, "{}", s)?;
        // /write!("{}", String::from_utf8(cursor.into_inner().into));
        Ok(())
    }
}
impl ContentTypes {
    /// Parse content types data from an xml reader.
    pub fn parse_from_xml_reader<R: BufRead>(reader: R) -> Self {
        quick_xml::de::from_reader(reader).unwrap()
    }

    /// Parse content types data from an xml str.
    pub fn parse_from_xml_str(reader: &str) -> Self {
        quick_xml::de::from_str(reader).unwrap()
    }

    /// follow OpenXML SDK function definitions, but not implemented.
    pub fn add_content_type() {}
    pub fn get_content_type() {}
    pub fn delete_content_type(&mut self, _content_type: &ContentType) {}

    /// Save to file path.
    pub fn save_as<P: AsRef<Path>>(&self, path: P) -> Result<(), OoxmlError> {
        let file = File::create(path)?;
        self.write(file)
    }

    /// Write to an writer
    pub fn write<W: std::io::Write>(&self, writer: W) -> Result<(), OoxmlError> {
        let mut xml = quick_xml::Writer::new(writer);
        use quick_xml::events::attributes::Attribute;
        use quick_xml::events::*;

        // 1. write decl
        xml.write_event(Event::Decl(BytesDecl::new(
            b"1.0",
            Some(b"UTF-8"),
            Some(b"yes"),
        )))?;

        // 2. start types element
        let mut elem = BytesStart::borrowed_name(TYPES_TAG_NAME.as_bytes());
        let ns = Attribute {
            key: XMLNS_ATTRIBUTE_NAME.as_bytes(),
            value: TYPES_NAMESPACE_URI.as_bytes().into(),
        };
        elem.extend_attributes(vec![ns]);
        xml.write_event(Event::Start(elem))?;

        // 3. write default entries
        for (key, value) in &self.defaults {
            xml.write_event(Event::Empty(
                BytesStart::borrowed_name(DEFAULT_TAG_NAME.as_bytes()).with_attributes(vec![
                    Attribute {
                        key: EXTENSION_ATTRIBUTE_NAME.as_bytes(),
                        value: key.as_bytes().into(),
                    },
                    Attribute {
                        key: CONTENT_TYPE_ATTRIBUTE_NAME.as_bytes(),
                        value: value.as_bytes().into(),
                    },
                ]),
            ))?;
        }

        // 4. write override entries
        for (key, value) in &self.overrides {
            xml.write_event(Event::Empty(
                BytesStart::borrowed_name(OVERRIDE_TAG_NAME.as_bytes()).with_attributes(vec![
                    Attribute {
                        key: PART_NAME_ATTRIBUTE_NAME.as_bytes(),
                        value: key.as_bytes().into(),
                    },
                    Attribute {
                        key: CONTENT_TYPE_ATTRIBUTE_NAME.as_bytes(),
                        value: value.as_bytes().into(),
                    },
                ]),
            ))?;
        }
        // ends types element.
        let end = BytesEnd::borrowed(TYPES_TAG_NAME.as_bytes());
        xml.write_event(Event::End(end))?;
        Ok(())
    }

    pub fn add_default_element(&mut self, extension: String, content_type: ContentType) {
        self.defaults.insert(extension, content_type);
    }

    pub fn add_override_element(&mut self, part_name: String, content_type: ContentType) {
        self.overrides.insert(part_name, content_type);
    }

    pub fn is_empty(&self) -> bool {
        self.defaults.is_empty() && self.overrides.is_empty()
    }
}

#[test]
fn test_de() {
    let raw = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="png" ContentType="image/png"/><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/><Override PartName="/docProps/custom.xml" ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"/><Override PartName="/xl/charts/chart1.xml" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/><Override PartName="/xl/charts/colors1.xml" ContentType="application/vnd.ms-office.chartcolorstyle+xml"/><Override PartName="/xl/charts/style1.xml" ContentType="application/vnd.ms-office.chartstyle+xml"/><Override PartName="/xl/drawings/drawing1.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/><Override PartName="/xl/drawings/drawing2.xml" ContentType="application/vnd.openxmlformats-officedocument.drawing+xml"/><Override PartName="/xl/sharedStrings.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/><Override PartName="/xl/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/><Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>"#;
    println!("{}", raw);
    let content_types: ContentTypes = quick_xml::de::from_str(raw).unwrap();
    println!("{:?}", content_types);
    let display = format!("{}", content_types);
    assert_eq!(raw, display);
}