Code Monkey home page Code Monkey logo

mjml-net's Introduction

MJML.NET

A blazingly-fast unofficial port of MJML 4 (by MailJet) to .NET 6 & 7.

Nuget

Introduction

MJML is a markup language created by Mailjet and designed to reduce the pain of coding a responsive email. Its semantic syntax makes the language easy and straightforward while its rich standard components library shortens your development time and lightens your email codebase. MJML’s open-source engine takes care of translating the MJML you wrote into responsive HTML.

You can find out more about MJML 4 from the official website.

| Official Website | Official Documentation | Official Onboarding |

Usage

Firstly, you'll need to reference the MJML.NET NuGet Package into your project.

PM > Install-Package Mjml.Net

Secondly, include MJML.NET namespace into your project.

using Mjml.Net;

Finally, the boilerplate code.

public static void Main (string[] args) {
    var mjmlRenderer = new MjmlRenderer();

    string text = @"
        <mjml>
            <mj-head>
                <mj-title>Hello World Example</mj-title>
            </mj-head>
            <mj-body>
                <mj-section>
                    <mj-column>
                        <mj-text>
                            Hello World!
                        </mj-text>
                    </mj-column>
                </mj-section>
            </mj-body>
        </mjml>";

    var options = new MjmlOptions {
        Beautify = false
    };

    var (html, errors) = mjmlRenderer.Render(text, options);
}

Before Version 2.0.0: XML Clenanup

MJML is not necessarily valid XML. To allow the XML parser to work properly, the MJML string needs to be sanitized before you render it. You can use the method FixXML for that. If you store the MJML text in the database, it is store to keep the original version and the sanitized version. With version 2.0 we moved to html parser that follows the official standards and can therefore accept any kind of HTML input. More about this under "Known Issues".

public static void Main (string[] args) {
    var mjmlRenderer = new MjmlRenderer();

    string text = @"
          <mjml>
              <mj-head>
                  <mj-title>Hello World Example</mj-title>
              </mj-head>
              <mj-body>
                  <mj-section>
                      <mj-column>
                          <mj-text>
                              Hello World!
                          </mj-text>
                      </mj-column>
                  </mj-section>
              </mj-body>
          </mjml>";

    var options = new MjmlOptions {
        Beautify = false
    };

    var xml = mjmlRenderer.FixXML(text, options);

    var (html, errors) = mjmlRenderer.Render(xml, options).Html;
}

Options

You can also specify options to the MJML parser.

Name Data Type Default Description
KeepComments bool false True to keep comments.
Breakpoint string 480px The default breakpoint to switch to mobile view.
Styles Style[]? [] A list of custom styles.
ForceOWAQueries bool false True to enable media queries for OWA.
Beautify bool true True to beatify the HTML. Impacts performance (slower).
Minify bool false True to minify the HTML.
Lax bool false In lax mode some errors in the XML will be fixed. Only work when the MJML is passed in as string. Do not turn this on in production, because it can hurt performance.
IdGenerator IIdGenerator Preset The ID generator to create random values for attributes like Ids.
Fonts IReadOnlyDictionary<string, Font[]> Preset A list of supported default fonts.
XmlEntities IReadOnlyDictionary<string, string> Preset A list of supported XML entities (deprecated with version 1.16.0)
ValidatorFactory IValidatorFactory? null The current validator, which also defines the validation mode.
FileLoader IFileLoader? null The file path loader for <mj-include path="..." type="..."> which handles loading the files from the specified path attribute. For example, SqlDatabaseFileLoader, InMemoryFileLoader, DiskFileLoader, etc...

Supported Components

MJML.NET tries to implement all functionality 1-2-1 with the MJML 4 project. However, due to JavaScript not being a typed language this means there has been considerate refactoring to the code to make it more aligned with C# typed requirements.

