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,一经查实,立即删除!

相关文章

2024年最全网易大数据面试题及参考答案(3万字长文持续更新)

目录 如何评价新用户的留存指标有哪些? 游戏业务中有哪些常用指标? 怎么制定游戏业务的目标 游戏业务中哪三个业务最重要 数据分析指标的阈值怎么确定 怎么衡量你在业务部门的贡献 如何衡量一个活动的ROI 跟领导汇报游戏业务,你会选择哪5个指标,为什么 介绍一下Ha…

【openavis】明厨亮灶算法仓

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

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

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

【MySQL进阶之路 | 高级篇】MVCC解决读写问题

1. 什么是MVCC MVCC (Multiversion Concurrency Control)&#xff0c;多版本并发控制。顾名思义&#xff0c;MVCC是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读操作有了保证。换言之&#xff0c;就是为了查询一些正在…

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

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

[C++] 小游戏 斗破苍穹2.12.2版本 zty出品

大家好&#xff0c;今天zty带来的是斗破苍穹的 2.12.2 版本&#xff0c;这个版本改进了许多皇冠竞技场的bug&#xff0c; 和一些文字仅存在一瞬间便消失了&#xff0c;废话不多说&#xff0c;请看code 先赞后看 养成习惯 CODE #include<stdio.h> #include<iostrea…

因即果,果即因

“有因才有果”、“先有因&#xff0c;后有果”&#xff0c;是人们通常的认知。 事实上有时可以理解为先有果再有因&#xff0c;为了某个果而造了某个因。 时间的方向可以理解为双向的——事情先发生了&#xff0c;然后给一个解释。 例如&#xff0c;某个人为了实现某个目标…

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;根据分…

学习C语言第12天(数组练习)

1.走台阶问题 设一个函数fit(n)是求n阶台阶有几种走法 第一步迈1个台阶 那有fit(n-1)种走法 第一步迈连个台阶 有fit(n-2)种走法 所以n阶台阶一共有fit(n-1)fit(n-2)种走法 斐波那契数列int fit(int n) {if (n < 2)return n;elsereturn fit(n - 1) fit(n - 2); } in…

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

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

LeetCode19 删除链表的倒数第N个结点

前言 题目&#xff1a; 19. 删除链表的倒数第N个结点 文档&#xff1a; 代码随想录——删除链表的倒数第N个结点 编程语言&#xff1a; C 解题状态&#xff1a; 成功解答&#xff01; 思路 最直接的想法就是先获取到链表的整体长度&#xff0c;减去倒数的个数&#xff0c;正向…

【计算机网络】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、下载 总览 自己做的项…

pytorch 源码阅读(3)——torch.fx

0 概述 FX 是一个供开发者用来转换 nn.Module 实例的工具包。FX 包含三个主要组件&#xff1a;符号跟踪器&#xff08;symbolic_traced&#xff09;、中间表示&#xff08;intermediate representation&#xff0c;IR&#xff09;和Python 代码生成&#xff08;Code generatio…

【iOS】——属性关键字

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

W3C XML Schema 活动

关于《W3C XML Schema 活动》的信息&#xff0c;我找到了一些相关资料。XML Schema 是一种基于 XML 的DTD&#xff08;文档类型定义&#xff09;替代物&#xff0c;它提供了对应用程序、文档结构、属性和数据类型的更好支持。XML 1.0 版本支持可定义文档结构的DTD&#xff0c;而…

Linux 动静态库

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