Tolaria 表格笔记文件格式详解:YAML 元数据 + CSV 正文的纯文本电子表格存储模型
【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria
Tolaria 将电子表格功能建立在「Markdown 文件 + YAML frontmatter + CSV-like 正文」这一纯文本约定之上,表格笔记通过_display: sheet切换为电子表格编辑器,展示状态则全部落在 frontmatter 的_sheet键下。本文完整拆解这一文件格式的结构、CSV 序列化规则、_sheet元数据字段、数字格式、跨表引用语法,并结合 sheetMetadata.ts、sheetCsv.ts、sheetExternalReferences.ts 等源码,说明格式在实际实现中的解析与校验行为,适合需要用脚本或 AI Agent 安全编辑表格笔记的开发者。
文件总体结构:frontmatter + CSV 正文
Sheet 笔记就是一个 Markdown 文件,由两部分组成:
- YAML frontmatter:存放笔记元数据,包括普通 Tolaria 字段和保留的表格展示字段;
- CSV-like 正文:直接存放行与单元格,没有 Markdown 表格包裹、没有围栏代码块、也没有内嵌的二进制工作簿 blob。
一个典型的季度模型 sheet 文件长这样(取自文档中的完整示例):
--- type: Project _display: sheet tags: - planning _sheet: show_grid_lines: true frozen_rows: 1 frozen_columns: 1 columns: A: width: 180 rows: "1": height: 32 cells: E6: num_fmt: "0.00%" bold: true --- Metric,January,February,March,Q1 Total Subscriptions,1200,1350,1500,=SUM(B2:D2) Expenses,650,700,760,=SUM(B3:D3) Net,=B2-B3,=C2-C3,=D2-D3,=SUM(B4:D4) Growth,,=(C4-B4)/B4,=(D4-C4)/C4,=(E4-B4)/B4仓库的示例 vault 里也有一个可对照的真实文件 tolaria-sheet-prototype-sample.md,其中_sheet设置了frozen_rows: 1、各列宽度和A2/A5的粗体,正文是一个含=SUM(B2:D2)等公式的季度报表。
这个设计的取舍记录在 ADR 0134 中:表格数据必须保持可检视、可 diff、Git 友好,交互行为(选择、键盘导航、格式化、复制粘贴)委托给 IronCalc workbook 引擎,Tolaria 自己只负责格式路由、纯文本适配层和持久化保护。
Frontmatter 部分
所有普通 Tolaria 字段在 sheet 笔记中继续可用:
typestatusdatetagsurl- 关系字段,如
belongs_to、related_to,以及自定义的 wikilink 属性
两个特殊字段:
_display: sheet是「以表格展示」的标记(display-as marker)。普通文本笔记省略该字段即可。注意type仍然是普通的语义元数据——一个 sheet 可以是任何 Tolaria 类型,type和「如何展示」是解耦的。_sheet键保留给电子表格的展示元数据(presentation metadata)。它遵循 Tolaria 其他下划线前缀系统字段的惯例:在常规属性面板中隐藏,但在原始源码中可见可编辑。
Body 正文的 CSV 规则
正文是 CSV-like 文本,规则如下:
- 行以换行符分隔;
- 单元格以逗号分隔;
- 包含逗号、引号或换行的单元格需要加引号;
- 引号单元格内部的引号通过双写转义(
""表示一个"); - 保存时尾部空行和空列可以被省略。
任何以=开头的输入都被当作公式,其余单元格视为字面值。
源码层面这些规则在 sheetCsv.ts 中有对应的实现与约束:
splitSheetDocument()负责按---分隔符切出 frontmatter 与 body(sheetCsv.ts#L65-L85),没有合法 frontmatter 时整个内容都视为 body;CsvRowParser是逐字符的状态机解析器,支持引号单元格内的双引号转义,并同时保留每行的原始文本(rawRows)和行终止符(rowTerminators)(sheetCsv.ts#L91-L160);- 序列化时
shouldQuoteCsvCell()在「包含逗号/引号/换行、或首尾有空白」时加引号,并对内部引号做双写转义(sheetCsv.ts#L217-L228); lastMeaningfulRowIndex()/lastMeaningfulColumnIndex()在写出时裁掉尾部空行空列,对应文档中「保存时尾部空行和空列可省略」的规则(sheetCsv.ts#L230-L244);- 更微妙的一点:
serializeCsvRowsPreservingSourceRows()对内容未变化的行原样保留源文本,只对真正改动的行重新序列化(sheetCsv.ts#L273-L328)。这意味着保存不会无谓地重写整个文件,Git diff 只出现真正变更的行——这是纯文本表格能保持 diff 友好的关键工程细节。
_sheet元数据:全部字段一览
Tolaria 把表格的展示状态以纯 YAML 存在_sheet下。完整字段表(继承自原参考文档):
| 字段 | 含义 |
|---|---|
show_grid_lines | 是否显示网格线 |
frozen_rows | 从顶部冻结的行数 |
frozen_columns | 从左侧冻结的列数 |
columns.<列>.width | 自定义列宽,键为列字母(如A、BC) |
rows."<行>".height | 自定义行高,键为一基行号 |
cells.<单元格>.num_fmt | 单元格的数字格式代码 |
cells.<单元格>.bold | 粗体 |
cells.<单元格>.italic | 斜体 |
cells.<单元格>.underline | 下划线 |
cells.<单元格>.strike | 删除线 |
cells.<单元格>.font_size | 字号 |
cells.<单元格>.font_color | 文字颜色 |
cells.<单元格>.fill_color | 单元格填充色 |
cells.<单元格>.horizontal_align | 水平对齐 |
cells.<单元格>.vertical_align | 垂直对齐 |
cells.<单元格>.wrap_text | 自动换行 |
cells.<单元格>.border_top | 上边框样式 |
cells.<单元格>.border_right | 右边框样式 |
cells.<单元格>.border_bottom | 下边框样式 |
cells.<单元格>.border_left | 左边框样式 |
单元格元数据的键使用A1 风格地址,如A1、B12、AA30。边框值存为「样式名 + 可选颜色」的字符串,例如:
border_bottom: "thin #d0d7de"从源码实现(sheetMetadata.ts)可以看到几个编辑该块时值得知道的细节:
- 缩进是严格的:
parseSheetMetadata()按行匹配_sheet:之后固定缩进的行——顶级设置(show_grid_lines等)必须缩进 2 格,columns:/rows:/cells:小节头缩进 2 格,小节键(列名/行号/单元格地址)缩进 4 格,属性赋值缩进 6 格(sheetMetadata.ts#L355-L407)。不符合这个缩进契约的行不会被解析进元数据。 - 列名/行号/单元格键都有校验:列名必须是纯大写字母(
columnIndexFromName()做 26 进制换算),行号键必须是一基正整数且序列化时会加引号("1":),单元格地址必须匹配^[A-Z]+[1-9]\d*$并被规范化为大写(sheetMetadata.ts#L133-L163)。 - 类型校验在赋值时执行:
width/height只接受数字,bold等只接受布尔,font_size只接受数字,边框必须能通过parseBorderMetadata()拆成「样式 + 可选颜色」(sheetMetadata.ts#L197-L260)。 - 写入是去重且有序的输出:
mergeSheetMetadata()先整体移除旧的_sheet块再重写,列按列序、行按行号、单元格按「先行后列」的 A1 顺序排序输出,空元数据不会写出空块(sheetMetadata.ts#L534-L564)。因此手工编辑_sheet时不必关心现有顺序,保存时会被规范化。
对应的 TypeScript 接口SheetCellMetadata/SheetBorderMetadata可以直接作为程序化生成该块的字段参考(sheetMetadata.ts#L11-L32)。
数字格式(num_fmt)
num_fmt使用电子表格风格的格式代码,常见例子:
| 格式 | 示例输出 |
|---|---|
#,##0 | 1,250 |
#,##0.00 | 1,250.50 |
0.00% | 12.35% |
$#,##0.00 | $1,250.50 |
yyyy-mm-dd | 2026-06-15 |
这些格式只影响展示,不改变 CSV 正文中存储的底层单元格输入。这也是「不要把公式转成显示值」这条 Agent 规则的底层原因:格式代码与原始输入是两层信息,写回显示值会永久丢失原始数据。
Markdown 样式导入
导入非公式 CSV 单元格时,简单的 Markdown 包裹符可以作为初始样式的种子:
| 单元格文本 | 存储值 | 样式 |
|---|---|---|
**Revenue** | Revenue | bold |
_Estimate_ | Estimate | italic |
***Total*** | Total | bold + italic |
~~Removed~~ | Removed | strike |
保存之后,样式归属_sheet元数据,正文里保留的是去掉包裹符的裸文本。
源码 sheetMarkdownCell.ts 的实现比文档表格更宽:除**/_/***/~~之外还支持__(粗体)和*(斜体)两种包裹符,并且只匹配对称包裹(首尾标记一致、内部非空);两个重要的守卫条件值得注意(sheetMarkdownCell.ts#L6-L17):
- 以
=开头的输入直接原样返回——公式永远不会被 Markdown 导入改写; - 若包裹符内部剥掉前导
*/_/~后以=开头,也判为公式候选并跳过样式剥离。
Wikilinks 与跨表引用
普通单元格中的 wikilink
非公式单元格可以存放标准 Tolaria wikilink:
Account,Source Newsletter,[[newsletter-revenue]] Sponsors,[[sponsorship-pipeline]]公式中的跨表引用
公式单元格可以用 Tolaria 的跨表语法引用其他 sheet 笔记:
=[[newsletter-revenue]].B5 =ROUND([[business-plan]].$E$12, 2) =[[device]].power.watts =[[launch-brief]].2三类引用的语义:
- 跨表单元格引用
[[note]].A1:[[...]]内部按普通 wikilink 解析目标笔记,点号后读一个 A1 风格的单元格。目前只支持单单元格,跨笔记的范围引用还不支持,范围公式请留在单个 sheet 笔记内部。 - frontmatter 引用
[[note]].property.path:点号后是标量属性路径,支持嵌套键;数字、布尔、字符串变成公式字面量。缺失的笔记、有歧义的笔记目标、缺失的属性、数组/map 等非标量值都会按未解析处理,以表格错误(如#N/A)呈现。 - 数字行引用
[[note]].2:读该笔记剥掉 frontmatter 后的正文第 N 行(一基),逗号作为文本保留。注意[[note]].A1是网格/单元格语义(逗号会拆分内容),[[note]].1才是整行语义。
这些语法的识别由 sheetExternalReferences.ts 中三个正则承担(L11-L13):
SHEET_EXTERNAL_CELL_REFERENCE_PATTERN = /\[\[([^\]\n]+?)\]\]\.(\$?)([A-Za-z]+)(\$?)([1-9]\d*)/g SHEET_EXTERNAL_LINE_REFERENCE_PATTERN = /\[\[([^\]\n]+?)\]\]\.([1-9]\d*)/g SHEET_EXTERNAL_FRONTMATTER_REFERENCE_PATTERN = /\[\[([^\]\n]+?)\]\]\.([A-Za-z_][A-Za-z0-9_-]*(?:\.[A-Za-z_][A-Za-z0-9_-]*)*)/g从实现里还能读出文档未展开的两条规则:
- A1 冲突裁决:
isCellAddressLikePropertyPath()判定「第一段看起来像 A1 地址(如B2)的路径」按跨表单元格引用处理,因此 frontmatter 属性名应避免与 A1 记法重名(sheetExternalReferences.ts#L92-L101); - 引用有边界上限:跨表单元格引用在复制/移动公式时,相对引用会随行列偏移,绝对引用(
$标记)保持不动,偏移后越出1..1048576行 /1..16384列边界的引用会保持原样不偏移(sheetExternalReferences.ts#L8-L9, L208-L242)。绝对标记遵循电子表格的复制行为:
| 引用 | 复制行为 |
|---|---|
[[revenue]].B5 | 行和列都可以偏移 |
[[revenue]].$B$5 | 行和列都固定 |
[[revenue]].B$5 | 行固定,列可偏移 |
[[revenue]].$B5 | 列固定,行可偏移 |
公式本身的求值由 IronCalc 引擎处理,Tolaria 在其上叠加 vault 感知的引用模型;基础函数语法(算术、SUM/IF/ROUND、绝对引用、文本拼接等)及 195 个内置函数目录见 spreadsheet-functions.md。
面向 Agent 与脚本的编辑守则
当你需要用脚本或 AI Agent 程序化编辑 sheet 笔记时,原参考文档给出的守则应完整遵循:
- 保留 YAML frontmatter 分隔符和普通 Tolaria 字段;
- 文件应显示为电子表格时,保留
_display: sheet; - 表格展示状态放在
_sheet下; - 按 CSV 解析和序列化正文,不要手动对每个逗号做拆分;
- 公式保持为公式,包括
[[sheet]].A1、[[note]].property.path、[[note]].1三种引用形态; - 避免把公式转换成它们的显示值;
- 单元格包含逗号、引号或换行时给 CSV 单元格加引号;
- 不要在一个笔记里塞多个工作表 tab;需要多表就新建另一个带
_display: sheet的笔记; - 不要在 Markdown 文件里存不透明的二进制工作簿状态。
文档还给出了一条降级策略:如果脚本无法安全地保持_sheet块,就不要动它,只编辑你真正理解的 CSV 正文单元格。结合源码来看,这一策略是可靠的——正文解析器(sheetCsv.ts)对未改动行保留原始文本,而_sheet块的解析是逐行独立、按缩进契约匹配的(sheetMetadata.ts#L409-L424),正文编辑不会牵连元数据块。
一个可直接模仿的最小安全编辑样例(参照 use-spreadsheets.md 中的原始文件示例):
--- type: Project _display: sheet status: Draft belongs_to: - "[[business-plan]]" _sheet: frozen_rows: 1 columns: A: width: 180 --- Metric,January,February,March,Q1 Total Subscriptions,1200,1350,1500,=SUM(B2:D2) Services,800,900,750,=SUM(B3:D3) Expenses,650,700,760,=SUM(B4:D4) Net,=B2+B3-B4,=C2+C3-C4,=D2+D3-D4,=SUM(B5:D5)注意belongs_to的 wikilink 值用了引号包裹——frontmatter 里的 wikilink 属性值与 CSV 正文是两套语法,写脚本时不要混用。
纯文本存储模型的工程含义
把这套格式放回 ADR 0134 的决策上下文,可以概括出它的取舍:
- 数据可读、可 diff:表格数据是 Git 友好的纯文本,冲突解决、版本追溯都走标准 Markdown 工具链;
- 展示与数据分离:所有展示状态(列宽、行高、冻结、格式)都是 frontmatter 里的 YAML 标量,
_sheet为空时整个块不写出,普通笔记与表格笔记的转换只是增删_display一个字段; - 单表笔记模型:当前设计是刻意的单 sheet——「跨表」通过 wikilink 跨笔记引用实现,而不是单文件内的多工作表,这与 wikilink 关系模型一致;
- 编辑体验委托给 IronCalc:选择、键盘导航、格式化控件、复制粘贴都来自 IronCalc workbook 包,Tolaria 侧代码限于格式路由、纯文本适配、持久化保护和公式自动补全。ADR 同时提到元数据提取有界、保存序列化做了防抖(可用时走空闲时段),超大工作簿未来可能需要增量脏区跟踪。
小结
Tolaria 的表格文件格式本质上是三层约定:frontmatter 管元数据(_display: sheet决定展示方式,_sheet管展示状态)、CSV 正文管数据与公式(=前缀即公式,引号规则与尾部裁剪都有明确实现)、wikilink 引用语法把表格接入整个 vault 的关系网络(单元格、frontmatter 属性、正文行三种跨笔记读取方式)。理解 sheetMetadata.ts 的缩进契约、sheetCsv.ts 的行保留序列化、sheetExternalReferences.ts 的引用正则与边界裁决,你就具备了手工或脚本化维护表格笔记所需的全部细节——既能让文件在 Git 里保持干净的 diff,也能让 IronCalc 驱动的表格编辑器正确还原每一个格式状态。
【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考