Type Component Implemented Tests State
Core mjml Feature Complete
Core mj-head Feature Complete
Core mj-body Feature Complete
Core mj-include Feature Complete
Head mj-attributes Feature Complete
Head mj-class Feature Complete
Head mj-all Feature Complete
Head mj-breakpoint Feature Complete
Head mj-font Feature Complete
Head mj-html-attributes Not Planned
Head mj-preview Feature Complete
Head mj-style Feature Complete
Head mj-title Feature Complete
Body mj-accordion Feature Complete
Body mj-button Feature Complete
Body mj-carousel Feature Complete
Body mj-column Feature Complete
Body mj-divider Feature Complete
Body mj-group Feature Complete
Body mj-hero Feature Complete
Body mj-image Feature Complete
Body mj-navbar Feature Complete
Body mj-raw Feature Complete
Body mj-section Feature Complete
Body mj-social Feature Complete
Body mj-spacer Feature Complete
Body mj-table Feature Complete
Body mj-text Feature Complete
Body mj-wrapper Feature Complete

Benchmarks

Performance was one of the key focuses for this project. We're aiming to support high througput while mainintaing low memory footprint. Below are the benchmarks for every public MJML template compiled (beautified and minified).

Important: These tests do not include loading or saving the template from/to disk.

If you'd like to run the benchmarks your self then you can run the Mjml.Net.Benchmarks project in release mode.

BenchmarkDotNet

> .\Mjml.Net.Benchmark.exe

Profiler

> .\Mjml.Net.Benchmark.exe -p -i 100

Benchmark Specs

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19043.1586 (21H1/May2021Update)
Intel Core i7-4790 CPU 3.60GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.101
  [Host]     : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT
  Job-YRBKPS : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT

Jit=RyuJit  Platform=X64  IterationCount=100 

Benchmark Key

MjmlTemplateFilePath : Value of the 'MjmlTemplateFilePath' parameter
Mean                 : Arithmetic mean of all measurements
Error                : Half of 99.9% confidence interval
StdDev               : Standard deviation of all measurements
Median               : Value separating the higher half of all measurements (50th percentile)
Allocated            : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
1 us                 : 1 Microsecond (0.000001 sec)

Benchmark Results

All times are in μs (1ms = 1000 μs)

Method MjmlTemplateFilePath Mean Error StdDev Median Allocated
Beautified Amario.mjml 924.9 7.24 20.42 920.8 810 KB
Minified Amario.mjml 980.1 25.57 75.39 974.1 810 KB
Beautified Arturia.mjml 438.2 9.29 27.38 438.9 384 KB
Minified Arturia.mjml 430.0 8.62 25.28 430.9 384 KB
Beautified Austin.mjml 762.0 13.06 38.30 759.7 615 KB
Minified Austin.mjml 756.0 13.42 39.16 756.3 615 KB
Beautified BlackFriday.mjml 188.3 4.39 12.94 189.2 186 KB
Minified BlackFriday.mjml 194.7 4.99 14.31 197.0 186 KB
Beautified Card.mjml 290.3 7.10 20.04 290.0 259 KB
Minified Card.mjml 284.3 5.82 17.17 287.0 259 KB
Beautified Christmas.mjml 413.5 8.55 25.09 417.3 360 KB
Minified Christmas.mjml 401.5 7.19 21.20 403.1 360 KB
Beautified HappyNewYear.mjml 156.3 3.30 9.72 156.4 166 KB
Minified HappyNewYear.mjml 155.2 3.46 10.21 155.2 166 KB
Beautified ManyHeroes.mjml 705.7 13.98 40.33 700.0 830 KB
Minified ManyHeroes.mjml 667.6 9.15 26.41 653.7 830 KB
Beautified OnePage.mjml 414.9 8.47 24.58 403.5 399 KB
Minified OnePage.mjml 424.5 9.15 26.83 416.4 399 KB
Beautified Proof.mjml 184.2 2.90 8.33 181.6 213 KB
Minified Proof.mjml 189.0 3.53 9.90 186.8 213 KB
Beautified Racoon.mjml 928.5 8.38 23.78 921.1 810 KB
Minified Racoon.mjml 926.7 11.90 32.98 912.9 810 KB
Beautified Reactivation.mjml 261.1 4.12 12.07 257.7 259 KB
Minified Reactivation.mjml 263.5 5.63 16.34 260.6 259 KB
Beautified RealEstate.mjml 707.6 11.65 33.43 701.2 623 KB
Minified RealEstate.mjml 725.4 20.80 58.66 708.5 623 KB
Beautified Recast.mjml 674.6 7.13 20.12 666.0 619 KB
Minified Recast.mjml 661.1 3.86 10.63 659.8 619 KB
Beautified Receipt.mjml 275.5 2.64 7.26 272.8 263 KB
Minified Receipt.mjml 274.5 1.93 5.50 272.8 263 KB
Beautified Referral.mjml 135.3 0.75 2.19 134.9 136 KB
Minified Referral.mjml 133.4 0.86 2.43 132.8 136 KB
Beautified SpheroDroids.mjml 376.4 2.28 6.43 374.7 351 KB
Minified SpheroDroids.mjml 374.5 3.66 10.01 371.8 351 KB
Beautified SpheroMini.mjml 395.9 4.43 12.79 390.4 368 KB
Minified SpheroMini.mjml 395.4 2.11 6.13 394.3 368 KB
Beautified UGGRoyale.mjml 1,126.0 6.03 17.09 1,122.9 1,004 KB
Minified UGGRoyale.mjml 1,154.0 10.93 31.89 1,142.2 1,004 KB
Beautified Welcome.mjml 125.1 0.89 2.49 124.1 131 KB
Minified Welcome.mjml 126.3 1.51 4.42 125.3 131 KB
Beautified Worldly.mjml 637.6 3.21 9.22 634.9 572 KB
Minified Worldly.mjml 629.6 4.24 11.60 627.9 572 KB

