write.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. /*
  2. +----------------------------------------------------------------------+
  3. | Vtiful Extension |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2017-2017 The Viest |
  6. +----------------------------------------------------------------------+
  7. | http://www.vtiful.com |
  8. +----------------------------------------------------------------------+
  9. | Author: viest <[email protected]> |
  10. +----------------------------------------------------------------------+
  11. */
  12. #include "php.h"
  13. #include "excel.h"
  14. #include "write.h"
  15. #include "xlsxwriter.h"
  16. #include "xlsxwriter/packager.h"
  17. STATIC void _prepare_defined_names(lxw_workbook *self);
  18. STATIC void _prepare_drawings(lxw_workbook *self);
  19. STATIC void _add_chart_cache_data(lxw_workbook *self);
  20. /*
  21. * According to the zval type written to the file
  22. */
  23. void type_writer(zval *value, zend_long row, zend_long columns, excel_resource_t *res)
  24. {
  25. switch (Z_TYPE_P(value)) {
  26. case IS_STRING:
  27. worksheet_write_string(res->worksheet, row, columns, ZSTR_VAL(zval_get_string(value)), NULL);
  28. break;
  29. case IS_LONG:
  30. worksheet_write_number(res->worksheet, row, columns, zval_get_long(value), NULL);
  31. break;
  32. case IS_DOUBLE:
  33. worksheet_write_number(res->worksheet, row, columns, zval_get_double(value), NULL);
  34. break;
  35. }
  36. }
  37. /*
  38. * Write the image to the file
  39. */
  40. void image_writer(zval *value, zend_long row, zend_long columns, excel_resource_t *res)
  41. {
  42. worksheet_insert_image(res->worksheet, row, columns, ZSTR_VAL(zval_get_string(value)));
  43. }
  44. /*
  45. * Write the image to the file
  46. */
  47. void formula_writer(zval *value, zend_long row, zend_long columns, excel_resource_t *res)
  48. {
  49. worksheet_write_formula(res->worksheet, row, columns, ZSTR_VAL(zval_get_string(value)), NULL);
  50. }
  51. /*
  52. * Call finalization code and close file.
  53. */
  54. lxw_error
  55. workbook_file(excel_resource_t *self, zval *handle)
  56. {
  57. lxw_worksheet *worksheet = NULL;
  58. lxw_packager *packager = NULL;
  59. lxw_error error = LXW_NO_ERROR;
  60. /* Add a default worksheet if non have been added. */
  61. if (!self->workbook->num_sheets)
  62. workbook_add_worksheet(self, NULL);
  63. /* Ensure that at least one worksheet has been selected. */
  64. if (self->workbook->active_sheet == 0) {
  65. worksheet = STAILQ_FIRST(self->workbook->worksheets);
  66. worksheet->selected = 1;
  67. worksheet->hidden = 0;
  68. }
  69. /* Set the active sheet. */
  70. STAILQ_FOREACH(worksheet, self->workbook->worksheets, list_pointers) {
  71. if (worksheet->index == self->workbook->active_sheet)
  72. worksheet->active = 1;
  73. }
  74. /* Set the defined names for the worksheets such as Print Titles. */
  75. _prepare_defined_names(self->workbook);
  76. /* Prepare the drawings, charts and images. */
  77. _prepare_drawings(self->workbook);
  78. /* Add cached data to charts. */
  79. _add_chart_cache_data(self->workbook);
  80. /* Create a packager object to assemble sub-elements into a zip file. */
  81. packager = lxw_packager_new(self->workbook->filename, self->workbook->options.tmpdir);
  82. /* If the packager fails it is generally due to a zip permission error. */
  83. if (packager == NULL) {
  84. fprintf(stderr, "[ERROR] workbook_close(): "
  85. "Error creating '%s'. "
  86. "Error = %s\n", self->workbook->filename, strerror(errno));
  87. error = LXW_ERROR_CREATING_XLSX_FILE;
  88. goto mem_error;
  89. }
  90. /* Set the workbook object in the packager. */
  91. packager->workbook = self->workbook;
  92. /* Assemble all the sub-files in the xlsx package. */
  93. error = lxw_create_package(packager);
  94. /* Error and non-error conditions fall through to the cleanup code. */
  95. if (error == LXW_ERROR_CREATING_TMPFILE) {
  96. fprintf(stderr, "[ERROR] workbook_close(): "
  97. "Error creating tmpfile(s) to assemble '%s'. "
  98. "Error = %s\n", self->workbook->filename, strerror(errno));
  99. }
  100. /* If LXW_ERROR_ZIP_FILE_OPERATION then errno is set by zlib. */
  101. if (error == LXW_ERROR_ZIP_FILE_OPERATION) {
  102. fprintf(stderr, "[ERROR] workbook_close(): "
  103. "Zlib error while creating xlsx file '%s'. "
  104. "Error = %s\n", self->workbook->filename, strerror(errno));
  105. }
  106. /* The next 2 error conditions don't set errno. */
  107. if (error == LXW_ERROR_ZIP_FILE_ADD) {
  108. fprintf(stderr, "[ERROR] workbook_close(): "
  109. "Zlib error adding file to xlsx file '%s'.\n",
  110. self->workbook->filename);
  111. }
  112. if (error == LXW_ERROR_ZIP_CLOSE) {
  113. fprintf(stderr, "[ERROR] workbook_close(): "
  114. "Zlib error closing xlsx file '%s'.\n", self->workbook->filename);
  115. }
  116. mem_error:
  117. if (handle) {
  118. zend_list_close(Z_RES_P(handle));
  119. lxw_packager_free(packager);
  120. lxw_workbook_free(self->workbook);
  121. }
  122. return error;
  123. }
  124. void _php_vtiful_excel_close(zend_resource *rsrc TSRMLS_DC)
  125. {
  126. //Here to PHP for gc
  127. }
  128. /*
  129. * Iterate through the worksheets and store any defined names used for print
  130. * ranges or repeat rows/columns.
  131. */
  132. STATIC void
  133. _prepare_defined_names(lxw_workbook *self)
  134. {
  135. lxw_worksheet *worksheet;
  136. char app_name[LXW_DEFINED_NAME_LENGTH];
  137. char range[LXW_DEFINED_NAME_LENGTH];
  138. char area[LXW_MAX_CELL_RANGE_LENGTH];
  139. char first_col[8];
  140. char last_col[8];
  141. STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) {
  142. /*
  143. * Check for autofilter settings and store them.
  144. */
  145. if (worksheet->autofilter.in_use) {
  146. lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
  147. "%s!_FilterDatabase", worksheet->quoted_name);
  148. lxw_rowcol_to_range_abs(area,
  149. worksheet->autofilter.first_row,
  150. worksheet->autofilter.first_col,
  151. worksheet->autofilter.last_row,
  152. worksheet->autofilter.last_col);
  153. lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!%s",
  154. worksheet->quoted_name, area);
  155. /* Autofilters are the only defined name to set the hidden flag. */
  156. _store_defined_name(self, "_xlnm._FilterDatabase", app_name,
  157. range, worksheet->index, LXW_TRUE);
  158. }
  159. /*
  160. * Check for Print Area settings and store them.
  161. */
  162. if (worksheet->print_area.in_use) {
  163. lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
  164. "%s!Print_Area", worksheet->quoted_name);
  165. /* Check for print area that is the max row range. */
  166. if (worksheet->print_area.first_row == 0
  167. && worksheet->print_area.last_row == LXW_ROW_MAX - 1) {
  168. lxw_col_to_name(first_col,
  169. worksheet->print_area.first_col, LXW_FALSE);
  170. lxw_col_to_name(last_col,
  171. worksheet->print_area.last_col, LXW_FALSE);
  172. lxw_snprintf(area, LXW_MAX_CELL_RANGE_LENGTH - 1, "$%s:$%s",
  173. first_col, last_col);
  174. }
  175. /* Check for print area that is the max column range. */
  176. else if (worksheet->print_area.first_col == 0
  177. && worksheet->print_area.last_col == LXW_COL_MAX - 1) {
  178. lxw_snprintf(area, LXW_MAX_CELL_RANGE_LENGTH - 1, "$%d:$%d",
  179. worksheet->print_area.first_row + 1,
  180. worksheet->print_area.last_row + 1);
  181. }
  182. else {
  183. lxw_rowcol_to_range_abs(area,
  184. worksheet->print_area.first_row,
  185. worksheet->print_area.first_col,
  186. worksheet->print_area.last_row,
  187. worksheet->print_area.last_col);
  188. }
  189. lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!%s",
  190. worksheet->quoted_name, area);
  191. _store_defined_name(self, "_xlnm.Print_Area", app_name,
  192. range, worksheet->index, LXW_FALSE);
  193. }
  194. /*
  195. * Check for repeat rows/cols. aka, Print Titles and store them.
  196. */
  197. if (worksheet->repeat_rows.in_use || worksheet->repeat_cols.in_use) {
  198. if (worksheet->repeat_rows.in_use
  199. && worksheet->repeat_cols.in_use) {
  200. lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
  201. "%s!Print_Titles", worksheet->quoted_name);
  202. lxw_col_to_name(first_col,
  203. worksheet->repeat_cols.first_col, LXW_FALSE);
  204. lxw_col_to_name(last_col,
  205. worksheet->repeat_cols.last_col, LXW_FALSE);
  206. lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
  207. "%s!$%s:$%s,%s!$%d:$%d",
  208. worksheet->quoted_name, first_col,
  209. last_col, worksheet->quoted_name,
  210. worksheet->repeat_rows.first_row + 1,
  211. worksheet->repeat_rows.last_row + 1);
  212. _store_defined_name(self, "_xlnm.Print_Titles", app_name,
  213. range, worksheet->index, LXW_FALSE);
  214. }
  215. else if (worksheet->repeat_rows.in_use) {
  216. lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
  217. "%s!Print_Titles", worksheet->quoted_name);
  218. lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
  219. "%s!$%d:$%d", worksheet->quoted_name,
  220. worksheet->repeat_rows.first_row + 1,
  221. worksheet->repeat_rows.last_row + 1);
  222. _store_defined_name(self, "_xlnm.Print_Titles", app_name,
  223. range, worksheet->index, LXW_FALSE);
  224. }
  225. else if (worksheet->repeat_cols.in_use) {
  226. lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
  227. "%s!Print_Titles", worksheet->quoted_name);
  228. lxw_col_to_name(first_col,
  229. worksheet->repeat_cols.first_col, LXW_FALSE);
  230. lxw_col_to_name(last_col,
  231. worksheet->repeat_cols.last_col, LXW_FALSE);
  232. lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
  233. "%s!$%s:$%s", worksheet->quoted_name,
  234. first_col, last_col);
  235. _store_defined_name(self, "_xlnm.Print_Titles", app_name,
  236. range, worksheet->index, LXW_FALSE);
  237. }
  238. }
  239. }
  240. }
  241. /*
  242. * Iterate through the worksheets and set up any chart or image drawings.
  243. */
  244. STATIC void
  245. _prepare_drawings(lxw_workbook *self)
  246. {
  247. lxw_worksheet *worksheet;
  248. lxw_image_options *image_options;
  249. uint16_t chart_ref_id = 0;
  250. uint16_t image_ref_id = 0;
  251. uint16_t drawing_id = 0;
  252. STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) {
  253. if (STAILQ_EMPTY(worksheet->image_data)
  254. && STAILQ_EMPTY(worksheet->chart_data))
  255. continue;
  256. drawing_id++;
  257. STAILQ_FOREACH(image_options, worksheet->chart_data, list_pointers) {
  258. chart_ref_id++;
  259. lxw_worksheet_prepare_chart(worksheet, chart_ref_id, drawing_id,
  260. image_options);
  261. if (image_options->chart)
  262. STAILQ_INSERT_TAIL(self->ordered_charts, image_options->chart,
  263. ordered_list_pointers);
  264. }
  265. STAILQ_FOREACH(image_options, worksheet->image_data, list_pointers) {
  266. if (image_options->image_type == LXW_IMAGE_PNG)
  267. self->has_png = LXW_TRUE;
  268. if (image_options->image_type == LXW_IMAGE_JPEG)
  269. self->has_jpeg = LXW_TRUE;
  270. if (image_options->image_type == LXW_IMAGE_BMP)
  271. self->has_bmp = LXW_TRUE;
  272. image_ref_id++;
  273. lxw_worksheet_prepare_image(worksheet, image_ref_id, drawing_id,
  274. image_options);
  275. }
  276. }
  277. self->drawing_count = drawing_id;
  278. }
  279. /*
  280. * Add "cached" data to charts to provide the numCache and strCache data for
  281. * series and title/axis ranges.
  282. */
  283. STATIC void
  284. _add_chart_cache_data(lxw_workbook *self)
  285. {
  286. lxw_chart *chart;
  287. lxw_chart_series *series;
  288. STAILQ_FOREACH(chart, self->ordered_charts, ordered_list_pointers) {
  289. _populate_range(self, chart->title.range);
  290. _populate_range(self, chart->x_axis->title.range);
  291. _populate_range(self, chart->y_axis->title.range);
  292. if (STAILQ_EMPTY(chart->series_list))
  293. continue;
  294. STAILQ_FOREACH(series, chart->series_list, list_pointers) {
  295. _populate_range(self, series->categories);
  296. _populate_range(self, series->values);
  297. _populate_range(self, series->title.range);
  298. }
  299. }
  300. }