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, "&amp;");
1238 				break;
1239 			case '<':
1240 				g_string_append (xml_string, "&lt;");
1241 				break;
1242 			case '>':
1243 				g_string_append (xml_string, "&gt;");
1244 				break;
1245 			case '\'':
1246 				g_string_append (xml_string, "&apos;");
1247 				break;
1248 			case '"':
1249 				g_string_append (xml_string, "&quot;");
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