CommonMark 見出しの仕様を理解したい

# CommonMark 見出しの仕様を理解したい

Markdown 見出しには以下の 2 つがある。これらは本家 Markdown [<sup>⧉</sup>][df-md-header] から継承されている。

[df-md-header]: https://daringfireball.net/projects/markdown/syntax#header "\# Header \- Daring Fireball: Markdown Syntax Documentation"

- ATX スタイル見出し (ATX headings)
- Setext スタイル見出し (Setext headings)

ATX や Setext: Structure Enhanced Text（シーテキスト）はともに計量マークアップ言語のこと。Markdown はこれらの計量マークアップ言語の影響を受けている。

これら見出しの CommonMark における仕様や違いを明らかにしたい。

:::note info
本記事では、記事の内容と判別が付かなくなることを避けるため、Markdown 見出しの結果を掲載していません。
:::

## ■ 用語の定義

Markdown 見出しのための用語を定義しておきたい。

- **見出しマーカー**

    エスケープされていない 1 つから 6 つの連続する `#`。ATX スタイル見出しに利用する。

- **見出し下線 (setext heading underline)**

    連続する 1 つ以上の `=` や `-`。Setext スタイル見出しに利用する。

- **見出し項目**

    見出しの内容。他のブロックは容認されず、テキストのみが内容として使われる。

    ```markdown
    見出しマーカー
    ↓
    ## Heading 2
        ↑
        見出し項目
        ↓
    Heading 2
    -----
    ↑ 見出し下線
    ```

- **見出し項目インデント**

    見出しマーカーと見出し項目との間に半角スペースやタブ。

    ```markdown
    ##   Heading 2
      ~~~
      ↑
      見出し項目インデント
    ```

    見出し項目インデントに使われる半角スペースやタブの量を **見出し項目インデント量** と呼ぶ。

- **装飾見出しマーカー**

    見出し項目の右側に置かれるエスケープされていない連続する `#` で構成される装飾のための見出しマーカー。見出し項目の右側に半角スペースやタブを挟んで置かれる。

    ```markdown
    ## Heading 2 ##
    ```

    装飾なので、あっても無くても良い。

## ■ ATX スタイル見出しの仕様 ##

1 つから 6 つの連続する `#` で表現される見出し。よく利用される。

ATX スタイル見出しの仕様は以下のようになっている。

- インデントは半角スペース 3 つまでが許容される
    - 半角スペースが 4 つ以上の場合、Indented code block に理解される
- 見出し項目には他のブロックを含めることは出来ない
- 空の見出し項目が許容される
- エスケープされていない 1 つから 6 つの連続する `#`
    - `#` の数が見出しレベルに対応している
- 見出し項目インデント量は少なくとも 1 つ以上挿入する必要がある
    - レンダリング後に見出し項目インデントは無視される
    - 見出し項目インデントが挿入されない場合、ハッシュタグとして理解されることがある
        ```markdown
        #Hashtag
        ```
- 装飾見出しマーカーの `#` の数は任意（見出しレベルは見出しマーカーで決定する）
    - レンダリング後になくなる
    - 見出し項目の右側に半角スペースやタブを挿入しない場合、装飾見出しマーカーはリテラルに理解される
    - 装飾見出しマーカーの右側に文字が挿入される場合、装飾見出しマーカーも含めてリテラルに理解される
        ```markdown
        <!-- 装飾見出しマーカーがリテラルに理解される例 -->
        ## Heading 2##
        ## Heading 2 ## header
        ```

CommonMark には ATX スタイル見出しの lazy 性への言及が無いようだが、ATX スタイル見出しは lazy continuation line を受け入れない。

```markdown
ここはパラグラフであり、見出しではない。
## 見出し
ここはパラグラフであり、見出しではない。
```

ただし、互換性から ATX スタイル見出しの前後には空白行を設けることが推奨される。

■ Setext スタイル
見出しの仕様
-----

- 空白行で中断されていない 1 行以上のテキストで構成される
    - 最初の行のインデントは半角スペース 3 つまでが許容される
    - 2 行目以降は任意の半角スペースやタブでインデントすることが出来る
- 見出し項目には他のブロックを含めることは出来ない
- 空の見出し項目は許容されない
- 見出し下線について
    - `=` は見出しレベル 1、`-` は見出しレベル 2
    - 見出しレベル 3 以降はない

reStructuredText では、`=` や `-` にこだわらず 6 種類の見出し下線（`#`、`*`、`=`、`-`、`^`、`"`）を利用することが出来る。その意味では、Markdown における Setext スタイル見出しは貧弱であるとも言える。

