← 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"<h3>{line[4:]}</h3>"
    if line.startswith("## "):
        return f"<h2>{line[3:]}</h2>"
    if line.startswith("# "):
        return f"<h1>{line[2:]}</h1>"
    if line == "":
        return ""
    # Replace **bold** with <strong> tags.
    while "**" in line:
        line = line.replace("**", "<strong>", 1).replace("**", "</strong>", 1)
    return f"<p>{line}</p>"

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

if __name__ == "__main__":
    sample = "# Hello\nThis is **bold** text.\n## A subheading"
    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():
    """# Title becomes an h1."""
    assert convert_line("# Title") == "<h1>Title</h1>"


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


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


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

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('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 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.