Added new user api unit test for the new fields.
[libgcal.git] / src / gcal_parser.c
blob78c1416183a57a2a790dd5994fd03a963e2057eb
1 /*
2 Copyright (c) 2008 Instituto Nokia de Tecnologia
3 All rights reserved.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 * Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
10 * Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13 * Neither the name of the INdT nor the names of its contributors
14 may be used to endorse or promote products derived from this software
15 without specific prior written permission.
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 POSSIBILITY OF SUCH DAMAGE.
30 /**
31 * @file gcal_parser.h
32 * @author Adenilson Cavalcanti da Silva <adenilson.silva@indt.org.br>
33 * @date Mon Mar 31 11:17:02 2008
35 * @brief A thin layer over \ref atom_parser.h, so I can plug another
36 * XML parser to libgcal if required.
37 * It creates a DOM document from libgcal atom stream and provides functions
38 * wrappers to extract data.
41 #include "gcal_parser.h"
42 #include "atom_parser.h"
43 #include "xml_aux.h"
45 #include <libxml/tree.h>
46 #include <string.h>
48 char scheme_href[] = "http://schemas.google.com/g/2005#kind";
49 char term_href_cal[] = "http://schemas.google.com/g/2005#event";
50 char term_href_cont[] = "http://schemas.google.com/contact/2008#contact";
51 /** A thin wrapper around libxml document structure
54 struct dom_document {
55 /** libxml DOM document structure pointer */
56 xmlDoc *document;
59 /* REMARK: this function is recursive, I'm not completely sure if this
60 * is a good idea (i.e. for small devices).
62 static char *get(xmlNode *a_node)
64 xmlNode *cur_node = NULL;
65 char *result = NULL;
66 xmlChar *uri = NULL;
68 for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
69 if (xmlHasProp(cur_node, "HREF")) {
70 uri = xmlGetProp(cur_node, "HREF");
71 if (uri) {
72 result = strdup(uri);
73 xmlFree(uri);
74 goto exit;
79 result = get(cur_node->children);
80 if (result)
81 goto exit;
84 exit:
85 return result;
89 int get_the_url(char *data, int length, char **url)
91 xmlDoc *doc = NULL;
92 xmlNode *root_element = NULL;
93 int result = -1;
95 *url = NULL;
96 doc = xmlReadMemory(data, length, "noname.xml", NULL, 0);
97 if (!doc)
98 goto exit;
100 root_element = xmlDocGetRootElement(doc);
101 *url = get(root_element);
102 if (*url)
103 result = 0;
105 xmlFreeDoc(doc);
106 xmlCleanupParser();
108 exit:
109 return result;
113 static char *get_edit(xmlNode *a_node)
115 xmlNode *cur_node = NULL;
116 char *result = NULL;
117 xmlChar *attr = NULL, *uri = NULL;
119 for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
120 if (xmlHasProp(cur_node, "rel")) {
121 attr = xmlGetProp(cur_node, "rel");
122 if (attr) {
123 if (!strcmp(attr, "edit")) {
124 uri = xmlGetProp(cur_node, "href");
125 if (uri)
126 result = strdup(uri);
127 xmlFree(attr);
128 xmlFree(uri);
129 goto exit;
132 xmlFree(attr);
137 result = get_edit(cur_node->children);
138 if (result)
139 goto exit;
142 exit:
143 return result;
146 int get_edit_url(char *data, int length, char **url)
148 xmlDoc *doc = NULL;
149 xmlNode *root_element = NULL;
150 int result = -1;
152 *url = NULL;
153 doc = xmlReadMemory(data, length, "noname.xml", NULL, 0);
154 if (!doc)
155 goto exit;
157 root_element = xmlDocGetRootElement(doc);
158 *url = get_edit(root_element);
159 if (*url)
160 result = 0;
162 xmlFreeDoc(doc);
163 xmlCleanupParser();
165 exit:
166 return result;
169 int get_edit_etag(char *data, int length, char **url)
171 xmlDoc *doc = NULL;
172 xmlNode *root_element = NULL;
173 int result = -1;
175 *url = NULL;
176 doc = xmlReadMemory(data, length, "noname.xml", NULL, 0);
177 if (!doc)
178 goto exit;
180 root_element = xmlDocGetRootElement(doc);
181 *url = get_etag_attribute(root_element);
182 if (*url)
183 result = 0;
185 xmlFreeDoc(doc);
186 xmlCleanupParser();
188 exit:
189 return result;
193 dom_document *build_dom_document(char *xml_data)
195 dom_document *ptr = NULL;
196 if (!xml_data)
197 goto exit;
199 if (build_doc_tree(&ptr, xml_data)) {
200 fprintf(stderr, "build_dom_document: failed doc parse");
201 goto cleanup;
204 goto exit;
206 cleanup:
207 if (ptr)
208 free(ptr);
210 exit:
211 return ptr;
215 void clean_dom_document(dom_document *doc)
217 if (doc)
218 clean_doc_tree(&doc);
222 int get_entries_number(dom_document *doc)
224 int result = -1;
225 if (!doc) {
226 fprintf(stderr, "get_entries_number: null document!");
227 goto exit;
230 result = atom_entries(doc);
231 exit:
232 return result;
235 int extract_all_entries(dom_document *doc,
236 struct gcal_event *data_extract, int length)
239 int result = -1, i;
240 xmlXPathObject *xpath_obj = NULL;
241 xmlNodeSet *nodes;
243 /* get the entry node list */
244 xpath_obj = atom_get_entries(doc);
245 if (!xpath_obj)
246 goto exit;
247 nodes = xpath_obj->nodesetval;
248 if (!nodes)
249 goto exit;
251 if (length != nodes->nodeNr) {
252 fprintf(stderr, "extract_all_entries: Size mismatch!");
253 goto cleanup;
256 /* extract the fields */
257 for (i = 0; i < length; ++i) {
258 result = atom_extract_data(nodes->nodeTab[i], &data_extract[i]);
259 if (result == -1)
260 goto cleanup;
263 result = 0;
265 cleanup:
266 xmlXPathFreeObject(xpath_obj);
268 exit:
269 return result;
272 int xmlentry_create(struct gcal_event *entry, char **xml_entry, int *length)
274 int result = -1;
275 xmlDoc *doc = NULL;
276 xmlNode *root, *node;
277 xmlNs *ns;
278 xmlChar *xml_str = NULL;
280 doc = xmlNewDoc(BAD_CAST "1.0");
281 root = xmlNewNode(NULL, BAD_CAST "entry");
283 if (!doc || !root)
284 goto exit;
286 xmlSetProp(root, BAD_CAST "xmlns", BAD_CAST atom_href);
287 /* Google Data API 2.0 requires ETag to edit an entry */
288 if (entry->common.etag)
289 xmlSetProp(root, BAD_CAST "gd:etag",
290 BAD_CAST entry->common.etag);
291 ns = xmlNewNs(root, BAD_CAST gd_href, BAD_CAST "gd");
293 xmlDocSetRootElement(doc, root);
296 /* entry ID, only if the 'entry' is already existant (i.e. the user
297 * of library just got one entry result from a request from
298 * server).
300 if (entry->common.id) {
301 node = xmlNewNode(NULL, "id");
302 if (!node)
303 goto cleanup;
304 xmlNodeAddContent(node, entry->common.id);
305 xmlAddChild(root, node);
308 /* category element */
309 node = xmlNewNode(NULL, "category");
310 if (!node)
311 goto cleanup;
312 xmlSetProp(node, BAD_CAST "scheme", BAD_CAST scheme_href);
313 xmlSetProp(node, BAD_CAST "term", BAD_CAST term_href_cal);
314 xmlAddChild(root, node);
316 /* title element */
317 node = xmlNewNode(NULL, "title");
318 if (!node)
319 goto cleanup;
320 xmlSetProp(node, BAD_CAST "type", BAD_CAST "text");
321 xmlNodeAddContent(node, entry->common.title);
322 xmlAddChild(root, node);
324 /* content element */
325 node = xmlNewNode(NULL, "content");
326 if (!node)
327 goto cleanup;
328 xmlSetProp(node, BAD_CAST "type", BAD_CAST "text");
329 xmlNodeAddContent(node, entry->content);
330 xmlAddChild(root, node);
332 /* entry edit URL, only if the 'entry' is already existant.
334 if (entry->common.edit_uri) {
335 node = xmlNewNode(NULL, "link");
336 if (!node)
337 goto cleanup;
338 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "edit");
339 xmlSetProp(node, BAD_CAST "type",
340 BAD_CAST "application/atom+xml");
341 xmlSetProp(node, BAD_CAST "href",
342 BAD_CAST entry->common.edit_uri);
343 xmlAddChild(root, node);
348 /* transparency */
349 node = xmlNewNode(ns, "transparency");
350 if (!node)
351 goto cleanup;
352 xmlSetProp(node, BAD_CAST "value",
353 BAD_CAST "http://schemas.google.com/g/2005#event.opaque");
354 xmlAddChild(root, node);
356 /* event status */
357 node = xmlNewNode(ns, "eventStatus");
358 if (!node)
359 goto cleanup;
360 xmlSetProp(node, BAD_CAST "value",
361 BAD_CAST "http://schemas.google.com/g/2005#event.confirmed");
362 xmlAddChild(root, node);
365 /* where */
366 if (entry->where) {
367 node = xmlNewNode(ns, "where");
368 if (!node)
369 goto cleanup;
370 xmlSetProp(node, BAD_CAST "valueString", BAD_CAST entry->where);
371 xmlAddChild(root, node);
374 /* when */
375 node = xmlNewNode(ns, "when");
376 if (!node)
377 goto cleanup;
378 if (entry->dt_start)
379 xmlSetProp(node, BAD_CAST "startTime",
380 BAD_CAST entry->dt_start);
381 if (entry->dt_end)
382 xmlSetProp(node, BAD_CAST "endTime", BAD_CAST entry->dt_end);
383 xmlAddChild(root, node);
386 xmlDocDumpMemory(doc, &xml_str, length);
387 /* xmlDocDumpMemory doesn't include the last 0 in the returned size */
388 ++(*length);
389 if (xml_str)
390 if ((*xml_entry = strdup(xml_str)))
391 result = 0;
393 cleanup:
395 if (xml_str)
396 xmlFree(xml_str);
398 if (doc)
399 xmlFreeDoc(doc);
401 exit:
403 return result;
407 int extract_all_contacts(dom_document *doc,
408 struct gcal_contact *data_extract, int length)
411 /* The logic of this function is the same of 'extract_all_entries'
412 * but I can't find a way to share code without having a common
413 * type for contact/calendar and registering a callback which
414 * would accept both types as a valid parameter and parse the
415 * DOM outputing a vector of contacts/entries.
417 int result = -1, i;
418 xmlXPathObject *xpath_obj = NULL;
419 xmlNodeSet *nodes;
421 /* get the contact node list */
422 xpath_obj = atom_get_entries(doc);
423 if (!xpath_obj)
424 goto exit;
425 nodes = xpath_obj->nodesetval;
426 if (!nodes)
427 goto exit;
429 if (length != nodes->nodeNr) {
430 /* FIXME: don't print to terminal! */
431 fprintf(stderr, "extract_all_contacts: Size mismatch!\n");
432 goto cleanup;
435 /* extract the fields */
436 for (i = 0; i < length; ++i) {
437 result = atom_extract_contact(nodes->nodeTab[i],
438 &data_extract[i]);
440 if (result == -1)
441 goto cleanup;
444 result = 0;
446 cleanup:
447 xmlXPathFreeObject(xpath_obj);
449 exit:
450 return result;
453 int xmlcontact_create(struct gcal_contact *contact, char **xml_contact,
454 int *length)
456 /* XXX: this function is pretty much a copy of 'xmlentry_create'
457 * some code could be shared if I provided a common type between
458 * contact X calendar.
460 int result = -1;
461 int i;
462 struct gcal_structured_subvalues *this_structured_entry;
463 int set_structured_entry = 0;
464 xmlDoc *doc = NULL;
465 xmlNode *root = NULL;
466 xmlNode *node = NULL;
467 xmlNode *node2 = NULL;
468 xmlNode *child = NULL;
469 xmlNs *ns;
470 xmlNs *ns2;
471 xmlChar *xml_str = NULL;
472 char *temp;
473 const char * rel_prefix = "http://schemas.google.com/g/2005#";
475 doc = xmlNewDoc(BAD_CAST "1.0");
476 root = xmlNewNode(NULL, BAD_CAST "atom:entry");
478 if (!doc || !root)
479 goto exit;
481 xmlSetProp(root, BAD_CAST "xmlns:atom", BAD_CAST atom_href);
482 /* Google Data API 2.0 requires ETag to edit an entry */
483 if (contact->common.etag)
484 xmlSetProp(root, BAD_CAST "gd:etag",
485 BAD_CAST contact->common.etag);
487 ns = xmlNewNs(root, BAD_CAST gd_href, BAD_CAST "gd");
489 /* Google contact group */
490 ns2 = xmlNewNs(root, BAD_CAST gContact_href, BAD_CAST "gContact");
492 xmlDocSetRootElement(doc, root);
494 /* category element */
495 node = xmlNewNode(NULL, "category");
496 if (!node)
497 goto cleanup;
498 xmlSetProp(node, BAD_CAST "scheme", BAD_CAST scheme_href);
499 xmlSetProp(node, BAD_CAST "term", BAD_CAST term_href_cont);
500 xmlAddChild(root, node);
502 /* entry ID, only if the 'contact' is already existant (i.e. the user
503 * of library just got one contact result from a request from
504 * server).
506 if (contact->common.id) {
507 node = xmlNewNode(NULL, "id");
508 if (!node)
509 goto cleanup;
510 xmlNodeAddContent(node, contact->common.id);
511 xmlAddChild(root, node);
514 /* Sets contact structured name (Google API 3.0) */
515 if (contact->structured_name_nr) {
516 set_structured_entry = 0;
517 for (this_structured_entry = contact->structured_name;
518 this_structured_entry != NULL;
519 this_structured_entry = this_structured_entry->next_field) {
520 if ((this_structured_entry->field_value != NULL)) {
521 if( !set_structured_entry ) {
522 if (!(node = xmlNewNode(ns, "name")))
523 goto cleanup;
524 set_structured_entry = 1;
527 if (!(child = xmlNewNode(ns, BAD_CAST this_structured_entry->field_key)))
528 goto cleanup;
529 xmlNodeAddContent(child, BAD_CAST this_structured_entry->field_value);
530 xmlAddChild(node, child);
534 if( set_structured_entry )
535 xmlAddChild(root, node);
536 } else if (contact->common.title) {
537 node = xmlNewNode(NULL, "gd:name");
538 if (!node)
539 goto cleanup;
540 node2 = xmlNewNode(NULL, "gd:fullName");
541 xmlNodeAddContent(node2, contact->common.title);
542 xmlAddChild(node, node2);
543 xmlAddChild(root, node);
546 /* entry edit URL, only if the 'entry' is already existant.
548 if (contact->common.edit_uri) {
549 node = xmlNewNode(NULL, "link");
550 if (!node)
551 goto cleanup;
552 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "edit");
553 xmlSetProp(node, BAD_CAST "type",
554 BAD_CAST "application/atom+xml");
555 xmlSetProp(node, BAD_CAST "href",
556 BAD_CAST contact->common.edit_uri);
557 xmlAddChild(root, node);
561 /* email addresses */
562 if (contact->emails_nr > 0) {
563 for (i = 0; i < contact->emails_nr; i++) {
564 if (!(node = xmlNewNode(ns, "email")))
565 goto cleanup;
566 temp = (char *)malloc((strlen(contact->emails_type[i])+strlen(rel_prefix)+1) * sizeof(char));
567 strcpy(temp, rel_prefix);
568 strcat(temp, contact->emails_type[i]);
569 xmlSetProp(node, BAD_CAST "rel",
570 BAD_CAST temp);
571 xmlSetProp(node, BAD_CAST "address",
572 BAD_CAST contact->emails_field[i]);
573 if (i == contact->pref_email)
574 xmlSetProp(node, BAD_CAST "primary",
575 BAD_CAST "true");
576 xmlAddChild(root, node);
577 free(temp);
581 /* Here begin extra fields */
582 if (contact->content) {
583 node = xmlNewNode(NULL, "atom:content");
584 if (!node)
585 goto cleanup;
586 xmlSetProp(node, BAD_CAST "type", BAD_CAST "text");
587 xmlNodeAddContent(node, contact->content);
588 xmlAddChild(root, node);
591 if (contact->nickname) {
592 node = xmlNewNode(NULL, "gContact:nickname");
593 if (!node)
594 goto cleanup;
595 xmlNodeAddContent(node, contact->nickname);
596 xmlAddChild(root, node);
599 if (contact->homepage) {
600 if (!(node = xmlNewNode(NULL, "gContact:website")))
601 goto cleanup;
602 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "home-page");
603 xmlSetProp(node, BAD_CAST "href", BAD_CAST contact->homepage);
604 xmlAddChild(root, node);
607 if (contact->blog) {
608 if (!(node = xmlNewNode(NULL, "gContact:website")))
609 goto cleanup;
610 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "blog");
611 xmlSetProp(node, BAD_CAST "href", BAD_CAST contact->blog);
612 xmlAddChild(root, node);
615 /* organization (it has 2 subelements: orgName, orgTitle) */
616 if (contact->org_name || contact->org_title) {
617 if (!(node = xmlNewNode(ns, "organization")))
618 goto cleanup;
619 xmlSetProp(node, BAD_CAST "rel",
620 BAD_CAST "http://schemas.google.com/g/2005#other");
622 if (contact->org_name) {
623 if (!(child = xmlNewNode(ns, "orgName")))
624 goto cleanup;
625 xmlNodeAddContent(child, contact->org_name);
626 xmlAddChild(node, child);
630 if (contact->org_title) {
631 if (!(child = xmlNewNode(ns, "orgTitle")))
632 goto cleanup;
633 xmlNodeAddContent(child, contact->org_title);
634 xmlAddChild(node, child);
637 xmlAddChild(root, node);
640 if (contact->occupation) {
641 node = xmlNewNode(NULL, "gContact:occupation");
642 if (!node)
643 goto cleanup;
644 xmlNodeAddContent(node, contact->occupation);
645 xmlAddChild(root, node);
648 /* Get phone numbers */
649 if (contact->phone_numbers_nr > 0) {
650 for (i = 0; i < contact->phone_numbers_nr; i++) {
651 if (!(node = xmlNewNode(ns, "phoneNumber")))
652 goto cleanup;
653 /* TODO: support user setting phone type */
655 temp = (char *)malloc((strlen(contact->phone_numbers_type[i])+strlen(rel_prefix)+1) * sizeof(char));
656 strcpy(temp, rel_prefix);
657 strcat(temp, contact->phone_numbers_type[i]);
658 xmlSetProp(node, BAD_CAST "rel",
659 BAD_CAST temp);
661 xmlNodeAddContent(node, contact->phone_numbers_field[i]);
662 xmlAddChild(root, node);
663 free(temp);
667 /* im addresses */
668 if (contact->im_nr > 0) {
669 for (i = 0; i < contact->im_nr; i++) {
670 if (!(node = xmlNewNode(ns, "im")))
671 goto cleanup;
672 temp = (char *)malloc((strlen(contact->im_type[i])+strlen(rel_prefix)+1) * sizeof(char));
673 strcpy(temp, rel_prefix);
674 strcat(temp, contact->im_type[i]);
675 xmlSetProp(node, BAD_CAST "rel",
676 BAD_CAST temp);
677 temp = (char *)malloc((strlen(contact->im_protocol[i])+strlen(rel_prefix)+1) * sizeof(char));
678 strcpy(temp, rel_prefix);
679 strcat(temp, contact->im_protocol[i]);
680 xmlSetProp(node, BAD_CAST "protocol",
681 BAD_CAST temp);
682 xmlSetProp(node, BAD_CAST "address",
683 BAD_CAST contact->im_address[i]);
684 if (i == contact->im_pref)
685 xmlSetProp(node, BAD_CAST "primary",
686 BAD_CAST "true");
687 xmlAddChild(root, node);
688 free(temp);
692 /* Sets contact structured postal addressees (Google API 3.0) */
693 /* TODO: move this to another function (identation is looking bad) */
694 if (contact->structured_address_nr > 0) {
695 for (i = 0; i < contact->structured_address_nr; i++) {
696 set_structured_entry = 0;
697 for (this_structured_entry = contact->structured_address;
698 this_structured_entry != NULL;
699 this_structured_entry = this_structured_entry->next_field) {
700 if (this_structured_entry->field_value &&
701 this_structured_entry->field_key &&
702 (this_structured_entry->field_typenr == i)) {
703 if (!set_structured_entry) {
704 if (!(node = xmlNewNode(ns, "structuredPostalAddress")))
705 goto cleanup;
706 // TODO: support user settting address type
707 temp = (char *)malloc((strlen(contact->structured_address_type[i])+strlen(rel_prefix)+2) * sizeof(char));
708 strcpy(temp, rel_prefix);
709 strcat(temp, contact->structured_address_type[i]);
710 xmlSetProp(node, BAD_CAST "rel", BAD_CAST temp);
711 set_structured_entry = 1;
712 free(temp);
715 if (!(child = xmlNewNode(ns, BAD_CAST this_structured_entry->field_key)))
716 goto cleanup;
717 xmlNodeAddContent(child, BAD_CAST this_structured_entry->field_value);
718 if (i == contact->structured_address_pref)
719 xmlSetProp(node, BAD_CAST "primary",
720 BAD_CAST "true");
721 xmlAddChild(node, child);
725 if (set_structured_entry)
726 xmlAddChild(root, node);
728 } else if (contact->post_address) {
729 node = xmlNewNode(NULL, "gd:structuredPostalAddress");
730 if (!node)
731 goto cleanup;
732 node2 = xmlNewNode(NULL, "gd:formattedAddress");
733 xmlNodeAddContent(node2, contact->post_address);
734 xmlAddChild(node, node2);
735 xmlAddChild(root, node);
738 /* Google group membership info */
739 if (contact->groupMembership_nr > 0) {
740 for (i = 0; i < contact->groupMembership_nr; i++) {
741 if (!(node = xmlNewNode(ns2, "groupMembershipInfo")))
742 goto cleanup;
743 xmlSetProp(node, BAD_CAST "deleted",
744 BAD_CAST "false");
745 xmlSetProp(node, BAD_CAST "href",
746 BAD_CAST contact->groupMembership[i]);
747 xmlAddChild(root, node);
751 /* birthday */
752 if (contact->birthday) {
753 /*if (!(node = xmlNewNode(NULL, BAD_CAST "gContact:birthday")))
754 goto cleanup;
755 xmlSetProp(node, BAD_CAST "xmlns", BAD_CAST "http://schemas.google.com/contact/2008");*/
756 if (!(node = xmlNewNode(NULL, "gContact:birthday")))
757 goto cleanup;
758 xmlSetProp(node, BAD_CAST "when", BAD_CAST contact->birthday);
759 xmlAddChild(root, node);
762 /* TODO: implement missing fields (im, geo location, what else?)
765 xmlDocDumpMemory(doc, &xml_str, length);
766 /* xmlDocDumpMemory doesn't include the last 0 in the returned size */
767 ++(*length);
768 if (xml_str)
769 if ((*xml_contact = strdup(xml_str)))
770 result = 0;
771 cleanup:
773 if (xml_str)
774 xmlFree(xml_str);
776 if (doc)
777 xmlFreeDoc(doc);
779 exit:
781 return result;