Skip to content

Commit

Permalink
多维表读写
Browse files Browse the repository at this point in the history
  • Loading branch information
sumneko committed Jul 25, 2024
1 parent b46443b commit e58a6f5
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 56 deletions.
20 changes: 10 additions & 10 deletions src/editorTable/editorData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import { ProjectileData as Projectile } from "../editor_meta/projectile";
import { TechData as Tech } from "../editor_meta/tech";
import * as y3 from 'y3-helper';

type KV = Record<string, string|number|boolean>;

interface CommonPatch {
/**
* 存放自定义的键值对。新增值只能为字符串、数字或布尔值。
*/
kv: Record<string, string|number|boolean>;
kv: KV;
}

type Data<T> = Omit<T, keyof CommonPatch> & CommonPatch;
Expand Down Expand Up @@ -52,7 +54,7 @@ interface KVShape {
value: string|number|boolean,
}

function fromKV(kvMap: Record<string, KVShape>): Record<string, string|number|boolean> {
function fromKV(kvMap: Record<string, KVShape>): KV {
let result: Record<string, string|number|boolean> = {};
// 按照 sort 字段的值排序,然后将重新组成 { K: V.value } 的形式
let kvList = Object.values(kvMap).sort((a, b) => a.sort - b.sort);
Expand All @@ -62,18 +64,16 @@ function fromKV(kvMap: Record<string, KVShape>): Record<string, string|number|bo
return result;
}

function toKV(kv: Record<string, string|number|boolean>, raw: Record<string, KVShape>): Record<string, KVShape> {
let result: Record<string, KVShape> = {};
function toKV(kv: KV, raw: Record<string, KVShape>): Record<string, KVShape> {
let result: Record<string, KVShape> = { ...raw };
let sort = 0;
for (let key in kv) {
let value = kv[key];
let rawKV = raw[key];
if (rawKV) {
for (let [key, value] of Object.entries(kv)) {
if (key in result) {
result[key] = {
...rawKV,
...result[key],
value,
};
sort = Math.max(sort, rawKV.sort);
sort = Math.max(sort, result[key].sort);
} else {
let etype, type, prop_cls;
if (typeof value === 'string') {
Expand Down
10 changes: 6 additions & 4 deletions src/editorTable/excel/excel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ export class Sheet {
* 已某个单元格为锚点,创建一个key-value的表格。
* 如果不提供参数,会自动猜测一个合适的位置。
* @param offset 锚点位置,如 `"B2"`
* @param skip 标题下额外跳过的行数(可能是标题的描述)
*/
public makeTable(offset?: string): Table {
public makeTable(offset?: string, skip?: number): Table {
if (!offset) {
offset = this.guessTableOffset();
if (!offset) {
Expand All @@ -100,7 +101,7 @@ export class Sheet {
}

let table: Table = {};
for (let r = row + 1; r <= this.sheet.rowCount; r++) {
for (let r = row + 1 + (skip ?? 0); r <= this.sheet.rowCount; r++) {
const row = this.sheet.getRow(r);
const key = row.getCell(col).toString();
if (!key) {
Expand Down Expand Up @@ -132,8 +133,9 @@ export class Sheet {
* 与 `makeTable` 不同,可以一个对象可以保存多行的数据。
* 如果不提供参数,会自动猜测一个合适的位置。
* @param offset 锚点位置,如 `"B2"`
* @param skip 标题下额外跳过的行数(可能是标题的描述)
*/
public makeMultiTable(offset?: string): MultiTable {
public makeMultiTable(offset?: string, skip?: number): MultiTable {
if (!offset) {
offset = this.guessTableOffset();
if (!offset) {
Expand Down Expand Up @@ -166,7 +168,7 @@ export class Sheet {
}
};

for (let r = row + 1; r <= this.sheet.rowCount; r++) {
for (let r = row + 1 + (skip ?? 0); r <= this.sheet.rowCount; r++) {
const row = this.sheet.getRow(r);
const key = row.getCell(col).toString();
if (key) {
Expand Down
76 changes: 48 additions & 28 deletions src/editorTable/excel/rule.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import * as vscode from 'vscode';
import * as y3 from 'y3-helper';

type Row = Record<string, string>;

/**
* 读取excel中的值。
* @param row excel中的一行数据
* @param rows excel中的每个对象的数据。
* @returns 返回需要写入表中的值。
*/
type ReaderLike<T> = (row: Record<string, string>) => T | undefined;
type ReaderFunc<T> = (...rows: Row[]) => NoInfer<T> | undefined;

/**
* 对数据进行处理。
* @param content excel中的值
* @param source 物编中的值。如果你用def修改过,这里会传入修改后的值(用于多个项目修改同一个值)。
*/
type AsLike<T> = (content: string, source?: T) => T | undefined;
type AsFunc<T> = (content: string, source?: T, ...extraContents: string[]) => NoInfer<T> | undefined;

function mergeObject(from: Record<string, any>, to: Record<string, any>) {
for (let key in from) {
Expand All @@ -35,8 +37,8 @@ class AsRule<T> {
constructor(private as?: As<T>) {}

protected value: any;
applyAs(content: any, source?: T): T | undefined {
let value = this.as ? callAs(this.as, content, source) : content;
applyAs(content: any, source?: T, ...extraContents: any[]): T | undefined {
let value = this.as ? callAs(this.as, content, source, ...extraContents) : content;
if (
value === undefined ||
value === null ||
Expand Down Expand Up @@ -84,20 +86,24 @@ class AsRule<T> {
}

class ReaderRule<T> extends AsRule<T> {
constructor(private reader: ReaderLike<T>, as?: As<T>) {
constructor(private reader: ReaderFunc<T>, as?: As<T>) {
super(as);
}

public applyReader(row: Record<string, string>, source?: T): T | undefined {
return this.applyAs(this.reader(row), source);
public applyReader(rows: Row | Row[], source?: T): T | undefined {
if (Array.isArray(rows)) {
return this.applyAs(this.reader(...rows), source, ...rows.slice(1));
} else {
return this.applyAs(this.reader(rows), source);
}
}
}

function callAs(as: As<any>, value: any, source?: any) {
function callAs(as: As<any>, value: any, source?: any, ...extraValues: any[]) {
if (as instanceof AsRule) {
return as.applyAs(value, source);
return as.applyAs(value, source, ...extraValues);
}
return as(value, source);
return as(value, source, ...extraValues);
}

const braver = {
Expand Down Expand Up @@ -162,17 +168,17 @@ const as = {
split: <T = string>(separator: string | RegExp, converter?: (value: string) => T) => {
let rule = new AsRule<T[]>((value) => braver.split(value, separator, converter));
return rule;
}
},
} as const;

const reader = {
/**
* excel的每一行都会调用此回调哈数,你需要返回最终写入表中的值。
* excel的每一个对象都会调用此回调哈数,你需要返回最终写入表中的值。
* 返回 `undefined` 时表示不做修改(使用物编里原来的值)。
* @param callback 一个回调函数,需要你返回最终写入表中的值。
* @returns
*/
rule: <T>(callback: ReaderLike<T>): ReaderRule<T> => new ReaderRule(callback),
rule: <T>(callback: ReaderFunc<T>): ReaderRule<T> => new ReaderRule(callback),
/**
* 将值视为数字。
* @param title 列标题
Expand Down Expand Up @@ -225,9 +231,9 @@ const reader = {
}
} as const;

type Reader<T> = string | undefined | ReaderLike<T> | ReaderRule<T>;
type Reader<T> = string | undefined | ReaderFunc<T> | ReaderRule<T>;

type As<T> = AsLike<T> | AsRule<T>;
type As<T> = AsFunc<T> | AsRule<T>;


type EditorDataField<N extends y3.consts.Table.NameCN> = keyof y3.table.EditorData<N>;
Expand Down Expand Up @@ -283,6 +289,11 @@ export class Rule<N extends y3.consts.Table.NameCN> {
*/
public offset?: string;

/**
* 要跳过多少行。如果标题下一行是描述,可以将此设置为 `1`。
*/
public skip?: number;

/**
* 对象的key在表格中的列名。如果不提供会使用第一列。
* 如果不存在会新建。
Expand All @@ -299,6 +310,11 @@ export class Rule<N extends y3.consts.Table.NameCN> {
*/
public overwrite?: boolean;

/**
* 是否是多维表。
*/
public multi?: boolean;

/**
* 定义一个根据excel字段的生成规则
* @param title excel中的列标题
Expand All @@ -317,18 +333,17 @@ export class Rule<N extends y3.consts.Table.NameCN> {
*/
public async apply() {
let fileName = this.path.path.match(/([^/\\]+)$/)?.[1] ?? this.path.fsPath;
fileName = fileName.replace(/\.[^.]+$/, '');
const ruleName = `${this.tableName}: ${fileName}/${this.sheetName ?? 1}`;
const ruleName = `${this.tableName}: ${fileName}@${this.sheetName ?? 1}`;
y3.log.info(`正在执行规则:"${ruleName}"`);
try {
let sheet = await y3.excel.loadFile(this.path, this.sheetName);
let sheetTable = sheet.makeTable();
let sheetTable = this.multi ? sheet.makeMultiTable(this.offset, this.skip) : sheet.makeTable(this.offset, this.skip);
let editorTable = y3.table.openTable(this.tableName);

for (let firstCol in sheetTable) {
let row = sheetTable[firstCol];
let key = this.key ? this.getValue(row, this.key) : firstCol;
let template = this.template ? this.getValue(row, this.template) : undefined;
let rows = sheetTable[firstCol];
let key = this.key ? this.getValue(rows, this.key) : firstCol;
let template = this.template ? this.getValue(rows, this.template) : undefined;
let objectKey = Number(key);
let templateKey: number | undefined = Number(template);
if (isNaN(objectKey)) {
Expand All @@ -349,7 +364,7 @@ export class Rule<N extends y3.consts.Table.NameCN> {
}

for (const action of this.rule._actions) {
let value = this.getValue(row, action.action, editorObject.data[action.field]);
let value = this.getValue(rows, action.action, editorObject.data[action.field]);
if (action.asRule) {
value = callAs(action.asRule, value, editorObject.data[action.field]);
}
Expand All @@ -360,20 +375,25 @@ export class Rule<N extends y3.consts.Table.NameCN> {
}
}
} catch (e) {
y3.log.error(`执行规则失败:"${ruleName}"\n${e}`);
y3.log.error(`执行规则失败:"${ruleName}"\n${(e as Error)?.stack}`);
vscode.window.showErrorMessage(`执行规则失败:"${ruleName}"\n${e}`);
}
}

private getValue(row: Record<string, string>, value: Reader<any>, source?: any): any {
private getValue(rows: Row | Row[], value: Reader<any>, source?: any): any {
let firstRow = Array.isArray(rows) ? rows[0] : rows;
if (typeof value === 'string') {
return row[value];
return firstRow[value];
}
if (typeof value === 'function') {
return value(row);
if (Array.isArray(rows)) {
return value(...rows);
} else {
return value(rows);
}
}
if (value instanceof ReaderRule) {
return value.applyReader(row, source);
return value.applyReader(rows, source);
}
throw new Error('未知的值类型: ' + String(value));
}
Expand Down
44 changes: 43 additions & 1 deletion template/plugin/6/6.2.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
let y3 = require('y3-helper')

export async function 多维表() {
export async function 手动() {
y3.excel.setBaseDir(y3.env.pluginUri)

let excel = await y3.excel.loadFile('6-更多的演示/6.2-多维表')
Expand All @@ -15,3 +15,45 @@ export async function 多维表() {

y3.print('多维表读取成功!')
}

export async function 规则1() {
y3.excel.setBaseDir(y3.env.pluginUri)

let {rule, reader} = y3.excel.rule('技能', '6-更多的演示/6.2-多维表')

rule.multi = true
rule.skip = 1

rule.key = '编号'
rule.data.name = '名字'
rule.data.kv = reader.rule((row1, row2) => {
// JS的类型检查有bug,直接写 `{}` 会有蜜汁报错
let kv = Object.create(null);
for (const title of ['字段1', '字段2', '字段3', '字段4']) {
let k = row1[title]
if (!k) continue
let v = row2[title]
kv[k] = v
}
return kv;
})
}

export async function 规则2() {
y3.excel.setBaseDir(y3.env.pluginUri)

let {rule, as} = y3.excel.rule('技能', '6-更多的演示/6.2-多维表')

rule.multi = true
rule.skip = 1
rule.key = '编号'

rule.def('名字', rule.field.name)
for (const title of ['字段1', '字段2', '字段3', '字段4']) {
rule.def(title, rule.field.kv, (k, source, v) => {
if (!k) return
source[k] = v
return source
})
}
}
Loading

0 comments on commit e58a6f5

Please sign in to comment.