Text and HTML Processing
This codebase provides a robust set of utilities for manipulating text and HTML, focusing on safety, correct truncation of markup, and intelligent conversion of plain text to hyperlinked content. These utilities are central to how the framework handles user-generated content and administrative displays.
Safe Strings and HTML Output
The foundation of HTML processing in this project is the distinction between "safe" and "unsafe" strings. This is managed primarily through the SafeString class.
SafeString
Located in django/utils/safestring.py, SafeString is a subclass of the standard Python str that also inherits from SafeData. When a string is wrapped in SafeString, it signals to the template engine and other output utilities that the content has already been sanitized or contains intentional HTML that should not be escaped.
A critical implementation detail is how SafeString handles concatenation. If you add a SafeString to a normal str, the result is a standard (unsafe) str to prevent accidental injection:
from django.utils.safestring import SafeString, mark_safe
# mark_safe returns a SafeString instance
header = mark_safe("<h1>Welcome</h1>")
content = " <script>alert('xss')</script>"
# Result is a standard str, which will be escaped on output
final_output = header + content
isinstance(final_output, SafeString) # False
Text Truncation
The Truncator class in django/utils/text.py provides sophisticated logic for shortening text by either character or word count. It is designed to handle both plain text and complex HTML.
Character and Word Truncation
Truncator wraps a string and provides two primary methods: chars() and words().
from django.utils.text import Truncator
text = "The quick brown fox jumps over the lazy dog"
truncator = Truncator(text)
# Truncate to 20 characters (includes the ellipsis)
print(truncator.chars(20)) # "The quick brown fox…"
# Truncate to 4 words
print(truncator.words(4)) # "The quick brown fox…"
HTML-Aware Truncation
A standout feature of Truncator is its ability to truncate HTML without breaking the markup. By passing html=True, the utility uses internal parsers (TruncateCharsHTMLParser or TruncateWordsHTMLParser) to:
- Count only the visible text, ignoring the length of HTML tags.
- Ensure that any tags opened within the truncated segment are correctly closed.
html_content = "<div><p>Hello <b>world</b>, this is a test.</p></div>"
truncator = Truncator(html_content)
# Truncate to 10 visible characters
# Result: "<div><p>Hello <b>wor…</b></p></div>"
print(truncator.chars(10, html=True))
Linkification with Urlizer
The Urlizer class in django/utils/html.py is responsible for finding URLs and email addresses in plain text and converting them into HTML <a> tags.
Punctuation Handling
Urlizer implements complex logic in trim_punctuation() to distinguish between a URL and the punctuation surrounding it. It recognizes trailing periods, commas, and balanced parentheses or brackets, ensuring that a link like (see https://example.com) correctly identifies the URL without including the closing parenthesis.
Protocol Assumptions and HTTPS
The handle_word() method determines the protocol for schemeless URLs (e.g., www.google.com).
- Current Behavior: It defaults to
http://unless theURLIZE_ASSUME_HTTPSsetting is enabled. - Future Change: The codebase includes a
RemovedInDjango70Warning, indicating that the default will switch tohttps://in Django 7.0.
# Usage via the urlize wrapper
from django.utils.html import urlize
# Standard linkification
urlize("Visit djangoproject.com")
# '<a href="http://djangoproject.com" rel="nofollow">djangoproject.com</a>'
# With truncation and nofollow disabled
urlize("https://verylongurl.com/path", trim_url_limit=15, nofollow=False)
# '<a href="https://verylongurl.com/path">https://verylon…</a>'
HTML Tag Stripping
For scenarios where HTML must be removed entirely, the codebase provides MLStripper in django/utils/html.py.
MLStripper
MLStripper is a subclass of html.parser.HTMLParser. Unlike a simple regex-based approach, it properly parses the HTML structure, collecting data and entities while discarding tags.
It is used internally by the strip_tags function, which includes a safety mechanism (MAX_STRIP_TAGS_DEPTH = 50) to prevent infinite loops or DoS attacks from maliciously nested tags.
from django.utils.html import strip_tags
unsafe_html = "<b>Bold</b><script>alert('xss')</script><i>Italic</i>"
# Result: "Boldalert('xss')Italic"
clean_text = strip_tags(unsafe_html)
Note that MLStripper preserves character entities (like &) and numeric references (like {) by implementing handle_entityref and handle_charref, ensuring that the resulting text remains readable.