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.rhkb.cn/news/386287.html

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

相关文章

力扣高频SQL 50题(基础版)第二十五题

文章目录 力扣高频SQL 50题&#xff08;基础版&#xff09;第二十五题619.只出现一次的最大数字题目说明实现过程准备数据实现方式结果截图 力扣高频SQL 50题&#xff08;基础版&#xff09;第二十五题 619.只出现一次的最大数字 题目说明 MyNumbers 表&#xff1a; ------…

wpf中轮询显示图片

本文的需求是&#xff0c;在一个文件夹中&#xff0c;放一堆图片的集合&#xff0c;然后在wpf程序中&#xff0c;按照定时的方式&#xff0c;循序显示照片。 全部代码 1.声明一个PictureInfo类 namespace WpfApp1 {public class PictureInfo{public string? FileName { get; …

【计算机网络】三次握手、四次挥手

问&#xff1a;三次握手 四次挥手 TCP 连接过程是 3 次握手&#xff0c;终止过程是 4 次挥手 3次握手 第一步&#xff1a;客户端向服务器发送一个带有 SYN&#xff08;同步&#xff09;标志的包&#xff0c;指示客户端要建立连接。 第二步&#xff1a;服务器收到客户端的请求…

Java设计模式—单例模式(Singleton Pattern)

目录 一、定义 二、应用场景 三、具体实现 示例一 示例二 四、懒汉与饿汉 饿汉模式 懒汉模式 五、总结 六、说明 一、定义 二、应用场景 ‌单例模式的应用场景主要包括以下几个方面&#xff1a; ‌日志系统&#xff1a;在应用程序中&#xff0c;通常只需要一个日…

Spring之Spring Bean的生命周期

Spring Bean的生命周期 通过BeanDefinition获取bean的定义信息调用构造函数实例化beanBean的依赖注入处理Aware接口&#xff08;BeanNameAware、BeanFactoryAware、ApplicationContextAware&#xff09;Bean的后置处理器BeanPostProcessor-前置初始化方法&#xff08;Initiali…

关于@JsonSerialize序列化与@JsonDeserialize反序列化注解的使用(密码加密与解密举例)

注&#xff1a;另一种方式参考 关于TableField中TypeHandler属性&#xff0c;自定义的类型处理器的使用&#xff08;密码加密与解密举例&#xff09;http://t.csdnimg.cn/NZy4G 1.简介 1.1 序列化与反序列化 学习注解之前&#xff0c;我们可以先了解一下什么是序列化与反序列…

115. 不同的子序列 dp入门(一)详细推导dp转移方程式

目录 1. 题目引入&#xff1a; 2. 动态规划解法 2.1 动态dp表示 2.2 动态方程推导: 2.3 具体分析 2.4 初始化 3. 代码如下 java版 c版 Python版 1. 题目引入&#xff1a; 给你两个字符串 s 和 t &#xff0c;统计并返回在 s 的 子序列 中 t 出现的个数&#xff0c;结果…

计算机基础(day1)

1.什么是内存泄漏&#xff1f;什么是内存溢出&#xff1f;二者有什么区别&#xff1f; 2.了解的操作系统有哪些&#xff1f; Windows&#xff0c;Unix&#xff0c;Linux&#xff0c;Mac 3. 什么是局域网&#xff0c;广域网&#xff1f; 4.10M 兆宽带是什么意思&#xff1f;理论…

OAK-FFC 分体式相机使用入门介绍

概述 OAK FFC 主控板和多种可选配镜头模组非常适合灵活的搭建您的3D人工智能产品原型。由于镜头是分体式的&#xff0c;因此你可以根据需要测量的距离&#xff0c;自定义深度相机安装基线&#xff0c;并根据你的项目要求&#xff08;分辨率、快门类型、FPS、光学元件&#xff…

项目风险管理:从理论到实践的探索

项目风险管理&#xff1a;从理论到实践的探索 前言一、项目风险识别二、项目风险应对策略三、综合应对策略结语 前言 在当今快速变化的商业环境中&#xff0c;项目管理已成为组织实现目标的关键工具。然而&#xff0c;项目的成功往往伴随着各种不确定性和潜在风险。有效的风险管…

