xlsx_group_write 1.0.1

excel xlsx分组输出器
Documentation
use std::path::Path;

#[cfg(feature = "date")]
use crate::date_util;
use crate::xlsx_util;
use anyhow::{anyhow, Result};
use umya_spreadsheet::{reader, writer, Spreadsheet, Worksheet};

// =======================================================
//  xlsx每行数据的写入方法相关定义 {
// =======================================================

/// xlsx行写入后的后续处理指令
pub enum XlsxLineAdvanceWriterResult {
    /// 中断写入循环
    Break,
    /// 跳过当前行,继续写入下一行
    Continue,
}

/// xlsx每一行数据的自定义写入方法
/// 通过返回不同值,控制数据循环的后续处理方法。
/// 1. 当前要写入的xlsx表格
/// 2. 当前行号
/// 3. T 为实际需要写入的数据
///
pub type XlsxLineAdvanceWriter<T> =
    fn(&mut Worksheet, u32, T) -> Option<XlsxLineAdvanceWriterResult>;

/// 原始待数据到XLSX行实际写入单元格数据的转换器
///
/// 如果设置了非None值,则使用快速模式写入数据,即使LINE_WRITER非None也不调用
///
/// 大部分时候excel写入时都是一个数据写入一列
/// 这时候如果还得通过LINE_WRITER自己通过xlsx相关api写入单元格数据
/// 起始没有必要。
///
/// 对于如上情况,可通过该数据转换器,直接根据返回的数据,自动写入xlsx单元格中
/// 终端用户不需要了解xlsx写入的API,进一步降低编码难度和智能化水平
pub type XlsxLineConverter<T> = fn(u32, T) -> Vec<XlsxColValue>;
/// 待写入到xlsx单元格中的数据
#[derive(Debug)]
pub struct XlsxColValue {
    ///    pub value: String,
    /// 值类型
    pub value_type: XlsxColValueType,
}
impl XlsxColValue {
    pub fn new(value: impl ToString, value_type: XlsxColValueType) -> Self {
        Self {
            value: value.to_string(),
            value_type,
        }
    }
}
/// 待写入xlsx单元格中的值类型
#[derive(Debug, PartialEq, Eq)]
pub enum XlsxColValueType {
    /// 字符类型
    StringValue,
    /// 数字类型
    NumberValue,
}
/// xlsx 行各类数据的写入方法
pub enum XlsxLineWriterModel {
    /// 完全自由的写入方法
    Advance,
    /// 仅需要提供数据到待写入数据转换的简便方法
    /// 无需了解xlsx写入api
    /// 推荐使用此模式
    Simple,
}

impl XlsxColValue {
    /// 将数据写入到xlsx表格的某一行中
    ///
    /// # 参数说明
    ///
    /// * sheet 要写入的表格
    /// * line_index 要写入的行序号,第一行序号为1
    /// * line_data 要写入的数据
    ///
    /// 内部方法,不建议外部调用
    pub fn write_line_data_simple(
        sheet: &mut Worksheet,
        line_index: u32,
        line_data: &[XlsxColValue],
    ) -> Result<()> {
        for (col_index, XlsxColValue { value, value_type }) in line_data.iter().enumerate() {
            match value_type {
                XlsxColValueType::NumberValue => {
                    let value: f64 = value.parse()?;
                    xlsx_util::XlsxWriterTool::set_excel_cell_value_number(
                        sheet,
                        (col_index + 1) as u32,
                        line_index,
                        value,
                    );
                }
                XlsxColValueType::StringValue => {
                    xlsx_util::XlsxWriterTool::set_excel_cell_value_str(
                        sheet,
                        (col_index + 1) as u32,
                        line_index,
                        value,
                    )
                }
            }
        }
        Ok(())
    }
}
// =======================================================
//  xlsx每行数据的写入方法相关定义 }
// =======================================================

// =======================================================
// 数据分组相关定义 {
// =======================================================

/// 数据分组输出的组id生成器
///
/// 如根据数据中某一字段分别生成不同的分组id,以便写入不同xlsx文件
///
/// 参数:
/// 1.当前要处理的数据
/// 返回当前要写入的分组id,
/// 后续根据该id决定写入那个sheet
/// 如果为新id,则自动从模板创建初始xlsx表格
pub type GroupMaker<T> = fn(T) -> String;

// =======================================================
// 数据分组相关定义  }
// =======================================================

/// 要输出的excel xlsx 文件信息
pub struct OutputXlsxFile {
    /// 写入行索引
    pub index: u32,
    /// 对应excel文件
    pub excel_book: Spreadsheet,
    /// 输出文件名称
    pub output_file_name: String,
    /// 关键字,用于与分类数据关联
    pub id: String,
}
impl OutputXlsxFile {
    /// 保存xlsx文件
    ///
    /// # 参数说明
    ///
    /// * passwrod 加密密码,None则为不加密
    pub fn save(&self, #[cfg(feature = "encrypt")] passwrod: Option<&str>) -> Result<()> {
        writer::xlsx::write(&self.excel_book, Path::new(&self.output_file_name))?;
        #[cfg(feature = "encrypt")]
        if let Some(passwrod) = passwrod {
            msoffice_crypt::encrypt(&self.output_file_name, passwrod, None)?;
        }
        Ok(())
    }
}

// =======================================================
// 初始xlsx相关模板定义 {
// =======================================================

/// 新增分组的xlsx文件初始化模板
pub enum XlsxInitTemplet {
    /// 自动生成表头
    /// 各列表头以逗号分隔
    Header(String),
    /// 从模板文件创建
    Advanced(XlsxAdvanceTemplate),
}