Known Issues

Before Version 1.17.0: Unknown HTML Entities

We are aware with an issue where by using HTML Character Entities (e.g. &copy;) are unknown and throw an unhandled exception during the rendering of the MJML document. This is because we use XmlReader as the main driver for parsing the MJML document.

The solution is to change the HTML Character Entity Names (e.g. &copy;) to there corresponding HTML Character Entity Number (e.g. &#169;) in the MJML document. Here are some of the common HTML Character Entities:

Result Description Entity Name Entity Number
non-breaking space &nbsp; &#160;
< less than &lt &#60;
> greater than &gt; &#62;
& ampersand &amp; &#38;
" double quotation mark &quot; &#34;
' single quotation mark (apostrophe) &apos; &#39;
© copyright &copy; &#169;
® registered trademark &reg; &#174;
registered trademark &trade; &#8482

Before Version 2.0.0: Non-encoded URL

We are aware of an issue with non-encoded URL's being recognized as character entities leading to an exception. This is because we use XmlReader as the main driver for parsing the MJML document. This solution is to URL encode all of the URLs in the template or turn on the lax mode with options.Lax = true.

Before Version 2.0.0: Html tag that is not XML.

Some HTML tags are not valid HTML. Especially line break (<br>). Convert these tags to XHTML (<br></br> or <br />) or turn on the lax mode with options.Lax = true.

Contribution

We really appreciate any contribution to the project to help provide a native version of MJML to C#. Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Appreciations

Once again, it's good to share some appreciation to the projects that make MJML.NET possible.

mjml-net's People

Contributors

edwardsalter avatar liamriddell avatar sebastianstehle avatar skybber avatar svehe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

mjml-net's Issues

Rename Projects

Currently, I feel the project names are disconnected. Here's what I think we should call them:

  • Mjml.Net
  • Mjml.Net.Benchmark
  • Mjml.Net.Generator
  • Mjml.Net.Tests
  • Tools

@SebastianStehle, I'd like you thoughts on this as you maintain lots of OS projects :)

V1 Status

Components

  • mjml
  • mj-head
  • mj-body
  • mj-include (NOT PLANNED)
  • mj-attributes (including mj-class and mj-all)
  • mj-breakpoint
  • mj-font
  • mj-html-attributes (NOT PLANNED)
  • mj-preview
  • mj-style
  • mj-style (inline) (NOT PLANNED)
  • mj-title
  • mj-accordion (NOT PLANNED)
  • #3
  • mj-carousel (NOT PLANNED)
  • #4
  • #5
  • #6
  • #7
  • #8
  • mj-navbar (V2)
  • mj-raw
  • #10
  • #11
  • #12
  • mj-table (V2)
  • #14
  • #71

Rendering

  • beautify
  • minify

Validation

Other

QA

  • More tests for mj-column
  • More tests for mj-section
  • More tests for mj-group
  • Check all width container width behavior (aka getChildContext)
  • #62
  • Write high level visual tests for a few complex templates?

V2 Status

I had a look to the template gallery and counted the tag usage:

mj-column           : 279 done
mj-text             : 204 done
mj-section          : 183 done
mj-image            : 154 done
mj-button           : 50 done
mj-raw              : 25 done
mj-body             : 22 done
mj-social           : 19 done
mj-spacer           : 17 done
mj-divider          : 15 done
mj-class            : 12 done
mj-head             : 10 done
mj-navbar           : 10 done
mj-attributes       : 8 done
mj-carousel         : 7
mj-title            : 6 done
mj-all              : 6 done
mj-style            : 6 done
mj-font             : 5 done
mj-group            : 4 done
mj-wrapper          : 3 done
mj-table            : 1 done

Tasks

QA

  • Compile all templates
  • Compile all templates dynamically with mjml so we can track errors.

Others

Performance results

I have written a small testrunner to test a file with many heroes. And so far the performance is aound 0.8 - 1ms on my machine :)

