2024-07-27 Unity Excel —— 使用 EPPlus 插件读取 Excel 文件

文章目录

  • 1 前言
  • 2 项目地址
  • 3 使用方法
    • 3.1 写入 Excel
    • 3.2 读取 Excel
    • 3.3 读写 csv 文件
  • 4 ExcelSheet 代码

1 前言

​ 前几日,一直被如何在 Unity 中读取 Excel 的问题给困扰,网上搜索相关教程相对古老(4、5 年以前了)。之前想用 csv 文件格式替代 Excel 存储,用 WPS 也能打开 csv 文件。但一个明显的坏处是单元格内不能出现逗号(“,”),因为 csv 文件依据逗号分隔单元格。而网上相关 Excel 的插件教程也是摸不着头脑,从 B 站上找了 19 年的 EPPlus 插件视频,结果插件不好用,变量经常报空,而且项目导出也存在问题。也有教程说直接在 NuGet 包中下载 EPPlus 插件,但是 Unity 加载过后就无法识别程序集,直接将 dll 导入 Unity 中的 Plugins 文件夹下也提示无法导入,预测原因之一可能是 EPPlus 插件需要进行验证。

​ 近日,偶然找到可以用的 EPPlus 插件,经过测试没有问题。因此分享到网上,并封装了 ExcelSheet 类,用于更加快速、方便地读写 Excel。由于之前写过 csv 的读写代码,因此也封装到 ExcelSheet 中,支持 xlsx 和 csv 两种文件格式的读写。

2 项目地址

​ Github 地址(项目内附带插件):https://github.com/zheliku/EPPlus-Learning。

3 使用方法

3.1 写入 Excel

public void Save(string filePath, string sheetName = null, FileFormat format = FileFormat.Xlsx);

​ 一个 ExcelSheet 对象即存储了一个表中的内容(Dictionary 中),new 出对象后直接索引每个单元的内容,可读取也可更改:

var sheet = new ExcelSheet();
sheet[0, 0] = "1"; // 第一行第一列赋值为 1
sheet[1, 2] = "2"; // 第二行第三列赋值为 2sheet.Save("test", "Sheet1", ExcelSheet.FileFormat.Xlsx); // 写入文件
image-20240727054509402

​ 上述 4 行代码即可完成 Excel 的写入。因此写入的大致流程为:

  1. 创建一个新对象 sheet;
  2. 通过索引器访问 sheet 的值并修改,没有修改的单元格不会写入,默认为空内容。
  3. 通过 Save() 方法保存到某个 Excel 文件中的某个表中。

注意:

  • 通过索引器修改时,仅仅是缓存中(Dictionary)的值发生变化。要写入文件,必须调用 Save() 方法。
  • Save() 方法:
    1. 第一个参数 filePath 是文件名,不需要带后缀(后缀名由第三个参数在方法内部决定)。
    2. 第二个参数 sheetName 是表名,如果 Excel 中没有该表,则会自动创建并写入内容。表名可不填,不填时默认与文件名称相同。
    3. 第三个参数 format 是保存的格式,有 xlsx 和 csv 两种。
  • 文件默认保存在 ExcelSheet.SAVE_PATH 下,可在外部调用进行更改,或者手动更改 ExcelSheet 代码。

3.2 读取 Excel

public void Load(string filePath, string sheetName = null, FileFormat format = FileFormat.Xlsx);

​ 可以创建 sheet 时就指定文件名进行读取:

var sheet = new ExcelSheet("test"); // 读取 test.xlsx 文件中 test 表的内容

​ 也可以先创建空 sheet,再用 Load 方法读取:

var sheet = new ExcelSheet(); 
sheet.Load("test", "Sheet1") // 读取 test.xlsx 文件中 Sheet1 表的内容

​ 读取后,内容存储在 sheet 的字典中,即可访问使用:

_sheet.Load("test", "Sheet1");for (int i = 0; i < _sheet.RowCount; i++) {     // 行遍历for (int j = 0; j < _sheet.ColCount; j++) { // 列遍历var value = _sheet[i, j];if (string.IsNullOrEmpty(value)) continue;Debug.Log($"Sheet[{i}, {j}]: {value}");}
}

