diff --git a/HISTORY.md b/HISTORY.md
index 4e0dc5c..a1bf12f 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,8 +2,10 @@
## 4.0.1 (unreleased)
-
-- Nothing changed yet.
+- keep line breaks: Do not reformat class attribute values.
+ This allows for multi-line or otherwise manually strangely formatted
+ class attributes.
+ [thet]
## 4.0.0 (2026-04-10)
diff --git a/zpretty/attributes.py b/zpretty/attributes.py
index 05ec795..7ab080a 100644
--- a/zpretty/attributes.py
+++ b/zpretty/attributes.py
@@ -115,7 +115,8 @@ def sort_attributes(self, name):
"""
if name.startswith("xmlns"):
return (0, name)
- if name in ("class", "id"):
+ # data-zpretty-class being the class replacement to avoid reformatting.
+ if name in ("class", "data-zpretty-class", "id"):
return (100, name)
if name.startswith("data"):
return (300, name)
diff --git a/zpretty/prettifier.py b/zpretty/prettifier.py
index 097d89d..03dfbe2 100644
--- a/zpretty/prettifier.py
+++ b/zpretty/prettifier.py
@@ -23,6 +23,9 @@ class ZPrettifier:
_ampersand_marker = str(uuid4())
_cdata_marker = str(uuid4())
_cdata_pattern = re.compile(r"", re.DOTALL)
+ _class_marker_name = "data-zpretty-class"
+ _class_marker = " data-zpretty-class="
+ _class_pattern = " class="
_doctype_marker = f""
_doctype_pattern = re.compile(
r"([]*(\[[^]]*\])?>)", re.IGNORECASE | re.DOTALL
@@ -50,6 +53,14 @@ def __init__(self, filename="", text="", encoding="utf8"):
# in the attributes so that bogus ones can be escaped
for el in soup.descendants:
attrs = getattr(el, "attrs", {})
+
+ # Restore class attributes.
+ # Beautiful soup changes the formatting of class attributes into a
+ # single-line attribute.
+ if self._class_marker_name in attrs:
+ attrs["class"] = attrs.get(self._class_marker_name)
+ del attrs[self._class_marker_name]
+
for key, value in attrs.items():
if self._ampersand_marker in value:
attrs[key] = value.replace(self._ampersand_marker, "&")
@@ -122,6 +133,11 @@ def _prepare_text(self):
text = re.sub(self._cdata_pattern, self._cdata_marker, text)
text = re.sub(self._doctype_pattern, self._doctype_marker, text)
+ # Replace class attributes.
+ # Beautiful soup changes the formatting of class attributes into a
+ # single-line attribute, which we want to prevent.
+ text = re.sub(self._class_pattern, self._class_marker, text)
+
# Get all the entities in the text and replace them with a marker
# The text might contain undefined entities that BeautifulSoup
# will strip out.
diff --git a/zpretty/tests/original/sample_pt.pt b/zpretty/tests/original/sample_pt.pt
index c563553..b657e10 100644
--- a/zpretty/tests/original/sample_pt.pt
+++ b/zpretty/tests/original/sample_pt.pt
@@ -39,6 +39,30 @@
Foo
Bar
+