https://github.com/SebastianStehle/mjml-test/blob/main/TestRunner/ManyHeroes.mjml

Many Heroes
* Elapsed after 30.6278ms. Length 55618
* Elapsed after 1.0765ms. Length 55618
* Elapsed after 1.8128ms. Length 55618
* Elapsed after 0.8266ms. Length 55618
* Elapsed after 1.0794ms. Length 55618
* Elapsed after 0.799ms. Length 55618
* Elapsed after 0.8069ms. Length 55618
* Elapsed after 0.8443ms. Length 55618
* Elapsed after 0.7996ms. Length 55618
* Elapsed after 0.8125ms. Length 55618
* Elapsed after 0.8127ms. Length 55618
* Elapsed after 0.8659ms. Length 55618
* Elapsed after 1.5186ms. Length 55618

Tests

@LiamRiddell Do you know how they run tests in mjml. I can hardly find anything. It seems like they have no tests at all or at least not part of the CI.

Layout phase

I understand now, why mjml is so weird in one case: The calculation of sizes.

Somehow they thought it would be a good idea to make everything in the render method. EVERY other rendering system has a measure phase and a render phase. Usually also a layout phase, but html takes care of it.

So we should implement a measure phase as well. It is actually very simple:

We just need one method:

void Measure(double parentWidth, int numSiblings)
{
}

usually it would look like this

void Measure(double parentWidth, int numSiblings)
{
    ActualWidth  = parentWidth;
    
    // Some other stuff like percentages here ;)
    
     var innerWidth =
        ActualWidth -
        UnitParser.Parse(PaddingLeft).Value -
      
      foreach (var child in Children)
      { 
         child.Measure(innerWidth, Children.Count);
      }
}

Then we measure everything first and then render it.

We gain performance, because we do not need the context stack anymore. And we can also get rid of the IProvidersWidth interface.

Rust Wrapper

I just found this: https://github.com/jdrouet/mrml

Could be worth to just make rust wrapper. I do not fully understand rust, but in theory we could be still faster. First because we would not have the overhead of calling a wrapper and I am not sure how good the renderer is there. Seems to be a lot of sub allocations, but not sure.

Getting attribute values

Hi,

getting attribute values is relatively expensive. You have to get the attribute from the node, classes, fallbacks and so on and then validate it.

To optimize it we have 2 alternatives:

  1. Ensure that we ask for every property only once (We can write code for that in debug mode): Best performance
  2. Cache the result: Less performance, but a little bit easier code.

Build string cleaner

  • html attributes
  • Invalid tags, e.g.
    => should become
    (if there is no closing tag).
  • entities (aka, copy and so on)

Source Generator Output Invalid for Enum Types

@SebastianStehle, When using the source generator for mj-section it creates the following output for type enum.

