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