Engineering Journal
Converting Markdown Blocks into Semantic HTML
How block-level Markdown parsing can transform headings, paragraphs, lists, quotes, and code blocks into structured semantic HTML.
Converting Markdown Blocks into Semantic HTML
Markdown documents are written as a continuous stream of text, but browsers consume structured HTML.
Bridging those two representations requires more than replacing a few characters.
A static site generator needs to understand the boundaries between blocks and determine what each block represents.
That was one of the central problems in my Static Site Generator project.
What Is a Markdown Block?
A block is a logical unit of Markdown content.
For example:
# Introduction
This project explores static site generation.
- Markdown parsing
- HTML rendering
- File generation
contains three major blocks:
Heading
Paragraph
List
Identifying those blocks gives the rest of the pipeline a reliable structure to work with.
Why Block Parsing Comes First
Consider this Markdown:
# Engineering Notes
A static site generator transforms content into HTML.
## Architecture
The system contains several processing stages.
Before inline formatting is considered, the parser needs to understand the document hierarchy.
A simplified representation could be:
Heading level 1
Paragraph
Heading level 2
Paragraph
Once the blocks are classified, each one can be processed independently.
Headings
A Markdown heading such as:
## Architecture
can become:
<h2>Architecture</h2>
The important information is:
- heading level
- heading content
The parser therefore needs to identify the number of leading # characters and associate that with the correct HTML element.
Paragraphs
Paragraphs are generally the default block.
For example:
Static websites are easy to deploy.
becomes:
<p>Static websites are easy to deploy.</p>
However, paragraphs cannot simply consume every line.
The parser needs to know where a paragraph ends and another block begins.
Blank lines are therefore important structural boundaries.
Unordered Lists
A list such as:
- Parser
- Renderer
- Generator
represents one list containing three items.
The resulting HTML is:
<ul>
<li>Parser</li>
<li>Renderer</li>
<li>Generator</li>
</ul>
This requires the parser to group consecutive list items into a single parent structure.
It should not generate three unrelated <ul> elements.
Ordered Lists
Ordered lists follow the same general principle:
1. Parse
2. Transform
3. Render
becomes:
<ol>
<li>Parse</li>
<li>Transform</li>
<li>Render</li>
</ol>
The block parser therefore needs to distinguish between ordered and unordered list markers.
Blockquotes
A blockquote:
> Build systems that are easy to understand.
can become:
<blockquote>
<p>Build systems that are easy to understand.</p>
</blockquote>
The important point is that the quote itself is a structural element.
Its contents may still require inline Markdown processing.
This demonstrates why block parsing and inline parsing should remain separate.
Code Blocks
Code blocks are another special case.
For example:
```python
print("hello")
```
contains content that should generally be preserved rather than interpreted as Markdown.
The generator needs to recognize the code-block boundary and prevent the contents from being processed as ordinary Markdown.
That is an important parsing boundary.
Block Classification
A useful mental model is:
Raw Markdown
│
▼
Split into blocks
│
▼
Classify each block
│
├── Heading
├── Paragraph
├── List
├── Quote
└── Code
Only after this stage should the system move toward HTML generation.
Semantic HTML
The goal is not merely to make HTML that looks correct.
The generated markup should express meaning.
A heading should be a heading.
A list should be a list.
A quotation should be a blockquote.
Code should be represented as code.
This improves accessibility, browser interpretation, maintainability, and search-engine understanding.
Why Semantics Matter for SEO
Search engines do not simply look at visible text.
Document structure provides important context.
A properly structured document gives crawlers a clearer hierarchy:
h1
├── paragraph
├── h2
│ ├── paragraph
│ └── list
└── h2
This is considerably more useful than generating a page composed almost entirely of generic <div> elements.
Testing Block Parsing
Block parsing benefits from explicit test cases.
For example:
heading
paragraph
unordered list
ordered list
blockquote
code block
Each case should produce the expected structural representation.
Edge cases matter too.
The parser needs predictable behavior around:
- multiple blank lines
- empty documents
- adjacent blocks
- malformed headings
- incomplete code fences
- list boundaries
Keeping the Parser Maintainable
The temptation when implementing a parser is to create one large function containing every rule.
That approach becomes difficult to maintain.
A better design is to separate concerns into smaller operations:
markdown_to_blocks
↓
block_to_block_type
↓
block-specific conversion
↓
HTML node generation
Each stage can then be tested independently.
Final Thoughts
Block-level parsing is the foundation of a Markdown-to-HTML pipeline.
Once Markdown is divided into meaningful structural units, the rest of the generator becomes significantly easier to reason about.
The broader lesson is useful beyond static websites:
Good parsers do not merely transform text. They recover structure.
That structure becomes the foundation for everything that follows.