Skip to content

jokance/CSExcelConverter

Repository files navigation

C# Excel导出工具

这是一个功能强大的Excel配置文件导出工具,支持将Excel数据转换为高效的C#代码,包含字典映射、嵌套字典映射、常量定义等多种格式。

功能特性

核心功能

  • Dict模式:将Excel数据导出为C#字典结构
  • Const模式:将Excel数据导出为C#常量定义
  • MulDict模式:将Excel数据导出为嵌套字典结构(多层键值映射)
  • 多端支持:分别导出到客户端(c)、服务端(s)或两者(cs)
  • 多文件合并:支持从多个Excel文件中读取数据并合并到一个字典
  • 字段常量优化:自动生成字段名常量,减少内存占用
  • 空值跳过:自动跳过空值字段,生成更简洁的代码
  • 列过滤:以#开头的列将被自动跳过
  • PostProcess后处理系统:支持自定义数据转换和类成员添加

数据类型支持

基础类型

  • int:整数类型
  • float:单精度浮点数
  • double:双精度浮点数
  • bool:布尔值(支持true/false和0/1)
  • string:字符串类型

集合类型

  • list:字符串列表(向后兼容)
  • list(int):整数列表
  • list(float):浮点数列表
  • list(double):双精度列表
  • list(bool):布尔值列表
  • list(string):字符串列表

注意:所有列表类型生成为完全不可修改的ImmutableArray<T>,保证数据完全安全性

字典类型

  • dict(string:int):字符串到整数的映射
  • dict(string:float):字符串到浮点数的映射
  • dict(string:bool):字符串到布尔值的映射
  • dict(string:string):字符串到字符串的映射
  • dict(keyType:valueType):支持任意类型组合
  • 嵌套字典dict(string:dict(int:int)) 等复杂结构

引用类型

  • refer(filename.sheetname.columnname):引用其他表格中的数据
    • 示例:refer(npc.NPC.type_id) - 引用npc.xlsx文件中NPC工作表的type_id列
    • 自动类型检测:自动推断被引用列的实际数据类型(int、string等)
    • 引用验证:导入时自动验证引用值的有效性,无效值会立即报错
    • 子目录支持:支持在ExcelRootDirectory的子目录中查找引用文件
    • 跨文件引用:可以引用其他Excel文件中的任意工作表和列
    • 同文件引用:也可以引用当前文件中的其他工作表

本地化类型

  • texttext(language):本地化字符串类型
    • 示例:texttext(zh)text(en)
    • TextId模式:生成基于哈希的文本ID,运行时从翻译文件加载
    • 后缀标识支持"apple#company" vs "apple#fruit" 产生不同翻译
    • 多语言支持:支持任意数量的目标语言
    • 翻译文件生成:自动生成翻译模板JSON文件
    • 运行时切换:支持动态语言切换,无需重新编译

类型修饰符

  • #unique:唯一性修饰符,确保该列中的所有数据都是唯一的
    • 示例:int#uniquestring#unique
    • 如果发现重复值,会抛出异常并指明具体的行号和重复值
    • 支持所有基础数据类型的唯一性验证

Excel文件格式

#Setting工作表配置

说明 示例
第1列 导出端 c(客户端), s(服务端), cs(两者)
第2列 导出模式 dict, const, mul_dict
第3列 Sheet映射 camera=CAMERA,item=ITEM (dict模式)
constants (const模式)
monsterdata=MONSTER_DATA (mul_dict模式)
第4列 输出文件名 Config.cs
第5列 额外参数 (预留)
第6列 多源文件 npc_1.xlsx,npc_2.xlsx,npc_3.xlsx
第7列 本地化模式 textid, direct, embedded (留空使用全局配置)

Dict模式工作表格式

说明 示例
第1行 说明文字 ID, 名称, #隐藏列, 数量, 价格, 类型引用, 唯一编码
第2行 字段名 id, name, hidden, count, price, type_ref, code
第3行 数据类型 int, string, string, list(int), dict(string:float), refer(types.ItemType.type_id), string#unique
第4行 导出端 cs, c, s, cs, c, cs, cs
第5行+ 数据行 1, "道具1", "", "10,20,30", "{"buy":9.99,"sell":4.99}", 1001, "ITEM_001"

说明

  • 以#开头的列(如"#隐藏列")将被自动跳过
  • 空值字段不会出现在生成的代码中
  • list类型用逗号分隔:"10,20,30"
  • dict类型使用JSON格式:"{\"key\":value}"
  • refer类型自动检测被引用列的数据类型,在上例中type_ref字段会变成int类型
  • #unique修饰符的列将进行唯一性验证,如发现重复值会立即抛出异常

