Coding style, un-uncommenting unit tests, some janitoring.
[libgcal.git] / src / gcal_parser.c
blobf2b9d192d4399645b911637b8b9b0360b61e1d92
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 {
537 /* title element */
538 node = xmlNewNode(NULL, "gd:name");
539 if (!node)
540 goto cleanup;
541 node2 = xmlNewNode(NULL, "gd:givenName");
542 xmlNodeAddContent(node2, contact->common.title);
543 xmlAddChild(node, node2);
544 xmlAddChild(root, node);
547 /* entry edit URL, only if the 'entry' is already existant.
549 if (contact->common.edit_uri) {
550 node = xmlNewNode(NULL, "link");
551 if (!node)
552 goto cleanup;
553 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "edit");
554 xmlSetProp(node, BAD_CAST "type",
555 BAD_CAST "application/atom+xml");
556 xmlSetProp(node, BAD_CAST "href",
557 BAD_CAST contact->common.edit_uri);
558 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 /* content element */
583 if (contact->content) {
584 node = xmlNewNode(NULL, "atom:content");
585 if (!node)
586 goto cleanup;
587 xmlSetProp(node, BAD_CAST "type", BAD_CAST "text");
588 xmlNodeAddContent(node, contact->content);
589 xmlAddChild(root, node);
592 if (contact->nickname) {
593 node = xmlNewNode(NULL, "gContact:nickname");
594 if (!node)
595 goto cleanup;
596 xmlNodeAddContent(node, contact->nickname);
597 xmlAddChild(root, node);
600 if (contact->homepage) {
601 if (!(node = xmlNewNode(NULL, "gContact:website")))
602 goto cleanup;
603 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "home-page");
604 xmlSetProp(node, BAD_CAST "href", BAD_CAST contact->homepage);
605 xmlAddChild(root, node);
608 if (contact->blog) {
609 if (!(node = xmlNewNode(NULL, "gContact:website")))
610 goto cleanup;
611 xmlSetProp(node, BAD_CAST "rel", BAD_CAST "blog");
612 xmlSetProp(node, BAD_CAST "href", BAD_CAST contact->blog);
613 xmlAddChild(root, node);
616 /* organization (it has 2 subelements: orgName, orgTitle) */
617 if (contact->org_name || contact->org_title) {
618 if (!(node = xmlNewNode(ns, "organization")))
619 goto cleanup;
620 xmlSetProp(node, BAD_CAST "rel",
621 BAD_CAST "http://schemas.google.com/g/2005#other");
623 if (contact->org_name) {
624 if (!(child = xmlNewNode(ns, "orgName")))
625 goto cleanup;
626 xmlNodeAddContent(child, contact->org_name);
627 xmlAddChild(node, child);
631 if (contact->org_title) {
632 if (!(child = xmlNewNode(ns, "orgTitle")))
633 goto cleanup;
634 xmlNodeAddContent(child, contact->org_title);
635 xmlAddChild(node, child);
638 xmlAddChild(root, node);
641 /* Get phone numbers */
642 if (contact->phone_numbers_nr > 0) {
643 for (i = 0; i < contact->phone_numbers_nr; i++) {
644 if (!(node = xmlNewNode(ns, "phoneNumber")))
645 goto cleanup;
646 /* TODO: support user setting phone type */
648 temp = (char *)malloc((strlen(contact->phone_numbers_type[i])+strlen(rel_prefix)+1) * sizeof(char));
649 strcpy(temp, rel_prefix);
650 strcat(temp, contact->phone_numbers_type[i]);
651 xmlSetProp(node, BAD_CAST "rel",
652 BAD_CAST temp);
654 xmlNodeAddContent(node, contact->phone_numbers_field[i]);
655 xmlAddChild(root, node);
656 free(temp);
660 /* Sets contact structured postal addressees (Google API 3.0) */
661 /* TODO: move this to another function (identation is looking bad) */
662 if (contact->structured_address_nr > 0) {
663 for (i = 0; i < contact->structured_address_nr; i++) {
664 set_structured_entry = 0;
665 for (this_structured_entry = contact->structured_address;
666 this_structured_entry != NULL;
667 this_structured_entry = this_structured_entry->next_field) {
668 if (this_structured_entry->field_value &&
669 this_structured_entry->field_key &&
670 (this_structured_entry->field_typenr == i)) {
671 if (!set_structured_entry) {
672 if (!(node = xmlNewNode(ns, "structuredPostalAddress")))
673 goto cleanup;
674 // TODO: support user settting address type
675 temp = (char *)malloc((strlen(contact->structured_address_type[i])+strlen(rel_prefix)+2) * sizeof(char));
676 strcpy(temp, rel_prefix);
677 strcat(temp, contact->structured_address_type[i]);
678 xmlSetProp(node, BAD_CAST "rel", BAD_CAST temp);
679 set_structured_entry = 1;
680 free(temp);
683 if (!(child = xmlNewNode(ns, BAD_CAST this_structured_entry->field_key)))
684 goto cleanup;
685 xmlNodeAddContent(child, BAD_CAST this_structured_entry->field_value);
686 xmlAddChild(node, child);
690 if (set_structured_entry)
691 xmlAddChild(root, node);
692 /* There can only be structuredPostalAddress OR postalAddress
693 * TODO: support user settting address type
696 else
698 if (contact->post_address) {
699 if (!(node = xmlNewNode(ns, "postalAddress")))
700 goto cleanup;
701 sprintf(temp,"http://schemas.google.com/g/2005#%s",contact->structured_address_type[i]);
702 xmlSetProp(node, BAD_CAST "rel", BAD_CAST temp);
703 xmlNodeAddContent(node, contact->post_address);
704 xmlAddChild(root, node);
705 free(temp);
712 /* Google group membership info */
713 if (contact->groupMembership_nr > 0) {
714 for (i = 0; i < contact->groupMembership_nr; i++) {
715 if (!(node = xmlNewNode(ns2, "groupMembershipInfo")))
716 goto cleanup;
717 xmlSetProp(node, BAD_CAST "deleted",
718 BAD_CAST "false");
719 xmlSetProp(node, BAD_CAST "href",
720 BAD_CAST contact->groupMembership[i]);
721 xmlAddChild(root, node);
725 /* birthday */
726 if (contact->birthday) {
727 /*if (!(node = xmlNewNode(NULL, BAD_CAST "gContact:birthday")))
728 goto cleanup;
729 xmlSetProp(node, BAD_CAST "xmlns", BAD_CAST "http://schemas.google.com/contact/2008");*/
730 if (!(node = xmlNewNode(NULL, "gContact:birthday")))
731 goto cleanup;
732 xmlSetProp(node, BAD_CAST "when", BAD_CAST contact->birthday);
733 xmlAddChild(root, node);
736 /* TODO: implement missing fields (im, what else?)
739 xmlDocDumpMemory(doc, &xml_str, length);
740 /* xmlDocDumpMemory doesn't include the last 0 in the returned size */
741 ++(*length);
742 if (xml_str)
743 if ((*xml_contact = strdup(xml_str)))
744 result = 0;
745 cleanup:
747 if (xml_str)
748 xmlFree(xml_str);
750 if (doc)
751 xmlFreeDoc(doc);
753 exit:
755 return result;