1 /* Copyright (C) 2016-2019 Shengyu Zhang <i@silverrainz.me>
2  *
3  * This file is part of Srain.
4  *
5  * Srain is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <glib.h>
20 
21 #include "srain.h"
22 #include "ret.h"
23 
24 #include "markup_renderer.h"
25 
26 struct _SrnMarkupRenderer {
27     GMarkupParser parser;
28     GString *str;
29     void *user_data;
30     bool is_parsing;
31 };
32 
33 #define ROOT_TAG "root"
34 
35 static void start_element(GMarkupParseContext *context, const gchar *element_name,
36         const gchar **attribute_names, const gchar **attribute_values,
37         gpointer user_data, GError **error);
38 static void end_element(GMarkupParseContext *context, const gchar *element_name,
39         gpointer user_data, GError **error);
40 static void text(GMarkupParseContext *context, const gchar *text, gsize text_len,
41         gpointer user_data, GError **error);
42 static void passthrough(GMarkupParseContext *context,
43         const gchar *passthrough_text, gsize text_len, gpointer user_data,
44         GError **error);
45 static void error(GMarkupParseContext *context, GError *error,
46 gpointer user_data);
47 
srn_markup_renderer_new(void)48 SrnMarkupRenderer *srn_markup_renderer_new(void) {
49     SrnMarkupRenderer *self;
50 
51     self = g_malloc0(sizeof(SrnMarkupRenderer));
52     self->parser.start_element = start_element;
53     self->parser.end_element = end_element;
54     self->parser.text = text;
55     self->parser.passthrough = passthrough;
56     self->parser.error = error;
57     self->is_parsing = FALSE;
58 
59     return self;
60 }
61 
srn_markup_renderer_free(SrnMarkupRenderer * self)62 void srn_markup_renderer_free(SrnMarkupRenderer *self) {
63     g_warn_if_fail(!self->is_parsing);
64     g_warn_if_fail(!self->str);
65     g_warn_if_fail(!self->user_data);
66     g_free(self);
67 }
68 
srn_markup_renderer_render(SrnMarkupRenderer * self,const char * markup_in,char ** markup_out,void * user_data)69 SrnRet srn_markup_renderer_render(SrnMarkupRenderer *self,
70         const char *markup_in, char **markup_out, void *user_data) {
71     GError *err;
72     GMarkupParseContext *parse_ctx;
73     SrnRet ret;
74 
75     g_return_val_if_fail(!self->is_parsing, SRN_ERR);
76 
77     self->str = g_string_new(NULL);
78     self->user_data = user_data;
79     self->is_parsing = TRUE;
80 
81     ret = SRN_OK;
82     err = NULL;
83     parse_ctx = g_markup_parse_context_new(&self->parser, 0, self, NULL);
84     g_markup_parse_context_parse(parse_ctx, "<" ROOT_TAG ">", -1, NULL);
85     g_markup_parse_context_parse(parse_ctx, markup_in, -1, &err);
86     g_markup_parse_context_parse(parse_ctx, "</" ROOT_TAG ">", -1, NULL);
87     g_markup_parse_context_end_parse(parse_ctx, NULL);
88     g_markup_parse_context_free(parse_ctx);
89 
90     if (err){
91         ret = RET_ERR("Markup parse error: %s", err->message);
92         g_error_free(err);
93     }
94 
95     if (markup_out) {
96         *markup_out = self->str->str;
97         g_string_free(self->str, FALSE);
98     } else {
99         g_string_free(self->str, TRUE);
100     }
101 
102     self->str = NULL;
103     self->user_data = NULL;
104     self->is_parsing = FALSE;
105 
106     return ret;
107 }
108 
srn_markup_renderer_get_markup_parser(SrnMarkupRenderer * self)109 GMarkupParser* srn_markup_renderer_get_markup_parser(SrnMarkupRenderer *self) {
110     return &self->parser;
111 }
112 
srn_markup_renderer_get_markup(SrnMarkupRenderer * self)113 GString* srn_markup_renderer_get_markup(SrnMarkupRenderer *self) {
114     g_warn_if_fail(self->is_parsing);
115     return self->str;
116 }
117 
srn_markup_renderer_get_user_data(SrnMarkupRenderer * self)118 void* srn_markup_renderer_get_user_data(SrnMarkupRenderer *self) {
119     g_warn_if_fail(self->is_parsing);
120     return self->user_data;
121 }
122 
123 /* GLib Markup parser callbacks */
124 
start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)125 static void start_element(GMarkupParseContext *context, const gchar *element_name,
126         const gchar **attribute_names, const gchar **attribute_values,
127         gpointer user_data, GError **error){
128     SrnMarkupRenderer *self;
129 
130     self = user_data;
131 
132     // Ignore root tag
133     if (g_strcmp0(element_name, ROOT_TAG) == 0){
134         return;
135     }
136 
137     self->str = g_string_append_c(self->str, '<');
138     self->str = g_string_append(self->str, element_name);
139     while (*attribute_names != NULL){
140         char *escaped_value = g_markup_escape_text(*attribute_values, -1);
141         g_string_append_printf(self->str, " %s=\"%s\"",
142                 *attribute_names, escaped_value);
143         g_free(escaped_value);
144         attribute_names++;
145         attribute_values++;
146     }
147     self->str = g_string_append_c(self->str, '>');
148 }
149 
end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)150 static void end_element(GMarkupParseContext *context, const gchar *element_name,
151         gpointer user_data, GError **error){
152     SrnMarkupRenderer *self;
153 
154     self = user_data;
155 
156     if (g_strcmp0(element_name, ROOT_TAG) == 0){
157         return;
158     }
159 
160     g_string_append_printf(self->str, "</%s>", element_name);
161 }
162 
163 /* NOTE: text is not nul-terminated */
text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)164 static void text(GMarkupParseContext *context, const gchar *text, gsize text_len,
165         gpointer user_data, GError **error){
166     char *markup;
167     SrnMarkupRenderer *self;
168 
169     self = user_data;
170 
171     markup = g_markup_escape_text(text, text_len);
172     self->str = g_string_append(self->str, markup);
173     g_free(markup);
174 }
175 
176 /* Called for strings that should be re-saved verbatim in this same
177  * position, but are not otherwise interpretable.  At the moment
178  * this includes comments and processing instructions.
179  */
passthrough(GMarkupParseContext * context,const gchar * passthrough_text,gsize text_len,gpointer user_data,GError ** error)180 static void passthrough(GMarkupParseContext *context,
181         const gchar *passthrough_text, gsize text_len, gpointer user_data,
182         GError **error){
183     /* Ignore comments for now */
184 }
185 
error(GMarkupParseContext * context,GError * error,gpointer user_data)186 static void error(GMarkupParseContext *context, GError *error,
187         gpointer user_data){
188     /* No need to deal with error here */
189 }
190