no title found

1 kaijuan

格式转换,

典故:“开卷有益”,陶渊明“好读书,不求甚解;每有会意,便欣然忘食”。

体验隐喻:你的工具做的所有事情,最终都指向同一个动作——“开卷”。

用户打开生成的 HTML/PDF,就像翻开一本排印精良的书。这个名字把复杂的格式转换,浓缩成了一个读者最本能的动作,极其有共鸣。

1.1 Arch Design

1.1.1 flatten heading or nested heading?

Markdown的Heading 是“扁平的(Flat)”,而 Org-mode 的 Heading 是“嵌套的树状结构(Tree)”

  1. Markdown (GFM/CommonMark) 中的 Heading

在标准的 Markdown AST 中,Heading 仅仅指的是标题本身,它直接映射为 HTML 中的 <h1> 到 <h6> 标签。标题下方的正文(段落、列表等)与标题在 AST 中是平级的兄弟节点(Siblings),它们之间没有任何包含关系。

# 标题
这里是正文段落。 

Markdown AST 视角(扁平序列):

[
    Heading(level: 1, content: "标题"),
    Paragraph(content: "这里是正文段落。"), // 与 Heading 是平级的兄弟
]  
  1. Org-mode 中的 Heading (Headline)

Org-mode 本质上是一个大纲(Outline)系统。在它的 AST 中,一个 Heading(在 Org 术语中常称为 Node 或 Headline)是一个容器。它不仅包含这行标题文本,还包含该标题下的所有正文内容(Section),甚至包含它下面所有低级别的子 Heading。

#+begin_src org-mode

2 格式转换

转为typst,

  • 导出到pdf
  • 导出到md
  • 导出到word

pandoc /home/touch/Downloads/水浒_第一回_-_DeepSeek_20260516T142649.md -o output.pdf --pdf-engine=typst -V mainfont="Sarasa Gothic SC"

cargo install --locked typst-cli

  • md导出
  • 精美的pdf导出

md规范

  • AST: GFM
  • org-mode 统一

通过org-mode/md-mode定义内容、结构 通过IR定义中间结构 IR转typst,配置模板转pdf/html/docx, 是否是最佳实践?

  1. 有org-mode导出html, 对于自定义code block, 比如begin_zongpi, 输出<div class="zongpi">, 通过css可配置样式。上面的架构,如何支持?
  2. typst模板,和css的关系。

3 标题

这里是正文段落。 #+end_src

[
    Headline {
        level: 1,
        title: "标题",
        section: [
            Paragraph("这里是正文段落。") // 正文是 Headline 的子节点
        ],
        children: [] // 如果有 `** 子标题`,会放在这里
    }
]  

Markdown/HTML/Typst天然扁平,org-mode天然嵌套 (树状大纲)

在通用文档转换工程领域(如业界黄金标准 Pandoc),方案 A(扁平 IR)是绝对的行业最佳实践。 在这四种格式(Markdown, HTML, Typst, Org)的交集中,方案 A 胜出的核心原因在于“少数服从多数”以及“复杂度隔离”原则。

如果选择方案 B,为了照顾 Org-mode 一种格式,你必须让 Markdown、HTML 和 Typst 的解析器全部承担“强行变树”的额外复杂度。

方案 A 下,如何优雅地处理 Org-mode? 选择方案 A 并不意味着放弃 Org-mode 的结构,而是把“树状与扁平相互转换”的复杂度,隔离在 Org-mode 自己的 Parser 和 Renderer 中,保持统一 IR 的纯净。

  1. Org 文本 -> 扁平 IR (Parser 阶段)

在解析 Org-mode 时,采用深度优先遍历(DFS),遍历到 Headline 时先输出一个 Block::Heading,然后递归遍历它的 section 内容和子 Headline,把它们平铺吐进一个扁平的 Vec<Block> 中。

  1. 扁平 IR -> Org 文本 (Renderer 阶段)

当你要把扁平的 IR 重新写回 Org-mode 时,Renderer 内部使用一个状态机(或者栈)来根据 Heading 的 level 隐式还原嵌套关系。

在 Rust 中,伪代码逻辑非常清晰:

struct OrgRenderer {
    // 可以在这里记录当前处于第几级标题,用来处理一些特殊的缩进或缩进抽屉
    current_level: u8, 
}

impl OrgRenderer {
    pub fn render(&mut self, blocks: &[Block]) -> String {
        let mut output = String::new();
        
        for block in blocks {
            match block {
                Block::Heading { level, content, meta } => {
                    self.current_level = *level;
                    // 根据元数据还原 Org 特有的 TODO 状态和 Tags
                    let todo = meta.extensions.get("org_keyword").map(|s| format!("{} ", s)).unwrap_or_default();
                    let tags = meta.extensions.get("org_tags").map(|s| format!(" :{}─", s)).unwrap_or_default();
                    
                    let stars = "*".repeat(*level as usize);
                    output.push_str(&format!("{} {}{}{}\n", stars, todo, self.render_inlines(content), tags));
                }
                Block::Paragraph { content, .. } => {
                    // 段落直接渲染,它在 Org 中自然属于上一个 Heading 之下(直到遇到下一个 Heading)
                    output.push_str(&format!("{}\n\n", self.render_inlines(content)));
                }
                // ... 处理其他块
                _ => {}
            }
        }
        output
    }
}  

工程结论

  • 选方案 A:你的核心 IR 极其轻量、扁平,编写 AST 转换插件(比如统计字数、替换所有链接)会变得无比简单,只需要一个简单的 for 循环或 map。
  • 隔离复杂度:让 Org-mode 的适配器自己去处理“拍平”和“复原”的逻辑。这是目前诸如 Pandoc 等成熟编译器项目的标准解法。