专栏名称: 奇舞精选
《奇舞精选》是由奇舞团维护的前端技术公众号。除周五外,每天向大家推荐一篇前端相关技术文章,每周五向大家推送汇总周刊内容。
目录
相关文章推荐
新浪科技  ·  转发微博-20250207185308 ·  19 小时前  
新浪科技  ·  【#DeepSeek如何变成真正生产力##专 ... ·  17 小时前  
51好读  ›  专栏  ›  奇舞精选

Rust赋能前端:写一个 Excel 生成引擎

奇舞精选  · 公众号  · 科技媒体 前端  · 2024-12-02 18:30

主要观点总结

文章详细介绍了如何用Rust编写一个Excel引擎的过程,从设计思路、代码结构、核心代码解释到构建XML元数据信息,并分享了相关的技术概念,如Excel文件结构、XML、Open XML Formats等。文章还包含了一个简单的Node应用示例,用于生成Excel文件。

关键观点总结

关键观点1: 设计思路与代码结构

文章首先描述了Excel文件的结构,然后解释了使用Rust构建Excel引擎的设计思路,包括项目初始化、代码结构以及各模块的功能。

关键观点2: 核心代码解释

详细解释了生成Excel文件的核心代码,包括解析前端数据、构建Excel数据结构、生成Excel压缩文件等。

关键观点3: XML与Open XML Formats

介绍了XML和Open XML Formats的概念,并说明了它们如何用于构建Excel文件。

关键观点4: 构建XML元数据信息

解释了如何创建XML元数据文件,包括[Content_Types].xml、xl/_rels/workbook.xml.rels和xl/workbook.xml。

关键观点5: Node应用示例

提供了一个简单的Node应用示例,用于生成Excel文件。


正文

年关将至,你今年成长了吗?

大家好,我是 柒八九 。一个 专注于前端开发技术/ Rust AI 应用知识分享 Coder

此篇文章所涉及到的技术有

  1. Rust
  2. WebAssembly
  3. Excel 引擎
  4. xml
  5. Rust 解析 JSON
  6. Rust 操作内存缓冲区
  7. 使用 zip::ZipWriter 创建 ZIP 文件

因为,行文字数所限,有些概念可能会一带而过亦或者提供对应的学习资料。请大家酌情观看。


前言

在上一篇 Rust 赋能前端: 纯血前端将 Table 导出 Excel 我们用很大的篇幅描述了,如何在前端页面中使用我们的 table2excel ( WebAssembly )。

有同学想获取上一篇的前端项目,等有空我会上传到 github 中。同时,也想着把 table2excel 发布到 npm 中。到时候,会通知大家的。

具体展示了,如何在前端对 静态表格 / 静态长表格(1 万条数据) / 静态表格合并 / 动态表格合并 等表格进行导出为 excel

运行效果

静态表格

静态长表格(1 万条数据)

静态表格合并

动态表格合并


但是呢,对于源码的解读,我们只是浅尝辄止。只是介绍了,如何将在前端构建的 Table 的信息转换为我们 Excel 引擎需要的信息。

那么我们今天就来讲讲 如何用 Rust 写一个 Excel 引擎


好了,天不早了,干点正事哇。

我们能所学到的知识点

  1. 设计思路
  2. 代码结构
  3. 核心代码解释

1. 设计思路

Excel 是一个压缩文件

先说可能打破大家认知的结论

Excel .xlsx 文件实际上是一个包含多个 XML 文件的压缩文件。

为了论证这个结论,我们来实际操作一下。(我用的是 Mac ,所以下面的操作都是基于 Mac ,至于其他环境大家可自行验证)

这是我们上一篇文件生成的 excle 文件。当然,你也可以用你本机的资源。

我们使用终端命令来执行 excel 的解压操作。(并且该文件的名字为 test.xlsx )

  1. 假设 .xlsx 文件在桌面上:

    cd ~/Desktop
  2. 更改扩展名 : 将 .xlsx 文件扩展名更改为 .zip

    mv test.xlsx test.zip
  3. 解压 ZIP 文件 : 使用 unzip 命令解压 ZIP 文件:

    unzip test.zip -d test_folder

    这将会把 .zip 文件解压到 test_folder 目录中。

然后,我们切换到 test_folder 目录中,执行 Vscode 的快捷命令 - code .

就会看到下面的目录结构

