GithubHelp home page GithubHelp logo

felixschwarz / mjml-python Goto Github PK

View Code? Open in Web Editor NEW
71.0 4.0 14.0 245 KB

Python implementation for MJML - a framework that makes responsive-email easy

License: MIT License

Python 100.00%
mjml python email responsive-email responsive

mjml-python's Issues

Add missing MJML components

The following MJML components are currently missing:

  • mjml-accordion
  • mjml-carousel
  • mjml-head-breakpoint
  • mjml-head-html-attributes
  • mjml-hero
  • mjml-navbar
    • mjml-navbar-link
  • mjml-social
  • mjml-spacer
  • mjml-wrapper

I'll begin work on a PR and check off components as I work my way through the above list.

Add json format support

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:

  • is it OK to add this feature?
  • for input, the API is not exactly the same as in js (python accepts a file descriptor, whereas js gets a string), is it OK to accept a dict for json-like format?

add changelog

We should have a changelog file to mention changes for non-git users.

css-class does not work with mj-section

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',
  })}>

https://github.com/mjmlio/mjml/blob/988819de3375867c09585d28f555166b97415200/packages/mjml-section/src/index.js#L414

I'll work on a PR and regression test.

extend functionality to handle "welcome-email" from email-templates repo

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.

mj-attributes with mj-body leads to empty body

With 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.

Call with mj-body

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": []}

Call with mj-html instead

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": []}

release planning for 0.9

@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.

Type hints for public interface?

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.

mj-include not supported

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.

Whitespace gets stripped between text & link inside <mj-text />

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

Unable to render template

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  &copy;  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.

support CSS inlining

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.

TypeError in mj_section.py

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.

Escaped HTML tags are "un-escaped" when rendering HTML

Consider:

from mjml import mjml_to_html

result = mjml_to_html("""
<mjml>
  <mj-body>
    <mj-text>
      Pretty unsafe: &lt;script&gt;
    </mj-text>
  </mj-body>
</mjml>""")

print(result["html"])

In the resulting HTML output, the formerly HTML-escaped < (&lt;) and > (&gt) 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

Child selector styles not being applied

.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 &gt;), 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.

Use mjml-stub inside a script

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.

Orphans opening tags from HTML `<br>`

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.

  1. Should I clean them up before using mjml-stub?
  2. Can I try to fix the lib to accept them?

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.