|
@@ -0,0 +1,352 @@
|
|
|
+/*
|
|
|
+ +----------------------------------------------------------------------+
|
|
|
+ | Vtiful Extension |
|
|
|
+ +----------------------------------------------------------------------+
|
|
|
+ | Copyright (c) 2017-2017 The Viest |
|
|
|
+ +----------------------------------------------------------------------+
|
|
|
+ | http://www.vtiful.com |
|
|
|
+ +----------------------------------------------------------------------+
|
|
|
+ | Author: viest <[email protected]> |
|
|
|
+ +----------------------------------------------------------------------+
|
|
|
+*/
|
|
|
+
|
|
|
+#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));
|
|
|
+ }
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|