talkatu/talkatu

0a9e000e7d0f
Parents a7ae8780678b
Children b70fde47bd44
Add support for rendering basic HTML in TalkatuHistory.

* Remove the data argument from class methods
* Renamed TalkatuHtmlParser to TalkatuHtmlRenderer
* Created TalkatuHtmlPangoRenderer
* Use the TalkatuMessage::content-type to determine how to render the text in
TalkatuHistory.

Testing Done:
Tested in the demo with a hack for the content type that will be fixed in a later review request.

Reviewed at https://reviews.imfreedom.org/r/403/
--- a/demo/talkatudemowindow.c Sat Jan 02 18:56:13 2021 -0600
+++ b/demo/talkatudemowindow.c Mon Jan 25 20:19:48 2021 -0600
@@ -213,14 +213,6 @@
TalkatuDemoWindow *window = TALKATU_DEMO_WINDOW(data);
static guint64 id = 0;
- if(TALKATU_IS_HTML_BUFFER(input)) {
- talkatu_message_set_content_type(TALKATU_MESSAGE(input),
- TALKATU_CONTENT_TYPE_HTML);
- } else if(TALKATU_IS_MARKDOWN_BUFFER(input)) {
- talkatu_message_set_content_type(TALKATU_MESSAGE(input),
- TALKATU_CONTENT_TYPE_MARKDOWN);
- }
-
talkatu_message_set_id(TALKATU_MESSAGE(input), id++);
talkatu_history_write_message(
--- a/po/POTFILES Sat Jan 02 18:56:13 2021 -0600
+++ b/po/POTFILES Mon Jan 25 20:19:48 2021 -0600
@@ -19,6 +19,7 @@
talkatu/talkatuhistoryrow.c
talkatu/talkatuhistory.c
talkatu/talkatuhtmlbuffer.c
+talkatu/talkatuhtmlrenderer.c
talkatu/talkatuinput.c
talkatu/talkatulinkdialog.c
talkatu/talkatumarkdownbuffer.c
--- a/talkatu/meson.build Sat Jan 02 18:56:13 2021 -0600
+++ b/talkatu/meson.build Mon Jan 25 20:19:48 2021 -0600
@@ -15,7 +15,8 @@
'talkatuhistory.h',
'talkatuhistoryrow.h',
'talkatuhtmlbuffer.h',
- 'talkatuhtmlparser.h',
+ 'talkatuhtmlpangorenderer.h',
+ 'talkatuhtmlrenderer.h',
'talkatuinput.h',
'talkatulinkdialog.h',
'talkatumarkdownbuffer.h',
@@ -46,7 +47,8 @@
'talkatuhistory.c',
'talkatuhistoryrow.c',
'talkatuhtmlbuffer.c',
- 'talkatuhtmlparser.c',
+ 'talkatuhtmlpangorenderer.c',
+ 'talkatuhtmlrenderer.c',
'talkatuinput.c',
'talkatulinkdialog.c',
'talkatumarkdownbuffer.c',
--- a/talkatu/reference/talkatu-docs.xml Sat Jan 02 18:56:13 2021 -0600
+++ b/talkatu/reference/talkatu-docs.xml Mon Jan 25 20:19:48 2021 -0600
@@ -40,7 +40,7 @@
<xi:include href="xml/talkatuhistory.xml"/>
<xi:include href="xml/talkatuhistoryrow.xml"/>
- <xi:include href="xml/talkatuhtmlparser.xml"/>
+ <xi:include href="xml/talkatuhtmlrenderer.xml"/>
<xi:include href="xml/talkatuinput.xml"/>
--- a/talkatu/talkatuhistoryrow.c Sat Jan 02 18:56:13 2021 -0600
+++ b/talkatu/talkatuhistoryrow.c Mon Jan 25 20:19:48 2021 -0600
@@ -21,6 +21,8 @@
#include <gtk/gtk.h>
#include <talkatu/talkatuhistoryrow.h>
+#include <talkatu/talkatuhtmlpangorenderer.h>
+#include <talkatu/talkatuhtmlrenderer.h>
/**
* SECTION:talkatuhistoryrow
@@ -100,6 +102,19 @@
if(content_type == TALKATU_CONTENT_TYPE_PANGO) {
gtk_label_set_markup(GTK_LABEL(row->content), contents);
+ } else if(content_type == TALKATU_CONTENT_TYPE_HTML) {
+ TalkatuHtmlRenderer *renderer = NULL;
+ TalkatuHtmlPangoRenderer *pr = NULL;
+
+ renderer = talkatu_html_pango_renderer_new();
+ pr = TALKATU_HTML_PANGO_RENDERER(renderer);
+
+ talkatu_html_renderer_render(renderer, contents);
+
+ gtk_label_set_markup(GTK_LABEL(row->content),
+ talkatu_html_pango_renderer_get_string(pr));
+
+ g_object_unref(G_OBJECT(renderer));
} else {
gtk_label_set_text(GTK_LABEL(row->content), contents);
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuhtmlpangorenderer.c Mon Jan 25 20:19:48 2021 -0600
@@ -0,0 +1,294 @@
+/*
+ * Talkatu - GTK widgets for chat applications
+ * Copyright (C) 2017-2021 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "talkatu/talkatuhtmlpangorenderer.h"
+
+typedef void (*TalkatuHtmlPangoRendererStartFunc)(TalkatuHtmlPangoRenderer *renderer,
+ const gchar *name,
+ const gchar **attribute_names,
+ const gchar **attribute_values);
+typedef void (*TalkatuHtmlPangoRendererFinishFunc)(TalkatuHtmlPangoRenderer *renderer,
+ const gchar *name);
+
+typedef struct {
+ TalkatuHtmlPangoRendererStartFunc start;
+ TalkatuHtmlPangoRendererFinishFunc finish;
+ const gchar *alias;
+} TalkatuHtmlPangoRendererElementFuncs;
+
+struct _TalkatuHtmlPangoRenderer {
+ TalkatuHtmlRenderer parent;
+
+ GString *str;
+
+ GHashTable *lookup;
+};
+
+G_DEFINE_TYPE(TalkatuHtmlPangoRenderer, talkatu_html_pango_renderer,
+ TALKATU_TYPE_HTML_RENDERER)
+
+/******************************************************************************
+ * TalkatuHtmlPangoRenderer Element Functions
+ *****************************************************************************/
+static void
+talkatu_html_pango_renderer_passthrough_start(TalkatuHtmlPangoRenderer *renderer,
+ const gchar *name,
+ const gchar **attribute_names,
+ const gchar **attribute_values)
+{
+ g_string_append_printf(renderer->str, "<%s>", name);
+}
+
+static void
+talkatu_html_pango_renderer_passthrough_finish(TalkatuHtmlPangoRenderer *renderer,
+ const gchar *name)
+{
+ g_string_append_printf(renderer->str, "</%s>", name);
+}
+
+static void
+talkatu_html_pango_renderer_code_start(TalkatuHtmlPangoRenderer *renderer,
+ const gchar *name,
+ const gchar **attribute_names,
+ const gchar **attribute_values)
+{
+ g_string_append(renderer->str, "<span font=\"monospace\">");
+}
+
+static void
+talkatu_html_pango_renderer_link_start(TalkatuHtmlPangoRenderer *renderer,
+ const gchar *name,
+ const gchar **attribute_names,
+ const gchar **attribute_values)
+{
+ gint i = 0;
+
+ g_string_append(renderer->str, "<a");
+
+ if(attribute_names != NULL) {
+ for(i = 0; attribute_names[i] != NULL; i++) {
+ if(g_ascii_strcasecmp(attribute_names[i], "href") == 0) {
+ g_string_append_printf(renderer->str,
+ " href=\"%s\"", attribute_values[i]);
+ }
+ }
+ }
+
+ g_string_append(renderer->str, ">");
+}
+
+static void
+talkatu_html_pango_renderer_font_start(TalkatuHtmlPangoRenderer *renderer,
+ const gchar *name,
+ const gchar **attribute_names,
+ const gchar **attribute_values)
+{
+ gint i = 0;
+
+ g_string_append(renderer->str, "<span");
+
+ if(attribute_names != NULL) {
+ for(i = 0; attribute_names[i] != NULL; i++) {
+ if(g_ascii_strcasecmp(attribute_names[i], "size") == 0) {
+ g_string_append_printf(renderer->str,
+ " font=\"%s\"", attribute_values[i]);
+ } else if(g_ascii_strcasecmp(attribute_names[i], "color") == 0) {
+ g_string_append_printf(renderer->str,
+ " foreground=\"%s\"", attribute_values[i]);
+ }
+ }
+ }
+
+ g_string_append(renderer->str, ">");
+}
+
+/******************************************************************************
+ * TalkatuHtmlRendererElementFuncs API
+ *****************************************************************************/
+TalkatuHtmlPangoRendererElementFuncs *
+talkatu_html_pango_renderer_element_funcs_new(TalkatuHtmlPangoRendererStartFunc start,
+ TalkatuHtmlPangoRendererFinishFunc finish,
+ const gchar *alias)
+{
+ TalkatuHtmlPangoRendererElementFuncs *funcs = NULL;
+
+ funcs = g_new(TalkatuHtmlPangoRendererElementFuncs, 1);
+ funcs->start = start;
+ funcs->finish = finish;
+ funcs->alias = alias;
+
+ return funcs;
+}
+
+TalkatuHtmlPangoRendererElementFuncs *
+talkatu_html_pango_renderer_element_funcs_new_passthrough(const gchar *alias) {
+ return talkatu_html_pango_renderer_element_funcs_new(
+ talkatu_html_pango_renderer_passthrough_start,
+ talkatu_html_pango_renderer_passthrough_finish,
+ alias
+ );
+}
+
+/******************************************************************************
+ * TalkatuHtmlRenderer Implementation
+ *****************************************************************************/
+static void
+talkatu_html_pango_renderer_reset(TalkatuHtmlRenderer *renderer) {
+ TalkatuHtmlPangoRenderer *pr = TALKATU_HTML_PANGO_RENDERER(renderer);
+
+ g_string_free(pr->str, TRUE);
+ pr->str = g_string_new("");
+}
+
+static void
+talkatu_html_pango_renderer_element_start(TalkatuHtmlRenderer *renderer,
+ const gchar *name,
+ const gchar **attribute_names,
+ const gchar **attribute_values)
+{
+ TalkatuHtmlPangoRenderer *pr = TALKATU_HTML_PANGO_RENDERER(renderer);
+ TalkatuHtmlPangoRendererElementFuncs *funcs = NULL;
+
+ funcs = g_hash_table_lookup(pr->lookup, name);
+ if(funcs != NULL && funcs->start != NULL) {
+ const gchar *real_name = (funcs->alias != NULL) ? funcs->alias : name;
+
+ funcs->start(pr, real_name, attribute_names, attribute_values);
+ }
+}
+
+static void
+talkatu_html_pango_renderer_element_finish(TalkatuHtmlRenderer *renderer,
+ const gchar *name)
+{
+ TalkatuHtmlPangoRenderer *pr = TALKATU_HTML_PANGO_RENDERER(renderer);
+ TalkatuHtmlPangoRendererElementFuncs *funcs = NULL;
+
+ funcs = g_hash_table_lookup(pr->lookup, name);
+ if(funcs != NULL && funcs->finish != NULL) {
+ const gchar *real_name = (funcs->alias != NULL) ? funcs->alias : name;
+
+ funcs->finish(pr, real_name);
+ }
+}
+
+static void
+talkatu_html_pango_renderer_text(TalkatuHtmlRenderer *renderer,
+ const gchar *text)
+{
+ TalkatuHtmlPangoRenderer *pr = TALKATU_HTML_PANGO_RENDERER(renderer);
+
+ g_string_append(pr->str, text);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+talkatu_html_pango_renderer_init(TalkatuHtmlPangoRenderer *renderer) {
+ TalkatuHtmlPangoRendererElementFuncs *funcs = NULL;
+
+ renderer->str = g_string_new("");
+
+ renderer->lookup = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+ g_free);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new(
+ talkatu_html_pango_renderer_link_start,
+ talkatu_html_pango_renderer_passthrough_finish,
+ NULL
+ );
+ g_hash_table_insert(renderer->lookup, "a", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new_passthrough(NULL);
+ g_hash_table_insert(renderer->lookup, "b", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new(
+ talkatu_html_pango_renderer_code_start,
+ talkatu_html_pango_renderer_passthrough_finish,
+ "span"
+ );
+ g_hash_table_insert(renderer->lookup, "code", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new_passthrough(NULL);
+ g_hash_table_insert(renderer->lookup, "i", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new(
+ talkatu_html_pango_renderer_font_start,
+ talkatu_html_pango_renderer_passthrough_finish,
+ "span"
+ );
+ g_hash_table_insert(renderer->lookup, "font", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new_passthrough(NULL);
+ g_hash_table_insert(renderer->lookup, "s", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new_passthrough("s");
+ g_hash_table_insert(renderer->lookup, "strike", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new_passthrough(NULL);
+ g_hash_table_insert(renderer->lookup, "sub", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new_passthrough(NULL);
+ g_hash_table_insert(renderer->lookup, "sup", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new_passthrough(NULL);
+ g_hash_table_insert(renderer->lookup, "tt", funcs);
+
+ funcs = talkatu_html_pango_renderer_element_funcs_new_passthrough(NULL);
+ g_hash_table_insert(renderer->lookup, "u", funcs);
+}
+
+static void
+talkatu_html_pango_renderer_finalize(GObject *obj) {
+ TalkatuHtmlPangoRenderer *renderer = TALKATU_HTML_PANGO_RENDERER(obj);
+
+ g_string_free(renderer->str, TRUE);
+ g_hash_table_destroy(renderer->lookup);
+
+ G_OBJECT_CLASS(talkatu_html_pango_renderer_parent_class)->finalize(obj);
+}
+
+static void
+talkatu_html_pango_renderer_class_init(TalkatuHtmlPangoRendererClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ TalkatuHtmlRendererClass *renderer_class = TALKATU_HTML_RENDERER_CLASS(klass);
+
+ obj_class->finalize = talkatu_html_pango_renderer_finalize;
+
+ renderer_class->reset = talkatu_html_pango_renderer_reset;
+ renderer_class->element_start = talkatu_html_pango_renderer_element_start;
+ renderer_class->element_finish = talkatu_html_pango_renderer_element_finish;
+ renderer_class->text = talkatu_html_pango_renderer_text;
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+TalkatuHtmlRenderer *
+talkatu_html_pango_renderer_new(void) {
+ return TALKATU_HTML_RENDERER(g_object_new(TALKATU_TYPE_HTML_PANGO_RENDERER,
+ NULL));
+}
+
+const gchar *
+talkatu_html_pango_renderer_get_string(TalkatuHtmlPangoRenderer *renderer) {
+ g_return_val_if_fail(TALKATU_IS_HTML_PANGO_RENDERER(renderer), NULL);
+
+ return renderer->str->str;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuhtmlpangorenderer.h Mon Jan 25 20:19:48 2021 -0600
@@ -0,0 +1,44 @@
+/*
+ * Talkatu - GTK widgets for chat applications
+ * Copyright (C) 2017-2021 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(TALKATU_GLOBAL_HEADER_INSIDE) && !defined(TALKATU_COMPILATION)
+#error "only <talkatu.h> may be included directly"
+#endif
+
+#ifndef TALKATU_HTML_PANGO_RENDERER_H
+#define TALKATU_HTML_PANGO_RENDERER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <talkatu/talkatuhtmlrenderer.h>
+
+#define TALKATU_TYPE_HTML_PANGO_RENDERER (talkatu_html_pango_renderer_get_type())
+G_DECLARE_FINAL_TYPE(TalkatuHtmlPangoRenderer, talkatu_html_pango_renderer,
+ TALKATU, HTML_PANGO_RENDERER, TalkatuHtmlRenderer)
+
+G_BEGIN_DECLS
+
+TalkatuHtmlRenderer *talkatu_html_pango_renderer_new(void);
+
+const gchar *talkatu_html_pango_renderer_get_string(TalkatuHtmlPangoRenderer *renderer);
+
+G_END_DECLS
+
+#endif /* TALKATU_HTML_PANGO_RENDERER_H */
+
--- a/talkatu/talkatuhtmlparser.c Sat Jan 02 18:56:13 2021 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,307 +0,0 @@
-/*
- * Talkatu - GTK widgets for chat applications
- * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
- *
- * This library is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#include <gumbo.h>
-
-#include "talkatuhtmlparser.h"
-
-/**
- * SECTION:talkatuhtmlparser
- * @Title: HTML Parsing
- * @Short_description: An Expat-like HTML Parser
- *
- * #TalkatuHtmlParser is an abstract class that will parse HTML and call the
- * registered instance methods for each element.
- */
-
-/**
- * TALKATU_TYPE_HTML_PARSER:
- *
- * The standard _get_type macro for #TalkatuHtmlParser.
- */
-
-/**
- * TalkatuHtmlParserClass:
- * @element_start: The method to call when an element is found. The attribute
- * names and values are passed in as a %NULL terminated array of
- * strings.
- * @element_finish: The method to call when all children of an element have been
- * processed.
- * @text: The method to call when can text or character data is found.
- * @comment: The method to call when a comment is found. The passed in comment
- * is the contents only and does not contain the start (<!--) and end
- * (-->) tags.
- *
- * An abstract class that will walk an HTML document and call the instance
- * methods of the child class for each node that is found.
- */
-
-#define _GUMBO_NODE_IS_CONTAINER(node) \
- ((node)->type == GUMBO_NODE_ELEMENT || \
- (node)->type == GUMBO_NODE_TEMPLATE)
-
-static GumboNode *
-talkatu_html_parser_find_next_sibling(GumboNode *node) {
- GumboNode *next = NULL;
-
- if(node->parent == NULL) {
- return NULL;
- }
-
- /* As long as we have a parent, we can use it with our node's
- * `index_within_parent` to figure out if we have any more siblings.
- */
- if(_GUMBO_NODE_IS_CONTAINER(node->parent)) {
- GumboElement element = node->parent->v.element;
-
- if(node->index_within_parent != element.children.length - 1) {
- next = element.children.data[node->index_within_parent+1];
- }
- }
-
- return next;
-}
-
-/******************************************************************************
- * Helper Implementations
- *****************************************************************************/
-static void
-talkatu_html_parser_element_start(TalkatuHtmlParser *parser,
- const gchar *name,
- GumboElement *element,
- gpointer data)
-{
- TalkatuHtmlParserClass *klass = TALKATU_HTML_PARSER_GET_CLASS(parser);
-
- if(klass->element_start) {
- const gchar **names = NULL, **values = NULL;
- guint length = element->attributes.length;
-
- if(length > 0) {
- guint i = 0;
-
- names = g_new(const gchar *, length + 1);
- values = g_new(const gchar *, length + 1);
-
- for(i = 0; i < length; i++) {
- GumboAttribute *attr = NULL;
-
- attr = (GumboAttribute *)element->attributes.data[i];
-
- names[i] = attr->name;
- values[i] = attr->value;
- }
-
- /* add our terminating null values to the end */
- names[i] = NULL;
- values[i] = NULL;
- }
-
- klass->element_start(parser, name, names, values, data);
-
- g_free(names);
- g_free(values);
- }
-}
-
-static void
-talkatu_html_parser_element_finish(TalkatuHtmlParser *parser,
- const gchar *name,
- gpointer data)
-{
- TalkatuHtmlParserClass *klass = TALKATU_HTML_PARSER_GET_CLASS(parser);
-
- if(klass->element_finish) {
- klass->element_finish(parser, name, data);
- }
-}
-
-static void
-talkatu_html_parser_text(TalkatuHtmlParser *parser, const gchar *text,
- gpointer data)
-{
- TalkatuHtmlParserClass *klass = TALKATU_HTML_PARSER_GET_CLASS(parser);
-
- if(klass->text) {
- klass->text(parser, text, data);
- }
-}
-
-static void
-talkatu_html_parser_comment(TalkatuHtmlParser *parser, const gchar *comment,
- gpointer data)
-{
- TalkatuHtmlParserClass *klass = TALKATU_HTML_PARSER_GET_CLASS(parser);
-
- if(klass->comment) {
- klass->comment(parser, comment, data);
- }
-}
-
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_ABSTRACT_TYPE(TalkatuHtmlParser, talkatu_html_parser, G_TYPE_OBJECT)
-
-static void
-talkatu_html_parser_init(TalkatuHtmlParser *parser) {
-}
-
-static void
-talkatu_html_parser_class_init(TalkatuHtmlParserClass *klass) {
-}
-
-/******************************************************************************
- * Public API
- *****************************************************************************/
-
-/**
- * talkatu_html_parser_parse:
- * @parser: The #TalkatuHtmlParser instance.
- * @html: The HTML text to parse.
- * @data: User data to pass to all of the handler functions.
- *
- * Starts parsing the given @html calling the #TalkatuHtmlParserClass functions
- * as necessary.
- */
-void
-talkatu_html_parser_parse(TalkatuHtmlParser *parser, const gchar *html,
- gpointer data)
-{
- GList *stack = NULL;
- GumboOutput *output = NULL;
-
- output = gumbo_parse(html);
-
- stack = g_list_prepend(stack, output->root);
-
- /* We create a stack with the first node and then process according to the
- * node type.
- *
- * For non-element nodes, we call the text/comment function as appropriate
- * and then look for their siblings. If the node has a sibling, we remove
- * the current node from the stack and replace it with its sibling.
- *
- * For element nodes, we call element_start and push the first child to the
- * stack if the node has children and immediately start processing the
- * child. If the element does not have children, we call element_finish,
- * remove it from the stack and look for a sibling to push to the stack.
- *
- * If the node does not have a sibling, we call element_finish on its parent
- * and remove it from the stack. Then we check for its parent and repeat
- * the process until we have found a sibling or have exhausted the stack.
- */
-
- while(stack != NULL) {
- GumboNode *node = (GumboNode *)stack->data;
- GumboNode *next = NULL;
- const gchar *tagname = NULL;
-
- switch(node->type) {
- case GUMBO_NODE_DOCUMENT:
- /* this is here to stop a warning from gcc. We could add a
- * default case, but then if a new type is added or something we
- * would mask the warning that it would generate.
- */
- break;
- case GUMBO_NODE_ELEMENT:
- case GUMBO_NODE_TEMPLATE:
- tagname = gumbo_normalized_tagname(node->v.element.tag);
- talkatu_html_parser_element_start(parser, tagname,
- &node->v.element, data);
-
- if(node->v.element.children.length > 0) {
- /* if we have at least one child, we throw it on the stack
- * and start processing that node.
- */
- node = (GumboNode *)(&node->v.element.children)->data[0];
- stack = g_list_prepend(stack, node);
-
- continue;
- } else {
- /* We have no children so we just call the finish method. */
- talkatu_html_parser_element_finish(parser, tagname, data);
- }
- break;
- case GUMBO_NODE_CDATA:
- case GUMBO_NODE_TEXT:
- case GUMBO_NODE_WHITESPACE:
- talkatu_html_parser_text(parser, node->v.text.text, data);
- break;
- case GUMBO_NODE_COMMENT:
- talkatu_html_parser_comment(parser, node->v.text.text, data);
- break;
- }
-
- /* check if we have a sibling */
- next = talkatu_html_parser_find_next_sibling(node);
-
- /* remove the node from the stack */
- stack = g_list_remove(stack, node);
-
- /* if the node was the last, we need to end element_finish for the
- * parent and pop the parent from the stack as well.
- */
- if(next != NULL) {
- stack = g_list_prepend(stack, next);
- } else if(node->parent != NULL && _GUMBO_NODE_IS_CONTAINER(node->parent)) {
- /* Our node has no other siblings, so we need to finish the parent
- * element.
- */
- GumboElement parent_element = node->parent->v.element;
- tagname = gumbo_normalized_tagname(parent_element.tag);
-
- talkatu_html_parser_element_finish(parser, tagname, data);
-
- /* while we still have elements on the list, pop them off until we
- * find one that still has children we haven't visited yet.
- */
- while(stack != NULL) {
- GumboElement element;
-
- node = (GumboNode *)stack->data;
-
- next = talkatu_html_parser_find_next_sibling(node);
-
- if(next != NULL) {
- /* we found a sibling, so drop the top most item and
- * put the sibling on the top of the stack.
- */
- stack = g_list_remove(stack, node);
- stack = g_list_prepend(stack, next);
-
- break;
- }
-
- if(node->parent->type != GUMBO_NODE_DOCUMENT) {
- element = node->parent->v.element;
- tagname = gumbo_normalized_tagname(element.tag);
-
- talkatu_html_parser_element_finish(parser, tagname, data);
- }
-
- /* If this node doesn't have a sibling, then pop it off the
- * stack.
- */
- stack = g_list_remove(stack, node);
- }
- }
- }
-
- gumbo_destroy_output(&kGumboDefaultOptions, output);
-}
--- a/talkatu/talkatuhtmlparser.h Sat Jan 02 18:56:13 2021 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/*
- * Talkatu - GTK widgets for chat applications
- * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
- *
- * This library is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#if !defined(TALKATU_GLOBAL_HEADER_INSIDE) && !defined(TALKATU_COMPILATION)
-#error "only <talkatu.h> may be included directly"
-#endif
-
-#ifndef TALKATU_HTML_H
-#define TALKATU_HTML_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-#define TALKATU_TYPE_HTML_PARSER (talkatu_html_parser_get_type())
-G_DECLARE_DERIVABLE_TYPE(TalkatuHtmlParser, talkatu_html_parser, TALKATU,
- HTML_PARSER, GObject)
-
-struct _TalkatuHtmlParserClass {
- /*< private >*/
- GObjectClass parent;
-
- /*< public >*/
- void (*element_start)(TalkatuHtmlParser *parser, const gchar *name, const gchar **attribute_names, const gchar **attribute_values, gpointer data);
- void (*element_finish)(TalkatuHtmlParser *parser, const gchar *name, gpointer data);
- void (*text)(TalkatuHtmlParser *parser, const gchar *text, gpointer data);
- void (*comment)(TalkatuHtmlParser *parser, const gchar *comment, gpointer data);
-};
-
-G_BEGIN_DECLS
-
-void talkatu_html_parser_parse(TalkatuHtmlParser *parser, const gchar *html, gpointer data);
-
-G_END_DECLS
-
-#endif /* TALKATU_HTML_H */
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuhtmlrenderer.c Mon Jan 25 20:19:48 2021 -0600
@@ -0,0 +1,322 @@
+/*
+ * Talkatu - GTK widgets for chat applications
+ * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <gumbo.h>
+
+#include "talkatuhtmlrenderer.h"
+
+/**
+ * SECTION:talkatuhtmlrenderer
+ * @Title: HTML Renderer
+ * @Short_description: An Expat-like HTML Renderer
+ *
+ * #TalkatuHtmlRenderer is an abstract class that will parse HTML and call the
+ * registered instance methods for each element allowing subclasses to render
+ * the HTML however they like.
+ */
+
+/**
+ * TALKATU_TYPE_HTML_RENDERER:
+ *
+ * The standard _get_type macro for #TalkatuHtmlRenderer.
+ */
+
+/**
+ * TalkatuHtmlRendererClass:
+ * @reset: The method to call to reset the renderer. This allows the renderer
+ * to be reused.
+ * @element_start: The method to call when an element is found. The attribute
+ * names and values are passed in as a %NULL terminated array of
+ * strings.
+ * @element_finish: The method to call when all children of an element have been
+ * processed.
+ * @text: The method to call when can text or character data is found.
+ * @comment: The method to call when a comment is found. The passed in comment
+ * is the contents only and does not contain the start (<!--) and end
+ * (-->) tags.
+ *
+ * An abstract class that will walk an HTML document and call the instance
+ * methods of the child class for each node that is found.
+ */
+
+#define _GUMBO_NODE_IS_CONTAINER(node) \
+ ((node)->type == GUMBO_NODE_ELEMENT || \
+ (node)->type == GUMBO_NODE_TEMPLATE)
+
+static GumboNode *
+talkatu_html_renderer_find_next_sibling(GumboNode *node) {
+ GumboNode *next = NULL;
+
+ if(node->parent == NULL) {
+ return NULL;
+ }
+
+ /* As long as we have a parent, we can use it with our node's
+ * `index_within_parent` to figure out if we have any more siblings.
+ */
+ if(_GUMBO_NODE_IS_CONTAINER(node->parent)) {
+ GumboElement element = node->parent->v.element;
+
+ if(node->index_within_parent != element.children.length - 1) {
+ next = element.children.data[node->index_within_parent+1];
+ }
+ }
+
+ return next;
+}
+
+/******************************************************************************
+ * Helper Implementations
+ *****************************************************************************/
+static void
+talkatu_html_renderer_element_start(TalkatuHtmlRenderer *renderer,
+ const gchar *name,
+ GumboElement *element)
+{
+ TalkatuHtmlRendererClass *klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer);
+
+ if(klass->element_start) {
+ const gchar **names = NULL, **values = NULL;
+ guint length = element->attributes.length;
+
+ if(length > 0) {
+ guint i = 0;
+
+ names = g_new(const gchar *, length + 1);
+ values = g_new(const gchar *, length + 1);
+
+ for(i = 0; i < length; i++) {
+ GumboAttribute *attr = NULL;
+
+ attr = (GumboAttribute *)element->attributes.data[i];
+
+ names[i] = attr->name;
+ values[i] = attr->value;
+ }
+
+ /* add our terminating null values to the end */
+ names[i] = NULL;
+ values[i] = NULL;
+ }
+
+ klass->element_start(renderer, name, names, values);
+
+ g_free(names);
+ g_free(values);
+ }
+}
+
+static void
+talkatu_html_renderer_element_finish(TalkatuHtmlRenderer *renderer,
+ const gchar *name)
+{
+ TalkatuHtmlRendererClass *klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer);
+
+ if(klass->element_finish) {
+ klass->element_finish(renderer, name);
+ }
+}
+
+static void
+talkatu_html_renderer_text(TalkatuHtmlRenderer *renderer, const gchar *text) {
+ TalkatuHtmlRendererClass *klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer);
+
+ if(klass->text) {
+ klass->text(renderer, text);
+ }
+}
+
+static void
+talkatu_html_renderer_comment(TalkatuHtmlRenderer *renderer,
+ const gchar *comment)
+{
+ TalkatuHtmlRendererClass *klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer);
+
+ if(klass->comment) {
+ klass->comment(renderer, comment);
+ }
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_ABSTRACT_TYPE(TalkatuHtmlRenderer, talkatu_html_renderer,
+ G_TYPE_OBJECT)
+
+static void
+talkatu_html_renderer_init(TalkatuHtmlRenderer *renderer) {
+}
+
+static void
+talkatu_html_renderer_class_init(TalkatuHtmlRendererClass *klass) {
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * talkatu_html_renderer_render:
+ * @renderer: The #TalkatuHtmlRenderer instance.
+ * @html: The HTML text to render.
+ *
+ * Renders the given @html calling the #TalkatuHtmlRendererClass functions as
+ * necessary.
+ */
+void
+talkatu_html_renderer_render(TalkatuHtmlRenderer *renderer, const gchar *html) {
+ GList *stack = NULL;
+ GumboOutput *output = NULL;
+
+ output = gumbo_parse(html);
+
+ stack = g_list_prepend(stack, output->root);
+
+ /* We create a stack with the first node and then process according to the
+ * node type.
+ *
+ * For non-element nodes, we call the text/comment function as appropriate
+ * and then look for their siblings. If the node has a sibling, we remove
+ * the current node from the stack and replace it with its sibling.
+ *
+ * For element nodes, we call element_start and push the first child to the
+ * stack if the node has children and immediately start processing the
+ * child. If the element does not have children, we call element_finish,
+ * remove it from the stack and look for a sibling to push to the stack.
+ *
+ * If the node does not have a sibling, we call element_finish on its parent
+ * and remove it from the stack. Then we check for its parent and repeat
+ * the process until we have found a sibling or have exhausted the stack.
+ */
+
+ while(stack != NULL) {
+ GumboNode *node = (GumboNode *)stack->data;
+ GumboNode *next = NULL;
+ const gchar *tagname = NULL;
+
+ switch(node->type) {
+ case GUMBO_NODE_DOCUMENT:
+ /* this is here to stop a warning from gcc. We could add a
+ * default case, but then if a new type is added or something we
+ * would mask the warning that it would generate.
+ */
+ break;
+ case GUMBO_NODE_ELEMENT:
+ case GUMBO_NODE_TEMPLATE:
+ tagname = gumbo_normalized_tagname(node->v.element.tag);
+ talkatu_html_renderer_element_start(renderer, tagname,
+ &node->v.element);
+
+ if(node->v.element.children.length > 0) {
+ /* if we have at least one child, we throw it on the stack
+ * and start processing that node.
+ */
+ node = (GumboNode *)(&node->v.element.children)->data[0];
+ stack = g_list_prepend(stack, node);
+
+ continue;
+ } else {
+ /* We have no children so we just call the finish method. */
+ talkatu_html_renderer_element_finish(renderer, tagname);
+ }
+ break;
+ case GUMBO_NODE_CDATA:
+ case GUMBO_NODE_TEXT:
+ case GUMBO_NODE_WHITESPACE:
+ talkatu_html_renderer_text(renderer, node->v.text.text);
+ break;
+ case GUMBO_NODE_COMMENT:
+ talkatu_html_renderer_comment(renderer, node->v.text.text);
+ break;
+ }
+
+ /* check if we have a sibling */
+ next = talkatu_html_renderer_find_next_sibling(node);
+
+ /* remove the node from the stack */
+ stack = g_list_remove(stack, node);
+
+ /* if the node was the last, we need to end element_finish for the
+ * parent and pop the parent from the stack as well.
+ */
+ if(next != NULL) {
+ stack = g_list_prepend(stack, next);
+ } else if(node->parent != NULL && _GUMBO_NODE_IS_CONTAINER(node->parent)) {
+ /* Our node has no other siblings, so we need to finish the parent
+ * element.
+ */
+ GumboElement parent_element = node->parent->v.element;
+ tagname = gumbo_normalized_tagname(parent_element.tag);
+
+ talkatu_html_renderer_element_finish(renderer, tagname);
+
+ /* while we still have elements on the list, pop them off until we
+ * find one that still has children we haven't visited yet.
+ */
+ while(stack != NULL) {
+ GumboElement element;
+
+ node = (GumboNode *)stack->data;
+
+ next = talkatu_html_renderer_find_next_sibling(node);
+
+ if(next != NULL) {
+ /* we found a sibling, so drop the top most item and
+ * put the sibling on the top of the stack.
+ */
+ stack = g_list_remove(stack, node);
+ stack = g_list_prepend(stack, next);
+
+ break;
+ }
+
+ if(node->parent->type != GUMBO_NODE_DOCUMENT) {
+ element = node->parent->v.element;
+ tagname = gumbo_normalized_tagname(element.tag);
+
+ talkatu_html_renderer_element_finish(renderer, tagname);
+ }
+
+ /* If this node doesn't have a sibling, then pop it off the
+ * stack.
+ */
+ stack = g_list_remove(stack, node);
+ }
+ }
+ }
+
+ gumbo_destroy_output(&kGumboDefaultOptions, output);
+}
+
+/**
+ * talkatu_html_renderer_reset:
+ * @renderer: The #TalkatuHtmlRenderer instance.
+ *
+ * Resets @renderer back to a clean state so that it can render new HTML.
+ */
+void
+talkatu_html_renderer_reset(TalkatuHtmlRenderer *renderer) {
+ TalkatuHtmlRendererClass *klass = NULL;
+
+ g_return_if_fail(TALKATU_IS_HTML_RENDERER(renderer));
+
+ klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer);
+ if(klass && klass->reset) {
+ klass->reset(renderer);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuhtmlrenderer.h Mon Jan 25 20:19:48 2021 -0600
@@ -0,0 +1,58 @@
+/*
+ * Talkatu - GTK widgets for chat applications
+ * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(TALKATU_GLOBAL_HEADER_INSIDE) && !defined(TALKATU_COMPILATION)
+#error "only <talkatu.h> may be included directly"
+#endif
+
+#ifndef TALKATU_HTML_RENDERER_H
+#define TALKATU_HTML_RENDERER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define TALKATU_TYPE_HTML_RENDERER (talkatu_html_renderer_get_type())
+G_DECLARE_DERIVABLE_TYPE(TalkatuHtmlRenderer, talkatu_html_renderer, TALKATU,
+ HTML_RENDERER, GObject)
+
+struct _TalkatuHtmlRendererClass {
+ /*< private >*/
+ GObjectClass parent;
+
+ /*< public >*/
+ void (*reset)(TalkatuHtmlRenderer *renderer);
+
+ void (*element_start)(TalkatuHtmlRenderer *renderer, const gchar *name, const gchar **attribute_names, const gchar **attribute_values);
+ void (*element_finish)(TalkatuHtmlRenderer *renderer, const gchar *name);
+ void (*text)(TalkatuHtmlRenderer *renderer, const gchar *text);
+ void (*comment)(TalkatuHtmlRenderer *renderer, const gchar *comment);
+
+ /*< private >*/
+ gpointer reserved[4];
+};
+
+G_BEGIN_DECLS
+
+void talkatu_html_renderer_render(TalkatuHtmlRenderer *renderer, const gchar *html);
+
+void talkatu_html_renderer_reset(TalkatuHtmlRenderer *renderer);
+
+G_END_DECLS
+
+#endif /* TALKATU_HTML_RENDERER_H */
+
--- a/talkatu/tests/meson.build Sat Jan 02 18:56:13 2021 -0600
+++ b/talkatu/tests/meson.build Mon Jan 25 20:19:48 2021 -0600
@@ -17,10 +17,17 @@
test('action-group', TEST_WRAPPER, args : e, is_parallel : false)
e = executable(
- 'test-html-parser',
- 'talkatutesthtmlparser.c',
+ 'test-html-pango-renderer',
+ 'talkatutesthtmlpangorenderer.c',
dependencies : [talkatu_dep, GLIB]
)
-test('html-parser', TEST_WRAPPER, args : e, is_parallel : false)
+test('html-pango-renderer', TEST_WRAPPER, args : e, is_parallel : false)
+
+e = executable(
+ 'test-html-renderer',
+ 'talkatutesthtmlrenderer.c',
+ dependencies : [talkatu_dep, GLIB]
+)
+test('html-renderer', TEST_WRAPPER, args : e, is_parallel : false)
endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/tests/talkatutesthtmlpangorenderer.c Mon Jan 25 20:19:48 2021 -0600
@@ -0,0 +1,177 @@
+/*
+ * talkatu
+ * Copyright (C) 2017-2021 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <talkatu.h>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_talkatu_html_pango_renderer_simple(void) {
+ TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new();
+ const gchar *inp = "plain text";
+ const gchar *exp = "plain text";
+ const gchar *act = NULL;
+
+ talkatu_html_renderer_render(renderer, inp);
+ act = talkatu_html_pango_renderer_get_string(TALKATU_HTML_PANGO_RENDERER(renderer));
+
+ g_assert_cmpstr(act, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_pango_renderer_mixed(void) {
+ TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new();
+ const gchar *inp = \
+ "<html><head></head><body>" \
+ "<i><b>emphasis <u>underline</u></b> <strike>strike</strike> italic</i>" \
+ "</body></html>";
+ const gchar *exp = \
+ "<i><b>emphasis <u>underline</u></b> <s>strike</s> italic</i>";
+ const gchar *act = NULL;
+
+ talkatu_html_renderer_render(renderer, inp);
+ act = talkatu_html_pango_renderer_get_string(TALKATU_HTML_PANGO_RENDERER(renderer));
+
+ g_assert_cmpstr(act, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_pango_renderer_with_comment(void) {
+ TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new();
+ const gchar *inp = \
+ "Hello, <!--Darkness, my old friend, here to conquer the--> World!";
+ const gchar *exp = "Hello, World!";
+ const gchar *act = NULL;
+
+ talkatu_html_renderer_render(renderer, inp);
+ act = talkatu_html_pango_renderer_get_string(TALKATU_HTML_PANGO_RENDERER(renderer));
+
+ g_assert_cmpstr(act, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_pango_renderer_with_attributes(void) {
+ TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new();
+ const gchar *inp = \
+ "<html><head></head><body>" \
+ "<font size=\"2\" color=\"#007f00\">talkatu</font>" \
+ "</body></html>";
+ const gchar *exp = "<span font=\"2\" foreground=\"#007f00\">talkatu</span>";
+ const gchar *act = NULL;
+
+ talkatu_html_renderer_render(renderer, inp);
+ act = talkatu_html_pango_renderer_get_string(TALKATU_HTML_PANGO_RENDERER(renderer));
+
+ g_assert_cmpstr(act, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_pango_renderer_with_nested_attributes(void) {
+ TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new();
+ const gchar *inp = \
+ "<html><head></head><body>" \
+ "<font size=\"2\" color=\"#007f00\">" \
+ "<a href=\"https://keep.imfreedom.org/talkatu/talkatu/\">talkatu</a>" \
+ "</font>" \
+ "</body></html>";
+ const gchar *exp = \
+ "<span font=\"2\" foreground=\"#007f00\">" \
+ "<a href=\"https://keep.imfreedom.org/talkatu/talkatu/\">talkatu</a>" \
+ "</span>";
+ const gchar *act = NULL;
+
+ talkatu_html_renderer_render(renderer, inp);
+ act = talkatu_html_pango_renderer_get_string(TALKATU_HTML_PANGO_RENDERER(renderer));
+
+ g_assert_cmpstr(act, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_pango_renderer_code(void) {
+ TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new();
+ const gchar *inp = \
+ "<html><head></head><body>" \
+ "<code>monkey</code>" \
+ "</body></html>";
+ const gchar *exp = \
+ "<span font=\"monospace\">monkey</span>";
+ const gchar *act = NULL;
+
+ talkatu_html_renderer_render(renderer, inp);
+ act = talkatu_html_pango_renderer_get_string(TALKATU_HTML_PANGO_RENDERER(renderer));
+
+ g_assert_cmpstr(act, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar **argv) {
+ gint ret = 0;
+
+ g_test_init(&argc, &argv, NULL);
+
+ gtk_init(&argc, &argv);
+
+ talkatu_init();
+
+ g_test_add_func(
+ "/html-pango-renderer/simple",
+ test_talkatu_html_pango_renderer_simple);
+
+ g_test_add_func(
+ "/html-pango-renderer/mixed-children",
+ test_talkatu_html_pango_renderer_mixed);
+
+ g_test_add_func(
+ "/html-pango-renderer/with-comment",
+ test_talkatu_html_pango_renderer_with_comment);
+
+ g_test_add_func(
+ "/html-pango-renderer/with-attributes",
+ test_talkatu_html_pango_renderer_with_attributes);
+
+ g_test_add_func(
+ "/html-pango-renderer/with-nested-attributes",
+ test_talkatu_html_pango_renderer_with_nested_attributes);
+
+ g_test_add_func(
+ "/html-pango-renderer/code",
+ test_talkatu_html_pango_renderer_code);
+
+ ret = g_test_run();
+
+ talkatu_uninit();
+
+ return ret;
+}
+
--- a/talkatu/tests/talkatutesthtmlparser.c Sat Jan 02 18:56:13 2021 -0600
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,228 +0,0 @@
-/*
- * talkatu
- * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#include <talkatu.h>
-
-/******************************************************************************
- * TestTalkatuHtmlParser
- *****************************************************************************/
-#define TEST_TALKATU_TYPE_HTML_PARSER (test_talkatu_html_parser_get_type())
-G_DECLARE_FINAL_TYPE(TestTalkatuHtmlParser, test_talkatu_html_parser,
- TEST_TALKATU, HTML_PARSER, TalkatuHtmlParser)
-
-struct _TestTalkatuHtmlParser {
- TalkatuHtmlParser parent;
-};
-
-static void
-test_talkatu_html_parser_element_start(TalkatuHtmlParser *parser,
- const gchar *name,
- const gchar **attr_names,
- const gchar **attr_values,
- gpointer data)
-{
- GString *str = (GString *)data;
-
- g_string_append_printf(str, "<%s", name);
-
- if(attr_names != NULL) {
- gint i = 0;
-
- for(i = 0; attr_names[i] != NULL; i++) {
- g_string_append_printf(str, " %s=\"", attr_names[i]);
- if(attr_values[i] != NULL) {
- g_string_append_printf(str, "%s", attr_values[i]);
- }
- g_string_append_printf(str, "\"");
- }
- }
-
- g_string_append_printf(str, ">");
-}
-
-static void
-test_talkatu_html_parser_element_finish(TalkatuHtmlParser *parser,
- const gchar *name,
- gpointer data)
-{
- g_string_append_printf((GString *)data, "</%s>", name);
-}
-
-static void
-test_talkatu_html_parser_text(TalkatuHtmlParser *parser, const gchar *text,
- gpointer data)
-{
- g_string_append_printf((GString *)data, "%s", text);
-}
-
-static void
-test_talkatu_html_parser_comment(TalkatuHtmlParser *parser,
- const gchar *comment, gpointer data)
-{
- g_string_append_printf((GString *)data, "<!--%s-->", comment);
-}
-
-G_DEFINE_TYPE(TestTalkatuHtmlParser, test_talkatu_html_parser, TALKATU_TYPE_HTML_PARSER);
-
-static void
-test_talkatu_html_parser_init(TestTalkatuHtmlParser *parser) {
-}
-
-static void
-test_talkatu_html_parser_class_init(TestTalkatuHtmlParserClass *klass) {
- TalkatuHtmlParserClass *parser_class = TALKATU_HTML_PARSER_CLASS(klass);
-
- parser_class->element_start = test_talkatu_html_parser_element_start;
- parser_class->element_finish = test_talkatu_html_parser_element_finish;
- parser_class->text = test_talkatu_html_parser_text;
- parser_class->comment = test_talkatu_html_parser_comment;
-}
-
-TalkatuHtmlParser *
-test_talkatu_html_parser_new(void) {
- return TALKATU_HTML_PARSER(g_object_new(TEST_TALKATU_TYPE_HTML_PARSER,
- NULL));
-}
-
-/******************************************************************************
- * Tests
- *****************************************************************************/
-static void
-test_talkatu_html_parser_simple(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- const gchar *exp = "<html><head></head><body>plain text</body></html>";
-
- talkatu_html_parser_parse(parser, "plain text", str);
-
- g_assert_cmpstr(str->str, ==, exp);
-
- g_string_free(str, TRUE);
- g_object_unref(G_OBJECT(parser));
-}
-
-static void
-test_talkatu_html_parser_mixed(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- const gchar *exp = \
- "<html><head></head><body>" \
- "<i><b>emphasis <u>underline</u></b> <strike>strike</strike> italic</i>" \
- "</body></html>";
-
- talkatu_html_parser_parse(parser, exp, str);
-
- g_assert_cmpstr(str->str, ==, exp);
-
- g_string_free(str, TRUE);
- g_object_unref(G_OBJECT(parser));
-}
-
-static void
-test_talkatu_html_parser_with_comment(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- const gchar *exp = \
- "<html><head></head><body>" \
- "Hello, <!--Darkness, my old friend, here to conquer the--> World!" \
- "</body></html>";
-
- talkatu_html_parser_parse(parser, exp, str);
-
- g_assert_cmpstr(str->str, ==, exp);
-
- g_string_free(str, TRUE);
- g_object_unref(G_OBJECT(parser));
-}
-
-static void
-test_talkatu_html_parser_with_attributes(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- const gchar *exp = \
- "<html><head></head><body>" \
- "<font size=\"2\" color=\"#007f00\">talkatu</font>" \
- "</body></html>";
-
- talkatu_html_parser_parse(parser, exp, str);
-
- g_assert_cmpstr(str->str, ==, exp);
-
- g_string_free(str, TRUE);
- g_object_unref(G_OBJECT(parser));
-}
-
-static void
-test_talkatu_html_parser_with_nested_attributes(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- const gchar *exp = \
- "<html><head></head><body>" \
- "<font size=\"2\" color=\"#007f00\">" \
- "<a href=\"https://keep.imfreedom.org/talkatu/talkatu/\">talkatu</a>" \
- "</font>" \
- "</body></html>";
-
- talkatu_html_parser_parse(parser, exp, str);
-
- g_assert_cmpstr(str->str, ==, exp);
-
- g_string_free(str, TRUE);
- g_object_unref(G_OBJECT(parser));
-}
-
-/******************************************************************************
- * Main
- *****************************************************************************/
-gint
-main(gint argc, gchar **argv) {
- gint ret = 0;
-
- g_test_init(&argc, &argv, NULL);
-
- gtk_init(&argc, &argv);
-
- talkatu_init();
-
- g_test_add_func(
- "/html-parser/simple",
- test_talkatu_html_parser_simple);
-
- g_test_add_func(
- "/html-parser/mixed-children",
- test_talkatu_html_parser_mixed);
-
- g_test_add_func(
- "/html-parser/with-comment",
- test_talkatu_html_parser_with_comment);
-
- g_test_add_func(
- "/html-parser/with-attributes",
- test_talkatu_html_parser_with_attributes);
-
- g_test_add_func(
- "/html-parser/with-nested-attributes",
- test_talkatu_html_parser_with_nested_attributes);
-
- ret = g_test_run();
-
- talkatu_uninit();
-
- return ret;
-}
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/tests/talkatutesthtmlrenderer.c Mon Jan 25 20:19:48 2021 -0600
@@ -0,0 +1,238 @@
+/*
+ * talkatu
+ * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <talkatu.h>
+
+/******************************************************************************
+ * TestTalkatuHtmlRenderer
+ *****************************************************************************/
+#define TEST_TALKATU_TYPE_HTML_PARSER (test_talkatu_html_renderer_get_type())
+G_DECLARE_FINAL_TYPE(TestTalkatuHtmlRenderer, test_talkatu_html_renderer,
+ TEST_TALKATU, HTML_RENDERER, TalkatuHtmlRenderer)
+
+struct _TestTalkatuHtmlRenderer {
+ TalkatuHtmlRenderer parent;
+
+ GString *str;
+};
+
+static void
+test_talkatu_html_renderer_element_start(TalkatuHtmlRenderer *renderer,
+ const gchar *name,
+ const gchar **attr_names,
+ const gchar **attr_values)
+{
+ TestTalkatuHtmlRenderer *tr = TEST_TALKATU_HTML_RENDERER(renderer);
+
+ g_string_append_printf(tr->str, "<%s", name);
+
+ if(attr_names != NULL) {
+ gint i = 0;
+
+ for(i = 0; attr_names[i] != NULL; i++) {
+ g_string_append_printf(tr->str, " %s=\"", attr_names[i]);
+ if(attr_values[i] != NULL) {
+ g_string_append_printf(tr->str, "%s", attr_values[i]);
+ }
+ g_string_append_printf(tr->str, "\"");
+ }
+ }
+
+ g_string_append_printf(tr->str, ">");
+}
+
+static void
+test_talkatu_html_renderer_element_finish(TalkatuHtmlRenderer *renderer,
+ const gchar *name)
+{
+ TestTalkatuHtmlRenderer *tr = TEST_TALKATU_HTML_RENDERER(renderer);
+
+ g_string_append_printf(tr->str, "</%s>", name);
+}
+
+static void
+test_talkatu_html_renderer_text(TalkatuHtmlRenderer *renderer,
+ const gchar *text)
+{
+ TestTalkatuHtmlRenderer *tr = TEST_TALKATU_HTML_RENDERER(renderer);
+
+ g_string_append_printf(tr->str, "%s", text);
+}
+
+static void
+test_talkatu_html_renderer_comment(TalkatuHtmlRenderer *renderer,
+ const gchar *comment)
+{
+ TestTalkatuHtmlRenderer *tr = TEST_TALKATU_HTML_RENDERER(renderer);
+
+ g_string_append_printf(tr->str, "<!--%s-->", comment);
+}
+
+G_DEFINE_TYPE(TestTalkatuHtmlRenderer, test_talkatu_html_renderer,
+ TALKATU_TYPE_HTML_RENDERER);
+
+static void
+test_talkatu_html_renderer_init(TestTalkatuHtmlRenderer *renderer) {
+ renderer->str = g_string_new("");
+}
+
+static void
+test_talkatu_html_renderer_finalize(GObject *obj) {
+ TestTalkatuHtmlRenderer *tr = TEST_TALKATU_HTML_RENDERER(obj);
+
+ g_string_free(tr->str, TRUE);
+
+ G_OBJECT_CLASS(test_talkatu_html_renderer_parent_class)->finalize(obj);
+}
+
+static void
+test_talkatu_html_renderer_class_init(TestTalkatuHtmlRendererClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ TalkatuHtmlRendererClass *renderer_class = TALKATU_HTML_RENDERER_CLASS(klass);
+
+ obj_class->finalize = test_talkatu_html_renderer_finalize;
+
+ renderer_class->element_start = test_talkatu_html_renderer_element_start;
+ renderer_class->element_finish = test_talkatu_html_renderer_element_finish;
+ renderer_class->text = test_talkatu_html_renderer_text;
+ renderer_class->comment = test_talkatu_html_renderer_comment;
+}
+
+TestTalkatuHtmlRenderer *
+test_talkatu_html_renderer_new(void) {
+ return TEST_TALKATU_HTML_RENDERER(g_object_new(TEST_TALKATU_TYPE_HTML_PARSER,
+ NULL));
+}
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_talkatu_html_renderer_simple(void) {
+ TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new();
+ const gchar *exp = "<html><head></head><body>plain text</body></html>";
+
+ talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), "plain text");
+
+ g_assert_cmpstr(renderer->str->str, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_renderer_mixed(void) {
+ TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new();
+ const gchar *exp = \
+ "<html><head></head><body>" \
+ "<i><b>emphasis <u>underline</u></b> <strike>strike</strike> italic</i>" \
+ "</body></html>";
+
+ talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), exp);
+
+ g_assert_cmpstr(renderer->str->str, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_renderer_with_comment(void) {
+ TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new();
+ const gchar *exp = \
+ "<html><head></head><body>" \
+ "Hello, <!--Darkness, my old friend, here to conquer the--> World!" \
+ "</body></html>";
+
+ talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), exp);
+
+ g_assert_cmpstr(renderer->str->str, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_renderer_with_attributes(void) {
+ TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new();
+ const gchar *exp = \
+ "<html><head></head><body>" \
+ "<font size=\"2\" color=\"#007f00\">talkatu</font>" \
+ "</body></html>";
+
+ talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), exp);
+
+ g_assert_cmpstr(renderer->str->str, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+static void
+test_talkatu_html_renderer_with_nested_attributes(void) {
+ TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new();
+ const gchar *exp = \
+ "<html><head></head><body>" \
+ "<font size=\"2\" color=\"#007f00\">" \
+ "<a href=\"https://keep.imfreedom.org/talkatu/talkatu/\">talkatu</a>" \
+ "</font>" \
+ "</body></html>";
+
+ talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), exp);
+
+ g_assert_cmpstr(renderer->str->str, ==, exp);
+
+ g_object_unref(G_OBJECT(renderer));
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+gint
+main(gint argc, gchar **argv) {
+ gint ret = 0;
+
+ g_test_init(&argc, &argv, NULL);
+
+ gtk_init(&argc, &argv);
+
+ talkatu_init();
+
+ g_test_add_func(
+ "/html-renderer/simple",
+ test_talkatu_html_renderer_simple);
+
+ g_test_add_func(
+ "/html-renderer/mixed-children",
+ test_talkatu_html_renderer_mixed);
+
+ g_test_add_func(
+ "/html-renderer/with-comment",
+ test_talkatu_html_renderer_with_comment);
+
+ g_test_add_func(
+ "/html-renderer/with-attributes",
+ test_talkatu_html_renderer_with_attributes);
+
+ g_test_add_func(
+ "/html-renderer/with-nested-attributes",
+ test_talkatu_html_renderer_with_nested_attributes);
+
+ ret = g_test_run();
+
+ talkatu_uninit();
+
+ return ret;
+}
+