Repository: jdrouet/mrml (language: rust)

Issue:
Panic rendering HTML inside Ending Tag
Hey, thank you so much for creating this amazing cli :)

I was testing some code and found out that using HTML inside `mj-social-element` panicked the mrml-cli compiler.

Sample:

```mjml
<mjml>
  <mj-body>
    <mj-section background-color="#e7e7e7">
      <mj-column>
        <mj-social>
          <mj-social-element name="facebook">
            Share <b>test</b> hi
          </mj-social-element>
        </mj-social>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
```


Output:

```bash
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "unable to parse mjml: UnexpectedElement(166)"', /home/mafios/.cargo/registry/src/github.com-1ecc6299db9ec823/mrml-cli-1.3.2/src/main.rs:52:40
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```


From the docs the inner content should be rendered just like `mj-raw`/`mj-text`:

> Some of the mjml components are "ending tags". These are mostly the components that will contain text contents, like mj-text or mj-buttons. These components can contain not only text, but also any HTML content
> 
> Here is the list of all ending tags : - mj-accordion-text - mj-accordion-title - mj-button - mj-navbar-link - mj-raw - mj-social-element - mj-text - mj-table


Refs:
https://documentation.mjml.io/#ending-tags


Discussion / hints:
@MateusAquino thanks for the report, I'll take care of it 😉 

Fix patch:
diff --git a/packages/mrml-core/src/mj_navbar_link/children.rs b/packages/mrml-core/src/mj_navbar_link/children.rs
deleted file mode 100644
index fadb0475..00000000
--- a/packages/mrml-core/src/mj_navbar_link/children.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use crate::comment::Comment;
-use crate::from_child;
-use crate::text::Text;
-
-#[derive(Debug)]
-#[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
-#[cfg_attr(feature = "json", serde(untagged))]
-pub enum MJNavbarLinkChild {
-    Comment(Comment),
-    Text(Text),
-}
-
-from_child!(MJNavbarLinkChild, Comment);
-from_child!(MJNavbarLinkChild, Text);
diff --git a/packages/mrml-core/src/mj_navbar_link/mod.rs b/packages/mrml-core/src/mj_navbar_link/mod.rs
index 255f54b9..d8729c37 100644
--- a/packages/mrml-core/src/mj_navbar_link/mod.rs
+++ b/packages/mrml-core/src/mj_navbar_link/mod.rs
@@ -1,4 +1,3 @@
-mod children;
 #[cfg(feature = "json")]
 mod json;
 #[cfg(feature = "parse")]
@@ -8,13 +7,13 @@ mod print;
 #[cfg(feature = "render")]
 mod render;
 
+use crate::mj_raw::MJRawChild;
 use crate::prelude::hash::Map;
-pub use children::MJNavbarLinkChild;
 
 pub const NAME: &str = "mj-navbar-link";
 
 #[derive(Debug, Default)]
 pub struct MJNavbarLink {
     attributes: Map<String, String>,
-    children: Vec<MJNavbarLinkChild>,
+    children: Vec<MJRawChild>,
 }
diff --git a/packages/mrml-core/src/mj_navbar_link/parse.rs b/packages/mrml-core/src/mj_navbar_link/parse.rs
index c1b5a1ab..52b042cb 100644
--- a/packages/mrml-core/src/mj_navbar_link/parse.rs
+++ b/packages/mrml-core/src/mj_navbar_link/parse.rs
@@ -1,14 +1,9 @@
-use super::{MJNavbarLink, MJNavbarLinkChild};
+use super::MJNavbarLink;
+use crate::mj_raw::MJRawChild;
 use crate::prelude::parse::{Error, Parsable, Parser};
 use crate::{parse_attribute, parse_child, parse_comment, parse_text};
 use xmlparser::{StrSpan, Tokenizer};
 
-impl Parsable for MJNavbarLinkChild {
-    fn parse<'a>(tag: StrSpan<'a>, _tokenizer: &mut Tokenizer<'a>) -> Result<Self, Error> {
-        Err(Error::UnexpectedElement(tag.start()))
-    }
-}
-
 #[derive(Debug, Default)]
 struct MJNavbarLinkParser(MJNavbarLink);
 
@@ -20,7 +15,7 @@ impl Parser for MJNavbarLinkParser {
     }
 
     parse_attribute!();
-    parse_child!(MJNavbarLinkChild);
+    parse_child!(MJRawChild);
     parse_comment!();
     parse_text!();
 }