Const模式工作表格式

说明 示例
第1列 常量名 MAX_LEVEL
第2列 导出端 cs
第3列 数据类型 int
第4列 常量值 100

MulDict模式工作表格式

MulDict模式用于创建嵌套字典结构,其中第一列作为主键,第二列作为子键:

说明 示例
第1行 说明文字 怪物ID, 等级, 名称, 血量, 伤害
第2行 字段名 monsterId, level, name, health, damage
第3行 数据类型 int, int, string, int, int
第4行 导出端 cs, cs, c, cs, c
第5行+ 数据行 1, 1, "哥布林", 50, 10

关键特点

  • 第一列(主键):作为外层字典的键值
  • 第二列(子键):作为内层字典的键值
  • 其余列:作为最内层字典的数据字段
  • 嵌套结构:生成 Dictionary<主键类型, Dictionary<子键类型, Dictionary<string, object>>>

使用场景

  • 游戏怪物数据:MONSTER_DATA[怪物ID][等级]
  • 物品升级数据:ITEM_UPGRADE[物品ID][等级]
  • 技能配置数据:SKILL_CONFIG[技能ID][等级]

使用方法

命令行模式

# 编译项目
dotnet build

# 基本用法
dotnet run config.xlsx

# 指定输出目录
dotnet run config.xlsx ./output

# 自定义客户端和服务端子目录
dotnet run config.xlsx ./output frontend backend

# 完整用法
dotnet run <Excel文件路径> [输出目录] [客户端子目录] [服务端子目录]

交互式模式

# 启动交互界面
dotnet run

# 然后选择:
# 1. 处理单个Excel文件
# 2. 处理多个Excel文件  
# 3. 处理目录中的所有Excel文件

交互模式会提示你配置:

  • 输出根目录 (默认: ./output)
  • 客户端子目录 (默认: client)
  • 服务端子目录 (默认: server)

输出结构

默认结构

output/
├── client/          # 客户端代码
│   └── InfoNpc.cs
└── server/          # 服务端代码
    └── InfoNpcConst.cs

自定义目录结构

# 使用自定义目录名
dotnet run config.xlsx ./game_config frontend backend
game_config/
├── frontend/        # 自定义客户端目录
│   └── InfoNpc.cs
└── backend/         # 自定义服务端目录
    └── InfoNpcConst.cs

示例输出

Dict模式输出示例(优化版)

using System.Collections.Generic;
using System.Collections.Immutable;

namespace GameConfig
{
    public static class ItemConfig
    {
        // 字段名常量
        public const string NAME = "name";
        public const string STATS = "stats";
        public const string PRICES = "prices";
        public const string CONFIG = "config";
        
        public static readonly Dictionary<int, Dictionary<string, object>> ITEMS = new()
        {
            [1001] = new Dictionary<string, object>
            {
                [NAME] = "魔剑",
                [STATS] = ImmutableArray.Create(new int[] { 100, 200, 50 }),
                [PRICES] = new Dictionary<string, float> { ["buy"] = 99.99f, ["sell"] = 49.99f },
                [CONFIG] = new Dictionary<string, bool> { ["tradeable"] = true, ["droppable"] = false },
            },
            [1002] = new Dictionary<string, object>
            {
                [NAME] = "盾牌",
                [STATS] = ImmutableArray.Create(new int[] { 50, 500, 10 }),
                [PRICES] = new Dictionary<string, float> { ["buy"] = 150f, ["sell"] = 75f },
                // 注意:空的config字段被自动跳过
            },
        };
    }
}

Const模式输出示例

namespace GameConfig
{
    public static class Constants
    {
        public static readonly int MAX_LEVEL = 100;
        public static readonly float CRITICAL_RATE = 0.25f;
        public static readonly bool DEBUG_MODE = false;
        public static readonly string GAME_VERSION = "1.0.0";
    }
}

MulDict模式输出示例

using System.Collections.Generic;

namespace GameConfig
{
    public static class MonsterData
    {
        // 字段名常量
        public const string NAME = "name";
        public const string HEALTH = "health";
        public const string DAMAGE = "damage";
        
