edit-xlsx 0.4.4

A quick and easy-to-use Rust library for Excel file editing.
Documentation
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
use quick_xml::{de, se};
use serde::{Deserialize, Serialize};
use crate::file::{XlsxFileType, XlsxFileWriter};
use crate::Properties;

#[derive(Debug, Deserialize, Serialize)]
#[serde(rename(serialize = "Properties", deserialize = "Properties"))]
pub(crate) struct AppProperties {
    #[serde(rename = "@xmlns", skip_serializing_if = "Option::is_none")]
    xmlns: Option<String>,
    #[serde(rename = "@xmlns:vt", skip_serializing_if = "Option::is_none")]
    xmlns_vt: Option<String>,
    #[serde(rename = "Application", skip_serializing_if = "Option::is_none")]
    application: Option<String>,
    #[serde(rename = "DocSecurity", skip_serializing_if = "Option::is_none")]
    doc_security: Option<u8>,
    #[serde(rename = "ScaleCrop", skip_serializing_if = "Option::is_none")]
    scale_crop: Option<bool>,
    #[serde(rename = "HeadingPairs", skip_serializing_if = "Option::is_none")]
    heading_pairs: Option<HeadingPairs>,
    #[serde(rename = "TitlesOfParts", skip_serializing_if = "Option::is_none")]
    titles_of_parts: Option<TitlesOfParts>,
    #[serde(rename = "Manager", skip_serializing_if = "Option::is_none")]
    manager: Option<String>,
    #[serde(rename = "Company", skip_serializing_if = "Option::is_none")]
    company: Option<String>,
    #[serde(rename = "LinksUpToDate", skip_serializing_if = "Option::is_none")]
    links_up_to_date: Option<bool>,
    #[serde(rename = "SharedDoc", skip_serializing_if = "Option::is_none")]
    shared_doc: Option<bool>,
    #[serde(rename = "HyperlinksChanged", skip_serializing_if = "Option::is_none")]
    hyperlinks_changed: Option<bool>,
    #[serde(rename = "AppVersion", skip_serializing_if = "Option::is_none")]
    app_version: Option<String>
}

impl AppProperties {
    pub(crate) fn update_by_properties(&mut self, properties: &Properties) {
        if let Some(manager) = properties.manager {
            self.manager = Some(String::from(manager));
        }
        if let Some(company) = properties.company {
            self.company = Some(String::from(company));
        }
    }
}

#[derive(Debug, Deserialize, Serialize)]
struct HeadingPairs {
    #[serde(rename(serialize = "vt:vector", deserialize = "vector"))]
    vt_vector: VtVector,
}

#[derive(Debug, Deserialize, Serialize)]
struct TitlesOfParts {
    #[serde(rename(serialize = "vt:vector", deserialize = "vector"))]
    vt_vector: VtVector,
}

#[derive(Debug, Deserialize, Serialize)]
struct VtVector {
    #[serde(rename = "@size")]
    size: u32,
    #[serde(rename = "@baseType", skip_serializing_if = "String::is_empty")]
    base_type: String,
    #[serde(rename(serialize = "vt:lpstr", deserialize = "lpstr"), default, skip_serializing_if = "Vec::is_empty")]
    vt_lpstr: Vec<VtLpstr>,
    #[serde(rename(serialize = "vt:variant", deserialize = "variant"), default, skip_serializing_if = "Vec::is_empty")]
    vt_variant: Vec<VtVariant>
}

#[derive(Debug, Deserialize, Serialize)]
struct VtVariant {
    #[serde(rename(serialize = "vt:lpstr", deserialize = "lpstr"), default, skip_serializing_if = "Vec::is_empty")]
    vt_lpstr: Vec<VtLpstr>,
    #[serde(rename(serialize = "vt:i4", deserialize = "i4"), default, skip_serializing_if = "Vec::is_empty")]
    vt_i4: Vec<VtI4>
}

#[derive(Debug, Deserialize, Serialize)]
struct VtLpstr {
    #[serde(rename = "$value", default, skip_serializing_if = "String::is_empty")]
    value: String
}

#[derive(Debug, Deserialize, Serialize)]
struct VtI4 {
    #[serde(rename = "$value", default, skip_serializing_if = "String::is_empty")]
    value: String
}

impl AppProperties {
    pub(crate) fn from_path<P: AsRef<Path>>(file_path: P) -> io::Result<AppProperties> {
        let file = File::open(&file_path)?;
        let mut archive = zip::ZipArchive::new(file)?;
        let mut file = archive.by_name("docProps/app.xml")?;
        let mut xml = String::new();
        file.read_to_string(&mut xml).unwrap();
        let properties: AppProperties = de::from_str(&xml).unwrap();
        Ok(properties)
    }

    pub(crate) fn save<P: AsRef<Path>>(&self, file_path: P) {
        let xml = se::to_string_with_root("Properties", &self).unwrap();
        let xml = format!("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n{}", xml);
        let mut file = XlsxFileWriter::from_path(file_path, XlsxFileType::AppProperties).unwrap();
        file.write_all(xml.as_ref()).unwrap();
    }
}