### ▽ Setext スタイル見出しの lazy 性

Setext スタイル見出しは上方向に lazy 性があるため、複数行を見出しにすることが出来る。

```markdown
ここの行は
すべて
見出し
=====
```

Qiita では複数行に渡る場合、強制改行と見なされるため、複数行に渡る見出しがレンダリングされる。[<sup>[要参照]</sup>](#-setext-スタイル見出しの仕様)

一方で、Setext スタイル見出しは上方向への、ブロック引用やリストの下方向への lazy 性があるため、これらには競合が生じる。これは以下の例に見るように、Setext スタイル見出しよりもブロック引用やリストの lazy 性が強い。

```markdown
> ここはブロック引用
ここは何になる？
ここは見出しのつもり
===

- ここはリスト項目
ここは何になる？
ここは見出しのつもり
---
```

> ここはブロック引用
ここは何になる？
ここは見出しのつもり
===

- ここはリスト項目
ここは何になる？
ここは見出しのつもり
---

Setext スタイル見出しよりもブロック引用やリストが優先されるため、連続する `-` は水平線 (Thematic breaks) となる。

## ■ ATX スタイルと Setext スタイルの比較

上で示した仕様を比較しておきたい。

|                      | ATX スタイル | Setext スタイル |
| :------------------- | :----------: | :-------------: |
| 見出しレベル         |    1 \~ 6    |     1 \~ 2      |
| ブロック要素の包含   |     不可     |      不可       |
| インライン要素の包含 |      可      |       可        |
| layz 性              |     なし     |      あり       |
| 空の見出し項目       |      可      |      不可       |

# 参考

- [CommonMark Spec](https://spec.commonmark.org/0.30/)
    - [§ 4.2 ATX headings](https://spec.commonmark.org/0.30/#atx-headings)
    - [§ 4.3 Setext headings](https://spec.commonmark.org/0.30/#setext-headings)
- [atx, the true structured text format](http://www.aaronsw.com/2002/atx/intro.html)（UTF-8 や Shift_JIS で閲覧する場合、一部文字化けが生じます。Macintosh に変更して閲覧すると文字化けが解消されます。）
- [Setext Documents Mirror](https://docutils.sourceforge.io/mirror/setext.html)
- [Setext \- Wikipedia](https://en.wikipedia.org/wiki/Setext)

文字化けを解消するには、[jinliming2/Chrome-Charset][charset] などのブラウザ拡張機能を利用すると良いだろう。

[charset]: https://github.com/jinliming2/Chrome-Charset "An extension used to modify the page default encoding for Chromium 55+ based browsers."

# 余談

markdown-it、Marked\.js、Remark\.js、commonmark\.js の 4 つで確認していたが、すべてで Setext スタイル見出しの取り扱いが CommonMark の仕様通りになっているわけではなかった。ATX スタイル見出しを活用する方が安定するだろう。

Setext スタイル見出しを使っている人をあまり見たことがないけれど、どれくらいの人が使っているのだろう。

## GitHub のハッシュタグとその回避

GitHub では、issue の番号を使って `#1` などとすると issue 1 に自動的にリンクされる。これは ATX スタイル見出しの仕様にもあったハッシュタグとして見なされる例である。

このハッシュタグの仕様について GitHub を調べても、以下のようなページを確認できるのみで、仕様についてはまったく示されていない。

> [Autolinked references and URLs - GitHub Docs](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/autolinked-references-and-urls#issues-and-pull-requests)

このハッシュタグについて、このリンクを回避したい場合、次のような策は上手く機能しない。

- `#` を `\` でエスケープする (e.g. `\#1`)
- HTML エンティティを利用する
    - `#` を変換する (e.g. `&num;2`, `&#35;3`, `&#x23;4`)
    - issue 番号を変換する (e.g. `#&#53;`)

うまく回避するには、`#` または issue 番号を全角にする。以下の例から確認することが出来る。

```markdown
\#1
&num;2
&#35;3
&#x23;4
#&#53;
#&#x36;
&num;&#55;
↑ リンクが作成される
↓ リンクが作成されない
＃8
#９
```

![GitHub-issue-hashtag](https://i.gyazo.com/ce0cb5480b062bb499f223683904a638.png)

<!--
## ■ Twitter

Twitter では任意の文字列をハッシュタグにすることで関連するツイートをまとめることが出来る。

ツイート内では HTML エンティティ参照が利用できないため、ハッシュタグを回避するには `♯` するしかない。あるいは ` 井 `。

```
#ツイート
＃ツイート
♯ツイート
井ツイート
```

最大文字数は 100 字となっている。
-->