Input

  static allowedAttributes = {
    'background-color': 'color',
    'background-url': 'string',
    'background-repeat': 'enum(repeat,no-repeat)',
    'background-size': 'string',
    'background-position': 'string',
    'background-position-x': 'string',
    'background-position-y': 'string',
    border: 'string',
    'border-bottom': 'string',
    'border-left': 'string',
    'border-radius': 'string',
    'border-right': 'string',
    'border-top': 'string',
    direction: 'enum(ltr,rtl)',
    'full-width': 'enum(full-width,false,)',
    padding: 'unit(px,%){1,4}',
    'padding-top': 'unit(px,%)',
    'padding-bottom': 'unit(px,%)',
    'padding-left': 'unit(px,%)',
    'padding-right': 'unit(px,%)',
    'text-align': 'enum(left,center,right)',
    'text-padding': 'unit(px,%){1,4}',
  }

  static defaultAttributes = {
    'background-repeat': 'repeat',
    'background-size': 'auto',
    'background-position': 'top center',
    direction: 'ltr',
    padding: '20px 0',
    'text-align': 'center',
    'text-padding': '4px 4px 4px 0',
  }

Output

    public partial struct SectionProps
    {
        [Bind("background-color", BindType.Color)]
        public string? BackgroundColor;

        [Bind("background-position", BindType.String)]
        public string BackgroundPosition = "top center";

        [Bind("background-position-x", BindType.String)]
        public string? BackgroundPositionX;

        [Bind("background-position-y", BindType.String)]
        public string? BackgroundPositionY;

        [Bind("background-repeat", BindType.enum(repeat, no-repeat))]
        public string BackgroundRepeat = "repeat";

        [Bind("background-size", BindType.String)]
        public string BackgroundSize = "auto";

        [Bind("background-url", BindType.String)]
        public string? BackgroundUrl;

        [Bind("border", BindType.String)]
        public string? Border;

        [Bind("border-bottom", BindType.String)]
        public string? BorderBottom;

        [Bind("border-left", BindType.String)]
        public string? BorderLeft;

        [Bind("border-radius", BindType.String)]
        public string? BorderRadius;

        [Bind("border-right", BindType.String)]
        public string? BorderRight;

        [Bind("border-top", BindType.String)]
        public string? BorderTop;

        [Bind("direction", BindType.enum(ltr, rtl))]
        public string Direction = "ltr";

        [Bind("full-width", BindType.enum(full-width,false,))]
        public string? FullWidth;

        [Bind("padding", BindType.PixelsOrPercent)]
        public string Padding = "20px 0";

        [Bind("padding-bottom", BindType.PixelsOrPercent)]
        public string? PaddingBottom;

        [Bind("padding-left", BindType.PixelsOrPercent)]
        public string? PaddingLeft;

        [Bind("padding-right", BindType.PixelsOrPercent)]
        public string? PaddingRight;

        [Bind("padding-top", BindType.PixelsOrPercent)]
        public string? PaddingTop;

        [Bind("text-align", BindType.Align)]
        public string TextAlign = "center";

        [Bind("text-padding", BindType.PixelsOrPercent)]
        public string TextPadding = "4px 4px 4px 0";
    }

Solution

We introduce a new BindType field for Enum. Then we can add an extension function to the BindAttribute to include expected values.

public BindAttribute(string name, BindType type, string[] validAttributeValues)
{
    Name = name;
    Type = type;
    ValidAttributeValues= validAttributeValues;
}

Example Output:

