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);
- 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/talkatuhtmlbuffer.c
+talkatu/talkatuhtmlrenderer.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 @@
+ 'talkatuhtmlpangorenderer.h', + 'talkatuhtmlrenderer.h', 'talkatumarkdownbuffer.h',
@@ -46,7 +47,8 @@
+ 'talkatuhtmlpangorenderer.c', + 'talkatuhtmlrenderer.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 <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)); 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 **attribute_names, + const gchar **attribute_values); +typedef void (*TalkatuHtmlPangoRendererFinishFunc)(TalkatuHtmlPangoRenderer *renderer, + TalkatuHtmlPangoRendererStartFunc start; + TalkatuHtmlPangoRendererFinishFunc finish; +} TalkatuHtmlPangoRendererElementFuncs; +struct _TalkatuHtmlPangoRenderer { + TalkatuHtmlRenderer parent; +G_DEFINE_TYPE(TalkatuHtmlPangoRenderer, talkatu_html_pango_renderer, + TALKATU_TYPE_HTML_RENDERER) +/****************************************************************************** + * TalkatuHtmlPangoRenderer Element Functions + *****************************************************************************/ +talkatu_html_pango_renderer_passthrough_start(TalkatuHtmlPangoRenderer *renderer, + const gchar **attribute_names, + const gchar **attribute_values) + g_string_append_printf(renderer->str, "<%s>", name); +talkatu_html_pango_renderer_passthrough_finish(TalkatuHtmlPangoRenderer *renderer, + g_string_append_printf(renderer->str, "</%s>", name); +talkatu_html_pango_renderer_code_start(TalkatuHtmlPangoRenderer *renderer, + const gchar **attribute_names, + const gchar **attribute_values) + g_string_append(renderer->str, "<span font=\"monospace\">"); +talkatu_html_pango_renderer_link_start(TalkatuHtmlPangoRenderer *renderer, + const gchar **attribute_names, + const gchar **attribute_values) + 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, ">"); +talkatu_html_pango_renderer_font_start(TalkatuHtmlPangoRenderer *renderer, + const gchar **attribute_names, + const gchar **attribute_values) + 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, + TalkatuHtmlPangoRendererElementFuncs *funcs = NULL; + funcs = g_new(TalkatuHtmlPangoRendererElementFuncs, 1); + funcs->finish = finish; +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, +/****************************************************************************** + * TalkatuHtmlRenderer Implementation + *****************************************************************************/ +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(""); +talkatu_html_pango_renderer_element_start(TalkatuHtmlRenderer *renderer, + 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); +talkatu_html_pango_renderer_element_finish(TalkatuHtmlRenderer *renderer, + 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); +talkatu_html_pango_renderer_text(TalkatuHtmlRenderer *renderer, + TalkatuHtmlPangoRenderer *pr = TALKATU_HTML_PANGO_RENDERER(renderer); + g_string_append(pr->str, text); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +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, + funcs = talkatu_html_pango_renderer_element_funcs_new( + talkatu_html_pango_renderer_link_start, + talkatu_html_pango_renderer_passthrough_finish, + 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, + 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, + 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); +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); +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; +/****************************************************************************** + *****************************************************************************/ +talkatu_html_pango_renderer_new(void) { + return TALKATU_HTML_RENDERER(g_object_new(TALKATU_TYPE_HTML_PANGO_RENDERER, +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" +#ifndef TALKATU_HTML_PANGO_RENDERER_H +#define TALKATU_HTML_PANGO_RENDERER_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) +TalkatuHtmlRenderer *talkatu_html_pango_renderer_new(void); +const gchar *talkatu_html_pango_renderer_get_string(TalkatuHtmlPangoRenderer *renderer); +#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 "talkatuhtmlparser.h"
- * SECTION:talkatuhtmlparser
- * @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
- * @element_finish: The method to call when all children of an element have been
- * @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
- * 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)
-talkatu_html_parser_find_next_sibling(GumboNode *node) {
- GumboNode *next = NULL;
- if(node->parent == 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];
-/******************************************************************************
- * Helper Implementations
- *****************************************************************************/
-talkatu_html_parser_element_start(TalkatuHtmlParser *parser,
- TalkatuHtmlParserClass *klass = TALKATU_HTML_PARSER_GET_CLASS(parser);
- if(klass->element_start) {
- const gchar **names = NULL, **values = NULL;
- guint length = element->attributes.length;
- 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];
- values[i] = attr->value;
- /* add our terminating null values to the end */
- klass->element_start(parser, name, names, values, data);
-talkatu_html_parser_element_finish(TalkatuHtmlParser *parser,
- TalkatuHtmlParserClass *klass = TALKATU_HTML_PARSER_GET_CLASS(parser);
- if(klass->element_finish) {
- klass->element_finish(parser, name, data);
-talkatu_html_parser_text(TalkatuHtmlParser *parser, const gchar *text,
- TalkatuHtmlParserClass *klass = TALKATU_HTML_PARSER_GET_CLASS(parser);
- klass->text(parser, text, data);
-talkatu_html_parser_comment(TalkatuHtmlParser *parser, const gchar *comment,
- TalkatuHtmlParserClass *klass = TALKATU_HTML_PARSER_GET_CLASS(parser);
- klass->comment(parser, comment, data);
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_ABSTRACT_TYPE(TalkatuHtmlParser, talkatu_html_parser, G_TYPE_OBJECT)
-talkatu_html_parser_init(TalkatuHtmlParser *parser) {
-talkatu_html_parser_class_init(TalkatuHtmlParserClass *klass) {
-/******************************************************************************
- *****************************************************************************/
- * 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
-talkatu_html_parser_parse(TalkatuHtmlParser *parser, const gchar *html,
- 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
- * 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.
- GumboNode *node = (GumboNode *)stack->data;
- GumboNode *next = NULL;
- const gchar *tagname = NULL;
- 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.
- 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);
- /* We have no children so we just call the finish method. */
- talkatu_html_parser_element_finish(parser, tagname, data);
- case GUMBO_NODE_WHITESPACE:
- talkatu_html_parser_text(parser, node->v.text.text, data);
- case GUMBO_NODE_COMMENT:
- talkatu_html_parser_comment(parser, node->v.text.text, data);
- /* 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.
- 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
- 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.
- node = (GumboNode *)stack->data;
- next = talkatu_html_parser_find_next_sibling(node);
- /* 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);
- 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 = 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"
-#include <glib-object.h>
-#define TALKATU_TYPE_HTML_PARSER (talkatu_html_parser_get_type())
-G_DECLARE_DERIVABLE_TYPE(TalkatuHtmlParser, talkatu_html_parser, TALKATU,
-struct _TalkatuHtmlParserClass {
- 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);
-void talkatu_html_parser_parse(TalkatuHtmlParser *parser, const gchar *html, gpointer data);
-#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 "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 + * @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 + * @element_finish: The method to call when all children of an element have been + * @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 + * 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) +talkatu_html_renderer_find_next_sibling(GumboNode *node) { + GumboNode *next = NULL; + if(node->parent == 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]; +/****************************************************************************** + * Helper Implementations + *****************************************************************************/ +talkatu_html_renderer_element_start(TalkatuHtmlRenderer *renderer, + TalkatuHtmlRendererClass *klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer); + if(klass->element_start) { + const gchar **names = NULL, **values = NULL; + guint length = element->attributes.length; + 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]; + values[i] = attr->value; + /* add our terminating null values to the end */ + klass->element_start(renderer, name, names, values); +talkatu_html_renderer_element_finish(TalkatuHtmlRenderer *renderer, + TalkatuHtmlRendererClass *klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer); + if(klass->element_finish) { + klass->element_finish(renderer, name); +talkatu_html_renderer_text(TalkatuHtmlRenderer *renderer, const gchar *text) { + TalkatuHtmlRendererClass *klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer); + klass->text(renderer, text); +talkatu_html_renderer_comment(TalkatuHtmlRenderer *renderer, + TalkatuHtmlRendererClass *klass = TALKATU_HTML_RENDERER_GET_CLASS(renderer); + klass->comment(renderer, comment); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_ABSTRACT_TYPE(TalkatuHtmlRenderer, talkatu_html_renderer, +talkatu_html_renderer_init(TalkatuHtmlRenderer *renderer) { +talkatu_html_renderer_class_init(TalkatuHtmlRendererClass *klass) { +/****************************************************************************** + *****************************************************************************/ + * talkatu_html_renderer_render: + * @renderer: The #TalkatuHtmlRenderer instance. + * @html: The HTML text to render. + * Renders the given @html calling the #TalkatuHtmlRendererClass functions as +talkatu_html_renderer_render(TalkatuHtmlRenderer *renderer, const gchar *html) { + 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 + * 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. + GumboNode *node = (GumboNode *)stack->data; + GumboNode *next = NULL; + const gchar *tagname = NULL; + 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. + case GUMBO_NODE_ELEMENT: + case GUMBO_NODE_TEMPLATE: + tagname = gumbo_normalized_tagname(node->v.element.tag); + talkatu_html_renderer_element_start(renderer, tagname, + 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); + /* We have no children so we just call the finish method. */ + talkatu_html_renderer_element_finish(renderer, tagname); + case GUMBO_NODE_WHITESPACE: + talkatu_html_renderer_text(renderer, node->v.text.text); + case GUMBO_NODE_COMMENT: + talkatu_html_renderer_comment(renderer, node->v.text.text); + /* 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. + 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 + 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. + node = (GumboNode *)stack->data; + next = talkatu_html_renderer_find_next_sibling(node); + /* 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); + 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 = 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. +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" +#ifndef TALKATU_HTML_RENDERER_H +#define TALKATU_HTML_RENDERER_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 { + 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); +void talkatu_html_renderer_render(TalkatuHtmlRenderer *renderer, const gchar *html); +void talkatu_html_renderer_reset(TalkatuHtmlRenderer *renderer); +#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)
- '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) + 'talkatutesthtmlrenderer.c', + dependencies : [talkatu_dep, GLIB] +test('html-renderer', TEST_WRAPPER, args : e, is_parallel : false) --- /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 @@
+ * 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/>. +/****************************************************************************** + *****************************************************************************/ +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)); +test_talkatu_html_pango_renderer_mixed(void) { + TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new(); + "<html><head></head><body>" \ + "<i><b>emphasis <u>underline</u></b> <strike>strike</strike> italic</i>" \ + "<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)); +test_talkatu_html_pango_renderer_with_comment(void) { + TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new(); + "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)); +test_talkatu_html_pango_renderer_with_attributes(void) { + TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new(); + "<html><head></head><body>" \ + "<font size=\"2\" color=\"#007f00\">talkatu</font>" \ + 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)); +test_talkatu_html_pango_renderer_with_nested_attributes(void) { + TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new(); + "<html><head></head><body>" \ + "<font size=\"2\" color=\"#007f00\">" \ + "<a href=\"https://keep.imfreedom.org/talkatu/talkatu/\">talkatu</a>" \ + "<span font=\"2\" foreground=\"#007f00\">" \ + "<a href=\"https://keep.imfreedom.org/talkatu/talkatu/\">talkatu</a>" \ + 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)); +test_talkatu_html_pango_renderer_code(void) { + TalkatuHtmlRenderer *renderer = talkatu_html_pango_renderer_new(); + "<html><head></head><body>" \ + "<code>monkey</code>" \ + "<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 argc, gchar **argv) { + g_test_init(&argc, &argv, NULL); + gtk_init(&argc, &argv); + "/html-pango-renderer/simple", + test_talkatu_html_pango_renderer_simple); + "/html-pango-renderer/mixed-children", + test_talkatu_html_pango_renderer_mixed); + "/html-pango-renderer/with-comment", + test_talkatu_html_pango_renderer_with_comment); + "/html-pango-renderer/with-attributes", + test_talkatu_html_pango_renderer_with_attributes); + "/html-pango-renderer/with-nested-attributes", + test_talkatu_html_pango_renderer_with_nested_attributes); + "/html-pango-renderer/code", + test_talkatu_html_pango_renderer_code); --- 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 @@
- * 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/>.
-/******************************************************************************
- * 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;
-test_talkatu_html_parser_element_start(TalkatuHtmlParser *parser,
- const gchar **attr_names,
- const gchar **attr_values,
- GString *str = (GString *)data;
- g_string_append_printf(str, "<%s", name);
- if(attr_names != NULL) {
- 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, ">");
-test_talkatu_html_parser_element_finish(TalkatuHtmlParser *parser,
- g_string_append_printf((GString *)data, "</%s>", name);
-test_talkatu_html_parser_text(TalkatuHtmlParser *parser, const gchar *text,
- g_string_append_printf((GString *)data, "%s", text);
-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);
-test_talkatu_html_parser_init(TestTalkatuHtmlParser *parser) {
-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;
-test_talkatu_html_parser_new(void) {
- return TALKATU_HTML_PARSER(g_object_new(TEST_TALKATU_TYPE_HTML_PARSER,
-/******************************************************************************
- *****************************************************************************/
-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));
-test_talkatu_html_parser_mixed(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- "<html><head></head><body>" \
- "<i><b>emphasis <u>underline</u></b> <strike>strike</strike> italic</i>" \
- talkatu_html_parser_parse(parser, exp, str);
- g_assert_cmpstr(str->str, ==, exp);
- g_string_free(str, TRUE);
- g_object_unref(G_OBJECT(parser));
-test_talkatu_html_parser_with_comment(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- "<html><head></head><body>" \
- "Hello, <!--Darkness, my old friend, here to conquer the--> World!" \
- talkatu_html_parser_parse(parser, exp, str);
- g_assert_cmpstr(str->str, ==, exp);
- g_string_free(str, TRUE);
- g_object_unref(G_OBJECT(parser));
-test_talkatu_html_parser_with_attributes(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- "<html><head></head><body>" \
- "<font size=\"2\" color=\"#007f00\">talkatu</font>" \
- talkatu_html_parser_parse(parser, exp, str);
- g_assert_cmpstr(str->str, ==, exp);
- g_string_free(str, TRUE);
- g_object_unref(G_OBJECT(parser));
-test_talkatu_html_parser_with_nested_attributes(void) {
- TalkatuHtmlParser *parser = test_talkatu_html_parser_new();
- GString *str = g_string_new("");
- "<html><head></head><body>" \
- "<font size=\"2\" color=\"#007f00\">" \
- "<a href=\"https://keep.imfreedom.org/talkatu/talkatu/\">talkatu</a>" \
- 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 argc, gchar **argv) {
- g_test_init(&argc, &argv, NULL);
- gtk_init(&argc, &argv);
- test_talkatu_html_parser_simple);
- "/html-parser/mixed-children",
- test_talkatu_html_parser_mixed);
- "/html-parser/with-comment",
- test_talkatu_html_parser_with_comment);
- "/html-parser/with-attributes",
- test_talkatu_html_parser_with_attributes);
- "/html-parser/with-nested-attributes",
- test_talkatu_html_parser_with_nested_attributes);
--- /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 @@
+ * 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/>. +/****************************************************************************** + * 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; +test_talkatu_html_renderer_element_start(TalkatuHtmlRenderer *renderer, + 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) { + 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, ">"); +test_talkatu_html_renderer_element_finish(TalkatuHtmlRenderer *renderer, + TestTalkatuHtmlRenderer *tr = TEST_TALKATU_HTML_RENDERER(renderer); + g_string_append_printf(tr->str, "</%s>", name); +test_talkatu_html_renderer_text(TalkatuHtmlRenderer *renderer, + TestTalkatuHtmlRenderer *tr = TEST_TALKATU_HTML_RENDERER(renderer); + g_string_append_printf(tr->str, "%s", text); +test_talkatu_html_renderer_comment(TalkatuHtmlRenderer *renderer, + 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); +test_talkatu_html_renderer_init(TestTalkatuHtmlRenderer *renderer) { + renderer->str = g_string_new(""); +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); +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, +/****************************************************************************** + *****************************************************************************/ +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)); +test_talkatu_html_renderer_mixed(void) { + TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new(); + "<html><head></head><body>" \ + "<i><b>emphasis <u>underline</u></b> <strike>strike</strike> italic</i>" \ + talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), exp); + g_assert_cmpstr(renderer->str->str, ==, exp); + g_object_unref(G_OBJECT(renderer)); +test_talkatu_html_renderer_with_comment(void) { + TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new(); + "<html><head></head><body>" \ + "Hello, <!--Darkness, my old friend, here to conquer the--> World!" \ + talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), exp); + g_assert_cmpstr(renderer->str->str, ==, exp); + g_object_unref(G_OBJECT(renderer)); +test_talkatu_html_renderer_with_attributes(void) { + TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new(); + "<html><head></head><body>" \ + "<font size=\"2\" color=\"#007f00\">talkatu</font>" \ + talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), exp); + g_assert_cmpstr(renderer->str->str, ==, exp); + g_object_unref(G_OBJECT(renderer)); +test_talkatu_html_renderer_with_nested_attributes(void) { + TestTalkatuHtmlRenderer *renderer = test_talkatu_html_renderer_new(); + "<html><head></head><body>" \ + "<font size=\"2\" color=\"#007f00\">" \ + "<a href=\"https://keep.imfreedom.org/talkatu/talkatu/\">talkatu</a>" \ + talkatu_html_renderer_render(TALKATU_HTML_RENDERER(renderer), exp); + g_assert_cmpstr(renderer->str->str, ==, exp); + g_object_unref(G_OBJECT(renderer)); +/****************************************************************************** + *****************************************************************************/ +main(gint argc, gchar **argv) { + g_test_init(&argc, &argv, NULL); + gtk_init(&argc, &argv); + "/html-renderer/simple", + test_talkatu_html_renderer_simple); + "/html-renderer/mixed-children", + test_talkatu_html_renderer_mixed); + "/html-renderer/with-comment", + test_talkatu_html_renderer_with_comment); + "/html-renderer/with-attributes", + test_talkatu_html_renderer_with_attributes); + "/html-renderer/with-nested-attributes", + test_talkatu_html_renderer_with_nested_attributes);