Step 1 of 5 · Reading · ~3 min
Read
Production Concerns
GFM Tables
GitHub Flavored Markdown extends CommonMark with pipe tables:
| Name | Age | City |
|-------|-----|---------|
| Alice | 30 | NYC |
| Bob | 25 | Berlin |
This renders to a <table> with a <thead> and a <tbody>. Tables are NOT in the CommonMark core; they are a GFM extension. Most modern parsers (cmark-gfm, markdown-it, pulldown-cmark) implement them behind a flag. Your program will convert a document of paragraphs and tables, following the spec's own rules for the awkward cases.
Anatomy of a table
| Part | What it is |
|---|---|
| Header row | the first row, cells separated by |; leading and trailing pipes are optional |
| Delimiter row | the second row, one cell per column, each cell dashes with optional colons |
| Body rows | every following line, up to the first blank line |
The delimiter row is what disambiguates a table from a plain paragraph that happens to contain |. It must sit directly under the header and must have the same number of cells, or the whole thing is not a table and the lines are an ordinary paragraph. A single | in text (a | b) is not a table either, because there is no delimiter row after it.
Alignment
The delimiter cells control column alignment, and the alignment is written on the <th> and on every <td> in that column:
| Delimiter cell | Alignment | Attribute |
|---|---|---|
--- | none | no attribute |
:-- | left | align="left" |
--: | right | align="right" |
:-: | center | align="center" |
A delimiter cell needs at least one dash (GFM allows a single -), with a colon at either or both ends. : alone and --x are not delimiter cells.
Awkward rows, in the spec's words
The spec is generous about the body and strict about the header:
| Situation | Behaviour |
|---|---|
| a body row has fewer cells than the header | the missing cells are empty <td></td> |
| a body row has more cells | the extra cells are ignored |
| a body line has no pipe at all | it is still a row; it fills the first cell |
| a pipe inside a cell | write |; it renders as a literal ` |
| a table with a header and no body rows | <thead> only; no <tbody> at all |
| a blank line | ends the table |
Splitting is therefore a small scanner and not a plain split: walk the line, treat \| as a literal character, treat any other | as the end of a cell, and remember that a pipe at the very end of the line closes the last cell without opening a new, empty one. Trim every cell, and escape the cell text for HTML (&, <, >, ") when you print it.
A worked trace
| code | note |, |---|---|, | a \| b | x < y |:
| Step | Result |
|---|---|
| split header | code, note (2 cells) |
| split delimiter | ---, ---: 2 cells, both valid, no alignment |
| split body row | `a |
| `<td>a |
Common mistakes
- Not requiring the same cell count.
| a | b |over|---|is a paragraph, not a one-column table. - Splitting on every
|. An escaped pipe belongs to its cell, and a pipe at the end of the line does not create an empty cell. - Printing an empty
<tbody>. With no body rows there is none. - Forgetting alignment on body cells. It goes on every
<td>too.
Your exercise
Convert the document. The starter provides escape_html, the blank-line test and the output helper for a cell; you write split_row, alignments (the delimiter-row check) and convert. Lines that are not part of a table form paragraphs, joined with single spaces.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…