felixschwarz / mjml-python Goto Github PK
View Code? Open in Web Editor NEWPython implementation for MJML - a framework that makes responsive-email easy
License: MIT License
Python implementation for MJML - a framework that makes responsive-email easy
License: MIT License
The following MJML components are currently missing:
I'll begin work on a PR and check off components as I work my way through the above list.
The javascript version of mjml handles json MJML both as input (if you give a dict to the mjml2html
function) and as an output (in a json
field).
Before sending you a PR:
We should have a changelog file to mention changes for non-git users.
The logic here is backwards from what it is in upstream:
wrapper_class = self.get_attr('css-class') if self.isFullWidth() else None
https://github.com/FelixSchwarz/mjml-stub/blob/main/mjml/elements/mj_section.py#L202
<div ${this.htmlAttributes({
class: this.isFullWidth() ? null : this.getAttribute('css-class'),
style: 'div',
})}>
I'll work on a PR and regression test.
Lower priority, but we should port mjml-validator as well: https://documentation.mjml.io/#validating-mjml
The upstream project also hosts a couple of example templates in the email-templates repo. One of these examples is welcome-email.mjml but that template can not processed currently due to shortcomings (even after PR #2) in this Python port:
File "…/mjml/mjml2html.py", line 168, in mjml_to_html
content = processing(mjBody, bodyHelpers, applyAttributes)
File "…/mjml/mjml2html.py", line 74, in processing
return component.render()
File "…/mjml/elements/mj_body.py", line 37, in render
children_str = self.renderChildren()
File "…/mjml/elements/_base.py", line 144, in renderChildren
output += renderer(component)
File "…/mjml/elements/_base.py", line 86, in <lambda>
renderer = lambda component: component.render()
File "…/mjml/elements/mj_section.py", line 92, in render
return self.renderSimple()
File "…/mjml/elements/mj_section.py", line 98, in renderSimple
section = self.renderSection()
File "…/mjml/elements/mj_section.py", line 145, in renderSection
{self.renderWrappedChildren()}
File "…/mjml/elements/mj_section.py", line 188, in renderWrappedChildren
{self.renderChildren(children, renderer=render_child)}
File "…/mjml/elements/_base.py", line 144, in renderChildren
output += renderer(component)
File "…/mjml/elements/mj_section.py", line 178, in render_child
{component.render()}
File "…/mjml/elements/mj_column.py", line 122, in render
column_str = self.renderColumn() if (not self.hasGutter()) else self.renderGutter()
File "…/mjml/elements/mj_column.py", line 233, in renderColumn
{self.renderChildren(children, renderer=render_child)}
File "…/mjml/elements/_base.py", line 144, in renderChildren
output += renderer(component)
File "…/mjml/elements/mj_column.py", line 220, in render_child
{component.render()}
File "…/mjml/elements/mj_text.py", line 54, in render
return self._render_content()
File "…/mjml/elements/mj_text.py", line 69, in _render_content
content_html = self.getContent() + children_html
File "…/mjml/core/api.py", line 62, in getContent
return self.content.strip()
AttributeError: 'NoneType' object has no attribute 'strip'
I noticed the upstream JS code complains about some illegal attributes and the mjml template in general looks pretty convoluted:
Line 2 of welcome-email.mjml (mj-body) — Attribute font-size is illegal
Line 8 of welcome-email.mjml (mj-section) — Attribute vertical-align is illegal
Line 10 of welcome-email.mjml (mj-text) — Attribute padding-top has invalid value: 50 for type Unit, only accepts (px, %) units and 1 value(s)
Line 13 of welcome-email.mjml (mj-section) — Attribute padding-top has invalid value: 20 for type Unit, only accepts (px, %) units and 1 value(s)
Nevertheless I think the Python code should be able to handle that (unless the Python code becomes too messy).
Also there is an upstream pull request to clean up that template by @willhertz but even after applying that change the Python code fails.
File "…/mjml/mjml2html.py", line 167, in mjml_to_html
globalDatas.headRaw = processing(mjHead, headHelpers)
File "…/mjml/mjml2html.py", line 72, in processing
return component.handler()
File "…/mjml/elements/head/mj_head.py", line 9, in handler
return self.handlerChildren()
File "…/mjml/elements/head/_head_base.py", line 28, in handlerChildren
return tuple(map(handle_children, childrens))
File "…/mjml/elements/head/_head_base.py", line 22, in handle_children
component.handler()
File "…/mjml/elements/head/mj_attributes.py", line 14, in handler
tagName = child['tagName']
TypeError: 'NoneType' object is not subscriptable
The Python code must be able to handle the updated template as the upstream validator does not complain about any invalid attributes there.
Hi @FelixSchwarz!
There was broken changes in css_inline after v0.9.0 interface was changed and now it does not allow inline_style_tags attribute.
Last working v0.9.0
New v0.10.0
Possible solutions:
css_inline.CSSInliner
interface end test how it worksWith mjml==0.10.0 I get the following unexpected results when passing in an mj-body
tag inside mj-attributes
within mj-head
. Also, if I try this on the MJML test page the body properly gets a background color.
mjml_to_html('''<mjml>
<mj-head>
<mj-attributes>
<mj-body background-color="#F6F6F6" />
</mj-attributes>
</mj-head>
<mj-body>
<mj-text>
TESTTESTTESTTESTTESTTESTTEST
</mj-text>
</mj-body>
</mjml>''')
Result has no content inside body:
{"html": "<!doctype html>\n<html xmlns="http://www.w3.org/1999/xhtml\" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">\n \n <title>\n \n </title>\n \n <meta http-equiv="X-UA-Compatible" content="IE=edge">\n \n <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1">\n <style type="text/css">\n #outlook a { padding:0; }\n body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }\n table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }\n img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }\n p { display:block;margin:13px 0; }\n </style>\n \n \n \n \n <style type="text/css">\n\n \n \n </style>\n <style type="text/css"></style>\n \n \n <body style="word-spacing:normal;">\n \n <div style="">\n \n", "errors": []}
I’m not sure mj-html is even supported in mjml, but you can also just remove it entirely and get the same result below:
mjml_to_html('''<mjml>
<mj-head>
<mj-attributes>
<mj-html background-color="#F6F6F6" />
</mj-attributes>
</mj-head>
<mj-body>
<mj-text>
TESTTESTTESTTESTTESTTESTTEST
</mj-text>
</mj-body>
</mjml>''')
Result has expected TESTTEST… content inside body:
{"html": "<!doctype html>\n<html xmlns="http://www.w3.org/1999/xhtml\" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">\n \n <title>\n \n </title>\n \n <meta http-equiv="X-UA-Compatible" content="IE=edge">\n \n <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1">\n <style type="text/css">\n #outlook a { padding:0; }\n body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }\n table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }\n img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }\n p { display:block;margin:13px 0; }\n </style>\n \n \n \n \n <link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700\" rel="stylesheet" type="text/css">\n <style type="text/css">\n @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);\n </style>\n \n\n \n \n <style type="text/css">\n\n \n \n </style>\n <style type="text/css"></style>\n \n \n <body style="word-spacing:normal;">\n \n <div style=""><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000">TESTTESTTESTTESTTESTTESTTEST\n \n", "errors": []}
I believe this is a bug. Setting something like this:
<mj-attributes>
<mj-column padding="0" />
</mj-attributes>
Raises the error:
TypeError: html_args() takes 1 positional argument but 2 were given
Looks like this line should unpack the dictionary with **
:
https://github.com/FelixSchwarz/mjml-stub/blob/9f87b517ebd4bace7881084c353410a262da527b/mjml/elements/mj_column.py#L185
@caseyjhol We have css_inline
pinned to >= 0.11, < 0.12
but the most recent version is 0.13.0
already. I just installed the newer version and all tests still pass. Any objections of removing the upper limit completely?
I realize this is still a stub implementation, but we're getting closer to full feature parity. I think it would be helpful to have a more descriptive name. Following this, we should be sure we can get this added to the "Ports and Language Bindings" section in the MJML documentation: https://documentation.mjml.io/#ports-and-language-bindings.
@caseyjhol: Is there anything else you would like to add to this repo soon-ish or should we rename the repo and release 0.9?
I remember you were working on a different css inlining mechanism.
No hurry from my side but it might be nice to release all the work you did so far.
Even its just an alpha/beta please add this package to https://pypi.org/ in order to easy up the installation process.
Thanks for the really useful library!
It would be even better if it came with type annotations for its public interface (i.e. just mjml_to_html
, as far as I can tell) and a py.typed marker to allow dependent projects to type check their usage of the library.
Until now we just relied on our templating engine to achieve a similar effect like <mj-include>
(reference documentation) but feedback from web designers is that they'd like <mj-include>
because it allows them to still use Atom's mjml preview feature.
This appears to be a bug inside mj-text
rendering. If I run this code:
from mjml import mjml_to_html
print(mjml_to_html('<mjml><mj-body><mj-text>Hello <a href="#">world</a></mj-text></mj-body></mjml>')['html'].split('\n')[-3])
I get this output:
<div style=""><div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000">Hello<a href="#">world</a></div></div>
The space between "Hello" and "<a" gets stripped during the parsing.
I haven’t been able to dive through the code enough, yet, but seems like the first children element inside this mj-text
should have elem["tail"] == " "
https://github.com/FelixSchwarz/mjml-stub/blob/main/mjml/elements/mj_text.py#L104
When running mjml-python I get this error that I do not get compared to running : ./node_modules/.bin/mjml index.mjml input.mjml -o output.html
Value Example:
<mjml>
<mj-head>
<mj-title>Your order has been cancelled!</mj-title>
<mj-attributes>
<mj-all padding="0px"></mj-all>
<mj-text font-family="Roboto, Helvetica, sans-serif" font-size="14px"></mj-text>
<mj-table font-family="Roboto, Helvetica, sans-serif" font-size="14px"></mj-table>
<mj-section background-color="#ffffff"></mj-section>
<mj-wrapper background-color="#ffffff"></mj-wrapper>
</mj-attributes>
<mj-style inline="inline">
.summary-header {
color: #85807f;
font-size: 16px;
}
.delimited {
border-bottom: 1px solid #b8b8b8;
}
.delimited > td {
padding-bottom: 16px;
}
.extra-info {
margin-top: 8px;
}
.cost {
font-weight: bold;
padding-left: 1em;
text-align: right;
vertical-align: top;
white-space: nowrap;
}
.total {
text-align: right;
font-size: 24px;
font-weight: bold;
}
table {
margin: 0 auto;
border-collapse: collapse;
font-family: Roboto, Helvetica, sans-serif;
}
td {
padding-top: 16px;
}
.bottom-spacer {
height: 3rem;
}
</mj-style>
<mj-style inline="inline">
.footer-text div {
font-size: 11px !important;
color: #000000 !important;
text-align: center !important;
}
</mj-style>
</mj-head>
<mj-body background-color="#dddddd">
<!-- Email Header -->
<mj-section>
<mj-column width="100%">
<mj-image src="https://www.foodandwine.com/thmb/DI29Houjc_ccAtFKly0BbVsusHc=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc()/crispy-comte-cheesburgers-FT-RECIPE0921-6166c6552b7148e8a8561f7765ddf20b.jpg" alt="" padding="0px"></mj-image>
</mj-column>
</mj-section>
<!-- Email Content -->
<mj-wrapper padding="20px">
<mj-section>
<mj-raw>{% for item in items %} </mj-raw>
<mj-column width="80%">
<mj-text>{{ item.title }}</mj-text>
<mj-image src="{{ item.image }}" alt="" padding="0px"></mj-image>
</mj-column>
<mj-raw>{% endfor %}</mj-raw>
</mj-section>
</mj-wrapper>
<!-- Footer -->
<mj-section padding-bottom="20px">
<mj-column>
<mj-text css-class="footer-text" padding-top="40px">
Copyright © All rights reserved.
</mj-text>
<mj-text css-class="footer-text" padding-top="10px">
<a href="/terms">Terms and Conditions</a>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
If I pass the string directly I get:
Traceback (most recent call last):
File "lib/python3.9/site-packages/mjml/mjml2html.py", line 79, in mjml_to_html
mjBody = mjml_root('mj-body')[0]
TypeError: 'NoneType' object is not callable
If I save the value to a file then I get:
results = mjml_to_html(rendered_html)
File "lib/python3.9/site-packages/mjml/mjml2html.py", line 230, in mjml_to_html
raise ImportError('CSS inlining is an optional feature. Run `pip install -e ".[css_inlining]"` to install the required dependencies.') # noqa: E501
ImportError: CSS inlining is an optional feature. Run `pip install -e ".[css_inlining]"` to install the required dependencies.
When I run pip install, I get:
ERROR: Could not find a version that satisfies the requirement css_inlining (from versions: none)
ERROR: No matching distribution found for css_inlining
Again, I don't get this error when using node.
It would be nice to support CSS inlining (similar to mjml upstream). There are some Python libraries which might be helpful:
Based on the number of github stars, forks I guess premailer seems to be the most popular library.
documents with any of those tags raise an AttributeError: 'dict' object has no attribute 'tagName'
btw I more meaningful error message would be helpful
The "innerXML" functionality for mj-raw
is marked as "ugly" and should be cleaned up.
there are multiple approaches to solve it:
https://stackoverflow.com/questions/4624062/get-all-text-inside-a-tag-in-lxml
https://stackoverflow.com/questions/11122397/how-do-i-get-the-whole-content-between-two-xml-tags-in-python
I don't mind which one to use
see #21 (comment)
Hi,
i got the following error:
File "/Users/handorf/.pyenv/versions/3.10.2/envs/ga3-exporter/lib/python3.10/site-packages/mjml/elements/mj_section.py", line 342, in renderWithBackground
vY = self._calc_origin_pos_value(is_y=False, bg_pos=bgPosY)
TypeError: MjSection._calc_origin_pos_value() got an unexpected keyword argument 'is_y'
seems to be a simple typo.
Consider:
from mjml import mjml_to_html
result = mjml_to_html("""
<mjml>
<mj-body>
<mj-text>
Pretty unsafe: <script>
</mj-text>
</mj-body>
</mjml>""")
print(result["html"])
In the resulting HTML output, the formerly HTML-escaped <
(<
) and >
(>
) are "un-escaped", so the rendered HTML actually contains Pretty unsafe: <script>
.
Why does this happen? This reverses the user's safety measures and can be dangerous.
The MJML reference implementation doesn't do this and correctly keeps such escape sequences untouched: https://mjml.io/try-it-live/fvvhZhdu9V
New upstream release v4.13.0 on 2022-08-19
We should add the ability to create and register custom components (as well as the ability to extend existing components).
.parent {
overflow: hidden;
box-shadow: 0 4px 10px 0px rgba(0, 0, 0, 0.1);
}
.parent > table > tbody > tr > td,
.parent > table > tbody > tr > td > div {
border-radius: 3px;
}
<div class="parent">
<table>
<tbody>
<tr>
<td>
<div>
Test
</div>
</td>
</tr>
</tbody>
</table>
</div>
Actual:
<div class="parent" style="overflow: hidden; box-shadow: 0 4px 10px 0px rgba(0, 0, 0, 0.1);">
<table>
<tbody>
<tr>
<td>
<div>
Test
</div>
</td>
</tr>
</tbody>
</table>
</div>
Expected:
<div class="parent" style="overflow: hidden; box-shadow: 0 4px 10px 0px rgba(0, 0, 0, 0.1);">
<table>
<tbody>
<tr>
<td style="border-radius: 3px;">
<div style="border-radius: 3px;">
Test
</div>
</td>
</tr>
</tbody>
</table>
</div>
Originally I thought this was an issue with css-inline, but it's actually an issue on our end. BeautifulSoup is escaping the carets (>
becomes >
), preventing child selectors from getting applied. We can either use html.unescape
in mjStyle, or perhaps we can decode from BeautifulSoup using formatter=None
. It might be better to limit this to mjStyle only and use html.unescape
so as not to affect the rest of the HTML. I'm still investigating the best approach.
currently fails with:
lxml.etree.XMLSyntaxError: Entity 'copy' not defined, line 853, column 19
see also:
https://stackoverflow.com/questions/31210655/nbsp-text-not-handled-by-the-lxml
Hi,
I would like to be able to do something like this
from mjml import parser
with open(file.mjml, 'r') as mjml_file:
html = parser(mjml_file)
Is that possible?
Cheers.
From Passport (mailjet's editor) I receive some <br>
HTML tags inside mjml-text
blobs. In XHTML, those should actually be <br/>
and it currently make the mjml-stub library choke.
Note that mjml.io is able to work with those tags
https://mjml.io/try-it-live/gb2kuVgCdRn
The HTML is generated properly with br tags, there are no errors mentioned. Only the syntax highlighter gets lost.
I'd be in favor of doing 2, but don't want to start before your approval.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.