Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

单元格 Parser

Parser 只用于 Excel、CSV 这类单元格输入。大多数 parser 告诉 Sora 如何把一个 cell 转成类型化值;columnstagged_columns 这类投影 parser 告诉 Sora 一个字段如何映射到多列输入。字符串形式的 default 会走单 cell parser 的同一套路径。TOML 行数据通常可以直接使用 TOML array/table,不需要单元格 parser。

当默认 cell 写法太冗长或容易歧义时,再声明 parser:

[[tables.fields]]
name = "tags"
type = "list<string>"
parser = { kind = "split", separator = "|" }

对应 cell:

starter|melee|weapon

Parser option 都是字符串。未知 parser、当前 parser 不支持的 option、空 option value 都会在 schema normalization 阶段报错。例外是 columns.prefixtagged_columns.prefix 这类投影前缀,"" 有明确含义。

自定义 Lua Parser

项目可以在 project.toml 里加载项目内的 Lua parser 脚本:

[parsers]
scripts = ["tools/parsers.lua"]

脚本路径按 project 文件所在目录解析。之后所有读取该 project 的命令都能使用这些自定义 parser,不需要反复写命令行参数:

sora build --project project.toml
sora export --project project.toml --data-root data --format json --out generated/config.json

CLI 命令也可以通过全局 --parser-script 临时追加 parser 脚本:

sora --parser-script tools/parsers.lua build --project project.toml
sora --parser-script tools/parsers.lua export --project project.toml --data-root data --format json --out generated/config.json

这个参数可以重复传,并追加在 project 配置的脚本之后。自定义 parser 属于项目可信代码。Sora 会用受限 Lua 标准库加载脚本,不暴露 ioospackagedebug

Parser 脚本返回一个带 parsers 的 table。每个 parser 必须定义 parse(cell, ctx)options 是支持的 parser option 列表。validate(field) 可选,会在 schema normalization 阶段执行。

return {
  parsers = {
    slug = {
      options = { "prefix" },
      validate = function(field)
        if field.type ~= "string" then
          error("slug parser requires string")
        end
      end,
      parse = function(cell, ctx)
        local text = string.lower(string.gsub(cell.text, "%s+", "-"))
        if ctx.options.prefix ~= nil then
          return ctx.options.prefix .. text
        end
        return text
      end,
    },
  },
}

Schema 字段按名字使用自定义 parser:

[[tables.fields]]
name = "tag"
type = "string"
parser = { kind = "slug", prefix = "item-" }

cell 包含 kindtext,以及适用时的 valuectx 包含 fieldtypeoptionspath,以及 rowcolumn、worksheet 的 sheet 等位置信息。Lua 返回值会映射成 Sora 数据值:nil、bool、integer、float、string、array-like table 和 string-keyed table。

自定义 Lua parser 是单 cell parser。它不会替代 columnstagged_columns 这类投影 parser,不能读取相邻 cell,也不会改变 schema、source loading 或生成 runtime 的行为。

默认解析

字段没有声明 parser 时,Sora 按字段类型做默认解析:

类型Cell 写法
bool布尔 cell、truefalse,或数字 cell:0 为 false,非 0 为 true。
i32i64ref<Table.key>整数 cell、整数字符串,或无小数部分的 float cell。
durationdhmsms 单位的时长文本,例如 500ms30s1h 30m。单位必须按从大到小排列。
f32f64数字 cell 或数字字符串。
stringenum<Name>cell 展示文本。
struct<Name>union<Name>JSON object 文本。
list<T>set<T>array<T,N>逗号分隔文本。JSON array 请使用 json parser。
map<K,V>JSON pair array,例如 [["atk",10],["hp",20]]
optional<T>空 cell 解析成 null;非空时按内部 T 解析。

默认集合解析刻意保持简单。Primitive item 会按类型解析。struct 和 union 集合 item 必须写成 JSON object 文本。嵌套集合不能靠单个分隔符可靠表达,应该使用 parser = { kind = "json" }

Parser 速查

Parser适用类型Cell 形状
splitlist<T>set<T>array<T,N>,或包在这些类型外的 optionala,b,c
tuplestruct<T>optional<struct<T>>Gold,0,100
columnsstruct<T>optional<struct<T>>多列
tuple_listlist<struct<T>>set<struct<T>>array<struct<T>,N>,或包在这些类型外的 optionalGold,0,100|Gem,0,5
mapmap<K,V>optional<map<K,V>>atk,10|hp,20
tagged_columns只能用于 union<T>多列
json任意类型匹配字段类型的 JSON value

array<T,N> 会检查 item 数量。tuple 会检查 value 数量是否等于被引用 struct 的字段数。

split

split 适合扁平集合,例如 primitive、enum、ref,或其它可以稳定用分隔符切开的简单值。

[[tables.fields]]
name = "starter_items"
type = "list<ref<Item.id>>"
parser = { kind = "split" }

Cell:

1001,1002,1003

解析结果:

[1001,1002,1003]

当逗号不适合作为分隔符时,声明 separator

[[tables.fields]]
name = "tags"
type = "set<string>"
parser = { kind = "split", separator = "|" }

Cell:

starter|melee|weapon

tuple

tuple 适合把一个很小的 struct 写在一个 cell 里。值的顺序等于该 struct 的字段声明顺序。

[[structs]]
name = "ResourceCost"

[[structs.fields]]
name = "kind"
type = "enum<ResourceKind>"

[[structs.fields]]
name = "id"
type = "i32"

[[structs.fields]]
name = "count"
type = "i32"

[[tables.fields]]
name = "price"
type = "struct<ResourceCost>"
parser = { kind = "tuple" }

Cell:

