xresloader文档
xresloader 是一组用于把Excel数据结构化并导出为程序可读的数据文件的导表工具集。它包含了一系列跨平台的工具、协议描述和数据读取代码。
主要功能特点:
跨平台(java 11 or upper)
Excel => protobuf/msgpack/lua/javascript/json/xml
完整支持协议结构,包括嵌套结构和数组嵌套
同时支持protobuf proto v2 和 proto v3
支持导出proto枚举值到lua/javascript代码和json/xml数据
支持导出proto描述信息值到lua/javascript代码和json/xml数据(支持自定义插件,方便用户根据proto描述自定义反射功能)
支持导出 UnrealEngine 支持的json或csv格式,支持自动生成和导出 UnrealEngine 的
DataTable
加载代码支持别名表,用于给数据内容使用一个易读的名字
支持验证器,可以在数据里直接填写proto字段名或枚举名,或者验证填入数据的是否有效
支持通过protobuf协议插件控制部分输出
支持自动合表,把多个Excel数据表合并成一个输出文件
支持公式
支持oneof,支持plain模式输入字符串转为数组或复杂结构,支持map
支持空数据压缩(裁剪)或保留定长数组
支持基于正则表达式分词的字段名映射转换规则
支持设置数据版本号
Lua输出支持全局导出或导出为
require
模块或导出为module
模块。Javascript输出支持全局导出或导出为
nodejs
模块或导出为AMD
模块。提供CLI批量转换工具(支持python 2.7/python 3 @ Windows、macOS、Linux)
提供GUI批量转换工具(支持Windows、macOS、Linux)
CLI/GUI批量转换工具支持include来实现配置复用
构建环境 |
构建状态 |
---|---|
直接下载发布包即可,无需构建打包 |
|
读表代码生成工具,直接下载即可,无需构建打包 |
|
文档 |
v2.11.0-rc2及以前版本更新迁移指引
由于 v2.11.0-rc3 版本变更了默认的索引器,导致对Excel一些内置的数据类型处理和先前有一些差异。比如对于日期时间类型、百分率等。
现在会先转出原始的文本,再根据protocol的目标类型做转换。如果需要回退到老的POI索引,可以使用 --enable-excel-formular
选项切换到老的索引器。
新版本开始使用JDK 11打包,如果仍然需要 JDK1.8打包请自行下载源码并修改 pom.xml
内 maven-compiler-plugin
的 source
和 target
后使用 mvn package
命令打包。
xresloader 主要文档分为以下几个模块:
下载工具集
模块或工具 |
下载地址 |
---|---|
转表工具-xresloader |
|
命令行批量转表工具-xresconv-cli |
|
GUI批量转表工具-xresconv-gui |
|
批量转表配置模板仓库-xresconv-conf |
转表工具-xresloader 下载jar文件即可,如果要使用插件,请下载 protocols.zip
命令行批量转表工具-xresconv-cli 下载压缩包并使用里面的*xresconv-cli.py*文件
GUI批量转表工具-xresconv-gui 下载对应平台的发布包即可
批量转表配置模板仓库-xresconv-conf 是给 xresconv-cli release 或 xresconv-gui release 使用的配置列表文件。 sample.xml 文件是功能完整的配置示例。 同时,配置文件支持include其他xml配置并做一些配置覆盖,sample_include.xml 文件是include功能的示例。 批量转表配置文件里配置的路径必须是绝对路径或者相对于xml配置文件的路径。
快速上手
Step-1: 下载转表工具
下载JRE/JDK 8或以上(推荐下载64位的: AdoptopenJDK/LibericaJDK/OpenJDK/Zulu)
打开 下载工具集 。下载最新版本的 转表工具-xresloader (xresloader-*.jar)。
下载或自己编译protobuf官方的protoc工具,可以去 https://github.com/google/protobuf/releases 下载预编译好的protoc
[可选/推荐] 如果要使用命令行版本的批量转换工具则要额外下载 命令行批量转表工具-xresconv-cli
[可选/推荐] 如果要使用GUI版本的批量转换工具则要额外下载 GUI批量转表工具-xresconv-gui
Step-2: 配置结构化的protobuf协议并使用protoc
我们需要先写协议描述文件,到时候转出的数据也是按这个结构打包的。比如: kind.proto
syntax = "proto3";
import "xresloader.proto";
// xresloader的发布页面 https://github.com/xresloader/xresloader/releases 下载 protocols.zip ,即可获取xresloader.proto
enum cost_type {
EN_CT_UNKNOWN = 0;
EN_CT_MONEY = 10001 [(org.xresloader.enum_alias) = "金币"];
EN_CT_DIAMOND = 10101 [(org.xresloader.enum_alias) = "钻石"];
}
message role_upgrade_cfg {
uint32 Id = 1;
uint32 Level = 2;
uint32 CostType = 3 [
(org.xresloader.verifier) = "cost_type", // 这里等同于在Excel中使用 @cost_type 标识
(org.xresloader.field_description) = "Refer to cost_type"
];
int32 CostValue = 4;
int32 ScoreAdd = 5;
}
proto v2也可以,可以参见 https://github.com/xresloader/xresloader/blob/master/sample/proto_v2/kind.proto 。
然后使用protoc生成描述文件和用于加载的代码文件:
protoc -o sample-conf/kind.pb --cpp_out sample-code -I sample-conf -I <xresloader协议目录>/extensions/v3 -I <xresloader协议目录>/extensions sample-conf/kind.proto <xresloader协议目录>/extensions/google/protobuf/descriptor.proto <xresloader协议目录>/extensions/v3/xresloader.proto <xresloader协议目录>/extensions/v3/xresloader_ue.proto;
这是最终的 数据转出目标 。
Step-3: 配置Excel数据源
按照协议的配置编辑Excel文件,role_tables.xlsx ,我们使用表名 upgrade_10001
。
第一行设为描述,第二行设置为字段映射列,后面是数据(具体设置请参照 Step-4: 配置批量转表配置文件)。
角色ID |
等级 |
货币类别 |
消耗值 |
---|---|---|---|
Id |
Level |
CostType |
CostValue |
10001 |
1 |
||
10001 |
2 |
1001 |
50 |
10001 |
3 |
1001 |
100 |
10001 |
4 |
1001 |
150 |
10001 |
5 |
1001 |
200 |
10001 |
6 |
1001 |
250 |
10001 |
7 |
1001 |
300 |
10001 |
8 |
1001 |
350 |
10001 |
9 |
1001 |
400 |
10001 |
10 |
1001 |
450 |
10001 |
11 |
1001 |
500 |
这是最终的 数据来源 。
Step-4: 配置批量转表配置文件
编辑配置转表配置, sample.xml 。这个文件用于告诉批量转表工具,xresloader的位置、工作目录从哪里读协议描述文件,如果映射字段转成什么类型等等。 简而言之就是把 数据转出目标 和 数据来源 关联起来。
<?xml version="1.0" encoding="UTF-8"?>
<root>
<global>
<work_dir desc="工作目录,相对于当前xml的目录,我们的Excel文件放在这里">.</work_dir>
<xresloader_path desc="指向前面下载的 转表工具-xresloader,相对于当前xml的目录">../xresloader/target/xresloader-2.9.0.jar</xresloader_path>
<proto desc="协议类型,-p选项">protobuf</proto>
<output_type desc="输出类型,对应-t选项,输出二进制">bin</output_type>
<output_type desc="多种输出时可以额外定义某个节点的重命名规则" rename="/(?i)\.bin$/\.json/">json</output_type>
<proto_file desc="协议描述文件,-f选项">kind.pb</proto_file>
<output_dir desc="输出目录,-o选项">../sample-data</output_dir>
<data_src_dir desc="数据源目录,-d选项"></data_src_dir>
<java_option desc="java选项-最大内存限制2GB">-Xmx2048m</java_option>
<java_option desc="java选项-客户端模式">-client</java_option>
<default_scheme name="KeyRow" desc="默认scheme模式参数-Key行号,对应上面Id、Level、CostType、CostValue那一行">2</default_scheme>
<option desc="全局自定义选项" name="美化文本输出,缩进为2个空格">--pretty 2</option>
</global>
<groups desc="分组信息(可选)">
<group id="client" name="客户端"></group>
<group id="server" name="服务器"></group>
</groups>
<category desc="类信息(用于GUI工具的树形结构分类显示)">
<tree id="all_cats" name="大分类">
<tree id="kind" name="角色配置"></tree>
</tree>
</category>
<list>
<item name="升级表" cat="kind" class="client server">
<scheme name="DataSource" desc="数据源(文件名|表名|数据起始行号,数据起始列号)">role_tables.xlsx|upgrade_10001|3,1</scheme>
<scheme name="ProtoName" desc="协议名">role_upgrade_cfg</scheme>
<scheme name="OutputFile" desc="输出文件名">role_upgrade_cfg.bin</scheme>
</item>
</list>
</root>
对于文件路径配置的说明: work_dir
、 xresloader_path
和 include
配置的路径是相对于xml文件的路径。其他的涉及路径配置的地方如果不是绝对路径的,都是相对于 work_dir
的路径。(具体含义请参考 批量转表工具 )
在查找Excel文件的时候,如果有配置 data_src_dir
,则会相对于这个配置的路径读取Excel,否则也是相对于 work_dir
。
Step-5: 运行转表工具
下面两种运行转表的工具,一种是命令行工具,另一种是有用户界面的GUI工具。选用一种即可。
我们假设执行环境的目录结构如下:
sample-conf (批量转表配置所在目录)
sample.xml
kind.proto
kind.pb (使用protoc生成的二进制协议描述文件)
role_tables.xlsx (Excel数据源)
xresloader.run.log (输出的日志文件,执行转表后自动生成,方便万一有错误排查)
sample-data (转出的配置数据所在目录)
role_upgrade_cfg.bin (输出的二进制配置文件,执行转表后自动生成)
xresloader
header (可在 https://github.com/xresloader/xresloader/releases 下载 protocols.zip 获得)
pb_header.proto (用于proto v2的转表头结构描述文件,读取数据的时候用)
pb_header_v3.proto (用于proto v3的转表头结构描述文件,读取数据的时候用)
target (可在 https://github.com/xresloader/xresloader/releases 下载 xresloader-\*.jar 获得)
xresloader-2.8.0.jar
xresconv-cli (命令行转表工具所在目录,可在 https://github.com/xresloader/xresconv-cli/releases 下载 )
xresconv-cli.py
print_color.py
xresconv-gui (GUI转表工具所在目录,可在 https://github.com/xresloader/xresconv-gui/releases 下载 )
GUI工具的文件列表...
Step-5.1: 命令行批量转表工具
python xresconv-cli/xresconv-cli.py sample-conf/sample.xml
输出如下:

Step-5.2: GUI批量转表工具
使用GUI工具,直接加载配置文件,选中要转换的表然后点击开始即可。

Step-6: 加载数据
执行完上面一步的转表流程后,我们得到了 xresloader/sample/role_upgrade_cfg.bin
这个二进制配置文件,接下来把它加载到我们的程序中就可以了。
比如我们用C++来加载。首先我们之前执行 protoc
的时候已经生成了配置协议的代码,然后还需要生成转表工具header的结构的代码。
protoc -I xresloader/header --cpp_out=sample-code xresloader/header/pb_header_v3.proto ;
然后你可以选择使用我们封装过的读取库解析或手动解析。
Step-6.1: (推荐)使用 xres-code-generator 生成解析代码(C++/Lua/C#/Upb Lua)
对于C++、Lua和C#,我们推荐使用 xres-code-generator 生成解析代码。(未来会开发更多的语言支持)。详见: 使用 xres-code-generator 生成解析代码 。
Step-6.2: 手动解析
手动解析的流程是先用 xresloader中header 里的 xresloader_datablocks
解析二进制文件,然后用协议的proto解析里面每条 data_block
字段。
每个 data_block
的条目对应配置里协议的每个message。(文件名: load_custom.cpp ):
#include <cstdio>
#include <iostream>
#include <fstream>
#include <google/protobuf/stubs/common.h>
#if GOOGLE_PROTOBUF_VERSION < 3000000
#include "pb_header.pb.h"
#else
#include "pb_header_v3.pb.h"
#endif
#include "kind.pb.h"
int main(int argc, char* argv[]) {
const char* file_path = "../sample-data/role_upgrade_cfg.bin";
if (argc > 1) {
file_path = argv[1];
} else {
printf("usage: %s <path to role_upgrade_cfg.bin>\n", argv[0]);
return 1;
}
org::xresloader::pb::xresloader_datablocks data_wrapper;
std::fstream fin;
fin.open(file_path, std::ios::in | std::ios::binary);
if (!fin.is_open()) {
printf("open %s failed\n", file_path);
return 1;
}
if (false == data_wrapper.ParseFromIstream(&fin)) {
printf("parse org::xresloader::pb::xresloader_datablocks failed. %s\n", data_wrapper.InitializationErrorString().c_str());
return 1;
}
printf("========================\ndata header: %s\n========================\n", data_wrapper.header().DebugString().c_str());
for (int i = 0; i < data_wrapper.data_block_size(); ++i) {
role_upgrade_cfg role_upg_data;
if (false == role_upg_data.ParseFromString(data_wrapper.data_block(i))) {
printf("parse role_upgrade_cfg for index %d failed. %s\n", i, role_upg_data.InitializationErrorString().c_str());
continue;
}
printf("role_upgrade_cfg => index %d: %s\n", i, role_upg_data.ShortDebugString().c_str());
}
return 0;
}
编译和运行:
g++ -I . -I<protobuf的include目录> -L<protobuf的lib目录> -std=c++11 -O0 -g -ggdb -Wall load_custom.cpp *.pb.cc -lprotobuf -o load_custom.exe && ./load_custom.exe ../sample-data/role_upgrade_cfg.bin
输出示例:
========================
data header: xres_ver: "1.4.3"
data_ver: "1.4.3.20180317040504"
count: 11
hash_code: "md5:7bbe88cca1eb23ebdce75b0e10b88b4a"
========================
role_upgrade_cfg => index 0: Id: 10001 Level: 1
role_upgrade_cfg => index 1: Id: 10001 Level: 2 CostType: 1001 CostValue: 50
role_upgrade_cfg => index 2: Id: 10001 Level: 3 CostType: 1001 CostValue: 100
role_upgrade_cfg => index 3: Id: 10001 Level: 4 CostType: 1001 CostValue: 150
role_upgrade_cfg => index 4: Id: 10001 Level: 5 CostType: 1001 CostValue: 200
role_upgrade_cfg => index 5: Id: 10001 Level: 6 CostType: 1001 CostValue: 250
role_upgrade_cfg => index 6: Id: 10001 Level: 7 CostType: 1001 CostValue: 300
role_upgrade_cfg => index 7: Id: 10001 Level: 8 CostType: 1001 CostValue: 350
role_upgrade_cfg => index 8: Id: 10001 Level: 9 CostType: 1001 CostValue: 400
role_upgrade_cfg => index 9: Id: 10001 Level: 10 CostType: 1001 CostValue: 450
role_upgrade_cfg => index 10: Id: 10001 Level: 11 CostType: 1001 CostValue: 500
加载数据可以有多种方法,这里提供加载二进制的方法。 更多关于输出类型和加载方式的信息请参见 数据输出和数据加载 。
Step-6.3: (老式接口,不推荐,请考虑使用上面6.1的加载方法)使用读取库模板解析
需要先下载读取库。
curl -L -k https://raw.githubusercontent.com/xresloader/xresloader/master/loader-binding/cxx/libresloader.h -o libresloader.h
然后读取的代码sample如下(文件名: load_with_libresloader.cpp )
#include <cstdio>
#include <iostream>
#include <fstream>
#include "kind.pb.h"
#include "libresloader.h"
int main(int argc, char* argv[]) {
const char* file_path = "../sample-data/role_upgrade_cfg.bin";
if (argc > 1) {
file_path = argv[1];
} else {
printf("usage: %s <path to role_upgrade_cfg.bin>\n", argv[0]);
return 1;
}
// key - value 型数据读取机制
do {
typedef xresloader::conf_manager_kv<role_upgrade_cfg, uint32_t, uint32_t> kind_upg_cfg_t;
kind_upg_cfg_t upg_mgr;
upg_mgr.set_key_handle([](kind_upg_cfg_t::value_type p) {
return kind_upg_cfg_t::key_type(p->id(), p->level());
});
upg_mgr.load_file(file_path);
kind_upg_cfg_t::value_type data1 = upg_mgr.get(10001, 4); // 获取Key 为 10001,4的条目
if (NULL == data1) {
std::cerr<< "role_upgrade_cfg id: 10001, level: 4 not found, load file "<< file_path<< " failed."<< std::endl;
break;
}
printf("%s\n", data1->DebugString().c_str());
} while(false);
// key - list 型数据读取机制
do {
typedef xresloader::conf_manager_kl<role_upgrade_cfg, uint32_t> kind_upg_cfg_t;
kind_upg_cfg_t upg_mgr;
upg_mgr.set_key_handle([](kind_upg_cfg_t::value_type p) {
return kind_upg_cfg_t::key_type(p->id());
});
upg_mgr.load_file(file_path);
printf("role_upgrade_cfg with id=%d has %llu items\n", 10001, static_cast<unsigned long long>(upg_mgr.get_list(10001)->size()));
kind_upg_cfg_t::value_type data1 = upg_mgr.get(10001, 0); // 获取Key 为 10001 下标为0(就是第一个)条目
if (NULL == data1) {
std::cerr<< "role_upgrade_cfg id: 10001 , index: 0, not found, load file "<< file_path<< " failed."<< std::endl;
break;
}
printf("%s\n", data1->DebugString().c_str());
} while(false);
return 0;
}
编译和运行:
g++ -I . -I<protobuf的include目录> -L<protobuf的lib目录> -std=c++11 -O0 -g -ggdb -Wall load_with_libresloader.cpp *.pb.cc -lprotobuf -o load_with_libresloader.exe && ./load_with_libresloader.exe ../sample-data/role_upgrade_cfg.bin
输出示例:
Id: 10001
Level: 4
CostType: 1001
CostValue: 150
role_upgrade_cfg with id=10001 has 11 items
Id: 10001
Level: 1
上面的例程和配置可以在 https://github.com/xresloader/xresloader-docs/tree/master/source/sample/quick_start 查看。
使用proto v2加载二进制数据的特别注意事项
需要额外注意一点的是,如果使用proto v2生成的代码或pb加载转出的数据,如果有 repeated
的数字字段,需要在proto文件里显式指明 packed
属性。
转表引擎-xresloader
在 快速上手 章节里我们提供了一个基本的转表使用流程。整个流程图示如下:

无论GUI工具还是CLI工具还是数据和配置,最终都是汇聚到调用 xresloader 转表引擎命令进行汇总和执行转换。 本章节主要是针对 xresloader 转表引擎的说明。
xresloader-可用参数列表
参数选项 |
描述 |
说明 |
---|---|---|
|
帮助信息 |
显示帮助和支持的参数列表 |
|
输出类型 |
|
|
协议描述类型 |
protobuf(默认值),capnproto(暂未实现),flatbuffer(暂未实现) |
|
协议描述文件 |
|
|
输出目录 |
默认为当前目录 |
|
数据源根目录 |
默认为当前目录 |
|
数据源描述文件 (scheme) |
|
|
数据源描述 |
可多个 |
如果设置了 |
||
|
打印版本号 |
|
|
重命名输出文件名 |
正则表达式 (如: |
|
输出协议描述中的常量 |
参数为字符串,表示输出的文件名 |
|
输出协议描述中的选项 |
参数为字符串,表示输出的文件名 |
|
设置数据版本号 |
参数为字符串,表示输出的数据的data_ver字段。 如果不设置将按执行时间自动生成一个。 会写出到转出数据的header中。 |
|
格式化输出 |
参数为整数,0代表关闭美化输出功能,大于0表示格式化时的缩进量 |
|
开启Excel公式实时计算 |
默认开启 2003版本的excel(*.xls)文件使用公式会大幅减慢转表速度 |
|
关闭Excel公式支持 |
2003版的excel(*.xls)关闭公式会大幅加快转表速度 |
|
禁止空列表项 |
默认开启。自动删除Excel中的空数据,不会转出到输出文件中 |
|
开启空列表项 |
开启空列表项。未填充数据将使用默认值,并转出到输出文件中 |
|
通过标准输入批量转表 |
通过标准输入批量转表,参数和上面的一样,每行都执行一次转表。 字符串参数可以用单引号或双引号包裹,但是都不支持转义。 |
|
lua输出写到全局表 |
输出协议描述中的常量到Lua脚本时,同时导入符号到全局表_G中 (仅对常量导出有效) |
|
lua输出使用module写出 |
输出Lua脚本时,使用 module(模块名, package.seeall) 导出到全局 |
|
xml输出的根节点tag |
输出格式为xml时的根节点的TagName |
|
导出javascript数据的模式 |
可选项:
|
|
导出javascript全局空间 |
导出数据到全局时,可以指定写入的名字空间 |
|
忽略未知的协议的依赖 |
忽略未知的输入协议的依赖项(>=2.9.0版本) |
批处理
如果我们需要一次性转出多个表,可以使用 --stdin
选项,然后再标准输入里输入其他的配置参数。这时候我们认为每个非空行都是一个数据转换组。
比如在 xresloader sample 的bash命令中:
echo '
-t lua -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' --pretty 2 -i kind.desc.lua
-t json -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' --pretty 2 -i kind.desc.json
-t json -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' -s '$XLSX_FILE' -m scheme_kind -n "/(?i)\.bin$/\.json/"
-t xml -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' -s '$XLSX_FILE' -m scheme_kind -n "/(?i)\.bin$/\.xml/"
-t msgpack -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' -s '$XLSX_FILE' -m scheme_kind -n "/(?i)\.bin$/\.msgpack.bin/"
-t js -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' --pretty 2 -s '$XLSX_FILE' -m scheme_kind -n "/(?i)\.bin$/\.js/" --javascript-global sample
-t js -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' --pretty 2 -m DataSource='$XLSX_FILE'|kind|3,1 -m MacroSource='$XLSX_FILE'|macro|2,1 -m ProtoName=role_cfg -m OutputFile=role_cfg.n.js -m KeyRow=2 -m KeyCase=lower -m KeyWordSplit=_ -m "KeyWordRegex=[A-Z_\$ \t\r\n]|[_\$ \t\r\n]|[a-zA-Z_\$]" --javascript-export nodejs
-t js -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' --pretty 2 -s '$XLSX_FILE' -m scheme_kind -n "/(?i)\.bin$/\.amd\.js/" --javascript-export amd
-t lua -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' --pretty 2 -m DataSource='$XLSX_FILE'|arr_in_arr|3,1 -m MacroSource='$XLSX_FILE'|macro|2,1 -m ProtoName=arr_in_arr_cfg -m OutputFile=arr_in_arr_cfg.lua -m KeyRow=2 -o proto_v3
-t bin -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' -m DataSource='$XLSX_FILE'|arr_in_arr|3,1 -m MacroSource='$XLSX_FILE'|macro|2,1 -m ProtoName=arr_in_arr_cfg -m OutputFile=arr_in_arr_cfg.bin -m KeyRow=2 -o proto_v3
-t json -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' -s '$XLSX_FILE' -m scheme_upgrade -n "/(?i)\.bin$/\.json/"
-t lua -p protobuf -o '$proto_dir' -f '$proto_dir/kind.pb' -s '$XLSX_FILE' -m scheme_upgrade -n "/(?i)\.bin$/\.lua/"
-t ue-csv -o '$proto_dir' -f '$proto_dir/kind.pb' -c KindConst.csv
-t ue-json -o '$proto_dir' -f '$proto_dir/kind.pb' -c KindConst.json
-t ue-csv -o '$proto_dir' -f '$proto_dir/kind.pb' -m DataSource='$XLSX_FILE'|arr_in_arr|3,1 -m MacroSource='$XLSX_FILE'|macro|2,1 -m ProtoName=arr_in_arr_cfg -m OutputFile=ArrInArrCfg.csv -m KeyRow=2 -m UeCfg-CodeOutput=|Public/Config|Private/Config
-t ue-json -o '$proto_dir' -f '$proto_dir/kind.pb' -m DataSource='$XLSX_FILE'|arr_in_arr|3,1 -m MacroSource='$XLSX_FILE'|macro|2,1 -m ProtoName=arr_in_arr_cfg -m OutputFile=ArrInArrCfg.json -m KeyRow=2 -m UeCfg-CodeOutput=|Public/Config|Private/Config
' | java -client -jar "$XRESLOADER" --stdin;
这里就有10项转出文件。批处理有个优势是java在运行时会对字节码做JIT,批处理则会只对字节码编译一次,能比每个转出文件运行一次命令快很多。
直接使用xresloader
直接使用转表引擎( xresloader )的示例可以参见 xresloader sample 。里面有几乎所有的使用方法。 包括但不限于转出到代码、转出枚举量、使用proto2、使用proto3、转出加载代码、批量转出等等。
Windows下的执行入口是 gen_sample_output.bat 或 gen_sample_output.ps1 。 Linux/macOS/BSD 的执行入口是 gen_sample_output.sh 。
使用前需要先使用 gen_protocol.py 生成proto v2的协议描述文件和使用 gen_protocol_v3.py 生成proto v3的协议描述文件。
协议->Excel数据映射和支持的配置读取源 (scheme)
在 快速上手 章节里我们提供了一个基本的转表使用流程。整个流程图示如下:

上一章节里我们展示了 转表引擎-xresloader 的最基础的使用方式。
在使用的时候,我们需要告诉 xresloader 怎么把Excel里的数据对引到协议的数据结构里。
本章节主要是针对 协议描述
的说明。
配置项的结构
所有的数据源和规则设置都是Key-Value的形式。并且Value有三个,分别是**主配置,次要配置,补充配置**。 这三个参数对于不同配置的含义是不同的,具体没想配置的含义请参照 data-mapping-available-options 章节。
数据映射-Scheme
我们通过一些列scheme的配置来告诉 xresloader 从Excel的哪些地方读取数据,又转化到协议的哪个数据结构和哪个字段中。
数据源
我们通过 DataSource
来读取我们从哪个文件、哪个表及第几行第几列开始读数据。 比如:

然后再通过 ProtoName
和 KeyRow
来把Excel数据映射到哪个协议数据结构里和映射哪些字段。

类型嵌套和Message嵌套
对于复杂的数据类型,我们使用 父级字段名.子结构字段名
来标识映射关系。比如在 xresloader sample 中。

以上划红线处为Excel KeyRow
配置和对应的协议结构。
数组和下标
对于数组类型(protobuf的repeated类型)。我们使用 字段名[0开始的下标]
来表示。如果是数组嵌套数组,那么则是 父级字段名[父结构下标].子结构字段名[子结构下标]
。

以上示例是 xresloader sample 中的 arr_in_arr
表。
可用的配置项
字段 |
简介 |
主配置 |
次配置 |
补充配置 |
说明 |
---|---|---|---|---|---|
DataSource |
配置数据源 |
文件路径 |
表名 |
数据起始行号,列号 (英文逗号分隔) |
必须。 可多个。多个则表示把多个Excel表数 据合并再生成配置输出,这意味着这多 个Excel表的描述Key的顺序和个数必须 相同。 |
MacroSource |
元数据数据源 |
文件路径 |
表名 |
数据起始行号,列号 (英文逗号分隔) |
|
编程接口配置 |
|||||
ProtoName |
协议描述名称 |
如: role_cfg |
|
||
OutputFile |
输出文件 |
如: role_cfg.bin |
|
||
KeyRow |
字段名描述行 |
如: 2 |
|
||
KeyCase |
字段名大小写 |
如: 小写 |
|
||
KeyWordSplit |
字段名分词字符 |
|
|||
KeyPrefix |
字段名固定前缀 |
|
|||
KeySuffix |
字段名固定后缀 |
|
|||
KeyWordRegex |
分词规则(正则表达式) 示例: |
判断规则 [A-Z_$ trn] |
移除分词符号规则 [_$ trn] |
前缀过滤规则 [a-zA-Z_$] |
|
Encoding |
编码转换 |
UTF-8 |
注:Google的protobuf库的代码里写死 了UTF-8,故而该选项对Protobuf的二 进制输出无效 |
||
UeCfg-UProperty |
UnrealEngine配置 支持的字段属性 |
字段分组 默认值: XResConfig |
蓝图权限 默认值: XResConfig |
编辑权限 默认值: EditAnywhere |
|
UeCfg-CaseConvert |
是否开启驼峰命名转换 (默认开启) |
true/false |
|
||
UeCfg-CodeOutput |
设置UE代码输出目录 |
代码输出目录 |
Publich目录前缀 |
Private目录前缀 |
|
UeCfg-RecursiveMode |
是否使用嵌套模式 (默认开启) |
true/false |
|
||
UeCfg-DestinationPath |
资源输出目录 |
资源输出目录 |
|
如果Excel里字段名使用上面示例里的规则,如果填的是 0UnlockLevel_num,则会忽略第一个0(不符合前缀过滤规则),按分词规则分词为Unlock、Level和num,
同时移除下划线分词符号(移除分词符号规则)。 然后按上面的大小写规则和 字段名分词字符
组成新的字段名,最后应用大小写规则。
假设 字段名分词字符
是 _
。 字段名大小写
是小写,则最后对应的协议的字段名是 unlock_level_num
。
字段名分词、大小写转换、等字段名转换的功能建议非必要不要使用。这里只是为了有些时候需要和其他工具搭配使用的时候的一些适配。
关于设置编码
由于protobuf里写死的UTF-8,所以编码设置不是对所有的功能都生效。如果输出的类型是代码文件或者文本文件,那么转表工具会尝试把文本内容转换成该编码。 对于二进制输出,这个选项是无效的。
从哪里读取字段映射信息?
字段映射信息我们除了可以直接使用 转表引擎-xresloader 的 -m
选项指定外,还支持多种读取来源。
如果从文件中读取,我们是根据文件后缀来区分读取来源的。
直接写在批量转表文件里(推荐)
在使用批量转表功能的时候建议直接写在批量转表配置里,详见 批量转表工具
直接写在Excel里: 文件后缀.xls,.xlsx
当字段映射信息保存在Excel里时,scheme的名字就是表名( -m
参数)。我们会先查找列明为 字段或header
、主配置或major
、次配置或minor
和 补充配置或addition
的字段,并依此列读取相应配置。如:
字段 |
简介 |
主配置 |
次配置 |
补充配置 |
说明 |
---|---|---|---|---|---|
DataSource |
配置数据源(文件路径,表名) |
资源转换示例.xlsx |
upgrade_10001 |
3,1 |
次配置为表名,补充配置为数据起始位置(行号, 列号) |
DataSource |
配置数据源(文件路径,表名) |
upgrade_10002 |
3,1 |
次配置为表名,补充配置为数据起始位置(行号, 列号) |
|
MacroSource |
元数据数据源(文件路径,表名) |
资源转换示例.xlsx |
macro |
2,1 |
次配置为表名,补充配置为数据起始位置(行号, 列号) |
编程接口配置 |
|||||
ProtoName |
协议描述名称 |
role_upgrade_cfg |
|||
OutputFile |
输出文件 |
role_upgrade_cfg.bin |
|||
KeyRow |
字段名描述行 |
2 |
|||
KeyCase |
字段名大小写 |
不变 |
大写/小写/不变 |
||
KeyWordSplit |
字段名分词字符 |
||||
KeyPrefix |
字段名固定前缀 |
||||
KeySuffix |
字段名固定后缀 |
||||
KeyWordRegex |
分词规则 |
(判断规则,移除分词符号规则,前缀过滤规则)正则表达式 |
|||
Encoding |
编码转换 |
UTF-8 |
直接写在json文件里: 文件后缀.json
当字段映射信息保存在Excel里时,我们认为json的根节点包含一个数组,下面时key-value类型数据,key为scheme的名字( -m
参数)。里面还是Key-Value类型或Key-List类型。对应着每项配置。如:
{
"scheme_kind": {
"DataSource": ["资源转换示例.xlsx", "kind", "3,1"],
"MacroSource": ["资源转换示例.xlsx", "macro", "2,1"],
"ProtoName": "role_cfg",
"OutputFile": "role_cfg.bin",
"KeyRow": 2,
"KeyCase": "小写",
"KeyWordSplit": "_",
"KeyWordRegex": ["[A-Z_\\$ \\t\\r\\n]", "[_\\$ \\t\\r\\n]", "[a-zA-Z_\\$]"],
"Encoding": "UTF-8"
}
}
直接写在ini文件里: 文件后缀.ini,.conf,.cfg
当字段映射信息保存在Excel里时,scheme的名字( -m
参数)是section的名字,里面的数据是:
Key名称.0 => Key名称的主配置
Key名称.1 => Key名称的次配置
Key名称.2 => Key名称的补充配置
比如:
[scheme_kind]
DataSource.0 = 资源转换示例.xlsx
DataSource.1 = kind
DataSource.2 = 3,1
MacroSource.0 = 资源转换示例.xlsx
MacroSource.1 = macro
MacroSource.2 = 2,1
ProtoName = role_cfg
OutputFile = role_cfg.bin
KeyRow = 2
KeyCase = 小写
KeyWordSplit = _
KeyWordRegex.0 = [A-Z_\$ \t\r\n]
KeyWordRegex.1 = [_\$ \t\r\n]
KeyWordRegex.2 = [a-zA-Z_\$]
Encoding = UTF-8
完整的样例
以上配置选项在 xresloader sample 中有完整的示例,并且在。 xresloader 的 README.md
中有举例说明。
数据输出和数据加载
所有输出的数据的结构都是按照 https://github.com/xresloader/xresloader/blob/master/header/pb_header_v3.proto 的 xresloader_datablocks
的结构。
> 转表功能和二进制数据读取的示例: https://github.com/xresloader/xresloader/tree/master/sample
> 文本和Msgpack数据读取示例: https://github.com/xresloader/xresloader/tree/master/loader-binding
输出类型
在 转表引擎-xresloader 里可以看到,转表工具可以把Excel数据源导出成多种输出。下面列举重要的几种,项目可以根据自己的情况选择一种或几种导出方式。比如如果做Web端的GM工具,可以使用导出成xml或者javascript代码。
导出为协议二进制数据 (推荐)
对应 -t bin
。这是推荐的转出方式,导出的是 xresloader_datablocks
打包后的二进制数据,文件占用最小。任何支持protobuf的语言和开发环境都可以读取。
其中每个 data_block
数据块都对应Excel里的一行数据,里面的数据格式是用户指定的协议打包成二进制后的数据。
导出为json、xml、lua代码等文本数据 (可选)
对应 -t json
、 -t xml
、 -t lua
、 -t js
。 输出的格式也是header+数据body的形式。
Json的数据格式是:
[
{
"count": "(数字)数据条目数量",
"xres_ver":"xresloader版本号",
"hash_code":"文本输出无hash码",
"data_ver":"数据版本号"
}, {
"协议名":[
{"Excel数据Key": "Excel数据内容"},
{"每行一条": "数据内容..."}
]
},
"协议名"
]
Xml的数据格式是:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<!--this file is generated by xresloader, please don't edit it.-->
<header>
<xres_ver>xresloader版本号</xres_ver>
<hash_code>文本输出无hash码</hash_code>
<data_ver>数据版本号</data_ver>
<count>数据条目数量</count>
</header>
<body>
<协议名>每行一条,数据内容
<Excel数据Key>Excel数据内容</Excel数据Key>
</协议名>
</body>
<data_message_type>协议名</data_message_type>
</root>
Lua和Javacript的输出方式和输出设置有关,也很容易看懂,这里就不全部列举了。只列举一个Lua的其中一种输出方式:
-- this file is generated by xresloader, please don't edit it.
return {
[1] = {
xres_ver = "xresloader版本号",
hash_code = "文本输出无hash码",
data_ver = "数据版本号",
count = 0, -- 数据条目数量,
},
[2] = "协议名",
["协议名"] = {
{ ["Excel数据Key"] = "Excel数据内容" }, -- 每行一条,数据内容
}
}
默认情况,文本数据的输出是紧缩的。就没有上面格式列举出的看起来美观,可以通过 --pretty 缩进数量
来设置格式化输出。
导出为Msgpack打包的二进制数据 (可选)
对应 -t msgpack
。 如果不希望引入复杂的加载库,又希望打包出的数据是紧缩的二进制数据。我们提供了打包成msgpack格式的选项。
读取msgpack的工具和库很多,并且效率也很高,语言支持很很好。数据输出结构是:
{
header : {
xres_ver: "版本号字符串",
data_ver: "版本号字符串",
count: 配置记录个数,
hash_code: "hash算法:hash值",
}
data_block: [
{配置内容},
{配置内容},
{配置内容},
],
data_message_type: "协议名"
}
使用Msgpack的话, https://github.com/xresloader/xresloader/tree/master/loader-binding/msgpack 里有python2和node.js的读取示例。
导出为UE支持的CSV或JSON数据和代码 (可选)
xresloader从2.0.0版本开始支持导出UE所支持的CSV或者JSON格式数据,使用 -t ue-csv
或 -t ue-json
可以指定导出的UE支持的数据格式内容。
导出UE数据后,我们还会导出对应加载数据的UE C++类代码,具体可用的控制选项参见 可用的配置项 。
输出的代码有两种模型,一种是扁平模型,会把所有热 repeated 字段和 message 类型平铺到输出的类里。另一种是保留原始结构的嵌套模式。
默认使用使用的是嵌套模式,可以通过 -m UeCfg-RecursiveMode=true/false
来控制是否开启嵌套模式。
xresloader sample ue csv 和 xresloader sample ue json 中的是两种模式的输出代码,可以很容易看出来两者的差异和相应插件的功能。
生成完数据后我们在输出目录生成一个 UnreaImportSettings.json 文件,用于 UEEditor-Cmd 的导入命令。
比如我们UE安装在环境变量 $UNREAL_ENGINE_ROOT
里,工程UE文件位于 $UNREAL_PROJECT_DIR/ShootingGame.uproject
。
然后导出目录是 $XRESLOADER_OUTPUT_DIR
那么我们可以通过
java -client -jar xresloader.jar -t ue-json -o $XRESLOADER_OUTPUT_DIR -f sample-conf/kind.pb \
-m DataSource=role_tables.xlsx|upgrade_10001|3,1 -m ProtoName=role_upgrade_cfg \
-m OutputFile=RoleUpgradeCfg.json -m KeyRow=2 \
-m UeCfg-CodeOutput=$UNREAL_PROJECT_DIR/Source/ShooterGame|Public/Config|Private/Config
来生成配置和代码。如果结构变化,可能需要重新生成工程编译工程的动态库。最后可以再通过UE的命令行工具重新导入资源(以Win64为例),如果之前导入过编辑器里回自动检测到然后提示刷新:
$UNREAL_ENGINE_ROOT/Engine/Binaries/Win64/UE4Editor-Cmd.exe $UNREAL_PROJECT_DIR/ShootingGame.uproject \
-run=ImportAssets -importsettings=$XRESLOADER_OUTPUT_DIR/UnreaImportSettings.json \
-AllowCommandletRendering -nosourcecontrol
然后需要增加蓝图接口获取Helper
URoleUpgradeCfgHelper* UMyBlueprintFunctionLibrary::GetRoleUpgradeCfg()
{
UClass* clazz = URoleUpgradeCfgHelper::StaticClass();
if (nullptr == clazz) {
return nullptr;
}
return clazz->GetDefaultObject<URoleUpgradeCfgHelper>();
}
就可以在蓝图中使用了:

如果我们希望在Excel里配置引用UE内的资源文件,可以使用 org.xresloader.ue.ue_type_name
插件和 org.xresloader.ue.ue_type_is_class
插件。
前者会把UE的输出代码转为 TSoftObjectPtr<ue_type_name>
来指向UE内的资源,后者会把UE的输出代码转为 TSoftClassPtr<ue_type_name>
来指向UE内的类型。
比如我们配置字段:
message monster_role {
option (org.xresloader.ue.helper) = "helper";
option (org.xresloader.msg_description) = "怪物角色表";
int32 monster_id = 1 [ (org.xresloader.ue.key_tag) = 1 ];
string pawn_class = 13 [ (org.xresloader.ue.ue_type_name) = "APawn", (org.xresloader.ue.ue_type_is_class) = true, (org.xresloader.field_description) = "机器人Pawn类型" ]; // 默认的蓝图类
}
那么我们可以在Excel中配置:
怪物ID |
默认的蓝图类 |
---|---|
monster_id |
pawn_class |
2001 |
Blueprint’/Game/Blueprints/Pawns/BotPawnDemo.BotPawnDemo_C’ |
2002 |
Blueprint’/Game/Blueprints/Pawns/BotPawnDemo_range.BotPawnDemo_range_C’ |
2003 |
Blueprint’/Game/Blueprints/Pawns/BotPawn_Melee.BotPawn_Melee_C’ |
导出枚举类型成代码 (可选)
对应 -c
然后可以使用 -t json
、 -t xml
、 -t lua
、 -t js
、 -t ue-csv
、 -t ue-json
来指定按哪种方式输出枚举量。
比如把protobuf协议里的枚举输出成Lua代码,kind.proto
文件:
import "xresloader.proto";
// 常量类型
enum game_const_config {
option allow_alias = true;
EN_GCC_UNKNOWN = 0;
EN_GCC_PERCENT_BASE = 10000;
EN_GCC_RANDOM_RANGE_UNIT = 10;
EN_GCC_RESOURCE_MAX_LIMIT = 9999999;
EN_GCC_LEVEL_LIMIT = 999;
EN_GCC_SOLDIER_TYPE_MASK = 100;
EN_GCC_ACTIVITY_TYPE_MASK = 1000;
EN_GCC_FORMULAR_TYPE_MASK = 10;
EN_GCC_SCREEN_WIDTH = 1136;
EN_GCC_SCREEN_HEIGHT = 640;
EN_GCC_CAMERA_OFFSET = 268;
}
// 货币类型
enum cost_type {
EN_CT_UNKNOWN = 0;
EN_CT_MONEY = 10001 [(org.xresloader.enum_alias) = "金币"];
EN_CT_DIAMOND = 10101 [(org.xresloader.enum_alias) = "钻石"];
}
// 这个message用于示例下面导出协议描述,对导出枚举数据无意义
message role_upgrade_cfg {
option (org.xresloader.ue.helper) = "helper";
option (org.xresloader.msg_description) = "Test role_upgrade_cfg with multi keys";
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;
}
Lua目标代码(标准形式):
-- this file is generated by xresloader, please don't edit it.
local const_res = {
game_const_config = {
EN_GCC_SCREEN_WIDTH = 1136,
EN_GCC_SCREEN_HEIGHT = 640,
EN_GCC_UNKNOWN = 0,
EN_GCC_CAMERA_OFFSET = 268,
EN_GCC_FORMULAR_TYPE_MASK = 10,
EN_GCC_LEVEL_LIMIT = 999,
EN_GCC_RESOURCE_MAX_LIMIT = 9999999,
EN_GCC_SOLDIER_TYPE_MASK = 100,
EN_GCC_PERCENT_BASE = 10000,
EN_GCC_RANDOM_RANGE_UNIT = 10,
EN_GCC_ACTIVITY_TYPE_MASK = 1000,
},
cost_type = {
EN_CT_DIAMOND = 10101,
EN_CT_MONEY = 10001,
EN_CT_UNKNOWN = 0,
},
}
return const_res
对于一些特殊的Lua环境(比如Unity中)可能希望按Lua 5.1的方式加载模块,那么我们也可以使用特殊选项来更换导出方式,比如使用 --lua-module ProtoEnums.Kind
后输出如下:
module("ProtoEnums.Kind", package.seeall)
-- this file is generated by xresloader, please don't edit it.
local const_res = {
game_const_config = {
EN_GCC_SCREEN_WIDTH = 1136,
EN_GCC_SCREEN_HEIGHT = 640,
EN_GCC_UNKNOWN = 0,
EN_GCC_CAMERA_OFFSET = 268,
EN_GCC_FORMULAR_TYPE_MASK = 10,
EN_GCC_LEVEL_LIMIT = 999,
EN_GCC_RESOURCE_MAX_LIMIT = 9999999,
EN_GCC_SOLDIER_TYPE_MASK = 100,
EN_GCC_PERCENT_BASE = 10000,
EN_GCC_RANDOM_RANGE_UNIT = 10,
EN_GCC_ACTIVITY_TYPE_MASK = 1000,
},
cost_type = {
EN_CT_DIAMOND = 10101,
EN_CT_MONEY = 10001,
EN_CT_UNKNOWN = 0,
},
}
game_const_config = const_res.game_const_config
cost_type = const_res.cost_type
于导出的代码,可以通过 --pretty 缩进数量
来设置格式化输出,上面的输出使用的都是 --pretty 2
。
其他语言和格式导出选项也类似上面的Lua的结构,具体请参考输出的文件内容加载。
导出协议描述成代码 (可选)
对应 -i
然后可以使用 -t json
、 -t xml
、 -t lua
、 -t js
、 -t ue-csv
、 -t ue-json
来指定按哪种方式输出枚举量。
比如把上述protobuf协议里的描述输出成Lua代码,协议文件见 kind.proto
Lua目标代码(标准形式):
-- this file is generated by xresloader, please don't edit it.
local const_res = {
files = {
{
enum_type = {
cost_type = {
name = "cost_type",
value = {
EN_CT_DIAMOND = {
name = "EN_CT_DIAMOND",
number = 10101,
options = {
enum_alias = "钻石",
},
},
EN_CT_MONEY = {
name = "EN_CT_MONEY",
number = 10001,
options = {
enum_alias = "金币",
},
},
},
},
game_const_config = {
name = "game_const_config",
options = {
allow_alias = true,
},
},
},
message_type = {
role_upgrade_cfg = {
field = {
CostType = {
name = "CostType",
number = 3,
options = {
field_description = "Refer to cost_type",
verifier = "cost_type",
},
type_name = "UINT32",
},
Id = {
name = "Id",
number = 1,
options = {
key_tag = 1000,
},
type_name = "UINT32",
},
Level = {
name = "Level",
number = 2,
options = {
key_tag = 1,
},
type_name = "UINT32",
},
},
name = "role_upgrade_cfg",
options = {
helper = "helper",
msg_description = "Test role_upgrade_cfg with multi keys",
},
},
},
name = "kind.proto",
package = "",
path = "kind.proto",
},
},
}
return const_res
同样,对于一些特殊的Lua环境(比如Unity中)可能希望按Lua 5.1的方式加载模块,那么我们也可以使用特殊选项来更换导出方式,比如使用 --lua-module ProtoOptions.Kind
。
输出的代码或文本同样可以通过 --pretty 缩进数量
来设置格式化输出,上面的输出使用的都是 --pretty 2
。
xresloader sample 中的 proto_v3/kind_option.js
, proto_v3/kind_option.lua
, proto_v3/kind_option.mod.lua
或 中的 proto_v2/kind_option.js
, proto_v2/kind_option.lua
, proto_v2/kind_option.mod.lua
有更多的示例。
其他语言和格式导出选项也类似上面的Lua的结构,具体请参考输出的文件内容加载。
Proto v2和Proto v3
转表工具同时支持proto v2和proto v3,但是转出是使用的proto v3模式。而对于proto v2和proto v3仅在数字类型的 repeated
字段上有些许区别。
详见: https://developers.google.com/protocol-buffers/docs/proto3#specifying-field-rules
简单地说,就是proto v2里数字类型的 repeated
字段默认是 [ packed = false ]
。打包结构是每个项目一个Key-Value数据对。
而在proto v3里是 [ packed = false ]
。打包结构是Key-Value个数N,而后紧挨着N个Value。
这可能导致转出的数据无法正常读取。解决方法也很简单,那就是对数字类型的 repeated
字段手动指定是否是packed。如:
message arr_in_arr {
optional string name = 1;
repeated int32 int_arr = 2 [ packed = true ];
repeated string str_arr = 3;
}
或proto v3版本。
message arr_in_arr {
string name = 1;
repeated int32 int_arr = 2 [ packed = true ];
repeated string str_arr = 3;
}
数据加载
前面小节我们大致展示了转出数据的结构,以此比较容易理解加载的方式。本小节则是对一些环境和语言的简单加载库。
方式-1(推荐): (推荐)使用 xres-code-generator 生成解析代码(C++/Lua/C#/Upb Lua)
对于C++、Lua和C#,我们推荐使用 xres-code-generator 生成解析代码。(未来会开发更多的语言支持)。详见: 使用 xres-code-generator 生成解析代码 。
方式-2(可选): 使用C++加载二进制数据
此加载方式需要上面的 导出为协议二进制数据 (推荐)
在 快速上手-方式.1: 使用读取库解析 里我们已经给出了这种加载方式的具体使用,这里不再复述。 这里提供的方式也支持protobuf的lite模式。
方式-3(可选): 使用lua-pbc加载二进制数据
此加载方式需要上面的 导出为协议二进制数据 (推荐)
对于一些中使用lua的项目,也可以选择使用 pbc 来加载数据。 我们在 https://github.com/xresloader/xresloader/tree/master/loader-binding/pbc 有使用pbc进行加载的manager封装。 在 https://github.com/owent-utils/lua/tree/master/src/data 里有对多项数据集的封装。这两部分都依赖 https://github.com/owent-utils/lua 仓库里提供的utility层。
简要的加载代码如下:
-- 加载lua加载器
local class = require('utils.class')
local loader = require('utils.loader')
-- 必须保证pbc已经载入
local pbc = protobuf
pbc.register(io.open('pb_header.pb', 'rb'):read('a')) -- 注册转表头描述文件
pbc.register(io.open('用户协议.pb', 'rb'):read('a')) -- 注册转表协议描述文件
local cfg = loader.load('data.pbc_config_data_set')
-- 设置路径规则 (一定要带一个%s)
-- 当读取协议message类型为PROTO的配置时,实际查找的协议名称为string.format(rule, PROTO)
-- 比如protobuf的package名称是config,那么这里rule填 config.%s
cfg:set_path_rule('%s')
-- 设置配置列表加载文件
-- cfg:set_list('data.conf_list') -- cfg:reload() 会在清空配置数据后执行require('data.conf_list')
简要的配置清单代码( data/conf_list.lua
)如下:
local class = require('utils.class')
local loader = require('utils.loader')
local cfg = loader.load('data.pbc_config_data_set')
-- role_cfg, 第二个参数是个函数,返回key,这样读入的数据可以按key-value模式组织起来
cfg:load_buffer_kv('role_cfg', io.open('role_cfg.bin', 'rb'):read('a'), function(k, v)
return v.id or k
end)
-- 第三个参数是个别名
cfg:load_buffer_kv('role_cfg', io.open('role_cfg.bin', 'rb'):read('a'), function(k, v)
return v.id or k
end, 'alias_name')
-- 这后面的时读取,不是加载
-- 别名和非别名的数据一样的
vardump(cfg:get('role_cfg'):get(10002)) -- dump id=10002的role_cfg表的数据
vardump(cfg:get('alias_name'):get(10002)) -- dump id=10002的role_cfg表的数据
-- 直接读取里面的字段
print(string.format('kind id=%d, name=%s, dep_test.name=%s', kind.id, kind.name, kind.dep_test.name))
方式-4(可选): 使用C#和DynamicMessage-net加载二进制数据
此加载方式需要上面的 导出为协议二进制数据 (推荐)
为了方便Unity能够不依赖反射动态获取类型和读取配置,我们提供了 DynamicMessage-net 项目。 这个项目依赖 protobuf-net 的底层。 详见项目主页: https://github.com/xresloader/DynamicMessage-net
方式-5(可选): 加载msgpack文本数据
此加载方式需要上面的 导出为Msgpack打包的二进制数据 (可选)
Msgpack的支持库语言和库很多,我们就不依依列举了。我们有一些python和node.js上的简单示例可以参见 https://github.com/xresloader/xresloader/tree/master/loader-binding/msgpack 。
方式-6(可选): 使用node.js加载javascript文本数据
此加载方式需要上面的 导出为json、xml、lua代码等文本数据 (可选)
把配置输出javascript代码的时候,我们支持Node.js模式和AMD模式。
比如,xresloader sample 中导出的 role_cfg.n.js 。我们可以通过以下代码加载:
const role_cfg_block = require('./role_cfg.n');
const role_cfg_header = role_cfg_block.role_cfg_header; // 数据头信息,header
const role_cfg = role_cfg_block.role_cfg; // 数据集合,Ayyar类型
// 读取数据
console.log(`we got ${role_cfg_header.count} rows, data version: ${role_cfg_header.data_ver}`);
for (const i in role_cfg) {
if (role_cfg[i].id === 10001) {
console.log('================= print data with id = 10001 =================');
console.log(role_cfg[i]);
}
}
详见: https://github.com/xresloader/xresloader/tree/master/loader-binding/javascript
方式-7(可选): 使用lua加载导出的枚举类型
上面 导出枚举类型成代码 (可选) 提到,我们可以把一些枚举类型放在proto文件里统一维护,然后不同的使用者导出成不同目标语言的代码。
而对于protobuf没有原生支持的语言,我们支持导出 lua
、 javascript
、 xml
或 json
辅助我们使用。
比如上面两种Lua导出,我们可以直接通过Lua脚本加载:
local const_enum = require('kind_const')
print('game_const_config.EN_GCC_PERCENT_BASE = ' .. const_enum.game_const_config.EN_GCC_PERCENT_BASE)
function dump_all_enum (pv, ident)
for k, v in pairs(pv) do
if string.sub(k, 0, 1) ~= '_' and 'table' == type(v) then
print(string.format('%s%s = {', ident, k))
dump_all_enum(v, ident .. ' ')
print(string.format('%s}', ident))
else
print(string.format('%s%s = %s,', ident, k, v))
end
end
end
dump_all_enum(const_enum, '')
让我们再来看看Lua 5.1的module模式的枚举类型加载:
require('kind_const_module')
print('game_const_config.EN_GCC_PERCENT_BASE = ' .. ProtoEnums.Kind.game_const_config.EN_GCC_PERCENT_BASE)
function dump_all_enum (pv, ident)
for k, v in pairs(pv) do
if string.sub(k, 0, 1) ~= '_' and 'table' == type(v) then
print(string.format('%s%s = {', ident, k))
dump_all_enum(v, ident .. ' ')
print(string.format('%s}', ident))
else
print(string.format('%s%s = %s,', ident, k, v))
end
end
end
dump_all_enum(ProtoEnums.Kind, '')
其他语言和格式的加载请参考输出文件。
批量转表工具
批量转表 - GUI和CLI工具示例
在实际项目中,我们一般会同时涉及几十甚至上百张表。为了统一配置,我们提供了批量转表工具。 批量转表分为 命令行批量转表工具-xresconv-cli 和 GUI批量转表工具-xresconv-gui 两个工具。
前者用于服务器和客户端发布流程的集成,后者主要提供给临时转表和策划验证数据时可以拿来转出部分数据。 这两个工具都以 批量转表配置模板仓库-xresconv-conf 中的配置为配置规范。

以上为 命令行批量转表工具-xresconv-cli 的输出示例。

以上为 GUI批量转表工具-xresconv-gui 的输出示例。
批量转表 - 配置示例
我们在 快速上手-配置批量转表配置文件 章节里也提供了一个简单的例子:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<global>
<work_dir desc="工作目录,相对于当前xml的目录,我们的Excel文件放在这里">.</work_dir>
<xresloader_path desc="指向前面下载的 转表工具-xresloader,相对于当前xml的目录">../xresloader/target/xresloader-2.9.0.jar</xresloader_path>
<proto desc="协议类型,-p选项">protobuf</proto>
<output_type desc="输出类型,对应-t选项,输出二进制">bin</output_type>
<output_type desc="多种输出时可以额外定义某个节点的重命名规则" rename="/(?i)\.bin$/\.json/">json</output_type>
<proto_file desc="协议描述文件,-f选项">kind.pb</proto_file>
<output_dir desc="输出目录,-o选项">../sample-data</output_dir>
<data_src_dir desc="数据源目录,-d选项"></data_src_dir>
<java_option desc="java选项-最大内存限制2GB">-Xmx2048m</java_option>
<java_option desc="java选项-客户端模式">-client</java_option>
<default_scheme name="KeyRow" desc="默认scheme模式参数-Key行号,对应上面Id、Level、CostType、CostValue那一行">2</default_scheme>
<option desc="全局自定义选项" name="美化文本输出,缩进为2个空格">--pretty 2</option>
</global>
<groups desc="分组信息(可选)">
<group id="client" name="客户端"></group>
<group id="server" name="服务器"></group>
</groups>
<category desc="类信息(用于GUI工具的树形结构分类显示)">
<tree id="all_cats" name="大分类">
<tree id="kind" name="角色配置"></tree>
</tree>
</category>
<list>
<item name="升级表" cat="kind" class="client server">
<scheme name="DataSource" desc="数据源(文件名|表名|数据起始行号,数据起始列号)">role_tables.xlsx|upgrade_10001|3,1</scheme>
<scheme name="ProtoName" desc="协议名">role_upgrade_cfg</scheme>
<scheme name="OutputFile" desc="输出文件名">role_upgrade_cfg.bin</scheme>
</item>
</list>
</root>
批量转表 - 配置结构规范
<?xml version="1.0" encoding="UTF-8"?>
<!-- <?xml-stylesheet type="text/xsl" href="helper/view.xsl"?> -->
<root>
<include desc="可以包含其他文件配置,然后本文件里的配置将会覆盖或合并配置,相对于当前xml的目录">sample.xml</include>
<global>
<work_dir desc="工作目录,相对于当前xml的目录">../xresloader/sample</work_dir>
<xresloader_path desc="xresloader地址,相对于当前xml的目录">../target/xresloader-2.9.0.jar</xresloader_path>
<proto desc="协议类型,-p选项">protobuf</proto>
<output_type desc="输出类型,-t选项,支持多个同时配置多种输出">bin</output_type>
<output_type desc="多种输出时可以额外定义某个节点的重命名规则" rename="/(?i)\.bin$/\.json/">json</output_type>
<output_type desc="可以通过指定class来限制输出的规则" rename="/(?i)\.bin$/\.csv/" class="client" >ue-csv</output_type>
<!-- output_type 里的class标签对应下面item里的class标签,均可配置多个,多个用空格隔开,任意一个class匹配都会启用这个输出 -->
<proto_file desc="协议描述文件,-f选项">proto_v3/kind.pb</proto_file>
<output_dir desc="输出目录,-o选项"></output_dir>
<data_src_dir desc="数据源目录,-d选项"></data_src_dir>
<data_version desc="数据版本号,留空则自动生成">1.0.0.0</data_version>
<rename desc="重命名规则,正则表达式:/搜索模式/替换内容/,对应xresloader的-n选项, 如果在output_type里设置了rename,以output_type里的rename为准,否则使用这里的全局配置" placeholder="/(?i)\.bin$/\.json/"></rename>
<java_option desc="java选项-最大内存限制2GB">-Xmx2048m</java_option>
<java_option desc="java选项-客户端模式">-client</java_option>
<default_scheme name="KeyRow" desc="默认scheme模式参数-Key行号">2</default_scheme>
<default_scheme name="MacroSource" desc="默认scheme模式参数-Key行号">资源转换示例.xlsx|macro|2,1</default_scheme>
</global>
<groups desc="分组信息">
<group id="client" name="客户端"></group>
<group id="server" name="服务器"></group>
</groups>
<category desc="类信息">
<tree id="all_cats" name="大分类">
<tree id="kind" name="角色配置"></tree>
</tree>
<tree id="test" name="测试"></tree>
</category>
<list>
<item file="资源转换示例.xlsx" scheme="scheme_kind" name="人物表" cat="kind" class="server"></item>
<item file="资源转换示例.xlsx" scheme="scheme_upgrade" name="升级表" cat="kind" class="server">
<option desc="自定义选项" name="移除空列表项">--disable-empty-list</option>
</item>
<item name="嵌套数组测试" cat="test" class="client server">
<scheme name="DataSource" desc="数据源(文件名|表名|数据起始行号,数据起始列号)">资源转换示例.xlsx|arr_in_arr|3,1</scheme>
<scheme name="ProtoName" desc="协议名">arr_in_arr_cfg</scheme>
<scheme name="OutputFile" desc="输出文件名">arr_in_arr_cfg.bin</scheme>
</item>
</list>
<gui>
<set_name desc="这个脚本用于修改树形节点的显示数据,便于策划核对具体的表名">
if (item_data.file) {
item_data.name += " (" + item_data.file.match(/([^.]+)\.\w+$/)[1] + ")"
}
</set_name>
<on_before_convert type="text/javascript" timeout="15000" description="事件执行结束必须调用resolve(value)或reject(reason)函数,以触发进行下一步">
// 这里可以执行nodejs代码,比如下面是Windows平台执行 echo work_dir
var os = require("os");
var spawn = require("child_process").spawn;
if (os.type().substr(0, 7).toLowerCase() == "windows") {
var exec = spawn("cmd", ["/c", "echo " + work_dir], {
cwd: work_dir,
encoding: 'utf-8'
});
exec.stdout.on("data", function(data) {
log_info(data);
});
exec.stderr.on("data", function(data) {
log_error(data);
});
exec.on("error", function(data) {
log_error(data.toString());
resolve();
// reject("执行失败" + data.toString());
});
exec.on("exit", function(code) {
if (code === 0) {
resolve();
} else {
resolve();
// reject("执行失败");
}
});
} else {
resolve();
}
</on_before_convert>
<on_after_convert type="text/javascript" timeout="60000" description="事件执行结束必须调用resolve(value)或reject(reason)函数,以触发进行下一步">
// 同上
alert_warning("自定义转表完成后事件,可以执行任意nodejs脚本");
resolve();
</on_after_convert>
<script name="自定义脚本" desc="用于自定义按钮" type="text/javascript">
// 同上
resolve();
</script>
</gui>
</root>
如上是所有支持的标签的说明及示例配置。
除了前面章节提及过的字段外,还有一些特别的配置。
//root/include
: 包含其他配置文件。相当于把其他配置文件离得配置复制过来。然后这个文件里有重复得配置则覆盖之。//root/global/work_dir
: 运行 xresloader 的目录。如果是相对目录的话相对于xml配置文件。//root/global/xresloader_path
: xresloader 的jar包的路径。如果是相对目录的话相对于xml配置文件。//root/global/java_option
: 用于传给java命令。所有的条目都会附加到java选项种。比如示例种的-Xmx2048m
用于设置最大堆为2GB,用于对于比较大的Excel导表的时候可能会临时占用较高内存。//root/global/default_scheme
: 默认的导表映射关系的配置(详见 可用的配置项 )。对所有转换条目都会附加这里面的配置项。可多个。//root/list/item/scheme
: 导表映射关系的配置(详见 可用的配置项 )。如果和上面default_scheme
冲突则会覆盖默认配置,仅对这个条目生效。可多个。//root/list/item/option
: 运行 xresloader 的额外附加参数(详见 转表引擎-xresloader )。比如使用--enable-empty-list
可以不移除Excel里的空数据,仅对这个条目生效。可多个。//root/global/output_type[class]
: 如果output_type
中配置了class
属性,则这个输出类型仅对//root/list/item
中也配置了同名class
属性的条目生效。 此特性可用于比如让UE-Csv
或UE-Json
的输出仅对客户端配置生效。class
属性可以设置多个,多个用空格隔开,配置多个时任意一个匹配都会启用output_type
。//root/global/output_type[tag]
: 如果output_type
中配置了tag
属性,则这个输出类型仅对//root/list/item
中也配置了同名tag
属性的条目生效。 此特性可用于比如让UE-Csv
或UE-Json
的输出仅对客户端配置生效。tag
属性可以设置多个,多个用空格隔开,配置多个时任意一个匹配都会启用output_type
。
CLI批量转表工具 - 启动参数
CLI工具在命令行或终端中执行,可用参数可以直接加 -h
查看。
GUI批量转表工具 - 启动参数
--input <文件名>
: 指定初始的转表清单文件。--debug
: 开启debug模式并启动开发人员工具。(便于调试自定义事件)--custom-selector/--custom-button <json文件名>
: 增加自定义选择器(自定义按钮),允许多个。(2.3.0版本及以上)
GUI批量转表工具 - 特殊事件
GUI工具 xresconv-gui 从2.1.0版本开始提供了一些特殊事件,便于用来做工具集成。事件响应内容必须是 node.js 代码,可以通过 require('包名')
来导入所有官方内置的模块。事件响应上下文还额外提供了一些接口用于和框架交互:
GUI事件 - 显示转表项名称 //root/gui/set_name
配置示例:
<gui>
<set_name description="设置转表项的名字字段,每个转表项会调用一次">
if (item_data.file) {
item_data.name += " (" + item_data.file.match(/([^.]+)\.\w+$/)[1] + ")"; // 显示名称追加数据源文件名
}
</set_name>
</gui>
事件接口和属性变量:
{
work_dir: "工作目录(要求版本>=2.2.0)",
configure_file: "载入的配置文件路径(要求版本>=2.2.0)",
item_data: {
id: "条目ID",
file: "数据源文件",
scheme: "数据源scheme表名",
name: "描述名称",
cat: "分类名称",
options: ["额外选项"],
desc: "描述信息",
scheme_data: {"元数据Key": "元数据Value"},
tags: ["tag列表"], // 版本 >= 2.2.3
classes: ["class列表"] // 版本 >= 2.2.3
},
data: {}, // 绑定在事件上的私有数据,可用于保存全局状态, 版本 >= 2.3.0
alert_warning: function(content, title, options) {}, // (要求版本>=2.2.0) 警告弹框, options 结构是 {yes: 点击是按钮回调, no: 点击否按钮回调, on_close: 关闭后回调}
alert_error: function(content, title) {}, // (要求版本>=2.2.0) 错误弹框
log_info: function (content) {}, // (要求版本>=2.2.0) 打印info日志
log_notice: function (content) {}, // 打印notice日志, 版本 >= 2.3.0
log_warning: function (content) {}, // 打印warning日志, 版本 >= 2.3.0
log_error: function (content) {} // (要求版本>=2.2.0) 打印error日志
}
比如 批量转表配置模板仓库-xresconv-conf 中的 sample.xml
文件,我们给所有条目的名字附加上了不带后缀的文件名。
GUI事件 - 转表前事件和转表成功后事件 //root/gui/on_before_convert
和 //root/gui/on_after_convert
注意在 //root/gui/on_before_convert
和 //root/gui/on_after_convert
事件中,执行完成以后一定要调用 resolve()
来通知上层框架执行成功,或调用 reject("错误消息")
来通知上层框架执行失败。
否则执行会一直等待到超时然后失败结束。
配置示例(创建子进程):
<gui>
<on_before_convert name="转表开始前事件" type="text/javascript" timeout="15000" description="事件执行结束必须调用resolve(value)或reject(reason)函数,以触发进行下一步">
// 这里可以执行nodejs代码,比如下面是Windows平台执行 echo work_dir
var os = require("os");
var spawn = require("child_process").spawn;
if (os.type().substr(0, 7).toLowerCase() == "windows") {
var exec = spawn("cmd", ["/c", "echo " + work_dir], {
cwd: work_dir,
encoding: 'utf-8'
});
exec.stdout.on("data", function(data) {
log_info(data);
});
exec.stderr.on("data", function(data) {
log_error(data);
});
exec.on("error", function(data) {
log_error(data.toString());
resolve();
// reject("执行失败" + data.toString());
});
exec.on("exit", function(code) {
if (code === 0) {
resolve();
} else {
resolve();
// reject("执行失败");
}
});
} else {
resolve();
}
</on_before_convert>
<on_after_convert name="转表完成后事件" type="text/javascript" timeout="60000" description="事件执行结束必须调用resolve(value)或reject(reason)函数,以触发进行下一步">
// 同上
alert_warning("自定义转表完成后事件,可以执行任意nodejs脚本");
resolve();
</on_after_convert>
</gui>
在这是里面必须是一个有效的nodejs代码,其中 //root/gui/on_before_convert[timeout]
和 //root/gui/on_after_convert[timeout]
可以用于控制超时时间,单位是毫秒。
传入的参数是:
{
work_dir: "执行xresloader的工作目录",
configure_file: "载入的配置文件路径(要求版本>=2.2.0)",
xresloader_path: "xresloader目录",
global_options: {"全局选项": "VALUE"},
selected_nodes: ["选中要执行转表的节点集合"],
selected_items: ["选中要执行转表的item对象集合,数据结构同上面的 item_data"], // 版本 >= 2.2.3
run_seq: "执行序号",
data: {}, // 绑定在事件上的私有数据,可用于保存全局状态, 版本 >= 2.3.0
alert_warning: function(content, title, options) {}, // 警告弹框, options 结构是 {yes: 点击是按钮回调, no: 点击否按钮回调, on_close: 关闭后回调}
alert_error: function(content, title) {}, // 错误弹框
log_info: function (content) {}, // 打印info日志
log_error: function (content) {}, // 打印error日志
log_notice: function (content) {}, // 打印notice日志, 版本 >= 2.3.0
log_warning: function (content) {}, // 打印warning日志, 版本 >= 2.3.0
resolve: function (value) {}, // 通知上层执行结束,相当于Promise的resolve
reject: function(reason) {}, // 通知上层执行失败,相当于Promise的reject
require: function (name) {} // 相当于 nodejs的 require(name) 用于导入nodejs 模块
}
在 批量转表配置模板仓库-xresconv-conf 中的 sample.xml
文件中也有示例。
GUI批量转表工具 - 自定义按钮
GUI自定义按钮 - 基本配置
从 2.3.0 版本开始,GUI工具增加了启动参数 --custom-selector/--custom-button <json文件名>``来自定义选择器(自定义按钮)。其中 ``json
文件的配置格式如下:
[{
"name": "选择器按钮名称", // [必须] 按钮显示名称
"by_schemes": [{ // [必须] item里配置file和scheme属性的选取规则(by_schemes和by_sheets里至少要配置一个)
"file": "文件名, 比如: 资源转换示例.xlsx", // [必须]
"scheme": "转表规则名, 比如: scheme_upgrade" // [可选] 此项可以为空,如果为空会命中所有file匹配的条目
}],
"by_sheets": [{ // [必须] item里的DataSource子节点配置DataSource的选取规则(by_schemes和by_sheets里至少要配置一个)
"file": "文件名, 比如: 资源转换示例.xlsx", // [必须]
"sheet": "文件名, 比如: arr_in_arr" // [可选] 此项可以为空,如果为空会命中所有DataSource中第一个选项和file匹配的条目
}],
"default_selected": false, // [可选] 默认选中
"style": "outline-secondary", // [可选] 按钮Style。默认: outline-secondary
// "action": ["unselect_all", "reload"] // [可选] 特殊行为,具体内容请参考下面的文档。
}] // 数组,可以多个按钮
上面的配置里, file
、 scheme
、 sheet
字段都支持 完全匹配的名称
、 glob: 通配符
和 regex: 正则表达式
三种形式。
特殊行为字段 action 可以控制按钮使用一些特殊功能而不是简单地选择和反选匹配项,目前支持的特殊功能如下:
reload
: 重新加载自定义按钮select_all
: 全部选中unselect_all
: 全部反选script: <脚本名字>
: 执行脚本,脚本名字 为//root/gui/script
节点的name
属性。
GUI自定义按钮 - 自定义脚本(点击回调)
上述配置中, script: <脚本名字>
里的 脚本名字 指向输入XML中 //root/gui/script[name=脚本名字]
的节点。比如如下配置中:
<gui>
<script name="自定义脚本" type="text/javascript">
// 同上
data.call_times = (data.call_times || 0) + 1;
alert_warning("自定义脚本,可用于自定义按钮");
log_notice(`自定义脚本:\n可用于自定义按钮 - notice日志(${data.call_times})`);
log_warning("自定义脚本\n可用于自定义按钮 - warning日志");
// resolve();
data.running = false;
resolve();
</script>
</gui>
我们可以把action配置成 ["script: 自定义脚本"]
来让点击按钮的时候执行这段脚本。和事件一样,这个脚本是一段 node.js 代码,额外提供的函数和属性变量有:
{
work_dir: "执行xresloader的工作目录",
xresloader_path: "xresloader目录",
global_options: {"全局选项": "VALUE"},
selected_nodes: ["选中要执行转表的节点集合"],
selected_items: ["选中要执行转表的item对象集合,数据结构同上面的 item_data"],
data: {}, // 绑定在按钮上的私有数据,可用于保存全局状态
alert_warning: function(content, title, options) {}, // 警告弹框, options 结构是 {yes: 点击是按钮回调, no: 点击否按钮回调, on_close: 关闭后回调}
alert_error: function(content, title) {}, // 错误弹框
log_info: function (content) {}, // 打印info日志
log_notice: function (content) {}, // 打印notice日志
log_warning: function (content) {}, // 打印warning日志
log_error: function (content) {}, // 打印error日志
resolve: function (value) {}, // 通知上层执行结束,相当于Promise的resolve
reject: function(reason) {}, // 通知上层执行失败,相当于Promise的reject
require: function (name) {} // 相当于 nodejs的 require(name) 用于导入nodejs 模块
}
GUI自定义按钮 - 按钮样式
按钮风格默认是 `outline-secondary`
。可选项为(详见: https://getbootstrap.com/docs/5.0/components/buttons/):
outline-primary
outline-secondary
outline-success
outline-danger
outline-warning
outline-info
outline-light
outline-dark
primary
secondary
success
danger
warning
info
light
dark
自定义按钮在 批量转表配置模板仓库-xresconv-conf 中的 sample.xml
和 xresconv-gui 中的 doc/custom-selector.json
文件里也有相应示例。
数据类型说明
数组、repeated和数据结构
因为Excel配置很多情况下都会留空,所以默认情况下如果Excel里的单元格被删除(空值不算),我们不会转出数据到数组结构中。也就是我们的数组时动态长度的。这样可以最大化压缩掉空值项。 同样对于optional的字段,空单元格也不会生成数据在二进制结构里。
比如:
第一排 |
第二排 |
第三排 |
---|---|---|
col[0] |
col[1] |
col[2] |
123 |
456 |
这样转出来的数据 col
的数组长度是2,其中: col[0]=123, col[1]=456
。
定长数组
有些情况我们使用数组来表示位置。比如:
第一排 |
第二排 |
第三排 |
---|---|---|
row[0] |
row[1] |
row[2] |
123 |
456 |
这种情况,我们希望。能够保持结构,即让row[1]转出为0(默认值)。
这时候 xresloader 提供了一个选项 --enable-empty-list
。在转表的时候使用这个选项,这样转出的数据就会是:
row
的数组长度是3,其中 row[0]=123, row[1]=0, row[2]=456
。
无符号整数
请注意有些语言(比如Java)没有无符号类型,但是协议层支持。转换这种类型的时候请确保Excel里填写的数值不会超出有符号类型的长度。否则可能导致裁剪。 在转表工具里,整数一律使用Long类型,所以注意即便协议类型是uint64,填写的数据也不要超过int64的范围。
日期和时间类型
如果Excel的单元格的类型设置为日期时间类型则会使用日期时间类型的数据转换规则。
由于POI内部是使用格式来判定是否是日期时间类型的,所以如果要使用日期时间类型,最好把格式设置成 年-月-日 时:分:秒
这种形式。
Excel里只保存绝对值时间,如果格式设置为 时:分:秒
那么 10:11:12
转出的时间是 1900-01-01 10:11:12
或 1902-01-01 10:11:12
。
但是我们使用的时候很多情况下需要配置一天的某个时间段,为了满足这种需求,我们假设所有的绝对值时间都在1970年以后。那么年份在 1970年或1970年之前 的时间,我们都会只取时、分、秒的部分计算时间戳。
比如上面的 10:11:12
会被转成 10*3600+11*60+12=36672
。
如果协议里的类型是字符串,那么时间转换的时候会检查时间格式,如果有“-”,则会按 yyyy-MM-dd
转换年月日部分;如果格式里有分号”:”,则会按 HH:mm:ss
转换时分秒部分。
如果两者都有我们会按 yyyy-MM-dd HH:mm:ss
转换年月日时分秒。
使用 xres-code-generator 生成解析代码
xres-code-generator 是一个基于 mako 模板引擎的代码生成工具,其内部提供了一些模板用于生成加载 xresloader 所生成的数据的代码。
仓库地址: https://github.com/xresloader/xres-code-generator
第一步,在proto文件中声明加载器和索引类型
导入 import "xrescode_extensions_v3.proto";
然后声明loader。更多可选项见: xres-code-generator 插件
syntax = "proto3";
import "xrescode_extensions_v3.proto";
message role_upgrade_cfg {
option (xrescode.loader) = {
file_path : "role_upgrade_cfg.bytes"
indexes : {
fields : "Id"
index_type : EN_INDEX_KL // Key - List 类型索引,映射关系为: (Id) => list<role_upgrade_cfg>
}
indexes : {
fields : "Id"
fields : "Level"
index_type : EN_INDEX_KV // Key - Value 类型索引,映射关系为: (Id, Level) => role_upgrade_cfg
}
// 允许多个索引,索引命名是所有的 [fields 字段].join("_"),也可以通过name属性自定义
tags : "client"
tags : "server"
};
int32 CostValue = 4;
int32 ScoreAdd = 5;
}
生成C++加载代码
使用模板
template/config_manager.h.mako
,template/config_manager.cpp.mako
,template/config_easy_api.h.mako
,template/config_easy_api.cpp.mako
,template/config_set.h.mako
,template/config_set.cpp.mako
生成加载代码
REPO_DIR=$PATH_TO_xres_code_generator;
mkdir -p "$REPO_DIR/sample/pbcpp";
cp -rvf "$REPO_DIR/template/common/cpp/"* "$REPO_DIR/sample/pbcpp";
PROTOC_BIN="$(which protoc)"
PYTHON_BIN="$(which python3 2>/dev/null)"
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
else
$PYTHON_BIN --version
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
fi
fi
if [[ $? -ne 0 ]] && [[ -e "$REPO_DIR/tools/find_protoc.py" ]]; then
PROTOC_BIN="$("$PYTHON_BIN" $REPO_DIR/tools/find_protoc.py)"
fi
"$PROTOC_BIN" -I "$REPO_DIR/sample/proto" -I "$REPO_DIR/pb_extension" "$REPO_DIR/sample/proto/"*.proto -o "$REPO_DIR/sample/sample.pb" ;
# You can use --pb-include-prefix "pbdesc/" to set subdirectory for generated files. This will influence the generated #include <...FILE_PATH>
"$PYTHON_BIN" "$REPO_DIR/xrescode-gen.py" -i "$REPO_DIR/template" -p "$REPO_DIR/sample/sample.pb" -o "$REPO_DIR/sample/pbcpp" \
-g "$REPO_DIR/template/config_manager.h.mako" -g "$REPO_DIR/template/config_manager.cpp.mako" \
-g "$REPO_DIR/template/config_easy_api.h.mako" -g "$REPO_DIR/template/config_easy_api.cpp.mako" \
-l "H:$REPO_DIR/template/config_set.h.mako" -l "S:$REPO_DIR/template/config_set.cpp.mako" \
"$@"
使用
config_manager
和config_easy_api
访问数据
#include <cstdio>
#include "config_manager.h"
#include "config_easy_api.h"
int main() {
// 初始化 ....
excel::config_manager::me()->init();
// 可选
// excel::config_manager::me()->set_version_loader([] (std::string& out) {
// // 读取版本号然后写出到 out
// return true; // 成功返回true,失败返回false
// });
// If you want to intergrate file loader to your system(such as UE or Unity), you should provide buffer loader handle
// excel::config_manager::me()->set_buffer_loader([] (std::string& out, const char* file_path) {
// // 读取文件名为file_path的二进制数据然后写出到out
// // file_path 即是pb插件 option (xrescode.loader) 中的file_path字段
// return true; // 成功返回true,失败返回false
// });
// Set 设置设置保留多少组不同版本的数据
// excel::config_manager::me()->set_group_number(8);
// 使用 set_override_same_version(true) 可以强制触发读取,即便版本号没变.
// excel::config_manager::me()->set_override_same_version(true);
// 设置日志输出回调,默认会输出到标准输出
// excel::config_manager::me()->set_on_log([](const log_caller_info_t& caller, const char* content) {
// // ...
// });
// 还可以设置一些其他的事件回调,详见生成的代码
// 调用 reload 来执行某个版本的数据加载
excel::config_manager::me()->reload();
// 然后就可以用config_easy_api或者config_manager的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;
}
使用示例可参见 xres-code-generator/sample ,使用 sample_gen.sh
可生成协议代码和加载示例代码。
生成Lua加载代码
使用模板
template/DataTableCustomIndex.lua.mako
和template/DataTableCustomIndex53.lua.mako
生成加载代码
REPO_DIR=$PATH_TO_xres_code_generator;
mkdir -p "$REPO_DIR/sample/pblua";
cp -rvf "$REPO_DIR/template/common/lua/"*.lua "$REPO_DIR/sample/pblua";
PROTOC_BIN="$(which protoc)"
PYTHON_BIN="$(which python3 2>/dev/null)"
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
else
$PYTHON_BIN --version
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
fi
fi
if [[ $? -ne 0 ]] && [[ -e "$REPO_DIR/tools/find_protoc.py" ]]; then
PROTOC_BIN="$("$PYTHON_BIN" $REPO_DIR/tools/find_protoc.py)"
fi
"$PROTOC_BIN" -I "$REPO_DIR/sample/proto" -I "$REPO_DIR/pb_extension" "$REPO_DIR/sample/proto/"*.proto -o "$REPO_DIR/sample/sample.pb" ;
"$PYTHON_BIN" "$REPO_DIR/xrescode-gen.py" -i "$REPO_DIR/template" -p "$REPO_DIR/sample/sample.pb" -o "$REPO_DIR/sample/pblua" \
-g "$REPO_DIR/template/DataTableCustomIndex.lua.mako" \
-g "$REPO_DIR/template/DataTableCustomIndex53.lua.mako" \
"$@"
使用
DataTableService53
访问数据
-- 我们使用 require(...) to 来加载 DataTableService53,DataTableCustomIndex53 和生成的数据文件,请确保 require(FILE_PATH) 可以加载它们
-- 假设 xresloader 生成的 lua 数据文件位于 ../../../xresloader/sample/proto_v3
package.path = '../../../xresloader/sample/proto_v3/?.lua;' .. package.path
local excel_config_service = require('DataTableService53')
-- 设置日志输出回调
-- excel_config_service:OnError = function (消息内容, 索引对象, 索引名称, 所有的key字段...) 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("\t%s=%s", k, tostring(v)))
end
-- 也可以通过DataTableService.GetCurrentGroup(self)获取分组和DataTableService.GetByGroup(self, group, loader_name)来实现配置分组和多版本功能
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
使用示例可参见 xres-code-generator/sample ,使用 sample_gen.sh
可生成协议代码和加载示例代码。
生成C#加载代码
使用模板
template/ConfigSet.cs.mako
和template/ConfigSetManager.cs.mako
生成加载代码
REPO_DIR=$PATH_TO_xres_code_generator;
mkdir -p "$REPO_DIR/sample/pbcs";
PROTOC_BIN="$(which protoc)"
PYTHON_BIN="$(which python3 2>/dev/null)"
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
else
$PYTHON_BIN --version
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
fi
fi
if [[ $? -ne 0 ]] && [[ -e "$REPO_DIR/tools/find_protoc.py" ]]; then
PROTOC_BIN="$("$PYTHON_BIN" $REPO_DIR/tools/find_protoc.py)"
fi
"$PROTOC_BIN" -I "$REPO_DIR/sample/proto" -I "$REPO_DIR/pb_extension" "$REPO_DIR/sample/proto/"*.proto -o "$REPO_DIR/sample/sample.pb" ;
"$PYTHON_BIN" "$REPO_DIR/xrescode-gen.py" -i "$REPO_DIR/template" -p "$REPO_DIR/sample/sample.pb" -o "$REPO_DIR/sample/pbcs" \
-g "$REPO_DIR/template/ConfigSet.cs.mako" \
-l "$REPO_DIR/template/ConfigSetManager.cs.mako" \
"$@"
使用
ConfigSetManager
访问数据.
using System;
using excel;
class Program {
static void Main(string[] args) {
ConfigSetManager.Instance.Reload();
// 当前C#数据集全部生成的单例类.
// 如果后续有需要再添加ConfigGroup管理等功能.
var table = config_set_role_upgrade_cfg.Instance.GetByIdLevel(10001, 3);
if (table != null) {
Console.WriteLine(table.ToString());
}
}
}
生成基于 upb 的Lua加载代码
使用 upb 的
protoc-gen-lua
插件生成lua表述信息
PROTOC_BIN="$(which protoc)"
PYTHON_BIN="$(which python3 2>/dev/null)"
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
else
$PYTHON_BIN --version
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
fi
fi
if [[ $? -ne 0 ]] && [[ -e "$REPO_DIR/tools/find_protoc.py" ]]; then
PROTOC_BIN="$("$PYTHON_BIN" $REPO_DIR/tools/find_protoc.py)"
fi
"$PROTOC_BIN" "--lua_out=$REPO_DIR/sample/upblua" --plugin=protoc-gen-lua=<PATH to protoc-gen-lua> "$REPO_DIR/pb_extension/xrescode_extensions_v3.proto" "$REPO_DIR/sample/proto/"*.proto
使用模板
template/DataTableCustomIndexUpb.lua.mako
生成加载代码
REPO_DIR=$PATH_TO_xres_code_generator;
mkdir -p "$REPO_DIR/sample/upblua";
PROTOC_BIN="$(which protoc)"
PYTHON_BIN="$(which python3 2>/dev/null)"
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
else
$PYTHON_BIN --version
if [[ $? -ne 0 ]]; then
PYTHON_BIN="$(which python)"
fi
fi
if [[ $? -ne 0 ]] && [[ -e "$REPO_DIR/tools/find_protoc.py" ]]; then
PROTOC_BIN="$("$PYTHON_BIN" $REPO_DIR/tools/find_protoc.py)"
fi
"$PROTOC_BIN" -I "$REPO_DIR/sample/proto" -I "$REPO_DIR/pb_extension" "$REPO_DIR/sample/proto/"*.proto -o "$REPO_DIR/sample/sample.pb" ;
"$PYTHON_BIN" "$REPO_DIR/xrescode-gen.py" -i "$REPO_DIR/template" -p "$REPO_DIR/sample/sample.pb" -o "$REPO_DIR/sample/upblua" \
-g "$REPO_DIR/template/DataTableCustomIndexUpb.lua.mako" \
"$@"
使用
DataTableServiceUpb
访问数据.
-- We will use require(...) to load
-- - DataTableServiceUpb
-- - DataTableCustomIndexUpb
-- - xrescode_extensions_v3_pb
-- - pb_header_v3_pb
-- - upb
-- - google/protobuf/descriptor_pb
-- - Other custom proto files generated by protoc-gen-lua
-- Please ensure these can be load by require(FILE_PATH)
local excel_config_service = require("DataTableServiceUpb")
local upb = require("upb")
-- Set logger
-- excel_config_service:OnError = function (message, data_set, indexName, keys...) end
excel_config_service:ReloadTables()
local role_upgrade_cfg = excel_config_service:Get("role_upgrade_cfg")
print("======================= Lazy load begin =======================")
local data = role_upgrade_cfg:GetByIndex("id_level", 10001, 3) -- using the Key-Value index: id_level
print("======================= Lazy load end =======================")
print("----------------------- Get by Key-Value index -----------------------")
print(string.format("Data of role_upgrade_cfg: id=10001, level=3 -> json_encode: %s",
upb.json_encode(data, { upb.JSONENC_PROTONAMES })))
print("----------------------- Get by reflection and Key-List index -----------------------")
local current_group = excel_config_service:GetCurrentGroup()
local role_upgrade_cfg2 = excel_config_service:GetByGroup(current_group, "role_upgrade_cfg")
local data2 = role_upgrade_cfg2:GetByIndex("id", 10001) -- using the Key-List index: id
for _, v1 in ipairs(data2) do
print(string.format("\tid: %s, level: %s", tostring(v1.Id), tostring(v1.Level)))
for fds in role_upgrade_cfg2:GetMessageDescriptor():fields() do
print(string.format("\t\t%s=%s", fds:name(), tostring(v1[fds:name()])))
end
end
自定义模板和更多语言
我们实现的所有加载代码模板都位于 xres-code-generator/template ,以后会实现更多语言的加载模板。用户也可以根据自己的需要,参照 xres-code-generator/template 实现自己的代码加载模板。
高级功能
文本替换(别名/宏)
为了便于理解,我们支持配置一组别名的表。在 MacroSource
中,主配置为文件名,次配置为表明,补充配置为Key-Value的开始行号和列号。比如:
字段 |
简介 |
主配置 |
次配置 |
补充配置 |
说明 |
---|---|---|---|---|---|
MacroSource |
文本宏数据源(文件路径,表名) |
资源转换示例.xlsx |
macro |
2,1 |
次配置为表名,补充配置为数据起始位置(行号, 列号) |
这时候我们会认为在文件 资源转换示例.xlsx
, macro
表中。从第 2
行开始,第 1
列为别名的Key,第 2
列为别名的Value。
这样我们在执行转表的读取数据时候,会尝试去这里查找是否又它的别名,如果有,则直接转换成Value。
比如 xresloader sample 的 upgrade_10001
表的 CostType
这一列是整数类型,但是我们可以配置成 游戏币
。就是因为我们在 macro
表中配置了。
键 |
值 |
---|---|
游戏币 |
10001 |
多表数据合并
如果Excel的多个表的结构相同(列对应的字段相同)。则我们可以通过配置多个 DataSource
来让 xresloader 对多个表进行数据合并。这样我们可以把数据按类型分布在几个表中并在转换的时候最后合并。
详见 xresloader sample 中 资源转换示例.xlsx
的 scheme_upgrade
、 upgrade_10001
和 upgrade_10002
表。
数据验证器
xresloader 提供了一个基于协议描述得高级功能- 数据验证器 。用于限制输入数据的范围。
数据验证器 的使用方法是在Excel的字段名后面跟 @
符号,然后输入协议名称或者数字范围 A-B
,多个验证器可以用 |
隔开。
这样在数据转出的时候转表工具会检查数据的合法性。比如:
角色ID |
等级 |
货币类别 |
消耗值 |
---|---|---|---|
Id |
Level |
CostType |
|
10001 |
1 |
||
10001 |
2 |
10001 |
50 |
上面这个表,如果 消耗值
这一列出现了[0, 1000]和[2000-3000]以外的值,转表工具会转表不通过并予以提示。
还有一个特殊的用法是,比如我们有技能要对单位的属性加成。然后我们定义单位属性的proto如下:
message unit_attribute {
int32 hp = 1;
int32 mp = 2;
int32 power = 3;
}
message skill_effect {
int32 id = 1;
int32 level = 2;
int32 func_type = 3;
int32 attr_type = 4;
int32 value = 5;
}
然后我们可以定义技能功能表如下:
技能ID |
等级 |
功能类别 |
属性 |
值 |
---|---|---|---|---|
id |
level |
func_type |
value |
|
20001 |
1 |
hp |
100 |
|
20001 |
2 |
1001 |
hp |
200 |
使用 skill_effect
转出如上的表, 属性
这个字段的验证器设为了 unit_attribute
,attr_type
的类型是int32。
这时在转出数据的时候,转出的数据是 unit_attribute.hp
的字段编号 1
。
Protobuf 插件支持
项目中可以导入 xresloader/header/extensions 目录, 然后通过导入 xresloader/header/extensions/v2 或 xresloader/header/extensions/v3 中的相应proto文件,就可以获得额外的插件扩展支持。
> 注意: 使用插件功能时 生成pb的时候也要导入插件的proto文件和protobuf官方include目录里的 google/protobuf/descriptor.proto 文件。
Protobuf插件 - Message插件
插件名称 |
类型 |
插件功能 |
---|---|---|
org.xresloader.msg_description |
string |
消息体描述信息,会写入输出的header中和代码中 |
org.xresloader.msg_require_mapping_all |
bool |
设置message的所有字段必须被全部映射 |
org.xresloader.msg_separator |
string |
Plain模式字段分隔符,可指定多个,用于在一个单元格内配置复杂格式时的分隔符列表,默认值: |
org.xresloader.ue.helper |
string |
生成UE Utility代码的类名后缀 |
org.xresloader.ue.not_data_table |
bool |
生成UE Utility代码时,不生产加载代码,这用于带name字段的依赖类型 |
比如 xresloader/sample/proto_v3/kind.proto 里, arr_in_arr_cfg
配置了相关字段,会影响到一些输出。
Protobuf插件 - Field插件
插件名称 |
类型 |
插件功能 |
---|---|---|
org.xresloader.field_description |
string |
字段描述信息,会写入输出的header中和代码中 |
org.xresloader.verifier |
string |
字段描述信息,会写入输出的header中和代码中 |
org.xresloader.field_alias |
string |
字段别名,可用于验证器和Excel中直接填别名 |
org.xresloader.field_ratio |
int32 |
数值放大倍数, |
org.xresloader.field_separator |
string |
Plain模式分隔符,可指定多个,用于在一个单元格内配置复杂格式时的分隔符列表,默认值: |
org.xresloader.field_required |
bool |
设置字段为 required ,用于向proto3提供,proto2的 required 约束 |
org.xresloader.ue.key_tag |
int32 |
生成UE代码时,如果需要支持多个Key组合成一个Name,用这个字段指定系数(必须大于0) |
org.xresloader.ue.ue_type_name |
string |
生成UE代码时,如果指定了这个字段,那么生成的字段类型将是 |
org.xresloader.ue.ue_type_is_class |
bool |
生成UE代码时,如果这个字段为true,那么生成的字段类型将是 |
比如我们定义单位属性的proto如下:
import "xresloader.proto";
message unit_attribute {
int32 hp = 1 [(org.xresloader.field_alias) = "生命"];
int32 mp = 2 [(org.xresloader.field_alias) = "魔力"];
int32 power = 3 [(org.xresloader.field_alias) = "力量"];
}
message skill_effect {
int32 id = 1;
int32 level = 2;
int32 func_type = 3;
int32 attr_type = 4;
int32 value = 5;
}
然后我们可以在Excel表中使用别名:
技能ID |
等级 |
功能类别 |
属性 |
值 |
---|---|---|---|---|
id |
level |
func_type |
value |
|
20001 |
1 |
生命 |
100 |
|
20001 |
2 |
1001 |
生命 |
200 |
Protobuf插件 - EnumValue插件
插件名称 |
类型 |
插件功能 |
---|---|---|
org.xresloader.enumv_description |
string |
枚举值描述信息,会写入输出的header中和代码中 |
org.xresloader.enum_alias |
string |
枚举值别名,可用于验证器和Excel中直接填别名 |
比如 xresloader/sample/proto_v3/kind.proto 里, role_upgrade_cfg
内的 CostType
这一列配置验证器引射到协议的 cost_type
和 协议描述字段。
syntax = "proto3";
import "xresloader.proto";
// xresloader的发布页面 https://github.com/xresloader/xresloader/releases 下载 protocols.zip ,即可获取xresloader.proto
enum cost_type {
EN_CT_UNKNOWN = 0;
EN_CT_MONEY = 10001 [(org.xresloader.enum_alias) = "金币"];
EN_CT_DIAMOND = 10101 [(org.xresloader.enum_alias) = "钻石"];
}
message role_upgrade_cfg {
uint32 Id = 1;
uint32 Level = 2;
uint32 CostType = 3 [
(org.xresloader.verifier) = "cost_type", // 这里等同于在Excel中使用 @cost_type 标识
(org.xresloader.field_description) = "Refer to cost_type"
];
int32 CostValue = 4;
int32 ScoreAdd = 5;
}
然后,我们就可以按如下方式配消耗类型:
角色ID |
等级 |
货币类别 |
消耗值 |
---|---|---|---|
Id |
Level |
CostType |
|
10001 |
1 |
EN_CT_MONEY |
10 |
10001 |
2 |
金币 |
50 |
Protobuf插件 - Oneof插件(2.8.0版本及以上)
插件名称 |
类型 |
插件功能 |
---|---|---|
org.xresloader.oneof_description |
string |
oneof描述信息,可能会写入输出的header中和代码中 |
org.xresloader.oneof_separator |
string |
Plain模式类型和值字段的分隔符,可指定多个,用于在一个单元格内配置复杂格式时的分隔符列表,默认值: |
仅导出部分字段
如果我们需要给客户端和服务器读取同一张Excel表里的不同字段的数据,只要proto不一样即可。对于proto中不存在的字段,我们在转换的时候会忽略掉。
即,我们可能会有一个 role_server
和 role_client
。这两个数据结构不一样,但指向同一个数据源。
批量转表的include标签
公式支持
xresloader 支持公式功能,但是不建议使用跨文件公式。是因为有些平台里,文件的引用可能会使用绝对路径,这时候如果改变一个文件中的值会影响另一个文件。 而另一个文件计算公式的时候读取失败,则会用之前的数据缓存(Excel中对所有公式的计算结果有缓存)。这时候数据可能滞后,但是是没有提示的。可能会引起困惑。
定长数组
详见 数据类型说明-定长数组 章节。
Plain模式(需要 xresloader 2.7.0及以上)
为了方便某些特殊场景使用,从 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模式中,message字段解析是严格按照配置的field number的顺序。
更多详情请参考 xresloader sample 的 arr_in_arr
表,对应协议是 xresloader/sample/proto_v3/kind.proto 中的 message arr_in_arr_cfg
。
``UE-Csv`` 和 ``UE-Json`` 输出的Plain模式需要 `xresloader`_ 2.8.0及以上。
Oneof/Union支持(需要 xresloader 2.8.0及以上)
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里后续的字段不需要配置。
更多详情请参考 xresloader sample 的 test_oneof
表,对应协议是 xresloader/sample/proto_v3/kind.proto 中的 message event_cfg
。
Map类型支持(需要 xresloader 2.9.0及以上)
从 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
模式的输出,我们会输入如下的代码:
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
属性指示类型名。
更多详情请参考 xresloader sample 的 arr_in_arr
表,对应协议是 xresloader/sample/proto_v3/kind.proto 中的 message arr_in_arr_cfg
。
生态和周边工具
二进制转可读文本工具: xresloader-dump-bin
使用 xresloader_ 时,如果输出的数据时协议二进制,那么在需要调试和对比不同版本的内容的时候时非常不方便的。为了解决这一问题,我们提供了一个命令行工具 xresloader-dump-bin 来把导出的二进制文件转换成文本内容展示出来。
xresloader_ 使用 Rust语言 开发,按照 Rust语言 标准的包管理模式。
使用示例: ./xresloader-dump-bin --pretty -p kind.pb -b arr_in_arr_cfg.bin
FAQ
哪里有完整的示例?
转表功能和二进制数据读取的示例见: https://github.com/xresloader/xresloader/tree/master/sample
文本和Msgpack数据读取示例见: https://github.com/xresloader/xresloader/tree/master/loader-binding
批量转表配置的示例见: https://github.com/xresloader/xresconv-conf
为什么会读到很多空数据?
Excel里编辑过的单元格即便删除了也会留下不可见的样式配置,这时候会导致转出的数据有空行。可以通过在Excel里删除行解决
为什么Excel里填的时间,但是转出来是一个负数?
Excel里的日期时间类型转成协议里整数时会转为Unix时间戳,但是Excel的时间是以1900年1月0号为基准的,这意味着如果时间格式是 hh:mm:ss
的话,49:30:01
会被转为 1900-1-2 1:31:01
。
时间戳因为是相对于 1970-01-01 00:00:00
的秒数,所以会是一个绝对值很大的负数。
Windows下控制台里执行执行会报文件编码错误?(java.nio.charset.UnsupportedCharsetException: cp65001)
这个问题涉及的几个Exception是:
ERROR StatusLogger Unable to inject fields into builder class for plugin type class org.apache.logging.log4j.core.appender.ConsoleAppender, element Console.
java.nio.charset.UnsupportedCharsetException: cp65001
at java.nio.charset.Charset.forName(Unknown Source)
at org.apache.logging.log4j.util.PropertiesUtil.getCharsetProperty(PropertiesUtil.java:146)
at org.apache.logging.log4j.util.PropertiesUtil.getCharsetProperty(PropertiesUtil.java:134)
...
...
和
ERROR StatusLogger Unable to invoke factory method in class class org.apache.logging.log4j.core.appender.ConsoleAppender for element Console.
java.lang.IllegalStateException: No factory method found for class org.apache.logging.log4j.core.appender.ConsoleAppender
at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.findFactoryMethod(PluginBuilder.java:224)
at org.apache.logging.log4j.core.config.plugins.util.PluginBuilder.build(PluginBuilder.java:130)
at org.apache.logging.log4j.core.config.AbstractConfiguration.createPluginObject(AbstractConfiguration.java:952)
...
...
这是因为在Windows控制台中,如果编码是UTF-8,java获取编码时会获取到cp65001,而这个编码java本身是不识别的。这种情况可以按下面的方法解决:
第一种: 执行xresloader之前先执行 chcp 936,切换到GBK编码
第二种: 在powershell里执行
C++加载代码编译时出现xresloader符号重定义(multiple definition of org::xresloader::pb::xresloader_XXX)
pb_header.pb.cc 和 pb_header_v3.pb.cc 只能保留一个
如果系统采用的是proto v3则保留pb_header_v3.pb.cc
如果系统采用的是老版本的proto v2则保留pb_header.pb.cc
C++加载代码编译时出现xresloader版本检查错误
具体表现为编译时输出 This file was generated by an older version of protoc ...
或 This file was generated by a newer version of protoc ...
。
这是因为protoc版本和目前所用的protobuf版本不一致,请尝试重新用目前所用的protoc根据配置的proto文件和header目录中的 pb_header_v3.proto
或 pb_header.proto
重新生成c++代码文件。
proto v2版本API解析repeated的整数或浮点数类型字段失败(Wire Type)
我们转表默认使用的是proto v3模式,几乎所有编码规则都是向前兼容到proto v2的,但是也有一个例外,就是repeated的数值类型。
repeated的数值类型在proto v2里默认是 [ packed = false ]
而在proto v3里是 [ packed = true ]
。解决方法是显式指定打包方式。
详见 Proto v2和Proto v3 。
为什么在proto里定义的是一个无符号(unsigned)类型(uint32、uint64等),实际输出的UE代码是有符号(signed)的(int32/int64)?
因为有一些语言是没有无符号(unsigned)类型的,为了统一数据类型,我们统一转换为有符号类型,转换方式和protobuf的java版SDK保持一致。如果需要使用大于int32最大值的uint32类型,请用int64代替。
为什么 UE-Csv
和 UE-Json
输出的代码会多一个 Name
字段?
因为对 UE-Json
输出中, Name
是一个特殊字段,也用于UE中内置的接口的查找索引。所以为了统一输出的数据结构( 这样无论是 UE-Csv
还是 UE-Json
都可以用相同的代码结构来导入 ),我们对 UE-Csv
和 UE-Json
统一自动生成 Name
字段。但是如果用户自定义了 Name
字段, 我们会使用用户自定义的 Name
字段。
要如何配置可以让Excel里的数据指向UE的类型或资源
可以使用 org.xresloader.ue.ue_type_name
插件和 org.xresloader.ue.ue_type_is_class
插件。详见: 导出为UE支持的CSV或JSON数据和代码 (可选)
为什么UE的代码输出里对 oneof
的case输出使用 FString
的字段名而不使用 UEnum()
主要是因为(当前版本4.X)UE的 UEnum()
的支持仅支持基于 uint8
的,但是protobuf的field number是 int32
。为了兼容性所以没有使用 UEnum()
。
如果输出int32的话在UE里不太好操作,所以输出了字符串类型,方便蓝图里或UE代码里通过UE内置的反射机制访问。
提示 Can not reserve enough space for XXX objecct heap
在转换很大的Excel文件时(上万行数据),会需要很高的内存(>=1GB)。所以为了方便我们在批量转表sample的xml中配置了 <java_option desc="java选项-最大内存限制2GB">-Xmx2048m</java_option>
。
如果出现这个提示可能是32位jre无法分配这么多地址空间导致的,可以在xml里删除这个配置。但是还是建议使用64位jre。
在 v2.10.0 版本以后,可以通过使用 --disable-excel-formular
关闭公式实时计算,这时候会使用内部的索引器,能够大幅降低内存和CPU开销。
> 关闭公式实时计算并不是指不支持公式。Excel在保存时会保存一份公式计算结果的缓存,关闭公式实时计算后会使用这个缓存。
环境和依赖项
转表工具- xresloader
xresloader 项目使用[apache maven](https://maven.apache.org/)管理包依赖和打包构建流程。
JDK 需要11或以上版本
命令行批量转表工具- xresconv-cli
xresconv-cli 项目使用 python 开发。
支持python 2.7和python 3
GUI批量转表工具- xresconv-gui
xresconv-gui 项目使用 nodejs 和 npm 做包管理。
使用 electronjs 实现用户界面
代码生成工具- xres-code-generator
xres-code-generator 项目使用 python 开发。
支持python 2.7和python 3
使用 mako 模板引擎
C#的动态Message支持- DynamicMessage-net
DynamicMessage-net 项目使用 .net core 或 .net framework 开发。
依赖 protobuf-net 的解码层
编译和打包
# 编译
mvn compile
# 打包
mvn package
以上命令会自动下载依赖文件、包和插件。
编译完成后,输出的结果默认会放在 target 目录下。
更新依赖包
需要更新依赖包版本只要修改[pom.xml](pom.xml)并修改版本号即可。
依赖包和插件的组名、包名和版本可以在以下仓库内找到:
中心maven仓库: http://search.maven.org/
或到下面列举的仓库列表中查找
其他仓库地址
公有仓库地址
`http://search.maven.org/ <http://search.maven.org/#browse>`_
http://mirrors.ibiblio.org/pub/mirrors/maven2/org/acegisecurity/
私有仓库地址
其他maven功能
批量转表工具和其他工具的开发文档请参见该项目的主页。
使用国内的源
国内的Maven源
由于国内访问官方maven仓库的速度比较慢,所以可以尝试使用oschina提供的maven仓库镜像
添加mirror节点到settings.xml里的mirrors即可。比如
<mirror>
<id>tencent-cloud</id>
<mirrorOf>central</mirrorOf>
<name>Tencent Cloud Mirror.</name>
<url>http://mirrors.cloud.tencent.com/nexus/repository/maven-public/</url>
</mirror>
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Mirror.</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
<mirror>
<id>repo2</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo2.maven.org/maven2/</url>
</mirror>
<mirror>
<id>ui</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://uk.maven.org/maven2/</url>
</mirror>
<mirror>
<id>jboss-public-repository-group</id>
<mirrorOf>central</mirrorOf>
<name>JBoss Public Repository Group</name>
<url>http://repository.jboss.org/nexus/content/groups/public</url>
</mirror>
<mirror>
<id>repo1</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo1.maven.org/maven2/</url>
</mirror>
如果$HOME/.m2下没有settings.xml文件,可以去 http://maven.apache.org/download.cgi 下载个发布包,然后复制一个出来
设置完maven配置之后,可以用如下命令编译打包
# 编译
mvn -s [settings.xml路径] compile
# 打包
mvn -s [settings.xml路径] package
加速NPM包下载
关闭npm的https
npm config set strict-ssl false
设置npm的软件源
npm config set registry "http://registry.npmjs.org/"
npm config set registry https://mirrors.tencent.com/npm/
npm config set registry https://registry.npm.taobao.org/
npm install -g cnpm --registry=https://registry.npm.taobao.org
代理
设置代理:
npm config set proxy=http://代理服务器ip:代理服务器端口
取消代理:
npm config delete http-proxy
取消代理:
npm config delete https-proxy
单独设置代理:
npm install --save-dev electron-prebuilt --proxy http://代理服务器ip:代理服务器端口
转表引擎设计模型
转表引擎是用于给各类其他工具集成的底层转表系统。所以它是一个没有用户界面的命令行工具。为了适应多种输入来源和多种输出目标,同时兼顾系统内缓存命中率,转表引擎工作的第一步是会先从输入的命令行参数或标准输入的参数中读取转表规则的来源和转换的目标类型;然后第二步是根据输入的规则描述文件选择相对应的解析模块,构建出完整的输入和输出的规则配置。接下来第三步转表引擎会根据这个配置选择结构化数据的描述解析模块(即:“输入协议模块”)读取整个数据结构的描述信息,并和读入Excel的列名相结合,构建出建立内部的抽象语法树(AST)。这一步的目的是实现把Excel中扁平化的数据描述翻译成结构化的数据描述;再进行第四步,把数据源中的每一行数据读取出来并构造出结构化的数据,并用数据验证器验证数据有效性。这一步的同时也可能会对数据做一些适配性质的处理。最后一步就是根据输出的类型,选取不同的输出模块写出到文件中。至此,转表的核心流程就完成了。
转表引擎架构和流程大致如下:

批量转表工具设计模型
批量转表工具都以 xresconv-conf 作为配置规范。具体的配置和功能点如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- <?xml-stylesheet type="text/xsl" href="helper/view.xsl"?> -->
<root>
<include desc="可以包含其他文件配置,然后本文件里的配置将会覆盖或合并配置,相对于当前xml的目录">sample.xml</include>
<global>
<work_dir desc="工作目录,相对于当前xml的目录">../xresloader/sample</work_dir>
<xresloader_path desc="xresloader地址,相对于当前xml的目录">../target/xresloader-2.9.0.jar</xresloader_path>
<proto desc="协议类型,-p选项">protobuf</proto>
<output_type desc="输出类型,-t选项,支持多个同时配置多种输出">bin</output_type>
<output_type desc="多种输出时可以额外定义某个节点的重命名规则" rename="/(?i)\.bin$/\.json/">json</output_type>
<output_type desc="可以通过指定class来限制输出的规则" rename="/(?i)\.bin$/\.csv/" class="client" >ue-csv</output_type>
<!-- output_type 里的class标签对应下面item里的class标签,均可配置多个,多个用空格隔开,任意一个class匹配都会启用这个输出 -->
<proto_file desc="协议描述文件,-f选项">proto_v3/kind.pb</proto_file>
<output_dir desc="输出目录,-o选项"></output_dir>
<data_src_dir desc="数据源目录,-d选项"></data_src_dir>
<data_version desc="数据版本号,留空则自动生成">1.0.0.0</data_version>
<rename desc="重命名规则,正则表达式:/搜索模式/替换内容/,对应xresloader的-n选项, 如果在output_type里设置了rename,以output_type里的rename为准,否则使用这里的全局配置" placeholder="/(?i)\.bin$/\.json/"></rename>
<java_option desc="java选项-最大内存限制2GB">-Xmx2048m</java_option>
<java_option desc="java选项-客户端模式">-client</java_option>
<default_scheme name="KeyRow" desc="默认scheme模式参数-Key行号">2</default_scheme>
<default_scheme name="MacroSource" desc="默认scheme模式参数-Key行号">资源转换示例.xlsx|macro|2,1</default_scheme>
</global>
<groups desc="分组信息">
<group id="client" name="客户端"></group>
<group id="server" name="服务器"></group>
</groups>
<category desc="类信息">
<tree id="all_cats" name="大分类">
<tree id="kind" name="角色配置"></tree>
</tree>
<tree id="test" name="测试"></tree>
</category>
<list>
<item file="资源转换示例.xlsx" scheme="scheme_kind" name="人物表" cat="kind" class="server"></item>
<item file="资源转换示例.xlsx" scheme="scheme_upgrade" name="升级表" cat="kind" class="server">
<option desc="自定义选项" name="移除空列表项">--disable-empty-list</option>
</item>
<item name="嵌套数组测试" cat="test" class="client server">
<scheme name="DataSource" desc="数据源(文件名|表名|数据起始行号,数据起始列号)">资源转换示例.xlsx|arr_in_arr|3,1</scheme>
<scheme name="ProtoName" desc="协议名">arr_in_arr_cfg</scheme>
<scheme name="OutputFile" desc="输出文件名">arr_in_arr_cfg.bin</scheme>
</item>
</list>
<gui>
<set_name desc="这个脚本用于修改树形节点的显示数据,便于策划核对具体的表名">
if (item_data.file) {
item_data.name += " (" + item_data.file.match(/([^.]+)\.\w+$/)[1] + ")"
}
</set_name>
<on_before_convert type="text/javascript" timeout="15000" description="事件执行结束必须调用resolve(value)或reject(reason)函数,以触发进行下一步">
// 这里可以执行nodejs代码,比如下面是Windows平台执行 echo work_dir
var os = require("os");
var spawn = require("child_process").spawn;
if (os.type().substr(0, 7).toLowerCase() == "windows") {
var exec = spawn("cmd", ["/c", "echo " + work_dir], {
cwd: work_dir,
encoding: 'utf-8'
});
exec.stdout.on("data", function(data) {
log_info(data);
});
exec.stderr.on("data", function(data) {
log_error(data);
});
exec.on("error", function(data) {
log_error(data.toString());
resolve();
// reject("执行失败" + data.toString());
});
exec.on("exit", function(code) {
if (code === 0) {
resolve();
} else {
resolve();
// reject("执行失败");
}
});
} else {
resolve();
}
</on_before_convert>
<on_after_convert type="text/javascript" timeout="60000" description="事件执行结束必须调用resolve(value)或reject(reason)函数,以触发进行下一步">
// 同上
alert_warning("自定义转表完成后事件,可以执行任意nodejs脚本");
resolve();
</on_after_convert>
<script name="自定义脚本" desc="用于自定义按钮" type="text/javascript">
// 同上
resolve();
</script>
</gui>
</root>
流程如下:

LICENSE
LICENSE - 文档

本文档采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
本许可协议授权之外的使用权限可以从 https://www.owent.net/about 处获得。
LICENSE - xresloader
xresloader 采用 The MIT License (MIT)
The MIT License (MIT)
Copyright (c) 2022 xresloader
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
关于 xresloader
工具集Github仓库地址: https://github.com/xresloader
作者: owent
联系作者请发邮件到 admin@owent.net 或 owt5008137@live.com 。