talkatu/talkatu
Clone
Summary
Browse
Changes
Graph
Update the minimum meson to 0.56.0
2021-08-03, Gary Kramlich
13ee9f88fa95
Update the minimum meson to 0.56.0
Testing Done:
ran `meson --wipe` and `ninja` and verified there were no warnings.
Reviewed at https://reviews.imfreedom.org/r/867/
/*
* 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
);
}
}