import json
import html
[docs]
class EditorJsRenderer:
def __init__(self, data, safe=True):
"""
:param data: JSON string o dict contenente i dati di Editor.js
:param safe: Se True, esegue escaping per prevenire XSS
"""
if isinstance(data, dict):
self.content = data
elif isinstance(data, str):
try:
self.content = json.loads(data)
except json.JSONDecodeError:
raise ValueError("EditorJsRenderer: invalid JSON string.")
else:
raise ValueError("EditorJsRenderer: data must be a dict or JSON string.")
self.safe = safe
[docs]
def render(self):
if "blocks" not in self.content:
return ""
return "".join(self.render_block(block) for block in self.content["blocks"])
[docs]
def render_block(self, block):
block_type = block.get("type")
renderer = getattr(self, f"render_{block_type}", self.render_unknown)
return renderer(block.get("data", {}))
[docs]
def escape(self, text):
return html.escape(text) if self.safe else text
[docs]
def render_paragraph(self, data):
text = data.get("text", "")
return f"<p>{text}</p>"
[docs]
def render_list(self, data):
def render_items(items):
html_items = ""
for item in items:
if isinstance(item, dict) and "items" in item:
# Nested list
nested_tag = "ul" if item.get("style") == "unordered" else "ol"
html_items += f"<li>{self.escape(item.get('content', ''))}{render_items(item['items'])}</li>"
else:
html_items += f"<li>{self.escape(item)}</li>"
return f"<{tag}>{html_items}</{tag}>"
tag = "ul" if data.get("style") == "unordered" else "ol"
items = data.get("items", [])
return render_items(items)
[docs]
def render_quote(self, data):
text = self.escape(data.get("text", ""))
caption = self.escape(data.get("caption", ""))
alignment = data.get("alignment", "left")
return f'<blockquote style="text-align: {alignment};"><p>{text}</p><footer>{caption}</footer></blockquote>'
[docs]
def render_code(self, data):
code = html.escape(data.get("code", ""))
return f"<pre><code>{code}</code></pre>"
[docs]
def render_image(self, data):
url = data.get("file", {}).get("url", "")
caption = self.escape(data.get("caption", ""))
return f'<figure><img src="{self.escape(url)}" alt="{caption}"><figcaption>{caption}</figcaption></figure>'
[docs]
def render_table(self, data):
rows = data.get("content", [])
has_headings = data.get("withHeadings", False)
if not rows:
return "<table></table>"
if has_headings and rows:
headings = rows[0]
rows = rows[1:]
html_headings = "".join(f"<th>{self.escape(cell)}</th>" for cell in headings)
html_rows = [f"<tr>{html_headings}</tr>"]
else:
html_rows = []
for row in rows:
html_cells = "".join(f"<td>{self.escape(cell)}</td>" for cell in row)
html_rows.append(f"<tr>{html_cells}</tr>")
if has_headings:
return f"""
<table>
<thead>{html_rows.pop(0)}</thead>
<tbody>{"".join(html_rows)}</tbody>
</table>
"""
else:
return f"<table><tbody>{''.join(html_rows)}</tbody></table>"
[docs]
def render_raw(self, data):
raw_html = data.get("html", "")
return raw_html
[docs]
def render_embed(self, data):
service = self.escape(data.get("service", ""))
embed_url = self.escape(data.get("embed", ""))
caption = self.escape(data.get("caption", ""))
if not embed_url:
return ""
return f"""
<figure class="embed-figure embed-{service}">
<div class="embed-responsive-wrapper">
<iframe src="{embed_url}" frameborder="0" allowfullscreen></iframe>
</div>
<figcaption>{caption}</figcaption>
</figure>
"""
[docs]
def render_divider(self, data):
return '<hr>'
[docs]
def render_unknown(self, data):
return f"<!-- Unsupported block type -->"