markdown-table-in-containers

このページは FormatArc のブログ記事が引用している実測の生データです。検索結果には表示されません (noindex)。

Files

FileSize
README.md4.7 KB
measure.mjs5.5 KB
package-lock.json49.1 KB
package.json341 B
results.json3.4 KB

README.md

# 表を容器(リスト項目 / 引用ブロック)の中に置いたときの実測

`markdown-table-broken.md` の症状 1 に追加した 2 つの H3「リスト項目の中に置いた表」「引用ブロックの中に置いた表」の一次データ。既存の `scripts/benchmarks/markdown-table-parsers/` は表それ自体の構文(列数・空行・ダッシュ本数)を対象にしていたが、そちらには「表を箇条書きや引用ブロックの中に埋め込んだときにどうなるか」のケースがなかった。

## なぜ測ったか

箇条書きの項目や引用ブロックの中に表を貼るのは実務でよくある書き方だが、CommonMark のリスト継続規則・引用ブロックの `>` 省略規則は表と組み合わさると直感に反する結果を生む。とくに「引用ブロックのヘッダー行と区切り行にだけ `>` を付ける」ケースは、表自体は描画されるのにデータ行だけが黙って消えるという、エラーも警告も出ない静かな不具合になる。既存記事のどのケースにも実測が無かったため、本サイトの Markdown レンダリングパイプライン(`lib/tooling.ts` が使う remark + remark-gfm + remark-rehype + rehype-stringify)で直接測定した。

## 測定対象

`remark-parse` + `remark-gfm` 4.0.1 + `remark-rehype` + `rehype-stringify` のパイプライン(本サイトの CSV → Markdown 整形パイプラインと同じ組み合わせ)。GitHub API や marked は対象外(この検証は本サイト自身のレンダリング結果を確認するものであり、パーサー間比較は `markdown-table-parsers/` が担当する)。

## 成果物

| ファイル | 内容 |
| --- | --- |
| `measure.mjs` | 再現用スクリプト(`npm run measure`)。12 ケースの Markdown 入力を定義し、各ケースの HTML 出力から `rendersAsTable` / `insideListItem` / `hasDataRow` を判定する |
| `results.json` | 12 ケースの入力・判定結果・実行環境メタ(Node バージョン、依存パッケージバージョン、実行日時) |

## ケースと実測結果

`- item`(マーカー幅 2 = `"- "`)配下の箇条書き:

| ケース | 表になるか | `<li>` の中か |
| --- | --- | --- |
| 字下げ 0、空行なし | ならない | — |
| 字下げ 0、空行あり | なる | 入らない(`<ul>` の外に出る) |
| 字下げ 1、空行なし | ならない | — |
| 字下げ 1、空行あり | なる | 入らない |
| 字下げ 2、空行なし | なる | 入る |
| 字下げ 2、空行あり | なる | 入る |

`1. item`(マーカー幅 3 = `"1. "`)配下の番号付きリスト:

| ケース | 表になるか | `<li>` の中か |
| --- | --- | --- |
| 字下げ 0、空行あり | なる | 入らない |
| 字下げ 2、空行あり | なる | 入らない |
| 字下げ 3、空行あり | なる | 入る |

引用ブロック(3 行の最小テーブルに `>` をどこまで付けるか):

| ケース | 表になるか | データ行 |
| --- | --- | --- |
| `>` が 1 行目だけ | ならない(3 行が 1 段落に潰れる) | — |
| `>` がヘッダー行と区切り行だけ | なる | **消える**(`<thead>` のみ。データ行は引用ブロックの外の段落として残る) |
| `>` が全行 | なる | 残る |

リストの結論は、リスト項目の継続行として認識されるにはマーカー幅(`"- "` なら 2、`"1. "` なら 3)と同じかそれ以上の字下げが要ることに一致する。マーカー幅未満の字下げは、空行が無ければ直前の段落に継続行として吸収されて表にならず、空行があればリストを終了させて表自体はリストの外側の兄弟ブロックとして描画される。

