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

news2024/9/8 23:40:09

文章目录

  • 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"; // 第二行第三列赋值为 2

sheet.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"; // 第二行第三列赋值为 2

sheet.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) ||                         // 不存在 Excel
            package.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;

            // 写入第一个 value
            var 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.coloradmin.cn/o/1957970.html

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

相关文章

探索 Electron:如何进行网址收藏并无缝收录网页图片内容?

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

嵌入式人工智能(28-基于树莓派4B的语音播报模块-SYN6288)

1、语音播报模块 语音播报在一些嵌入式场景中很常见&#xff0c;广泛应用于游戏篮球机音效语音播报&#xff0c;跑步机语音导航&#xff0c;按摩椅语音操作指引&#xff0c;设备故障提示&#xff0c;设备操作引导语音&#xff0c;车载安全语音警示&#xff0c;公共场所语音提示…

系统移植(七)u-boot移植 ④ trusted版本

文章目录 一、U-boot源码适配&#xff08;一&#xff09;执行make stm32mp15_trusted_defconfig命令进行配置&#xff0c;生成.config文件&#xff08;二&#xff09;执行make menuconfig命令&#xff0c;对u-boot源码进行重新配置1. 对u-boot源码进行配置&#xff0c;移除pmic…

Executable Code Actions Elicit Better LLM Agents

Executable Code Actions Elicit Better LLM Agents Github: https://github.com/xingyaoww/code-act 一、动机 大语言模型展现出很强的推理能力。但是现如今大模型作为Agent的时候&#xff0c;在执行Action时依然还是通过text-based&#xff08;文本模态&#xff09;后者JSO…

Java Web——第一天

Web开发课程安排 Web标准 Web标准也称为网页标准&#xff0c;由一系列的标准组成&#xff0c;大部分由W3C( World Wide Web Consortium&#xff0c;万维网联盟)负责制定 三个组成部分: HTML:负责网页的结构 (页面素和内容) CSS:负责网页的表现 (页面元素的外观、位置等页面…

Ubuntu 20.04.6 安装 Elasticsearch

1.准备 -- 系统更新 sudo apt update sudo apt upgrade -- 安装vim 文本编辑器 sudo apt install vim-- jdk 版本确认 java -versionjdk 安装可以参照&#xff1a;https://blog.csdn.net/CsethCRM/article/details/140768670 2.官方下载Elasticsearch 官方地址&#xff1a;h…

PPT图表制作

一、表格的底纹 插入→表格→绘制表格→表设计→选择单元格→底纹 二、把一张图片做成九宫格 1. 把一张图片画成九宫格&#xff08;处理过后还是一张图片&#xff0c;但是有框线&#xff09; 绘制33表格→插入图片→全选表格单元格→右键设置形状格式→填充→图片或纹理填充…

Teamcenter RAC开发,创建Item的两种方式

1、如果描述不必填&#xff0c;采用胖客户端的创建方式 newItem itemType.create(newItemId, "", targetTypeComp.getTypeName(), item_name, // "test1", null, null2、如果描述必填&#xff0c;则需要采用SOA的创…

汇川CodeSysPLC教程03-2-6 ModBus TCP

什么是ModBus TCP&#xff1f; ModBus TCP是一种基于TCP/IP协议的工业网络通信协议&#xff0c;常用于工业自动化和控制系统。它是ModBus协议的一个变种&#xff0c;ModBus协议最初由Modicon&#xff08;现在是施耐德电气的一部分&#xff09;在1979年开发。 以下是ModBus TC…

二分类、多分类、多标签分类的评价指标

前言 在机器学习和深度学习中&#xff0c;常见的分类任务可以分为&#xff1a;二分类&#xff08;Binary Classification); 多分类&#xff08;Multi-class Classification); 多标签分类&#xff08;Multi-label Classification); 序列分类 (Sequence Classification); 图分类…

SQL优化相关

