深藏若虛

Markdown 表格語法原則

Principle of Markdown Table Syntax

緣起

儘管 Markdown 語法中的表格不在最初的標準裡,但近來多數 Markdown 的實作都有擴充產生表格的語法。雖然這些擴充讓使用 Markdown 編寫文章更加多元,不過 Markdown 的表格語法在繪製上仍是有點麻煩,尤其是最近自己許多文章都需要使用表格輔助呈現,不斷的手刻 Markdown 表格實在是很耗費心力與時間,所以就在想說何不自己實作一個 CSV 資料格式和 Markdown Table 語法的轉換器!而在實作轉換器之前,了解 Markdown 主流實作對於 Table 語法的定義就很重要,遂有這篇文章作為實驗筆記與總結。

本篇文章會以符合下列兩種實作為主:

  1. PHP Markdown Extra: WordPress Jetpack 採用的實作,也就等於本部落採用的實作。
  2. GitHub Flavored Markdown: Github 在 Markdown 標準上提出的擴充。

摘要(TL;DR)

  • Markdown Table 的組成以是否有標題列分隔列做為判定,資料列的有無不影響其判定。
  • 表格的欄位數以標題列的欄位數作為基準,有少則補。有多的,PHP 是直接判斷為文字,GFM 則是隱藏。
  • 分隔列每欄至少要有三個 - (dash),可在 - 的最左最右插入 : 指定文字對齊方式, : 也算在最少三個 - 的數量裡。
  • 欄位以 | (pipe) 劃分,表格外側可有可不有,但若只有單一欄位,則每欄至少要有一個 |
  • 欄位裡,文字與 | 之間的空白符號對程式來說無意義,但對於編寫者能加強可讀性。

解說與展示

基本構成

標準的 Markdown 表格應該長得像是這樣:

Age           | Time  | Food | Gold | Requirement
--------------|:-----:|-----:| ----:|------------------------
Feudal Age    | 02:10 |  500 |    0 | Dark Age building x 2
Castle Age    | 02:40 |  800 |  200 | Feudal Age building x 2
Imperial Age  | 03:30 | 1000 |  800 | Castle Age building x 2    

顯示結果如下:

Age Time Food Gold Requirement
Feudal Age 02:10 500 0 Dark Age building x 2
Castle Age 02:40 800 200 Feudal Age building x 2
Imperial Age 03:30 1000 800 Castle Age building x 2

其組成元素有三:標題列分隔列資料列

  1. 第一列為標題列
  2. 第二列為分隔列
  3. 第三列與之後都是資料列
Age           | Time  | Food | Gold      <--標題列 (必要)
--------------|-------|------|------     <--分隔列 (必要)
Feudal Age    | 02:10 |  500 |    0      <--資料列 (選填)
Castle Age    | 02:40 |  800 |  200
Imperial Age  | 03:30 | 1000 |  800 

標題列和分隔列是必要存在,可以沒有資料列,但若沒有標題和分隔列,則不會被視為一個 Makrdown 表格,例如下面幾種都不會被判定為 Markdown 表格:

# 錯誤:沒有標題列

--------------|:-----:|-----:| ----:|------------------------
Feudal Age    | 02:10 |  500 |    0 | Dark Age building x 2
Castle Age    | 02:40 |  800 |  200 | Feudal Age building x 2
Imperial Age  | 03:30 | 1000 |  800 | Castle Age building x 2


# 錯誤:沒有分隔列

Age           | Time  | Food | Gold | Requirement
Feudal Age    | 02:10 |  500 |    0 | Dark Age building x 2
Castle Age    | 02:40 |  800 |  200 | Feudal Age building x 2
Imperial Age  | 03:30 | 1000 |  800 | Castle Age building x 2

欄位數量

欄位數量由標題列的欄位數決定的。標題列有幾欄,資料列就會顯示幾欄,少的補空,有多的,PHP 是直接判斷為文字,GFM 則是隱藏。。

State   | Age           | Time  | Food 
------- | --------------|-------|------|------|------------------------
More    | Feudal Age    | 02:10 |  500 |    0 | Dark Age building x 2
Conform | Castle Age    | 02:40 |  800 
Less    | Imperial Age
State Age Time Food
More Feudal Age 02:10 500 | 0 | Dark Age building x 2
Conform Castle Age 02:40 800
Less Imperial Age

水平分隔列

分隔線列中,每欄的水平分隔符號數量至少要有 3 個,3 個以上無論多寡皆不影響。

正確

Age           | Time  | Food | Gold | Requirement
------------  | ----  | ---  | ---  | ---
Feudal Age    | 02:10 |  500 |    0 | Dark Age building x 2
Castle Age    | 02:40 |  800 |  200 | Feudal Age building x 2
Imperial Age  | 03:30 | 1000 |  800 | Castle Age building x 

