444 lines
10 KiB
C
444 lines
10 KiB
C
/*****************************************************************************
|
|
* comment - A library for creating Excel XLSX comment files.
|
|
*
|
|
* Used in conjunction with the libxlsxwriter library.
|
|
*
|
|
* Copyright 2014-2021, John McNamara, jmcnamara@cpan.org. See LICENSE.txt.
|
|
*
|
|
*/
|
|
|
|
#include "xlsxwriter/xmlwriter.h"
|
|
#include "xlsxwriter/comment.h"
|
|
#include "xlsxwriter/utility.h"
|
|
|
|
/*
|
|
* Forward declarations.
|
|
*/
|
|
|
|
STATIC int _author_id_cmp(lxw_author_id *tuple1, lxw_author_id *tuple2);
|
|
|
|
#ifndef __clang_analyzer__
|
|
LXW_RB_GENERATE_AUTHOR_IDS(lxw_author_ids, lxw_author_id,
|
|
tree_pointers, _author_id_cmp);
|
|
#endif
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Private functions.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/*
|
|
* Comparator for the author ids.
|
|
*/
|
|
STATIC int
|
|
_author_id_cmp(lxw_author_id *author_id1, lxw_author_id *author_id2)
|
|
{
|
|
return strcmp(author_id1->author, author_id2->author);
|
|
}
|
|
|
|
/*
|
|
* Check if an author already existing in the author/id table.
|
|
*/
|
|
STATIC uint8_t
|
|
_check_author(lxw_comment *self, char *author)
|
|
{
|
|
lxw_author_id tmp_author_id;
|
|
lxw_author_id *existing_author = NULL;
|
|
|
|
if (!author)
|
|
return LXW_TRUE;
|
|
|
|
tmp_author_id.author = author;
|
|
existing_author = RB_FIND(lxw_author_ids,
|
|
self->author_ids, &tmp_author_id);
|
|
|
|
if (existing_author)
|
|
return LXW_TRUE;
|
|
else
|
|
return LXW_FALSE;
|
|
}
|
|
|
|
/*
|
|
* Get the index used for an author name.
|
|
*/
|
|
STATIC uint32_t
|
|
_get_author_index(lxw_comment *self, char *author)
|
|
{
|
|
lxw_author_id tmp_author_id;
|
|
lxw_author_id *existing_author = NULL;
|
|
lxw_author_id *new_author_id = NULL;
|
|
|
|
if (!author)
|
|
return 0;
|
|
|
|
tmp_author_id.author = author;
|
|
existing_author = RB_FIND(lxw_author_ids,
|
|
self->author_ids, &tmp_author_id);
|
|
|
|
if (existing_author) {
|
|
return existing_author->id;
|
|
}
|
|
else {
|
|
new_author_id = calloc(1, sizeof(lxw_author_id));
|
|
|
|
if (new_author_id) {
|
|
new_author_id->id = self->author_id;
|
|
new_author_id->author = lxw_strdup(author);
|
|
self->author_id++;
|
|
|
|
RB_INSERT(lxw_author_ids, self->author_ids, new_author_id);
|
|
|
|
return new_author_id->id;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a new comment object.
|
|
*/
|
|
lxw_comment *
|
|
lxw_comment_new(void)
|
|
{
|
|
lxw_comment *comment = calloc(1, sizeof(lxw_comment));
|
|
GOTO_LABEL_ON_MEM_ERROR(comment, mem_error);
|
|
|
|
comment->author_ids = calloc(1, sizeof(struct lxw_author_ids));
|
|
GOTO_LABEL_ON_MEM_ERROR(comment->author_ids, mem_error);
|
|
RB_INIT(comment->author_ids);
|
|
|
|
return comment;
|
|
|
|
mem_error:
|
|
lxw_comment_free(comment);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Free a comment object.
|
|
*/
|
|
void
|
|
lxw_comment_free(lxw_comment *comment)
|
|
{
|
|
struct lxw_author_id *author_id;
|
|
struct lxw_author_id *next_author_id;
|
|
|
|
if (!comment)
|
|
return;
|
|
|
|
if (comment->author_ids) {
|
|
for (author_id =
|
|
RB_MIN(lxw_author_ids, comment->author_ids);
|
|
author_id; author_id = next_author_id) {
|
|
|
|
next_author_id =
|
|
RB_NEXT(lxw_author_ids, worksheet->author_id, author_id);
|
|
RB_REMOVE(lxw_author_ids, comment->author_ids, author_id);
|
|
free(author_id->author);
|
|
free(author_id);
|
|
}
|
|
|
|
free(comment->author_ids);
|
|
}
|
|
|
|
free(comment);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* XML functions.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/*
|
|
* Write the XML declaration.
|
|
*/
|
|
STATIC void
|
|
_comment_xml_declaration(lxw_comment *self)
|
|
{
|
|
lxw_xml_declaration(self->file);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* XML file assembly functions.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/*
|
|
* Write the <t> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_text_t(lxw_comment *self, lxw_vml_obj *comment_obj)
|
|
{
|
|
lxw_xml_data_element(self->file, "t", comment_obj->text, NULL);
|
|
}
|
|
|
|
/*
|
|
* Write the <family> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_family(lxw_comment *self, lxw_vml_obj *comment_obj)
|
|
{
|
|
struct xml_attribute_list attributes;
|
|
struct xml_attribute *attribute;
|
|
|
|
LXW_INIT_ATTRIBUTES();
|
|
LXW_PUSH_ATTRIBUTES_INT("val", comment_obj->font_family);
|
|
|
|
lxw_xml_empty_tag(self->file, "family", &attributes);
|
|
|
|
LXW_FREE_ATTRIBUTES();
|
|
}
|
|
|
|
/*
|
|
* Write the <rFont> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_r_font(lxw_comment *self, lxw_vml_obj *comment_obj)
|
|
{
|
|
struct xml_attribute_list attributes;
|
|
struct xml_attribute *attribute;
|
|
char font_name[LXW_ATTR_32];
|
|
|
|
if (comment_obj->font_name)
|
|
lxw_snprintf(font_name, LXW_ATTR_32, "%s", comment_obj->font_name);
|
|
else
|
|
lxw_snprintf(font_name, LXW_ATTR_32, "Tahoma");
|
|
|
|
LXW_INIT_ATTRIBUTES();
|
|
LXW_PUSH_ATTRIBUTES_STR("val", font_name);
|
|
|
|
lxw_xml_empty_tag(self->file, "rFont", &attributes);
|
|
|
|
LXW_FREE_ATTRIBUTES();
|
|
}
|
|
|
|
/*
|
|
* Write the <color> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_color(lxw_comment *self)
|
|
{
|
|
struct xml_attribute_list attributes;
|
|
struct xml_attribute *attribute;
|
|
char indexed[] = "81";
|
|
|
|
LXW_INIT_ATTRIBUTES();
|
|
LXW_PUSH_ATTRIBUTES_STR("indexed", indexed);
|
|
|
|
lxw_xml_empty_tag(self->file, "color", &attributes);
|
|
|
|
LXW_FREE_ATTRIBUTES();
|
|
}
|
|
|
|
/*
|
|
* Write the <sz> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_sz(lxw_comment *self, lxw_vml_obj *comment_obj)
|
|
{
|
|
struct xml_attribute_list attributes;
|
|
struct xml_attribute *attribute;
|
|
|
|
LXW_INIT_ATTRIBUTES();
|
|
LXW_PUSH_ATTRIBUTES_DBL("val", comment_obj->font_size);
|
|
|
|
lxw_xml_empty_tag(self->file, "sz", &attributes);
|
|
|
|
LXW_FREE_ATTRIBUTES();
|
|
}
|
|
|
|
/*
|
|
* Write the <rPr> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_r_pr(lxw_comment *self, lxw_vml_obj *comment_obj)
|
|
{
|
|
lxw_xml_start_tag(self->file, "rPr", NULL);
|
|
|
|
/* Write the sz element. */
|
|
_comment_write_sz(self, comment_obj);
|
|
|
|
/* Write the color element. */
|
|
_comment_write_color(self);
|
|
|
|
/* Write the rFont element. */
|
|
_comment_write_r_font(self, comment_obj);
|
|
|
|
/* Write the family element. */
|
|
_comment_write_family(self, comment_obj);
|
|
|
|
lxw_xml_end_tag(self->file, "rPr");
|
|
}
|
|
|
|
/*
|
|
* Write the <r> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_r(lxw_comment *self, lxw_vml_obj *comment_obj)
|
|
{
|
|
lxw_xml_start_tag(self->file, "r", NULL);
|
|
|
|
/* Write the rPr element. */
|
|
_comment_write_r_pr(self, comment_obj);
|
|
|
|
/* Write the t element. */
|
|
_comment_write_text_t(self, comment_obj);
|
|
|
|
lxw_xml_end_tag(self->file, "r");
|
|
}
|
|
|
|
/*
|
|
* Write the <text> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_text(lxw_comment *self, lxw_vml_obj *comment_obj)
|
|
{
|
|
lxw_xml_start_tag(self->file, "text", NULL);
|
|
|
|
/* Write the r element. */
|
|
_comment_write_r(self, comment_obj);
|
|
|
|
lxw_xml_end_tag(self->file, "text");
|
|
}
|
|
|
|
/*
|
|
* Write the <comment> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_comment(lxw_comment *self, lxw_vml_obj *comment_obj)
|
|
{
|
|
struct xml_attribute_list attributes;
|
|
struct xml_attribute *attribute;
|
|
char ref[LXW_MAX_CELL_NAME_LENGTH];
|
|
|
|
lxw_rowcol_to_cell(ref, comment_obj->row, comment_obj->col);
|
|
|
|
LXW_INIT_ATTRIBUTES();
|
|
LXW_PUSH_ATTRIBUTES_STR("ref", ref);
|
|
LXW_PUSH_ATTRIBUTES_INT("authorId", comment_obj->author_id);
|
|
|
|
lxw_xml_start_tag(self->file, "comment", &attributes);
|
|
|
|
/* Write the text element. */
|
|
_comment_write_text(self, comment_obj);
|
|
|
|
lxw_xml_end_tag(self->file, "comment");
|
|
|
|
LXW_FREE_ATTRIBUTES();
|
|
}
|
|
|
|
/*
|
|
* Write the <commentList> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_comment_list(lxw_comment *self)
|
|
{
|
|
lxw_vml_obj *comment_obj;
|
|
|
|
lxw_xml_start_tag(self->file, "commentList", NULL);
|
|
|
|
STAILQ_FOREACH(comment_obj, self->comment_objs, list_pointers) {
|
|
/* Write the comment element. */
|
|
_comment_write_comment(self, comment_obj);
|
|
}
|
|
|
|
lxw_xml_end_tag(self->file, "commentList");
|
|
|
|
}
|
|
|
|
/*
|
|
* Write the <author> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_author(lxw_comment *self, char *author)
|
|
{
|
|
lxw_xml_data_element(self->file, "author", author, NULL);
|
|
}
|
|
|
|
/*
|
|
* Write the <authors> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_authors(lxw_comment *self)
|
|
{
|
|
lxw_vml_obj *comment_obj;
|
|
char *author;
|
|
|
|
lxw_xml_start_tag(self->file, "authors", NULL);
|
|
|
|
/* Set the default author (from worksheet_set_comments_author()). */
|
|
if (self->comment_author) {
|
|
_get_author_index(self, self->comment_author);
|
|
_comment_write_author(self, self->comment_author);
|
|
}
|
|
else {
|
|
_get_author_index(self, "");
|
|
_comment_write_author(self, "");
|
|
}
|
|
|
|
STAILQ_FOREACH(comment_obj, self->comment_objs, list_pointers) {
|
|
author = comment_obj->author;
|
|
|
|
if (author) {
|
|
|
|
if (!_check_author(self, author))
|
|
_comment_write_author(self, author);
|
|
|
|
comment_obj->author_id = _get_author_index(self, author);
|
|
}
|
|
}
|
|
|
|
lxw_xml_end_tag(self->file, "authors");
|
|
}
|
|
|
|
/*
|
|
* Write the <comments> element.
|
|
*/
|
|
STATIC void
|
|
_comment_write_comments(lxw_comment *self)
|
|
{
|
|
struct xml_attribute_list attributes;
|
|
struct xml_attribute *attribute;
|
|
char xmlns[] =
|
|
"http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
|
|
LXW_INIT_ATTRIBUTES();
|
|
LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
|
|
|
|
lxw_xml_start_tag(self->file, "comments", &attributes);
|
|
|
|
LXW_FREE_ATTRIBUTES();
|
|
}
|
|
|
|
/*
|
|
* Assemble and write the XML file.
|
|
*/
|
|
void
|
|
lxw_comment_assemble_xml_file(lxw_comment *self)
|
|
{
|
|
/* Write the XML declaration. */
|
|
_comment_xml_declaration(self);
|
|
|
|
/* Write the comments element. */
|
|
_comment_write_comments(self);
|
|
|
|
/* Write the authors element. */
|
|
_comment_write_authors(self);
|
|
|
|
/* Write the commentList element. */
|
|
_comment_write_comment_list(self);
|
|
|
|
lxw_xml_end_tag(self->file, "comments");
|
|
}
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Public functions.
|
|
*
|
|
****************************************************************************/
|