Parser reliability

Markdown Table Parser Edge Cases and Repair Guide

Prepared and reviewed by the md2text maintainer. Parser-specific claims are checked against the public compatibility lab and automated regression suite.

Markdown tables become difficult to convert when the same character can mean two different things. A pipe may separate columns, appear literally in a status value, belong to inline code, or sit inside an ordinary sentence. A reliable workflow has to establish where a table starts, where it ends, and how many cells each row is expected to contain before it worries about border style.

This guide combines the parser edge cases and repair steps that matter most in real documentation. The behavior described here is checked against the production converter. You can inspect the source, generated output, metrics, and diagnostics for every case in the Parser Compatibility Lab.

Start with the separator row

A Markdown table is established by a header followed immediately by a valid separator row:

| Name | Status |
|---|---|
| API | Ready |

Each separator cell needs at least three dashes, with optional colons for alignment. These are valid:

|:---|:---:|---:|

This is not a valid separator:

| === | pending |

md2text deliberately leaves table-shaped text alone when the separator row is invalid. Requiring that structural signal prevents a shell command or ordinary paragraph containing | from being misidentified as a table.

Tables without leading and trailing pipes are still valid when the separator is clear:

Name | Score
--- | ---:
Alpha | 12

If detection fails, inspect the separator before adjusting whitespace elsewhere.

Keep literal pipes inside their cells

An escaped pipe belongs to the cell content:

| Status |
|---|
| success \| warning |

The converted value is success | warning. The escape marker disappears and the literal pipe does not create a second column.

Inline code needs similar treatment because commands and expressions often contain pipes:

| Example |
|---|
| `cat file | grep error` |

The pipe inside the backticks is code. Both `a | b` and `a \| b` remain one cell in md2text. This matters for shell commands, union types, regular expressions, and fallback expressions.

Markdown link labels can contain pipes too:

| Reference | Notes |
|---|---|
| [A | B migration](https://example.com) | Rollout guide |

The readable text output keeps the label and destination together as A | B migration (https://example.com). The pipe stays inside the label instead of shifting the row.

Protect code and prose boundaries

A fenced code block can contain table-shaped lines or shell pipes:

```sh
cat access.log | grep 500
```

That block is code, not a table. Keep its fences intact when pasting a mixed document. md2text removes the fence markers for plain-text output but preserves the code inside without running table detection on it.

Ordinary prose also stays ordinary unless the following line is a valid separator row:

Use A | B in prose, not as a table.

A pipe alone is not enough evidence. This conservative rule is important for support notes, deployment instructions, and command documentation where prose and shell syntax regularly share the same document.

End a table before prose begins

A particularly risky boundary appears when prose follows a table without a blank line:

| Key | Value |
|---|---|
| A | 1 |
Use A | B for fallback.

The final sentence has one pipe and could superficially look like a two-cell row. Its shape is prose-like, however, so md2text ends the table after A | 1, preserves the sentence in its original order, and emits an informational diagnostic:

Line 4: This looks like prose after a table. Consider adding a blank line after the table.

The Grid ASCII result is:

+-----+-------+
| Key | Value |
+-----+-------+
| A   | 1     |
+-----+-------+

Use A | B for fallback.

The conversion is not blocked because a useful result is still possible. Add a blank line after the table in the source when you can; explicit boundaries help future editors as well as parsers.

Repair inconsistent rows deliberately

The header establishes the expected column count. In this example, the body row is one cell short:

| Name | Role | Status |
|---|---|---|
| Sam | Reviewer |

md2text pads the missing cell in its best-effort output and reports the exact source line:

Line 3: Expected 3 columns, found 2. Add the missing cell separators or leave an empty cell such as `|  |`.

Use the warning to decide whether the blank was intentional. If it was, make the empty cell explicit:

| Sam | Reviewer |  |

If it was not, add the missing value. Diagnostics should point to uncertainty; they should not silently invent document meaning.

Intentional empty cells are supported across Grid ASCII, Unicode box, and Simple text:

| A | B | C |
|---|---|---|
| 1 |  | 3 |
|  | 2 |  |

Their positions remain stable because width and alignment are calculated per column after the row shape is normalized.

Check visible width, not string length

Plain-text alignment depends on terminal display width. JavaScript string length is not enough because CJK characters and many emoji occupy more visual space than ordinary ASCII characters.

| 名称 | 状态 |
|---|---|
| 表格 | 完成 |

md2text uses the same visible-width calculation for padding and the result health summary. That keeps alignment and the reported maximum line width consistent. The CJK and emoji width guide explains the tradeoffs in more detail.

After conversion, use the health status as a destination check:

Maximum visible widthStatusPractical meaning
80 or less80-col safeSuitable for many terminals and source comments
81 to 120May wrapPreview in the destination before publishing
More than 120Too wideShorten cells, split the table, or use Simple text

No width calculation can control a proportional email font. For destinations that support Markdown, copy the output as a fenced text block so spaces stay monospaced.

Preserve multiple tables in mixed documents

Documentation often contains prose, code, and more than one table. The converter should transform only the valid table blocks and preserve everything else:

Deployment summary

| Service | Status |
|---|---|
| API | Ready |

Notes for support follow here.

Owner | Queue
--- | ---
Platform | Review

md2text reports the total table count, total rows, maximum column count, and maximum visible output line. When there are multiple tables, it also exposes each table’s source line range and output separately so one table can be copied without taking the surrounding paragraphs.

This is safer than treating the entire document as one table-shaped block. It also makes row counts useful: an unexpected increase can reveal that prose was swallowed, while an unexpected decrease can reveal that a separator was invalid.

A repeatable repair workflow

Use this order when a table looks wrong:

  1. Find the header and verify the next line is a valid dash-and-colon separator.
  2. Count header cells, then compare each body row with that expected count.
  3. Escape literal pipes and check pipes inside inline code or link labels.
  4. Restore fenced code markers around commands and examples.
  5. Add blank lines where a table meets prose, another table, or a code block.
  6. Shorten long cells instead of forcing paragraphs into a fixed-width grid.
  7. Convert in all relevant output styles and inspect diagnostics and maximum line width.
  8. Preview the final text in the actual email client, terminal, comment box, or support tool.

This sequence starts with structure, then content, then destination fit. Randomly adding spaces rarely fixes the underlying ambiguity.

Verified behavior checklist

The maintained regression suite currently covers:

BoundaryExpected behavior
Escaped pipeLiteral pipe remains inside one cell
Inline code pipeCode expression remains one cell
Link label pipeLink text and URL remain readable
Fenced codeNo table is detected inside the fence
Ordinary prose pipeProse is preserved unchanged
Prose after tableTable ends and an informational diagnostic appears
No outer pipesValid separator still establishes the table
CJK and emojiVisible width drives padding and health metrics
Missing cellsBest-effort output plus a line-level warning
Multiple tablesEach table is converted independently
Invalid separatorSource remains ordinary text
Empty cellsColumn positions remain stable

See all 15 source fixtures and production-generated results in the Parser Compatibility Lab. The lab and automated tests share one data source, so the public examples cannot quietly drift away from the behavior being tested.

Convert your own table

Paste a Markdown table into the converter and choose ASCII grid, Unicode box, or Simple text output. Conversion runs locally in your browser.

Convert a Markdown table