diff --git a/packages/mrml-core/src/mj_navbar_link/print.rs b/packages/mrml-core/src/mj_navbar_link/print.rs
index 0e903709..0aecaf55 100644
--- a/packages/mrml-core/src/mj_navbar_link/print.rs
+++ b/packages/mrml-core/src/mj_navbar_link/print.rs
@@ -1,15 +1,6 @@
-use super::{MJNavbarLink, MJNavbarLinkChild};
+use super::MJNavbarLink;
 use crate::print_attrs;
 
-impl Print for MJNavbarLinkChild {
-    fn print(&self, pretty: bool, level: usize, indent_size: usize) -> String {
-        match self {
-            Self::Comment(elt) => elt.print(pretty, level, indent_size),
-            Self::Text(elt) => elt.print(pretty, level, indent_size),
-        }
-    }
-}
-
 print_attrs!(MJNavbarLink, super::NAME);
 
 #[cfg(test)]
diff --git a/packages/mrml-core/src/mj_navbar_link/render.rs b/packages/mrml-core/src/mj_navbar_link/render.rs
index 26a557f7..faf47acd 100644
--- a/packages/mrml-core/src/mj_navbar_link/render.rs
+++ b/packages/mrml-core/src/mj_navbar_link/render.rs
@@ -1,4 +1,4 @@
-use super::{MJNavbarLink, MJNavbarLinkChild, NAME};
+use super::{MJNavbarLink, NAME};
 use crate::helper::condition::conditional_tag;
 use crate::helper::size::Pixel;
 use crate::helper::tag::Tag;
@@ -7,15 +7,6 @@ use crate::prelude::render::{Error, Header, Options, Render, Renderable};
 use std::cell::{Ref, RefCell};
 use std::rc::Rc;
 
-impl<'r, 'e: 'r, 'h: 'r> Renderable<'r, 'e, 'h> for MJNavbarLinkChild {
-    fn renderer(&'e self, header: Rc<RefCell<Header<'h>>>) -> Box<dyn Render<'h> + 'r> {
-        match self {
-            Self::Text(elt) => elt.renderer(header),
-            Self::Comment(elt) => elt.renderer(header),
-        }
-    }
-}
-
 struct MJNavbarLinkRender<'e, 'h> {
     header: Rc<RefCell<Header<'h>>>,
     element: &'e MJNavbarLink,
diff --git a/packages/mrml-core/src/mj_social_element/children.rs b/packages/mrml-core/src/mj_social_element/children.rs
deleted file mode 100644
index ea6ab9e9..00000000
--- a/packages/mrml-core/src/mj_social_element/children.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use crate::comment::Comment;
-use crate::from_child;
-use crate::text::Text;
-
-#[derive(Debug)]
-#[cfg_attr(feature = "json", derive(serde::Deserialize, serde::Serialize))]
-#[cfg_attr(feature = "json", serde(untagged))]
-pub enum MJSocialElementChild {
-    Comment(Comment),
-    Text(Text),
-}
-
-from_child!(MJSocialElementChild, Comment);
-from_child!(MJSocialElementChild, Text);
diff --git a/packages/mrml-core/src/mj_social_element/mod.rs b/packages/mrml-core/src/mj_social_element/mod.rs
index 5f547f13..dddb9231 100644
--- a/packages/mrml-core/src/mj_social_element/mod.rs
+++ b/packages/mrml-core/src/mj_social_element/mod.rs
@@ -1,4 +1,3 @@
-mod children;
 #[cfg(feature = "json")]
 mod json;
 #[cfg(feature = "render")]
@@ -10,13 +9,13 @@ mod print;
 #[cfg(feature = "render")]
 mod render;
 
+use crate::mj_raw::MJRawChild;
 use crate::prelude::hash::Map;
-pub use children::MJSocialElementChild;
 
 pub const NAME: &str = "mj-social-element";
 
 #[derive(Debug, Default)]
 pub struct MJSocialElement {
     attributes: Map<String, String>,
-    children: Vec<MJSocialElementChild>,
+    children: Vec<MJRawChild>,
 }
diff --git a/packages/mrml-core/src/mj_social_element/parse.rs b/packages/mrml-core/src/mj_social_element/parse.rs
index 9fb92ced..869660af 100644
--- a/packages/mrml-core/src/mj_social_element/parse.rs
+++ b/packages/mrml-core/src/mj_social_element/parse.rs
@@ -1,14 +1,9 @@
-use super::{MJSocialElement, MJSocialElementChild};
+use super::MJSocialElement;
+use crate::mj_raw::MJRawChild;
 use crate::prelude::parse::{Error, Parsable, Parser};
 use crate::{parse_attribute, parse_child, parse_comment, parse_text};
 use xmlparser::{StrSpan, Tokenizer};
 
-impl Parsable for MJSocialElementChild {
-    fn parse<'a>(tag: StrSpan<'a>, _tokenizer: &mut Tokenizer<'a>) -> Result<Self, Error> {
-        Err(Error::UnexpectedElement(tag.start()))
-    }
-}
-
 #[derive(Debug, Default)]
 struct MJSocialElementParser(MJSocialElement);
 
