[evolution/449-support-markdown-in-composer: 15/15] Split markdown utilities into its own file




commit cf21c6cda4b61680b80afff2f9487dabec1a7ef9
Author: Milan Crha <mcrha redhat com>
Date:   Mon Feb 7 21:42:45 2022 +0100

    Split markdown utilities into its own file

 src/e-util/CMakeLists.txt                      |   2 +
 src/e-util/e-markdown-editor.c                 |  68 +---
 src/e-util/e-markdown-editor.h                 |   5 -
 src/e-util/e-markdown-utils.c                  | 503 +++++++++++++++++++++++++
 src/e-util/e-markdown-utils.h                  |  28 ++
 src/e-util/e-util-enums.h                      |  14 +
 src/e-util/e-util.h                            |   1 +
 src/e-util/test-markdown-editor.c              | 311 ++++++++++++++-
 src/em-format/e-mail-formatter-text-markdown.c |   2 +-
 9 files changed, 861 insertions(+), 73 deletions(-)
---
diff --git a/src/e-util/CMakeLists.txt b/src/e-util/CMakeLists.txt
index e81570ad3c..cc8c06c2b0 100644
--- a/src/e-util/CMakeLists.txt
+++ b/src/e-util/CMakeLists.txt
@@ -169,6 +169,7 @@ set(SOURCES
        e-mail-signature-tree-view.c
        e-map.c
        e-markdown-editor.c
+       e-markdown-utils.c
        e-marshal.c
        e-menu-tool-action.c
        e-menu-tool-button.c
@@ -445,6 +446,7 @@ set(HEADERS
        e-mail-signature-tree-view.h
        e-map.h
        e-markdown-editor.h
+       e-markdown-utils.h
        e-menu-tool-action.h
        e-menu-tool-button.h
        e-misc-utils.h
diff --git a/src/e-util/e-markdown-editor.c b/src/e-util/e-markdown-editor.c
index 84a51c5252..b4cc2d0d66 100644
--- a/src/e-util/e-markdown-editor.c
+++ b/src/e-util/e-markdown-editor.c
@@ -6,16 +6,13 @@
 
 #include "evolution-config.h"
 
-#ifdef HAVE_MARKDOWN
-#include <cmark.h>
-#endif
-
 #include <glib/gi18n-lib.h>
 #include <gtk/gtk.h>
 
 #include <libedataserverui/libedataserverui.h>
 
 #include "e-content-editor.h"
+#include "e-markdown-utils.h"
 #include "e-misc-utils.h"
 #include "e-spell-text-view.h"
 #include "e-web-view.h"
@@ -232,7 +229,7 @@ e_markdown_editor_insert_content (EContentEditor *cnt_editor,
        self = E_MARKDOWN_EDITOR (cnt_editor);
 
        if ((flags & E_CONTENT_EDITOR_INSERT_TEXT_HTML) != 0) {
-               text = e_markdown_util_html_to_text (content, -1);
+               text = e_markdown_utils_html_to_text (content, -1, E_MARKDOWN_HTML_TO_TEXT_FLAG_NONE);
                content = text;
        }
 
@@ -1506,7 +1503,7 @@ e_markdown_editor_dup_html (EMarkdownEditor *self)
 
        #ifdef HAVE_MARKDOWN
        text = e_markdown_editor_dup_text (self);
-       html = e_markdown_util_text_to_html (text, -1);
+       html = e_markdown_utils_text_to_html (text, -1);
 
        g_free (text);
 
@@ -1515,62 +1512,3 @@ e_markdown_editor_dup_html (EMarkdownEditor *self)
        return NULL;
        #endif
 }
-
-/**
- * e_markdown_util_text_to_html:
- * @plain_text: plain text with markdown to convert to HTML
- * @length: length of the @plain_text, or -1 when it's nul-terminated
- *
- * Convert @plain_text, possibly with markdown, into the HTML.
- *
- * Note: The function can return %NULL when was not built
- *    with the markdown support.
- *
- * Returns: (transfer full) (nullable): text converted into HTML,
- *    or %NULL, when was not built with the markdown support.
- *    Free the string with g_free(), when no longer needed.
- *
- * Since: 3.44
- **/
-gchar *
-e_markdown_util_text_to_html (const gchar *plain_text,
-                             gssize length)
-{
-       #ifdef HAVE_MARKDOWN
-       GString *html;
-       gchar *converted;
-
-       if (length == -1)
-               length = plain_text ? strlen (plain_text) : 0;
-
-       converted = cmark_markdown_to_html (plain_text ? plain_text : "", length,
-               CMARK_OPT_VALIDATE_UTF8 | CMARK_OPT_UNSAFE);
-
-       html = e_str_replace_string (converted, "<blockquote>", "<blockquote type=\"cite\">");
-
-       g_free (converted);
-
-       return g_string_free (html, FALSE);
-       #else
-       return NULL;
-       #endif
-}
-
-/**
- * e_markdown_util_html_to_text:
- * @html: a text in HTML
- * @length: length of the @html, or -1 when it's nul-terminated
- *
- * Convert @html into the markdown text.
- *
- * Returns: (transfer full) (nullable): HTML converted into markdown.
- *    Free the string with g_free(), when no longer needed.
- *
- * Since: 3.44
- **/
-gchar *
-e_markdown_util_html_to_text (const gchar *html,
-                             gssize length)
-{
-       return NULL;
-}
diff --git a/src/e-util/e-markdown-editor.h b/src/e-util/e-markdown-editor.h
index a24e5f67eb..075e7af259 100644
--- a/src/e-util/e-markdown-editor.h
+++ b/src/e-util/e-markdown-editor.h
@@ -56,11 +56,6 @@ void         e_markdown_editor_set_text              (EMarkdownEditor *self,
 gchar *                e_markdown_editor_dup_text              (EMarkdownEditor *self);
 gchar *                e_markdown_editor_dup_html              (EMarkdownEditor *self);
 
-gchar *                e_markdown_util_text_to_html            (const gchar *plain_text,
-                                                        gssize length);
-gchar *                e_markdown_util_html_to_text            (const gchar *html,
-                                                        gssize length);
-
 G_END_DECLS
 
 #endif /* E_MARKDOWN_EDITOR_H */
diff --git a/src/e-util/e-markdown-utils.c b/src/e-util/e-markdown-utils.c
new file mode 100644
index 0000000000..eceb067fb8
--- /dev/null
+++ b/src/e-util/e-markdown-utils.c
@@ -0,0 +1,503 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-config.h"
+
+#ifdef HAVE_MARKDOWN
+#include <cmark.h>
+#endif
+
+#include <libxml/HTMLparser.h>
+#include <libxml/HTMLtree.h>
+
+#include "e-misc-utils.h"
+
+#include "e-markdown-utils.h"
+
+#define dd(x)
+
+/**
+ * e_markdown_utils_text_to_html:
+ * @plain_text: plain text with markdown to convert to HTML
+ * @length: length of the @plain_text, or -1 when it's nul-terminated
+ *
+ * Convert @plain_text, possibly with markdown, into the HTML.
+ *
+ * Note: The function can return %NULL when was not built
+ *    with the markdown support.
+ *
+ * Returns: (transfer full) (nullable): text converted into HTML,
+ *    or %NULL, when was not built with the markdown support.
+ *    Free the string with g_free(), when no longer needed.
+ *
+ * Since: 3.44
+ **/
+gchar *
+e_markdown_utils_text_to_html (const gchar *plain_text,
+                              gssize length)
+{
+       #ifdef HAVE_MARKDOWN
+       GString *html;
+       gchar *converted;
+
+       if (length == -1)
+               length = plain_text ? strlen (plain_text) : 0;
+
+       converted = cmark_markdown_to_html (plain_text ? plain_text : "", length,
+               CMARK_OPT_VALIDATE_UTF8 | CMARK_OPT_UNSAFE);
+
+       html = e_str_replace_string (converted, "<blockquote>", "<blockquote type=\"cite\">");
+
+       g_free (converted);
+
+       return g_string_free (html, FALSE);
+       #else
+       return NULL;
+       #endif
+}
+
+typedef struct _HTMLToTextData {
+       GString *buffer;
+       gboolean in_body;
+       gint in_code;
+       gint in_pre;
+       gint in_paragraph;
+       gboolean in_paragraph_end;
+       gboolean in_li;
+       GString *quote_prefix;
+       gchar *href;
+       GString *link_text;
+       gboolean plain_text;
+       GSList *list_index; /* gint; -1 for unordered list */
+} HTMLToTextData;
+
+static void
+markdown_utils_sax_start_element_cb (gpointer ctx,
+                                    const xmlChar *xcname,
+                                    const xmlChar **xcattrs)
+{
+       HTMLToTextData *data = ctx;
+       const gchar *name = (const gchar *) xcname;
+       #if dd(1)+0
+       {
+               gint ii;
+
+               printf ("%s: '%s'\n", G_STRFUNC, name);
+               for (ii = 0; xcattrs && xcattrs[ii]; ii++) {
+                       printf ("   attr[%d]: '%s'\n", ii, xcattrs[ii]);
+               }
+       }
+       #endif
+
+       if (g_ascii_strcasecmp (name, "body") == 0) {
+               data->in_body = TRUE;
+               return;
+       }
+
+       if (!data->in_body)
+               return;
+
+       if (g_ascii_strcasecmp (name, "a") == 0) {
+               if (!data->plain_text && !data->href) {
+                       gchar *href = NULL;
+                       gint ii;
+
+                       for (ii = 0; xcattrs && xcattrs[ii]; ii++) {
+                               if (g_ascii_strcasecmp ((const gchar *) xcattrs[ii], "href") == 0 &&
+                                   xcattrs[ii + 1]) {
+                                       href = g_strdup ((const gchar *) xcattrs[ii + 1]);
+                                       break;
+                               }
+                       }
+
+                       if (href && *href) {
+                               data->href = href;
+                               data->link_text = g_string_new (NULL);
+                       } else {
+                               g_free (href);
+                       }
+               }
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "blockquote") == 0) {
+               if (data->in_paragraph_end) {
+                       if (data->quote_prefix->len)
+                               g_string_append (data->buffer, data->quote_prefix->str);
+
+                       g_string_append_c (data->buffer, '\n');
+
+                       data->in_paragraph_end = FALSE;
+               }
+
+               g_string_append (data->quote_prefix, "> ");
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "br") == 0) {
+               if (data->plain_text) {
+                       g_string_append (data->buffer, "\n");
+
+                       if (data->quote_prefix->len)
+                               g_string_append (data->buffer, data->quote_prefix->str);
+               } else {
+                       g_string_append (data->buffer, "<br>");
+               }
+
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "b") == 0 ||
+           g_ascii_strcasecmp (name, "strong") == 0) {
+               if (!data->plain_text)
+                       g_string_append (data->buffer, "**");
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "i") == 0 ||
+           g_ascii_strcasecmp (name, "em") == 0) {
+               if (!data->plain_text)
+                       g_string_append (data->buffer, "_");
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "pre") == 0) {
+               data->in_paragraph++;
+               data->in_pre++;
+               if (data->in_pre == 1) {
+                       if (!data->plain_text)
+                               g_string_append (data->buffer, "```\n");
+               }
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "code") == 0) {
+               data->in_code++;
+               if (data->in_code == 1 && !data->in_pre && !data->plain_text)
+                       g_string_append (data->buffer, "`");
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "h1") == 0 ||
+           g_ascii_strcasecmp (name, "h2") == 0 ||
+           g_ascii_strcasecmp (name, "h3") == 0 ||
+           g_ascii_strcasecmp (name, "h4") == 0 ||
+           g_ascii_strcasecmp (name, "h5") == 0 ||
+           g_ascii_strcasecmp (name, "h6") == 0) {
+               if (data->in_paragraph_end) {
+                       g_string_append_c (data->buffer, '\n');
+                       data->in_paragraph_end = FALSE;
+               }
+
+               data->in_paragraph++;
+               if (data->quote_prefix->len)
+                       g_string_append (data->buffer, data->quote_prefix->str);
+
+               if (!data->plain_text) {
+                       switch (name[1]) {
+                       case '1':
+                               g_string_append (data->buffer, "# ");
+                               break;
+                       case '2':
+                               g_string_append (data->buffer, "## ");
+                               break;
+                       case '3':
+                               g_string_append (data->buffer, "### ");
+                               break;
+                       case '4':
+                               g_string_append (data->buffer, "#### ");
+                               break;
+                       case '5':
+                               g_string_append (data->buffer, "##### ");
+                               break;
+                       case '6':
+                               g_string_append (data->buffer, "###### ");
+                               break;
+                       }
+               }
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "p") == 0 ||
+           g_ascii_strcasecmp (name, "div") == 0) {
+               if (data->in_paragraph_end) {
+                       data->in_paragraph_end = FALSE;
+
+                       if (data->quote_prefix->len)
+                               g_string_append (data->buffer, data->quote_prefix->str);
+
+                       g_string_append_c (data->buffer, '\n');
+               }
+
+               data->in_paragraph++;
+               if (data->quote_prefix->len)
+                       g_string_append (data->buffer, data->quote_prefix->str);
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "ul") == 0) {
+               if (data->in_paragraph_end) {
+                       g_string_append_c (data->buffer, '\n');
+                       data->in_paragraph_end = FALSE;
+               }
+               data->list_index = g_slist_prepend (data->list_index, GINT_TO_POINTER (-1));
+               data->in_li = FALSE;
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "ol") == 0) {
+               if (data->in_paragraph_end) {
+                       g_string_append_c (data->buffer, '\n');
+                       data->in_paragraph_end = FALSE;
+               }
+               data->list_index = g_slist_prepend (data->list_index, GINT_TO_POINTER (1));
+               data->in_li = FALSE;
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "li") == 0) {
+               data->in_paragraph_end = FALSE;
+               data->in_li = TRUE;
+
+               if (data->list_index) {
+                       gint index = GPOINTER_TO_INT (data->list_index->data);
+                       gint level = g_slist_length (data->list_index) - 1;
+
+                       if (data->quote_prefix->len)
+                               g_string_append (data->buffer, data->quote_prefix->str);
+
+                       if (level > 0)
+                               g_string_append_printf (data->buffer, "%*s", level * 3, "");
+
+                       if (index == -1) {
+                               g_string_append (data->buffer, "- ");
+                       } else {
+                               g_string_append_printf (data->buffer, "%d. ", index);
+                               data->list_index->data = GINT_TO_POINTER (index + 1);
+                       }
+               }
+               return;
+       }
+}
+
+static void
+markdown_utils_sax_end_element_cb (gpointer ctx,
+                                  const xmlChar *xcname)
+{
+       HTMLToTextData *data = ctx;
+       const gchar *name = (const gchar *) xcname;
+
+       dd (printf ("%s: '%s'\n", G_STRFUNC, name);)
+
+       if (g_ascii_strcasecmp (name, "body") == 0) {
+               data->in_body = FALSE;
+               return;
+       }
+
+       if (!data->in_body)
+               return;
+
+       if (g_ascii_strcasecmp (name, "a") == 0) {
+               if (!data->plain_text && data->href && data->link_text) {
+                       g_string_append_printf (data->buffer, "[%s](%s)", data->link_text->str, data->href);
+
+                       g_free (data->href);
+                       data->href = NULL;
+
+                       g_string_free (data->link_text, TRUE);
+                       data->link_text = NULL;
+               }
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "blockquote") == 0) {
+               if (data->quote_prefix->len > 1)
+                       g_string_truncate (data->quote_prefix, data->quote_prefix->len - 2);
+
+               data->in_paragraph_end = data->quote_prefix->len > 1;
+
+               if (!data->in_paragraph_end)
+                       g_string_append_c (data->buffer, '\n');
+
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "b") == 0 ||
+           g_ascii_strcasecmp (name, "strong") == 0) {
+               if (!data->plain_text)
+                       g_string_append (data->buffer, "**");
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "i") == 0 ||
+           g_ascii_strcasecmp (name, "em") == 0) {
+               if (!data->plain_text)
+                       g_string_append (data->buffer, "_");
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "pre") == 0) {
+               if (data->in_paragraph > 0)
+                       data->in_paragraph--;
+
+               if (data->in_pre > 0) {
+                       data->in_pre--;
+                       if (data->in_pre == 0 && !data->plain_text)
+                               g_string_append (data->buffer, "```");
+                       g_string_append_c (data->buffer, '\n');
+               }
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "code") == 0) {
+               if (data->in_code > 0) {
+                       data->in_code--;
+                       if (data->in_code == 0 && !data->in_pre && !data->plain_text)
+                               g_string_append (data->buffer, "`");
+               }
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "p") == 0 ||
+           g_ascii_strcasecmp (name, "div") == 0 ||
+           g_ascii_strcasecmp (name, "h1") == 0 ||
+           g_ascii_strcasecmp (name, "h2") == 0 ||
+           g_ascii_strcasecmp (name, "h3") == 0 ||
+           g_ascii_strcasecmp (name, "h4") == 0 ||
+           g_ascii_strcasecmp (name, "h5") == 0 ||
+           g_ascii_strcasecmp (name, "h6") == 0) {
+               g_string_append_c (data->buffer, '\n');
+
+               data->in_paragraph_end = TRUE;
+
+               if (data->in_paragraph > 0)
+                       data->in_paragraph--;
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "ul") == 0 ||
+           g_ascii_strcasecmp (name, "ol") == 0) {
+               if (data->list_index)
+                       data->list_index = g_slist_remove (data->list_index, data->list_index->data);
+               data->in_paragraph_end = data->list_index == NULL;
+
+               if (!data->in_paragraph_end && data->buffer->len && data->buffer->str[data->buffer->len - 1] 
== '\n')
+                       g_string_truncate (data->buffer, data->buffer->len - 1);
+
+               return;
+       }
+
+       if (g_ascii_strcasecmp (name, "li") == 0) {
+               g_string_append_c (data->buffer, '\n');
+
+               data->in_paragraph_end = FALSE;
+               data->in_li = FALSE;
+
+               return;
+       }
+}
+
+static void
+markdown_utils_sax_characters_cb (gpointer ctx,
+                                 const xmlChar *xctext,
+                                 gint len)
+{
+       HTMLToTextData *data = ctx;
+       const gchar *text = (const gchar *) xctext;
+
+       dd (printf ("%s: text:'%.*s' in_body:%d in_paragraph:%d in_li:%d\n", G_STRFUNC, len, text, 
data->in_body, data->in_paragraph, data->in_li);)
+
+       if (data->in_body && (data->in_paragraph || data->in_li)) {
+               if (data->link_text) {
+                       g_string_append_len (data->link_text, text, len);
+               } else {
+                       gsize from_index = data->buffer->len;
+
+                       g_string_append_len (data->buffer, text, len);
+
+                       if (data->quote_prefix->len && !data->in_li && strchr (data->buffer->str + 
from_index, '\n')) {
+                               gint ii;
+
+                               for (ii = from_index; ii < data->buffer->len; ii++) {
+                                       if (data->buffer->str[ii] == '\n') {
+                                               g_string_insert (data->buffer, ii + 1, 
data->quote_prefix->str);
+                                               ii += data->quote_prefix->len + 1;
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+static void
+markdown_utils_sax_warning_cb (gpointer ctx,
+                              const gchar *msg,
+                              ...)
+{
+       /* Ignore these */
+}
+
+static void
+markdown_utils_sax_error_cb (gpointer ctx,
+                            const gchar *msg,
+                            ...)
+{
+       /* Ignore these */
+}
+
+/**
+ * e_markdown_utils_html_to_text:
+ * @html: a text in HTML
+ * @length: length of the @html, or -1 when it's nul-terminated
+ * @flags: a bit-or of %EMarkdownHTMLToTextFlags
+ *
+ * Convert @html into the markdown text. The @flags influence
+ * what can be preserved from the @html.
+ *
+ * Returns: (transfer full) (nullable): HTML converted into markdown text.
+ *    Free the string with g_free(), when no longer needed.
+ *
+ * Since: 3.44
+ **/
+gchar *
+e_markdown_utils_html_to_text (const gchar *html,
+                              gssize length,
+                              EMarkdownHTMLToTextFlags flags)
+{
+       htmlParserCtxtPtr ctxt;
+       htmlSAXHandler sax;
+       HTMLToTextData data;
+
+       memset (&data, 0, sizeof (HTMLToTextData));
+
+       data.buffer = g_string_new (NULL);
+       data.quote_prefix = g_string_new (NULL);
+       data.plain_text = (flags & E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT) != 0;
+
+       memset (&sax, 0, sizeof (htmlSAXHandler));
+
+       sax.startElement = markdown_utils_sax_start_element_cb;
+       sax.endElement = markdown_utils_sax_end_element_cb;
+       sax.characters = markdown_utils_sax_characters_cb;
+       sax.warning = markdown_utils_sax_warning_cb;
+       sax.error = markdown_utils_sax_error_cb;
+
+       ctxt = htmlCreatePushParserCtxt (&sax, &data, html ? html : "", html && length == -1 ? strlen (html) 
: html ? length : 0, "", XML_CHAR_ENCODING_UTF8);
+
+       htmlParseChunk (ctxt, "", 0, 1);
+
+       htmlFreeParserCtxt (ctxt);
+
+       g_free (data.href);
+
+       if (data.link_text)
+               g_string_free (data.link_text, TRUE);
+
+       g_string_free (data.quote_prefix, TRUE);
+       g_slist_free (data.list_index);
+
+       return g_string_free (data.buffer, FALSE);
+}
diff --git a/src/e-util/e-markdown-utils.h b/src/e-util/e-markdown-utils.h
new file mode 100644
index 0000000000..928dbbbb70
--- /dev/null
+++ b/src/e-util/e-markdown-utils.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MARKDOWN_UTILS_H
+#define E_MARKDOWN_UTILS_H
+
+#include <glib.h>
+
+#include <e-util/e-util-enums.h>
+
+G_BEGIN_DECLS
+
+gchar *                e_markdown_utils_text_to_html           (const gchar *plain_text,
+                                                        gssize length);
+gchar *                e_markdown_utils_html_to_text           (const gchar *html,
+                                                        gssize length,
+                                                        EMarkdownHTMLToTextFlags flags);
+
+G_END_DECLS
+
+#endif /* E_MARKDOWN_UTILS_H */
diff --git a/src/e-util/e-util-enums.h b/src/e-util/e-util-enums.h
index bd2f2ca7f3..8c13745a7f 100644
--- a/src/e-util/e-util-enums.h
+++ b/src/e-util/e-util-enums.h
@@ -642,6 +642,20 @@ typedef enum {
  **/
 #define E_CONFIG_LOOKUP_RESULT_LAST_KIND E_CONFIG_LOOKUP_RESULT_TASK_LIST
 
+/**
+ * EMarkdownHTMLToTextFlags:
+ * @E_MARKDOWN_HTML_TO_TEXT_FLAG_NONE: no flag set
+ * @E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT: disallow any HTML, save in plain text
+ *
+ * Flags used in e_markdown_util_html_to_text().
+ *
+ * Since: 3.44
+ **/
+typedef enum { /*< flags >*/
+       E_MARKDOWN_HTML_TO_TEXT_FLAG_NONE       = 0,
+       E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT = 1 << 0
+} EMarkdownHTMLToTextFlags;
+
 G_END_DECLS
 
 #endif /* E_UTIL_ENUMS_H */
diff --git a/src/e-util/e-util.h b/src/e-util/e-util.h
index 666363e9e8..6801baf41c 100644
--- a/src/e-util/e-util.h
+++ b/src/e-util/e-util.h
@@ -153,6 +153,7 @@
 #include <e-util/e-mail-signature-tree-view.h>
 #include <e-util/e-map.h>
 #include <e-util/e-markdown-editor.h>
+#include <e-util/e-markdown-utils.h>
 #include <e-util/e-menu-tool-action.h>
 #include <e-util/e-menu-tool-button.h>
 #include <e-util/e-misc-utils.h>
diff --git a/src/e-util/test-markdown-editor.c b/src/e-util/test-markdown-editor.c
index 88d439dd7e..9fc5dc32a3 100644
--- a/src/e-util/test-markdown-editor.c
+++ b/src/e-util/test-markdown-editor.c
@@ -19,10 +19,153 @@ window_delete_event_cb (GtkWidget *widget,
        return FALSE;
 }
 
+static void
+editor_to_plain_text_cb (GObject *button,
+                        EMarkdownEditor *editor)
+{
+       GtkTextView *text_view;
+       GtkTextBuffer *buffer;
+       gchar *text;
+
+       text_view = g_object_get_data (button, "text_view");
+       buffer = gtk_text_view_get_buffer (text_view);
+       text = e_markdown_editor_dup_text (editor);
+
+       gtk_text_buffer_set_text (buffer, text, -1);
+
+       g_free (text);
+}
+
+static void
+editor_to_html_cb (GObject *button,
+                  EMarkdownEditor *editor)
+{
+       GtkTextView *text_view;
+       GtkTextBuffer *buffer;
+       gchar *text;
+
+       text_view = g_object_get_data (button, "text_view");
+       buffer = gtk_text_view_get_buffer (text_view);
+       text = e_markdown_editor_dup_html (editor);
+
+       gtk_text_buffer_set_text (buffer, text ? text : "NULL", -1);
+
+       g_free (text);
+}
+
+/* The cmark can add an empty line at the end of the HTML, thus compare without it too */
+static gboolean
+texts_are_same (gchar *text1,
+               gchar *text2)
+{
+       gint len1 = 0, len2 = 0;
+       gboolean text1_modified = FALSE, text2_modified = FALSE;
+       gboolean same;
+
+       if (text1 && text2) {
+               len1 = strlen (text1);
+               len2 = strlen (text2);
+
+               if (len1 + 1 == len2 && text2[len2 - 1] == '\n') {
+                       text2[len2 - 1] = '\0';
+                       text2_modified = TRUE;
+               } else if (len1 == len2 + 1 && text1[len1 - 1] == '\n') {
+                       text1[len1 - 1] = '\0';
+                       text1_modified = TRUE;
+               }
+       }
+
+       same = g_strcmp0 (text1, text2) == 0;
+
+       if (text1_modified)
+               text1[len1 - 1] = '\n';
+       else if (text2_modified)
+               text2[len2 - 1] = '\n';
+
+       return same || ((text1_modified || text2_modified) && g_strcmp0 (text1, text2) == 0);
+}
+
+static void
+plain_text_to_html_cb (GObject *button,
+                      GtkLabel *label)
+{
+       GtkTextView *plain_text_view, *html_text_view;
+       GtkTextBuffer *plain_buffer, *html_buffer;
+       GtkTextIter start_iter, end_iter;
+       gchar *old_text, *new_text, *tmp;
+
+       plain_text_view = g_object_get_data (button, "plain_text_view");
+       plain_buffer = gtk_text_view_get_buffer (plain_text_view);
+
+       html_text_view = g_object_get_data (button, "html_text_view");
+       html_buffer = gtk_text_view_get_buffer (html_text_view);
+
+       gtk_text_buffer_get_bounds (html_buffer, &start_iter, &end_iter);
+       old_text = gtk_text_buffer_get_text (html_buffer, &start_iter, &end_iter, FALSE);
+
+       gtk_text_buffer_get_bounds (plain_buffer, &start_iter, &end_iter);
+       tmp = gtk_text_buffer_get_text (plain_buffer, &start_iter, &end_iter, FALSE);
+
+       new_text = e_markdown_utils_text_to_html (tmp, -1);
+
+       gtk_text_buffer_set_text (html_buffer, new_text ? new_text : "NULL", -1);
+
+       if (texts_are_same (new_text, old_text))
+               gtk_label_set_text (label, "HTML text matches");
+       else
+               gtk_label_set_text (label, "Old and new HTML texts differ");
+
+       g_free (old_text);
+       g_free (new_text);
+       g_free (tmp);
+}
+
+static void
+html_to_plain_text_cb (GObject *button,
+                      GtkLabel *label)
+{
+       GtkTextView *plain_text_view, *html_text_view;
+       GtkTextBuffer *plain_buffer, *html_buffer;
+       GtkTextIter start_iter, end_iter;
+       GtkToggleButton *disallow_html_check;
+       gchar *old_text, *new_text, *tmp;
+
+       disallow_html_check = g_object_get_data (button, "disallow_html_check");
+
+       plain_text_view = g_object_get_data (button, "plain_text_view");
+       plain_buffer = gtk_text_view_get_buffer (plain_text_view);
+
+       html_text_view = g_object_get_data (button, "html_text_view");
+       html_buffer = gtk_text_view_get_buffer (html_text_view);
+
+       gtk_text_buffer_get_bounds (plain_buffer, &start_iter, &end_iter);
+       old_text = gtk_text_buffer_get_text (plain_buffer, &start_iter, &end_iter, FALSE);
+
+       gtk_text_buffer_get_bounds (html_buffer, &start_iter, &end_iter);
+       tmp = gtk_text_buffer_get_text (html_buffer, &start_iter, &end_iter, FALSE);
+
+       new_text = e_markdown_utils_html_to_text (tmp, -1, E_MARKDOWN_HTML_TO_TEXT_FLAG_NONE |
+               (gtk_toggle_button_get_active (disallow_html_check) ? E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT 
: 0));
+
+       gtk_text_buffer_set_text (plain_buffer, new_text ? new_text : "NULL", -1);
+
+       if (texts_are_same (new_text, old_text))
+               gtk_label_set_text (label, "Plain text matches");
+       else
+               gtk_label_set_text (label, "Old and new plain texts differ");
+
+       g_free (old_text);
+       g_free (new_text);
+       g_free (tmp);
+}
+
 static gint
 on_idle_create_widget (gpointer user_data)
 {
-       GtkWidget *window, *editor;
+       GtkWidget *window, *editor, *widget, *button1, *button2;
+       GtkGrid *grid;
+       GtkTextView *plain_text_view;
+       GtkTextView *html_text_view;
 
        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
        gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
@@ -31,6 +174,20 @@ on_idle_create_widget (gpointer user_data)
                window, "delete-event",
                G_CALLBACK (window_delete_event_cb), NULL);
 
+       widget = gtk_grid_new ();
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "visible", TRUE,
+               NULL);
+
+       gtk_container_add (GTK_CONTAINER (window), widget);
+
+       grid = GTK_GRID (widget);
+
        editor = e_markdown_editor_new ();
 
        g_object_set (G_OBJECT (editor),
@@ -41,7 +198,157 @@ on_idle_create_widget (gpointer user_data)
                "visible", TRUE,
                NULL);
 
-       gtk_container_add (GTK_CONTAINER (window), editor);
+       gtk_grid_attach (grid, editor, 0, 0, 3, 1);
+
+       widget = gtk_button_new_with_label ("vvv   As Plain Text   vvv");
+       button1 = widget;
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_START,
+               "hexpand", FALSE,
+               "valign", GTK_ALIGN_START,
+               "vexpand", FALSE,
+               "visible", TRUE,
+               NULL);
+
+       gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+       widget = gtk_button_new_with_label ("vvv   As HTML   vvv");
+       button2 = widget;
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_START,
+               "hexpand", FALSE,
+               "valign", GTK_ALIGN_START,
+               "vexpand", FALSE,
+               "visible", TRUE,
+               NULL);
+
+       gtk_grid_attach (grid, widget, 2, 1, 1, 1);
+
+       widget = gtk_text_view_new ();
+       plain_text_view = GTK_TEXT_VIEW (widget);
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "visible", TRUE,
+               "editable", TRUE,
+               "wrap-mode", GTK_WRAP_WORD_CHAR,
+               NULL);
+
+       widget = gtk_scrolled_window_new (NULL, NULL);
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "visible", TRUE,
+               "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               NULL);
+
+       gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (plain_text_view));
+
+       gtk_grid_attach (grid, widget, 0, 2, 1, 2);
+
+       widget = gtk_text_view_new ();
+       html_text_view = GTK_TEXT_VIEW (widget);
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "visible", TRUE,
+               "editable", TRUE,
+               "wrap-mode", GTK_WRAP_WORD_CHAR,
+               NULL);
+
+       widget = gtk_scrolled_window_new (NULL, NULL);
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "visible", TRUE,
+               "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               NULL);
+
+       gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (html_text_view));
+
+       gtk_grid_attach (grid, widget, 2, 2, 1, 2);
+
+       g_object_set_data (G_OBJECT (button1), "text_view", plain_text_view);
+       g_signal_connect (button1, "clicked", G_CALLBACK (editor_to_plain_text_cb), editor);
+
+       g_object_set_data (G_OBJECT (button2), "text_view", html_text_view);
+       g_signal_connect (button2, "clicked", G_CALLBACK (editor_to_html_cb), editor);
+
+       widget = gtk_button_new_with_label (">\n>\n>");
+       button1 = widget;
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_START,
+               "hexpand", FALSE,
+               "valign", GTK_ALIGN_START,
+               "vexpand", FALSE,
+               "visible", TRUE,
+               NULL);
+
+       gtk_grid_attach (grid, widget, 1, 2, 1, 1);
+
+       widget = gtk_button_new_with_label ("<\n<\n<");
+       button2 = widget;
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_START,
+               "hexpand", FALSE,
+               "valign", GTK_ALIGN_START,
+               "vexpand", FALSE,
+               "visible", TRUE,
+               NULL);
+
+       gtk_grid_attach (grid, widget, 1, 3, 1, 1);
+
+       widget = gtk_label_new ("");
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_START,
+               "hexpand", FALSE,
+               "valign", GTK_ALIGN_START,
+               "vexpand", FALSE,
+               "visible", TRUE,
+               NULL);
+
+       gtk_grid_attach (grid, widget, 0, 4, 3, 1);
+
+       g_object_set_data (G_OBJECT (button1), "plain_text_view", plain_text_view);
+       g_object_set_data (G_OBJECT (button1), "html_text_view", html_text_view);
+       g_signal_connect (button1, "clicked", G_CALLBACK (plain_text_to_html_cb), widget);
+
+       g_object_set_data (G_OBJECT (button2), "plain_text_view", plain_text_view);
+       g_object_set_data (G_OBJECT (button2), "html_text_view", html_text_view);
+       g_signal_connect (button2, "clicked", G_CALLBACK (html_to_plain_text_cb), widget);
+
+       widget = gtk_check_button_new_with_label ("HTML2Text: Disallow HTML");
+
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_START,
+               "hexpand", FALSE,
+               "valign", GTK_ALIGN_START,
+               "vexpand", FALSE,
+               "visible", TRUE,
+               NULL);
+
+       gtk_grid_attach (grid, widget, 0, 5, 3, 1);
+
+       g_object_set_data (G_OBJECT (button2), "disallow_html_check", widget);
 
        gtk_widget_show (window);
 
diff --git a/src/em-format/e-mail-formatter-text-markdown.c b/src/em-format/e-mail-formatter-text-markdown.c
index 1d0fb906dc..1e2e634dcc 100644
--- a/src/em-format/e-mail-formatter-text-markdown.c
+++ b/src/em-format/e-mail-formatter-text-markdown.c
@@ -65,7 +65,7 @@ emfe_text_markdown_format (EMailFormatterExtension *extension,
                e_mail_formatter_format_text (formatter, part, output_stream, cancellable);
                g_output_stream_flush (output_stream, cancellable, NULL);
 
-               html = e_markdown_util_text_to_html ((const gchar *) g_memory_output_stream_get_data 
(G_MEMORY_OUTPUT_STREAM (output_stream)),
+               html = e_markdown_utils_text_to_html ((const gchar *) g_memory_output_stream_get_data 
(G_MEMORY_OUTPUT_STREAM (output_stream)),
                        g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (output_stream)));
 
                g_object_unref (output_stream);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]