thecodex.expert · The Codex Family of Knowledge
Tier 2 · Intermediate · Python Project

Markdown to HTML

Turn Markdown text into a web page. Learn to parse a simple text format line by line and transform it into something new.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want a converter that reads Markdown (the simple text format with # headings and **bold**) and turns it into HTML a browser can show. It teaches parsing — reading text, recognising patterns, and transforming each piece — the skill behind every format converter and compiler.

Where this shows up: every static-site generator, every README renderer on GitHub, every “rich text” editor, every documentation tool. Reading one format and emitting another is one of the most reusable patterns in software.

2 How to Think About It

Think about how Markdown maps to HTML, before any code:

The plan — in plain English
1. Read the Markdown line by line. → 2. For each line, recognise its type: a line starting with # is a heading; a blank line separates paragraphs; everything else is body text. → 3. Emit the matching HTML tag for each. → 4. Join it all into a finished page.

Yes

No

Yes

No

Read markdown lines

For each line

Starts with hash?

Make a heading tag

Blank line?

Skip

Make a paragraph

Collect HTML

Join into page

3 The Build — explained part by part

Here is the complete converter. Each part is explained below.

Pythonmd2html.py
# Convert simple Markdown to HTML.

def convert_line(line):
    line = line.rstrip()
    if line.startswith("### "):
        return f&quot;<h3>{line[4:]}</h3>&quot;
    if line.startswith(&quot;## &quot;):
        return f&quot;<h2>{line[3:]}</h2>&quot;
    if line.startswith(&quot;# &quot;):
        return f&quot;<h1>{line[2:]}</h1>&quot;
    if line == &quot;&quot;:
        return &quot;&quot;
    # Replace **bold** with <strong> tags.
    while &quot;**&quot; in line:
        line = line.replace(&quot;**&quot;, &quot;<strong>&quot;, 1).replace(&quot;**&quot;, &quot;</strong>&quot;, 1)
    return f&quot;<p>{line}</p>&quot;

def convert(markdown_text):
    lines = markdown_text.split(&quot;\n&quot;)
    html_lines = [convert_line(line) for line in lines]
    return &quot;\n&quot;.join(html for html in html_lines if html)

if __name__ == &quot;__main__&quot;:
    sample = &quot;# Hello\nThis is **bold** text.\n## A subheading&quot;
    print(convert(sample))
What each part does — in plain words
convert_line(line) — the core: it looks at one line and decides what it is. We check the most specific pattern first (### before ## before # ) because ### also starts with #.

line[4:] — slice off the first 4 characters (&ldquo;### &rdquo;) to keep just the heading text.

the while "**" loop — turn each pair of ** into an opening then closing <strong> tag. Replacing one at a time, alternating, pairs them correctly.

convert(markdown_text) — split the whole document into lines, convert each, and join the non-empty results into the final HTML.

if __name__ == "__main__": — a sample run so you can see it work immediately. This block only runs when the file is run directly, not when imported by the tests.
Common mistakes — and how to avoid them
✗ Checking # before ### — then every heading becomes h1, because ### also starts with #.
✓ Check the most specific (longest) prefix first.
✗ Forgetting the space in "# " — then #tag in text is wrongly treated as a heading.
✓ Match "# " with the trailing space, as real Markdown requires.
✗ Slicing the wrong number of characters — line[1:] for "# " leaves a leading space.
✓ Slice off the full prefix length, including the space: line[2:].

4 Test & Prove Each Part

We test each Markdown pattern converts to the right HTML — headings, paragraphs, and bold.

A # line becomes an h1 heading
A ## line becomes an h2 heading
Plain text becomes a paragraph
**bold** becomes a strong tag
Pythontest_md2html.py
from md2html import convert_line, convert


def test_h1():
    &quot;&quot;&quot;# Title becomes an h1.&quot;&quot;&quot;
    assert convert_line(&quot;# Title&quot;) == &quot;<h1>Title</h1>&quot;


def test_h2():
    &quot;&quot;&quot;## Sub becomes an h2.&quot;&quot;&quot;
    assert convert_line(&quot;## Sub&quot;) == &quot;<h2>Sub</h2>&quot;


def test_paragraph():
    &quot;&quot;&quot;Plain text becomes a paragraph.&quot;&quot;&quot;
    assert convert_line(&quot;Hello there&quot;) == &quot;<p>Hello there</p>&quot;


def test_bold():
    &quot;&quot;&quot;**bold** becomes a strong tag.&quot;&quot;&quot;
    assert convert_line(&quot;a **b** c&quot;) == &quot;<p>a <strong>b</strong> c</p>&quot;

Run with pytest -v. Because convert_line handles one line, each test checks one rule. When parsing, testing each pattern in isolation makes bugs easy to pin down.

5 The Interface

INPUTmarkdown texta string of Markdown
What it expects
# Hello
This is **bold** text.
OUTPUThtmlbrowser-ready markup
What it returns
<h1>Hello</h1>
<p>This is <strong>bold</strong> text.</p>

6 Run It & Automate It

Run it locally
python3 md2html.py
See the sample convert. Then try feeding it a real .md file.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
<h1>Hello</h1>
<p>This is <strong>bold</strong> text.</p>
<h2>A subheading</h2>
If it breaks — how to fix it
🚨 All headings come out as h1.
Your checks are in the wrong order. Check ### before ## before #.
🚨 Bold text shows the asterisks.
Your ** replacement is not pairing. Replace one at a time, alternating open/close.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage(&#x27;Get the code&#x27;) { steps { checkout scm } }       // download the code
        stage(&#x27;Install&#x27;) { steps { sh &#x27;python3 -m venv venv && . venv/bin/activate && pip install pytest&#x27; } }  // test tool
        stage(&#x27;Test&#x27;) { steps { sh &#x27;. venv/bin/activate && pytest -v&#x27; } }   // run every test
    }
    post {
        success { echo &#x27;All tests passed.&#x27; }
        failure { echo &#x27;A test failed — look above.&#x27; }
    }
}
🎯 Try this next — make it yours
  1. Italics. Convert *text* to <em>. (Teaches: another pattern.)
  2. Links. Turn [text](url) into an anchor. (Teaches: regular expressions.)
  3. Read and write files. Convert a .md file to a .html file. (Teaches: file I/O.)
What you learned
You learned parsing: reading text line by line, recognising patterns, and transforming each into something new — with string slicing and replacement. This is the foundation of every converter and the first step toward a compiler. Related: String Methods, Comprehensions.