1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3 * GData Client
4 * Copyright (C) Philip Withnall 2009 <philip@tecnocode.co.uk>
5 *
6 * GData Client 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 * GData Client is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <config.h>
21 #include <glib.h>
22 #include <glib/gi18n-lib.h>
23 #include <sys/time.h>
24 #include <time.h>
25 #include <string.h>
26 #include <libxml/parser.h>
27 #include <json-glib/json-glib.h>
28
29 #include "gdata-parser.h"
30 #include "gdata-service.h"
31 #include "gdata-types.h"
32 #include "gdata-private.h"
33
34 static gchar *
print_element(xmlNode * node)35 print_element (xmlNode *node)
36 {
37 gboolean node_has_ns = (node->ns == NULL || node->ns->prefix == NULL ||
38 xmlStrcmp (node->ns->href, (xmlChar*) "http://www.w3.org/2005/Atom") == 0) ? FALSE : TRUE;
39
40 if (node->parent == NULL) {
41 /* No parent node */
42 if (node_has_ns == TRUE)
43 return g_strdup_printf ("<%s:%s>", node->ns->prefix, node->name);
44 else
45 return g_strdup_printf ("<%s>", node->name);
46 } else {
47 /* We have a parent node, which makes things a lot more complex */
48 gboolean parent_has_ns = (node->parent->type == XML_DOCUMENT_NODE || node->parent->ns == NULL || node->parent->ns->prefix == NULL ||
49 xmlStrcmp (node->parent->ns->href, (xmlChar*) "http://www.w3.org/2005/Atom") == 0) ? FALSE : TRUE;
50
51 if (parent_has_ns == TRUE && node_has_ns == TRUE)
52 return g_strdup_printf ("<%s:%s/%s:%s>", node->parent->ns->prefix, node->parent->name, node->ns->prefix, node->name);
53 else if (parent_has_ns == FALSE && node_has_ns == TRUE)
54 return g_strdup_printf ("<%s/%s:%s>", node->parent->name, node->ns->prefix, node->name);
55 else
56 return g_strdup_printf ("<%s/%s>", node->parent->name, node->name);
57 }
58 }
59
60 gboolean
gdata_parser_error_required_content_missing(xmlNode * element,GError ** error)61 gdata_parser_error_required_content_missing (xmlNode *element, GError **error)
62 {
63 gchar *element_string = print_element (element);
64
65 /* Translators: the parameter is the name of an XML element, including the angle brackets ("<" and ">").
66 *
67 * For example:
68 * A <title> element was missing required content. */
69 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A %s element was missing required content."), element_string);
70 g_free (element_string);
71
72 return FALSE;
73 }
74
75 gboolean
gdata_parser_error_not_iso8601_format(xmlNode * element,const gchar * actual_value,GError ** error)76 gdata_parser_error_not_iso8601_format (xmlNode *element, const gchar *actual_value, GError **error)
77 {
78 gchar *element_string = print_element (element);
79 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
80 /* Translators: the first parameter is the name of an XML element (including the angle brackets ("<" and ">")),
81 * and the second parameter is the erroneous value (which was not in ISO 8601 format).
82 *
83 * For example:
84 * The content of a <media:group/media:uploaded> element (‘2009-05-06 26:30Z’) was not in ISO 8601 format. */
85 _("The content of a %s element (‘%s’) was not in ISO 8601 format."), element_string, actual_value);
86 g_free (element_string);
87
88 return FALSE;
89 }
90
91 gboolean
gdata_parser_error_unknown_property_value(xmlNode * element,const gchar * property_name,const gchar * actual_value,GError ** error)92 gdata_parser_error_unknown_property_value (xmlNode *element, const gchar *property_name, const gchar *actual_value, GError **error)
93 {
94 gchar *property_string, *element_string;
95
96 property_string = g_strdup_printf ("@%s", property_name);
97 element_string = print_element (element);
98
99 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
100 /* Translators: the first parameter is the name of an XML property, the second is the name of an XML element
101 * (including the angle brackets ("<" and ">")) to which the property belongs, and the third is the unknown value.
102 *
103 * For example:
104 * The value of the @time property of a <media:group/media:thumbnail> element (‘00:01:42.500’) was unknown. */
105 _("The value of the %s property of a %s element (‘%s’) was unknown."), property_string, element_string, actual_value);
106 g_free (property_string);
107 g_free (element_string);
108
109 return FALSE;
110 }
111
112 gboolean
gdata_parser_error_unknown_content(xmlNode * element,const gchar * actual_content,GError ** error)113 gdata_parser_error_unknown_content (xmlNode *element, const gchar *actual_content, GError **error)
114 {
115 gchar *element_string = print_element (element);
116
117 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
118 /* Translators: the first parameter is the name of an XML element (including the angle brackets ("<" and ">")),
119 * and the second parameter is the unknown content of that element.
120 *
121 * For example:
122 * The content of a <gphoto:access> element (‘protected’) was unknown. */
123 _("The content of a %s element (‘%s’) was unknown."), element_string, actual_content);
124 g_free (element_string);
125
126 return FALSE;
127 }
128
129 gboolean
gdata_parser_error_required_property_missing(xmlNode * element,const gchar * property_name,GError ** error)130 gdata_parser_error_required_property_missing (xmlNode *element, const gchar *property_name, GError **error)
131 {
132 gchar *property_string, *element_string;
133
134 property_string = g_strdup_printf ("@%s", property_name);
135 element_string = print_element (element);
136
137 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
138 /* Translators: the first parameter is the name of an XML element (including the angle brackets ("<" and ">")),
139 * and the second is the name of an XML property which it should have contained.
140 *
141 * For example:
142 * A required property of a <entry/gAcl:role> element (@value) was not present. */
143 _("A required property of a %s element (%s) was not present."), element_string, property_string);
144 g_free (property_string);
145 g_free (element_string);
146
147 return FALSE;
148 }
149
150 gboolean
gdata_parser_error_mutexed_properties(xmlNode * element,const gchar * property1_name,const gchar * property2_name,GError ** error)151 gdata_parser_error_mutexed_properties (xmlNode *element, const gchar *property1_name, const gchar *property2_name, GError **error)
152 {
153 gchar *property1_string, *property2_string, *element_string;
154
155 property1_string = g_strdup_printf ("@%s", property1_name);
156 property2_string = g_strdup_printf ("@%s", property2_name);
157 element_string = print_element (element);
158
159 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
160 /* Translators: the first two parameters are the names of XML properties of an XML element given in the third
161 * parameter (including the angle brackets ("<" and ">")).
162 *
163 * For example:
164 * Values were present for properties @rel and @label of a <entry/gContact:relation> element when only one of the
165 * two is allowed. */
166 _("Values were present for properties %s and %s of a %s element when only one of the two is allowed."),
167 property1_string, property2_string, element_string);
168 g_free (property1_string);
169 g_free (property2_string);
170 g_free (element_string);
171
172 return FALSE;
173 }
174
175 gboolean
gdata_parser_error_required_element_missing(const gchar * element_name,const gchar * parent_element_name,GError ** error)176 gdata_parser_error_required_element_missing (const gchar *element_name, const gchar *parent_element_name, GError **error)
177 {
178 /* NOTE: This can't take an xmlNode, since such a node wouldn't exist. */
179 gchar *element_string = g_strdup_printf ("<%s/%s>", parent_element_name, element_name);
180
181 /* Translators: the parameter is the name of an XML element, including the angle brackets ("<" and ">").
182 *
183 * For example:
184 * A required element (<entry/title>) was not present. */
185 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A required element (%s) was not present."), element_string);
186 g_free (element_string);
187
188 return FALSE;
189 }
190
191 gboolean
gdata_parser_error_duplicate_element(xmlNode * element,GError ** error)192 gdata_parser_error_duplicate_element (xmlNode *element, GError **error)
193 {
194 gchar *element_string = print_element (element);
195
196 /* Translators: the parameter is the name of an XML element, including the angle brackets ("<" and ">").
197 *
198 * For example:
199 * A singleton element (<feed/title>) was duplicated. */
200 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A singleton element (%s) was duplicated."), element_string);
201 g_free (element_string);
202
203 return FALSE;
204 }
205
206 gboolean
gdata_parser_int64_from_date(const gchar * date,gint64 * _time)207 gdata_parser_int64_from_date (const gchar *date, gint64 *_time)
208 {
209 gchar *iso8601_date;
210 gboolean success;
211 GTimeVal time_val;
212
213 if (strlen (date) != 10 && strlen (date) != 8)
214 return FALSE;
215
216 /* Note: This doesn't need translating, as it's outputting an ISO 8601 time string */
217 iso8601_date = g_strdup_printf ("%sT00:00:00Z", date);
218 success = g_time_val_from_iso8601 (iso8601_date, &time_val);
219 g_free (iso8601_date);
220
221 if (success == TRUE)
222 *_time = time_val.tv_sec;
223
224 return success;
225 }
226
227 gchar *
gdata_parser_date_from_int64(gint64 _time)228 gdata_parser_date_from_int64 (gint64 _time)
229 {
230 time_t secs;
231 struct tm *tm;
232
233 secs = _time;
234 tm = gmtime (&secs);
235
236 /* Note: This doesn't need translating, as it's outputting an ISO 8601 date string */
237 return g_strdup_printf ("%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
238 }
239
240 gchar *
gdata_parser_int64_to_iso8601(gint64 _time)241 gdata_parser_int64_to_iso8601 (gint64 _time)
242 {
243 GTimeVal time_val;
244
245 time_val.tv_sec = _time;
246 time_val.tv_usec = 0;
247
248 return g_time_val_to_iso8601 (&time_val);
249 }
250
251 gboolean
gdata_parser_int64_from_iso8601(const gchar * date,gint64 * _time)252 gdata_parser_int64_from_iso8601 (const gchar *date, gint64 *_time)
253 {
254 GTimeVal time_val;
255
256 if (g_time_val_from_iso8601 (date, &time_val) == TRUE) {
257 *_time = time_val.tv_sec;
258 return TRUE;
259 }
260
261 return FALSE;
262 }
263
264 gboolean
gdata_parser_error_required_json_content_missing(JsonReader * reader,GError ** error)265 gdata_parser_error_required_json_content_missing (JsonReader *reader, GError **error)
266 {
267 const gchar *element_string = json_reader_get_member_name (reader);
268
269 /* Translators: the parameter is the name of an JSON element.
270 *
271 * For example:
272 * A ‘title’ element was missing required content. */
273 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A ‘%s’ element was missing required content."), element_string);
274
275 return FALSE;
276 }
277
278 gboolean
gdata_parser_error_duplicate_json_element(JsonReader * reader,GError ** error)279 gdata_parser_error_duplicate_json_element (JsonReader *reader, GError **error)
280 {
281 const gchar *element_string = json_reader_get_member_name (reader);
282
283 /* Translators: the parameter is the name of an JSON element.
284 *
285 * For example:
286 * A singleton element (title) was duplicated. */
287 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR, _("A singleton element (%s) was duplicated."), element_string);
288
289 return FALSE;
290 }
291
292 gboolean
gdata_parser_error_not_iso8601_format_json(JsonReader * reader,const gchar * actual_value,GError ** error)293 gdata_parser_error_not_iso8601_format_json (JsonReader *reader, const gchar *actual_value, GError **error)
294 {
295 const gchar *element_string = json_reader_get_member_name (reader);
296
297 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
298 /* Translators: the first parameter is the name of an JSON element,
299 * and the second parameter is the erroneous value (which was not in ISO 8601 format).
300 *
301 * For example:
302 * The content of a ‘uploaded’ element (‘2009-05-06 26:30Z’) was not in ISO 8601 format. */
303 _("The content of a ‘%s’ element (‘%s’) was not in ISO 8601 format."), element_string, actual_value);
304
305 return FALSE;
306 }
307
308 gboolean
gdata_parser_error_from_json_error(JsonReader * reader,const GError * json_error,GError ** error)309 gdata_parser_error_from_json_error (JsonReader *reader,
310 const GError *json_error, GError **error)
311 {
312 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
313 /* Translators: the parameter is an error message. */
314 _("Invalid JSON was received from the server: %s"), json_error->message);
315
316 return FALSE;
317 }
318
319 /*
320 * gdata_parser_boolean_from_property:
321 * @element: the XML element which owns the property to parse
322 * @property_name: the name of the property to parse
323 * @output: the return location for the parsed boolean value
324 * @default_output: the default value to return in @output if the property can't be found
325 * @error: a #GError, or %NULL
326 *
327 * Parses a GData boolean value from the property @property_name of @element.
328 * The boolean value should be of the form: "<element property_name='[true|false]'/>".
329 * A %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error will be returned in @error if parsing fails, and @output will not be set.
330 *
331 * If no property with the name @property_name exists in @element and @default_output is <code class="literal">0</code>,
332 * @output will be set to %FALSE.
333 * If @default_output is <code class="literal">1</code>, @output will be set to %TRUE. If @default_output is <code class="literal">-1</code>,
334 * a %GDATA_SERVICE_ERROR_PROTOCOL_ERROR will be returned in @error.
335 *
336 * Return value: %TRUE on successful parsing, %FALSE otherwise
337 *
338 * Since: 0.7.0
339 */
340 gboolean
gdata_parser_boolean_from_property(xmlNode * element,const gchar * property_name,gboolean * output,gint default_output,GError ** error)341 gdata_parser_boolean_from_property (xmlNode *element, const gchar *property_name, gboolean *output, gint default_output, GError **error)
342 {
343 xmlChar *value = xmlGetProp (element, (xmlChar*) property_name);
344
345 if (value == NULL) {
346 /* Missing property */
347 if (default_output == -1)
348 return gdata_parser_error_required_property_missing (element, property_name, error);
349 *output = (default_output == 1) ? TRUE : FALSE;
350 } else if (xmlStrcmp (value, (xmlChar*) "false") == 0) {
351 *output = FALSE;
352 } else if (xmlStrcmp (value, (xmlChar*) "true") == 0) {
353 *output = TRUE;
354 } else {
355 /* Parsing failed */
356 gdata_parser_error_unknown_property_value (element, property_name, (gchar*) value, error);
357 xmlFree (value);
358 return FALSE;
359 }
360
361 xmlFree (value);
362 return TRUE;
363 }
364
365 /*
366 * gdata_parser_is_namespace:
367 * @element: the element to check
368 * @namespace_uri: the URI of the desired namespace
369 *
370 * Checks whether @element is in the namespace identified by @namespace_uri. If @element isn't in a namespace,
371 * %FALSE is returned.
372 *
373 * Return value: %TRUE if @element is in @namespace_uri; %FALSE otherwise
374 *
375 * Since: 0.7.0
376 */
377 gboolean
gdata_parser_is_namespace(xmlNode * element,const gchar * namespace_uri)378 gdata_parser_is_namespace (xmlNode *element, const gchar *namespace_uri)
379 {
380 if ((element->ns != NULL && xmlStrcmp (element->ns->href, (const xmlChar*) namespace_uri) == 0) ||
381 (element->ns == NULL && strcmp (namespace_uri, "http://www.w3.org/2005/Atom") == 0))
382 return TRUE;
383 return FALSE;
384 }
385
386 /*
387 * gdata_parser_string_from_element:
388 * @element: the element to check against
389 * @element_name: the name of the element to parse
390 * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
391 * @output: the return location for the parsed string content
392 * @success: the return location for a value which is %TRUE if the string was parsed successfully, %FALSE if an error was encountered,
393 * and undefined if @element didn't match @element_name
394 * @error: a #GError, or %NULL
395 *
396 * Gets the string content of @element if its name is @element_name, subject to various checks specified by @options.
397 *
398 * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
399 *
400 * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
401 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
402 *
403 * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
404 * @success will be set to %TRUE.
405 *
406 * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_string_from_element() can be chained
407 * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
408 * a given @element. If any of the calls to gdata_parser_string_from_element() return %TRUE, the value of @success can be examined.
409 *
410 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
411 *
412 * Since: 0.7.0
413 */
414 gboolean
gdata_parser_string_from_element(xmlNode * element,const gchar * element_name,GDataParserOptions options,gchar ** output,gboolean * success,GError ** error)415 gdata_parser_string_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions options,
416 gchar **output, gboolean *success, GError **error)
417 {
418 xmlChar *text;
419
420 /* Check it's the right element */
421 if (xmlStrcmp (element->name, (xmlChar*) element_name) != 0)
422 return FALSE;
423
424 /* Check if the output string has already been set */
425 if (options & P_NO_DUPES && *output != NULL) {
426 *success = gdata_parser_error_duplicate_element (element, error);
427 return TRUE;
428 }
429
430 /* Get the string and check it for NULLness or emptiness */
431 text = xmlNodeListGetString (element->doc, element->children, TRUE);
432 if ((options & P_REQUIRED && text == NULL) || (options & P_NON_EMPTY && text != NULL && *text == '\0')) {
433 xmlFree (text);
434 *success = gdata_parser_error_required_content_missing (element, error);
435 return TRUE;
436 } else if (options & P_DEFAULT && (text == NULL || *text == '\0')) {
437 text = (xmlChar*) g_strdup ("");
438 }
439
440 /* Success! */
441 g_free (*output);
442 *output = (gchar*) text;
443 *success = TRUE;
444
445 return TRUE;
446 }
447
448 /*
449 * gdata_parser_int64_time_from_element:
450 * @element: the element to check against
451 * @element_name: the name of the element to parse
452 * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
453 * @output: (out caller-allocates): the return location for the parsed time value
454 * @success: the return location for a value which is %TRUE if the time val was parsed successfully, %FALSE if an error was encountered,
455 * and undefined if @element didn't match @element_name
456 * @error: a #GError, or %NULL
457 *
458 * Gets the time value of @element if its name is @element_name, subject to various checks specified by @options. It expects the text content
459 * of @element to be a date or time value in ISO 8601 format. The returned time value will be a UNIX timestamp (seconds since the epoch).
460 *
461 * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
462 *
463 * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
464 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
465 *
466 * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
467 * @success will be set to %TRUE.
468 *
469 * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_int64_time_from_element() can be chained
470 * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
471 * a given @element. If any of the calls to gdata_parser_int64_time_from_element() return %TRUE, the value of @success can be examined.
472 *
473 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
474 *
475 * Since: 0.7.0
476 */
477 gboolean
gdata_parser_int64_time_from_element(xmlNode * element,const gchar * element_name,GDataParserOptions options,gint64 * output,gboolean * success,GError ** error)478 gdata_parser_int64_time_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions options,
479 gint64 *output, gboolean *success, GError **error)
480 {
481 xmlChar *text;
482 GTimeVal time_val;
483
484 /* Check it's the right element */
485 if (xmlStrcmp (element->name, (xmlChar*) element_name) != 0)
486 return FALSE;
487
488 /* Check if the output time val has already been set */
489 if (options & P_NO_DUPES && *output != -1) {
490 *success = gdata_parser_error_duplicate_element (element, error);
491 return TRUE;
492 }
493
494 /* Get the string and check it for NULLness */
495 text = xmlNodeListGetString (element->doc, element->children, TRUE);
496 if (options & P_REQUIRED && (text == NULL || *text == '\0')) {
497 xmlFree (text);
498 *success = gdata_parser_error_required_content_missing (element, error);
499 return TRUE;
500 }
501
502 /* Attempt to parse the string as a GTimeVal */
503 if (g_time_val_from_iso8601 ((gchar*) text, &time_val) == FALSE) {
504 *success = gdata_parser_error_not_iso8601_format (element, (gchar*) text, error);
505 xmlFree (text);
506 return TRUE;
507 }
508
509 *output = time_val.tv_sec;
510
511 /* Success! */
512 xmlFree (text);
513 *success = TRUE;
514
515 return TRUE;
516 }
517
518 /*
519 * gdata_parser_int64_from_element:
520 * @element: the element to check against
521 * @element_name: the name of the element to parse
522 * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
523 * @output: (out caller-allocates): the return location for the parsed integer value
524 * @default_output: default value for the property, used with %P_NO_DUPES to detect duplicates
525 * @success: the return location for a value which is %TRUE if the integer was parsed successfully, %FALSE if an error was encountered,
526 * and undefined if @element didn't match @element_name
527 * @error: a #GError, or %NULL
528 *
529 * Gets the integer value of @element if its name is @element_name, subject to various checks specified by @options. It expects the text content
530 * of @element to be an integer in base 10 format.
531 *
532 * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
533 *
534 * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
535 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
536 *
537 * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
538 * @success will be set to %TRUE.
539 *
540 * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_int64_from_element() can be chained
541 * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
542 * a given @element. If any of the calls to gdata_parser_int64_from_element() return %TRUE, the value of @success can be examined.
543 *
544 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
545 *
546 * Since: 0.13.0
547 */
548 gboolean
gdata_parser_int64_from_element(xmlNode * element,const gchar * element_name,GDataParserOptions options,gint64 * output,gint64 default_output,gboolean * success,GError ** error)549 gdata_parser_int64_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions options,
550 gint64 *output, gint64 default_output, gboolean *success, GError **error)
551 {
552 xmlChar *text;
553 gchar *end_ptr;
554 gint64 val;
555
556 /* Check it's the right element */
557 if (xmlStrcmp (element->name, (xmlChar*) element_name) != 0) {
558 return FALSE;
559 }
560
561 /* Check if the output value has already been set */
562 if (options & P_NO_DUPES && *output != default_output) {
563 *success = gdata_parser_error_duplicate_element (element, error);
564 return TRUE;
565 }
566
567 /* Get the string and check it for NULLness */
568 text = xmlNodeListGetString (element->doc, element->children, TRUE);
569 if (options & P_REQUIRED && (text == NULL || *text == '\0')) {
570 xmlFree (text);
571 *success = gdata_parser_error_required_content_missing (element, error);
572 return TRUE;
573 }
574
575 /* Attempt to parse the string as a 64-bit integer */
576 val = g_ascii_strtoll ((const gchar*) text, &end_ptr, 10);
577 if (*end_ptr != '\0') {
578 *success = gdata_parser_error_unknown_content (element, (gchar*) text, error);
579 xmlFree (text);
580 return TRUE;
581 }
582
583 *output = val;
584
585 /* Success! */
586 xmlFree (text);
587 *success = TRUE;
588
589 return TRUE;
590 }
591
592 /*
593 * gdata_parser_object_from_element_setter:
594 * @element: the element to check against
595 * @element_name: the name of the element to parse
596 * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
597 * @object_type: the type of the object to parse
598 * @_setter: a function to call once parsing's finished to return the object (#GDataParserSetterFunc)
599 * @_parent_parsable: the first parameter to pass to @_setter (of type #GDataParsable)
600 * @success: the return location for a value which is %TRUE if the object was parsed successfully, %FALSE if an error was encountered,
601 * and undefined if @element didn't match @element_name
602 * @error: a #GError, or %NULL
603 *
604 * Gets the object content of @element if its name is @element_name, subject to various checks specified by @options. If @element matches
605 * @element_name, @element will be parsed as an @object_type, which must extend #GDataParsable. If parsing is successful, @_setter will be called
606 * with its first parameter as @_parent_parsable, and its second as the parsed object of type @object_type. @_setter must reference the parsed object
607 * it's passed if it wants to keep it, as gdata_parser_object_from_element_setter will unreference it before returning.
608 *
609 * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
610 *
611 * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
612 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
613 *
614 * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
615 * @success will be set to %TRUE.
616 *
617 * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_object_from_element_setter() can be chained
618 * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
619 * a given @element. If any of the calls to gdata_parser_object_from_element_setter() return %TRUE, the value of @success can be examined.
620 *
621 * @_setter and @_parent_parsable are both #gpointers to avoid casts having to be put into calls to gdata_parser_object_from_element_setter().
622 * @_setter is actually of type #GDataParserSetterFunc, and @_parent_parsable should be a subclass of #GDataParsable.
623 * Neither parameter should be %NULL. No checks are implemented against these conditions (for efficiency reasons), so calling code must be correct.
624 *
625 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
626 *
627 * Since: 0.7.0
628 */
629 gboolean
gdata_parser_object_from_element_setter(xmlNode * element,const gchar * element_name,GDataParserOptions options,GType object_type,gpointer _setter,gpointer _parent_parsable,gboolean * success,GError ** error)630 gdata_parser_object_from_element_setter (xmlNode *element, const gchar *element_name, GDataParserOptions options, GType object_type,
631 gpointer /* GDataParserSetterFunc */ _setter, gpointer /* GDataParsable * */ _parent_parsable,
632 gboolean *success, GError **error)
633 {
634 GDataParsable *parsable, *parent_parsable;
635 GDataParserSetterFunc setter;
636 GError *local_error = NULL;
637
638 g_return_val_if_fail (!(options & P_REQUIRED) || !(options & P_IGNORE_ERROR), FALSE);
639
640 /* We're lax on the types so that we don't have to do loads of casting when calling the function, which makes the parsing code more legible */
641 setter = (GDataParserSetterFunc) _setter;
642 parent_parsable = (GDataParsable*) _parent_parsable;
643
644 /* Check it's the right element */
645 if (xmlStrcmp (element->name, (xmlChar*) element_name) != 0)
646 return FALSE;
647
648 /* Get the object and check for instantiation failure */
649 parsable = _gdata_parsable_new_from_xml_node (object_type, element->doc, element, NULL, &local_error);
650 g_assert ((parsable == NULL) == (local_error != NULL));
651
652 if (options & P_REQUIRED && parsable == NULL) {
653 /* The error has already been set by _gdata_parsable_new_from_xml_node() */
654 g_propagate_error (error, g_steal_pointer (&local_error));
655 *success = FALSE;
656 return TRUE;
657 } else if ((options & P_IGNORE_ERROR) && parsable == NULL) {
658 g_clear_error (&local_error);
659 *success = TRUE;
660 return TRUE;
661 } else if (local_error != NULL) {
662 g_propagate_error (error, g_steal_pointer (&local_error));
663 *success = FALSE;
664 return TRUE;
665 }
666
667 /* Success! */
668 setter (parent_parsable, parsable);
669 g_object_unref (parsable);
670 *success = TRUE;
671
672 return TRUE;
673 }
674
675 /*
676 * gdata_parser_object_from_element:
677 * @element: the element to check against
678 * @element_name: the name of the element to parse
679 * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
680 * @object_type: the type of the object to parse
681 * @_output: the return location for the parsed object (of type #GDataParsable)
682 * @success: the return location for a value which is %TRUE if the object was parsed successfully, %FALSE if an error was encountered,
683 * and undefined if @element didn't match @element_name
684 * @error: a #GError, or %NULL
685 *
686 * Gets the object content of @element if its name is @element_name, subject to various checks specified by @options. If @element matches
687 * @element_name, @element will be parsed as an @object_type, which must extend #GDataParsable. If parsing is successful, the parsed object will be
688 * returned in @_output, which must be of type #GDataParsable (or a subclass). Ownership of the parsed object will pass to the calling code.
689 *
690 * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
691 *
692 * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
693 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
694 *
695 * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
696 * @success will be set to %TRUE.
697 *
698 * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_object_from_element() can be chained
699 * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
700 * a given @element. If any of the calls to gdata_parser_object_from_element() return %TRUE, the value of @success can be examined.
701 *
702 * @_object is a #gpointer to avoid casts having to be put into calls to gdata_parser_object_from_element(). It is actually of type #GDataParsable
703 * and must not be %NULL. No check is implemented against this condition (for efficiency reasons), so calling code must be correct.
704 *
705 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
706 *
707 * Since: 0.7.0
708 */
709 gboolean
gdata_parser_object_from_element(xmlNode * element,const gchar * element_name,GDataParserOptions options,GType object_type,gpointer _output,gboolean * success,GError ** error)710 gdata_parser_object_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions options, GType object_type,
711 gpointer /* GDataParsable ** */ _output, gboolean *success, GError **error)
712 {
713 GDataParsable *parsable, **output;
714
715 /* We're lax on the types so that we don't have to do loads of casting when calling the function, which makes the parsing code more legible */
716 output = (GDataParsable**) _output;
717
718 /* Check it's the right element */
719 if (xmlStrcmp (element->name, (xmlChar*) element_name) != 0)
720 return FALSE;
721
722 /* If we're not using a setter, check if the output already exists */
723 if (options & P_NO_DUPES && *output != NULL) {
724 *success = gdata_parser_error_duplicate_element (element, error);
725 return TRUE;
726 }
727
728 /* Get the object and check for instantiation failure */
729 parsable = _gdata_parsable_new_from_xml_node (object_type, element->doc, element, NULL, error);
730 if (options & P_REQUIRED && parsable == NULL) {
731 /* The error has already been set by _gdata_parsable_new_from_xml_node() */
732 *success = FALSE;
733 return TRUE;
734 }
735
736 /* Success! */
737 if (*output != NULL)
738 g_object_unref (*output);
739 *output = parsable;
740 *success = TRUE;
741
742 return TRUE;
743 }
744
745 /*
746 * gdata_parser_string_from_json_member:
747 * @reader: #JsonReader cursor object to read JSON node from
748 * @member_name: the name of the member to parse
749 * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
750 * @output: the return location for the parsed string content
751 * @success: the return location for a value which is %TRUE if the string was parsed successfully, %FALSE if an error was encountered,
752 * and undefined if @element didn't match @element_name
753 * @error: a #GError, or %NULL
754 *
755 * Gets the string content of @element if its name is @element_name, subject to various checks specified by @options.
756 *
757 * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
758 *
759 * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
760 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
761 *
762 * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
763 * @success will be set to %TRUE.
764 *
765 * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_string_from_element() can be chained
766 * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
767 * a given @element. If any of the calls to gdata_parser_string_from_element() return %TRUE, the value of @success can be examined.
768 *
769 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
770 *
771 * Since: 0.15.0
772 */
773 gboolean
gdata_parser_string_from_json_member(JsonReader * reader,const gchar * member_name,GDataParserOptions options,gchar ** output,gboolean * success,GError ** error)774 gdata_parser_string_from_json_member (JsonReader *reader, const gchar *member_name, GDataParserOptions options,
775 gchar **output, gboolean *success, GError **error)
776 {
777 const gchar *text;
778 const GError *child_error = NULL;
779
780 /* Check if there's such element */
781 if (g_strcmp0 (json_reader_get_member_name (reader), member_name) != 0) {
782 return FALSE;
783 }
784
785 /* Check if the output string has already been set. The JSON parser guarantees this can't happen. */
786 g_assert (!(options & P_NO_DUPES) || *output == NULL);
787
788 /* Get the string and check it for NULLness or emptiness. Check for parser errors first. */
789 text = json_reader_get_string_value (reader);
790 child_error = json_reader_get_error (reader);
791 if (child_error != NULL) {
792 *success = gdata_parser_error_from_json_error (reader,
793 child_error,
794 error);
795 return TRUE;
796 } else if ((options & P_REQUIRED && text == NULL) || (options & P_NON_EMPTY && text != NULL && *text == '\0')) {
797 *success = gdata_parser_error_required_json_content_missing (reader, error);
798 return TRUE;
799 } else if (options & P_DEFAULT && (text == NULL || *text == '\0')) {
800 text = "";
801 }
802
803 /* Success! */
804 g_free (*output);
805 *output = g_strdup (text);
806 *success = TRUE;
807
808 return TRUE;
809 }
810
811 /*
812 * gdata_parser_int_from_json_member:
813 * @reader: #JsonReader cursor object to read JSON node from
814 * @member_name: the name of the member to parse
815 * @options: a bitwise combination of parsing options from #GDataParserOptions,
816 * or %P_NONE
817 * @output: the return location for the parsed integer content
818 * @success: the return location for a value which is %TRUE if the integer was
819 * parsed successfully, %FALSE if an error was encountered, and undefined if
820 * @element didn't match @element_name
821 * @error: a #GError, or %NULL
822 *
823 * Gets the integer content of @element if its name is @element_name, subject to
824 * various checks specified by @options.
825 *
826 * If @element doesn't match @element_name, %FALSE will be returned, @error will
827 * be unset and @success will be unset.
828 *
829 * If @element matches @element_name but one of the checks specified by @options
830 * fails, %TRUE will be returned, @error will be set to a
831 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
832 *
833 * If @element matches @element_name and all of the checks specified by @options
834 * pass, %TRUE will be returned, @error will be unset and @success will be set
835 * to %TRUE.
836 *
837 * The reason for returning the success of the parsing in @success is so that
838 * calls to gdata_parser_int_from_element() can be chained together in a large
839 * "or" statement based on their return values, for the purposes of determining
840 * whether any of the calls matched a given @element. If any of the calls to
841 * gdata_parser_int_from_element() return %TRUE, the value of @success can be
842 * examined.
843 *
844 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
845 *
846 * Since: 0.17.0
847 */
848 gboolean
gdata_parser_int_from_json_member(JsonReader * reader,const gchar * member_name,GDataParserOptions options,gint64 * output,gboolean * success,GError ** error)849 gdata_parser_int_from_json_member (JsonReader *reader,
850 const gchar *member_name,
851 GDataParserOptions options,
852 gint64 *output, gboolean *success,
853 GError **error)
854 {
855 gint64 value;
856 const GError *child_error = NULL;
857
858 /* Check if there's such element */
859 if (g_strcmp0 (json_reader_get_member_name (reader), member_name) != 0) {
860 return FALSE;
861 }
862
863 value = json_reader_get_int_value (reader);
864 child_error = json_reader_get_error (reader);
865
866 if (child_error != NULL) {
867 *success = gdata_parser_error_from_json_error (reader,
868 child_error,
869 error);
870 return TRUE;
871 }
872
873 /* Success! */
874 *output = value;
875 *success = TRUE;
876
877 return TRUE;
878 }
879
880 /*
881 * gdata_parser_int64_time_from_json_member:
882 * @reader: #JsonReader cursor object to read JSON node from
883 * @element_name: the name of the element to parse
884 * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
885 * @output: (out caller-allocates): the return location for the parsed time value
886 * @success: the return location for a value which is %TRUE if the time val was parsed successfully, %FALSE if an error was encountered,
887 * and undefined if @element didn't match @element_name
888 * @error: a #GError, or %NULL
889 *
890 * Gets the time value of @element if its name is @element_name, subject to various checks specified by @options. It expects the text content
891 * of @element to be a date or time value in ISO 8601 format. The returned time value will be a UNIX timestamp (seconds since the epoch).
892 *
893 * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
894 *
895 * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
896 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
897 *
898 * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
899 * @success will be set to %TRUE.
900 *
901 * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_int64_time_from_element() can be chained
902 * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
903 * a given @element. If any of the calls to gdata_parser_int64_time_from_element() return %TRUE, the value of @success can be examined.
904 *
905 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
906 *
907 * Since: 0.15.0
908 */
909 gboolean
gdata_parser_int64_time_from_json_member(JsonReader * reader,const gchar * member_name,GDataParserOptions options,gint64 * output,gboolean * success,GError ** error)910 gdata_parser_int64_time_from_json_member (JsonReader *reader, const gchar *member_name, GDataParserOptions options,
911 gint64 *output, gboolean *success, GError **error)
912 {
913 const gchar *text;
914 GTimeVal time_val;
915 const GError *child_error = NULL;
916
917 /* Check if there's such element */
918 if (g_strcmp0 (json_reader_get_member_name (reader), member_name) != 0) {
919 return FALSE;
920 }
921
922 /* Check if the output time val has already been set. The JSON parser guarantees this can't happen. */
923 g_assert (!(options & P_NO_DUPES) || *output == -1);
924
925 /* Get the string and check it for NULLness. Check for errors first. */
926 text = json_reader_get_string_value (reader);
927 child_error = json_reader_get_error (reader);
928 if (child_error != NULL) {
929 *success = gdata_parser_error_from_json_error (reader,
930 child_error,
931 error);
932 return TRUE;
933 } else if (options & P_REQUIRED && (text == NULL || *text == '\0')) {
934 *success = gdata_parser_error_required_json_content_missing (reader, error);
935 return TRUE;
936 }
937
938 /* Attempt to parse the string as a GTimeVal */
939 if (g_time_val_from_iso8601 ((gchar*) text, &time_val) == FALSE) {
940 *success = gdata_parser_error_not_iso8601_format_json (reader, text, error);
941 return TRUE;
942 }
943
944 /* Success! */
945 *output = time_val.tv_sec;
946 *success = TRUE;
947
948 return TRUE;
949 }
950
951 /*
952 * gdata_parser_boolean_from_json_member:
953 * @reader: #JsonReader cursor object to read the JSON node from
954 * @member_name: the name of the JSON object member to parse
955 * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
956 * @output: (out caller-allocates): the return location for the parsed boolean value
957 * @success: (out caller-allocates): the return location for a value which is %TRUE if the boolean was parsed successfully, %FALSE if an error was encountered,
958 * and undefined if @member_name was not found in the current object in @reader
959 * @error: (allow-none): a #GError, or %NULL
960 *
961 * Gets the boolean value of the @member_name member of the current object in the #JsonReader, subject to various checks specified by @options.
962 *
963 * If no member matching @member_name can be found in the current @reader JSON object, %FALSE will be returned, @error will be unset and @success will be unset. @output will be undefined.
964 *
965 * If @member_name is found but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
966 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE. @output will be undefined.
967 *
968 * If @member_name is found and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
969 * @success will be set to %TRUE. @output will be set to the parsed value.
970 *
971 * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_boolean_from_json_node() can be chained
972 * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
973 * a given JSON node. If any of the calls to gdata_parser_boolean_from_json_node() return %TRUE, the value of @success can be examined.
974 *
975 * Return value: %TRUE if @member_name was found, %FALSE otherwise
976 *
977 * Since: 0.15.0
978 */
979 gboolean
gdata_parser_boolean_from_json_member(JsonReader * reader,const gchar * member_name,GDataParserOptions options,gboolean * output,gboolean * success,GError ** error)980 gdata_parser_boolean_from_json_member (JsonReader *reader, const gchar *member_name, GDataParserOptions options, gboolean *output, gboolean *success, GError **error)
981 {
982 gboolean val;
983 const GError *child_error = NULL;
984
985 /* Check if there's such an element. */
986 if (g_strcmp0 (json_reader_get_member_name (reader), member_name) != 0) {
987 return FALSE;
988 }
989
990 /* Get the boolean. Check for parse errors. */
991 val = json_reader_get_boolean_value (reader);
992 child_error = json_reader_get_error (reader);
993 if (child_error != NULL) {
994 *success = gdata_parser_error_from_json_error (reader,
995 child_error,
996 error);
997 return TRUE;
998 }
999
1000 /* Success! */
1001 *output = val;
1002 *success = TRUE;
1003
1004 return TRUE;
1005 }
1006
1007 /*
1008 * gdata_parser_strv_from_json_member:
1009 * @reader: #JsonReader cursor object to read JSON node from
1010 * @element_name: the name of the element to parse
1011 * @options: a bitwise combination of parsing options from #GDataParserOptions,
1012 * or %P_NONE
1013 * @output: (out callee-allocates) (transfer full): the return location for the
1014 * parsed string array
1015 * @success: the return location for a value which is %TRUE if the string array
1016 * was parsed successfully, %FALSE if an error was encountered, and undefined
1017 * if @element didn't match @element_name
1018 * @error: a #GError, or %NULL
1019 *
1020 * Gets the string array of @element if its name is @element_name, subject to
1021 * various checks specified by @options. It expects the @element to be an array
1022 * of strings.
1023 *
1024 * If @element doesn't match @element_name, %FALSE will be returned, @error will
1025 * be unset and @success will be unset.
1026 *
1027 * If @element matches @element_name but one of the checks specified by @options
1028 * fails, %TRUE will be returned, @error will be set to a
1029 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
1030 *
1031 * If @element matches @element_name and all of the checks specified by @options
1032 * pass, %TRUE will be returned, @error will be unset and @success will be set
1033 * to %TRUE.
1034 *
1035 * The reason for returning the success of the parsing in @success is so that
1036 * calls to gdata_parser_strv_from_element() can be chained together in a large
1037 * "or" statement based on their return values, for the purposes of determining
1038 * whether any of the calls matched a given @element. If any of the calls to
1039 * gdata_parser_strv_from_element() return %TRUE, the value of @success can be
1040 * examined.
1041 *
1042 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
1043 *
1044 * Since: 0.17.0
1045 */
1046 gboolean
gdata_parser_strv_from_json_member(JsonReader * reader,const gchar * member_name,GDataParserOptions options,gchar *** output,gboolean * success,GError ** error)1047 gdata_parser_strv_from_json_member (JsonReader *reader,
1048 const gchar *member_name,
1049 GDataParserOptions options,
1050 gchar ***output, gboolean *success,
1051 GError **error)
1052 {
1053 guint i, len;
1054 GPtrArray *out;
1055 const GError *child_error = NULL;
1056
1057 /* Check if there's such element */
1058 if (g_strcmp0 (json_reader_get_member_name (reader),
1059 member_name) != 0) {
1060 return FALSE;
1061 }
1062
1063 /* Check if the output strv has already been set. The JSON parser
1064 * guarantees this can't happen. */
1065 g_assert (!(options & P_NO_DUPES) || *output == NULL);
1066
1067 len = json_reader_count_elements (reader);
1068 child_error = json_reader_get_error (reader);
1069
1070 if (child_error != NULL) {
1071 *success = gdata_parser_error_from_json_error (reader,
1072 child_error,
1073 error);
1074 return TRUE;
1075 }
1076
1077 out = g_ptr_array_new_full (len + 1 /* NULL terminator */, g_free);
1078
1079 for (i = 0; i < len; i++) {
1080 const gchar *val;
1081
1082 json_reader_read_element (reader, i);
1083 val = json_reader_get_string_value (reader);
1084 child_error = json_reader_get_error (reader);
1085
1086 if (child_error != NULL) {
1087 *success = gdata_parser_error_from_json_error (reader,
1088 child_error,
1089 error);
1090 json_reader_end_element (reader);
1091 g_ptr_array_unref (out);
1092 return TRUE;
1093 }
1094
1095 g_ptr_array_add (out, g_strdup (val));
1096
1097 json_reader_end_element (reader);
1098 }
1099
1100 /* NULL terminator. */
1101 g_ptr_array_add (out, NULL);
1102
1103 /* Success! */
1104 *output = (gchar **) g_ptr_array_free (out, FALSE);
1105 *success = TRUE;
1106
1107 return TRUE;
1108 }
1109
1110 /*
1111 * gdata_parser_color_from_json_member:
1112 * @reader: #JsonReader cursor object to read JSON node from
1113 * @element_name: the name of the element to parse
1114 * @options: a bitwise combination of parsing options from #GDataParserOptions,
1115 * or %P_NONE
1116 * @output: (out caller-allocates): the return location for the parsed colour
1117 * value
1118 * @success: the return location for a value which is %TRUE if the colour was
1119 * parsed successfully, %FALSE if an error was encountered, and undefined if
1120 * @element didn't match @element_name
1121 * @error: a #GError, or %NULL
1122 *
1123 * Gets the colour value of @element if its name is @element_name, subject to
1124 * various checks specified by @options. It expects the text content of
1125 * @element to be an RGB colour in hexadecimal format, with an optional leading
1126 * hash symbol (for example, `#RRGGBB` or `RRGGBB`).
1127 *
1128 * If @element doesn't match @element_name, %FALSE will be returned, @error
1129 * will be unset and @success will be unset.
1130 *
1131 * If @element matches @element_name but one of the checks specified by
1132 * @options fails, %TRUE will be returned, @error will be set to a
1133 * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
1134 *
1135 * If @element matches @element_name and all of the checks specified by
1136 * @options pass, %TRUE will be returned, @error will be unset and @success
1137 * will be set to %TRUE.
1138 *
1139 * The reason for returning the success of the parsing in @success is so that
1140 * calls to gdata_parser_color_from_json_member() can be chained together in a
1141 * large "or" statement based on their return values, for the purposes of
1142 * determining whether any of the calls matched a given @element. If any of the
1143 * calls to gdata_parser_color_from_json_member() return %TRUE, the value of
1144 * @success can be examined.
1145 *
1146 * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
1147 *
1148 * Since: 0.17.2
1149 */
1150 gboolean
gdata_parser_color_from_json_member(JsonReader * reader,const gchar * member_name,GDataParserOptions options,GDataColor * output,gboolean * success,GError ** error)1151 gdata_parser_color_from_json_member (JsonReader *reader,
1152 const gchar *member_name,
1153 GDataParserOptions options,
1154 GDataColor *output,
1155 gboolean *success,
1156 GError **error)
1157 {
1158 const gchar *text;
1159 GDataColor colour;
1160 const GError *child_error = NULL;
1161
1162 /* Check if there's such an element */
1163 if (g_strcmp0 (json_reader_get_member_name (reader), member_name) != 0) {
1164 return FALSE;
1165 }
1166
1167 /* Check if the output colour has already been set. The JSON parser
1168 * guarantees this can't happen. */
1169 g_assert (!(options & P_NO_DUPES) ||
1170 (output->red == 0 && output->green == 0 && output->blue == 0));
1171
1172 /* Get the string and check it for NULLness. Check for errors first. */
1173 text = json_reader_get_string_value (reader);
1174 child_error = json_reader_get_error (reader);
1175 if (child_error != NULL) {
1176 *success = gdata_parser_error_from_json_error (reader, child_error, error);
1177 return TRUE;
1178 } else if (options & P_REQUIRED && (text == NULL || *text == '\0')) {
1179 *success = gdata_parser_error_required_json_content_missing (reader, error);
1180 return TRUE;
1181 }
1182
1183 /* Attempt to parse the string as a hexadecimal colour. */
1184 if (gdata_color_from_hexadecimal (text, &colour) == FALSE) {
1185 /* Error */
1186 g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
1187 /* Translators: the first parameter is the name of an XML element (including the angle brackets
1188 * ("<" and ">"), and the second parameter is the erroneous value (which was not in hexadecimal
1189 * RGB format).
1190 *
1191 * For example:
1192 * The content of a <entry/gCal:color> element (‘00FG56’) was not in hexadecimal RGB format. */
1193 _("The content of a %s element (‘%s’) was not in hexadecimal RGB format."),
1194 member_name, text);
1195 *success = FALSE;
1196
1197 return TRUE;
1198 }
1199
1200 /* Success! */
1201 *output = colour;
1202 *success = TRUE;
1203
1204 return TRUE;
1205 }
1206
1207 void
gdata_parser_string_append_escaped(GString * xml_string,const gchar * pre,const gchar * element_content,const gchar * post)1208 gdata_parser_string_append_escaped (GString *xml_string, const gchar *pre, const gchar *element_content, const gchar *post)
1209 {
1210 /* Allocate 10 extra bytes when reallocating the GString, to try and avoid having to reallocate again, by assuming
1211 * there will be an increase in the length of element_content when escaped of less than 10 characters. */
1212 /* #define SIZE_FUZZINESS 10*/
1213
1214 /* guint new_size;*/
1215 const gchar *p;
1216
1217 /* Expand xml_string as necessary */
1218 /* TODO: There is no way to expand the allocation of a GString if you know in advance how much room
1219 * lots of append operations are going to require. */
1220 /* new_size = xml_string->len + strlen (pre) + strlen (element_content) + strlen (post) + SIZE_FUZZINESS;
1221 if (new_size > xml_string->allocated_len)
1222 g_string_set_size (xml_string, new_size);*/
1223
1224 /* Append the pre content */
1225 if (pre != NULL)
1226 g_string_append (xml_string, pre);
1227
1228 /* Loop through the string to be escaped. Code adapted from GLib's g_markup_escape_text() function.
1229 * Copyright 2000, 2003 Red Hat, Inc.
1230 * Copyright 2007, 2008 Ryan Lortie <desrt@desrt.ca> */
1231 p = element_content;
1232 while (p != NULL && *p != '\0') {
1233 const gchar *next = g_utf8_next_char (p);
1234
1235 switch (*p) {
1236 case '&':
1237 g_string_append (xml_string, "&");
1238 break;
1239 case '<':
1240 g_string_append (xml_string, "<");
1241 break;
1242 case '>':
1243 g_string_append (xml_string, ">");
1244 break;
1245 case '\'':
1246 g_string_append (xml_string, "'");
1247 break;
1248 case '"':
1249 g_string_append (xml_string, """);
1250 break;
1251 default: {
1252 gunichar c = g_utf8_get_char (p);
1253
1254 if ((0x1 <= c && c <= 0x8) ||
1255 (0xb <= c && c <= 0xc) ||
1256 (0xe <= c && c <= 0x1f) ||
1257 (0x7f <= c && c <= 0x84) ||
1258 (0x86 <= c && c <= 0x9f)) {
1259 g_string_append_printf (xml_string, "&#x%x;", c);
1260 } else {
1261 g_string_append_len (xml_string, p, next - p);
1262 break;
1263 }
1264 }
1265 }
1266 p = next;
1267 }
1268
1269 /* Append the post content */
1270 if (post != NULL)
1271 g_string_append (xml_string, post);
1272 }
1273
1274 /* TODO: Should be perfectly possible to make this modify the string in-place */
1275 gchar *
gdata_parser_utf8_trim_whitespace(const gchar * s)1276 gdata_parser_utf8_trim_whitespace (const gchar *s)
1277 {
1278 glong len;
1279 const gchar *_s;
1280
1281 /* Skip the leading whitespace */
1282 while (*s != '\0' && g_unichar_isspace (g_utf8_get_char (s)))
1283 s = g_utf8_next_char (s);
1284
1285 /* Find the end of the string and backtrack until we've passed all the whitespace */
1286 len = g_utf8_strlen (s, -1);
1287 _s = g_utf8_offset_to_pointer (s, len - 1);
1288 while (len > 0 && g_unichar_isspace (g_utf8_get_char (_s))) {
1289 _s = g_utf8_prev_char (_s);
1290 len--;
1291 }
1292 _s = g_utf8_next_char (_s);
1293
1294 return g_strndup (s, _s - s);
1295 }
1296