Skip to content

Commit

Permalink
Improve include handling. (#191)
Browse files Browse the repository at this point in the history
* Improve include handling.

* Fixes

* Some comments.
  • Loading branch information
SebastianStehle authored Feb 4, 2024
1 parent d087dfa commit 9e264dc
Show file tree
Hide file tree
Showing 26 changed files with 153 additions and 30 deletions.
9 changes: 6 additions & 3 deletions Mjml.Net/Component.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Mjml.Net.Internal;

namespace Mjml.Net;
namespace Mjml.Net;

public abstract class Component : IComponent
{
Expand Down Expand Up @@ -100,6 +98,11 @@ public virtual void Read(IHtmlReader htmlReader, IMjmlReader mjmlReader, GlobalC
{
}

protected virtual void ClearChildren()
{
childNodes?.Clear();
}

protected virtual void BeforeBind(GlobalContext context)
{
}
Expand Down
2 changes: 2 additions & 0 deletions Mjml.Net/Components/Body/BodyComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public override void Render(IHtmlRenderer renderer, GlobalContext context)
renderer.StartBuffer();

renderer.StartElement("div")
.Attr("lang", context.GlobalData.Values.OfType<Language>().FirstOrDefault()?.Value)
.Attr("dir", context.GlobalData.Values.OfType<Direction>().FirstOrDefault()?.Value)
.Class(CssClass)
.Style("background-color", BackgroundColor);

Expand Down
2 changes: 1 addition & 1 deletion Mjml.Net/Components/Body/CarouselComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public override void Render(IHtmlRenderer renderer, GlobalContext context)

context.SetGlobalData("mj-carousel", new Style(HeadStyle));

renderer.StartConditional("<!--[if !mso><!-->");
renderer.StartConditional("<!--[if !mso]><!-->");
{
renderer.StartElement("div")
.Class("mj-carousel");
Expand Down
1 change: 0 additions & 1 deletion Mjml.Net/Components/Body/ColumnComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ private void RenderColumn(IHtmlRenderer renderer, GlobalContext context)
renderer.StartElement("tr");
renderer.StartElement("td")
.Attr("align", child.GetAttribute("align"))
.Attr("vertical-align", child.GetAttribute("vertical-align"))
.Class(child.GetAttribute("css-class"))
.Style("background", child.GetAttribute("container-background-color"))
.Style("font-size", "0px")
Expand Down
2 changes: 1 addition & 1 deletion Mjml.Net/Components/Body/NavbarComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ private void RenderHamburger(IHtmlRenderer renderer, GlobalContext context)
{
var key = context.Options.IdGenerator.Next();

renderer.StartConditional("<!--[if !mso><!-->");
renderer.StartConditional("<!--[if !mso]><!-->");
{
renderer.StartElement("input", true)
.Attr("id", key)
Expand Down
70 changes: 68 additions & 2 deletions Mjml.Net/Components/IncludeComponent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Mjml.Net.Helpers;
using Mjml.Net.Components.Body;
using Mjml.Net.Components.Head;
using Mjml.Net.Helpers;
using Mjml.Net.Types;

namespace Mjml.Net.Components;
Expand Down Expand Up @@ -59,10 +61,74 @@ public override void Read(IHtmlReader htmlReader, IMjmlReader mjmlReader, Global

if (!string.IsNullOrWhiteSpace(content))
{
mjmlReader.ReadFragment(content, actualPath, Parent!);
// Add the new element to the include parent, so actually after the include to have a correct parent-child relationship.
mjmlReader.ReadFragment(content, actualPath, this);

static void AddToHead(IComponent component, IComponent parent)
{
// Go to the root element to find the parent.
while (parent.Parent != null)
{
parent = parent.Parent;
}

if (parent is not RootComponent root)
{
return;
}

var head = root.ChildNodes.OfType<HeadComponent>().FirstOrDefault();

// If there is no head element in the current tree, add one.
if (head == null)
{
head = new HeadComponent();

root.InsertChild(head, 0);
}

foreach (var child in component.ChildNodes)
{
Add(child, head);
}
}

static void Add(IComponent component, IComponent parent)
{
if (component is HeadComponent)
{
// Add head children to the root head.
AddToHead(component, parent);
}
else if (component is RootComponent or BodyComponent or IncludeComponent)
{
// Just ignore these component and add the children to the parent.
foreach (var child in component.ChildNodes)
{
Add(child, parent);
}
}
else
{
parent.AddChild(component);
}
}

if (Parent != null)
{
Add(this, Parent);
}

// The children have been added to our parent or to the head element.
ClearChildren();
}
}

public override void Measure(GlobalContext context, double parentWidth, int numSiblings, int numNonRawSiblings)
{
base.Measure(context, parentWidth, numSiblings, numNonRawSiblings);
}

public override void Render(IHtmlRenderer renderer, GlobalContext context)
{
if (ActualType == IncludeType.Mjml || string.IsNullOrWhiteSpace(Path))
Expand Down
13 changes: 11 additions & 2 deletions Mjml.Net/Components/RootComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@

namespace Mjml.Net.Components;

public sealed class RootComponent : Component
public partial class RootComponent : Component
{
private static readonly string DefaultMeta = Resources.DefaultMeta;
private static readonly string DefaultStyles = Resources.DefaultStyles;
private static readonly string DefaultComments = Resources.DefaultComments;

public override string ComponentName => "mjml";

[Bind("lang", BindType.RequiredString)]
public string Lang = "und";

[Bind("dir", BindType.RequiredString)]
public string Dir = "auto";

public override void Render(IHtmlRenderer renderer, GlobalContext context)
{
context.SetGlobalData("lang", new Language(Lang));
context.SetGlobalData("dir", new Direction(Dir));

RenderChildren(renderer, context);

if (Parent != null)
Expand All @@ -20,7 +29,7 @@ public override void Render(IHtmlRenderer renderer, GlobalContext context)
}

renderer.Content("<!doctype html>");
renderer.Content("<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">");
renderer.Content($"<html lang=\"{Lang}\" dir=\"{Dir}\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\">");
renderer.Content(string.Empty);

RenderHead(renderer, context);
Expand Down
6 changes: 6 additions & 0 deletions Mjml.Net/Components/RootData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ public sealed record BodyBuffer(StringBuilder? Buffer) : GlobalData;
public sealed record Background(string Color) : GlobalData;

public sealed record HeadBuffer(StringBuilder? Buffer) : GlobalData;

public sealed record Language(string Value) : GlobalData;

public sealed record Direction(string Value) : GlobalData;

public sealed record ForceOWADesktop(bool Value) : GlobalData;
2 changes: 0 additions & 2 deletions Mjml.Net/GlobalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ public void SetOptions(MjmlOptions options)
public void Clear()
{
GlobalData.Clear();

fileLoader = null;
attributesByClass.Clear();
attributesByName.Clear();

Options = null!;
}

Expand Down
2 changes: 2 additions & 0 deletions Mjml.Net/MjmlRenderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ private void ReadElement(string name, IHtmlReader reader, IComponent? parent, st

var binder = DefaultPools.Binders.Get().Setup(context, parent, component.ComponentName);

// Add all binders to list, so that we can return them later to the pool.
allBinders.Add(binder);

for (var i = 0; i < reader.AttributeCount; i++)
Expand Down Expand Up @@ -140,6 +141,7 @@ private void ReadElement(string name, IHtmlReader reader, IComponent? parent, st
Read(reader, component, file);
}

// If there is no parent, we handle the root and we can render everything top to bottom.
if (parent == null)
{
component.Bind(context);
Expand Down
1 change: 0 additions & 1 deletion Tests/ComplexTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using Castle.Components.DictionaryAdapter;
using Mjml.Net;
using Mjml.Net.Validators;
using Tests.Internal;
Expand Down
29 changes: 29 additions & 0 deletions Tests/Components/IncludeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,35 @@ public void Should_include_mjml()
AssertHelpers.HtmlFileAssert("Components.Outputs.TextWhitespace.html", result);
}

[Fact]
public void Should_include_mjml_with_dummy_body()
{
var files = new Dictionary<string, string>
{
["./text.mjml"] = @"
<mjml>
<mj-body>
<mj-text>Hello MJML</mj-text>
</mj-body>
</mjml>"
};

const string source = @"
<mjml-test head=""false"">
<mj-text>Before Include</mj-text>
<mj-include path=""./text.mjml"" />
<mj-text>After Include</mj-text>
</mjml-test>
";

var result = TestHelper.Render(source, new MjmlOptions
{
FileLoader = () => new InMemoryFileLoader(files)
});

AssertHelpers.HtmlFileAssert("Components.Outputs.TextInclude.html", result);
}

[Fact]
public void Should_include_mjml_nested()
{
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/Carousel.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--[if !mso><!-->
<!--[if !mso]><!-->
<div class="mj-carousel">
<input class="mj-carousel-radio mj-carousel-41d58ca8b0b9-radio mj-carousel-41d58ca8b0b9-radio-1" checked="checked" type="radio" name="mj-carousel-radio-41d58ca8b0b9" id="mj-carousel-41d58ca8b0b9-radio-1" style="display:none;mso-hide:all;" />
<input class="mj-carousel-radio mj-carousel-41d58ca8b0b9-radio mj-carousel-41d58ca8b0b9-radio-2" type="radio" name="mj-carousel-radio-41d58ca8b0b9" id="mj-carousel-41d58ca8b0b9-radio-2" style="display:none;mso-hide:all;" />
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/CarouselIconWidth.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--[if !mso><!-->
<!--[if !mso]><!-->
<div class="mj-carousel">
<input class="mj-carousel-radio mj-carousel-e4cd685c98ce-radio mj-carousel-e4cd685c98ce-radio-1" checked="checked" type="radio" name="mj-carousel-radio-e4cd685c98ce" id="mj-carousel-e4cd685c98ce-radio-1" style="display:none;mso-hide:all;" />
<input class="mj-carousel-radio mj-carousel-e4cd685c98ce-radio mj-carousel-e4cd685c98ce-radio-2" type="radio" name="mj-carousel-radio-e4cd685c98ce" id="mj-carousel-e4cd685c98ce-radio-2" style="display:none;mso-hide:all;" />
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/CarouselImageWithHref.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--[if !mso><!-->
<!--[if !mso]><!-->
<div class="mj-carousel">
<input class="mj-carousel-radio mj-carousel-0ecde39840c5-radio mj-carousel-0ecde39840c5-radio-1" checked="checked" type="radio" name="mj-carousel-radio-0ecde39840c5" id="mj-carousel-0ecde39840c5-radio-1" style="display:none;mso-hide:all;" />
<input class="mj-carousel-radio mj-carousel-0ecde39840c5-radio mj-carousel-0ecde39840c5-radio-2" type="radio" name="mj-carousel-radio-0ecde39840c5" id="mj-carousel-0ecde39840c5-radio-2" style="display:none;mso-hide:all;" />
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/CarouselImagesFive.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--[if !mso><!-->
<!--[if !mso]><!-->
<div class="mj-carousel">
<input class="mj-carousel-radio mj-carousel-6123601308e5-radio mj-carousel-6123601308e5-radio-1" checked="checked" type="radio" name="mj-carousel-radio-6123601308e5" id="mj-carousel-6123601308e5-radio-1" style="display:none;mso-hide:all;" />
<input class="mj-carousel-radio mj-carousel-6123601308e5-radio mj-carousel-6123601308e5-radio-2" type="radio" name="mj-carousel-radio-6123601308e5" id="mj-carousel-6123601308e5-radio-2" style="display:none;mso-hide:all;" />
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/CarouselImagesOne.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--[if !mso><!-->
<!--[if !mso]><!-->
<div class="mj-carousel">
<input class="mj-carousel-radio mj-carousel-a8a9d55bbf42-radio mj-carousel-a8a9d55bbf42-radio-1" checked="checked" type="radio" name="mj-carousel-radio-a8a9d55bbf42" id="mj-carousel-a8a9d55bbf42-radio-1" style="display:none;mso-hide:all;" />
<div class="mj-carousel-content mj-carousel-a8a9d55bbf42-content" style="display:table;width:100%;table-layout:fixed;text-align:center;font-size:0px;">
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/CarouselImagesTwo.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
 <!--[if !mso><!-->
 <!--[if !mso]><!-->
<div class="mj-carousel">
<input class="mj-carousel-radio mj-carousel-424249025dc2-radio mj-carousel-424249025dc2-radio-1" checked="checked" type="radio" name="mj-carousel-radio-424249025dc2" id="mj-carousel-424249025dc2-radio-1" style="display:none;mso-hide:all;" />
<input class="mj-carousel-radio mj-carousel-424249025dc2-radio mj-carousel-424249025dc2-radio-2" type="radio" name="mj-carousel-radio-424249025dc2" id="mj-carousel-424249025dc2-radio-2" style="display:none;mso-hide:all;" />
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/CarouselThumbnailWidth.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
 <!--[if !mso><!-->
 <!--[if !mso]><!-->
<div class="mj-carousel">
<input class="mj-carousel-radio mj-carousel-94288ecacbe4-radio mj-carousel-94288ecacbe4-radio-1" checked="checked" type="radio" name="mj-carousel-radio-94288ecacbe4" id="mj-carousel-94288ecacbe4-radio-1" style="display:none;mso-hide:all;" />
<input class="mj-carousel-radio mj-carousel-94288ecacbe4-radio mj-carousel-94288ecacbe4-radio-2" type="radio" name="mj-carousel-radio-94288ecacbe4" id="mj-carousel-94288ecacbe4-radio-2" style="display:none;mso-hide:all;" />
Expand Down
8 changes: 4 additions & 4 deletions Tests/Components/Outputs/List.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%" style="vertical-align:top;">
<tbody>
<tr>
<td vertical-align="top" style="font-size:0px;word-break:break-word;">
<td style="font-size:0px;word-break:break-word;">
<ul align="left" role="presentation" type="disc" class="list-item-wrap" style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;margin-bottom:0;margin-top:0;mso-line-height-rule:exactly;padding:0;text-align:left;">
<li role="list-item" class="list-item" style="margin:0;padding:0;padding-left:3px;text-align:left;">
<span class="list-item__text" style="text-align:left;">
Expand All @@ -13,7 +13,7 @@
</td>
</tr>
<tr>
<td vertical-align="top" style="font-size:0px;word-break:break-word;">
<td style="font-size:0px;word-break:break-word;">
<ul align="left" role="presentation" type="disc" class="list-item-wrap" style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;margin-bottom:0;margin-top:0;mso-line-height-rule:exactly;padding:0;text-align:left;">
<li role="list-item" class="list-item" style="margin:0;padding:0;padding-left:3px;text-align:left;">
<span class="list-item__text" style="text-align:left;">
Expand All @@ -24,7 +24,7 @@
</td>
</tr>
<tr>
<td vertical-align="top" style="font-size:0px;word-break:break-word;">
<td style="font-size:0px;word-break:break-word;">
<ul align="left" role="presentation" type="disc" class="list-item-wrap" style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;margin-bottom:0;margin-top:0;mso-line-height-rule:exactly;padding:0;text-align:left;">
<li role="list-item" class="list-item" style="margin:0;padding:0;padding-left:3px;text-align:left;">
<span class="list-item__text" style="text-align:left;">
Expand All @@ -35,7 +35,7 @@
</td>
</tr>
<tr>
<td vertical-align="top" style="font-size:0px;word-break:break-word;">
<td style="font-size:0px;word-break:break-word;">
<ul align="left" role="presentation" type="disc" class="list-item-wrap" style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;margin-bottom:0;margin-top:0;mso-line-height-rule:exactly;padding:0;text-align:left;">
<li role="list-item" class="list-item" style="margin:0;padding:0;padding-left:3px;text-align:left;">
<span class="list-item__text" style="text-align:left;">
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/Navbar.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--[if !mso><!-->
<!--[if !mso]><!-->
<input type="checkbox" id="4c48e6fe53b37010" class="mj-menu-checkbox" style="display:none !important; max-height:0; visibility:hidden;" />
<!--<![endif]-->
<div class="mj-menu-trigger" style="display:none;max-height:0px;max-width:0px;font-size:0px;overflow:hidden;">
Expand Down
2 changes: 1 addition & 1 deletion Tests/Components/Outputs/NavbarWithLinks.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!--[if !mso><!-->
<!--[if !mso]><!-->
<input type="checkbox" id="b5e7b1c2f1d5bc37" class="mj-menu-checkbox" style="display:none !important; max-height:0; visibility:hidden;" />
<!--<![endif]-->
<div class="mj-menu-trigger" style="display:none;max-height:0px;max-width:0px;font-size:0px;overflow:hidden;">
Expand Down
9 changes: 9 additions & 0 deletions Tests/Components/Outputs/TextInclude.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
Before&nbsp;Include
</div>
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
Hello&nbsp;MJML
</div>
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1;text-align:left;color:#000000;">
After&nbsp;Include
</div>
3 changes: 1 addition & 2 deletions Tests/HtmlSpecialCaseTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using AngleSharp.Dom;
using System.Text.Encodings.Web;
using Mjml.Net;
using System.Text.Encodings.Web;
using Tests.Internal;
using Xunit;

Expand Down
4 changes: 2 additions & 2 deletions Tests/Internal/AssertHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ private static void FormatDiff(IDiff diff, StringBuilder sb)
sb.AppendDiff($"Missing attribute {d.Control.Attribute.Name} at {d.Control.Path}.");
break;
case UnexpectedNodeDiff d:
sb.AppendDiff($"Unespected node at {d.Test.Path}.");
sb.AppendDiff($"Unexpected node at {d.Test.Path}.");
break;
case UnexpectedAttrDiff d:
sb.AppendDiff($"Unespected attribute at {d.Test.Path}.");
sb.AppendDiff($"Unexpected attribute at {d.Test.Path}.");
break;
default:
sb.AppendDiff("Other error");
Expand Down
2 changes: 2 additions & 0 deletions Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<None Remove="Components\Outputs\FontUbuntu2.html" />
<None Remove="Components\Outputs\List.html" />
<None Remove="Components\Outputs\StyleInclude.html" />
<None Remove="Components\Outputs\TextInclude.html" />
<None Remove="Components\Outputs\TextRawWhitespace.html" />
<None Remove="Components\Outputs\TextWhitespace.html" />
<None Remove="Components\Outputs\TextWithEntity.html" />
Expand Down Expand Up @@ -107,6 +108,7 @@
<EmbeddedResource Include="Components\Outputs\TablePercent.html" />
<EmbeddedResource Include="Components\Outputs\Table.html" />
<EmbeddedResource Include="Components\Outputs\TextRawWhitespace.html" />
<EmbeddedResource Include="Components\Outputs\TextInclude.html" />
<EmbeddedResource Include="Components\Outputs\TextWhitespace.html" />
<EmbeddedResource Include="Components\Outputs\TextWithEntity.html" />
<EmbeddedResource Include="Components\Outputs\TextWithHtmlAndWhitespace.html" />
Expand Down

0 comments on commit 9e264dc

Please sign in to comment.