Skip to content

Step 1 of 5 · Reading · ~3 min

Read

Block Elements

Headings

A heading is the simplest block in Markdown, which makes it the right place to learn the habit the whole parser runs on: a line either matches a block rule exactly, or it is not that block at all. There is no "close enough". Getting comfortable with that all-or-nothing test now is what makes lists, fences and blockquotes tractable later.

What turns a line into a heading?

CommonMark calls this style an ATX heading: one to six # characters at the start of a line, then a space or tab, then the text.

# Release notes      ->  <h1>Release notes</h1>
###### Deep          ->  <h6>Deep</h6>
####### Seven        ->  not a heading — seven hashes
#Tag                 ->  not a heading — no space

Six is the ceiling because HTML stops at <h6>. A seventh # does not clamp down to six: the line stops being a heading and becomes ordinary paragraph text.

Why insist on the space?

Because without it every hashtag anyone types becomes an <h1>. #5 bolt is a bolt size. #Tag is a tag. That single space is the entire boundary between "structure the author meant" and "a character that happened to be typed first". The spec is explicit that many older parsers skipped this check — which is exactly why documents written for them render differently everywhere else.

What happens to hashes at the end of the line?

A run of # after the text is optional decoration and gets dropped: ## Ship it ## is just <h2>Ship it</h2>. But the closing run only counts when a space or tab comes before it. # foo# keeps the hash — it is <h1>foo#</h1>, not <h1>foo</h1>.

That one rule separates a spec-accurate pattern from a sloppy one:

Pattern## Ship it ### foo#
^(#{1,6})[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$Ship itfoo#
^(#{1,6})\s+(.+?)\s*#*\s*$Ship itfoo — hash eaten

Both pass this lesson's tests. Only the first is right.

python

None is the signal "this was not a heading — hand the line back to whoever else wants it". Your parser will grow more of these handlers; every one of them answers yes or no and passes on cleanly.

The decision, as the block phase sees it

Rendering diagram…

Markdown's other heading style — text underlined with = or - — has its own lesson, and its own much nastier ambiguity.

Your exercise

Turn each input line into a heading tag, or pass it through untouched.

The mistake the grader catches is clamping instead of rejecting. If you write the level as min(len(hashes), 6), the line ####### Not a heading (too many #) comes back as <h6>Not a heading (too many #)</h6>, and the very first visible test fails on that line. Seven hashes is not a level-7 heading and not a level-6 heading — it is a plain line, echoed byte for byte.

The second trap is the same shape: #NoSpace must also come back verbatim. Require the space in the pattern rather than stripping hashes first, and both cases fall out for free.

Up nextParagraphs & Line BreaksBlock Elements

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…