Skip to content

Step 1 of 5 · Reading · ~4 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". Your program will read a document line by line and rewrite every ATX heading into its <h1> to <h6> tag, leaving every other line byte for byte as it found it.

What turns a line into a heading?

CommonMark (section 4.2) calls this style an ATX heading: up to three spaces of indent, one to six # characters, then a space, a tab, or the end of the line, then the text.

# Release notes      ->  <h1>Release notes</h1>
###### Deep          ->  <h6>Deep</h6>
   ## Indented 3     ->  <h2>Indented 3</h2>
    # Indented 4     ->  not a heading (four spaces is code)
####### Seven        ->  not a heading (seven hashes)
#Tag                 ->  not a heading (no space)
#                    ->  <h1></h1>

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

Why insist on the space?

Without it, every hashtag anyone types would become an <h1>. #5 bolt is a bolt size; #Tag is a tag. That single space is the boundary between structure the author meant and a character that happened to be typed first. The end of the line counts too, which is why a bare # is a legal, empty heading.

What happens to hashes at the end of the line?

A run of # after the text is optional decoration (a "closing sequence") and gets dropped: ## Ship it ## is just <h2>Ship it</h2>. The rule has a catch: the closing run only counts when a space or tab comes before it, or when it is the whole content.

SourceHeading textWhy
## Ship it ##Ship itspace before the closing run
# foo#foo#no space, so the # is text
# C# and F#C# and F#both hashes follow a letter
### foo ### bfoo ### bthe run is not at the end
### ###(empty)the closing run is the whole content

A worked trace

Take ### Ship it ## (two spaces in front, three behind):

  1. Skip up to three leading spaces: two skipped. The next character is #.
  2. Count the run of #: three, so level 3. The character after the run is a space, so this is a heading.
  3. Skip the spaces after the opener. The text starts at S.
  4. Trim trailing spaces and tabs from the right: the text is now Ship it ##.
  5. Walk left over the trailing # run: two hashes. The character before them is a space, so the run is a closing sequence. Cut it.
  6. Trim trailing spaces again: Ship it. Emit <h3>Ship it</h3>.
Rendering diagram…

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

Common mistakes

  • Clamping instead of rejecting. Taking min(hashes, 6) turns ####### x into an <h6>. Seven hashes is a plain line.
  • Stripping hashes before checking the space. #NoSpace must come back unchanged; test for the space first.
  • Eating a # that belongs to the text. # foo# has no closing sequence. A pattern like "any #s at the end" turns it into foo.
  • Treating an empty heading as an error. # and ## ## are headings with no text.

Your exercise

Turn every heading line into its tag and echo everything else untouched, one output line per input line. The starter already reads the lines and prints the result; you write the function that decides what a single line becomes. Heading text is copied as it is: escaping < and & is a later lesson.

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…