引用ブロックの結論で新規性があるのは「ヘッダー行と区切り行にだけ `>` を付ける」ケースで、GFM のテーブル拡張は 3 行のうち最初の 2 行だけで表と認識するため `<table>` は生成されるが、`>` の無いデータ行は引用ブロックに属さず、通常の段落テキストとして表の外に落ちる。エラーも警告も出ない。

## 再現手順

```bash
cd scripts/benchmarks/markdown-table-in-containers
npm install
npm run measure    # results.json を上書き
```

## 測定していないこと

- GitHub 本番レンダラー・marked での挙動(このディレクトリはパーサー間比較を目的としていない。パーサー差は `markdown-table-parsers/` を参照)
- 3 階層以上のネスト(リストの中の引用ブロック、引用ブロックの中のリストなど)
- Obsidian や Notion など、remark-gfm を使わない実装での挙動

results.json

{
  "generatedAt": "2026-08-16T01:42:28.464Z",
  "environment": {
    "node": "v26.7.0",
    "platform": "darwin/arm64",
    "dependencies": {
      "remark-gfm": "4.0.1",
      "remark-parse": "11.0.0",
      "remark-rehype": "11.1.2",
      "rehype-stringify": "10.0.1",
      "unified": "11.0.5"
    }
  },
  "results": [
    {
      "case": "1) unordered list, indent 0, no blank line",
      "input": "- item\n| A | B |\n| --- | --- |\n| 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": false,
        "insideListItem": "n/a (no table)",
        "hasDataRow": false
      }
    },
    {
      "case": "2) unordered list, indent 0, blank line",
      "input": "- item\n\n| A | B |\n| --- | --- |\n| 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "outside list",
        "hasDataRow": true
      }
    },
    {
      "case": "3) unordered list, indent 1, blank line",
      "input": "- item\n\n | A | B |\n | --- | --- |\n | 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "outside list",
        "hasDataRow": true
      }
    },
    {
      "case": "4) unordered list, indent 2, no blank line",
      "input": "- item\n  | A | B |\n  | --- | --- |\n  | 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "inside <li>",
        "hasDataRow": true
      }
    },
    {
      "case": "5) unordered list, indent 2, blank line",
      "input": "- item\n\n  | A | B |\n  | --- | --- |\n  | 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "inside <li>",
        "hasDataRow": true
      }
    },
    {
      "case": "6) unordered list, indent 1, no blank line",
      "input": "- item\n | A | B |\n | --- | --- |\n | 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": false,
        "insideListItem": "n/a (no table)",
        "hasDataRow": false
      }
    },
    {
      "case": "7) ordered list, indent 0, blank line",
      "input": "1. item\n\n| A | B |\n| --- | --- |\n| 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "outside list",
        "hasDataRow": true
      }
    },
    {
      "case": "8) ordered list, indent 2, blank line",
      "input": "1. item\n\n  | A | B |\n  | --- | --- |\n  | 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "outside list",
        "hasDataRow": true
      }
    },
    {
      "case": "9) ordered list, indent 3, blank line",
      "input": "1. item\n\n   | A | B |\n   | --- | --- |\n   | 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "inside <li>",
        "hasDataRow": true
      }
    },
    {
      "case": "10) blockquote, > on first line only",
      "input": "> | A | B |\n| --- | --- |\n| 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": false,
        "insideListItem": "n/a (no list)",
        "hasDataRow": false
      }
    },
    {
      "case": "11) blockquote, > on header + separator only",
      "input": "> | A | B |\n> | --- | --- |\n| 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "n/a (no list)",
        "hasDataRow": false
      }
    },
    {
      "case": "12) blockquote, > on every line",
      "input": "> | A | B |\n> | --- | --- |\n> | 1 | 2 |\n",
      "verdict": {
        "rendersAsTable": true,
        "insideListItem": "n/a (no list)",
        "hasDataRow": true
      }
    }
  ]
}