{
  "version": "https://jsonfeed.org/version/1",
  "title": "Commonmark on LLBBL Blog",
  "icon": "https://avatars.micro.blog/avatars/2023/40/125738.jpg",
  "home_page_url": "https://llbbl.blog/",
  "feed_url": "https://llbbl.blog/feed.json",
  "items": [
      {
        "id": "http://llbbl.micro.blog/2026/08/16/markdown-is-not-a-format.html",
        "title": "Markdown Is Not a Format, It's an Argument",
        "content_html": "<p>I&rsquo;ve covered PNG and text files, and now it&rsquo;s time for Markdown, which can be thought of as a philosophy of formatting or a lifestyle of text documents more so than an actually well defined file format. It has structure, and it has specifications, plural, and nothing agrees.</p>\n<p>Here is three lines of Markdown run through five parsers:</p>\n<pre tabindex=\"0\"><code>INPUT: &#34;- outer\\n  - inner\\n&#34;\n\nPython-Markdown      &lt;ul&gt; &lt;li&gt;outer&lt;/li&gt; &lt;li&gt;inner&lt;/li&gt; &lt;/ul&gt;\nmarkdown2            &lt;ul&gt; &lt;li&gt;outer &lt;ul&gt; &lt;li&gt;inner&lt;/li&gt; &lt;/ul&gt;&lt;/li&gt; &lt;/ul&gt;\nmistune              &lt;ul&gt; &lt;li&gt;outer&lt;ul&gt; &lt;li&gt;inner&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt;\nmarko (CommonMark)   &lt;ul&gt; &lt;li&gt; outer&lt;ul&gt; &lt;li&gt;inner&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt;\ncmark-gfm (GitHub)   &lt;ul&gt; &lt;li&gt;outer &lt;ul&gt; &lt;li&gt;inner&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt;\n</code></pre><p>Five parsers, five different results. Most of that is cosmetic whitespace, but look at the first one: Python-Markdown produced a <strong>flat list</strong>. The nesting is gone. That&rsquo;s not a formatting difference, that&rsquo;s a different document.</p>\n<hr>\n<h2 id=\"the-original-spec-was-an-essay\">The Original Spec Was an Essay</h2>\n<p>John Gruber released Markdown in March 2004, along with a Perl script called <code>Markdown.pl</code>. The design goal was stated plainly:</p>\n<blockquote>\n<p>The overriding design goal for Markdown&rsquo;s formatting syntax is to make it as readable as possible. The idea is that a Markdown-formatted document should be publishable as-is, as plain text, without looking like it&rsquo;s been marked up with tags or formatting instructions.</p>\n</blockquote>\n<p>That goal was met, and it&rsquo;s why we&rsquo;re all still using it twenty years later. The syntax borrowed from conventions people had already invented for plain text email and Usenet: <code>=</code> and <code>-</code> underlines from Setext, <code>#</code> headers from atx, <code>&gt;</code> quoting from Usenet, <code>*</code> for emphasis from Textile and reStructuredText. None of it was new. That was the point.</p>\n<p>What Markdown shipped without was a grammar. The specification was English prose describing the syntax with examples, and the tiebreaker for anything the prose didn&rsquo;t cover was &ldquo;whatever <code>Markdown.pl</code> does.&rdquo; A Perl script full of regular expressions became the definition of the format by default.</p>\n<p>That works fine until someone writes a second implementation.</p>\n<hr>\n<h2 id=\"where-the-prose-ran-out\">Where the Prose Ran Out</h2>\n<p>The ambiguities weren&rsquo;t exotic. They were things you hit in the first week:</p>\n<p><strong>How much indentation nests a list?</strong> Two spaces? Four? One tab? The original prose didn&rsquo;t say clearly, and the answer interacts with the rule that four spaces means a code block.</p>\n<p><strong>What happens inside raw HTML?</strong> If you write a <code>&lt;div&gt;</code> and put Markdown inside it, does the Markdown get processed? Gruber&rsquo;s implementation had behavior; the prose didn&rsquo;t specify it.</p>\n<p><strong>When does a <code>*</code> open emphasis versus just being an asterisk?</strong> In <code>a * b * c</code>, are those multiplication signs or emphasis delimiters?</p>\n<p><strong>Do underscores work inside words?</strong> This one bites daily:</p>\n<pre tabindex=\"0\"><code>INPUT: &#34;snake_case_variable&#34;\n\nPython-Markdown      &lt;p&gt;snake_case_variable&lt;/p&gt;\nmarkdown2            &lt;p&gt;snake&lt;em&gt;case&lt;/em&gt;variable&lt;/p&gt;\nmistune              &lt;p&gt;snake_case_variable&lt;/p&gt;\nmarko (CommonMark)   &lt;p&gt;snake_case_variable&lt;/p&gt;\ncmark-gfm (GitHub)   &lt;p&gt;snake_case_variable&lt;/p&gt;\n</code></pre><p>markdown2 italicizes your variable name. Every other parser leaves it alone. Both are defensible readings of a spec that never addressed it.</p>\n<p>Or the heading with no space after the hash:</p>\n<pre tabindex=\"0\"><code>INPUT: &#34;#Heading&#34;\n\nPython-Markdown      &lt;h1&gt;Heading&lt;/h1&gt;\nmarkdown2            &lt;h1&gt;Heading&lt;/h1&gt;\nmistune              &lt;p&gt;#Heading&lt;/p&gt;\nmarko (CommonMark)   &lt;p&gt;#Heading&lt;/p&gt;\ncmark-gfm (GitHub)   &lt;p&gt;#Heading&lt;/p&gt;\n</code></pre><p>Half of them give you a heading, half give you a paragraph starting with a hash. This one matters because <code>#hashtag</code> at the start of a line is a real thing people write.</p>\n<hr>\n<h2 id=\"everyone-wrote-their-own\">Everyone Wrote Their Own</h2>\n<p>With no formal spec, every implementation became a dialect, and the popular ones added features:</p>\n<ul>\n<li><strong>PHP Markdown Extra</strong> (Michel Fortin, 2005) added pipe tables, definition lists, footnotes, fenced code blocks, and attribute blocks.</li>\n<li><strong>MultiMarkdown</strong> (Fletcher Penney, 2005) added metadata frontmatter, cross-references, citations, and LaTeX export.</li>\n<li><strong>Pandoc Markdown</strong> (John MacFarlane, 2006) built a real AST-based parser and added YAML frontmatter, TeX math, grid tables, and citations.</li>\n<li><strong>kramdown</strong> (Thomas Leitner, 2009) added inline attribute lists and its own math support.</li>\n</ul>\n<p>Each is a superset of a slightly different reading of the original. A document written for one is not guaranteed to render correctly in another, and the failure mode is silent: you don&rsquo;t get a parse error, you get the wrong document.</p>\n<hr>\n<h2 id=\"commonmark-specify-the-ambiguity-away\">CommonMark: Specify the Ambiguity Away</h2>\n<p>On 3 September 2014, Jeff Atwood announced a spec effort on Coding Horror under the name <strong>Standard Markdown</strong>, with John MacFarlane as primary author and people from GitHub, Reddit, Stack Exchange, and Meteor involved. The goal was not a new dialect and not a replacement for Gruber&rsquo;s syntax, but an unambiguous description of what the existing syntax should mean in every case.</p>\n<p>The name lasted about a day. That night, by Atwood&rsquo;s account, Gruber emailed him and MacFarlane privately, called the name &ldquo;infuriating,&rdquo; and asked that the project be renamed and the domain taken down. On 4 September, Atwood published a follow-up retitling it <strong>Common Markdown</strong>, which shortly became the one-word <strong>CommonMark</strong>.</p>\n<p>Worth being precise here, because this story gets retold badly: this was not a trademark action. Gruber holds no registered trademark on &ldquo;Markdown&rdquo; and did not invoke one. It was an objection to the name, made in private email, and the only public record of his side is Atwood&rsquo;s paraphrase. There is no Daring Fireball post about it.</p>\n<p>The naming fight is a footnote. The approach is the interesting part. Rather than describing the syntax in prose and hoping, CommonMark defines a parsing <em>algorithm</em> and ships an executable test suite pairing exact input with exact expected HTML, more than 500 examples embedded in the spec document itself. Conformance is not a matter of opinion. You run the tests.</p>\n<p>The algorithm works in two passes.</p>\n<p><strong>Phase one walks the document line by line and builds block structure.</strong> Container blocks (blockquotes, lists, list items) and leaf blocks (headings, code blocks, paragraphs, HTML blocks) get assembled into a tree. Link reference definitions get collected. No inline formatting is considered at all in this phase, which is why block structure always wins: a <code>&gt;</code> at the start of a line is a blockquote marker regardless of what emphasis you thought you were in the middle of.</p>\n<p><strong>Phase two walks the text inside leaf blocks and resolves inline structure.</strong> This is where emphasis, links, images, code spans, and inline HTML get parsed, using a delimiter stack.</p>\n<p>That two-phase split is the single most useful thing to know about Markdown parsing, because it explains most surprising behavior. If your emphasis &ldquo;leaked&rdquo; across a list item boundary, it didn&rsquo;t; blocks were decided before emphasis was ever considered.</p>\n<h3 id=\"the-emphasis-rules-are-hard\">The Emphasis Rules Are Hard</h3>\n<p>Emphasis is the hardest part of the spec, and CommonMark&rsquo;s solution is a set of flanking rules. A run of <code>*</code> or <code>_</code> is classified as <strong>left-flanking</strong> (can open emphasis) or <strong>right-flanking</strong> (can close it) based on the characters on either side, roughly: a delimiter can open if it&rsquo;s not followed by whitespace, and can close if it&rsquo;s not preceded by whitespace, with extra conditions around punctuation.</p>\n<p>Then there&rsquo;s a special case for underscores: an <code>_</code> can open emphasis only if it&rsquo;s left-flanking <strong>and not</strong> right-flanking. That single asymmetry is what makes <code>snake_case_variable</code> safe, because the middle underscores are both left- and right-flanking and are therefore disqualified from opening anything. Asterisks don&rsquo;t get that rule, which is why <code>snake*case*variable</code> still italicizes.</p>\n<p>This is what &ldquo;specifying the ambiguity away&rdquo; costs. The rule isn&rsquo;t elegant. It exists because real documents contain identifiers, and a spec that italicizes your variable names is wrong no matter how clean its grammar is.</p>\n<p>You can see the payoff in the nesting case:</p>\n<pre tabindex=\"0\"><code>INPUT: &#34;*foo**bar**baz*&#34;\n\nPython-Markdown      &lt;p&gt;&lt;em&gt;foo&lt;/em&gt;&lt;em&gt;bar&lt;/em&gt;&lt;em&gt;baz&lt;/em&gt;&lt;/p&gt;\neveryone else        &lt;p&gt;&lt;em&gt;foo&lt;strong&gt;bar&lt;/strong&gt;baz&lt;/em&gt;&lt;/p&gt;\n</code></pre><p>Four parsers agree, and the one that predates the delimiter-stack approach gets it wrong in a way that changes the meaning.</p>\n<hr>\n<h2 id=\"gfm-is-a-layer-not-a-fork\">GFM Is a Layer, Not a Fork</h2>\n<p>GitHub Flavored Markdown is CommonMark plus five extensions, and it&rsquo;s specified against CommonMark rather than diverging from it:</p>\n<ol>\n<li><strong>Tables</strong>, pipe-delimited with alignment colons</li>\n<li><strong>Task lists</strong>, <code>- [ ]</code> and <code>- [x]</code>, rendered as checkboxes</li>\n<li><strong>Strikethrough</strong>, <code>~~text~~</code></li>\n<li><strong>Autolinks</strong>, bare URLs linkified without brackets</li>\n<li><strong>A raw HTML filter</strong> that neutralizes dangerous tags by escaping their opening bracket</li>\n</ol>\n<p>That last one is a security control rather than a formatting feature, which tells you something about what it&rsquo;s like to run a Markdown renderer on user-submitted content at GitHub&rsquo;s scale.</p>\n<p>The extension boundary is visible if you feed the same table to both:</p>\n<pre tabindex=\"0\"><code>INPUT:\n| a | b |\n|---|---|\n| 1 | 2 |\n\nCommonMark  &lt;p&gt;| a | b | |---|---| | 1 | 2 |&lt;/p&gt;\ncmark-gfm   &lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;a&lt;/th&gt;&lt;th&gt;b&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;...\n</code></pre><p>Tables are not Markdown. Tables are a GFM extension. CommonMark renders that input as a paragraph containing literal pipe characters, and it is correct to do so.</p>\n<p>Tables, footnotes, task lists, strikethrough, frontmatter, math, and Mermaid diagrams are all extensions. None of them are guaranteed anywhere.</p>\n<hr>\n<h2 id=\"what-to-do-about-it\">What To Do About It</h2>\n<p>The practical takeaways are short.</p>\n<p><strong>Know which parser you&rsquo;re targeting.</strong> &ldquo;It renders on GitHub&rdquo; tells you about cmark-gfm, and nothing about your static site generator, your docs pipeline, or someone&rsquo;s RSS reader.</p>\n<p><strong>Prefer the constructs everyone agrees on.</strong> Headings with a space after the hash, fenced code blocks, asterisks for emphasis, blank lines between blocks, four-space or consistent nesting. Boring Markdown survives transport.</p>\n<p><strong>Don&rsquo;t rely on parser-specific behavior you discovered by accident.</strong> If nesting a list at two spaces works in your tool, that&rsquo;s your tool, not the format.</p>\n<p>There is even a formal way to say which dialect you mean. RFC 7763 registers <code>text/markdown</code> as a media type, and RFC 7764 defines a <code>variant</code> parameter for exactly this problem:</p>\n<pre tabindex=\"0\"><code>text/markdown; variant=CommonMark\ntext/markdown; variant=GFM\ntext/markdown; variant=Original\n</code></pre><p>The standards process looked at Markdown, concluded that saying &ldquo;this is Markdown&rdquo; is not specific enough to be useful, and standardized a way to say which Markdown you meant.</p>\n<p>That&rsquo;s the tradeoff Markdown made. PNG picked one answer and enforced it with a checksum. A text file refuses to answer anything. Markdown let a million answers bloom, got adopted everywhere precisely because it was easy to implement badly, and has spent the last decade trying to agree with itself.</p>\n<p>I&rsquo;ll take that trade. But it&rsquo;s worth knowing that when you write Markdown, you are not writing in a format. You&rsquo;re writing in a dialect, and hoping the reader speaks it.</p>\n<h2 id=\"sources\">Sources</h2>\n<ul>\n<li><a href=\"https://daringfireball.net/projects/markdown/syntax\">Daring Fireball: Markdown</a> — Gruber&rsquo;s original 2004 syntax document and design goals</li>\n<li><a href=\"https://spec.commonmark.org/\">CommonMark Specification</a> — the parsing algorithm, emphasis flanking rules, and executable test suite</li>\n<li><a href=\"https://spec.commonmark.org/0.31.2/#appendix-a-a-parsing-strategy\">CommonMark parsing strategy appendix</a> — the two-phase block/inline design</li>\n<li><a href=\"https://github.github.com/gfm/\">GitHub Flavored Markdown Spec</a> — the five extensions, specified against CommonMark</li>\n<li><a href=\"https://datatracker.ietf.org/doc/html/rfc7763\">RFC 7763</a> and <a href=\"https://datatracker.ietf.org/doc/html/rfc7764\">RFC 7764</a> — the <code>text/markdown</code> media type and the registered dialect variants, both by S. Leonard, March 2016</li>\n<li><a href=\"https://blog.codinghorror.com/standard-flavored-markdown/\">Coding Horror: Standard Flavored Markdown</a> and <a href=\"https://blog.codinghorror.com/standard-markdown-is-now-common-markdown/\">Standard Markdown is now Common Markdown</a> — Atwood&rsquo;s announcement and the rename a day later</li>\n<li><a href=\"https://daringfireball.net/2004/03/introducing_markdown\">Daring Fireball: Introducing Markdown</a> — the original 15 March 2004 announcement</li>\n<li><a href=\"https://github.com/github/cmark-gfm/blob/master/extensions/tagfilter.c\"><code>tagfilter.c</code> in cmark-gfm</a> — the nine tags GFM&rsquo;s raw HTML filter neutralizes</li>\n</ul>\n<blockquote>\n<p>I&rsquo;d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at <a href=\"https://micro.blog/llbbl?remote_follow=1\">@logan@llbbl.blog</a>.</p>\n</blockquote>\n",
        "date_published": "2026-08-16T10:00:00-05:00",
        "url": "https://llbbl.blog/2026/08/16/markdown-is-not-a-format.html",
        "tags": ["Programming","Markdown","File-formats","Commonmark","Parsing"]
      }
  ]
}