Gold,0,100

解析结果:

{"kind":"Gold","id":0,"count":100}

如果 struct 字段值里经常出现逗号,可以换分隔符:

parser = { kind = "tuple", separator = "|" }

Cell:

Gold|0|100

columns

columns 适合把一个 struct 展开成普通 Excel/CSV 列来编辑,而不是写 JSON 或一个紧凑 tuple cell。它只能用于 table field 上的 struct<T>optional<struct<T>>

[[structs]]
name = "ResourceCost"

[[structs.fields]]
name = "kind"
type = "enum<ResourceKind>"

[[structs.fields]]
name = "id"
type = "i32"

[[structs.fields]]
name = "count"
type = "i32"

[[tables.fields]]
name = "price"
type = "struct<ResourceCost>"
parser = { kind = "columns", prefix = "price_" }

CSV header 和行:

id,name,price_kind,price_id,price_count
1,Iron Sword,Gold,0,100

解析出的 price

{"kind":"Gold","id":0,"count":100}

默认 prefix 会使用字段名,例如字段 price 会投影出 price.kindprice.idprice.count。只有当 struct 字段名应该直接位于当前表顶层时,才使用 prefix = ""。Sora 会拒绝投影列名冲突。

columns 不会递归展开嵌套 struct 或 union。如果被展开出来的 struct 字段本身仍然是复杂类型,可以给这个子字段声明 tuplesplitmapjson 这类单 cell parser;如果嵌套数据很大或需要重复出现,应该拆成独立表,再用 ref 或派生字段连接。这样可以避免表变得很宽,也让复杂记录更容易复用。

生成 XLSX 模板时,同一个 columns 字段投影出来的列会使用同一组表头颜色。

tuple_list

tuple_list 适合一组小 struct。separator 用来切一个 struct 内部的字段,item_separator 用来切 list item。

[[tables.fields]]
name = "materials"
type = "list<struct<ResourceCost>>"
parser = { kind = "tuple_list" }

Cell:

Item,2003,4|Gold,0,1000

解析结果:

[
  {"kind":"Item","id":2003,"count":4},
  {"kind":"Gold","id":0,"count":1000}
]

自定义分隔符:

parser = { kind = "tuple_list", separator = ":", item_separator = ";" }

Cell:

Item:2003:4;Gold:0:1000

map

map 适合简单 key/value pair。separator 用来切 key 和 value,item_separator 用来切 map entry。

[[tables.fields]]
name = "attributes"
type = "map<string,i32>"
parser = { kind = "map" }

Cell:

atk,10|hp,20

解析结果:

[["atk",10],["hp",20]]

Sora 导出 map 时使用 pair array,这样非 string key 也不会有歧义。如果你更想在 cell 里写 JSON,也可以使用 parser = { kind = "json" },然后写同样的 pair-array 形状:

[["atk",10],["hp",20]]

tagged_columns

tagged_columns 用来把一个 union<T> 值展开到多列 Excel/CSV 中编辑。它只能用于类型正好是 union<T> 的 table field。它不能用于 optional<union<T>>list<union<T>>set<union<T>> 或其它容器。

[[unions]]
name = "EventCondition"
tag = "type"

[[unions.variants]]
name = "QuestCompleted"

[[unions.variants.fields]]
name = "quest_id"
type = "ref<Quest.id>"

[[unions.variants]]
name = "HasItem"

[[unions.variants.fields]]
name = "item_id"
type = "ref<Item.id>"

[[unions.variants.fields]]
name = "count"
type = "i32"

[[tables.fields]]
name = "value"
type = "union<EventCondition>"
parser = { kind = "tagged_columns", prefix = "" }

CSV header 和行:

id,type,quest_id,item_id,count
1,QuestCompleted,5002,,
2,HasItem,,1001,2

tag 列填写 union variant 名称。只有被选中的 variant 的字段列可以填写值。默认 prefix 会使用字段名,例如字段 condition 会投影出 condition.typecondition.quest_idcondition.item_id。只有当这些列应该直接位于当前表顶层时,才使用 prefix = ""

Sora 会拒绝投影列名冲突。例如表里已经有普通字段 type,同时又对 tag 也是 type 的 union 使用 prefix = ""

tagged_columns 也不会递归展开 variant 字段里的嵌套 struct 或嵌套 union。Variant 字段仍然可以使用 tuplesplitmapjson 这类单 cell parser。如果某个 variant 需要很大的嵌套对象或重复嵌套对象,应该把那部分数据建成独立表,再通过引用或派生字段组合,而不是继续把 union 行横向展开。

生成 XLSX 模板时,同一个 tagged_columns 字段投影出来的列会使用同一组表头颜色,tag 列会在同色组里更醒目。

json

json 适合嵌套值、容器里的 union、嵌套集合,以及任何需要明确转义的复杂形状。

[[tables.fields]]
name = "actions"
type = "list<union<RewardAction>>"
parser = { kind = "json" }

Cell:

[
  {"type":"AddItem","item_id":1007,"count":3},
  {"type":"UnlockStage","stage_id":9002}
]

单个 union 值:

[[tables.fields]]
name = "condition"
type = "union<EventCondition>"
parser = { kind = "json" }

Cell:

{"type":"QuestCompleted","quest_id":5002}

map<K,V> 的 JSON 写法是 pair array,不是 JSON object:

[["atk",10],["hp",20]]

怎么选

需求推荐
扁平 primitive 列表split
一个紧凑 structtuple
一个 struct 展开成多列columns
一组紧凑 structtuple_list
简单 key/value pairmap
一个 union 展开成多列tagged_columns
嵌套值、容器里的 union、需要转义、或想直接写 JSONjson