@@ -21,7 +16,7 @@ impl Parser for MJSocialElementParser {
 
     parse_attribute!();
     parse_comment!();
-    parse_child!(MJSocialElementChild);
+    parse_child!(MJRawChild);
     parse_text!();
 }
 
@@ -30,3 +25,24 @@ impl Parsable for MJSocialElement {
         MJSocialElementParser::default().parse(tokenizer)?.build()
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::mjml::MJML;
+
+    #[test]
+    fn parse_ending_tag() {
+        let template = r#"
+        <mjml>
+          <mj-body>
+            <mj-social>
+              <mj-social-element name="facebook">
+                Share <b>test</b> hi
+              </mj-social-element>
+            </mj-social>
+          </mj-body>
+        </mjml>
+        "#;
+        MJML::parse(template.to_string()).unwrap();
+    }
+}
diff --git a/packages/mrml-core/src/mj_social_element/print.rs b/packages/mrml-core/src/mj_social_element/print.rs
index 20d3f26d..c49733fb 100644
--- a/packages/mrml-core/src/mj_social_element/print.rs
+++ b/packages/mrml-core/src/mj_social_element/print.rs
@@ -1,15 +1,6 @@
-use super::{MJSocialElement, MJSocialElementChild};
+use super::MJSocialElement;
 use crate::print_attrs;
 
-impl Print for MJSocialElementChild {
-    fn print(&self, pretty: bool, level: usize, indent_size: usize) -> String {
-        match self {
-            Self::Comment(elt) => elt.print(pretty, level, indent_size),
-            Self::Text(elt) => elt.print(pretty, level, indent_size),
-        }
-    }
-}
-
 print_attrs!(MJSocialElement, super::NAME);
 
 #[cfg(test)]
diff --git a/packages/mrml-core/src/mj_social_element/render.rs b/packages/mrml-core/src/mj_social_element/render.rs
index b9978749..a9ef663c 100644
--- a/packages/mrml-core/src/mj_social_element/render.rs
+++ b/packages/mrml-core/src/mj_social_element/render.rs
@@ -1,5 +1,5 @@
 use super::network::SocialNetwork;
-use super::{MJSocialElement, MJSocialElementChild, NAME};
+use super::{MJSocialElement, NAME};
 use crate::helper::size::{Pixel, Size};
 use crate::helper::tag::Tag;
 use crate::prelude::hash::Map;
@@ -9,15 +9,6 @@ use std::rc::Rc;
 
 const DEFAULT_ICON_ORIGIN: &str = "https://www.mailjet.com/images/theme/v1/icons/ico-social/";
 
