Repository: collective/zpretty

Issue:
problem in combination of "&" and ";" in attributes.
Affects: zpretty 3.1.0a1

## problem 1

Running `zpretty` without any options on this input:
```
<a href="a=b&c=d"></a>
<div tal:define="var view/attr;"></div>
```

Produces this output:
```
<a href="a=b&c=d"></a>
<div tal:define="var view/attr;"></a>
```

Or with even more stripped down input:

```
<a attr="&"></a>
<div attr=";"></div>
```

produces:
```
<a attr="&"></a>
<div attr=";"></a>
```

## problem 2

This input:
```
<a tal:attributes="href python:'&';"></a>
```

Produces this output:
```
<a tal:attributes="
     href python:'&';;
   "></a>
```

There is an extra semicolon which shouldn't be there.




Discussion:

Patch:
diff --git a/HISTORY.md b/HISTORY.md
index c475ebf..64da5b9 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,15 +2,15 @@
 
 ## 3.1.0a2 (unreleased)
 
-
-- Nothing changed yet.
-
+- Improve the regular expression that matches the entities
+  (Fixes #130)
+  [ale-rt]
 
 ## 3.1.0a1 (2023-05-04)
 
 - Add command line parameters to include/exclude files and folders
   (Implements #96)
-  [ale-rt]  
+  [ale-rt]
 - Be tolerant with characters forbidden in XML when dealing with tal attributes
   (See #125)
   [ale-rt]
diff --git a/zpretty/prettifier.py b/zpretty/prettifier.py
index e28e7f7..9d0a15e 100644
--- a/zpretty/prettifier.py
+++ b/zpretty/prettifier.py
@@ -81,12 +81,16 @@ class ZPrettifier(object):
         # Get all the entities in the text and replace them with a marker
         # The text might contain undefined entities that BeautifulSoup
         # will strip out.
-        entities = set(re.findall(r"&[^;]+;", text))
-        for entitiy in entities:
+        entities = {
+            _[0]
+            for _ in re.findall(
+                r"(&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});)", text, re.IGNORECASE
+            )
+        }
+        for entity in entities:
             marker = str(uuid4())
-            self._entity_mapping[entitiy] = marker
-            text = text.replace(entitiy, marker)
-
+            self._entity_mapping[entity] = marker
+            text = text.replace(entity, marker)
         return "\n".join(
             line if line.strip() else self._newlines_marker
             for line in text.splitlines()


Test patch:
diff --git a/zpretty/tests/test_zpretty.py b/zpretty/tests/test_zpretty.py
index 016b847..92b6ee2 100644
--- a/zpretty/tests/test_zpretty.py
+++ b/zpretty/tests/test_zpretty.py
@@ -185,6 +185,12 @@ class TestZpretty(TestCase):
         self.assertPrettified('<root a=";&amp;" />', '<root a=";&amp;"></root>\n')
         self.assertPrettified('<root a="&amp;;" />', '<root a="&amp;;"></root>\n')
 
+    def test_ampersand_and_column_in_separate_attrs(self):
+        self.assertPrettified(
+            '<foo a="&" />\n<tal:bar b=";" />',
+            '<foo a="&amp;"></foo>\n<tal:bar b=";" />\n',
+        )
+
     def test_sample_html(self):
         self.prettify("sample_html.html")