Formatting Markdown with Bootstrap

Published March 1, 2024, 1:23 p.m. by alex


Intro + Problem Statement

This website is programmed with Python-Django, and the dynamic blog posts are written locally in markdown, then uploaded to the server where they are stored. The server is responsible for converting the markdown text to HTML, where it is then sent to the client web-browser for display. Bootstrap CSS is then used to style this HTML code.

Problem Statement:
How do I use bootstrap to style markdown text?

For example, lets take a look at a markdown table:

| Heading One | Heading Two | Heading Three |
| ----------- | ----------- | ------------- |
| Row1 Col1   | Row1 Col2   | Row1 Col3     |
| Row2 Col1   | Row2 Col2   | Row2 Col3     |

The above markdown table will be converted to the following standard HTML table:

<table>
    <thead>
        <tr>
            <th>Heading One</th>
            <th>Heading Two</th>
            <th>Heading Three</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Row1 Col1</td>
            <td>Row1 Col2</td>
            <td>Row1 Col3</td>
        </tr>
        <tr>
            <td>Row2 Col1</td>
            <td>Row2 Col2</td>
            <td>Row2 Col3</td>
        </tr>
    </tbody>
</table>

In order to display the above standard HTML table as a bootstrap 5 table, including the styling, the following classes would need to be added to the HTML code for the <table> element:

class="table table-hover table-sm table-bordered"

I searched for publicly available solutions, maybe there is extension to the python-markdown package that I am using? I couldn't find a way a way to automatically do that as to keep my writing workflow as simple as possible. I know that the HTML code is structured in a standard way, and it should always be written consistently to follow this structure. If only there was a way to parse text and add some pre-defined text to it based on a matching rule...


Regex!

There are multiple places in the python code that I could inject this regex function. I opted to create a series of new template filters alongside the {{ markdownify }} filter I was already using to convert the text to HTML. The new template filter would use a pre-processor and a post-processor, before and after the markdown filter: { pre-processor | markdownify | post-processor }. I chose to do this for multiple reasons.

  1. I have a way to sanitize text if needed before applying markdownify.
  2. I have the ability to create my own syntax if I want. It could be similar enough to markdown, and I want to convert it to HTML before markdownify can modify it.
  3. After my own conversion or markdownify's conversion to HTML, I can modify the HTML further (add bootstrap classes to HTML elements, apply in-line styles, create other bootstrap objects like a modal or collapse).

Lets now take a look at the template filters themselves.

Below you have an example of a post-processor, which will automatically create a bootstrap table (Case #3)

@register.filter()
@stringfilter
def post-processor(original_text):

    # Apply bootstrap css styles to HTML TABLE elements
    bootstrap_table = re.sub(
        pattern=r'<table>\n'
                r'<thead>'
                r'([\s\S]*?)'
                r'</thead>'
                r'([\s\S]*?)'
                r'</table>',
        repl=r'<table class="table table-hover table-sm table-bordered ">'
             r'<thead>'
             r'\1'
             r'</thead>'
             r'\2'
             r'</table>',
        string=original_text,
        flags=re.MULTILINE
    )

    return bootstrap_table

In order to create this bit of code I utilized the built-in dev tools in my browser to view the base HTML for the generated table. I then used regex101.com to build the regex string.

The pattern defines what a HTML table will look like, and breaks the table into 3 parts.

  1. statically typed HTML code that is required to build a table.
  2. capture group 1: This bit of text is dynamic and would be entered in by me, when writing in markdown. This is the table heading.
  3. capture group 2: This bit of text is also dynamic and represents the body of the table.

The repl defines the HTML table with the bootstrap classes applied to it.

I realize that this is a bit verbose, and could have been done in fewer lines of code. But in the future I have the ability to easily expand this pattern since I have separated the HTML from the content.


Solution Example + Final Thoughts

Now my code will automatically convert this:

| Heading One | Heading Two | Heading Three |
| ----------- | ----------- | ------------- |
| Row1 Col1   | Row1 Col2   | Row1 Col3     |
| Row2 Col1   | Row2 Col2   | Row2 Col3     |

To this:

Heading One Heading Two Heading Three
Row1 Col1 Row1 Col2 Row1 Col3
Row2 Col1 Row2 Col2 Row2 Col3

This will be a multi part series, later I will give examples of the pre-processor, some challenges with that solution, and an additional system to identify, and automatically color-code network addresses.

As a sneak peak... the color was generated automatically.

AA:BB:CC:00:11:22
AA:BB:CC:00:11:22
AA:BB:CC:00:11:22
AA:BB:CC:00:11:22