​ 使用 Load 时,默认会先清空 sheet 中原有的内容,再读取表格,即覆盖读取。

3.3 读写 csv 文件

var sheet = new ExcelSheet();
sheet[0, 0] = "1"; // 第一行第一列赋值为 1
sheet[1, 2] = "2"; // 第二行第三列赋值为 2sheet.Save("test", "Sheet1", ExcelSheet.FileFormat.Csv); // 写入 csv 文件
image-20240727054748988

​ 与 Excel 文件的读写类似,只需要更改 format 为 ExcelSheet.FileFormat.Csv 即可,便会保存为 test.csv 文件。需要注意,读写 csv 文件时,参数 sheetName 不起任何作用,因为 csv 文件中没有表。

4 ExcelSheet 代码

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using OfficeOpenXml;/// <summary>
/// Excel 文件存储和读取器
/// </summary>
public partial class ExcelSheet
{public static string SAVE_PATH = Application.streamingAssetsPath + "/Excel/";private int _rowCount = 0; // 最大行数private int _colCount = 0; // 最大列数public int RowCount { get => _rowCount; }public int ColCount { get => _colCount; }private Dictionary<Index, string> _sheetDic = new Dictionary<Index, string>(); // 缓存当前数据的字典public ExcelSheet() { }public ExcelSheet(string filePath, string sheetName = null, FileFormat format = FileFormat.Xlsx) {Load(filePath, sheetName, format);}public string this[int row, int col] {get {// 越界检查if (row >= _rowCount || row < 0)Debug.LogError($"ExcelSheet: Row {row} out of range!");if (col >= _colCount || col < 0)Debug.LogError($"ExcelSheet: Column {col} out of range!");// 不存在结果,则返回空字符串return _sheetDic.GetValueOrDefault(new Index(row, col), "");}set {_sheetDic[new Index(row, col)] = value;// 记录最大行数和列数if (row >= _rowCount) _rowCount = row + 1;if (col >= _colCount) _colCount = col + 1;}}/// <summary>/// 存储 Excel 文件/// </summary>/// <param name="filePath">文件路径,不需要写文件扩展名</param>/// <param name="sheetName">表名,如果没有指定表名,则使用文件名。若使用 csv 格式,则忽略此参数</param>/// <param name="format">保存的文件格式</param>public void Save(string filePath, string sheetName = null, FileFormat format = FileFormat.Xlsx) {string fullPath  = SAVE_PATH + filePath + FileFormatToExtension(format); // 文件完整路径var    index     = fullPath.LastIndexOf("/", StringComparison.Ordinal);var    directory = fullPath[..index];if (!Directory.Exists(directory)) { // 如果文件所在的目录不存在,则先创建目录Directory.CreateDirectory(directory);}switch (format) {case FileFormat.Xlsx:SaveAsXlsx(fullPath, sheetName);break;case FileFormat.Csv:SaveAsCsv(fullPath);break;default: throw new ArgumentOutOfRangeException(nameof(format), format, null);}Debug.Log($"ExcelSheet: Save sheet \"{filePath}::{sheetName}\" successfully.");}/// <summary>/// 读取 Excel 文件/// </summary>/// <param name="filePath">文件路径,不需要写文件扩展名</param>/// <param name="sheetName">表名,如果没有指定表名,则使用文件名</param>/// <param name="format">保存的文件格式</param>public void Load(string filePath, string sheetName = null, FileFormat format = FileFormat.Xlsx) {// 清空当前数据Clear();string fullPath = SAVE_PATH + filePath + FileFormatToExtension(format); // 文件完整路径if (!File.Exists(fullPath)) { // 不存在文件,则报错Debug.LogError($"ExcelSheet: Can't find path \"{fullPath}\".");return;}switch (format) {case FileFormat.Xlsx:LoadFromXlsx(fullPath, sheetName);break;case FileFormat.Csv:LoadFromCsv(fullPath);break;default: throw new ArgumentOutOfRangeException(nameof(format), format, null);}Debug.Log($"ExcelSheet: Load sheet \"{filePath}::{sheetName}\" successfully.");}public void Clear() {_sheetDic.Clear();_rowCount = 0;_colCount = 0;}
}public partial class ExcelSheet
{public struct Index{public int Row;public int Col;public Index(int row, int col) {Row = row;Col = col;}}/// <summary>/// 保存的文件格式/// </summary>public enum FileFormat{Xlsx,Csv}private string FileFormatToExtension(FileFormat format) {return $".{format.ToString().ToLower()}";}private void SaveAsXlsx(string fullPath, string sheetName) {var index    = fullPath.LastIndexOf("/", StringComparison.Ordinal);var fileName = fullPath[(index + 1)..];sheetName ??= fileName[..fileName.IndexOf(".", StringComparison.Ordinal)]; // 如果没有指定表名,则使用文件名var       fileInfo = new FileInfo(fullPath);using var package  = new ExcelPackage(fileInfo);if (!File.Exists(fullPath) ||                         // 不存在 Excelpackage.Workbook.Worksheets[sheetName] == null) { // 或者没有表,则添加表package.Workbook.Worksheets.Add(sheetName);       // 创建表时,Excel 文件也会被创建}var sheet = package.Workbook.Worksheets[sheetName];var cells = sheet.Cells;cells.Clear(); // 先清空数据foreach (var pair in _sheetDic) {var i = pair.Key.Row;var j = pair.Key.Col;cells[i + 1, j + 1].Value = pair.Value;}package.Save(); // 保存文件}private void SaveAsCsv(string fullPath) {using FileStream fs = new FileStream(fullPath, FileMode.Create, FileAccess.Write);Index idx = new Index(0, 0);for (int i = 0; i < _rowCount; i++) {idx.Row = i;idx.Col = 0;// 写入第一个 valuevar value = _sheetDic.GetValueOrDefault(idx, "");if (!string.IsNullOrEmpty(value))fs.Write(Encoding.UTF8.GetBytes(value));// 写入后续 value,需要添加 ","for (int j = 1; j < _colCount; j++) {idx.Col = j;value   = "," + _sheetDic.GetValueOrDefault(idx, "");fs.Write(Encoding.UTF8.GetBytes(value));}// 写入 "\n"fs.Write(Encoding.UTF8.GetBytes("\n"));}}private void LoadFromXlsx(string fullPath, string sheetName) {var index    = fullPath.LastIndexOf("/", StringComparison.Ordinal);var fileName = fullPath[(index + 1)..];sheetName ??= fileName[..fileName.IndexOf(".", StringComparison.Ordinal)]; // 如果没有指定表名,则使用文件名var fileInfo = new FileInfo(fullPath);using var package = new ExcelPackage(fileInfo);var sheet = package.Workbook.Worksheets[sheetName];if (sheet == null) { // 不存在表,则报错Debug.LogError($"ExcelSheet: Can't find sheet \"{sheetName}\" in file \"{fullPath}\"");return;}_rowCount = sheet.Dimension.Rows;_colCount = sheet.Dimension.Columns;var cells = sheet.Cells;for (int i = 0; i < _rowCount; i++) {for (int j = 0; j < _colCount; j++) {var value = cells[i + 1, j + 1].Text;if (string.IsNullOrEmpty(value)) continue; // 有数据才记录_sheetDic.Add(new Index(i, j), value);}}}private void LoadFromCsv(string fullPath) {// 读取文件string[] lines = File.ReadAllLines(fullPath); // 读取所有行for (int i = 0; i < lines.Length; i++) {string[] line = lines[i].Split(','); // 读取一行,逗号分割for (int j = 0; j < line.Length; j++) {if (line[j] != "") // 有数据才记录_sheetDic.Add(new Index(i, j), line[j]);}// 更新最大行数和列数_colCount = Mathf.Max(_colCount, line.Length);_rowCount = i + 1;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/49507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【openavis】明厨亮灶算法仓

明厨亮灶算法仓主要用于学校食堂&#xff0c;餐厅等饮食卫生安全监管场景&#xff0c;目前包含的算法如下&#xff1a; 算法类型 算法卡片 明厨亮灶算法仓 老鼠检测 垃圾桶未盖 厨师服检测 厨师帽检测 口罩检测 手套检测 动火离人 1. 算法规格介绍&#xff1a; 算…

基于STM32瑞士军刀--【FreeRTOS开发】学习笔记(一)|| RISC / 底层代码执行步骤 / 汇编指令

本篇文章基于韦东山老师讲课笔记和自己理解编写。 RISC ARM芯片属于精简指令集计算机(RISC&#xff1a;Reduced Instruction Set Computing)&#xff0c;它所用的指令比较简单&#xff0c;有如下特点&#xff1a; ① 对内存只有读、写指令 ② 对于数据的运算是在CPU内部实现 …

你在找提升效率的解决方案还是追求效果的解决方案

企业在寻求“解决方案”时&#xff0c;最好先想清楚&#xff0c;你是想提升某项工作的效率&#xff0c;还是要改善某项工作的效果&#xff1f; 提升效率的解决方案主要是为了在保证质量的前提下提升某项确定工作的完成速度。以政务解决方案为例&#xff1a;当任何人都能通过移…

HAL STM32 SPI/ABZ/PWM方式读取MT6816磁编码器数据

HAL STM32 SPI/ABZ/PWM方式读取MT6816磁编码器数据 &#x1f4da;MT6816相关资料&#xff08;来自商家的相关资料&#xff09;&#xff1a; 资料&#xff1a;https://pan.baidu.com/s/1CAbdLBRi2dmL4D7cFve1XA?pwd8888 提取码&#xff1a;8888&#x1f4cd;驱动代码编写&…

某量JS逆向

https://chat.sensetime.com/wb/chat 目录 一、发起请求 二、观察发现只有入参 __data__ 进行了加密&#xff0c;返回是明文 三、 观察JS调用栈 四、从JS中搜索 __data__ 五、使用XHR对Ajax请求进行断点 六、再次发起请求就会断点拦住请求 七、对XHR入口分析 八、逐个…

【C++】选择结构- 嵌套if语句

嵌套if语句的语法格式&#xff1a; if(条件1) { if(条件1满足后判断是否满足此条件) {条件2满足后执行的操作} else {条件2不满足执行的操作} } 下面是一个实例 #include<iostream> using namespace std;int main4() {/*提示用户输入一个高考分数&#xff0c;根据分…

花几千上万学习Java,真没必要!(二十九)

1、基本数据类型包装类&#xff1a; 测试代码1&#xff1a; package apitest.com; //使用Integer类的不同方法处理整数。 //将字符串转换为整数&#xff08;parseInt&#xff09;和Integer对象&#xff08;valueOf&#xff09;&#xff0c; //将整数转换回字符串&#xff08;…

【计算机网络】DNS命令练习与抓包分析实验

一&#xff1a;实验目的 1&#xff1a;掌握DNS缓存的清除方法&#xff0c;了解DNS缓存的作用和影响。 2&#xff1a;熟悉nslookup和dig等DNS查询工具的使用&#xff0c;理解DNS查询的基本原理和过程。 3&#xff1a;通过抓包和分析&#xff0c;深入了解DNS查询和响应消息的格…

html+css 实现悬浮按钮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽效果&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 文…

Python+Flask+MySQL+日线指数与情感指数预测的股票信息查询系统【附源码,运行简单】

PythonFlaskMySQL日线指数与情感指数预测的股票信息查询系统【附源码&#xff0c;运行简单】 总览 1、《股票信息查询系统》1.1 方案设计说明书设计目标工具列表 2、详细设计2.1 登录2.2 程序主页面2.3 个人中心界面2.4 基金详情界面2.5 其他功能贴图 3、下载 总览 自己做的项…

【iOS】——属性关键字

属性关键字的类型 在iOS中属性关键字分为四种类型&#xff1a; 可访问性: readonly ,readwrite原子性 &#xff1a; atomic &#xff0c;nonatomic内存管理 &#xff1a; retain/strong/copy&#xff0c; assign/unsafe_unretained&#xff0c;weak方法命名&#xff1a;sette…

Linux 动静态库

一、动静态库 1、库的理解 库其实是给我们提供方法的实现&#xff0c;如上面的对于printf函数的实现就是在库中实现的&#xff0c;而这个库也就是c标准库&#xff0c;本质也是文件&#xff0c;也有对应的路径 2、区别 静态库是指编译链接时&#xff0c;把库文件的代码全部加入…

前端canvas——赛贝尔曲线

曲线之美&#xff0c;不在于曲线本身&#xff0c;而在于用的人。 所以就有了这期赛贝尔曲线。 新规矩&#xff0c;先上个GIT。 效果图 开局一张图&#xff0c;代码全靠编。 代码 画骨 先想着怎么画一个心形吧&#xff0c;等你想好了&#xff0c;就知道怎么画了。 首先就还…

Cyberchef基础概念之-循环语句操作-Jump/Label

在本专栏的前面的文章介绍了fork,merge,subsection,register等多种概念来解决实际场景的问题。本文将介绍的Jump/Label的操作类似于编程语言中的for和while的功能&#xff0c;相信在学会使用jump操作后&#xff0c;将有助于解决更为复杂的数据处理问题。 本文将详细的介绍该操…

每日OJ_力扣+牛客_另类加法_不用加号的加法

目录 力扣面试题 17.01. 不用加号的加法 解析代码 牛客另类加法 解析代码 力扣面试题 17.01. 不用加号的加法 面试题 17.01. 不用加号的加法 设计一个函数把两个数字相加。不得使用 或者其他算术运算符。 示例: 输入: a 1, b 1 输出: 2提示&#xff1a; a, b 均可能…

OpenSSL学习笔记及在项目中的使用

OpenSSL官方命令手册&#xff1a;OpenSSL commands - OpenSSL Documentation 参考教程&#xff1a; 操作&#xff1a;OpenSSL的基本使用教程(一&#xff09;_openssl.exe使用教程-CSDN博客 操作&#xff1a;Linux和Shell回炉复习系列文章总目录 - 骏马金龙 - 博客园 (cnblog…

【MR】现代机器人学-时间最优时间缩放

MR章节目录 第2章 配置空间 第3章 刚体运动 第4章 正向运动学 第5章 速度运动学与静力学 第6章 逆向运动学 第7章 闭链运动学 第8章 开链动力学 第9章 轨迹生成 9.1 定义 9.2 点到点轨迹 9.3 多项式通过点轨迹 9.4 时间最优时间缩放 第10章 运动规划 第11章 机器人控制 第12章 …

20 Python常用内置函数——eval()

内置函数 eval() 函数用来计算字符串的值&#xff0c;在有些场合也可以用来实现类型转换的功能。除此之外&#xff0c;eval() 也可以对字节串进行求值&#xff0c;还可以执行内置函数 compile() 编译生成的代码对象。 print(eval(b35)) print([eval(8), type(eval(8))]) # 把…

Keras入门:一维线性回归问题

目录 一、一维变量线性回归 1. 数据生成 2. 建立训练模型 3. 作图 4. 完整代码 一、一维变量线性回归 1. 数据生成 import keras import numpy as np import matplotlib.pyplot as plt #matplotlib inline xnp.linspace(0, 100, 30) #0~100之间&#xff0c;生成30个数 y…

构建现代数据湖

现代数据湖是一半数据仓库和一半数据湖&#xff0c;对所有事情都使用对象存储。使用对象存储来构建数据仓库是通过 Open Table Formats OTF&#xff09; 实现的&#xff0c;例如 Apache Iceberg、Apache Hudi 和 Delta Lake&#xff0c;这些规范一旦实现&#xff0c;就可以无缝…