Skip to content

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

PartWhat it is
Header rowthe first row, cells separated by |; leading and trailing pipes are optional
Delimiter rowthe second row, one cell per column, each cell dashes with optional colons
Body rowsevery 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 cellAlignmentAttribute
---noneno attribute
:--leftalign="left"
--:rightalign="right"
:-:centeralign="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:

SituationBehaviour
a body row has fewer cells than the headerthe missing cells are empty <td></td>
a body row has more cellsthe extra cells are ignored
a body line has no pipe at allit is still a row; it fills the first cell
a pipe inside a cellwrite |; it renders as a literal `
a table with a header and no body rows<thead> only; no <tbody> at all
a blank lineends 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 |:

StepResult
split headercode, note (2 cells)
split delimiter---, ---: 2 cells, both valid, no alignment
split body row`a
print`<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.

Up nextTwo-Phase Parser ArchitectureProduction Concerns

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…