Skip to content

Escape pipes when writing a CoefTable as markdown - #1007

Merged
andreasnoack merged 1 commit into
masterfrom
an/escape-markdown-pipes
Aug 24, 2026
Merged

Escape pipes when writing a CoefTable as markdown#1007
andreasnoack merged 1 commit into
masterfrom
an/escape-markdown-pipes

Conversation

@andreasnoack

@andreasnoack andreasnoack commented Aug 21, 2026

Copy link
Copy Markdown
Member

show(::IO, ::MIME"text/markdown", ::CoefTable) never escapes |, so any cell containing one silently destroys the table. Downstream packages hit this with the R-style Pr(>|z|) p-value header (GLM, MixedModels, and others build their coefficient tables with it), which is why it is easy to reproduce but invisible from inside StatsBase — no header or fixture here contains a pipe.

Reproduction

julia> ct = CoefTable(Any[[0.1615], [0.0499], [3.24], [0.0012]],
                      ["Coef.", "Std. Error", "z", "Pr(>|z|)"], ["Age"], 4, 3);

julia> md = sprint(show, MIME"text/markdown"(), ct);

julia> only(Markdown.parse(md).content)   # expected a Table
Markdown.Paragraph(...)

The header row carries the two extra pipes from Pr(>|z|), so it presents 10 pipe-delimited fields against the alignment row's 8. No markdown parser accepts that as a table; Julia's degrades it to a paragraph and then applies inline typography to the --- rules, turning them into en-dashes. Rendered through Documenter or any markdown viewer, the whole table appears as one run of pipes and dashes.

Fix

Escape | in row names, column names and string cells — the same rule the Markdown stdlib applies in plain(::IO, ::Markdown.Table) — before the column widths are measured, so the widths stay consistent with what is printed.

Added a test covering a pipe in a header, a row name and a string cell, asserting that every row presents the same number of cells and that the output parses as a Markdown.Table. It fails without the fix. The two existing golden-output markdown tests are unchanged, since neither contains a pipe.

Related

#1008 is an alternative that goes further: it drops the hand-rolled writer in favour of building a Markdown.Table, which removes this class of bug rather than this instance of it. That one changes the emitted spacing and adds Markdown as a dependency, so it is offered separately rather than folded in here.

🤖 Generated with Claude Code

An unescaped `|` ends the cell it sits in, so a row containing one presents
more cells than the alignment row and the table stops parsing as a table at
all. Downstream packages hit this with the R-style `Pr(>|z|)` p-value header:
the header row then has 10 pipe-delimited fields against the alignment row's
8, so Julia's Markdown parser degrades the whole table to a paragraph (and
turns the `---` rules into en-dashes on the way).

Escape `|` in row names, column names and string cells — the same rule the
Markdown stdlib applies in `plain(::IO, ::Markdown.Table)` — before the column
widths are measured, so the widths stay consistent with what is printed.
@andreasnoack
andreasnoack force-pushed the an/escape-markdown-pipes branch from 7cf6a81 to c0defdf Compare August 21, 2026 13:47

@palday palday left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andreasnoack care to patch bump and release this right away? I'll take a look at #1008 when I get some time later in the week

@andreasnoack
andreasnoack merged commit cfe5604 into master Aug 24, 2026
10 checks passed
@andreasnoack
andreasnoack deleted the an/escape-markdown-pipes branch August 24, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants