/* +----------------------------------------------------------------------+ | Vtiful Extension | +----------------------------------------------------------------------+ | Copyright (c) 2017-2017 The Viest | +----------------------------------------------------------------------+ | http://www.vtiful.com | +----------------------------------------------------------------------+ | Author: viest | +----------------------------------------------------------------------+ */ #include "php.h" #include "excel.h" #include "write.h" #include "xlsxwriter.h" #include "xlsxwriter/packager.h" STATIC void _prepare_defined_names(lxw_workbook *self); STATIC void _prepare_drawings(lxw_workbook *self); STATIC void _add_chart_cache_data(lxw_workbook *self); /* * According to the zval type written to the file */ void type_writer(zval *value, zend_long row, zend_long columns, excel_resource_t *res) { switch (Z_TYPE_P(value)) { case IS_STRING: worksheet_write_string(res->worksheet, row, columns, ZSTR_VAL(zval_get_string(value)), NULL); break; case IS_LONG: worksheet_write_number(res->worksheet, row, columns, zval_get_long(value), NULL); break; case IS_DOUBLE: worksheet_write_string(res->worksheet, row, columns, ZSTR_VAL(zval_get_string(value)), NULL); break; } } /* * Call finalization code and close file. */ lxw_error workbook_file(excel_resource_t *self, zval *handle) { lxw_worksheet *worksheet = NULL; lxw_packager *packager = NULL; lxw_error error = LXW_NO_ERROR; /* Add a default worksheet if non have been added. */ if (!self->workbook->num_sheets) workbook_add_worksheet(self, NULL); /* Ensure that at least one worksheet has been selected. */ if (self->workbook->active_sheet == 0) { worksheet = STAILQ_FIRST(self->workbook->worksheets); worksheet->selected = 1; worksheet->hidden = 0; } /* Set the active sheet. */ STAILQ_FOREACH(worksheet, self->workbook->worksheets, list_pointers) { if (worksheet->index == self->workbook->active_sheet) worksheet->active = 1; } /* Set the defined names for the worksheets such as Print Titles. */ _prepare_defined_names(self->workbook); /* Prepare the drawings, charts and images. */ _prepare_drawings(self->workbook); /* Add cached data to charts. */ _add_chart_cache_data(self->workbook); /* Create a packager object to assemble sub-elements into a zip file. */ packager = lxw_packager_new(self->workbook->filename, self->workbook->options.tmpdir); /* If the packager fails it is generally due to a zip permission error. */ if (packager == NULL) { fprintf(stderr, "[ERROR] workbook_close(): " "Error creating '%s'. " "Error = %s\n", self->workbook->filename, strerror(errno)); error = LXW_ERROR_CREATING_XLSX_FILE; goto mem_error; } /* Set the workbook object in the packager. */ packager->workbook = self->workbook; /* Assemble all the sub-files in the xlsx package. */ error = lxw_create_package(packager); /* Error and non-error conditions fall through to the cleanup code. */ if (error == LXW_ERROR_CREATING_TMPFILE) { fprintf(stderr, "[ERROR] workbook_close(): " "Error creating tmpfile(s) to assemble '%s'. " "Error = %s\n", self->workbook->filename, strerror(errno)); } /* If LXW_ERROR_ZIP_FILE_OPERATION then errno is set by zlib. */ if (error == LXW_ERROR_ZIP_FILE_OPERATION) { fprintf(stderr, "[ERROR] workbook_close(): " "Zlib error while creating xlsx file '%s'. " "Error = %s\n", self->workbook->filename, strerror(errno)); } /* The next 2 error conditions don't set errno. */ if (error == LXW_ERROR_ZIP_FILE_ADD) { fprintf(stderr, "[ERROR] workbook_close(): " "Zlib error adding file to xlsx file '%s'.\n", self->workbook->filename); } if (error == LXW_ERROR_ZIP_CLOSE) { fprintf(stderr, "[ERROR] workbook_close(): " "Zlib error closing xlsx file '%s'.\n", self->workbook->filename); } mem_error: if (handle) { zend_list_close(Z_RES_P(handle)); lxw_packager_free(packager); lxw_workbook_free(self->workbook); } return error; } void _php_vtiful_excel_close(zend_resource *rsrc TSRMLS_DC) { //Here to PHP for gc } /* * Iterate through the worksheets and store any defined names used for print * ranges or repeat rows/columns. */ STATIC void _prepare_defined_names(lxw_workbook *self) { lxw_worksheet *worksheet; char app_name[LXW_DEFINED_NAME_LENGTH]; char range[LXW_DEFINED_NAME_LENGTH]; char area[LXW_MAX_CELL_RANGE_LENGTH]; char first_col[8]; char last_col[8]; STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) { /* * Check for autofilter settings and store them. */ if (worksheet->autofilter.in_use) { lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH, "%s!_FilterDatabase", worksheet->quoted_name); lxw_rowcol_to_range_abs(area, worksheet->autofilter.first_row, worksheet->autofilter.first_col, worksheet->autofilter.last_row, worksheet->autofilter.last_col); lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!%s", worksheet->quoted_name, area); /* Autofilters are the only defined name to set the hidden flag. */ _store_defined_name(self, "_xlnm._FilterDatabase", app_name, range, worksheet->index, LXW_TRUE); } /* * Check for Print Area settings and store them. */ if (worksheet->print_area.in_use) { lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH, "%s!Print_Area", worksheet->quoted_name); /* Check for print area that is the max row range. */ if (worksheet->print_area.first_row == 0 && worksheet->print_area.last_row == LXW_ROW_MAX - 1) { lxw_col_to_name(first_col, worksheet->print_area.first_col, LXW_FALSE); lxw_col_to_name(last_col, worksheet->print_area.last_col, LXW_FALSE); lxw_snprintf(area, LXW_MAX_CELL_RANGE_LENGTH - 1, "$%s:$%s", first_col, last_col); } /* Check for print area that is the max column range. */ else if (worksheet->print_area.first_col == 0 && worksheet->print_area.last_col == LXW_COL_MAX - 1) { lxw_snprintf(area, LXW_MAX_CELL_RANGE_LENGTH - 1, "$%d:$%d", worksheet->print_area.first_row + 1, worksheet->print_area.last_row + 1); } else { lxw_rowcol_to_range_abs(area, worksheet->print_area.first_row, worksheet->print_area.first_col, worksheet->print_area.last_row, worksheet->print_area.last_col); } lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!%s", worksheet->quoted_name, area); _store_defined_name(self, "_xlnm.Print_Area", app_name, range, worksheet->index, LXW_FALSE); } /* * Check for repeat rows/cols. aka, Print Titles and store them. */ if (worksheet->repeat_rows.in_use || worksheet->repeat_cols.in_use) { if (worksheet->repeat_rows.in_use && worksheet->repeat_cols.in_use) { lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH, "%s!Print_Titles", worksheet->quoted_name); lxw_col_to_name(first_col, worksheet->repeat_cols.first_col, LXW_FALSE); lxw_col_to_name(last_col, worksheet->repeat_cols.last_col, LXW_FALSE); lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!$%s:$%s,%s!$%d:$%d", worksheet->quoted_name, first_col, last_col, worksheet->quoted_name, worksheet->repeat_rows.first_row + 1, worksheet->repeat_rows.last_row + 1); _store_defined_name(self, "_xlnm.Print_Titles", app_name, range, worksheet->index, LXW_FALSE); } else if (worksheet->repeat_rows.in_use) { lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH, "%s!Print_Titles", worksheet->quoted_name); lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!$%d:$%d", worksheet->quoted_name, worksheet->repeat_rows.first_row + 1, worksheet->repeat_rows.last_row + 1); _store_defined_name(self, "_xlnm.Print_Titles", app_name, range, worksheet->index, LXW_FALSE); } else if (worksheet->repeat_cols.in_use) { lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH, "%s!Print_Titles", worksheet->quoted_name); lxw_col_to_name(first_col, worksheet->repeat_cols.first_col, LXW_FALSE); lxw_col_to_name(last_col, worksheet->repeat_cols.last_col, LXW_FALSE); lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!$%s:$%s", worksheet->quoted_name, first_col, last_col); _store_defined_name(self, "_xlnm.Print_Titles", app_name, range, worksheet->index, LXW_FALSE); } } } } /* * Iterate through the worksheets and set up any chart or image drawings. */ STATIC void _prepare_drawings(lxw_workbook *self) { lxw_worksheet *worksheet; lxw_image_options *image_options; uint16_t chart_ref_id = 0; uint16_t image_ref_id = 0; uint16_t drawing_id = 0; STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) { if (STAILQ_EMPTY(worksheet->image_data) && STAILQ_EMPTY(worksheet->chart_data)) continue; drawing_id++; STAILQ_FOREACH(image_options, worksheet->chart_data, list_pointers) { chart_ref_id++; lxw_worksheet_prepare_chart(worksheet, chart_ref_id, drawing_id, image_options); if (image_options->chart) STAILQ_INSERT_TAIL(self->ordered_charts, image_options->chart, ordered_list_pointers); } STAILQ_FOREACH(image_options, worksheet->image_data, list_pointers) { if (image_options->image_type == LXW_IMAGE_PNG) self->has_png = LXW_TRUE; if (image_options->image_type == LXW_IMAGE_JPEG) self->has_jpeg = LXW_TRUE; if (image_options->image_type == LXW_IMAGE_BMP) self->has_bmp = LXW_TRUE; image_ref_id++; lxw_worksheet_prepare_image(worksheet, image_ref_id, drawing_id, image_options); } } self->drawing_count = drawing_id; } /* * Add "cached" data to charts to provide the numCache and strCache data for * series and title/axis ranges. */ STATIC void _add_chart_cache_data(lxw_workbook *self) { lxw_chart *chart; lxw_chart_series *series; STAILQ_FOREACH(chart, self->ordered_charts, ordered_list_pointers) { _populate_range(self, chart->title.range); _populate_range(self, chart->x_axis->title.range); _populate_range(self, chart->y_axis->title.range); if (STAILQ_EMPTY(chart->series_list)) continue; STAILQ_FOREACH(series, chart->series_list, list_pointers) { _populate_range(self, series->categories); _populate_range(self, series->values); _populate_range(self, series->title.range); } } }