Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion zpretty/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
16 changes: 16 additions & 0 deletions zpretty/prettifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class ZPrettifier:
_ampersand_marker = str(uuid4())
_cdata_marker = str(uuid4())
_cdata_pattern = re.compile(r"<!\[CDATA\[(.*?)\]\]>", re.DOTALL)
_class_marker_name = "data-zpretty-class"
_class_marker = " data-zpretty-class="
_class_pattern = " class="
_doctype_marker = f"<!DOCTYPE foo-{str(uuid4())}>"
_doctype_pattern = re.compile(
r"(<!DOCTYPE[^>[]*(\[[^]]*\])?>)", re.IGNORECASE | re.DOTALL
Expand Down Expand Up @@ -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, "&")
Expand Down Expand Up @@ -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.
Expand Down
24 changes: 24 additions & 0 deletions zpretty/tests/original/sample_pt.pt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@
Foo
Bar
</pre>
<section class="class-a">
<div class="
class-b
class-c

${'class-d' if True else ''}
button button-important
pil pil-${'blue' if True else ''}

pat-inject
"
data-pat-inject="
source: .main-content .important-stuff;
target: .class-b .target
&amp;&amp;
source: .header #logo;
target: .logo;
${'add: class-d' if True else ''}
history:;
"
>
okay
</div>
</section>
<form action="#"
method="post"
>
Expand Down
Loading