2 Copyright (c) 2008 Instituto Nokia de Tecnologia
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.
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"
45 #include <libxml/tree.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
55 /** libxml DOM document structure pointer */
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
;
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");
79 result
= get(cur_node
->children
);
89 int get_the_url(char *data
, int length
, char **url
)
92 xmlNode
*root_element
= NULL
;
96 doc
= xmlReadMemory(data
, length
, "noname.xml", NULL
, 0);
100 root_element
= xmlDocGetRootElement(doc
);
101 *url
= get(root_element
);
113 static char *get_edit(xmlNode
*a_node
)
115 xmlNode
*cur_node
= 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");
123 if (!strcmp(attr
, "edit")) {
124 uri
= xmlGetProp(cur_node
, "href");
126 result
= strdup(uri
);
137 result
= get_edit(cur_node
->children
);
146 int get_edit_url(char *data
, int length
, char **url
)
149 xmlNode
*root_element
= NULL
;
153 doc
= xmlReadMemory(data
, length
, "noname.xml", NULL
, 0);
157 root_element
= xmlDocGetRootElement(doc
);
158 *url
= get_edit(root_element
);
169 int get_edit_etag(char *data
, int length
, char **url
)
172 xmlNode
*root_element
= NULL
;
176 doc
= xmlReadMemory(data
, length
, "noname.xml", NULL
, 0);
180 root_element
= xmlDocGetRootElement(doc
);
181 *url
= get_etag_attribute(root_element
);
193 dom_document
*build_dom_document(char *xml_data
)
195 dom_document
*ptr
= NULL
;
199 if (build_doc_tree(&ptr
, xml_data
)) {
200 fprintf(stderr
, "build_dom_document: failed doc parse");
215 void clean_dom_document(dom_document
*doc
)
218 clean_doc_tree(&doc
);
222 int get_entries_number(dom_document
*doc
)
226 fprintf(stderr
, "get_entries_number: null document!");
230 result
= atom_entries(doc
);
235 int extract_all_entries(dom_document
*doc
,
236 struct gcal_event
*data_extract
, int length
)
240 xmlXPathObject
*xpath_obj
= NULL
;
243 /* get the entry node list */
244 xpath_obj
= atom_get_entries(doc
);
247 nodes
= xpath_obj
->nodesetval
;
251 if (length
!= nodes
->nodeNr
) {
252 fprintf(stderr
, "extract_all_entries: Size mismatch!");
256 /* extract the fields */
257 for (i
= 0; i
< length
; ++i
) {
258 result
= atom_extract_data(nodes
->nodeTab
[i
], &data_extract
[i
]);
266 xmlXPathFreeObject(xpath_obj
);
272 int xmlentry_create(struct gcal_event
*entry
, char **xml_entry
, int *length
)
276 xmlNode
*root
, *node
;
278 xmlChar
*xml_str
= NULL
;
280 doc
= xmlNewDoc(BAD_CAST
"1.0");
281 root
= xmlNewNode(NULL
, BAD_CAST
"entry");
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
300 if (entry
->common
.id
) {
301 node
= xmlNewNode(NULL
, "id");
304 xmlNodeAddContent(node
, entry
->common
.id
);
305 xmlAddChild(root
, node
);
308 /* category element */
309 node
= xmlNewNode(NULL
, "category");
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
);
317 node
= xmlNewNode(NULL
, "title");
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");
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");
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
);
349 node
= xmlNewNode(ns
, "transparency");
352 xmlSetProp(node
, BAD_CAST
"value",
353 BAD_CAST
"http://schemas.google.com/g/2005#event.opaque");
354 xmlAddChild(root
, node
);
357 node
= xmlNewNode(ns
, "eventStatus");
360 xmlSetProp(node
, BAD_CAST
"value",
361 BAD_CAST
"http://schemas.google.com/g/2005#event.confirmed");
362 xmlAddChild(root
, node
);
367 node
= xmlNewNode(ns
, "where");
370 xmlSetProp(node
, BAD_CAST
"valueString", BAD_CAST entry
->where
);
371 xmlAddChild(root
, node
);
375 node
= xmlNewNode(ns
, "when");
379 xmlSetProp(node
, BAD_CAST
"startTime",
380 BAD_CAST entry
->dt_start
);
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 */
390 if ((*xml_entry
= strdup(xml_str
)))
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.
418 xmlXPathObject
*xpath_obj
= NULL
;
421 /* get the contact node list */
422 xpath_obj
= atom_get_entries(doc
);
425 nodes
= xpath_obj
->nodesetval
;
429 if (length
!= nodes
->nodeNr
) {
430 /* FIXME: don't print to terminal! */
431 fprintf(stderr
, "extract_all_contacts: Size mismatch!\n");
435 /* extract the fields */
436 for (i
= 0; i
< length
; ++i
) {
437 result
= atom_extract_contact(nodes
->nodeTab
[i
],
447 xmlXPathFreeObject(xpath_obj
);
453 int xmlcontact_create(struct gcal_contact
*contact
, char **xml_contact
,
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.
462 struct gcal_structured_subvalues
*this_structured_entry
;
463 int set_structured_entry
= 0;
465 xmlNode
*root
= NULL
;
466 xmlNode
*node
= NULL
;
467 xmlNode
*node2
= NULL
;
468 xmlNode
*child
= NULL
;
471 xmlChar
*xml_str
= NULL
;
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");
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");
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
506 if (contact
->common
.id
) {
507 node
= xmlNewNode(NULL
, "id");
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")))
524 set_structured_entry
= 1;
527 if (!(child
= xmlNewNode(ns
, BAD_CAST this_structured_entry
->field_key
)))
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");
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");
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")))
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",
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",
575 xmlAddChild(root
, node
);
580 /* Here begin extra fields */
581 if (contact
->content
) {
582 node
= xmlNewNode(NULL
, "atom:content");
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");
594 xmlNodeAddContent(node
, contact
->nickname
);
595 xmlAddChild(root
, node
);
598 if (contact
->homepage
) {
599 if (!(node
= xmlNewNode(NULL
, "gContact:website")))
601 xmlSetProp(node
, BAD_CAST
"rel", BAD_CAST
"home-page");
602 xmlSetProp(node
, BAD_CAST
"href", BAD_CAST contact
->homepage
);
603 xmlAddChild(root
, node
);
607 if (!(node
= xmlNewNode(NULL
, "gContact:website")))
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")))
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")))
624 xmlNodeAddContent(child
, contact
->org_name
);
625 xmlAddChild(node
, child
);
629 if (contact
->org_title
) {
630 if (!(child
= xmlNewNode(ns
, "orgTitle")))
632 xmlNodeAddContent(child
, contact
->org_title
);
633 xmlAddChild(node
, child
);
636 xmlAddChild(root
, node
);
639 if (contact
->occupation
) {
640 node
= xmlNewNode(NULL
, "gContact:occupation");
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")))
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",
660 xmlNodeAddContent(node
, contact
->phone_numbers_field
[i
]);
661 xmlAddChild(root
, node
);
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")))
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;
689 if (!(child
= xmlNewNode(ns
, BAD_CAST this_structured_entry
->field_key
)))
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");
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")))
714 xmlSetProp(node
, BAD_CAST
"deleted",
716 xmlSetProp(node
, BAD_CAST
"href",
717 BAD_CAST contact
->groupMembership
[i
]);
718 xmlAddChild(root
, node
);
723 if (contact
->birthday
) {
724 /*if (!(node = xmlNewNode(NULL, BAD_CAST "gContact:birthday")))
726 xmlSetProp(node, BAD_CAST "xmlns", BAD_CAST "http://schemas.google.com/contact/2008");*/
727 if (!(node
= xmlNewNode(NULL
, "gContact:birthday")))
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 */
740 if ((*xml_contact
= strdup(xml_str
)))