        public static readonly Dictionary<int, Dictionary<int, Dictionary<string, object>>> MONSTER_DATA = new()
        {
            [1] = new Dictionary<int, Dictionary<string, object>>
            {
                [1] = new Dictionary<string, object>
                {
                    [NAME] = "哥布林",
                    [HEALTH] = 50,
                    [DAMAGE] = 10,
                },
                [5] = new Dictionary<string, object>
                {
                    [NAME] = "哥布林战士",
                    [HEALTH] = 120,
                    [DAMAGE] = 25,
                },
                [10] = new Dictionary<string, object>
                {
                    [NAME] = "哥布林精英",
                    [HEALTH] = 250,
                    [DAMAGE] = 45,
                },
            },
            [2] = new Dictionary<int, Dictionary<string, object>>
            {
                [1] = new Dictionary<string, object>
                {
                    [NAME] = "兽人",
                    [HEALTH] = 80,
                    [DAMAGE] = 15,
                },
                [5] = new Dictionary<string, object>
                {
                    [NAME] = "兽人狂战士",
                    [HEALTH] = 180,
                    [DAMAGE] = 35,
                },
            },
        };
    }
}

使用方式

// 访问1号怪物5级的数据
var goblinLevel5 = MonsterData.MONSTER_DATA[1][5];
string name = (string)goblinLevel5[MonsterData.NAME];     // "哥布林战士"
int health = (int)goblinLevel5[MonsterData.HEALTH];       // 120
int damage = (int)goblinLevel5[MonsterData.DAMAGE];       // 25

// 检查某个怪物是否存在特定等级
if (MonsterData.MONSTER_DATA.ContainsKey(1) && 
    MonsterData.MONSTER_DATA[1].ContainsKey(10))
{
    // 处理1号怪物10级数据
}

数据类型详细说明

Bool类型支持

支持多种布尔值表示方式:

  • 文本形式true, false (不区分大小写)
  • 数字形式1=true, 0=false
  • 列表形式"1,0,true,false"ImmutableArray.Create(new bool[] { true, false, true, false })

List类型使用

Excel列类型: list(int)
Excel单元格值: "100,200,50"
生成C#代码: ImmutableArray.Create(new int[] { 100, 200, 50 })

完全不可修改性

  • 使用ImmutableArray<T>类型,完全不可变
  • 支持索引访问:array[0]array.Length等读取操作
  • 无法修改元素:array[0] = newValue 编译错误
  • 无修改方法:没有Add()Remove()Clear()等修改方法
  • 修改操作返回新数组:array.Add(item) 返回新的ImmutableArray,原数组不变
  • 保证配置数据的绝对安全性和不变性

Dict类型使用

Excel列类型: dict(string:int)  
Excel单元格值: {"attack":100,"defense":50}
生成C#代码: new Dictionary<string, int> { ["attack"] = 100, ["defense"] = 50 }

嵌套Dict使用

Excel列类型: dict(string:dict(int:int))
Excel单元格值: {"bonus":{"1":10,"2":20}}
生成C#代码: new Dictionary<string, Dictionary<int, int>> { 
    ["bonus"] = new Dictionary<int, int> { [1] = 10, [2] = 20 } 
}

Refer类型使用

基本语法

Excel列类型: refer(filename.sheetname.columnname)

实际示例

1. 跨文件引用

文件结构:
├── config.xlsx           # 主配置文件
├── Excel/
│   ├── npc/
│   │   └── npc_data.xlsx # NPC数据文件
│   └── items.xlsx        # 物品数据文件

config.xlsx 中的配置:
字段名: npc_type
数据类型: refer(npc_data.NPC.type_id)
数据值: 1001

系统会:
1. 在Excel/子目录中找到 npc_data.xlsx
2. 读取NPC工作表的type_id列
3. 检测type_id列的数据类型(如int)
4. 验证1001是否存在于type_id列中
5. 生成时使用int类型:[NPC_TYPE] = 1001

2. 同文件引用

在同一个Excel文件中引用其他工作表:
数据类型: refer(config.ItemTypes.id)
// 引用当前文件中ItemTypes工作表的id列

目录搜索策略

系统会按以下优先级查找引用文件:

  1. ExcelRootDirectory递归搜索 (默认./Excel)

    • 在Excel目录及其所有子目录中查找
    • 支持任意深度的嵌套目录结构
  2. 当前Excel文件目录

    • 在当前处理的Excel文件所在目录查找
  3. 当前工作目录

    • 在程序运行的根目录查找

错误处理

引用文件不存在:

警告: 无法找到引用的工作表或数据 npc_data.xlsx#NPC.type_id

引用值无效:

错误: 字段 'npc_type' 在第 5 行存在无效引用值: 9999,
请检查 npc_data.xlsx#NPC.type_id

适用场景

  • 外键关系:物品引用物品类型、NPC引用NPC类型
  • 配置层级:子配置引用主配置的ID
  • 数据完整性:确保引用关系的有效性
  • 分表管理:大型配置分散在多个文件中管理

高级功能

多文件合并功能

功能概述

当需要处理大量数据时(如NPC、物品等),可以将数据分散到多个Excel文件中,然后在导出时自动合并到一个字典中。