public partial struct SectionProps
    {
        [Bind("background-color", BindType.Color)]
        public string? BackgroundColor;

        [Bind("background-position", BindType.String)]
        public string BackgroundPosition = "top center";

        [Bind("background-position-x", BindType.String)]
        public string? BackgroundPositionX;

        [Bind("background-position-y", BindType.String)]
        public string? BackgroundPositionY;

        [Bind("background-repeat", BindType.Enum, new[] { "repeat", "no-repeat" })]
        public string BackgroundRepeat = "repeat";

        [Bind("background-size", BindType.String)]
        public string BackgroundSize = "auto";

        [Bind("background-url", BindType.String)]
        public string? BackgroundUrl;

        [Bind("border", BindType.String)]
        public string? Border;

        [Bind("border-bottom", BindType.String)]
        public string? BorderBottom;

        [Bind("border-left", BindType.String)]
        public string? BorderLeft;

        [Bind("border-radius", BindType.String)]
        public string? BorderRadius;

        [Bind("border-right", BindType.String)]
        public string? BorderRight;

        [Bind("border-top", BindType.String)]
        public string? BorderTop;

        [Bind("direction", BindType.Enum, new[] { "ltr", "rtl" })]
        public string Direction = "ltr";

        [Bind("full-width", BindType.Enum, new[] { "full-width", "false" })]
        public string? FullWidth;

        [Bind("padding", BindType.PixelsOrPercent)]
        public string Padding = "20px 0";

        [Bind("padding-bottom", BindType.PixelsOrPercent)]
        public string? PaddingBottom;

        [Bind("padding-left", BindType.PixelsOrPercent)]
        public string? PaddingLeft;

        [Bind("padding-right", BindType.PixelsOrPercent)]
        public string? PaddingRight;

        [Bind("padding-top", BindType.PixelsOrPercent)]
        public string? PaddingTop;

        [Bind("text-align", BindType.Align)]
        public string TextAlign = "center";

        [Bind("text-padding", BindType.PixelsOrPercent)]
        public string TextPadding = "4px 4px 4px 0";
    }

Performance Improvements

Just a list of performance improvements we could implement.

  • Ensure that we query each attribute only once or cache the results.
  • Implement a custom stack that reuses the elements.
  • Check the performance implication of XmlReader.Subtree.

Extensions Helpers

Move NodeExtenstions into the Mjml.Net.Extensions name space. Furthermore, include any other Extensions we've got laying around.

URLs in attributes can cause exceptions

Template: UGGRoyale.mjml and Arturia.mjml

There is a <mj-text> which contains an <a> with the href:

<!-- UGGRoyale.mjml -->
<a href="https://email.ugg.com/pub/sf/ResponseForm?_ri_=X0Gzc2X%3DYQpglLjHJlTQGjmR9kDzfwzbMzackp9f3F8d45ze2zfbW8hYB6AzgB7gaIYB3tN5qwvasVXMtX%3DYQpglLjHJlTQGNXlMNF3fFUYHWCzdBj2rGzgJpzem2KonGoezgzbfUyABevzgl4CepTMC&_ei_=EolaGGF4SNMvxFF7KucKuWP7XpARYKtbBY43fLBYxvXNpGx2Z89j4M" style="text-decoration:underline; color:#111111;">View in Browser</a>

<!-- Arturia.mjml -->
<a href="https://www.arturia.com/index.php?option=com_myarturia&view=newsletter&task=unsubscribeFromEmail&email={email}&token={title}&utm_source=elasticemail&utm_medium=email&utm_campaign=arturia_unsubscribe_2016&utm_term=nl-ispark-launch&utm_content=unsubscribe_footer" style="text-decoration:none;color:#2DDCB4">Unsubscribe</a>

Any URL parameter due to the & is being recognised as XML External Entity.

