Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The dialect deviates from CommonMark for three reasons:
## Implemented syntax

- Core block syntax: paragraphs, ATX headings, thematic breaks, block quotes, ordered/unordered lists, indented code, raw HTML, link reference definitions.
- Tables: GFM/PHP Extra pipe tables with alignment. Use raw HTML for tables with row spans, column spans, or block content in cells. Table elements are included in the HTML subset.
- Tables: GFM/PHP Extra pipe tables with alignment. Prefer explicit widths such as `{: colwidths="1fr 3fr"}`. Unequal separator dash counts provide shorthand (`-|---` requests a 25% / 75% split); equal counts leave sizing automatic. Explicit `colwidths` overrides dash widths. Use raw HTML for tables with row spans, column spans, or block content in cells. Table elements are included in the HTML subset.
- GFM: task lists, `~~x~~` strikethrough, angle autolinks, and bare autolinks. Bare URL and email autolinking is on by default. Disable it with `bare_autolinks=False`. Explicit CommonMark angle autolinks remain enabled.
- Code: backtick/tilde fenced code blocks, info strings, and Pandoc-style code attributes.
- HTML-in-md: elements that `md` can emit, conventional phrasing tags (`u`, `kbd`, `b`, `i`, `ins`, `s`), and custom elements. Other tags render as literal text. `{=html}` raw blocks pass arbitrary HTML through.
Expand Down
10 changes: 7 additions & 3 deletions docs/DIALECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Fenced and indented code use `pre > code`. A language becomes a `language-*` cla

```markdown
| Feature | Status | Notes |
|:--------|:------:|------:|
|:------|:------:|------:|
| Tables | ready | yes |
```

Expand All @@ -214,7 +214,11 @@ MDHTML:

Pipe tables require a header. Cells use `align`; MDHTML deliberately retains `align` because it directly expresses the value converters need. Complex tables — row and column spans, block cell content, headerless bodies, footers — are written as raw HTML table soup, which is in the raw HTML subset; those cells carry `rowspan` and `colspan` as ordinary attributes.

A table may carry mixed fixed and proportional widths as a `colwidths` attribute (`colwidths="1.2in 1fr 2fr"`, on a pipe table via its caption or IAL, or directly on a raw `<table>`). Lengths fix columns; `fr` tracks share the remaining width; the HTML exporter lowers the attribute to a `colgroup`. A `width` attribute on the table (same spellings) lowers to an inline style width: a CSS length verbatim, a bare number as px, an invalid value left as a visible attribute; it merges after `colwidths`' lowering, so an explicit width beats its `width:100%`. Non-HTML exporters ignore both.
A table may carry mixed fixed and proportional widths as a `colwidths` attribute (`colwidths="1.2in 1fr 2fr"`, on a pipe table via its caption or IAL, or directly on a raw `<table>`). Lengths fix columns; `fr` tracks share the remaining width. The HTML exporter lowers the attribute to a `colgroup`; the Typst exporter uses the tracks directly.

Unequal dash counts in a pipe table's separator row are shorthand for proportional `colwidths`: `-|---` sets `colwidths="1fr 3fr"`. Only dashes count; alignment colons and surrounding whitespace do not affect widths. Equal dash counts leave sizing automatic. Use an explicit attribute such as `{: colwidths="1fr 1fr"}` to request equal widths. Explicit `colwidths` overrides inferred widths. Inference applies regardless of source line length or cell content. Markdown export writes widths as an explicit attribute rather than reconstructing separator lengths.

A `width` attribute on the table lowers to an inline style width: a CSS length verbatim, a bare number as px, an invalid value left as a visible attribute. It merges after `colwidths`' lowering, so an explicit width beats its `width:100%`. The Typst exporter ignores the table's `width` attribute.

## Definition lists and fenced divs

Expand Down Expand Up @@ -348,7 +352,7 @@ A caption line glued directly below a table becomes `caption`; its trailing attr

