NodeJS 转表工具实现
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
dist/
|
dist/
|
||||||
node_modules/
|
node_modules/
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
*.xlsl
|
||||||
5
.vscode/launch.json
vendored
5
.vscode/launch.json
vendored
@@ -8,7 +8,10 @@
|
|||||||
"skipFiles": [
|
"skipFiles": [
|
||||||
"<node_internals>/**"
|
"<node_internals>/**"
|
||||||
],
|
],
|
||||||
"program": "${workspaceFolder}/dist/binary.js"
|
"program": "${workspaceFolder}/dist/binary.js",
|
||||||
|
"args": [
|
||||||
|
"./excel-exporter.json"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
63
README.md
63
README.md
@@ -1,3 +1,62 @@
|
|||||||
# NodeJS 起始项目
|
# Excel 配置表数据导出工具
|
||||||
|
|
||||||
搭建好 TypeScript NodeJS 的空项目,提供编译、调试流程,内置 tiny 使用代码库。
|
将 Excel 配置表中的数据导出为方便程序读取和使用的数据。
|
||||||
|
|
||||||
|
## 目前支持的导出格式有:
|
||||||
|
* JSON 文件
|
||||||
|
* C# 类型声明
|
||||||
|
~~* TypeScript 声明文件(需要配合 JSON 使用)~~
|
||||||
|
~~* Godot 引擎的 GDScript 脚本文件~~
|
||||||
|
|
||||||
|
## 表格格式说明
|
||||||
|
|
||||||
|
* 每个 xlsl 文件中可以有多张表(Sheet),每张表会都导出一份数据文件,表名必须符合标识符规范
|
||||||
|
* 表名为 `@skip` 或以 `@skip` 开头的表会被忽略,不会导出数据文件
|
||||||
|
* 第一列值为 `@skip` 的行会被忽略,视为无效数据行
|
||||||
|
* 整行所有列为空的行会被忽略,视为无效数据行
|
||||||
|
* 每张表的**第一个有效数据行**用作字段名,决定了导出数据所拥有的属性,**字段名必须符合标识符命名规范**
|
||||||
|
* 字段名所在的行中不填名称的列视为空字段,该列的数据在导出时会被忽略
|
||||||
|
* 相同名称的字段导出时会被合并为数组
|
||||||
|
* 导出属性的数据类型由**整列所填写的数据类型**决定,支持以下数据类型
|
||||||
|
* 字符串
|
||||||
|
* 数值(优先使用整形)
|
||||||
|
* 布尔值
|
||||||
|
* 空(`null`)
|
||||||
|
* 该工具设计原则是简单易用,表格字段可由策划自由调整, 不支持数据引用,暂不支持结构体
|
||||||
|
|
||||||
|
## Windows 安装
|
||||||
|
安装 NodeJS, 注意勾选将 Node 添加到环境变量 `PATH` 中
|
||||||
|
|
||||||
|
## 使用
|
||||||
|
修改配置表
|
||||||
|
修改 excel-exporter.json 修改工具配置
|
||||||
|
双击 转表.bat 执行转换工作
|
||||||
|
|
||||||
|
### 配置示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"input": [
|
||||||
|
{ "file": "装备表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "关卡表.xlsx", "encode": "GBK"},
|
||||||
|
],
|
||||||
|
"parser": {
|
||||||
|
"first_row_as_field_comment": true
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"json": {
|
||||||
|
"enabled": true,
|
||||||
|
"directory": "../../client/Assets/Resources/data/json",
|
||||||
|
"indent": "\t"
|
||||||
|
},
|
||||||
|
"csharp": {
|
||||||
|
"enabled": true,
|
||||||
|
"directory": "../../client/Assets/Resources/data/csharp",
|
||||||
|
"namespace": "game.data",
|
||||||
|
"base_type": "tiny.data.UniqueIDObject",
|
||||||
|
"file_name": "data",
|
||||||
|
"ignore_id": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
32
excel-exporter.json
Normal file
32
excel-exporter.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"input": [
|
||||||
|
{ "file": "士兵表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "统帅表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "武器表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "装备表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "关卡表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "箱子奖励招募表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "僵尸表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "任务表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "伤害动作表.xlsx", "encode": "GBK"},
|
||||||
|
{ "file": "签到表.xlsx", "encode": "GBK"}
|
||||||
|
],
|
||||||
|
"parser": {
|
||||||
|
"first_row_as_field_comment": true
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"json": {
|
||||||
|
"enabled": true,
|
||||||
|
"directory": "../../client/Assets/Resources/data/json",
|
||||||
|
"indent": "\t"
|
||||||
|
},
|
||||||
|
"csharp": {
|
||||||
|
"enabled": true,
|
||||||
|
"directory": "../../client/Assets/Resources/data/csharp",
|
||||||
|
"namespace": "game.data",
|
||||||
|
"base_type": "tiny.data.UniqueIDObject",
|
||||||
|
"file_name": "data",
|
||||||
|
"ignore_id": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,5 +14,9 @@
|
|||||||
"typescript": "^3.9.2",
|
"typescript": "^3.9.2",
|
||||||
"webpack": "^4.43.0",
|
"webpack": "^4.43.0",
|
||||||
"webpack-cli": "^3.3.11"
|
"webpack-cli": "^3.3.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"colors": "^1.4.0",
|
||||||
|
"xlsx": "^0.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
src/excel-exporter/ExcelExporterApplication.ts
Normal file
71
src/excel-exporter/ExcelExporterApplication.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { FileAccess, ModeFlags } from "tiny/io";
|
||||||
|
import { ParserConfigs, TableParser, TableData } from "./TableParser";
|
||||||
|
import { ExporterConfigs, TableExporter } from "./TableExporter";
|
||||||
|
import { JSONExporter } from "./exporters/JSONExporter";
|
||||||
|
import { CSharpExporter } from "./exporters/CSharpExporter";
|
||||||
|
import * as colors from "colors";
|
||||||
|
|
||||||
|
export interface Configurations {
|
||||||
|
/** 解析配置 */
|
||||||
|
parser?: ParserConfigs,
|
||||||
|
/** 要读取的 XLSL 文档 */
|
||||||
|
input: {"file": string, encode: string}[],
|
||||||
|
/** 导出配置 */
|
||||||
|
output: { [key: string]: ExporterConfigs }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const exporters: {[key:string]: new(config: ExporterConfigs) => TableExporter } = {
|
||||||
|
json: JSONExporter,
|
||||||
|
csharp: CSharpExporter,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ExcelExporterApplication {
|
||||||
|
|
||||||
|
configs: Configurations = null;
|
||||||
|
parser: TableParser = null;
|
||||||
|
tables: { [key: string]: TableData } = {};
|
||||||
|
exporters: TableExporter[] = [];
|
||||||
|
|
||||||
|
constructor(config_file: string) {
|
||||||
|
let file = FileAccess.open(config_file, ModeFlags.READ);
|
||||||
|
this.configs = JSON.parse(file.get_as_utf8_string()) as Configurations;
|
||||||
|
file.close();
|
||||||
|
this.parser = new TableParser(this.configs.parser);
|
||||||
|
|
||||||
|
for (const key in this.configs.output) {
|
||||||
|
let cls = exporters[key];
|
||||||
|
if (cls) {
|
||||||
|
const exporter = new cls(this.configs.output[key]);
|
||||||
|
exporter.name = key;
|
||||||
|
this.exporters.push(exporter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse() {
|
||||||
|
for (const item of this.configs.input) {
|
||||||
|
console.log(colors.grey(`解析配表文件: ${item.file}`));
|
||||||
|
let sheets = this.parser.parse_xlsl(item.file);
|
||||||
|
for (const name in sheets) {
|
||||||
|
this.tables[name] = sheets[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(colors.green(`解析所有配表文件完成`));
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
export() {
|
||||||
|
for (const exporter of this.exporters) {
|
||||||
|
if (exporter.configs.enabled) {
|
||||||
|
console.log(colors.white(`执行 ${exporter.name} 导出:`));
|
||||||
|
for (const name in this.tables) {
|
||||||
|
exporter.export(name, this.tables[name]);
|
||||||
|
}
|
||||||
|
exporter.finalize();
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/excel-exporter/TableExporter.ts
Normal file
46
src/excel-exporter/TableExporter.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { TableData } from "./TableParser";
|
||||||
|
import { FileAccess, ModeFlags, DirAccess } from "tiny/io";
|
||||||
|
import { path } from "tiny/path";
|
||||||
|
|
||||||
|
export interface ExporterConfigs {
|
||||||
|
enabled: boolean,
|
||||||
|
directory: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TableExporter {
|
||||||
|
configs: ExporterConfigs = null;
|
||||||
|
name: string = "";
|
||||||
|
|
||||||
|
constructor(configs: ExporterConfigs) {
|
||||||
|
this.configs = configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected line(text = "", indent = 0) {
|
||||||
|
let line = "";
|
||||||
|
for (let i = 0; i < indent; i++) {
|
||||||
|
line += "\t";
|
||||||
|
}
|
||||||
|
line += text;
|
||||||
|
line += "\n";
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected save_text(file_path: string, text: string) {
|
||||||
|
let dir = path.dirname(file_path);
|
||||||
|
if (!DirAccess.exists(dir)) {
|
||||||
|
DirAccess.make_dir(dir, true);
|
||||||
|
}
|
||||||
|
let file = FileAccess.open(file_path, ModeFlags.WRITE);
|
||||||
|
file.save_as_utf8_string(text);
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 导出配置表数据
|
||||||
|
* @param name 表名称
|
||||||
|
* @param table 表数据
|
||||||
|
*/
|
||||||
|
export(name: string, table: TableData) { }
|
||||||
|
|
||||||
|
/** 全部配置表导出完毕后保存文件 */
|
||||||
|
finalize() {}
|
||||||
|
}
|
||||||
255
src/excel-exporter/TableParser.ts
Normal file
255
src/excel-exporter/TableParser.ts
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import * as xlsl from "xlsx";
|
||||||
|
import { FileAccess, ModeFlags } from "tiny/io";
|
||||||
|
import * as colors from "colors";
|
||||||
|
|
||||||
|
type RawTableData = xlsl.CellObject[][];
|
||||||
|
|
||||||
|
export interface ParserConfigs {
|
||||||
|
/** 第一行作为注释 */
|
||||||
|
first_row_as_field_comment: boolean;
|
||||||
|
/** 固定数组长度 */
|
||||||
|
constant_array_length: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DataType {
|
||||||
|
null = 'null',
|
||||||
|
int = 'int',
|
||||||
|
bool = 'bool',
|
||||||
|
float = 'float',
|
||||||
|
string = 'string',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColumnDescription {
|
||||||
|
type: DataType;
|
||||||
|
name: string;
|
||||||
|
is_array?: boolean;
|
||||||
|
comment?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableData {
|
||||||
|
headers: ColumnDescription[],
|
||||||
|
values: any[][]
|
||||||
|
}
|
||||||
|
|
||||||
|
const SKIP_PREFIX = "@skip";
|
||||||
|
|
||||||
|
export class TableParser {
|
||||||
|
|
||||||
|
configs: ParserConfigs = null;
|
||||||
|
|
||||||
|
constructor(configs: ParserConfigs) {
|
||||||
|
this.configs = configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public parse_xlsl(path) {
|
||||||
|
return this.load_raw_xlsl_data(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected load_raw_xlsl_data(path: string): { [key: string]: TableData } {
|
||||||
|
var file = FileAccess.open(path, ModeFlags.READ);
|
||||||
|
let wb = xlsl.read(file.get_as_array());
|
||||||
|
file.close();
|
||||||
|
let raw_tables: { [key: string]: RawTableData } = {};
|
||||||
|
for (const name of wb.SheetNames) {
|
||||||
|
let sheet_name = name.trim();
|
||||||
|
if (sheet_name.startsWith(SKIP_PREFIX)) continue;
|
||||||
|
raw_tables[sheet_name] = this.parse_sheet(wb.Sheets[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tables: { [key: string]: TableData } = {};
|
||||||
|
for (const name in raw_tables) {
|
||||||
|
console.log(colors.grey(`\t解析配置表 ${name}`));
|
||||||
|
tables[name] = this.process_table(raw_tables[name]);
|
||||||
|
}
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected parse_sheet(sheet: xlsl.WorkSheet): RawTableData {
|
||||||
|
let range = xlsl.utils.decode_range(sheet['!ref']);
|
||||||
|
var rows: RawTableData = [];
|
||||||
|
for (let r = range.s.r; r <= range.e.r; r++) {
|
||||||
|
let R = xlsl.utils.encode_row(r);
|
||||||
|
let row: xlsl.CellObject[] = [];
|
||||||
|
for (let c = range.s.c; c <= range.e.c; c++) {
|
||||||
|
let C = xlsl.utils.encode_col(c);
|
||||||
|
let cell = sheet[`${C}${R}`] as xlsl.CellObject;
|
||||||
|
row.push(cell);
|
||||||
|
}
|
||||||
|
rows.push(row);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected process_table(raw: RawTableData): TableData {
|
||||||
|
|
||||||
|
let headers: ColumnDescription[] = [];
|
||||||
|
|
||||||
|
let column_values: xlsl.CellObject[][] = [];
|
||||||
|
let ignored_columns = new Set<number>();
|
||||||
|
// 去除无用的列
|
||||||
|
let rows: RawTableData = [];
|
||||||
|
for (const row of raw) {
|
||||||
|
if (this.is_valid_row(row)) {
|
||||||
|
rows.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let column = 0;
|
||||||
|
for (let c = 0; c < rows[0].length; c++) {
|
||||||
|
let first = rows[0][c];
|
||||||
|
if (this.get_data_type(first) != DataType.string) {
|
||||||
|
ignored_columns.add(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let column_cells = this.get_column(rows, c, 1);
|
||||||
|
let type = DataType.null;
|
||||||
|
let types = new Set<DataType>();
|
||||||
|
for (const cell of column_cells) {
|
||||||
|
types.add(this.get_data_type(cell));
|
||||||
|
}
|
||||||
|
let type_order = [ DataType.string, DataType.float, DataType.int, DataType.bool ];
|
||||||
|
for (const t of type_order) {
|
||||||
|
if (types.has(t)) {
|
||||||
|
type = t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let comment: string = undefined;
|
||||||
|
if (this.configs.first_row_as_field_comment) {
|
||||||
|
comment = this.get_cell_value(raw[0][c], DataType.string) as string;
|
||||||
|
}
|
||||||
|
headers.push({
|
||||||
|
type,
|
||||||
|
comment,
|
||||||
|
name: first.v as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
column_values.push([]);
|
||||||
|
for (const cell of column_cells) {
|
||||||
|
column_values[column].push(cell);
|
||||||
|
}
|
||||||
|
column += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let values: RawTableData = [];
|
||||||
|
for (let r = 0; r < rows.length - 1; r++) {
|
||||||
|
let row: any = [];
|
||||||
|
for (let c = 0; c < column_values.length; c++) {
|
||||||
|
row.push(column_values[c][r])
|
||||||
|
}
|
||||||
|
values.push(row);
|
||||||
|
}
|
||||||
|
return this.parse_values(headers, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected parse_values(raw_headers : ColumnDescription[], raw_values: RawTableData) {
|
||||||
|
type FiledInfo = {
|
||||||
|
column: ColumnDescription,
|
||||||
|
start: number,
|
||||||
|
indexes: number[]
|
||||||
|
};
|
||||||
|
let field_maps = new Map<string, FiledInfo>();
|
||||||
|
let field_list: FiledInfo[] = [];
|
||||||
|
let c_idx = 0;
|
||||||
|
for (const column of raw_headers) {
|
||||||
|
if (!field_maps.has(column.name)) {
|
||||||
|
const field = {
|
||||||
|
column,
|
||||||
|
start: c_idx,
|
||||||
|
indexes: [ c_idx ]
|
||||||
|
};
|
||||||
|
field_list.push(field);
|
||||||
|
field_maps.set(column.name, field);
|
||||||
|
} else {
|
||||||
|
let field = field_maps.get(column.name);
|
||||||
|
field.column.is_array = true;
|
||||||
|
field.indexes.push(c_idx);
|
||||||
|
}
|
||||||
|
c_idx += 1;
|
||||||
|
}
|
||||||
|
let headers: ColumnDescription[] = [];
|
||||||
|
for (const filed of field_list) {
|
||||||
|
headers.push(filed.column);
|
||||||
|
}
|
||||||
|
let values: any[][] = [];
|
||||||
|
for (const raw_row of raw_values) {
|
||||||
|
let row: any[] = [];
|
||||||
|
for (const filed of field_list) {
|
||||||
|
if (filed.column.is_array) {
|
||||||
|
let arr = [];
|
||||||
|
for (const idx of filed.indexes) {
|
||||||
|
const cell = raw_row[idx];
|
||||||
|
if (cell || this.configs.constant_array_length) {
|
||||||
|
arr.push(this.get_cell_value(cell, filed.column.type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row.push(arr);
|
||||||
|
} else {
|
||||||
|
const cell = raw_row[filed.start];
|
||||||
|
row.push(this.get_cell_value(cell, filed.column.type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
headers,
|
||||||
|
values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected is_valid_row(row: xlsl.CellObject[]) {
|
||||||
|
let first = row[0];
|
||||||
|
if (this.get_data_type(first) == DataType.string && (first.v as string).trim().startsWith(SKIP_PREFIX)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let all_empty = true;
|
||||||
|
for (const cell of row) {
|
||||||
|
all_empty = all_empty && this.get_data_type(cell) == DataType.null;
|
||||||
|
}
|
||||||
|
if (all_empty) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get_column(table: RawTableData, column: number, start_row: number = 0): xlsl.CellObject[] {
|
||||||
|
let cells: xlsl.CellObject[] = [];
|
||||||
|
for (let r = start_row; r < table.length; r++) {
|
||||||
|
const row = table[r];
|
||||||
|
cells.push(row[column]);
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get_data_type(cell: xlsl.CellObject): DataType {
|
||||||
|
if (!cell) return DataType.null;
|
||||||
|
switch (cell.t) {
|
||||||
|
case 'b':
|
||||||
|
return DataType.bool;
|
||||||
|
case 'n':
|
||||||
|
return Number.isInteger(cell.v as number) ? DataType.int : DataType.float;
|
||||||
|
case 's':
|
||||||
|
case 'd':
|
||||||
|
return DataType.string;
|
||||||
|
case 'e':
|
||||||
|
case 'z':
|
||||||
|
default:
|
||||||
|
return DataType.null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get_cell_value(cell: xlsl.CellObject, type: DataType) {
|
||||||
|
switch (type) {
|
||||||
|
case DataType.bool:
|
||||||
|
return cell.v as boolean == true;
|
||||||
|
case DataType.int:
|
||||||
|
return cell ? cell.v as number : 0;
|
||||||
|
case DataType.float:
|
||||||
|
return cell ? cell.v as number : 0;
|
||||||
|
case DataType.string:
|
||||||
|
return cell ? cell.v + '' : '';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/excel-exporter/exporters/CSharpExporter.ts
Normal file
85
src/excel-exporter/exporters/CSharpExporter.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { TableExporter, ExporterConfigs } from "excel-exporter/TableExporter";
|
||||||
|
import { TableData, DataType } from "excel-exporter/TableParser";
|
||||||
|
import { path } from "tiny/path";
|
||||||
|
import * as colors from "colors";
|
||||||
|
|
||||||
|
interface CSharpExporterConfigs extends ExporterConfigs {
|
||||||
|
namespace: string,
|
||||||
|
base_type: string,
|
||||||
|
file_name: string,
|
||||||
|
ignore_id: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CSharpExporter extends TableExporter {
|
||||||
|
protected declear_content = "";
|
||||||
|
protected classes: string[] = [];
|
||||||
|
|
||||||
|
constructor(configs: ExporterConfigs) {
|
||||||
|
super(configs);
|
||||||
|
if ( typeof ((this.configs as CSharpExporterConfigs).namespace) != 'string') {
|
||||||
|
(this.configs as CSharpExporterConfigs).namespace = "game.data";
|
||||||
|
}
|
||||||
|
if ( typeof ((this.configs as CSharpExporterConfigs).base_type) != 'string') {
|
||||||
|
(this.configs as CSharpExporterConfigs).namespace = "object";
|
||||||
|
}
|
||||||
|
if ( typeof ((this.configs as CSharpExporterConfigs).file_name) != 'string') {
|
||||||
|
(this.configs as CSharpExporterConfigs).file_name = "data";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.declear_content += this.line("// Tool generated file DO NOT MODIFY");
|
||||||
|
this.declear_content += this.line("using System;");
|
||||||
|
this.declear_content += this.line();
|
||||||
|
this.declear_content += this.line("namespace " + (this.configs as CSharpExporterConfigs).namespace + " {")
|
||||||
|
this.declear_content += this.line("%CLASSES%");
|
||||||
|
this.declear_content += this.line("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export(name: string, table: TableData) {
|
||||||
|
const base_type = (this.configs as CSharpExporterConfigs).base_type;
|
||||||
|
let body = "";
|
||||||
|
for (const field of table.headers) {
|
||||||
|
if (field.name == 'id' && (this.configs as CSharpExporterConfigs).ignore_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let type = "object";
|
||||||
|
switch (field.type) {
|
||||||
|
case DataType.bool:
|
||||||
|
case DataType.float:
|
||||||
|
case DataType.string:
|
||||||
|
case DataType.int:
|
||||||
|
type = field.type;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = "object";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (field.is_array) {
|
||||||
|
type += "[]";
|
||||||
|
}
|
||||||
|
if (field.comment) {
|
||||||
|
let comment = field.comment.split("\r\n").join("\t");
|
||||||
|
comment = comment.split("\n").join("\t");
|
||||||
|
body += this.line(`/// <summary>${comment}</summary>`, 1);
|
||||||
|
}
|
||||||
|
body += this.line(`${type} ${field.name};`, 1);
|
||||||
|
}
|
||||||
|
let class_text = this.line(`public class ${name} : ${base_type} {\n${body}\n}`);
|
||||||
|
this.classes.push(class_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
let class_text = "";
|
||||||
|
for (const cls of this.classes) {
|
||||||
|
class_text += cls;
|
||||||
|
class_text += this.line();
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = path.join(this.configs.directory, (this.configs as CSharpExporterConfigs).file_name);
|
||||||
|
if (!file.endsWith(".cs")) {
|
||||||
|
file += ".cs";
|
||||||
|
}
|
||||||
|
this.save_text(file, this.declear_content.replace("%CLASSES%", class_text));
|
||||||
|
console.log(colors.green(`\t${file}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/excel-exporter/exporters/JSONExporter.ts
Normal file
67
src/excel-exporter/exporters/JSONExporter.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { TableExporter, ExporterConfigs } from "excel-exporter/TableExporter";
|
||||||
|
import { TableData } from "excel-exporter/TableParser";
|
||||||
|
import { path } from "tiny/path";
|
||||||
|
import * as colors from "colors";
|
||||||
|
|
||||||
|
interface JSONExporterConfigs extends ExporterConfigs {
|
||||||
|
/** 缩进字符 */
|
||||||
|
indent: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JSONExporter extends TableExporter {
|
||||||
|
|
||||||
|
constructor(configs: ExporterConfigs) {
|
||||||
|
super(configs);
|
||||||
|
if ( typeof ((this.configs as JSONExporterConfigs).indent) != 'string') {
|
||||||
|
(this.configs as JSONExporterConfigs).indent = " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected recursively_order_keys(unordered: object | Array<object>) {
|
||||||
|
// If it's an array - recursively order any
|
||||||
|
// dictionary items within the array
|
||||||
|
if (Array.isArray(unordered)) {
|
||||||
|
unordered.forEach((item, index) => {
|
||||||
|
unordered[index] = this.recursively_order_keys(item);
|
||||||
|
});
|
||||||
|
return unordered;
|
||||||
|
}
|
||||||
|
// If it's an object - let's order the keys
|
||||||
|
if (typeof unordered === 'object' && unordered != null) {
|
||||||
|
var ordered = {};
|
||||||
|
Object.keys(unordered).sort().forEach((key) => {
|
||||||
|
ordered[key] = this.recursively_order_keys(unordered[key]);
|
||||||
|
});
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
return unordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
export(name: string, table: TableData) {
|
||||||
|
const file = path.join(this.configs.directory, name + ".json");
|
||||||
|
let headers = table.headers;
|
||||||
|
let values = [];
|
||||||
|
for (const row of table.values) {
|
||||||
|
let new_row = {};
|
||||||
|
for (let i = 0; i < headers.length; i++) {
|
||||||
|
const field = headers[i];
|
||||||
|
new_row[field.name] = row[i];
|
||||||
|
}
|
||||||
|
values.push(new_row);
|
||||||
|
}
|
||||||
|
let indent = "";
|
||||||
|
const configs = (this.configs as JSONExporterConfigs);
|
||||||
|
if (configs.indent) {
|
||||||
|
if (typeof (configs.indent) == 'number') {
|
||||||
|
for (let i = 0; i < configs.indent; i++) {
|
||||||
|
indent += " ";
|
||||||
|
}
|
||||||
|
} else if (typeof configs.indent == 'string') {
|
||||||
|
indent = configs.indent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const text = JSON.stringify(this.recursively_order_keys(values), null, indent);
|
||||||
|
this.save_text(file, text);
|
||||||
|
console.log(colors.green(`\t ${name} ==> ${file}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main.ts
16
src/main.ts
@@ -1,4 +1,18 @@
|
|||||||
import { get_startup_arguments } from "./tiny/env";
|
import { get_startup_arguments } from "./tiny/env";
|
||||||
|
import { ExcelExporterApplication } from "excel-exporter/ExcelExporterApplication";
|
||||||
|
import { FileAccess } from "tiny/io";
|
||||||
|
import * as colors from "colors";
|
||||||
|
|
||||||
(async function main(argv: string[]) {
|
(async function main(argv: string[]) {
|
||||||
console.log(argv);
|
|
||||||
|
let config_file = argv[argv.length - 1];
|
||||||
|
if (config_file.endsWith(".json") && FileAccess.exists(config_file)) {
|
||||||
|
let app = new ExcelExporterApplication(config_file);
|
||||||
|
app.parse();
|
||||||
|
app.export();
|
||||||
|
console.log(colors.green("All Done"));
|
||||||
|
} else {
|
||||||
|
console.log(colors.red("请传入配置文件作为参数"));
|
||||||
|
}
|
||||||
|
|
||||||
})(get_startup_arguments());
|
})(get_startup_arguments());
|
||||||
|
|||||||
2
转表.bat
Normal file
2
转表.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
call node ./dist/binary.js ./excel-exporter.json
|
||||||
|
pause
|
||||||
Reference in New Issue
Block a user