core:概念与节点
core 是纯数据层,包含概念类、文档树的类型定义和树的校验函数。 它不依赖 React,可以在任何 JavaScript 环境中使用,包括 Node.js(比如在服务端校验文档)。
概念类
FirstClassConcept
new FirstClassConcept({
type: "group" | "inline" | "structure" | "support" | "abstract",
name: string,
parameter_prototype?: ParameterList, // 参数原型,缺省为 {}
meta_parameters?: MetaParameters, // 元参数,缺省为 {}
})
一级概念声明一类文档成分的机制:有哪些参数、各参数的类型和默认值。
(type, name) 二元组是它的唯一标识。
meta_parameters(元参数)是少数几个影响编辑器行为的开关,与普通参数不同,写作者不可见:
| 字段 | 含义 |
|---|---|
force_inline?: boolean | 强制以行内样式渲染。比如行内图片:虽然是支撑节点,但要嵌在文字流里。 |
force_block?: boolean | 强制以块级样式渲染。 |
force_void?: boolean | 强制无子节点(编辑器不允许在其中输入内容)。 |
SecondClassConcept
new SecondClassConcept({
type: AllConceptTypes,
first_concept: string, // 继承的一级概念名
name: string, // 写作者使用的名字,写进节点的 concept 字段
default_override?: ParameterList, // 重写参数默认值(写作者仍可修改)
fixed_override?: FixedParameterList, // 固定参数值(写作者不可修改,支持函数型)
})
二级概念是写作者实际使用的词汇。fixed_override 中值为
{type: "function", val: "p => ..."} 的参数是函数型参数:
印刷时以处理后的参数表为入参求值,让参数值随其他参数联动(用法见教程第 7 章)。
节点类型
七种节点的接口和子节点约束如下(直观的介绍见教程第 1 章):
| 类型 | 关键字段 | 子节点约束 |
|---|---|---|
TextNode | {text: string} | 无子节点 |
ParagraphNode | {children},没有 type 字段 | text / inline / support |
InlineNode | type: "inline" 加概念字段 | text / inline |
GroupNode | type: "group" 加概念字段和 relation | 非叶节点(不能直接是 text 或 inline) |
StructNode | type: "structure" 加概念字段和 relation | 只能是 group |
SupportNode | type: "support" 加概念字段 | 固定为 [{text: ""}] 占位 |
AbstractNode | type: "abstract" 加概念字段 | 与 group 相同;文档根节点就是此类型 |
「概念字段」指所有概念节点共有的四个字段:
idx: string // 节点编号,全文唯一,由 gene_idx() 生成
concept: string // 二级概念名
parameters: ParameterList
abstract: AbstractNode[] // 附着在此节点上的抽象节点
常用的联合类型:
type Node = TextNode | ParagraphNode | ConceptNode
type ConceptNode = InlineNode | GroupNode | StructNode | SupportNode | AbstractNode
type NonLeafNode = ParagraphNode | NonLeafConceptNode
type AllConceptTypes = "group" | "inline" | "structure" | "support" | "abstract"
type AllNodeTypes = AllConceptTypes | "paragraph" | "text"
参数类型
参数的值不是裸值,而是带类型标注的小对象,三种基础类型如下。 这个设计在教程第 1 章解释过:类型标注让编辑器能为每种参数生成合适的编辑控件。
type ParameterValue =
| {type: "string", val: string, [k: string]: any}
| {type: "number", val: number, [k: string]: any}
| {type: "boolean", val: boolean, [k: string]: any}
interface ParameterList { [key: string]: ParameterValue }
参数值对象允许携带额外字段。默认参数编辑器认识其中的 choices: [...]:
带这个字段的参数会渲染成下拉选择而不是自由输入。
FixedParameterValue 在此基础上额外允许 {type: "function", val: string}。
类型守卫与查找工具
处理来历不明的节点对象时(遍历一棵树、解析剪贴板内容都属于这种情况),第一件事是判断它是哪种节点。 core 为每种节点提供了守卫函数,返回值兼作 TypeScript 的类型收窄;此外还有两个按路径查找节点的工具:
| 函数 | 说明 |
|---|---|
is_concetnode(node) | 是否为概念节点。注意函数名里的 concet 是历史拼写。 |
is_inlinenode / is_groupnode / is_supportnode / is_structnode / is_abstractnode | 各类型的守卫函数 |
is_paragraphnode / is_textnode | 段落和文本的守卫函数 |
get_node_type(node): AllNodeTypes | 返回节点类型字符串;遇到无法辨认的对象抛 BadNodeError |
find_node_by_path(root, path) | 按路径(子节点下标数组)查找节点 |
find_concept_nodes_by_path(root, path) | 收集路径沿途经过的所有概念节点,不含根节点 |
校验
校验函数有两个,签名如下:
validate(tree: any): [boolean, string]
validate_parameters(parameters: any, gene_msg: (s: string) => string): [boolean, string]
validate 递归检查一棵树是否为合法的文档树:字段是否齐全、参数类型是否匹配、
各类节点的子节点约束是否成立。返回值是一个二元组,第一项表示是否合法,
第二项在不合法时给出带节点路径的错误说明,形如 node [1,0]: ...。
在把外部数据(用户上传、旧存档)交给编辑器之前应当先调用它,用法见教程第 7 章。
validate_parameters 是其中校验参数表的部分,也单独导出。
通用工具(lib/utils)
另有几个不属于任何模块的通用小工具。其中 merge_object 值得单独认识:
样式配置「只写想改的项、其余沿用默认」的合并语义就是它实现的:
| 函数 | 说明 |
|---|---|
gene_idx(): string | 生成一个随机的节点编号(数字字符串) |
merge_object(a, b) | 递归合并两个对象,叶子值冲突时以 b 为准。样式配置的「部分覆盖」就是用它实现的。 |
object_foreach(obj, fn) | 对对象的每个值做映射,返回新对象 |
异常类
库定义了三个异常类,都继承自 Error,可以用 instanceof 判别:
UnexpectedParametersError(函数收到了不符合约定的参数)、
BadNodeError(遇到了不符合约定的节点)、
ImpossibleError(执行到了理论上不可能到达的代码路径)。