ul-table: Markdown Tables Without New Syntax

ul-table is an HTML processor that lets you write tables as bulleted lists, in Markdown.

Table of Contents
Simple Example
About ul-table
Why?
Structure
Markdown → HTML → HTML Conversion
Details
Comparison: Tedious Inline HTML
Stylesheet
Adding HTML Attributes
Cells
Columns
Rows
Example: Markdown and HTML Inside Cells
Markdown Quirks to Be Aware Of
Comparisons
CommonMark Doesn't Have Tables
Github Tables are Awkward
Conclusion
Related Docs
Appendix: Implemention
Algorithm Notes
Appendix: Real Examples
HTML Quirks
Ideas for Features

Simple Example

To make this table:

Shell Version
bash 5.2
OSH 0.25.0

You write:

<table>

- thead
  - Shell
  - Version
- tr
  - [bash](https://www.gnu.org/software/bash/)
  - 5.2
- tr
  - [OSH](https://oils.pub/)
  - 0.25.0

</table>

Any Markdown processor will produce this:

And then our ul-table plugin transforms that into the table shown.

So the conversion takes 2 steps. The intermediate form is what sourcehut or Github will show, because they currently don't support ul-table.

This is good, because it means that ul-table degrades gracefully! You can use it anywhere without worrying about breakage.

About ul-table

Why?

Because it's tedious to read, write, and edit <tr> and <td> and </td> and </tr>. Aligning columns is also tedious in HTML.

Other design goals:

Structure

You make tables with a two-level Markdown list, between <table> tags. The top level list contains either:

thead zero or one, at the beginning
tr zero or more, after thead

The second level contains the contents of cells, but you don't write td or <td>.

(This format looks similar to tables in reStructuredText).

Markdown → HTML → HTML Conversion

As mentioned, it takes two steps to convert:

  1. Any Markdown translator will produce a <table> <ul> <li> ... </li> </ul> </table> structure.
  2. Our ul-table plugin transforms that into a <table> <tr> <td> </td> </tr> </table> structure, which is a normal HTML table.

So ul-table is an HTML processor, not a Markdown processor. But it's meant to be used with Markdown.

Details

Comparison: Tedious Inline HTML

Here's the equivalent in CommonMark:

<table>
  <thead>
    <tr>
      <td>Shell</td>
      <td>Version</td>
    </tr>
  </thead>
  <tr>
    <td>

<!-- be careful not to indent this 4 spaces! -->
[bash](https://www.gnu.org/software/bash/)

    </td>
    <td>5.2</td>
  </tr>
  <tr>
    <td>

[OSH](https://oils.pub/)

    </td>
    <td>0.25.0</td>
  </tr>

</table>

It uses the rule where you can embed Markdown inside HTML inside Markdown. With ul-table, you don't need this mutual nesting.

The ul-table text is also shorter!


Trivia: with CommonMark, you get an extra <p> element:

<td>
  <p>OSH</p>
</td>

ul-table can produce simpler HTML:

<td>
  OSH
</td>

Stylesheet

To make the table look nice, I add a <style> tag, inside Markdown:

<style>
table {
  margin: 0 auto;
}
td {
  padding-left: 1em;
  padding-right: 1em;
}
</style>

Adding HTML Attributes

HTML attributes like <tr class=foo> and <td id=bar> let you format and style your table.

You can add attributes to cells, columns, and rows.

Cells

Add cell attributes with a cell-attrs tag before the cell contents:

- thead
  - Name
  - Age
- tr
  - Alice
  - <cell-attrs class=num /> 42

It's important that cell-attrs is a self-closing tag:

<cell-attrs />  # Yes
<cell-attrs>    # No: this is an opening tag

How does this work? ul-table takes the attributes from <cell-attrs />, and puts it on the generated <td>.

Columns

Add attributes to every cell in a column the same way, except in the thead section:

- thead
  - Name
  - <cell-attrs class=num /> Age
- tr
  - Alice
  - 42     <!-- this cell gets class=num -->
- tr
  - Bob
  - 9      <!-- this cells gets class=num -->

This is particularly useful for aligning numbers to the right:

<style>
.num {
  text-align: right;
}
</style>

Example:

Name Age
Alice 42
Bob 9

If the same attribute appears in the thead and a tr section, the values are concatenated, with a space. Example:

<td class="from-thead from-tr">

Rows

Add row attributes like this:

- thead
  - Name
  - Age
- tr
  - Alice
  - 42
- tr <row-attrs class="special-row />
  - Bob
  - 9

Example: Markdown and HTML Inside Cells

Here's an example that uses more features. Source code of this table: doc/ul-table.md.

Shell Version Example Code
bash 5.2
echo sh=$bash
ls /tmp | wc -l
echo
dash 1.5 Inline HTML
mksh 4.0
HTML table inside
this table no way to re-enter inline markdown though?
zsh 3.6 Unordered List
  • one
  • two
yash 1.0 Ordered List
  1. one
  2. two

ksh

This is paragraph one.

This is paragraph two

Another cell with ...

... multiple paragraphs.

Markdown Quirks to Be Aware Of

Here are some quirks I ran into when creating ul-tables.

(1) CommonMark doesn't allow empty list items:

- thead
  -
  - above is not rendered as a list item

You can work around this by using a comment, or invisible character:

- tr
  - <!-- empty -->
  - above is OK
- tr
  - &nbsp;
  - also OK

As similar issue is that line breaks affect backtick expansion to <code>:

- tr
  - <cell-attrs /> <!-- we need something on this line -->
    ... More `proc` features ...

I think this is also because <cell-attrs /> doesn't "count" as text, so the list item is considered empty.

(2) Likewise, a cell with a literal hyphen may need a comment or space in front of it:

- tr
  - <!-- hyphen --> -
  - &nbsp; -

Comparisons

CommonMark Doesn't Have Tables

Related discussions:

Github Tables are Awkward

Github-flavored Markdown has an non-standard extension for tables:

This style is hard to read and write, especially with large tables:

| Command | Description |
| --- | --- |
| git status | List all new or modified files |
| git diff | Show file differences that haven't been staged |

Our style is less noisy, and more easily editable:

<table>

- thead
  - Command
  - Description
- tr
  - git status
  - List all new or modified files
- tr
  - git diff
  - Show file differences that haven't been staged

</table>

Conclusion

ul-table is a nice way of writing and maintaining HTML tables. The appendix has links and details.

Related Docs

Appendix: Implemention

Algorithm Notes

TODO: I would like someone to produce a DOM-based implementation!

Our implementation is pretty low-level. It's meant to avoid the "big load anti-pattern" (allocating too much), so it's a necessarily more verbose.

A DOM-based implementation should be much less than 1000 lines.

Appendix: Real Examples

I converted the tables in these September posts to ul-table:

The markup was much shorter and simpler after conversion!

TODO:

HTML Quirks

Ideas for Features

We could help users edit well-formed tables with enforced column names:

- thead
  - <cell-attrs ult-name=name /> Name
  - <cell-attrs ult-name=age /> Age
- tr 
  - <cell-attrs ult-name=name /> Hi
  - <cell-attrs ult-name=age /> 5

This is a bit verbose, but may be worth it for large tables.

Less verbose syntax idea:

- thead
  - <ult col=NAME /> <cell-attrs class=foo /> Name
  - <ult col=AGE /> Age
- tr 
  - <ult col=NAME /> Hi
  - <ult col=AGE /> 5

Even less verbose:

- thead
  - {NAME} Name
  - {AGE} Age
- tr 
  - {NAME} Hi
  - {AGE} 5

The obvious problem is that we might want the literal text {NAME} in the header. It's unlikely, but possible.

Generated on Fri, 27 Dec 2024 01:21:31 +0000