錯誤

Age            | Time  | Food | Gold | Requirement
------- -------|-------|---   |  --- | --
Feudal Age     | 02:10 |  500 |    0 | Dark Age building x 2
Castle Age     | 02:40 |  800 |  200 | Feudal Age building x 2
Imperial Age   | 03:30 | 1000 |  800 | Castle Age building x 2

第一欄被空白中斷                          第五欄少於三個。

文字對齊

預設的文字對齊方式是標題列置中,資料列靠左。若要改變對其方式可以在分隔列的各欄位插入 : 作為文字位置的指示符號。在分隔符號 - 最左插入 : 就是靠左,若是在最右插入就是靠右,若兩邊都有就是置中,並且會一併套用在標題列和資料列。

Default    | Left       | Center     | Right
-----------| :--------- | :--------: | ---------: 
         x |          x |          x |          x 
xxxxxxxxxx | xxxxxxxxxx | xxxxxxxxxx | xxxxxxxxxx

效果如下表:

Default Left Center Right
x x x x
xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx

另外 : 是可以算進分隔符號最低數量要求的,像是下面的語法是可以正確顯示的:

Default    | Left       | Center     | Right
---        | :--        | :-:        | --: 
         x |          x |          x |          x 
xxxxxxxxxx | xxxxxxxxxx | xxxxxxxxxx | xxxxxxxxxx

垂直分隔符號

在 Markdown 裡,是以 | 作為垂直分隔符號來區分欄位,主要是需要放置在兩欄之間,至於外側的垂直分隔符號可放可不放,但若只有一欄,則每列至少一定要有一個垂直分隔符號。

下方的範例展示外側垂直分隔線都是正確的。

# 外側皆有

| Age           | Time  | Food | Gold |
| ------------  | ----  | ---  | ---  |
| Feudal Age    | 02:10 |  500 |    0 |
| Castle Age    | 02:40 |  800 |  200 |
| Imperial Age  | 03:30 | 1000 |  800 |


# 左側有、右側沒有

| Age           | Time  | Food | Gold 
| ------------  | ----  | ---  | ---  
| Feudal Age    | 02:10 |  500 |    0 
| Castle Age    | 02:40 |  800 |  200 
| Imperial Age  | 03:30 | 1000 |  800 


# 右側有、左側沒有

Age             | Time  | Food | Gold |
--------------- | ----  | ---  | ---  |
Feudal Age      | 02:10 |  500 |    0 |
Castle Age      | 02:40 |  800 |  200 |
Imperial Age    | 03:30 | 1000 |  800 |


# 兩側皆無

Age             | Time  | Food | Gold 
--------------- | ----  | ---  | ---  
Feudal Age      | 02:10 |  500 |    0 
Castle Age      | 02:40 |  800 |  200 
Imperial Age    | 03:30 | 1000 |  800 


# 外側時有時無

  Age           | Time  | Food | Gold
| ------------  | ----  | ---  | ---  |
  Feudal Age    | 02:10 |  500 |    0 |
| Castle Age    | 02:40 |  800 |  200
  Imperial Age  | 03:30 | 1000 |  800

空白不影響排版

使用空白排版 Markdown 表格的好處是讓編寫者好懂,但對於程式來說有沒有空白都不影響程式的判讀。下面兩種表表格顯示的結果是一樣的:

# 有用空白排版

Age             | Time  | Food | Gold 
--------------- | ----  | ---  | ---  
Feudal Age      | 02:10 |  500 |    0 
Castle Age      | 02:40 |  800 |  200 
Imperial Age    | 03:30 | 1000 |  800 


# 沒有用空白排版

Age|Time|Food|Gold 
---|---|---|---  
Feudal Age|02:10|500|0 
Castle Age|02:40|800|00 
Imperial Age|03:30|1000|800 

總結

有了這些語法的認知,就可以開始著手寫轉換工具了。比較麻煩的部分大概是在垂直分隔符號的彈性導致判讀欄位需要多花點心思吧。

個人比較喜歡的排版方式大致是外側無垂直分隔符號,垂直分隔符號與文字中間至少隔一個空白,並且排版成每列欄位等寬。比較細節的部分大概就是會將文字靠左,數字與時間靠右。

整體來說也就是本文最上面語法展示那樣。所以可能比較難在現成的套件庫裡找到完全符合我的需求,這也就是我為什麼打算自己手刻的原因囉。

最後希望這篇文章能幫助讀者對於 Markdown Table 語法細節的認知。


Information Technology , ,