Some unit test regarding the new fields.
[libgcal.git] / src / gcal_parser.c
blob142fe1122d113e82d605310b8d6165fd869b841c
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);
560 /* email addresses */
561 if (contact->emails_nr > 0) {
562 for (i = 0; i < contact->emails_nr; i++) {
563 if (!(node = xmlNewNode(ns, "email")))
564 goto cleanup;
565 temp = (char *)malloc((strlen(contact->emails_type[i])+strlen(rel_prefix)+1) * sizeof(char));
566 strcpy(temp, rel_prefix);
567 strcat(temp, contact->emails_type[i]);
568 xmlSetProp(node, BAD_CAST "rel",
569 BAD_CAST temp);
570 xmlSetProp(node, BAD_CAST "address",
571 BAD_CAST contact->emails_field[i]);
572 if (i == contact->pref_email)
573 xmlSetProp(node, BAD_CAST "primary",
574 BAD_CAST "true");
575 xmlAddChild(root, node);
576 free(temp);
580 /* Here begin extra fields */
581 if (contact->content) {
582 node = xmlNewNode(NULL, "atom:content");
583 if (!node)
584 goto cleanup;
585 xmlSetProp(node, BAD_CAST "type", BAD_CAST "text");
586 xmlNodeAddContent(node, contact->content);
587 xmlAddChild(root, node);
590 if (contact->nickname) {
591 node = xmlNewNode(NULL, "gContact:nickname");
592 if (!node)
593 goto cleanup;
594 xmlNodeAddContent(node, contact->nickname);
595 xmlAddChild(root, node);
598 if (contact->homepage) {
599 if (!(node = xmlNewNode(NULL, "gContact:website")))
600 goto cleanup;
601 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "home-page");
602 xmlSetProp(node, BAD_CAST "href", BAD_CAST contact->homepage);
603 xmlAddChild(root, node);
606 if (contact->blog) {
607 if (!(node = xmlNewNode(NULL, "gContact:website")))
608 goto cleanup;
609 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "blog");
610 xmlSetProp(node, BAD_CAST "href", BAD_CAST contact->blog);
611 xmlAddChild(root, node);
614 /* organization (it has 2 subelements: orgName, orgTitle) */
615 if (contact->org_name || contact->org_title) {
616 if (!(node = xmlNewNode(ns, "organization")))
617 goto cleanup;
618 xmlSetProp(node, BAD_CAST "rel",
619 BAD_CAST "http://schemas.google.com/g/2005#other");
621 if (contact->org_name) {
622 if (!(child = xmlNewNode(ns, "orgName")))
623 goto cleanup;
624 xmlNodeAddContent(child, contact->org_name);
625 xmlAddChild(node, child);
629 if (contact->org_title) {
630 if (!(child = xmlNewNode(ns, "orgTitle")))
631 goto cleanup;
632 xmlNodeAddContent(child, contact->org_title);
633 xmlAddChild(node, child);
636 xmlAddChild(root, node);
639 if (contact->occupation) {
640 node = xmlNewNode(NULL, "gContact:occupation");
641 if (!node)
642 goto cleanup;
643 xmlNodeAddContent(node, contact->occupation);
644 xmlAddChild(root, node);
647 /* Get phone numbers */
648 if (contact->phone_numbers_nr > 0) {
649 for (i = 0; i < contact->phone_numbers_nr; i++) {
650 if (!(node = xmlNewNode(ns, "phoneNumber")))
651 goto cleanup;
652 /* TODO: support user setting phone type */
654 temp = (char *)malloc((strlen(contact->phone_numbers_type[i])+strlen(rel_prefix)+1) * sizeof(char));
655 strcpy(temp, rel_prefix);
656 strcat(temp, contact->phone_numbers_type[i]);
657 xmlSetProp(node, BAD_CAST "rel",
658 BAD_CAST temp);
660 xmlNodeAddContent(node, contact->phone_numbers_field[i]);
661 xmlAddChild(root, node);
662 free(temp);
666 /* Sets contact structured postal addressees (Google API 3.0) */
667 /* TODO: move this to another function (identation is looking bad) */
668 if (contact->structured_address_nr > 0) {
669 for (i = 0; i < contact->structured_address_nr; i++) {
670 set_structured_entry = 0;
671 for (this_structured_entry = contact->structured_address;
672 this_structured_entry != NULL;
673 this_structured_entry = this_structured_entry->next_field) {
674 if (this_structured_entry->field_value &&
675 this_structured_entry->field_key &&
676 (this_structured_entry->field_typenr == i)) {
677 if (!set_structured_entry) {
678 if (!(node = xmlNewNode(ns, "structuredPostalAddress")))
679 goto cleanup;
680 // TODO: support user settting address type
681 temp = (char *)malloc((strlen(contact->structured_address_type[i])+strlen(rel_prefix)+2) * sizeof(char));
682 strcpy(temp, rel_prefix);
683 strcat(temp, contact->structured_address_type[i]);
684 xmlSetProp(node, BAD_CAST "rel", BAD_CAST temp);
685 set_structured_entry = 1;
686 free(temp);
689 if (!(child = xmlNewNode(ns, BAD_CAST this_structured_entry->field_key)))
690 goto cleanup;
691 xmlNodeAddContent(child, BAD_CAST this_structured_entry->field_value);
692 xmlAddChild(node, child);
696 if (set_structured_entry)
697 xmlAddChild(root, node);
699 } else if (contact->post_address) {
700 node = xmlNewNode(NULL, "gd:structuredPostalAddress");
701 if (!node)
702 goto cleanup;
703 node2 = xmlNewNode(NULL, "gd:formattedAddress");
704 xmlNodeAddContent(node2, contact->post_address);
705 xmlAddChild(node, node2);
706 xmlAddChild(root, node);
709 /* Google group membership info */
710 if (contact->groupMembership_nr > 0) {
711 for (i = 0; i < contact->groupMembership_nr; i++) {
712 if (!(node = xmlNewNode(ns2, "groupMembershipInfo")))
713 goto cleanup;
714 xmlSetProp(node, BAD_CAST "deleted",
715 BAD_CAST "false");
716 xmlSetProp(node, BAD_CAST "href",
717 BAD_CAST contact->groupMembership[i]);
718 xmlAddChild(root, node);
722 /* birthday */
723 if (contact->birthday) {
724 /*if (!(node = xmlNewNode(NULL, BAD_CAST "gContact:birthday")))
725 goto cleanup;
726 xmlSetProp(node, BAD_CAST "xmlns", BAD_CAST "http://schemas.google.com/contact/2008");*/
727 if (!(node = xmlNewNode(NULL, "gContact:birthday")))
728 goto cleanup;
729 xmlSetProp(node, BAD_CAST "when", BAD_CAST contact->birthday);
730 xmlAddChild(root, node);
733 /* TODO: implement missing fields (im, geo location, what else?)
736 xmlDocDumpMemory(doc, &xml_str, length);
737 /* xmlDocDumpMemory doesn't include the last 0 in the returned size */
738 ++(*length);
739 if (xml_str)
740 if ((*xml_contact = strdup(xml_str)))
741 result = 0;
742 cleanup:
744 if (xml_str)
745 xmlFree(xml_str);
747 if (doc)
748 xmlFreeDoc(doc);
750 exit:
752 return result;