文章目录 SQL优化1. 数据插入2. 主键优化页分裂页合并索引设计原则 3. order by 优化4. group by 优化5. limit优化6. count优化7. update 优化 SQL优化 1. 数据插入 当我们需要插入多条数据时候&#xff0c;建议使用批量插入&#xff0c;因为每次插入数据都会执行一条SQL&am…

使用YApi平台来管理接口

快速上手 进入YApi官网&#xff0c;进行注册登录https://yapi.pro/添加项目 3. 添加分类 4. 添加接口 5. 添加参数 添加返回数据 可以添加期望 验证 YAPI&#xff08;Yet Another Practice Interface&#xff09;是一个现代化的接口管理平台&#xff0c;由淘宝团队…

pip install albumentations安装下载遇19kB/s超级慢细水管解决办法

albumentations 是一个用于图像增强的 Python 库&#xff0c;它提供了丰富的图像变换功能&#xff0c;可以用于数据增强&#xff0c;从而提高深度学习模型的泛化能力。 直接安装命令&#xff1a; pip install albumentations但是如果半夜遇到这种19kB/s的下载速度 为头发着想&…

【通信模块】LoRa与LoRaWAN简介

LoRaWAN网络 技象科技相关文章总结&#xff0c;学习笔记&#xff0c;原文链接如下&#xff0c;转载请标明该出处&#xff1a; LORA&#xff1a; https://www.techphant.cn/tag/l-2 LORAWAN&#xff1a;https://www.techphant.cn/tag/l-3 其他&#xff1a;如LAN https://www…

网络通信---TCP协议1

今日内容 三次握手: 指建立tcp连接时&#xff0c;需要客户端和服务端总共发送三次报文确认连接。 四次挥手&#xff1a; 断开一个tcp连接&#xff0c;需要客户端和服务端发送四个报文以确认断开。 编程模型 TCP报文 客户端 服务端

第6篇文献研读生态廊道相关综述

该文发在生态与农村环境学报。该文章写了生态廊道概念的发展历程、生态廊道类型及功能、生态廊道划定的理论和方法、生态廊道的时间和国内大型生态廊道建设实践。 这篇文章可以让大家了解生态廊道的知识。

Rocketmq-5.3.0和对应dashboard的最新版本Docker镜像,本人亲自制作,部署十分方便,奉献给大家

一、Rocketmq 最新版本5.3.0&#xff0c;采用docker镜像安装方式 二、官方rocketmq镜像对内存等参数配置非常不灵活便利 为了快速、灵活部署rocketmq&#xff0c;以及能方便对其内存等参数进行设置&#xff0c;特意制作了关于它的docker镜像。 三、镜像获取 最新rocketmq-5.…

使用二进制来理解数据和二进制的计算

1 使用二进制来理解数据 和人类的思维习惯不同的是&#xff0c;计算机将把所有的东西数字化之后才会进行处理。那么计算机能理解的数字是什么样的呢&#xff1f;其实&#xff0c;在计算机内部&#xff0c;不管是什么信息都使用二进制来保存和处理的。 计算机为什么要用二进制…

芋道微服务全栈开发日记(商品sku数据归类为规格属性)

商品的每一条规格和属性在数据库里都是单一的一条数据&#xff0c;从数据库里查出来后&#xff0c;该怎么归类为对应的规格和属性值&#xff1f;如下图&#xff1a; 在商城模块&#xff0c;商品的单规格、多规格、单属性、多属性功能可以说是非常完整&#xff0c;如下图&#x…

搭建规范化的vue2项目

项目包含的库 Vue2VuexRouterEslintPrettier 环境 vue&#xff1a;2.6.14 eslint&#xff1a;7.32.0 prettier&#xff1a;2.4.1 eslint-plugin-prettier&#xff1a;4.0.0 eslint-plugin-vue&#xff1a;8.0.3 vue/cli&#xff1a;5.0.8 步骤 全局安装cli工具 npm in…