配置方式

在#Setting工作表的第6列(sources列)配置需要合并的源文件:

target | mode | key   | filename | extra | sources
both   | dict | NpcId | InfoNpc  |       | npc_1.xlsx,npc_2.xlsx,npc_3.xlsx

工作机制

数据读取顺序:

  1. 当前文件优先:首先读取配置文件本身的对应sheet数据
  2. 源文件依次读取:按sources配置的顺序读取每个文件
  3. 数据自动合并:所有数据合并到同一个字典中
  4. 唯一性验证:跨所有文件维护#unique约束

列结构定义:

  • 从第一个源文件读取列定义(字段名、类型、导出端等)
  • 所有文件必须具有相同的列结构
  • 支持当前文件没有对应sheet,仅从源文件读取

使用示例

文件结构:

config.xlsx           # 主配置文件,包含#Setting
├── npc_1.xlsx        # NPC数据文件1:基础NPC
├── npc_2.xlsx        # NPC数据文件2:精英NPC  
└── npc_3.xlsx        # NPC数据文件3:BOSS NPC

#Setting配置:

target | mode | mappings | filename | extra | sources
both   | dict | npcs     | InfoNpc  |       | npc_1.xlsx,npc_2.xlsx,npc_3.xlsx

执行结果:

  • 读取config.xlsx中的npcs工作表(如果存在)
  • 读取npc_1.xlsx中的npcs工作表
  • 读取npc_2.xlsx中的npcs工作表
  • 读取npc_3.xlsx中的npcs工作表
  • 所有NPC数据合并生成InfoNpc.cs

支持的模式

  • Dict模式:多文件数据合并到单个字典
  • Const模式:多文件常量合并到单个常量类
  • MulDict模式:多文件嵌套字典合并

错误处理

  • 文件不存在:显示警告但继续处理其他文件
  • 工作表缺失:显示警告但继续处理其他文件
  • 唯一性冲突:立即抛出异常,显示重复值的文件和行号
  • 列结构不匹配:使用第一个源文件的列定义为准

适用场景

  • 大型数据集:NPC数据、物品数据、技能数据等
  • 分类管理:按类别、等级、区域分散到不同文件
  • 团队协作:多人维护不同类型的配置数据
  • 版本管理:便于Git等版本控制系统管理

性能优化

  • 内存优化:逐个文件读取,避免同时加载所有文件
  • 唯一性高效验证:使用HashSet进行O(1)重复检查
  • 错误快速定位:精确显示问题文件和行号

本地化系统

概述

本地化系统提供高效的多语言支持,采用文本ID方式。您只需在Excel中填写主要语言的文本,系统会自动生成文本ID,运行时从翻译文件中动态加载对应语言的翻译。

核心优势

  • 简化Excel编辑:Excel中只需填写主语言文本,无需复杂的多语言配置
  • 运行时翻译:支持动态语言切换,无需重新编译代码
  • 独立翻译管理:翻译人员可以独立处理JSON翻译文件
  • 基于哈希的文本ID:一致性强、抗冲突的文本标识系统
  • 上下文消歧:使用后缀标记支持同文本不同翻译
  • 优雅降级:缺失翻译时的后备机制

全局配置

本地化设置在全局级别配置,确保所有导出的一致性:

var globalConfig = new GlobalConfig
{
    SupportedLanguages = "en,zh,ja,fr",           // 支持的语言代码
    DefaultLocalizationMode = LocalizationMode.TextId, // 默认本地化模式
    PrimaryLanguage = "en",                       // 主语言(Excel中使用)
    LocalizationOutputPath = "Localization"      // 翻译文件输出目录
};

本地化模式

  • TextId模式(推荐):生成文本ID,翻译在运行时加载
  • Direct模式:直接使用原始文本,不进行本地化
  • Embedded模式:传统的内嵌翻译模式(不推荐新项目使用)

使用文本类型

在Excel列类型定义中使用text类型:

// 列类型定义
text           // 使用全局默认主语言
text(zh)       // 指定中文为主语言
text(en)       // 指定英文为主语言

// Excel单元格数据(简单文本格式)
"Hello World"
"你好世界"

// 上下文消歧(后缀标记)
"apple"           // 将翻译为 "苹果"
"apple#company"   // 将翻译为 "苹果公司"  
"apple#fruit"     // 明确标识为水果(如需要区分)

基于哈希的文本ID系统

