soup = BeautifulSoup(html, "lxml")
if soup.find(True) is None:
problems.append("No HTML elements found")
for tag in soup.find_all(["script", "iframe", "object", "embed", "form"]):
problems.append(f"Disallowed tag <{tag.name}> present")
for img in soup.find_all("img"):
if not (img.get("src") or "").strip():
problems.append("img without src")
leftovers = re.findall(r"%([A-Za-z0-9_]+)%", html)
if leftovers:
problems.append(f"Unsubstituted variables remain: {sorted(set(leftovers))[:10]}")
return problems
def html_to_pdf(html: str) -> bytes:
"""Real HTML → PDF conversion via WeasyPrint."""
try:
return WeasyHTML(string=html).write_pdf()
except Exception as e:
raise HtmlValidationError(f"PDF conversion failed: {e}") from e
def sanitize_preview(html: str) -> str:
"""Sanitize HTML for safe rendering in the in-app editor/preview (server-side)."""
import bleach
allowed_tags = list(bleach.ALLOWED_TAGS) + ["table", "thead", "tbody", "tr", "td", "th",
"img", "p", "span", "div", "h1", "h2", "h3",
"strong", "em", "a", "ul", "ol", "li", "br", "hr", "font", "center"]
allowed_attrs = {"a": ["href", "title"], "img": ["src", "alt", "width", "height"],
"*": ["style", "class", "align", "bgcolor", "width", "border", "cellpadding", "cellspacing"]}
protocols = ["http", "https", "mailto"]
return bleach.clean(html or "", tags=allowed_tags, attributes=allowed_attrs, protocols=protocols, strip=False)
def sanitize_html_for_email(html: str) -> str:
"""Remove scripts/forms from outgoing email HTML (keep email-safe markup)."""
return sanitize_preview(html)
def generate_attachment_filename(kind: str, invoice_number: str) -> str:
"""Unique, deterministic filenames like Invoice_INV-XXXX.pdf per campaign rules."""
---FILENAME-GEN---
134:def generate_attachment_filename(kind: str, invoice_number: str) -> str:
135- """Unique, deterministic filenames like Invoice_INV-XXXX.pdf per campaign rules."""
136- inv = re.sub(r"[^A-Za-z0-9-]", "", invoice_number or "")
137- if kind == "invoice":
138- return f"Invoice_{inv or 'INV' + secrets.token_hex(3)}.pdf"
139- if kind == "order":
140- return f"Order_{inv or 'ORD' + secrets.token_hex(3)}.pdf"
141- if kind == "receipt":
142- return f"Receipt_{inv or 'RF' + secrets.token_hex(3)}.pdf"