Skip to content

Fix: keep trailing content out of an unmatched emphasis closer (#743) - #950

Open
dualfroz wants to merge 1 commit into
xoofx:mainfrom
dualfroz:fix/emphasis-trivia-roundtrip-743
Open

Fix: keep trailing content out of an unmatched emphasis closer (#743)#950
dualfroz wants to merge 1 commit into
xoofx:mainfrom
dualfroz:fix/emphasis-trivia-roundtrip-743

Conversation

@dualfroz

@dualfroz dualfroz commented Sep 5, 2026

Copy link
Copy Markdown

Problem

Fixes #743

Problem

Since v28.0, parsing a grid-table-like block with EnableTrackTrivia() and then
rendering it back with RoundtripRenderer produces invalid output. The reported
input round-trips cleanly up to v27.0 but is corrupted from v28.0 onward:

Input:

+---------------+---------------+--------------------+
| Fruit         | Price         | Advantages         |
+===============+===============+====================+
| Bananas       | first line    | first line         |
|               | next line     | next line          |
+---------------+---------------+--------------------+

Corrupted round-trip output (the middle separator column collapses to a single
= and a stray run of = is appended after the last line):

+---------------+---------------+--------------------+
| Fruit         | Price         | Advantages         |
+===============+=+====================+
| Bananas       | first line    | first line         |
|               | next line     | next line          |
+---------------+---------------+--------------------+==============

Root cause

The block is a plain paragraph (the reporter's pipeline does not enable grid
tables). The +===...===+ separators are runs of =, which the EmphasisExtras
"marked" delimiter (==, minimum count 2) parses as emphasis.

EmphasisInlineParser.ProcessEmphasis matches an opener run against a closer run.
Each match embraces everything that follows the opener - including the closer and
every inline after it - and later moves the trailing content back out. That
"move out" step only runs when the closer is fully consumed (DelimiterCount == 0)
or when the opener is fully consumed. When both the opener and the closer keep
leftover delimiter characters that fall below the minimum count (e.g. a run of 15
= is 7 pairs plus one leftover), the trailing content stays nested inside the
innermost emphasis. The roundtrip renderer then writes the closing delimiters
after that trailing content, which pushes the closing run to the end of the block.

The regression was introduced by Fix emphasis when EnableTrackTrivia() is used (#561), which made emphasis detection work under trivia and thereby exposed this
pre-existing gap in the leftover-closer handling.

Fix

After the matching loop for a closer, if the closer still holds leftover delimiter
characters and is nested inside the emphasis it closed, move the inlines that
follow the closer out to the outermost enclosing emphasis. This mirrors the
existing behaviour for a fully consumed opener. The leftover closer itself keeps
its position, so cases with no trailing content (already covered by
TestEmphasisExtended, e.g. 2223222 -> 2<two-only>32</two-only>) are
unchanged.

One file changed: src/Markdig/Parsers/Inlines/EmphasisInlineParser.cs.

Tests

Added src/Markdig.Tests/TestEmphasisRoundtrip.cs:

  • GridTableSeparatorRoundtripsWithTrackTrivia - the exact issue input round-trips
    to itself.
  • UnbalancedMarkedRunsRoundtrip - minimal reduction (+===============+===============+).
  • BalancedMarkedEmphasisStillParses - ==bold== still renders <mark>bold</mark>,
    confirming emphasis detection under EmphasisExtras is preserved.

The two round-trip tests fail before the fix and pass after it. The full test
suite passes (3798 passed, 1 pre-existing skip, 0 failed).

When EnableTrackTrivia() is enabled, an emphasis closer whose delimiter
run is only partially consumed (its leftover dropping below the minimum
count) kept the inlines that follow it nested inside the emphasis. The
roundtrip renderer then emitted the closing delimiters after that
trailing content, corrupting the output, for example a grid table
separator row using the "marked" (==) delimiter from EmphasisExtras.

Move the trailing content out to the outermost enclosing emphasis when
the closer can no longer be matched, mirroring the existing handling for
a fully consumed opener. The leftover closer keeps its position, so
cases without trailing content are unaffected. Adds a regression test.
@dualfroz
dualfroz force-pushed the fix/emphasis-trivia-roundtrip-743 branch from f57efa9 to 0a17da1 Compare September 5, 2026 23:19
@xoofx xoofx added the bug label Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GridTable - invalid output (and/or parse)

2 participants