# xresloader转表工具链增加了一些新功能(map,oneof支持，输出矩阵，基于模板引擎的加载代码生成等)

[xresloader](https://github.com/xresloader/xresloader) 是一组用于把Excel数据结构化并导出为程序可读的数据文件的导表工具集。它包含了一系列跨平台的工具、协议描述和数据读取代码。支持把Excel配置输出成 protobuf二进制、xml、json、lua、javascript、nodejs、msgpack、\[UE]\[10]的Json格式及支持蓝图的代码、\[UE]\[10]的Csv格式及支持蓝图的代码。

最近一段时间有一些其他项目组也用了 [xresloader](https://github.com/xresloader/xresloader) 工具链来执行转表。提了一些需求，我并且针对我们自己的项目需要耶新增了一系列功能。这里总结介绍一下吧。

Github: <https://github.com/xresloader>

文档: <https://xresloader.atframe.work/>

| 主要项目                                                             | 说明     | 状态                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ---------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [xresloader](https://github.com/xresloader/xresloader)           | 转表引擎   | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/xresloader/xresloader)](https://github.com/xresloader/xresloader/releases) [![GitHub Actions status](https://github.com/xresloader/xresloader/workflows/Master%20Building/badge.svg)](https://github.com/xresloader/xresloader/actions) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/xresloader/xresloader) ![GitHub repo size](https://img.shields.io/github/repo-size/xresloader/xresloader) ![GitHub All Releases](https://img.shields.io/github/downloads/xresloader/xresloader/total) ![GitHub forks](https://img.shields.io/github/forks/xresloader/xresloader?style=social) ![GitHub stars](https://img.shields.io/github/stars/xresloader/xresloader?style=social)       |
| [xresconv-gui](https://github.com/xresloader/xresconv-gui)       | GUI客户端 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/xresloader/xresconv-gui)](https://github.com/xresloader/xresconv-gui/releases) [![GitHub Actions status](https://github.com/xresloader/xresconv-gui/workflows/build/badge.svg)](https://github.com/xresloader/xresconv-gui/actions) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/xresloader/xresconv-gui) ![GitHub repo size](https://img.shields.io/github/repo-size/xresloader/xresconv-gui) ![GitHub All Releases](https://img.shields.io/github/downloads/xresloader/xresconv-gui/total) ![GitHub forks](https://img.shields.io/github/forks/xresloader/xresconv-gui?style=social) ![GitHub stars](https://img.shields.io/github/stars/xresloader/xresconv-gui?style=social) |
| [xresconv-cli](https://github.com/xresloader/xresconv-cli)       | 命令行客户端 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/xresloader/xresconv-cli)](https://github.com/xresloader/xresconv-cli/releases) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/xresloader/xresconv-cli) ![GitHub repo size](https://img.shields.io/github/repo-size/xresloader/xresconv-cli) ![GitHub forks](https://img.shields.io/github/forks/xresloader/xresconv-cli?style=social) ![GitHub stars](https://img.shields.io/github/stars/xresloader/xresconv-cli?style=social)                                                                                                                                                                                                                                                    |
| [xresloader-docs](https://github.com/xresloader/xresloader-docs) | 文档     | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/xresloader/xresloader-docs) ![GitHub repo size](https://img.shields.io/github/repo-size/xresloader/xresloader-docs) ![Build Status](https://readthedocs.org/projects/xresloader-docs/badge/?version=latest)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| [xresconv-conf](https://github.com/xresloader/xresconv-conf)     | 转表配置规范 | ![GitHub repo size](https://img.shields.io/github/repo-size/xresloader/xresconv-conf)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

## plain模式

为了方便某些特殊场景使用，从 [xresloader](https://github.com/xresloader/xresloader) 2.7.0版本开始，我们开支支持Plain模式。 Plain模式的配置方式允许把数字和字符串数组和整个message配置在一个单元格里，多个元素或者多个字段按分隔符分割。分隔符支持多个候选项，实际执行会采用按输入的字符串中，第一个找到的候选项。 默认的分隔符候选项是 `,;|` 。

Plain模式不需要额外配置，当数组元素没有配置下标或者配置的映射字段直接指向一个message时，将自动使用Plain模式解析。

比如对于以下协议：

```
message cfg {
    int32 id = 1;
    plain_message plain_msg = 2;
    repeated int32 plain_arr = 3;
}

message plain_message {
    int32 id = 1;
    repeated int32 param = 2;
}
```

如果Excel配置是如下形式:

| 配置ID | Plain结构    | Plain数组    |
| ---- | ---------- | ---------- |
| id   | plain\_msg | plain\_arr |
| 101  | 101\|1,2,3 | 7;8;9      |

那么对于 `plain_msg` 字段输入的字符串是 `101|1,2,3` ，第一个 `|` 会作为 `plain_msg` 的字段分隔符， `,` 会作为 `plain_msg.param` 的数组分隔符。 而对于 `plain_arr` 字段输入的字符串是 `7;8;9` , `;` 会作为数组分隔符。

如果想要指定自定义分隔符，特别是对 `repeated message` 要区分message的分隔符和数组的分隔符，可以使用使用 `org.xresloader.field_separator` 插件和 `org.xresloader.msg_separator` 插件。 需要注意的是，对于数组（repeated）的字段，字段分隔符仅接受通过 `org.xresloader.field_separator` 指定，而非数组的复杂数据结构（非repeated message） `org.xresloader.field_separator` 插件和 `org.xresloader.msg_separator` 都可以用于指定分隔符。

更多详情见: [《文档 - Plain模式（需要 xresloader 2.7.0及以上）》](https://xresloader.atframe.work/zh_CN/latest/users/advance_usage.html#plain-xresloader-2-7-0)

## oneof支持

[xresloader](https://github.com/xresloader/xresloader) 对Oneof的支持和Plain模式类似，并且只能通过Plain模式一样的方法配置，可以使用 `org.xresloader.oneof_separator` 插件指定自定义分隔符。

Oneof/Union支持的配置方法是直接在Excel字段映射中配置oneof的名字。输入字符串中第一组为字段的名字、数字标识（field number）或别名，第二组为对应的类型的Plain模式输入。比如:

```
// 常量类型
enum cost_type {
    EN_CT_UNKNOWN              = 0;
    EN_CT_MONEY                = 10001 [(org.xresloader.enum_alias) = "金币"];
    EN_CT_DIAMOND              = 10101 [(org.xresloader.enum_alias) = "钻石"];
}
message cfg {
  int32 id = 1;
  oneof reward {
    plain_message msg       = 11 [ (org.xresloader.field_alias) = "嵌套结构" ];
    int64         user_exp  = 12 [ (org.xresloader.field_alias) = "数字类型" ];
    string        note      = 13 [ (org.xresloader.field_alias) = "描述文本" ];
    cost_type     enum_type = 14 [ (org.xresloader.field_alias) = "货币类型" ];
  }
}
message plain_message {
    int32 id = 1;
    repeated int32 param = 2;
}
```

以下输入都是允许的：

| 配置ID | Oneof结构               |
| ---- | --------------------- |
| id   | reward                |
| 1001 | msg\|101;1,2,3        |
| 1002 | 数字类型\|100             |
| 1003 | 13\|Hello World       |
| 1004 | enum\_type\|金币        |
| 1005 | 货币类型\|EN\_CT\_DIAMOND |

需要特别注意的是，和Plain模式一样，message字段解析是严格按照配置的field number的顺序，如果message里有嵌套的oneof，那么oneof的输入位置是第一个相关字段的位置，并且该oneof里后续的字段不需要配置。

对 `UE-Json` 和 `UE-Csv` 输出的蓝图代码中，增加指示oneof分支的字段，便于对 oneof 输出的分支判断和反射使用。

更多详情见: [《文档 - Oneof/Union支持（需要 xresloader 2.8.0及以上）》](https://xresloader.atframe.work/zh_CN/latest/users/advance_usage.html#oneof-union-xresloader-2-8-0)

## Map类型支持

从 [xresloader](https://github.com/xresloader/xresloader) 2.9.0 版本开始，我们支持使用 protobuf 内置的map类型。map类型的数据输入配置和数组类似，与其不同的是，我们增加了内置的 `key` 和 `value` 字段用于通过标准模式指定元素的 `key` 和 `value`。 当然我们也可以使用Plain模式的输入。比如以下的协议:

```
message dep2_cfg {
    uint32 id = 1;
    string level = 2;
}
message arr_in_arr_cfg {
    option (org.xresloader.ue.helper)       = "helper";
    option (org.xresloader.msg_description) = "Test arr_in_arr_cfg";

    uint32   id                       = 1 [ (org.xresloader.ue.key_tag) = 1, (org.xresloader.field_description) = "This is a Key" ];
    map<int32, string>    test_map_is = 7;
    map<string, dep2_cfg> test_map_sm = 8 [ (org.xresloader.field_separator) = "|" ]; 
}
```

我们接受如下的Excel输入:

| 配置ID | Map嵌套模式\[0].key       | Map嵌套模式\[0].value       | Map嵌套模式\[1].key       | Map嵌套模式\[1].value       | MapPlain模式                |
| ---- | --------------------- | ----------------------- | --------------------- | ----------------------- | ------------------------- |
| id   | test\_map\_is\[0].key | test\_map\_is\[0].value | test\_map\_is\[1].key | test\_map\_is\[1].value | test\_map\_sm             |
| 1001 | 10                    | Map嵌套模式\[0].value       | 11                    | Map嵌套模式\[1].value       | aa;111,112\|特殊:字符;121,122 |
| 1002 | 20                    | Map嵌套模式\[0].value       | 21                    | Map嵌套模式\[1].value       | ba;211,212\|特殊.字符;221,222 |
| 1003 | 30                    | Map嵌套模式\[0].value       | 31                    | Map嵌套模式\[1].value       | ca;311,312\|cb;321,322    |

对于 `UE-Csv` 和 `UE-Json` 模式的输出，我们会输入如下的代码：

```cpp
USTRUCT(BlueprintType)
struct FArrInArrCfg : public FTableRowBase
{
    GENERATED_USTRUCT_BODY()

    // Start of fields
    /** Field Type: STRING, Name: Name, Index: 0. This field is generated for UE Editor compatible. **/
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "XResConfig")
    FName Name;

    // This is a Key
    /** Field Type: INT, Name: Id, Index: 1 **/
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "XResConfig")
    int32 Id;

    /** Field Type: MESSAGE, Name: TestMapIs, Index: 7 **/
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "XResConfig")
    TMap< int32, FString > TestMapIs;

    /** Field Type: MESSAGE, Name: TestMapSm, Index: 8 **/
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "XResConfig")
    TMap< FString, FDep2Cfg > TestMapSm;

};
```

特别的对于 `xml` 类型的输出，由于map中的key的数据可能会不符合 `xml` 的tag的规则，所以我们对于map输出的数据中 `tagName` 采用类型名， 即 `string` , `int32` , `int64` 。 然后增加 `key` 属性用于指示map中key的内容，增加 `type` 属性指示类型名。

更多详情见: [《文档 - Map类型支持（需要 xresloader 2.9.0及以上）》](https://xresloader.atframe.work/zh_CN/latest/users/advance_usage.html#map-xresloader-2-9-0)

## 杂项功能和优化

* 增加 `--require-mapping-all` 用于检查message中所有字段都被映射，用于检查配置遗漏
* 增加protobuf插件 - `org.xresloader.msg_require_mapping_all` 可以设置某个message的所有字段必须被全部映射，用于检查配置遗漏
* 增加protobuf插件 - `org.xresloader.field_required` 用于向proto3提供，proto2的 **required** 约束
* 协议里直接配置 `enum` 类型也支持默认增加该类型的验证器
* 文本输出的模式全部为有序输出，方便使用diff查看差异
* 增加输出数据源和协议名，方便运行时自动化分析配置问题
* 内置插件proto和官方proto。这样输入的pb文件不需要打包官方的proto描述和[xresloader](https://github.com/xresloader/xresloader)自带的插件和头的proto了。

## 基于模板引擎的加载代码生成

目前我们项目组开发了工具来自动生成加载代码，包括 **C++** 、 **lua** 和 **C#** 版本。支持 **多索引** 、 **多级索引** 、 **多版本支持** 、 **支持嵌入客户端引擎** 、 **支持C++98 - C++20** 、 **支持简单的分表分文件加载规则** 。

比如:

```
syntax = "proto3";

import "xresloader.proto";
import "xresloader_ue.proto";

import "xrescode_extensions_v3.proto";

message role_upgrade_cfg {
    option (org.xresloader.ue.helper)       = "helper";
    option (org.xresloader.msg_description) = "Test role_upgrade_cfg with multi keys";

    option (xrescode.loader) = {
        file_path : "role_upgrade_cfg.bytes"
        indexes : {
            fields : "Id"
            index_type : EN_INDEX_KL // Key - List index: (Id) => list<role_upgrade_cfg>
        }
        indexes : {
            fields : "Id"
            fields : "Level"
            index_type : EN_INDEX_KV // Key - Value index: (Id, Level) => role_upgrade_cfg
        }
        tags : "client"
        tags : "server"
    };

    uint32 Id        = 1 [ (org.xresloader.ue.key_tag) = 1000 ];
    uint32 Level     = 2 [ (org.xresloader.ue.key_tag) = 1 ];
    uint32 CostType  = 3 [ (org.xresloader.verifier) = "cost_type", (org.xresloader.field_description) = "Refer to cost_type" ];
    int32  CostValue = 4;
    int32  ScoreAdd  = 5;
}
```

### 加载代码生成 - C++

生成的C++索引代码类似这样:

```cpp
namespace excel {
    class config_set_role_upgrade_cfg {
    public:
        typedef const ::role_upgrade_cfg item_type;
        typedef ::role_upgrade_cfg proto_type;
        typedef std::shared_ptr<item_type> item_ptr_type;

        // 省略掉一些加载代码 ...

        // ------------------------- index: id -------------------------
    public:
        typedef std::vector<item_ptr_type> id_value_type;
        const id_value_type* get_list_by_id(uint32_t Id);
        item_ptr_type get_by_id(uint32_t Id, size_t index);
    private:
        const id_value_type* _get_list_by_id(uint32_t Id);
    public:
        typedef std::map<std::tuple<uint32_t>, id_value_type> id_container_type;
        const id_container_type& get_all_of_id() const;

    private:
        id_container_type id_data_;

        // ------------------------- index: id_level -------------------------
    public:
        typedef item_ptr_type id_level_value_type;
        id_level_value_type get_by_id_level(uint32_t Id, uint32_t Level);
        typedef std::map<std::tuple<uint32_t, uint32_t>, id_level_value_type> id_level_container_type;
        const id_level_container_type& get_all_of_id_level() const;

    private:
        id_level_container_type id_level_data_;
    };
}
```

可以用如下代码加载:

```cpp
#include <cstdio>

#include "config_manager.h"
#include "config_easy_api.h"

int main() {
    // Initialize ....
    excel::config_manager::me()->init();

    // Call reload to generate a configure group
    excel::config_manager::me()->reload();

    // Now you can load data by easy api or config_manager's raw API
    auto cfg = excel::get_role_upgrade_cfg_by_id_level(10001, 3); // using the Key-Value index: id_level
    if (cfg) {
        printf("%s\n", cfg->DebugString().c_str());
    }
    return 0;
}
```

### 加载代码生成 - Lua

生成的lua索引代码类似这样:

```lua
module("DataTableCustomIndex", package.seeall)
--
-- generated by xrescode on 2020-05-07 18:12:18, please don't edit it
--

-- ======================================== role_upgrade_cfg ========================================
role_upgrade_cfg = {
    -- require("DataTableService").Get("role_upgrade_cfg"):GetByIndex("id", "Id")
    { indexName = "id", filePath = "role_upgrade_cfg.bytes", options = { isList = true, allowNotFound = true }, keys = { "Id" } },
    -- require("DataTableService").Get("role_upgrade_cfg"):GetByIndex("id_level", "Id", "Level")
    { indexName = "id_level", filePath = "role_upgrade_cfg.bytes", options = { isList = false, allowNotFound = true }, keys = { "Id", "Level" } },
}
```

然后可以用这样加载:

```lua
-- We will use require(...) to load DataTableService53,DataTableCustomIndex53 and custom data files, please ensure these can be load by require(FILE_PATH)
-- Assuming the generated lua files by xresloader is located at ../../../xresloader/sample/proto_v3
package.path = '../../../xresloader/sample/proto_v3/?.lua;' .. package.path
local excel_config_service = require('DataTableService53')

-- Set logger
-- excel_config_service:OnError = function (message, index, indexName, keys...) end

excel_config_service:ReloadTables()

local role_upgrade_cfg = excel_config_service:Get("role_upgrade_cfg")
local data = role_upgrade_cfg:GetByIndex("id_level", 10001, 3) -- using the Key-Value index: id_level
for k,v in pairs(data) do
    print(string.format("%s=%s", k, tostring(v)))
end

-- We can also use DataTableService.GetCurrentGroup(self) and DataTableService.GetByGroup(self, group, loader_name) to support multi-version loader
local current_group = excel_config_service:GetCurrentGroup()
local role_upgrade_cfg2 = excel_config_service:GetByGroup(current_group, "role_upgrade_cfg")
local data2 = role_upgrade_cfg:GetByIndex("id", 10001) -- using the Key-List index: id
print("=======================")
for _,v1 in ipairs(data2) do
    print(string.format("\tid: %s, level: %s", tostring(v1.Id), tostring(v1.Level)))
    for k,v2 in pairs(v1) do
        print(string.format("\t\t%s=%s", k, tostring(v2)))
    end
end
```

### 加载代码生成 - C\#

同样，也支持生成C#代码，生成的代码就不贴了，加载代码如下：

```csharp
using System;
using excel;
class Program {
    static void Main(string[] args) {
        ConfigSetManager.Instance.Reload();
        // The C# configSet now generated by Singleton Classes.
        // For the multi ConfigGroup management may be added when need.
        var table = config_set_role_upgrade_cfg.Instance.GetByIdLevel(10001, 3);
        if (table != null) {
            Console.WriteLine(table.ToString());
        }
    }
}
```

目前的C#的代码生成的版本还不支持多版本并存。

更多详情见工具的代码仓库： <https://github.com/xresloader/xres-code-generator>

\[10]: <https://www.unrealengine.com/>
