Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

V is for visible characters - Python A to Z

Code in this blog post was written with versions: Python: 3.14

Python A-Z is a blog series about Python. Each day, I share insights, ideas and examples for different parts of Python development that match with the letter of the day. Blaugust is an annual blogging festival in August where the goal is to write a blog post every day of the month.

I recently built a website to display my Pokédex Binder. I have a physical binder where I collect Pokémon TCG cards in a way where I aim to have one card for each Pokémon and their regional variants (and some forms when applicable).

For each card, I can write a short note to tell why it’s in my binder.

Here’s an example of my most recent addition: an Onix card that’s signed by all the players in our recent retro tournament where I finished 2nd.

A web ui that mimics a card binder showing 9 Pokemon cards from Sabrina's Gengar to Electrode. On the left page of the binder, a Onix card is selected and shown as full page size with a custom note sharing it was a prize from a retro tournament.

The box has limited size so I need a way to control that in data entry: it should tell me when I’ve written too much. At the same time, I want to allow myself to use Markdown to add formatting and links so I can’t simply count the characters of the input.

I needed a way to count the characters that a user actually sees in the final note box. To justify this as an entry for the V day, I’m calling those “visible characters”.

I asked around in Mastodon and found two solutions.

The first solution, that I ended up using is strip-markdown library.

import strip_markdown

MAX_NOTE_LENGTH = 205

def is_valid_length(note: str) -> bool:
	visible = strip_markdown.strip_markdown(note)
	return len(visible) < 205

for card in pokedex:
  if not is_valid_length(card.note):
    raise ValueError(f'{card.id} has too long note.'
  
  add_to_binder(card) # imagine it exists	

A bit later, Jan-Erik shared his solution which uses Python-Markdown:

from markdown import Markdown
from io import StringIO

def unmark_element(element, stream=None):
    if stream is None:
        stream = StringIO()
    if element.text:
        stream.write(element.text)
    for sub in element:
        unmark_element(sub, stream)
    if element.tail:
        stream.write(element.tail)
    return stream.getvalue()

Markdown.output_formats["plain"] = unmark_element

__md = Markdown(output_format="plain")
__md.stripTopLevelTags = False

def strip_markdown(text):
    return __md.convert(text)


If something above resonated with you, let's start a discussion about it! Email me at juhis@hamatti.org and share your thoughts. This year, I want to have more deeper discussions with people from around the world and I'd love if you'd be part of that.