Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
422: Building a Markdown-to-HTML Converter
When I first started writing parsers, I fell into a trap that I still see a lot of developers stumble into today: the belief that a Markdown-to-HTML converter is just a series of .replace() calls. It seems intuitive. You see **bold**, you replace it with <strong>bold</strong>, and you move on. But this approach falls apart the second your input gets real.
Thinking .replace() handles the heavy lifting
Let's look at why that's a dangerous path. Imagine you're converting a technical document that contains both a header and a Python code block. Your input looks like this:
# My Python Guide
Here is how to comment in Python:
```python
# This is a comment, not a header
```
If you're just using text.replace('# ', '<h1>'), your converter is going to happily turn that Python comment into an <h1> tag. Your output becomes a mess of broken HTML because the code didn't "know" it was inside a code block. String replacement is blind; it doesn't understand context or state.
Using Regular Expressions for Pattern Recognition
To do this right, we need to move away from literal replacements and toward pattern matching. Python's re module is our best friend here. Instead of looking for an exact string, we look for a pattern—like "a hash symbol at the start of a line followed by a space."
I usually start by handling the "block-level" elements first (headers, paragraphs) and then diving into "inline" elements (bold, italics). Here is how I'd approach a header conversion using a capturing group:
import re
def convert_headers(text):
# ^# matches a # at the start of the line
# (.*) captures everything after the space into Group 1
# $ ensures we match until the end of the line
return re.sub(r'^# (.*)$', r'<h1>\1</h1>', text, flags=re.MULTILINE)
The \1 in the replacement string tells Python to put whatever it found in the first set of parentheses back into the string. It's much cleaner and far more predictable than .replace().
Managing State to Prevent "Leaking" Styles
The real challenge is the "context" problem I mentioned earlier. You can't just run a bunch of regexes over the whole file; you have to track whether you are currently "inside" a special block, like a code fence (```). I typically handle this by iterating through the lines of the file and using a boolean flag.
I've found that a simple in_code_block variable is the most reliable way to prevent your header or bold logic from triggering inside a snippet of code. When the flag is true, you ignore all other formatting rules and just wrap the line in <pre> or <code> tags until you hit the closing fence.
Once you've handled the blocks, you can then run your inline regexes (like \*\*(.*?)\*\* for bold) on the resulting text. Because the code blocks are already wrapped in their own tags, you can choose to skip them or process them differently, keeping your document structure intact.
📋 Practical Task
Building a Markdown-to-HTML List Parser
Your task is to extend a basic converter to handle unordered lists. In Markdown, a list item starts with a * or - . However, in HTML, the <li> tags must be wrapped inside a single <ul> tag.
Write a function convert_lists(lines) that takes a list of strings (lines from a file) and returns a single string. Your logic must:
- Detect when a sequence of lines starting with
*or-begins. - Wrap the entire group of list items in one set of
<ul>...</ul>tags. - Convert each individual line into an
<li>...</li>tag. - Ensure that when the list ends (a line doesn't start with a bullet), the
<ul>is closed before processing the next line.
Test Case:
Input: ["* Apple", "* Banana", "Not a fruit", "* Cherry"]
Expected Output: "<ul><li>Apple</li><li>Banana</li></ul>Not a fruit<ul><li>Cherry</li></ul>"
There are no comments for now.