我们来简单解释一下比较重要文件的含义

  1. worksheets 文件夹用于存放 excel sheet 信息,由于我们之前的 excel 只有一个 sheet 。所以这里只有一个 sheet1.xml 。如果生成的 excel 有多个 sheet 。那么这里就有多个 sheetN.xml 文件
  • clos 定义每个列的宽度
  • sheetData 用于定义 excel 中每个 cell 的值
  • merge 维护每个 sheet 的合并信息
  • sharedStrings.xml 是一种优化方案, excel 中存在多个相同的值,那么我们可以存放到这里,然后在 sheetN.xml 引用这些值,可以节省 excel 的存储空间。
  • styles.xml 用于存放 excel 的样式信息。虽然,我们的引擎暂未支持样式的处理,但是后期也是可以把这块给加上的。

  • 啥是 XML

    关于 xml 有很多文章来介绍它。我们在摘录关于 维基百科\_xml [1] 的定义。

    XML 是一种用于存储、传输和重建任意数据的 标记语言 文件格式 。其定义了一套用于编码文档的规则,这些规则使得文档既易于人类阅读,也易于机器处理。

    然后,如果大家不想看英文内容,大家也可以看 xml 中文解释 [2] ,这里就不过多解释了。但是呢,有一点还是想多啰嗦下。

    Open XML Formats

    到此为止,我已经默认大家已经对 xml 有了些许的了解。然后,我们再解释一个概念。

    上面说了, excel 是一堆 xml 组成的压缩文件。其实呢,还有一个定语,是符合 Open XML Formats 格式的 xml

    我们还是直接从 Office*Open_XML*维基百科 [3] 中寻找答案。

    Office Open XML (也非正式地称为 OOXML )是微软开发的一种 基于 XML 的压缩文件格式 ,用于表示 spreadsheets (也就是 excel )、 ppt word


    在 Excel 中使用 XML

    为了更加深大家对 Excel 的理解,或者更准确的说是 Excel xml 之前的关系。我们写一个简单的 Node 应用。

    注意:我们需要构造符合 Excel 标准的 XML 结构

    具体代码如下:

    const fs = require('fs');

    // 用来生成 Excel XML 格式的函数
    function generateExcelXml(data{
        const xmlHeader = ``;
        const worksheetXml = `
        
            
                `
    ;

        // 创建表格行
        let rowsXml = '';
        data.forEach(row => {
            rowsXml += '';
            row.forEach(cell => {
                rowsXml += `${cell}`;
            });
            rowsXml += '';
        });

        const footerXml = `
                
            
        `
    ;

        // 合并所有部分
        const fullXml = `${xmlHeader}${worksheetXml}${rowsXml}${footerXml}`;
        return fullXml;
    }

    // 示例数据
    const data = [
        ['Name''Age''City'],
        ['北宸'25'北京'],
        ['南蓁'30'山西'],
        ['Front789'35'晋中']
    ];

    // 生成 XML 内容
    const xmlContent = generateExcelXml(data);

    // 保存为 Excel 可读取的 XML 文件
    fs.writeFileSync('workbook.xml', xmlContent, 'utf8');

    代码说明:

    1. XML 头部 :指定了 XML 文件的版本和编码方式。
    2. :工作簿的根元素, Excel 使用 ss 命名空间来定义 XML 文件的结构。
    3. :工作表定义,每个工作簿可以有多个工作表,这里定义了一个工作表 Sheet1
    4. :表格,包含多行数据。
    5. :行元素,每行包含多个单元格。
    6. :单元格,里面包含数据。
    7. 保存文件 :将生成的 XML 内容写入 workbook.xml 文件。

    然后,我们运行上面的代码后,就会生成一个 workbook.xml 文件。随后,我们将该文件拖入到 WPS 中。

    看到的效果如下:

    可以看到,我们刚才用代码生成的 xml ,是正常显示为 excel 格式。并且数据也是正确的。

    还有一点需要说明,当我们把刚才生成的 xml 拖入到 WPS 时,它会跳出一个提示框,问你需要将该 xml 以何种模式展示。这步也反向证明了 Office_Open_XML 是微软开发的一种基于 XML 的压缩文件格式,用于表示 spreadsheets(也就是 excel)、ppt 和 word 这个概念。


    2. 代码结构

    项目初始化

    该内容,在上一篇讲过,我们就直接复制过来了。

    我们通过 cargo new --lib table2excel 来构建一个项目。

    同时呢,我们在项目根目录中创建用于打包优化的文件。

    1. build.sh
    2. tools/optimize-rust.sh
    3. tools/optimize-wasm.sh

    这个我们在之前的 Rust 赋能前端:为 WebAssembly 瘦身 中介绍过相关概念,这里就不再赘述了。

    项目结构

    src 目录下,我们有如下的目录结构

    ├── json2sheet.rs
    ├── lib.rs
    ├── sheet_data.rs
    ├── struct_define.rs
    ├── utils.rs
    ├── xml.rs
    └── xml_meta.rs
    1. json2sheet.rs 在上一篇文章中讲过,它的作用就是将前端页面中传入的 json 转换为构建 xml 的所需结构
    2. lib.rs 这里只有一个函数,就是我们在前端调用的主函数 generate_excel
    3. sheet_data.rs :该文件用于基于 json2sheet.rs 返回的数据和 json 中特定的数据,构建 xml 的数据部分
    4. struct_define.rs :用于存放该项目中用到的 Struct
    5. utils.rs :用于定义一下工具方法。
    • log_to_console 封装web_sys [4] ::console,用于在前端中打印信息
    • set_panic_hook 封装console_error_panic_hook [5] ,让错误更好的控制台捕获
  • xml.rs :基于 sheet_data 拼装 xml 信息
  • xml_meta :用于生成符合 open xml 的元数据信息
  • 下面,我们就会拿我认为主要的代码,来讲讲核心逻辑。


    3. 核心代码解释

    lib.rs

    引入第三方包和自定义模块

    use struct_define::{ CellValue, InnerCell };
    use wasm_bindgen::prelude::*;
    use std::io::prelude::*;
    use zip;
    use zip::write::FileOptions;
    use std::io::Cursor;

    pub mod struct_define;
    pub mod xml;
    pub mod utils;
    pub mod json2sheet;
    pub mod xml_meta;
    pub mod sheet_data;

    const ROOT_RELS: &'static [u8] = br#"
    "#
    ;
    1. wasm_bindgen [6] 这是 Rust 编译为 WebAssembly 绕不开的大山,这里就不再展示细说了。

    2. zip [7] :前面说了, excel 就是一堆 xml zip 压缩包。所以,我们使用 zip 来处理压缩

    3. std::io::Cursor : Cursor 是一种用于内存缓冲区的类型,它提供了对内存中的数据进行读取和写入的功能。

    • 通过实现 Seek Cursor 使得这些 缓冲区可以像文件一样进行随机访问
    • Cursor 可用于多种类型的缓冲区,比如 Vec 和切片 ( &[u8] ),并能够利用标准库中的 I/O 特性实现 数据的读取和写入

    核心代码

    该代码的主要功能是生成一个 Excel 文件( .xlsx 格式),它通过将 JSON 数据处理为 Excel 格式并使用 zip 压缩库将其封装成一个 .xlsx 文件。

    #[wasm_bindgen]
    pub async fn generate_excel(raw_data: &JsValue) -> Result<Vec<u8>, JsValue> {
        utils::set_panic_hook();

       // 解析前端传入的数据
        let data = json2sheet::process_json(raw_data);

        let mut shared_strings = vec!();
        let mut sheets_info: VecString, String)> = vec!();

        // 创建压缩文件的内存缓冲区
        let buf: Vec<u8> = vec!();
        let w = Cursor::new(buf);
        let mut zip = zip::ZipWriter::new(w);
        let options = FileOptions::default()
            .compression_method(zip::CompressionMethod::Stored)
            .unix_permissions(0o755);

        let sheet = &data.data;
        let mut rows: Vec<Vec> = vec!();

        // 将行数据处理成 InnerCell 格式
        if let Some(cell) = &sheet.cells {
            for (row_index, row) in cell.iter().enumerate() {
                let mut inner_row: Vec = vec!();
                for (col_index, cell) in row.iter().enumerate() {
                    if let Some(value) = cell {
                        let cell_name = sheet_data::cell_offsets_to_index(row_index, col_index);
                        let mut inner_cell = InnerCell::new(cell_name);
                        if let Ok(_) = value.parse::<f64>() {
                            inner_cell.value = CellValue::Value(value.to_owned());
                        } else {
                            inner_cell.value = CellValue::SharedString(shared_strings.len() as u32);
                            shared_strings.push(value.to_owned());
                        }
                        inner_row.push(inner_cell);
                    }
                }
                rows.push(inner_row);
            }
        }

        // 获取 sheet 信息并开始写入压缩文件
        let sheet_info = sheet_data::get_sheet_info(sheet.name.clone(), 0);
        zip.start_file(sheet_info.0.clone(), options).unwrap();
        zip.write_all(
            sheet_data::get_sheet_data(rows, &sheet.cols, &sheet.rows, &sheet.merged).as_bytes()
        ).unwrap();
        sheets_info.push(sheet_info);

        // 写入 _rels/.rels 文件
        zip.start_file("_rels/.rels", options).unwrap();
        zip.write_all(ROOT_RELS).unwrap();

        // 创建 XML 元数据
        let (content_types, rels, workbook) = xml_meta::create_open_xml_meta(sheets_info);

        // 写入各种 XML 文件
        zip.start_file("[Content_Types].xml", options).unwrap();
        zip.write_all(content_types.as_bytes()).unwrap();

        zip.start_file("xl/_rels/workbook.xml.rels", options).unwrap();
        zip.write_all(rels.as_bytes()).unwrap();
        zip.start_file("xl/workbook.xml", options).unwrap();
        zip.write_all(workbook.as_bytes()).unwrap();

        // 写入 sharedStrings.xml 文件
        zip.start_file("xl/sharedStrings.xml", options).unwrap();
        zip.write_all(sheet_data::get_shared_strings_data(shared_strings, 0).as_bytes()).unwrap();

        // 完成压缩并返回结果
        let res = zip.finish().unwrap();
        Ok(res.get_ref().to_vec())
    }

    该函数的主要核心步骤如下:

    1. 接收 JSON 数据并处理 :接收 JsValue 类型的输入数据,这个数据是通过 json2sheet::process_json 函数处理后的 JSON 数据。
    2. 构建 Excel 数据结构 :解析并转换 JSON 数据为 InnerCell 格式的行数据,以便在 Excel 中进行存储。
    3. 生成 Excel 压缩文件(.xlsx 格式) :通过 zip 库创建一个内存中的 ZIP 文件,并将 Excel 文件的不同部分(如 workbook.xml , sharedStrings.xml )写入该 ZIP 文件。
    4. 异步处理 :通过 async/await 使得函数能够在 JavaScript 中异步执行,避免阻塞主线程。

    下面我们就简单来对代码中重要的核心部分做一个简单的解释。

    1. 设置 Panic Hook

    utils::set_panic_hook();

    这行代码设置了一个 Panic Hook ,用于在 Rust 中发生 panic 时,能够捕获并进行适当的处理。通常在 WebAssembly 中使用它来处理错误。

    2. 处理 JSON 数据

    let data = json2sheet::process_json(raw_data);

    process_json 函数处理传入的 JSON 数据,将其转换成适合构建 Excel 的数据结构。 raw_data 是通过 JsValue 类型传入的,在调用该函数后,它被转换成一个包含 Excel 工作表数据的结构(例如:行、列、单元格等)。

    3. 初始化压缩文件 (ZIP)

    let buf: Vec<u8> = vec!();
    let w = Cursor::new(buf);
    let mut zip = zip::ZipWriter::new(w);

    这段代码创建了一个内存缓冲区( Vec ),并将其包装在 Cursor 中。 zip::ZipWriter 用于创建一个 ZIP 文件,在其中写入 Excel 文件的各个部分。

    4. 写入工作表数据(行数据)

    if let Some(cell) = &sheet.cells {
        for (row_index, row) in cell.iter().enumerate() {
            let mut inner_row: Vec = vec!();
            for (col_index, cell) in row.iter().enumerate() {
                // 省略部分代码
            }
            rows.push(inner_row);
        }
    }

    这一部分将从 cells (一个包含 Excel 工作表所有行的 Vec >> )中获取每一行数据,逐个单元格处理,将每个单元格的数据转换为 InnerCell 对象,并将它们组织成行。每个 InnerCell 可能是直接存储值(如数字),或者是共享字符串(如果该单元格是文本)。所有的共享字符串都会被存储在 shared_strings 中。

    5. 写入 Excel 文件的各个部分

    let sheet_info = sheet_data::get_sheet_info(sheet.name.clone(), 0);
    zip.start_file(sheet_info.0.clone(), options).unwrap();
    zip.write_all(
        sheet_data::get_sheet_data(rows, &sheet.cols, &sheet.rows, &sheet.merged).as_bytes()
    ).unwrap();

    这段代码处理工作表( sheet_info ),并将其写入 ZIP 文件中。它还将当前工作表的数据(如行、列、合并单元格等)写入到 ZIP 文件中。

    6. 写入其他 Excel 文件元数据

    zip.start_file("_rels/.rels", options).unwrap();
    zip.write_all(ROOT_RELS).unwrap();

    这部分写入 Excel 文件的关系文件( _rels/.rels ),它用于描述文件之间的关系,例如工作表与数据文件之间的关系。

    接下来的代码还会写入 Excel 文件所需的其他 XML 文件:

    • [Content_Types].xml :描述 Excel 文件中各种文件类型。
    • xl/_rels/workbook.xml.rels :描述工作簿的关系文件。
    • xl/workbook.xml :工作簿的主 XML 文件。
    • xl/sharedStrings.xml :存储共享字符串(如文本)数据。

    这些文件,我们在文章刚开始就用见到过了,也就是说这些文件是构成 excel 压缩文件的基础

    7. 完成 ZIP 压缩并返回结果

    let res = zip.finish().unwrap();
    Ok(res.get_ref().to_vec())

    在完成所有数据写入后,调用 zip.finish() 来结束 ZIP 文件的创建。最后,返回一个 Vec ,它包含了压缩后的 .xlsx 文件内容。


    json2sheet.rs - 处理 JSON 数据

    这步,我们在上一篇文章中( Rust 赋能前端: 纯血前端将 Table 导出 Excel 讲过了,为了不让文章看起来又臭又长,所以这里就不再过多解释了。

    总结一句话,其实就是将从前端环境传入的 Table 的配置信息,转换为我们生成 xml 需要的数据格式。


    sheet_data.rs - 基于信息构建 xml

    我们在 lib.rs 中,当基于 sheet.cells 信息构建完 rows 信息后,我们此时其实已经收集了可以构建 xml 的所有数据信息。那么,我们就可以调用 sheet_data::get_sheet_data 来处理相关的逻辑。

    sheet_data::get_sheet_data(xx).as_bytes()

    主要代码

    该函数的主要功能是 将传入的 Excel 数据(如单元格内容、列、行、高度、合并单元格等)转换成符合 Excel 2006 XML 格式的字符串(即 元素) 。它生成的 XML 数据可以嵌入到一个 Excel 文件( .xlsx 文件)中,作为 excel 数据部分 。这个过程是通过 构造 XML 元素并为其添加属性和子元素来实现的

    pub fn get_sheet_data(
        cells: Vec<Vec>,
        columns: &Option<Vec<Option>>,
        rows: &Option<Vec<Option>>,
        merged: &Option<Vec>
    ) -> String {
        let mut worksheet = Element::new("worksheet");
        let mut sheet_view = Element::new("sheetView");
        sheet_view.add_attr("workbookViewId""0");
        let mut sheet_views = Element::new("sheetViews");
        sheet_views.add_children(vec![sheet_view]);
        let mut sheet_format_pr = Element::new("sheetFormatPr");
        sheet_format_pr
            .add_attr("customHeight""1")
            .add_attr("defaultRowHeight""15.75")
            .add_attr("defaultColWidth""14.43");

        let mut cols = Element::new("cols");
        let mut cols_children = vec!();

        match columns {
            Some(columns) => {
                for (index, column) in columns.iter().enumerate() {
                    match column {
                        Some(col) => {
                            let mut column_element = Element::new("col");
                            column_element
                                .add_attr("min", (index + 1).to_string())
                                .add_attr("max", (index + 1).to_string())
                                .add_attr("customWidth""1")
                                .add_attr("width", (col.width / WIDTH_COEF).to_string());
                            cols_children.push(column_element);
                        }
                        None => (),
                    }
                }
            }
            None => (),
        }
        let mut rows_info: HashMap<usize, &RowData> = HashMap::new();
        match rows {
            Some(rows) => {
                for (index, column) in rows.iter().enumerate() {
                    match column {
                        Some(row) => {
                            rows_info.insert(index, row);
                        }
                        None => (),
                    }
                }
            }
            None => (),
        }

        let mut sheet_data = Element::new("sheetData");
        let mut sheet_data_rows = vec!();
        for (index, row) in cells.iter().enumerate() {
            let mut row_el = Element::new("row");
            row_el.add_attr("r", (index + 1).to_string());
            match rows_info.get(&index) {
                Some(row_data) => {
                    row_el
                        .add_attr("ht", (row_data.height * HEIGHT_COEF).to_string())
                        .add_attr("customHeight""1");
                }
                None => (),
            }
            let mut row_cells = vec!();
            for cell in row {
                let mut cell_el = Element::new("c");
                cell_el.add_attr("r", &cell.cell);
                match &cell.value {
                    CellValue::Value(ref v) => {
                        let mut value_cell = Element::new("v");
                        value_cell.add_value(v);
                        cell_el.add_children(vec![value_cell]);
                        utils::log!("value {}", v);
                    }
                    CellValue::SharedString(ref s) => {
                        cell_el.add_attr("t""s");
                        let mut value_cell = Element::new("v");
                        value_cell.add_value(s.to_string());
                        cell_el.add_children(vec![value_cell]);
                    }
                    CellValue::None => (),
                }
                row_cells.push(cell_el);
            }

            row_el.add_children(row_cells);
            sheet_data_rows.push(row_el);
        }
        sheet_data.add_children(sheet_data_rows);

        let mut worksheet_children = vec![sheet_views, sheet_format_pr];
        if cols_children.len() > 0 {
            cols.add_children(cols_children);
            worksheet_children.push(cols);
        }
        worksheet_children.push(sheet_data);

        match merged {
            Some(merged) => {
                if merged.len() > 0 {
                    let mut merged_cells_element = Element::new("mergeCells");
                    merged_cells_element.add_attr("count" , merged.len().to_string()).add_children(
                        merged
                            .iter()
                            .map(|MergedCell { from, to }| {
                                let p1 = cell_offsets_to_index(from.row as usize, from.column as usize);
                                let p2 = cell_offsets_to_index(to.row as usize, to.column as usize);
                                let cell_ref = format!("{}:{}", p1, p2);
                                let mut merged_cell = Element::new("mergeCell");
                                merged_cell.add_attr("ref", cell_ref);
                                merged_cell
                            })
                            .collect()
                    );
                    worksheet_children.push(merged_cells_element);
                }
            }
            None => (),
        }

        worksheet
            .add_attr("xmlns:xm""http://schemas.microsoft.com/office/excel/2006/main")
            .add_attr("xmlns:x14ac""http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac")
            .add_attr("xmlns:x14""http://schemas.microsoft.com/office/spreadsheetml/2009/9/main")
            .add_attr("xmlns:mv""urn:schemas-microsoft-com:mac:vml")
            .add_attr("xmlns:mc""http://schemas.openxmlformats.org/markup-compatibility/2006")
            .add_attr("xmlns:mx""http://schemas.microsoft.com/office/mac/excel/2008/main")
            .add_attr("xmlns:r""http://schemas.openxmlformats.org/officeDocument/2006/relationships")
            .add_attr("xmlns""http://schemas.openxmlformats.org/spreadsheetml/2006/main")
            .add_children(worksheet_children);

        worksheet.to_xml()
    }

    核心功能分析

    还记得我们文章刚开始的解压缩后的 test_folder 我们就来看看,我们是如何用代码生成这些信息的。

    1. 初始化工作表元素

    let mut worksheet = Element::new("worksheet");

    首先,创建一个 worksheet 元素,这个元素将表示整个 Excel 工作表,并作为最终的 XML 输出。

    该元素是 sheet 的根元素

    2. 创建 sheetView sheetViews

    let mut sheet_view = Element::new("sheetView");
    sheet_view.add_attr("workbookViewId""0");
    let mut sheet_views = Element::new("sheetViews");
    sheet_views.add_children(vec![sheet_view]);

    sheetView 元素描述了工作表的视图设置(如显示模式等)。这里添加了一个 sheetView 元素,并设置了其 workbookViewId 属性。 sheetViews 是一个容器元素,包含了多个 sheetView 元素。

    3. 设置工作表格式

    let mut sheet_format_pr = Element::new("sheetFormatPr");
    sheet_format_pr
        .add_attr("customHeight""1")
        .add_attr("defaultRowHeight""15.75")
        .add_attr("defaultColWidth""14.43");

    sheetFormatPr 元素定义了工作表的格式,包括行高( defaultRowHeight )和列宽( defaultColWidth )等属性。此处设置了默认行高为 15.75 和默认列宽为 14.43

    4. 处理列数据并生成 cols 元素

    let mut cols = Element::new("cols");
    let mut cols_children = vec!();

    这段代码处理传入的列数据( columns )。如果列数据存在,遍历每一列,并根据列的宽度生成 元素,并将其添加到 cols 中。每个列元素会包含以下属性:







    请到「今天看啥」查看全文