Markdown custom heading id: brace ids under pandoc
Bobby Huang ·
The section Heading ids in the CommonMark spec lists grep results from the 0.31.2 text beside a control. Quoted HTML from the test file hid.md sits in Setting an id in braces with the markdown reader. Output with header_attributes taken out is quoted under When the header attributes extension is off. Two commonmark runs of that same file sit under Commonmark with and without the attributes extension. A heading with a class and an id meets four reader settings in Adding a class next to the id. Laid out in Check a heading id with pandoc are six numbered steps, and grep runs in steps 3 and 6.
Key takeaways
- Grep results on the spec text for brace and extension strings, plus a control, are listed in Heading ids in the CommonMark spec.
- Quoted output under Setting an id in braces with the markdown reader shows the heading id and the link that targets it.
- With header_attributes removed, the heading and its id are quoted in When the header attributes extension is off.
- Output from two commonmark runs of hid.md appears in Commonmark with and without the attributes extension.
- Class and id output for one heading line is compared in Adding a class next to the id.
You want a link that lands on one heading, and you'd like to pick the name it points at yourself. This post puts one way of writing that name to a pandoc 3.8.3 test: a markdown custom heading id typed inside a pair of braces after the heading words.
Two small test files went through pandoc on macOS under four reader settings, and pandoc --version showed 3.8.3. The output shows which settings turned the braces into an id and which kept them as plain heading text. What follows is only what pandoc printed in this test; no viewer's handling of that output was checked.
Heading ids in the CommonMark spec
Ids that pandoc makes on its own, and the suffix it adds to a repeated heading, are covered in the heading links guide. The question here is smaller: does CommonMark 0.31.2 give you a way to write an id by hand?
Grep found no such syntax in the spec text. On that file, grep -c '{#' returned 0, and grep -ci 'header_attributes' also returned 0. As a check that the file was searchable, grep -ci 'heading' returned 67.
The spec's ATX heading rule (lines 1043 to 1053) starts from an opening sequence of # signs, and the string of characters after it gets parsed as inline content. That definition has no id part, so braces typed after the heading words belong to the heading content.
In pandoc, the syntax comes from extensions. The filtered listing from pandoc --list-extensions=markdown | grep -e header_attributes -e auto_identifiers -e '^.attributes' included +auto_identifiers and +header_attributes. For the commonmark reader, pandoc --list-extensions=commonmark | grep -e header_attributes -e auto_identifiers -e '^.attributes' printed -attributes among its lines.
Setting an id in braces with the markdown reader
One heading with braces, a link to it, and two more headings with the same words make up hid.md:
# Setup steps {#setup}
See [setup](#setup).
# Setup steps
# Setup steps
Here are the first two lines that came back when pandoc -f markdown -t html hid.md ran:
<h1 id="setup">Setup steps</h1>
<p>See <a href="#setup">setup</a>.</p>
The brace text left the heading, and the id came out as setup, the word after the # inside the braces. The link's href="#setup" matches that id. The two repeat headings below it also got ids from pandoc; the heading links guide linked above explains those.
When the header attributes extension is off
This run took the extension away to show what the braces do without it: pandoc -f markdown-header_attributes -t html hid.md. The first two output lines:
<h1 id="setup-steps-setup">Setup steps {#setup}</h1>
<p>See <a href="#setup">setup</a>.</p>
The heading text kept the braces. The heading still got an id, but that id was setup-steps-setup. The link kept href="#setup", and no heading in this output held id="setup".
A count backs that up. pandoc -f markdown-header_attributes -t html hid.md | grep -c 'id="setup"' gave 0. With the full -f markdown reader in its place, that count gave 1.
Commonmark with and without the attributes extension
No plus or minus sign followed the reader name when pandoc -f commonmark -t html hid.md ran, and it printed:
<h1>Setup steps {#setup}</h1>
<p>See <a href="#setup">setup</a>.</p>
<h1>Setup steps</h1>
<h1>Setup steps</h1>
None of the three headings got an id here. This run printed the braces as heading text, which fits the spec definition quoted earlier. The link still asked for #setup, yet no heading in this HTML carried that id.
Turning on pandoc's attributes extension changed the first heading. pandoc -f commonmark+attributes -t html hid.md returned:
<h1 id="setup">Setup steps</h1>
<p>See <a href="#setup">setup</a>.</p>
<h1>Setup steps</h1>
<h1>Setup steps</h1>
The heading with braces got id="setup" and lost the brace text. The two plain repeat headings got no id in this run. In the markdown run they did get ids, so switching readers changed more than the line with braces.
Adding a class next to the id
A separate test file, hid-class.md, holds one line with an id and a class:
# Owner notes {#notes .wide}
Here is what came back from four reader settings:
pandoc -f markdown -t html hid-class.mdreturned<h1 class="wide" id="notes">Owner notes</h1>.pandoc -f commonmark+attributes -t html hid-class.mdreturned that same line.pandoc -f commonmark -t html hid-class.mdreturned<h1>Owner notes {#notes .wide}</h1>.pandoc -f markdown-header_attributes -t html hid-class.mdreturned<h1 id="owner-notes-notes-.wide">Owner notes {#notes .wide}</h1>.
The dot marked wide as a class. In the braces the id came first, yet pandoc wrote class before id in its HTML. Counting shows the split: pandoc -f markdown -t html hid-class.md | grep -c 'class="wide"' gave 1, while running that pipe with -f commonmark gave 0.
Linking to a custom id
In the markdown and commonmark+attributes runs, the link [setup](#setup) and the heading with id="setup" came out in the same HTML. In the other two runs, the link had no heading with a matching id.
For a link from one file to a heading that lives in another file, the path part is covered in the local file links guide. A contents list built from heading links has its own contents list guide.
Check a heading id with pandoc
When a heading link lands nowhere, test the file with whichever reader you intend to keep.
- Show the file's text with
cat hid.mdand confirm the braces come after the heading words, on the heading line. - Run one conversion, for instance
pandoc -f commonmark -t html hid.md. If{#setup}shows in the heading text, that reader made no id from the braces in this test. - Next, count the id.
pandoc -f markdown -t html hid.md | grep -c 'id="setup"'gave1. With-f commonmarkin place of-f markdown, the count was0, and-f commonmark+attributesgave1. - Compare with the extension off. Turn it off with
pandoc -f markdown-header_attributes -t html hid.mdand read the firsth1line; here it heldid="setup-steps-setup". - Set the link beside the heading ids. All four runs in this test printed the link as
href="#setup", and a heading withid="setup"showed up in two of them. - For a class, count it too:
pandoc -f markdown -t html hid-class.md | grep -c 'class="wide"'gave1, and with-f commonmarkit gave0.
Where Sashmark fits
In the v1 plan, Sashmark would have you edit text at the spot where the page renders it. Saving after such an edit would then bring the clean Round-Trip in that plan, leaving each byte of an unedited line as it was. Flagging what is new in a file since your last read is the planned job of its Changes View, meant to work with no git. It is in development and has not been released. The app itself has no AI features. See the planned features, the help pages and the roadmap for where each part stands.
More at sashmark.com.
Questions
- What syntax gives a Markdown heading a custom id?
- Brace syntax after the heading text, and the id pandoc printed for it, are shown in Setting an id in braces with the markdown reader.
- Why does my {#id} show up in the heading text?
- Under Commonmark with and without the attributes extension, two reader runs of hid.md are quoted.
- Can a heading have a class as well as an id?
- HTML for a heading with both is compared across four reader settings in Adding a class next to the id.
- Which id did pandoc give my heading?
- Steps that count ids and classes in pandoc's HTML for the test files are listed in Check a heading id with pandoc.