libxlsxwriter/src/packager.c in fast_excel-0.2.6 vs libxlsxwriter/src/packager.c in fast_excel-0.3.0

- old
+ new

@@ -1,11 +1,11 @@ /***************************************************************************** * packager - A library for creating Excel XLSX packager files. * * Used in conjunction with the libxlsxwriter library. * - * Copyright 2014-2018, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. * */ #include "xlsxwriter/xmlwriter.h" #include "xlsxwriter/packager.h" @@ -13,10 +13,13 @@ #include "xlsxwriter/utility.h" STATIC lxw_error _add_file_to_zip(lxw_packager *self, FILE * file, const char *filename); +STATIC lxw_error _add_buffer_to_zip(lxw_packager *self, unsigned char *buffer, + size_t buffer_size, const char *filename); + /* * Forward declarations. */ /***************************************************************************** @@ -71,11 +74,11 @@ /* * Create a new packager object. */ lxw_packager * -lxw_packager_new(const char *filename, char *tmpdir) +lxw_packager_new(const char *filename, char *tmpdir, uint8_t use_zip64) { lxw_packager *packager = calloc(1, sizeof(lxw_packager)); GOTO_LABEL_ON_MEM_ERROR(packager, mem_error); packager->buffer = calloc(1, LXW_ZIP_BUFFER_SIZE); @@ -84,10 +87,11 @@ packager->filename = lxw_strdup(filename); packager->tmpdir = tmpdir; GOTO_LABEL_ON_MEM_ERROR(packager->filename, mem_error); packager->buffer_size = LXW_ZIP_BUFFER_SIZE; + packager->use_zip64 = use_zip64; /* Initialize the zip_fileinfo struct to Jan 1 1980 like Excel. */ packager->zipfile_info.tmz_date.tm_sec = 0; packager->zipfile_info.tmz_date.tm_min = 0; packager->zipfile_info.tmz_date.tm_hour = 0; @@ -162,16 +166,22 @@ */ STATIC lxw_error _write_worksheet_files(lxw_packager *self) { lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; - uint16_t index = 1; + uint32_t index = 1; lxw_error err; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, "xl/worksheets/sheet%d.xml", index++); if (worksheet->optimize_row) lxw_worksheet_write_single_row(worksheet); @@ -190,63 +200,141 @@ return LXW_NO_ERROR; } /* + * Write the chartsheet files. + */ +STATIC lxw_error +_write_chartsheet_files(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_chartsheet *chartsheet; + char sheetname[LXW_FILENAME_LENGTH] = { 0 }; + uint32_t index = 1; + lxw_error err; + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + chartsheet = sheet->u.chartsheet; + else + continue; + + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, + "xl/chartsheets/sheet%d.xml", index++); + + chartsheet->file = lxw_tmpfile(self->tmpdir); + if (!chartsheet->file) + return LXW_ERROR_CREATING_TMPFILE; + + lxw_chartsheet_assemble_xml_file(chartsheet); + + err = _add_file_to_zip(self, chartsheet->file, sheetname); + RETURN_ON_ERROR(err); + + fclose(chartsheet->file); + } + + return LXW_NO_ERROR; +} + +/* * Write the /xl/media/image?.xml files. */ STATIC lxw_error _write_image_files(lxw_packager *self) { lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; lxw_image_options *image; lxw_error err; FILE *image_stream; char filename[LXW_FILENAME_LENGTH] = { 0 }; - uint16_t index = 1; + uint32_t index = 1; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; if (STAILQ_EMPTY(worksheet->image_data)) continue; STAILQ_FOREACH(image, worksheet->image_data, list_pointers) { lxw_snprintf(filename, LXW_FILENAME_LENGTH, "xl/media/image%d.%s", index++, image->extension); - /* Check that the image file exists and can be opened. */ - image_stream = fopen(image->filename, "rb"); - if (!image_stream) { - LXW_WARN_FORMAT1("Error adding image to xlsx file: file " - "doesn't exist or can't be opened: %s.", - image->filename); - return LXW_ERROR_CREATING_TMPFILE; + if (!image->is_image_buffer) { + /* Check that the image file exists and can be opened. */ + image_stream = fopen(image->filename, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("Error adding image to xlsx file: file " + "doesn't exist or can't be opened: %s.", + image->filename); + return LXW_ERROR_CREATING_TMPFILE; + } + + err = _add_file_to_zip(self, image_stream, filename); + fclose(image_stream); } + else { + err = _add_buffer_to_zip(self, + image->image_buffer, + image->image_buffer_size, filename); + } - err = _add_file_to_zip(self, image_stream, filename); - fclose(image_stream); - RETURN_ON_ERROR(err); } } return LXW_NO_ERROR; } /* + * Write the xl/vbaProject.bin file. + */ +STATIC lxw_error +_add_vba_project(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_error err; + FILE *image_stream; + + if (!workbook->vba_project) + return LXW_NO_ERROR; + + /* Check that the image file exists and can be opened. */ + image_stream = fopen(workbook->vba_project, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("Error adding vbaProject.bin to xlsx file: " + "file doesn't exist or can't be opened: %s.", + workbook->vba_project); + return LXW_ERROR_CREATING_TMPFILE; + } + + err = _add_file_to_zip(self, image_stream, "xl/vbaProject.bin"); + fclose(image_stream); + RETURN_ON_ERROR(err); + + return LXW_NO_ERROR; +} + +/* * Write the chart files. */ STATIC lxw_error _write_chart_files(lxw_packager *self) { lxw_workbook *workbook = self->workbook; lxw_chart *chart; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; - uint16_t index = 1; + uint32_t index = 1; lxw_error err; STAILQ_FOREACH(chart, workbook->ordered_charts, ordered_list_pointers) { lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, @@ -259,32 +347,53 @@ lxw_chart_assemble_xml_file(chart); err = _add_file_to_zip(self, chart->file, sheetname); RETURN_ON_ERROR(err); - self->chart_count++; - fclose(chart->file); } return LXW_NO_ERROR; } /* + * Count the chart files. + */ +uint32_t +_get_chart_count(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_chart *chart; + uint32_t chart_count = 0; + + STAILQ_FOREACH(chart, workbook->ordered_charts, ordered_list_pointers) { + chart_count++; + } + + return chart_count; +} + +/* * Write the drawing files. */ STATIC lxw_error _write_drawing_files(lxw_packager *self) { lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; lxw_drawing *drawing; char filename[LXW_FILENAME_LENGTH] = { 0 }; - uint16_t index = 1; + uint32_t index = 1; lxw_error err; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + worksheet = sheet->u.chartsheet->worksheet; + else + worksheet = sheet->u.worksheet; + drawing = worksheet->drawing; if (drawing) { lxw_snprintf(filename, LXW_FILENAME_LENGTH, "xl/drawings/drawing%d.xml", index++); @@ -296,19 +405,44 @@ lxw_drawing_assemble_xml_file(drawing); err = _add_file_to_zip(self, drawing->file, filename); RETURN_ON_ERROR(err); fclose(drawing->file); - - self->drawing_count++; } } return LXW_NO_ERROR; } /* + * Count the drawing files. + */ +uint32_t +_get_drawing_count(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_worksheet *worksheet; + lxw_drawing *drawing; + uint32_t drawing_count = 0; + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + worksheet = sheet->u.chartsheet->worksheet; + else + worksheet = sheet->u.worksheet; + + drawing = worksheet->drawing; + + if (drawing) + drawing_count++; + } + + return drawing_count; +} + +/* * Write the sharedStrings.xml file. */ STATIC lxw_error _write_shared_strings_file(lxw_packager *self) { @@ -338,14 +472,16 @@ */ STATIC lxw_error _write_app_file(lxw_packager *self) { lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; + lxw_chartsheet *chartsheet; lxw_defined_name *defined_name; lxw_app *app; - uint16_t named_range_count = 0; + uint32_t named_range_count = 0; char *autofilter; char *has_range; char number[LXW_ATTR_32] = { 0 }; lxw_error err = LXW_NO_ERROR; @@ -359,18 +495,36 @@ if (!app->file) { err = LXW_ERROR_CREATING_TMPFILE; goto mem_error; } - lxw_snprintf(number, LXW_ATTR_32, "%d", self->workbook->num_sheets); + if (self->workbook->num_worksheets) { + lxw_snprintf(number, LXW_ATTR_32, "%d", + self->workbook->num_worksheets); + lxw_app_add_heading_pair(app, "Worksheets", number); + } - lxw_app_add_heading_pair(app, "Worksheets", number); + if (self->workbook->num_chartsheets) { + lxw_snprintf(number, LXW_ATTR_32, "%d", + self->workbook->num_chartsheets); + lxw_app_add_heading_pair(app, "Charts", number); + } - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { - lxw_app_add_part_name(app, worksheet->name); + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (!sheet->is_chartsheet) { + worksheet = sheet->u.worksheet; + lxw_app_add_part_name(app, worksheet->name); + } } + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) { + chartsheet = sheet->u.chartsheet; + lxw_app_add_part_name(app, chartsheet->name); + } + } + /* Add the Named Ranges parts. */ TAILQ_FOREACH(defined_name, workbook->defined_names, list_pointers) { has_range = strchr(defined_name->formula, '!'); autofilter = strstr(defined_name->app_name, "_FilterDatabase"); @@ -566,13 +720,17 @@ STATIC lxw_error _write_content_types_file(lxw_packager *self) { lxw_content_types *content_types = lxw_content_types_new(); lxw_workbook *workbook = self->workbook; - lxw_worksheet *worksheet; + lxw_sheet *sheet; char filename[LXW_MAX_ATTRIBUTE_LENGTH] = { 0 }; - uint16_t index = 1; + uint32_t index = 1; + uint32_t worksheet_index = 1; + uint32_t chartsheet_index = 1; + uint32_t drawing_count = _get_drawing_count(self); + uint32_t chart_count = _get_chart_count(self); lxw_error err = LXW_NO_ERROR; if (!content_types) { err = LXW_ERROR_MEMORY_MALLOC_FAILED; goto mem_error; @@ -591,23 +749,41 @@ lxw_ct_add_default(content_types, "jpeg", "image/jpeg"); if (workbook->has_bmp) lxw_ct_add_default(content_types, "bmp", "image/bmp"); - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { - lxw_snprintf(filename, LXW_FILENAME_LENGTH, - "/xl/worksheets/sheet%d.xml", index++); - lxw_ct_add_worksheet_name(content_types, filename); + if (workbook->vba_project) + lxw_ct_add_default(content_types, "bin", + "application/vnd.ms-office.vbaProject"); + + if (workbook->vba_project) + lxw_ct_add_override(content_types, "/xl/workbook.xml", + LXW_APP_MSEXCEL "sheet.macroEnabled.main+xml"); + else + lxw_ct_add_override(content_types, "/xl/workbook.xml", + LXW_APP_DOCUMENT "spreadsheetml.sheet.main+xml"); + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) { + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "/xl/chartsheets/sheet%d.xml", chartsheet_index++); + lxw_ct_add_chartsheet_name(content_types, filename); + } + else { + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "/xl/worksheets/sheet%d.xml", worksheet_index++); + lxw_ct_add_worksheet_name(content_types, filename); + } } - for (index = 1; index <= self->chart_count; index++) { + for (index = 1; index <= chart_count; index++) { lxw_snprintf(filename, LXW_FILENAME_LENGTH, "/xl/charts/chart%d.xml", index); lxw_ct_add_chart_name(content_types, filename); } - for (index = 1; index <= self->drawing_count; index++) { + for (index = 1; index <= drawing_count; index++) { lxw_snprintf(filename, LXW_FILENAME_LENGTH, "/xl/drawings/drawing%d.xml", index); lxw_ct_add_drawing_name(content_types, filename); } @@ -635,13 +811,14 @@ STATIC lxw_error _write_workbook_rels_file(lxw_packager *self) { lxw_relationships *rels = lxw_relationships_new(); lxw_workbook *workbook = self->workbook; - lxw_worksheet *worksheet; + lxw_sheet *sheet; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; - uint16_t index = 1; + uint32_t worksheet_index = 1; + uint32_t chartsheet_index = 1; lxw_error err = LXW_NO_ERROR; if (!rels) { err = LXW_ERROR_MEMORY_MALLOC_FAILED; goto mem_error; @@ -651,23 +828,36 @@ if (!rels->file) { err = LXW_ERROR_CREATING_TMPFILE; goto mem_error; } - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { - lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, "worksheets/sheet%d.xml", - index++); - lxw_add_document_relationship(rels, "/worksheet", sheetname); + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) { + lxw_snprintf(sheetname, + LXW_FILENAME_LENGTH, + "chartsheets/sheet%d.xml", chartsheet_index++); + lxw_add_document_relationship(rels, "/chartsheet", sheetname); + } + else { + lxw_snprintf(sheetname, + LXW_FILENAME_LENGTH, + "worksheets/sheet%d.xml", worksheet_index++); + lxw_add_document_relationship(rels, "/worksheet", sheetname); + } } lxw_add_document_relationship(rels, "/theme", "theme/theme1.xml"); lxw_add_document_relationship(rels, "/styles", "styles.xml"); if (workbook->sst->string_count) lxw_add_document_relationship(rels, "/sharedStrings", "sharedStrings.xml"); + if (workbook->vba_project) + lxw_add_ms_package_relationship(rels, "/vbaProject", + "vbaProject.bin"); + lxw_relationships_assemble_xml_file(rels); err = _add_file_to_zip(self, rels->file, "xl/_rels/workbook.xml.rels"); fclose(rels->file); @@ -686,16 +876,21 @@ _write_worksheet_rels_file(lxw_packager *self) { lxw_relationships *rels; lxw_rel_tuple *rel; lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; - uint16_t index = 0; + uint32_t index = 0; lxw_error err; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; index++; if (STAILQ_EMPTY(worksheet->external_hyperlinks) && STAILQ_EMPTY(worksheet->external_drawing_links)) @@ -734,25 +929,92 @@ return LXW_NO_ERROR; } /* + * Write the chartsheet .rels files for chartsheets that contain links to + * external data such as drawings. + */ +STATIC lxw_error +_write_chartsheet_rels_file(lxw_packager *self) +{ + lxw_relationships *rels; + lxw_rel_tuple *rel; + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_worksheet *worksheet; + char sheetname[LXW_FILENAME_LENGTH] = { 0 }; + uint32_t index = 0; + lxw_error err; + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + worksheet = sheet->u.chartsheet->worksheet; + else + continue; + + index++; + + /* TODO. This should never be empty. Put check higher up. */ + if (STAILQ_EMPTY(worksheet->external_drawing_links)) + continue; + + rels = lxw_relationships_new(); + + rels->file = lxw_tmpfile(self->tmpdir); + if (!rels->file) { + lxw_free_relationships(rels); + return LXW_ERROR_CREATING_TMPFILE; + } + + STAILQ_FOREACH(rel, worksheet->external_hyperlinks, list_pointers) { + lxw_add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + } + + STAILQ_FOREACH(rel, worksheet->external_drawing_links, list_pointers) { + lxw_add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + } + + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, + "xl/chartsheets/_rels/sheet%d.xml.rels", index); + + lxw_relationships_assemble_xml_file(rels); + + err = _add_file_to_zip(self, rels->file, sheetname); + + fclose(rels->file); + lxw_free_relationships(rels); + + RETURN_ON_ERROR(err); + } + + return LXW_NO_ERROR; +} + +/* * Write the drawing .rels files for worksheets that contain charts or * drawings. */ STATIC lxw_error _write_drawing_rels_file(lxw_packager *self) { lxw_relationships *rels; lxw_rel_tuple *rel; lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; - uint16_t index = 1; + uint32_t index = 1; lxw_error err; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + worksheet = sheet->u.chartsheet->worksheet; + else + worksheet = sheet->u.worksheet; if (STAILQ_EMPTY(worksheet->drawing_links)) continue; rels = lxw_relationships_new(); @@ -847,11 +1109,12 @@ filename, &self->zipfile_info, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0, -MAX_WBITS, DEF_MEM_LEVEL, - Z_DEFAULT_STRATEGY, NULL, 0, 0, 0, 0); + Z_DEFAULT_STRATEGY, NULL, 0, 0, 0, + self->use_zip64); if (error != ZIP_OK) { LXW_ERROR("Error adding member to zipfile"); RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); } @@ -879,19 +1142,51 @@ } size_read = fread(self->buffer, 1, self->buffer_size, file); } + error = zipCloseFileInZip(self->zipfile); + if (error != ZIP_OK) { + LXW_ERROR("Error in closing member in the zipfile"); + RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); + } + + return LXW_NO_ERROR; +} + +STATIC lxw_error +_add_buffer_to_zip(lxw_packager *self, unsigned char *buffer, + size_t buffer_size, const char *filename) +{ + int16_t error = ZIP_OK; + + error = zipOpenNewFileInZip4_64(self->zipfile, + filename, + &self->zipfile_info, + NULL, 0, NULL, 0, NULL, + Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0, + -MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, NULL, 0, 0, 0, + self->use_zip64); + + if (error != ZIP_OK) { + LXW_ERROR("Error adding member to zipfile"); + RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); + } + + error = zipWriteInFileInZip(self->zipfile, + buffer, (unsigned int) buffer_size); + if (error < 0) { + LXW_ERROR("Error in writing member in the zipfile"); RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); } - else { - error = zipCloseFileInZip(self->zipfile); - if (error != ZIP_OK) { - LXW_ERROR("Error in closing member in the zipfile"); - RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); - } + + error = zipCloseFileInZip(self->zipfile); + if (error != ZIP_OK) { + LXW_ERROR("Error in closing member in the zipfile"); + RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); } return LXW_NO_ERROR; } @@ -902,13 +1197,25 @@ lxw_create_package(lxw_packager *self) { lxw_error error; int8_t zip_error; + error = _write_content_types_file(self); + RETURN_ON_ERROR(error); + + error = _write_root_rels_file(self); + RETURN_ON_ERROR(error); + + error = _write_workbook_rels_file(self); + RETURN_ON_ERROR(error); + error = _write_worksheet_files(self); RETURN_ON_ERROR(error); + error = _write_chartsheet_files(self); + RETURN_ON_ERROR(error); + error = _write_workbook_file(self); RETURN_ON_ERROR(error); error = _write_chart_files(self); RETURN_ON_ERROR(error); @@ -917,40 +1224,37 @@ RETURN_ON_ERROR(error); error = _write_shared_strings_file(self); RETURN_ON_ERROR(error); - error = _write_app_file(self); - RETURN_ON_ERROR(error); - - error = _write_core_file(self); - RETURN_ON_ERROR(error); - error = _write_custom_file(self); RETURN_ON_ERROR(error); error = _write_theme_file(self); RETURN_ON_ERROR(error); error = _write_styles_file(self); RETURN_ON_ERROR(error); - error = _write_content_types_file(self); + error = _write_worksheet_rels_file(self); RETURN_ON_ERROR(error); - error = _write_workbook_rels_file(self); + error = _write_chartsheet_rels_file(self); RETURN_ON_ERROR(error); - error = _write_worksheet_rels_file(self); - RETURN_ON_ERROR(error); - error = _write_drawing_rels_file(self); RETURN_ON_ERROR(error); error = _write_image_files(self); RETURN_ON_ERROR(error); - error = _write_root_rels_file(self); + error = _add_vba_project(self); + RETURN_ON_ERROR(error); + + error = _write_core_file(self); + RETURN_ON_ERROR(error); + + error = _write_app_file(self); RETURN_ON_ERROR(error); zip_error = zipClose(self->zipfile, NULL); if (zip_error) { RETURN_ON_ZIP_ERROR(zip_error, LXW_ERROR_ZIP_CLOSE);