【Git-驯化】一文搞懂git中rm命令的使用技巧

【Git-驯化】一文搞懂git中rm命令的使用技巧 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内容文档关注&#xff1a;微信公…

五、Spring Boot - 上手篇(1)

&#x1f33b;&#x1f33b;目录 一、快速入门&#xff1a;创建第一个SpringBoot 工程1.1 点击File--->New--->Project...1.2 选择版本和依赖的相关骨架包1.3 设置项目保存目录1.4 项目创建完成&#xff0c;工程主界面如下1.5 项目说明1.6 启动项目1.7 编写 HelloControl…

快速上手,spring boot3整合task实现定时任务

在已经上线的项目中&#xff0c;定时任务是必不可少的。基于spring boot自动装配的原理&#xff0c;我们要集成task定时任务还是非常简单的。只需要简单的两步就可以实现。 1、创建一个spring boot项目&#xff0c;并在项目的启动类&#xff08;也不一定非要是启动类&#xff…

如何排查GD32 MCU复位是由哪个复位源导致的?

上期为大家讲解了GD32 MCU复位包括电源复位和系统复位&#xff0c;其中系统复位还包括独立看门狗复位、内核软复位、窗口看门狗复位等&#xff0c;在一个GD32系统中&#xff0c;如果莫名其妙产生了MCU复位&#xff0c;如何排查具体是由哪个复位源导致的呢&#xff1f; GD32 MC…

【RabbitMQ】MQ相关概念

一、MQ的基本概念 定义&#xff1a;MQ全称为Message Queue&#xff0c;是一种提供消息队列服务的中间件&#xff0c;也称为消息中间件。它允许应用程序通过读写队列中的消息来进行通信&#xff0c;而无需建立直接的连接。作用&#xff1a;主要用于分布式系统之间的通信&#x…

vulntarget-b

实际部署之后centos7 的ip有所变动分别是 :192.168.127.130以及10.0.20.30 Centos7 老规矩还是先用fscan扫一下服务和端口&#xff0c;找漏洞打 直接爆出来一个SSH弱口令…&#xff0c;上来就不用打了&#xff0c;什么意思&#xff1f;&#xff1f;&#xff1f; 直接xshell…

STM32--HAL库--定时器篇

一&#xff1a;如何配置定时器 打开对应工程串口配置好的工程&#xff08;上一篇博客&#xff09;做如下配置&#xff1a; 定时器的中断溢出时间计算公式是&#xff1a; 由图得T100*1000/100MHz 注&#xff1a;100MHz100000000 所以溢出时间等于1ms 关于上图4的自动重装…

【网络安全】文件上传黑白名单及数组绕过技巧

不安全的文件上传&#xff08;Unsafe FileUpload&#xff09; 不安全的文件上传是指Web应用程序在处理用户上传的文件时&#xff0c;没有采取足够的安全措施&#xff0c;导致攻击者可能利用这些漏洞上传恶意文件&#xff0c;进而对服务器或用户造成危害。 目录 一、文件上传…

Unity横板动作游戏 - 素材导入和整理

导入素材 编辑器布局 点击每个窗口右上角的三个点可以有更多的窗口选项。 在屏幕的右上角有一个菜单可以保存布局或读取已经报错的布局。 工具按钮 编辑器上的工具按钮在启动的时候是蓝色的&#xff0c;在不启动的时候是灰色的。 这个按钮将会决定场景中的物体是以锚点显示还…

Oracle配置TCPS加密协议测试

文章目录 一、环境信息二、配置过程1.创建证书2.监听配置2.1.配置sqlnet.ora2.2.配置listener.ora文件2.3.配置tnsnames.ora文件2.4.重载监听 3.数据库本地测试3.1. tcps登录测试3.2.日志监控 一、环境信息 操作系统&#xff1a;Linux 版本信息&#xff1a;Oracle 19c 参考文档…