&_ei_=
&view=
&task=
System.Xml.XmlException: '=' is an unexpected token. The expected token is ';'. Line 202, position 230.
   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
   at System.Xml.XmlTextReaderImpl.ThrowUnexpectedToken(String expectedToken1, String expectedToken2)
   at System.Xml.XmlTextReaderImpl.ThrowUnexpectedToken(Int32 pos, String expectedToken1, String expectedToken2)
   at System.Xml.XmlTextReaderImpl.HandleEntityReference(Boolean isInAttributeValue, EntityExpandType expandType, Int32& charRefEndPos)
   at System.Xml.XmlTextReaderImpl.ParseAttributeValueSlow(Int32 curPos, Char quoteChar, NodeData attr)
   at System.Xml.XmlTextReaderImpl.ParseAttributes()
   at System.Xml.XmlTextReaderImpl.ParseElement()
   at System.Xml.XmlTextReaderImpl.ParseElementContent()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlWriter.WriteNode(XmlReader reader, Boolean defattr)
   at System.Xml.XmlReader.ReadOuterXml()
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-net\Mjml.Net\MjmlRenderContext.cs:line 130
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-net\Mjml.Net\MjmlRenderContext.cs:line 148
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-net\Mjml.Net\MjmlRenderContext.cs:line 148
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-net\Mjml.Net\MjmlRenderContext.cs:line 148
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-net\Mjml.Net\MjmlRenderContext.cs:line 148
   at Mjml.Net.MjmlRenderContext.Read(XmlReader reader) in C:\Source\mjml-net\Mjml.Net\MjmlRenderContext.cs:line 58
   at Mjml.Net.MjmlRenderer.Render(XmlReader xml, MjmlOptions options) in C:\Source\mjml-net\Mjml.Net\MjmlRenderer.cs:line 111
   at Mjml.Net.MjmlRenderer.Render(String mjml, MjmlOptions options) in C:\Source\mjml-net\Mjml.Net\MjmlRenderer.cs:line 87
   at Mjml.Net.Benchmarking.TestRunner.Run(String fileName, String filePath, MjmlRenderer mjmlRenderer) in C:\Source\mjml-net\Mjml.Net.Benchmark\TestRunner.cs:line 47
   at Mjml.Net.Benchmarking.TestRunner.Run(Int32 numberOfIterations) in C:\Source\mjml-net\Mjml.Net.Benchmark\TestRunner.cs:line 31

This issue is important as users would need to URL Encode.

Conditional Tags

Change all instances of hardcoded Conditional Tags being injected via renderer.Content("<...>") to use the new extension methods.

Html Entities Unsupported

While writing the benchmarks project, I ran into an issue where Html Entities cause the following:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.Xml.XmlException: Reference to undeclared entity 'copy'. Line 82, position 67.
   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.HandleGeneralEntityReference(String name, Boolean isInAttributeValue, Boolean pushFakeEntityIfNullResolver, Int32 entityStartLinePos)
   at System.Xml.XmlTextReaderImpl.HandleEntityReference(Boolean isInAttributeValue, EntityExpandType expandType, Int32& charRefEndPos)
   at System.Xml.XmlTextReaderImpl.ParseText(Int32& startPos, Int32& endPos, Int32& outOrChars)
   at System.Xml.XmlTextReaderImpl.ParseText()
   at System.Xml.XmlTextReaderImpl.ParseElementContent()
   at System.Xml.XmlTextReaderImpl.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at System.Xml.XmlSubtreeReader.Read()
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-test\Mjml.Net\MjmlRenderContext.cs:line 120
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-test\Mjml.Net\MjmlRenderContext.cs:line 146
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-test\Mjml.Net\MjmlRenderContext.cs:line 146
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-test\Mjml.Net\MjmlRenderContext.cs:line 146
   at Mjml.Net.MjmlRenderContext.ReadElement(String name, XmlReader parentReader, IComponent parent) in C:\Source\mjml-test\Mjml.Net\MjmlRenderContext.cs:line 146
   at Mjml.Net.MjmlRenderContext.Read(XmlReader reader) in C:\Source\mjml-test\Mjml.Net\MjmlRenderContext.cs:line 56

Update README and NuGet

  • Remove Experimental
  • Add new benchmark results to Readme.
  • Update the project description to better reflect the current state.
  • Mention the cleanup method => slower
  • Mention some options (beautify) and the performance impact => slower
  • Mesure performance with memory allocations
  • Fix the benchmark templates
  • Update NuGet information
  • Deprecate LiamRiddell/MjmlDotNet and Archive
  • Add MIT License @SebastianStehle
  • Project Description @SebastianStehle

Bugs

I have found a few bugs, that are obvious to catch when you compare html results.

I thought that we can find a solution where we can just compare the result one a visual basis, but if we do not have a good testing infrastructure or access to mjml there is no chance for that. Because even if we would implement something by chrome, there would be no guarantee that we have no missed anything that is just rendered for thunderbird.

So our only guarantee is to get a 1:1 rendering to work.

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.