impl XlsxInitTemplet {
    /// 新增xlsx文件初始化
    ///
    /// # 参数说明
    ///
    /// * id 新增xlsx对应的数据类别关键字,实际数据根据该关键字进行分割写入对应xlsx文件中
    /// * file_name 新增的xlsx文件的存放位置
    pub fn init_new_xlsx(&self, id: &str, file_name: &str) -> Result<OutputXlsxFile> {
        match self {
            XlsxInitTemplet::Header(header_str) => {
                // 无模板,直接创建xlsx文件
                let mut book = umya_spreadsheet::new_file();
                let sheet = book
                    .get_sheet_mut(&0)
                    .map_err(|err| anyhow!("读取待写入excel表格失败:{}", err))?;
                // 有表头,写入表头
                for (col, value) in header_str.split(',').enumerate() {
                    xlsx_util::XlsxWriterTool::set_excel_cell_value_str(
                        sheet,
                        (col + 1) as u32,
                        1,
                        value,
                    );
                }
                Ok(OutputXlsxFile {
                    index: 2,
                    excel_book: book,
                    output_file_name: file_name.into(),
                    id: id.into(),
                })
            }
            XlsxInitTemplet::Advanced(template) => template.new_from_template(id, file_name),
        }
    }

    /// 自动生成表头模式
    ///
    /// # 参数说明
    ///
    /// * header 表头,各列表头以逗号分隔
    pub fn new_header(header: &str) -> Self {
        Self::Header(header.into())
    }

    /// 从模板文件创建模式
    ///
    /// # 参数说明
    ///
    /// * file_path 模板文件路径
    /// * start_index 数据写入起始行
    pub fn new_advance(file_path: &str, start_line: u32) -> Self {
        Self::Advanced(XlsxAdvanceTemplate {
            file_path: file_path.into(),
            start_line,
        })
    }
}

/// xlsx高级模板文件定义
///
/// 用于从模板xlsx文件初始化xlsx文件
pub struct XlsxAdvanceTemplate {
    /// 文件存储位置
    file_path: String,
    /// 数据起始写入行
    start_line: u32,
}

impl XlsxAdvanceTemplate {
    /// 从模板文件创建新的xlsx文件
    ///
    /// # 参数说明
    ///
    /// * id 该xlsx的编号,用于后续数据分组写入是匹配
    /// * file_name 实际存放xlsx完整文件名称
    pub fn new_from_template(&self, id: &str, file_name: &str) -> Result<OutputXlsxFile> {
        // 读取模板文件
        let book = reader::xlsx::read(&self.file_path)?;
        Ok(OutputXlsxFile {
            index: self.start_line,
            excel_book: book,
            output_file_name: file_name.into(),
            id: id.into(),
        })
    }
}

// =======================================================
// 初始xlsx相关模板定义 }
// =======================================================

// =======================================================
// 输出文件名称相关定义 {
// =======================================================

/// 完全自定义的输出文件名称名称生成器
///
/// 参数1:当期数据的分组id
///
pub type OutputFileNameAdvanceGetter = fn(&str) -> String;

/// 固定模式的简单文件名称生成
///
/// 生成文件名称格式:特定前缀-分组id-输出日期
pub struct OutputFileNameSimpleGetter {
    /// 默认文件名前缀
    /// 还有文件完整路径
    /// 如/a/b/c/file_name_pref
    ///
    /// 实际生成文件名称为
    /// default_file_name_pref-分组id-日期.xlsx
    file_name_pref: &'static str,
    #[cfg(feature = "date")]
    /// 是否自动在文件名称末尾添加导出日期
    is_append_date: bool,
}
impl OutputFileNameSimpleGetter {
    pub const fn new(
        file_name_pref: &'static str,
        #[cfg(feature = "date")] is_append_date: bool,
    ) -> Self {
        Self {
            file_name_pref,
            #[cfg(feature = "date")]
            is_append_date,
        }
    }
    /// 获取保持文件完整名称(含路径)
    ///
    /// # 参数说明
    ///
    /// * group_id 分组id
    pub fn get_save_file_name(&self, group_id: &str) -> String {
        // 自动生成文件名称
        #[cfg(feature = "date")]
        if self.is_append_date {
            // 在文件名称中加入输出日期
            format!(
                "{}-{}-{}.xlsx",
                self.file_name_pref,
                group_id,
                date_util::get_now_date()
            )
        } else {
            format!("{}-{}.xlsx", self.file_name_pref, group_id)
        }
        #[cfg(not(feature = "date"))]
        format!("{}-{}.xlsx", self.file_name_pref, group_id)
    }
}

// =======================================================
// 输出文件名相关定义 }
// =======================================================

/// 分组输出完毕后的结果信息
#[derive(Debug)]
pub struct XlsxGroupWriteResp {
    /// 该文件名对应的分组id
    pub group_id: String,
    /// 成功保存的文件名
    pub file_name: String,
}
impl XlsxGroupWriteResp {
    pub fn new(group_id: &str, file_name: &str) -> Self {
        Self {
            file_name: file_name.into(),
            group_id: group_id.into(),
        }
    }
}
impl From<OutputXlsxFile> for XlsxGroupWriteResp {
    fn from(output_file: OutputXlsxFile) -> Self {
        Self::new(&output_file.id, &output_file.output_file_name)
    }
}
impl From<&OutputXlsxFile> for XlsxGroupWriteResp {
    fn from(output_file: &OutputXlsxFile) -> Self {
        Self::new(&output_file.id, &output_file.output_file_name)
    }
}

/// 文件输出方式
#[derive(Debug, PartialEq, Eq)]
pub enum XlsxWriteModel {
    /// 同时导出合并和分组文件
    MergeAndGroup,
    /// 只导出分组文件
    GroupOnly,
    /// 只导出合并文件
    MergeOnly,
}