# YAML double-quoted 字符串内嵌真换行 → line folding 折叠成 space

**日期**：2026-05-17
**场景**：用 Python 生成 YAML 文件，sidebar.rows 每条希望含多行内容（动作 / 填写两段）。Python 字符串里用 `\n`（真 LF）作为段分隔，写入 YAML 后看似是多行：

```yaml
sidebar:
  rows:
    - "创建 PO，分配占位 SN
      状态(Build)=Ordered
      填写：FF 内部序列号..."
```

但 js-yaml 解析后，**多行被折叠成单个 space**：

```js
spec.sidebar.rows[0]  // → "创建 PO，分配占位 SN 状态(Build)=Ordered 填写：..."
                     //   ↑ LF 不见了，变成 space
```

renderer 用 `text.split('\n')` 按 LF 拆段就拆不开，整段挤成一行（仅靠 maxChars 自动换行，语义换行点丢失）。

## 根因

**YAML 1.1 spec 对 double-quoted scalar 内嵌真换行的处理**：
- 默认行为 = line folding（折行）
- 多个连续换行 → 单个 LF
- 单个换行 → 单个 space

这是为了让 YAML 作者可以用 `\n` 作为视觉换行写长字符串而不引入多余换行符。完全反直觉，但符合 spec。

## 验证

```bash
node -e "
const yaml = require('js-yaml');
const s = yaml.load(\`sidebar:\\n  rows:\\n    - \\\"foo\\nbar\\\"\\n\`);
console.log(JSON.stringify(s.sidebar.rows[0]));
// → \"foo bar\"  ← LF 变成 space！
"
```

要让 YAML double-quoted scalar 真正含 LF，必须写**字面 `\n`**（两字符 backslash+n）：

```yaml
- "foo\nbar"   # YAML 文件含字面 \n，parser 解码成 LF
```

但 Python 写文件时容易混淆：
- `'foo\nbar'`  → Python 字符串含 LF（1 字符），写入文件就是 LF（fold 成 space）
- `'foo\\nbar'` → Python 字符串含 backslash+n（2 字符），写入文件就是字面 `\n`（parser 解码成 LF）

并且实际项目里 Python `'\\n'` 在 heredoc / shell 转义里很容易再被吃一层 backslash，写到文件最后变成 LF。

## 修复（推荐 — YAML block scalar）

不用 double-quoted scalar，用 **block scalar `|-`**（literal block，preserve newlines, strip trailing）：

```yaml
sidebar:
  rows:
    - |-
      动作：创建 PO，分配占位 SN
      填写：FF 内部序列号、型号、采购单号、采购日期...
    - |-
      动作：跟进工厂生产进度
      填写：QC 状态
```

`|-` 修饰：
- `|`  literal block，**保留**内部换行
- `-`  strip trailing newline（不在末尾多一个 LF）

js-yaml 解析后 `s.sidebar.rows[0]` = `"动作：创建 PO，...\n填写：FF 内部序列号..."`（**真正含 LF**），renderer `split('\n')` 拆段正常。

Python 生成 block scalar：
```python
def yaml_block_scalar(text, indent='      '):
    return '|-\n' + '\n'.join(indent + l for l in text.split('\n'))

for action, fields in entries:
    rows_yaml.append('    - ' + yaml_block_scalar(f'动作：{action}\n填写：{fields}'))
```

## 工程化保险

**永远不要在 YAML double-quoted scalar 里靠真换行表达多行**。要么：
1. 用 `\n` 字面 escape（Python 里 `'\\n'`，并验证文件里是 backslash+n）
2. 用 block scalar `|` / `|-`（推荐）

写 YAML 生成器后，**先用 js-yaml / PyYAML 解析一遍确认换行幸存**：
```js
const parsed = yaml.load(content);
console.assert(parsed.sidebar.rows[0].includes('\n'), '换行丢失');
```

## 类似的 YAML 反直觉行为

- `>` folded scalar：所有换行折成 space，空行变 LF（"段落"模式）
- `>-` folded + strip trailing
- `|+` literal + keep trailing
- 顶层缩进对齐影响 block scalar 内容（缩进越多越裁剪）

YAML 字符串处理是一片雷区。**遇事不决用 block scalar `|-`**。
