1 /*
2 * Copyright © 2008 Ryan Lortie
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * See the included COPYING file for more information.
12 */
13
14 #include <string.h>
15 #include <stdio.h>
16 #include <glib.h>
17
18 /* keep track of GString instances to make sure nothing leaks */
19 static int strings_allocated;
20
21 /* === the GMarkupParser functions === */
22 static void
23 subparser_start_element (GMarkupParseContext *context,
24 const gchar *element_name,
25 const gchar **attribute_names,
26 const gchar **attribute_values,
27 gpointer user_data,
28 GError **error)
29 {
30 g_string_append_printf (user_data, "{%s}", element_name);
31
32 /* we don't like trouble... */
33 if (strcmp (element_name, "trouble") == 0)
34 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
35 "we don't like trouble");
36 }
37
38 static void
39 subparser_end_element (GMarkupParseContext *context,
40 const gchar *element_name,
41 gpointer user_data,
42 GError **error)
43 {
44 g_string_append_printf (user_data, "{/%s}", element_name);
45 }
46
47 static void
48 subparser_error (GMarkupParseContext *context,
49 GError *error,
50 gpointer user_data)
51 {
52 g_string_free (user_data, TRUE);
53 strings_allocated--;
54 }
55
56 static GMarkupParser subparser_parser =
57 {
58 subparser_start_element,
59 subparser_end_element,
60 NULL,
61 NULL,
62 subparser_error
63 };
64
65 /* convenience functions for a parser that does not
66 * replay the starting tag into the subparser...
67 */
68 static void
69 subparser_start (GMarkupParseContext *ctx)
70 {
71 gpointer user_data;
72
73 user_data = g_string_new (NULL);
74 strings_allocated++;
75 g_markup_parse_context_push (ctx, &subparser_parser, user_data);
76 }
77
78 static char *
79 subparser_end (GMarkupParseContext *ctx,
80 GError **error)
81 {
82 GString *string;
83 char *result;
84
85 string = g_markup_parse_context_pop (ctx);
86 result = g_string_free_and_steal (g_steal_pointer (&string));
87 strings_allocated--;
88
89 if (result == NULL || result[0] == '\0')
90 {
91 g_free (result);
92 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
93 "got no data");
94
95 return NULL;
96 }
97
98 return result;
99 }
100
101 /* convenience functions for a parser that -does-
102 * replay the starting tag into the subparser...
103 */
104 static gboolean
105 replay_parser_start (GMarkupParseContext *ctx,
106 const char *element_name,
107 const char **attribute_names,
108 const char **attribute_values,
109 GError **error)
110 {
111 GError *tmp_error = NULL;
112 gpointer user_data;
113
114 user_data = g_string_new (NULL);
115 strings_allocated++;
116
117 subparser_parser.start_element (ctx, element_name,
118 attribute_names, attribute_values,
119 user_data, &tmp_error);
120
121 if (tmp_error)
122 {
123 g_propagate_error (error, tmp_error);
124 g_string_free (user_data, TRUE);
125 strings_allocated--;
126
127 return FALSE;
128 }
129
130 g_markup_parse_context_push (ctx, &subparser_parser, user_data);
131
132 return TRUE;
133 }
134
135 static char *
136 replay_parser_end (GMarkupParseContext *ctx,
137 GError **error)
138 {
139 GError *tmp_error = NULL;
140 GString *string;
141 char *result;
142
143 string = g_markup_parse_context_pop (ctx);
144
145 subparser_parser.end_element (ctx, g_markup_parse_context_get_element (ctx),
146 string, &tmp_error);
147
148 if (tmp_error)
149 {
150 g_propagate_error (error, tmp_error);
151 g_string_free (string, TRUE);
152 strings_allocated--;
153
154 return NULL;
155 }
156
157 result = g_string_free_and_steal (g_steal_pointer (&string));
158 strings_allocated--;
159
160 if (result == NULL || result[0] == '\0')
161 {
162 g_free (result);
163 g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
164 "got no data");
165
166 return NULL;
167 }
168
169 return result;
170 }
171
172
173 /* === start interface between subparser and calling parser === */
174 static void subparser_start (GMarkupParseContext *ctx);
175 static char *subparser_end (GMarkupParseContext *ctx,
176 GError **error);
177 /* === end interface between subparser and calling parser === */
178
179 /* === start interface between replay parser and calling parser === */
180 static gboolean replay_parser_start (GMarkupParseContext *ctx,
181 const char *element_name,
182 const char **attribute_names,
183 const char **attribute_values,
184 GError **error);
185 static char *replay_parser_end (GMarkupParseContext *ctx,
186 GError **error);
187 /* === end interface between replay parser and calling parser === */
188
189
190
191 /* now comes our parser for the test.
192 *
193 * we recognise the tags <test> and <sub>.
194 * <test> is ignored.
195 * <sub> invokes the subparser (no replay).
196 *
197 * "unknown tags" are passed to the reply subparser
198 * (so the unknown tag is fed to the subparser...)
199 */
200 static void
201 start_element (GMarkupParseContext *context,
202 const gchar *element_name,
203 const gchar **attribute_names,
204 const gchar **attribute_values,
205 gpointer user_data,
206 GError **error)
207 {
208 g_string_append_printf (user_data, "<%s>", element_name);
209
210 if (strcmp (element_name, "test") == 0)
211 {
212 /* do nothing */
213 }
214 else if (strcmp (element_name, "sub") == 0)
215 {
216 /* invoke subparser */
217 subparser_start (context);
218 }
219 else
220 {
221 /* unknown tag. invoke replay subparser */
222 if (!replay_parser_start (context, element_name,
223 attribute_names, attribute_values,
224 error))
225 return;
226 }
227 }
228
229 static void
230 end_element (GMarkupParseContext *context,
231 const gchar *element_name,
232 gpointer user_data,
233 GError **error)
234 {
235 if (strcmp (element_name, "test") == 0)
236 {
237 /* do nothing */
238 }
239 else if (strcmp (element_name, "sub") == 0)
240 {
241 char *result;
242
243 if ((result = subparser_end (context, error)) == NULL)
244 return;
245
246 g_string_append_printf (user_data, "<<%s>>", result);
247 g_free (result);
248 }
249 else
250 {
251 char *result;
252
253 if ((result = replay_parser_end (context, error)) == NULL)
254 return;
255
256 g_string_append_printf (user_data, "[[%s]]", result);
257 g_free (result);
258 }
259
260 g_string_append_printf (user_data, "</%s>", element_name);
261 }
262
263 static GMarkupParser parser =
264 {
265 start_element,
266 end_element,
267 NULL,
268 NULL,
269 NULL
270 };
271
272 typedef struct
273 {
274 const char *markup;
275 const char *result;
276 const char *error_message;
277 } TestCase;
278
279 static void
280 test (gconstpointer user_data)
281 {
282 const TestCase *tc = user_data;
283 GMarkupParseContext *ctx;
284 GString *string;
285 gboolean result;
286 GError *error;
287
288 error = NULL;
289 string = g_string_new (NULL);
290 ctx = g_markup_parse_context_new (&parser, G_MARKUP_DEFAULT_FLAGS, string, NULL);
291 result = g_markup_parse_context_parse (ctx, tc->markup,
292 strlen (tc->markup), &error);
293 if (result)
294 result = g_markup_parse_context_end_parse (ctx, &error);
295 g_markup_parse_context_free (ctx);
296 g_assert (strings_allocated == 0);
297
298 if (result)
299 {
300 if (tc->error_message)
301 g_error ("expected failure (about '%s') passed!\n"
302 " in: %s\n out: %s",
303 tc->error_message, tc->markup, string->str);
304 }
305 else
306 {
307 if (!tc->error_message)
308 g_error ("unexpected failure: '%s'\n"
309 " in: %s\n out: %s",
310 error->message, tc->markup, string->str);
311
312 if (!strstr (error->message, tc->error_message))
313 g_error ("failed for the wrong reason.\n"
314 " expecting message about '%s'\n"
315 " got message '%s'\n"
316 " in: %s\n out: %s",
317 tc->error_message, error->message, tc->markup, string->str);
318 }
319
320 if (strcmp (string->str, tc->result) != 0)
321 g_error ("got the wrong result.\n"
322 " expected: '%s'\n"
323 " got: '%s'\n"
324 " input: %s",
325 tc->result, string->str, tc->markup);
326
327 if (error)
328 g_error_free (error);
329
330 g_string_free (string, TRUE);
331 }
332
333 TestCase test_cases[] = /* successful runs */
334 {
335 /* in */ /* out */ /* error */
336 { "<test/>", "<test></test>", NULL },
337 { "<sub><foo/></sub>", "<sub><<{foo}{/foo}>></sub>", NULL },
338 { "<sub><foo/><bar/></sub>", "<sub><<{foo}{/foo}{bar}{/bar}>></sub>", NULL },
339 { "<foo><bar/></foo>", "<foo>[[{foo}{bar}{/bar}{/foo}]]</foo>", NULL },
340 { "<foo><x/><y/></foo>", "<foo>[[{foo}{x}{/x}{y}{/y}{/foo}]]</foo>", NULL },
341 { "<foo/>", "<foo>[[{foo}{/foo}]]</foo>", NULL },
342 { "<sub><foo/></sub><bar/>", "<sub><<{foo}{/foo}>></sub>"
343 "<bar>[[{bar}{/bar}]]</bar>", NULL }
344 };
345
346 TestCase error_cases[] = /* error cases */
347 {
348 /* in */ /* out */ /* error */
349 { "<foo><>", "<foo>", ">"},
350 { "", "", "empty" },
351 { "<trouble/>", "<trouble>", "trouble" },
352 { "<sub><trouble>", "<sub>", "trouble" },
353 { "<foo><trouble>", "<foo>", "trouble" },
354 { "<sub></sub>", "<sub>", "no data" },
355 { "<sub/>", "<sub>", "no data" }
356 };
357
358 #define add_tests(func, basename, array) \
359 G_STMT_START { \
360 gsize __add_tests_i; \
361 \
362 for (__add_tests_i = 0; \
363 __add_tests_i < G_N_ELEMENTS (array); \
364 __add_tests_i++) \
365 { \
366 char *testname; \
367 \
368 testname = g_strdup_printf ("%s/%" G_GSIZE_FORMAT, \
369 basename, __add_tests_i); \
370 g_test_add_data_func (testname, &array[__add_tests_i], func); \
371 g_free (testname); \
372 } \
373 } G_STMT_END
374
375 int
376 main (int argc, char **argv)
377 {
378 g_setenv ("LC_ALL", "C", TRUE);
379 g_test_init (&argc, &argv, NULL);
380 add_tests (test, "/glib/markup/subparser/success", test_cases);
381 add_tests (test, "/glib/markup/subparser/failure", error_cases);
382 return g_test_run ();
383 }