系统为每个文本生成基于哈希的稳定ID:

  • "Hello World"T8B7C4D2F (Hello World的哈希)
  • "apple"TA1B2C3D4 (apple的哈希)
  • "apple#company"T5E6F7A8B (apple#company的哈希)

这确保了:

  • 相同文本=相同ID:相同文本在不同表格中共享翻译
  • 不同上下文=不同ID:后缀标记创建独特翻译
  • 抗冲突性:基于SHA-256的哈希防止ID冲突

Excel数据格式示例

#Setting工作表配置

| Target | Mode | Mappings | FileName | ExtraParams | Sources | LocalizationMode |
|--------|------|----------|----------|-------------|---------|------------------|
| cs     | dict | Items    | Items.cs |             |         |                  |

说明: LocalizationMode列为可选,留空则使用全局默认模式。

数据工作表示例(Items)

| Description | Field Name | Type     | Target | Row 1 Data          | Row 2 Data            |
|-------------|------------|----------|--------|---------------------|-----------------------|
| Item ID     | id         | int      | cs     | 1                   | 2                     |
| Item Name   | name       | text     | cs     | Sword               | Shield                |
| Description | desc       | text     | cs     | A sharp blade       | Protective gear       |
| Price       | price      | int      | cs     | 100                 | 150                   |

关键特点:Excel中只需直接填写主语言文本,无需复杂的JSON格式!

生成的代码示例

TextId模式输出(推荐)

生成的C#代码 (Items.cs):

using System.Collections.Generic;
using System.Collections.Immutable;
using CSExcelConverter.Models;

namespace GameConfig
{
    public static class Items
    {
        public static readonly Dictionary<int, Dictionary<string, object>> ITEMS = new()
        {
            [1] = new Dictionary<string, object>
            {
                ["name"] = "T8B7C4D2F",     // "Sword"的哈希ID
                ["desc"] = "TA1B2C3D4",    // "A sharp blade"的哈希ID
                ["price"] = 100,
            },
            [2] = new Dictionary<string, object>
            {
                ["name"] = "T5E6F7A8B",     // "Shield"的哈希ID
                ["desc"] = "T9F8E7D6C",    // "Protective gear"的哈希ID
                ["price"] = 150,
            },
        };

        /// <summary>
        /// 获取本地化文本,如果找不到翻译则返回文本ID
        /// </summary>
        public static string GetText(string textId, params object[] args)
        {
            return LocalizationService.Instance.GetText(textId, textId, args);
        }

        /// <summary>
        /// 获取本地化文本,指定默认文本
        /// </summary>
        public static string GetText(string textId, string defaultText, params object[] args)
        {
            return LocalizationService.Instance.GetText(textId, defaultText, args);
        }
    }
}

生成的支持文件

TextIds.json(文本ID映射表):

{
    "GeneratedTime": "2025-09-06 20:30:00",
    "TotalEntries": 4,
    "Entries": [
        {
            "TextId": "T8B7C4D2F",
            "OriginalText": "Sword",
            "Context": "Items.name",
            "SheetName": "Items",
            "ColumnName": "name",
            "RowIndex": 5
        },
        {
            "TextId": "TA1B2C3D4", 
            "OriginalText": "A sharp blade",
            "Context": "Items.desc",
            "SheetName": "Items",
            "ColumnName": "desc", 
            "RowIndex": 5
        }
        // ... 更多条目
    ]
}

Translations_ZH.json(翻译模板):

{
    "$meta": {
        "Language": "zh",
        "GeneratedTime": "2025-09-06 20:30:00",
        "TotalEntries": 4,
        "Instructions": [
            "请将右侧的原文翻译为对应的目标语言",
            "保持占位符 {0}, {1} 等不变",
            "如果不需要翻译某个条目,可以删除该行或设置为空字符串"
        ]
    },
    "T8B7C4D2F": {
        "original": "Sword",
        "translation": "",
        "context": "Items.name",
        "note": "Row 5"
    },
    "TA1B2C3D4": {
        "original": "A sharp blade",
        "translation": "锋利的刀刃", 
        "context": "Items.desc",
        "note": "Row 5"
    },
    "T5E6F7A8B": {
        "original": "apple#company",
        "translation": "苹果公司",
        "context": "Items.name (suffix: company)",
        "note": "Row 6 - 上下文特定翻译"
    }
    // ... 更多翻译
}

运行时使用

初始化设置

using CSExcelConverter.Models;

// 游戏启动时初始化本地化服务
LocalizationService.Instance.Initialize(
    localizationPath: "./Localization",  // 翻译文件目录
    defaultLanguage: "en",               // 默认语言
    fallbackLanguage: "en"               // 后备语言
);

// 动态切换语言
LocalizationService.Instance.CurrentLanguage = "zh";

游戏代码中使用

// 方法1:使用生成的帮助方法
var itemName = Items.GetText((string)Items.ITEMS[1]["name"]);

// 方法2:直接使用LocalizationService
var textId = (string)Items.ITEMS[1]["name"];
var itemName = LocalizationService.Instance.GetText(textId, "Default Text");

// 方法3:使用扩展方法
var itemName = textId.Localize("Default Text");

// 带格式参数
var message = Items.GetText("ITEMS_LEVEL_UP_MSG", "Level Up!", playerLevel, newSkill);

语言管理

// 检查可用语言
var languages = LocalizationService.Instance.SupportedLanguages;

// 检查翻译是否存在
bool hasTranslation = LocalizationService.Instance.HasTranslation("T8B7C4D2F", "zh");

// 获取翻译统计
var stats = LocalizationService.Instance.GetStats();
Console.WriteLine($"总文本ID数量: {stats.TotalTextIds}");
Console.WriteLine($"当前语言: {stats.CurrentLanguage}");

// 重新加载翻译(开发时热重载很有用)
LocalizationService.Instance.Reload("./Localization");

翻译工作流程

  1. 全局配置:在全局配置中设定支持的语言

    var globalConfig = new GlobalConfig
    {
        SupportedLanguages = "en,zh,ja,fr",
        DefaultLocalizationMode = LocalizationMode.TextId,
        PrimaryLanguage = "en"
    };
  2. Excel设置:在数据工作表中使用text类型列

  3. 生成代码:运行CSExcelConverter生成C#代码和翻译模板

  4. 翻译:将Translations_*.json文件发送给翻译人员

  5. 部署:将完成的翻译文件包含在游戏构建中

  6. 运行时:初始化LocalizationService并根据需要切换语言

最佳实践

  1. 基于哈希的文本ID:由文本内容生成,确保构建间的一致性
  2. 上下文消歧:当同一文本需要不同翻译时使用后缀标记(apple#company
  3. 共享翻译:相同文本在不同表格中自动共享同一翻译
  4. 后缀使用示例
    • OK vs OK#button vs OK#dialog
    • apple vs apple#fruit vs apple#company
    • close vs close#door vs close#application
  5. 翻译管理:基于哈希的ID保持稳定,便于翻译管理
  6. 性能:哈希查找在运行时翻译查询中极其快速

高级功能

自定义文本ID生成

// TextIdManager支持自定义前缀和上下文处理
var textId = textIdManager.GetOrCreateTextId(
    originalText: "Hello World",
    context: "MainMenu.Welcome",
    sheetName: "UI", 
    columnName: "text",
    rowIndex: 10
);

翻译验证

// 内置翻译完整性验证
var validator = new TranslationValidator();
var issues = validator.ValidateTranslations("./Localization", requiredLanguages);
foreach (var issue in issues)
{
    Console.WriteLine($"缺失: {issue.TextId}{issue.Language}");
}

列过滤

在Excel第一行(说明行)以#开头的列将被自动跳过:

第1行: ID | 名称 | #调试信息 | 价格
第2行: id | name | debug    | price

结果:#调试信息列不会出现在生成的代码中。

空值处理

空的单元格或空字符串字段不会出现在生成的字典中:

// 如果某个道具的description字段为空,生成的代码中不会包含该字段
[1001] = new Dictionary<string, object>
{
    [NAME] = "道具名",
    // [DESCRIPTION] = "", // 空值字段被自动跳过
    [PRICE] = 99,
}

字段常量优化

自动生成字段名常量,避免重复的字符串字面值:

// 传统方式:多次使用字符串字面值
["name"] = "道具1",
["name"] = "道具2",

// 优化后:使用常量
public const string NAME = "name";
[NAME] = "道具1",
[NAME] = "道具2",

唯一性约束 (#unique)

使用 #unique 修饰符确保列数据的唯一性:

语法格式

基础类型#unique

支持的类型

  • int#unique:整数唯一性
  • string#unique:字符串唯一性
  • float#unique:浮点数唯一性
  • double#unique:双精度数唯一性
  • bool#unique:布尔值唯一性

使用示例

Excel表格示例:
行1: 道具ID | 道具名称 | 唯一编码
行2: id     | name    | code
行3: int    | string  | string#unique
行4: cs     | cs      | cs
行5: 1001   | "剑"    | "SWORD_001" 
行6: 1002   | "盾"    | "SHIELD_001"
行7: 1003   | "弓"    | "SWORD_001"  ← 重复值!

错误处理

当检测到重复值时,程序会立即抛出异常:

字段 'code' 在第 7 行存在重复值: SWORD_001

适用场景

  • ID字段:确保每个实体的ID都是唯一的
  • 编码字段:确保业务编码不重复
  • 键值字段:确保作为字典键的字段值唯一
  • 配置名称:确保配置项名称不冲突

PostProcess 后处理系统

概述

PostProcess系统允许用户通过自定义后处理文件对导出的数据进行二次处理,实现数据转换、验证和增强功能。

使用方法

1. 创建后处理文件

在项目根目录下的PostProcess/目录中创建后处理文件,命名格式为:{导出文件名}_PostProcess.cs

示例:

  • 导出文件 InfoNpc.cs → 后处理文件 InfoNpc_PostProcess.cs
  • 导出文件 ItemConfig.cs → 后处理文件 ItemConfig_PostProcess.cs

2. 实现IPostProcessor接口

using CSExcelConverter.Models;

namespace CSExcelConverter.PostProcess
{
    public class InfoNpc_PostProcess : IPostProcessor
    {
        public PostProcessResult ProcessDictData(string className, 
            Dictionary<string, List<Dictionary<string, object?>>> data, 
            ExportTarget target)
        {
            var result = new PostProcessResult();
            
            // 克隆数据避免修改原始数据
            var processedData = CloneData(data);
            
            // 数据处理逻辑...
            
            return result;
        }
        
        // 实现其他接口方法...
    }
}

功能特性

数据修改

// 修改特定记录的值
if (processedData.ContainsKey("NPC"))
{
    foreach (var row in processedData["NPC"])
    {
        if (row["id"]?.ToString() == "1002")
        {
            row["name"] = "李四"; // 修改1002号NPC的名称
        }
    }
}

添加计算字段

// 为所有记录添加计算字段
foreach (var sheetData in processedData.Values)
{
    foreach (var row in sheetData)
    {
        // 基于年龄添加是否成年字段
        if (row.ContainsKey("age") && row["age"] is int age)
        {
            row["isAdult"] = age >= 18;
        }
        
        // 添加处理标记
        row["processedBy"] = "PostProcessor";
    }
}

两种方式添加类成员

方式1:字典方式(推荐)

// 简洁易用的字典方式
result.AdditionalFields["MAX_LEVEL"] = new AdditionalField
{
    Type = "int",
    Value = 100,
    IsConstant = true,
    Comment = "最大等级限制"
};

result.AdditionalFields["NPC_NAMES"] = new AdditionalField
{
    Type = "ImmutableArray<string>",
    Value = new string[] { "张三", "李四" },
    IsReadOnly = true,
    Comment = "NPC名称列表"
};

生成结果:

/// <summary>
/// 最大等级限制
/// </summary>
public const int MAX_LEVEL = 100;

/// <summary>
/// NPC名称列表
/// </summary>
public static readonly ImmutableArray<string> NPC_NAMES = ImmutableArray.Create("张三", "李四");

方式2:传统逐行方式

// 适合复杂方法和自定义逻辑
result.AdditionalMembers.Add("/// <summary>");
result.AdditionalMembers.Add("/// 检查NPC是否有效");
result.AdditionalMembers.Add("/// </summary>");
result.AdditionalMembers.Add("public static bool IsValidNpc(int npcId)");
result.AdditionalMembers.Add("{");
result.AdditionalMembers.Add("    return npcId > 0 && NPC.ContainsKey(npcId);");
result.AdditionalMembers.Add("}");

支持的字段类型

  • 基础类型: int, string, bool, float, double
  • 集合类型: ImmutableArray<T> (自动格式化)
  • 字段修饰符:
    • const (IsConstant = true)
    • static readonly (IsReadOnly = true, 默认)
    • static (IsReadOnly = false)

应用场景

  • 数据转换: 修改特定字段值,格式转换
  • 计算字段: 基于现有数据生成新字段
  • 数据验证: 添加数据校验逻辑
  • 辅助方法: 生成便捷的查询和操作方法
  • 常量定义: 添加业务相关的常量和配置
  • 集合预处理: 预计算常用的数据集合

执行时机

当导出工具检测到对应的后处理文件时,会自动:

  1. 动态编译: 使用Roslyn编译器编译后处理文件
  2. 数据处理: 对每个导出目标(客户端/服务端)分别执行后处理
  3. 代码生成: 将处理后的数据和额外成员整合到最终的C#代码中
  4. 错误处理: 如果后处理失败,会显示详细错误信息但不影响基础导出

依赖环境

  • .NET 9.0: 运行时环境
  • EPPlus: 用于读取Excel文件
  • Microsoft.CodeAnalysis.CSharp: Roslyn编译器,用于PostProcess动态编译
  • 跨平台支持: Windows, macOS, Linux

更新日志

v3.3 最新功能 - 本地化系统

  • 本地化系统:支持基于哈希TextId的多语言文本管理
  • text类型支持texttext(language) 列类型用于本地化文本
  • TextId模式:生成基于哈希的文本ID,运行时动态加载翻译
  • 后缀消歧支持"apple#company" vs "apple#fruit" 支持上下文特定翻译
  • 全局本地化配置:支持的语言、默认模式、主语言等全局设置
  • 翻译文件生成:自动生成TextIds.json映射表和Translations_*.json翻译模板
  • LocalizationService:完整的运行时本地化服务,支持动态语言切换
  • 基于哈希的文本ID:使用SHA-256确保ID一致性和抗冲突性
  • TextIdManager:高效的文本ID管理,支持冲突检测和统计
  • 多种本地化模式:TextId(推荐)、Direct、Embedded模式支持
  • 简化Excel编辑:Excel中只需填写主语言文本,无需复杂多语言配置
  • 翻译验证工具:内置翻译完整性检查和统计功能

v3.2 功能 - 引用类型系统

  • refer类型支持refer(filename.sheetname.columnname) 语法引用其他表格数据
  • 自动类型推断:自动检测被引用列的实际数据类型(int/string/float等)
  • 引用验证:导入时验证引用值的有效性,无效值立即报错并指明位置
  • 子目录支持:支持在ExcelRootDirectory的任意子目录中搜索引用文件
  • 跨文件引用:可以引用其他Excel文件中的任意工作表和列
  • 同文件引用:支持引用当前文件中的其他工作表
  • 多级搜索策略:ExcelRootDirectory递归搜索 → 当前文件目录 → 工作目录
  • 智能缓存:构建引用数据缓存,提高验证效率
  • 详细错误信息:引用失败时提供精确的文件、工作表、列信息

v3.1 功能 - 多文件合并系统

  • 多文件合并功能:支持从多个Excel文件读取数据并合并到一个字典
  • 当前文件优先读取:自动读取配置文件本身的对应sheet数据
  • 灵活的源文件配置:通过#Setting表第6列配置多个源文件
  • 跨文件唯一性验证:#unique约束在所有文件间生效
  • 智能错误处理:文件缺失或工作表缺失时显示警告但继续处理
  • 全模式支持:Dict、Const、MulDict三种模式均支持多文件合并
  • 向后兼容:不配置sources时保持原有单文件模式
  • 性能优化:逐文件读取,内存占用优化,高效重复检查

v3.0 功能 - PostProcess后处理系统

  • PostProcess后处理系统:支持自定义数据转换和类成员添加
  • 动态编译:使用Roslyn编译器动态编译后处理文件
  • 数据修改:修改特定记录值,添加计算字段
  • 字典式类成员添加:通过AdditionalFields简洁添加常量和字段
  • 传统式类成员添加:通过AdditionalMembers添加复杂方法和逻辑
  • 自动字段常量生成:自动为后处理添加的字段生成常量
  • 多种字段类型支持:const、static readonly、static修饰符
  • ImmutableArray自动格式化:智能处理集合类型格式化
  • 错误处理机制:后处理失败不影响基础导出功能

v2.2 功能

  • 唯一性约束支持 (#unique):类型后缀加#unique修饰符
  • ✅ 实时唯一性验证:导出时检查数据唯一性,发现重复立即报错
  • ✅ 精确错误定位:准确显示重复数据的行号和具体值
  • ✅ 全类型支持:int#unique, string#unique, float#unique, double#unique, bool#unique
  • ✅ 多模式兼容:Dict模式和MulDict模式均支持唯一性约束

v2.1 功能

  • MulDict模式:多层嵌套字典支持
  • ✅ 双重键值映射:支持主键+子键的嵌套结构
  • ✅ 复杂数据关系:适用于等级系统、分类数据等场景
  • List类型完全不可修改:所有列表类型生成为ImmutableArray形式
  • ✅ 数据完全安全性:配置数据无法在运行时被意外修改,元素值也无法更改
  • ✅ ImmutableArray支持:自动导入System.Collections.Immutable命名空间

v2.0 新增功能

  • ✅ 字段常量优化,减少内存占用
  • ✅ 空值自动跳过功能
  • ✅ 列过滤功能(#开头列)
  • ✅ Bool类型0/1支持
  • ✅ List类型泛型支持
  • ✅ Dict类型完整支持
  • ✅ 嵌套数据结构支持
  • ✅ JSON格式数据解析

v1.0 基础功能

  • ✅ Dict/Const双模式导出
  • ✅ 多端分离导出
  • ✅ 交互式用户界面
  • ✅ 基础数据类型支持

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages