[evolution/449-support-markdown-in-composer: 15/15] Split markdown utilities into its own file
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution/449-support-markdown-in-composer: 15/15] Split markdown utilities into its own file
- Date: Tue, 8 Feb 2022 09:51:57 +0000 (UTC)
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]