```markdown
| Stage | Days |
|:------|-----:|
|:------|------:|
| Ship | 3 |
: Delivery stages {#tbl-stages}
```
Expand Down
6 changes: 3 additions & 3 deletions docs/sample.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ <h2>Block quotes and rules</h2>
</blockquote>
<h2>Tables</h2>
<pre><code class="language-markdown">| Feature | Status | Notes |
|:--------|:------:|------:|
|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
Expand Down Expand Up @@ -306,7 +306,7 @@ <h2>Captions and figures</h2>
<pre><code class="language-markdown">![A cute puppy](puppy.jpg){#fig-diagram width="180"}

| Stage | Days |
|:------|-----:|
|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
Expand Down Expand Up @@ -378,7 +378,7 @@ <h2>Template tokens and filling</h2>
{{/office}}

| Item | Qty |
|------|-----|
|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
Expand Down
6 changes: 3 additions & 3 deletions examples/sample-clean.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Angle links work too: <https://example.com/spec>.
## Tables

| Feature | Status | Notes |
|:--------|:------:|------:|
|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
Expand Down Expand Up @@ -232,7 +232,7 @@ its trailing attribute list applies to the table:
![A cute puppy](puppy.jpg){#fig-diagram width="180"}

| Stage | Days |
|:------|-----:|
|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
Expand Down Expand Up @@ -286,7 +286,7 @@ The {{name}} office ({{city}}) handles this matter.
{{/office}}

| Item | Qty |
|------|-----|
|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
Expand Down
12 changes: 6 additions & 6 deletions examples/sample-render.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,14 @@ Angle links work too: <https://example.com/spec>.

`````markdown
| Feature | Status | Notes |
|:--------|:------:|------:|
|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
`````

| Feature | Status | Notes |
|:--------|:------:|------:|
|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
Expand Down Expand Up @@ -419,7 +419,7 @@ its trailing attribute list applies to the table:
![A cute puppy](puppy.jpg){#fig-diagram width="180"}

| Stage | Days |
|:------|-----:|
|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
Expand All @@ -428,7 +428,7 @@ its trailing attribute list applies to the table:
![A cute puppy](puppy.jpg){#fig-diagram width="180"}

| Stage | Days |
|:------|-----:|
|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
Expand Down Expand Up @@ -506,7 +506,7 @@ The {{name}} office ({{city}}) handles this matter.
{{/office}}

| Item | Qty |
|------|-----|
|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
Expand All @@ -523,7 +523,7 @@ The {{name}} office ({{city}}) handles this matter.
{{/office}}

| Item | Qty |
|------|-----|
|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
Expand Down
6 changes: 3 additions & 3 deletions examples/sample.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Angle links work too: <https://example.com/spec>.

`````markdown
| Feature | Status | Notes |
|:--------|:------:|------:|
|:------|:------:|------:|
| Tables | ready | aligned columns |
| Math | ready | brackets mode |
| HTML | ready | raw or markdown-enabled |
Expand Down Expand Up @@ -271,7 +271,7 @@ its trailing attribute list applies to the table:
![A cute puppy](puppy.jpg){#fig-diagram width="180"}

| Stage | Days |
|:------|-----:|
|:------|------:|
| Ship | 3 |
| Clear | 5 |
: Delivery stages {#tbl-stages}
Expand Down Expand Up @@ -333,7 +333,7 @@ The {{name}} office ({{city}}) handles this matter.
{{/office}}

| Item | Qty |
|------|-----|
|------|------|
{{#items}}
| {{name}} | {{qty}} |
{{/items}}
Expand Down
14 changes: 10 additions & 4 deletions src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1432,7 +1432,7 @@ impl<'a> ContainerBuilder<'a> {
let Some(header_line) = lines.last().cloned() else { return false };
let paragraph_len = lines.len();
let Some(header) = split_table_row(&header_line) else { return false };
let Some(aligns) = parse_table_separator(line) else { return false };
let Some((aligns, attrs)) = parse_table_separator(line) else { return false };
if header.len() != aligns.len() { return false; }
if self.record_trace {
let lead = self.cur_offset + (line.len() - line.trim_start().len());
Expand All @@ -1443,7 +1443,7 @@ impl<'a> ContainerBuilder<'a> {
}
let head = header.into_iter().map(|cell| cell.trim().to_string()).collect();
let table = BuildKind::Table {
attrs: Attr::default(),
attrs,
caption: None,
row_tokens: Vec::new(),
head: vec![draft_inline_table_row(head, &aligns)],
Expand Down Expand Up @@ -3003,23 +3003,29 @@ fn raw_table_cells(line: &str) -> Vec<String> {
cells
}

fn parse_table_separator(line: &str) -> Option<Vec<Align>> {
fn parse_table_separator(line: &str) -> Option<(Vec<Align>, Attr)> {
let cells = split_table_row(line)?;
let mut aligns = Vec::new();
let mut widths = Vec::new();
for cell in cells {
let c = cell.trim();
let left = c.starts_with(':');
let right = c.ends_with(':');
let dashes = c.trim_matches(':');
if dashes.is_empty() || !dashes.chars().all(|x| x == '-') { return None; }
widths.push(dashes.len());
aligns.push(match (left, right) {
(true, true) => Align::Center,
(true, false) => Align::Left,
(false, true) => Align::Right,
_ => Align::None,
});
}
Some(aligns)
let mut attrs = Attr::default();
if widths.windows(2).any(|w| w[0] != w[1]) {
attrs.set_pair("colwidths", widths.iter().map(|w| format!("{w}fr")).collect::<Vec<_>>().join(" "));
}
Some((aligns, attrs))
}

pub(crate) fn paragraph_interrupts(line: &str) -> bool { starts_block(line) || list_interrupts_paragraph(line) || def_marker(line).is_some() }
Expand Down
11 changes: 11 additions & 0 deletions tests/canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ fn panel_titles_and_nested_panels_roundtrip() {
assert!(!html.contains("<h2") && !html.contains("<h3"));
}

#[test]
fn table_dash_widths_roundtrip() {
let source = "| A | B |\n|:---|---------:|\n| x | y |\n";
let options = Options::default();
let document = parse(source, &options);
let html = render(&document);
assert!(html.contains(r#"colwidths="3fr 9fr""#));
let canonical = render_md(&document);
assert_eq!(render(&parse(&canonical, &options)), html);
}

#[test]
fn canonical_markdown_preserves_mdhtml_tree() {
let source = r#"---
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/dialect.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A paragraph with *em*, **strong**, ~~gone~~, H~2~O, [a link](https://example.com
[^n]: Footnote *body*.

| Left | Right | Center |
|:-----|------:|:------:|
|:------|------:|:------:|
| a | b | c |

Term
Expand Down
8 changes: 4 additions & 4 deletions tests/source/cmark-gfm/extensions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ textual alignment of the columns is shown to be irrelevant.
Hello!

| _abc_ | セン |
| ----- | ---- |
| ----- | ----- |
| 1. Block elements inside cells don't work. | |
| But _**inline elements do**_. | x |

Expand Down Expand Up @@ -81,7 +81,7 @@ Here we demonstrate some edge cases about what is and isn't a table.
| Not enough table | to be considered table |

| Just enough table | to be considered table |
| ----------------- | ---------------------- |
| ---------------------- | ---------------------- |

| ---- | --- |

Expand Down Expand Up @@ -149,7 +149,7 @@ the end:
Hello!

| _abc_ | セン |
| ----- | ---- |
| ----- | ----- |
| this row has a space at the end | |
| But _**inline elements do**_. | x |

Expand Down Expand Up @@ -181,7 +181,7 @@ Table alignment:

```````````````````````````````` example
aaa | bbb | ccc | ddd | eee
:-- | --- | :-: | --- | --:
:--- | --- | :---: | --- | ---:
fff | ggg | hhh | iii | jjj
.
<table>
Expand Down
2 changes: 1 addition & 1 deletion tests/source/cmark-gfm/spec.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3376,7 +3376,7 @@ they are. Likewise, use of leading and trailing pipes may be inconsistent:

```````````````````````````````` example table
| abc | defghi |
:-: | -----------:
:-: | -:
bar | baz
.
<table>
Expand Down
2 changes: 1 addition & 1 deletion tests/source/php-markdown-extra.mdtest/Tables.text
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Table alignement:
Table alignement (alternate spacing):

| Default | Right | Center | Left |
| --------- | :-------- | :-------: | --------: |
| ------- | :------- | :-------: | -------: |
| Long Cell | Long Cell | Long Cell | Long Cell |
| Cell | Cell | Cell | Cell |

Expand Down
24 changes: 24 additions & 0 deletions tests/test_focused.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,30 @@ def test_table_ial_line_attaches():
assert html.startswith('<table class="c">')
assert '{: .c}' not in html


def test_table_dash_widths():
from mdhtml import mdhtml2md
for sep, widths in (("|---|---------|", "3fr 9fr"), (" :--- | ---------: ", "3fr 9fr"), ("|:-:|---|", "1fr 3fr")):
src = f"| A | B |\n{sep}\n| x | [link](https://example.com) |\n"
html = md2mdhtml(src)
assert f'colwidths="{widths}"' in html
assert_html(html, md2mdhtml(src + f'{{: colwidths="{widths}"}}'))
assert_html(md2mdhtml(mdhtml2md(html)), html)
nested = "> - | A | B |\n> |:---|---------:|\n> | x | y |\n"
assert 'colwidths="3fr 9fr"' in md2mdhtml(nested)


def test_table_dash_widths_auto_and_explicit():
for sep in ("|---|---|", "|:---|---:|", "|:---:| --- |", "|------|------|"):
assert "colwidths" not in md2mdhtml(f"| A | B |\n{sep}\n| x | y |\n")
assert "colwidths" not in md2mdhtml("| A |\n|:-----:|\n| x |\n")
src = "| A | B |\n|---|---------|\n| x | y |\n"
ial = '{: colwidths="1fr 1fr"}'
for authored in (ial + "\n" + src, src + ial, src + ': Caption {colwidths="1fr 1fr"}'):
html = md2mdhtml(authored)
assert 'colwidths="1fr 1fr"' in html
assert 'colwidths="3fr 9fr"' not in html

def test_raw_attribute_blocks_and_inlines():
html = md2mdhtml('```{=docx}\n<w:br w:type="page"/>\n```\n')
assert html == '<script type="application/vnd.mdhtml.raw" data-format="docx"><w:br w:type="page"/>\n</script>\n'
Expand Down