-impl<'r, 'e: 'r, 'h: 'r> Renderable<'r, 'e, 'h> for MJSocialElementChild {
-    fn renderer(&'e self, header: Rc<RefCell<Header<'h>>>) -> Box<dyn Render<'h> + 'r> {
-        match self {
-            Self::Text(elt) => elt.renderer(header),
-            Self::Comment(elt) => elt.renderer(header),
-        }
-    }
-}
-
 struct MJSocialElementRender<'e, 'h> {
     header: Rc<RefCell<Header<'h>>>,
     element: &'e MJSocialElement,
@@ -232,3 +223,22 @@ impl<'r, 'e: 'r, 'h: 'r> Renderable<'r, 'e, 'h> for MJSocialElement {
         })
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use crate::helper::test::compare;
+    use crate::mjml::MJML;
+    use crate::prelude::render::Options;
+
+    #[test]
+    fn render_ending_tag() {
+        let opts = Options::default();
+        let template =
+            include_str!("../../resources/compare/success/mj-social-element-ending.mjml");
+        let expected =
+            include_str!("../../resources/compare/success/mj-social-element-ending.html");
+        let root = MJML::parse(template.to_string()).unwrap();
+        let result = root.render(&opts).unwrap();
+        compare(expected, result.as_str());
+    }
+}
diff --git a/resources/compare/success/mj-social-element-ending.html b/resources/compare/success/mj-social-element-ending.html
new file mode 100644
index 00000000..46f5881a
--- /dev/null
+++ b/resources/compare/success/mj-social-element-ending.html
@@ -0,0 +1,131 @@
+<!doctype html>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
+
+<head>
+  <title>
+  </title>
+  <!--[if !mso]><!-->
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <!--<![endif]-->
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <style type="text/css">
+    #outlook a {
+      padding: 0;
+    }
+
+    body {
+      margin: 0;
+      padding: 0;
+      -webkit-text-size-adjust: 100%;
+      -ms-text-size-adjust: 100%;
+    }
+
+    table,
+    td {
+      border-collapse: collapse;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+    }
+
+    img {
+      border: 0;
+      height: auto;
+      line-height: 100%;
+      outline: none;
+      text-decoration: none;
+      -ms-interpolation-mode: bicubic;
+    }
+
+    p {
+      display: block;
+      margin: 13px 0;
+    }
+  </style>
+  <!--[if mso]>
+        <noscript>
+        <xml>
+        <o:OfficeDocumentSettings>
+          <o:AllowPNG/>
+          <o:PixelsPerInch>96</o:PixelsPerInch>
+        </o:OfficeDocumentSettings>
+        </xml>
+        </noscript>
+        <![endif]-->
+  <!--[if lte mso 11]>
+        <style type="text/css">
+          .mj-outlook-group-fix { width:100% !important; }
+        </style>
+        <![endif]-->
+  <!--[if !mso]><!-->
+  <link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
+  <style type="text/css">
+    @import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
+  </style>
+  <!--<![endif]-->
+  <style type="text/css">
+    @media only screen and (min-width:480px) {
+      .mj-column-per-100 {
+        width: 100% !important;
+        max-width: 100%;
+      }
+    }
+  </style>
+  <style media="screen and (min-width:480px)">
+    .moz-text-html .mj-column-per-100 {
+      width: 100% !important;
+      max-width: 100%;
+    }
+  </style>
+  <style type="text/css">
+  </style>
+</head>
+
+<body style="word-spacing:normal;">
+  <div style="">
+    <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" bgcolor="#e7e7e7" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->
+    <div style="background:#e7e7e7;background-color:#e7e7e7;margin:0px auto;max-width:600px;">
+      <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#e7e7e7;background-color:#e7e7e7;width:100%;">
+        <tbody>
+          <tr>
+            <td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
+              <!--[if mso | IE]><table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td class="" style="vertical-align:top;width:600px;" ><![endif]-->
+              <div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
+                <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
+                  <tbody>
+                    <tr>
+                      <td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
+                        <!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" ><tr><td><![endif]-->
+                        <table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
+                          <tr>
+                            <td style="padding:4px;vertical-align:middle;">
+                              <table border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#3b5998;border-radius:3px;width:20px;">
+                                <tr>
+                                  <td style="font-size:0;height:20px;vertical-align:middle;width:20px;">
+                                    <img height="20" src="https://www.mailjet.com/images/theme/v1/icons/ico-social/facebook.png" style="border-radius:3px;display:block;" width="20" />
+                                  </td>
+                                </tr>
+                              </table>
+                            </td>
+                            <td style="vertical-align:middle;">
+                              <span style="color:#333333;font-size:13px;font-family:Ubuntu, Helvetica, Arial, sans-serif;line-height:22px;text-decoration:none;"> Share <b>test</b> hi </span>
+                            </td>
+                          </tr>
+                        </table>
+                        <!--[if mso | IE]></td></tr></table><![endif]-->
+                      </td>
+                    </tr>
+                  </tbody>
+                </table>
+              </div>
+              <!--[if mso | IE]></td></tr></table><![endif]-->
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <!--[if mso | IE]></td></tr></table><![endif]-->
+  </div>
+</body>
+
+</html>
diff --git a/resources/compare/success/mj-social-element-ending.mjml b/resources/compare/success/mj-social-element-ending.mjml
new file mode 100644
index 00000000..203a855b
--- /dev/null
+++ b/resources/compare/success/mj-social-element-ending.mjml
@@ -0,0 +1,13 @@
+<mjml>
+  <mj-body>
+    <mj-section background-color="#e7e7e7">
+      <mj-column>
+        <mj-social>
+          <mj-social-element name="facebook">
+            Share <b>test</b> hi
+          </mj-social-element>
+        </mj-social>
+      </mj-column>
+    </mj-section>
+  </mj-body>
+</mjml>


Test patch: