#include "xlsxio_write.h" #include "xlsxio_version.h" #include #include #include #include #include #ifndef _WIN32 #include #endif #include #include #ifdef USE_MINIZIP # include # if !defined(Z_DEFLATED) && defined(MZ_COMPRESS_METHOD_DEFLATE) /* support minizip2 which defines MZ_COMPRESS_METHOD_DEFLATE instead of Z_DEFLATED */ # define Z_DEFLATED MZ_COMPRESS_METHOD_DEFLATE # endif # define ZIPFILETYPE zipFile #else # if (defined(STATIC) || defined(BUILD_XLSXIO_STATIC) || defined(BUILD_XLSXIO_STATIC_DLL) || (defined(BUILD_XLSXIO) && !defined(BUILD_XLSXIO_DLL) && !defined(BUILD_XLSXIO_SHARED))) && !defined(ZIP_STATIC) # define ZIP_STATIC # endif # include # ifndef ZIP_RDONLY typedef struct zip zip_t; typedef struct zip_source zip_source_t; # endif # define ZIPFILETYPE zip_t # ifndef USE_LIBZIP # define USE_LIBZIP # endif #endif #if defined(_WIN32) && !defined(USE_PTHREADS) # define USE_WINTHREADS # include #else # define USE_PTHREADS # include #endif #if defined(_MSC_VER) # undef DLL_EXPORT_XLSXIO # define DLL_EXPORT_XLSXIO # define va_copy(dst,src) ((dst) = (src)) #endif #ifdef _WIN32 # define pipe(fds) _pipe(fds, 4096, _O_BINARY) # define read _read # define write _write # define write _write # define close _close # define fdopen _fdopen #else # define _fdopen(f) f #endif //#undef WITHOUT_XLSX_STYLES #define DEFAULT_BUFFERED_ROWS 5 #define FONT_CHAR_WIDTH 7 //#define CALCULATE_COLUMN_WIDTH(characters) ((double)characters + .75) #define CALCULATE_COLUMN_WIDTH(characters) ((double)(long)(((long)characters * FONT_CHAR_WIDTH + 5) * 256 / FONT_CHAR_WIDTH) / 256.0) #define CALCULATE_COLUMN_HEIGHT(characters) ((double)characters * 12.75) DLL_EXPORT_XLSXIO void xlsxiowrite_get_version (int* pmajor, int* pminor, int* pmicro) { if (pmajor) *pmajor = XLSXIO_VERSION_MAJOR; if (pminor) *pminor = XLSXIO_VERSION_MINOR; if (pmicro) *pmicro = XLSXIO_VERSION_MICRO; } DLL_EXPORT_XLSXIO const char* xlsxiowrite_get_version_string () { return XLSXIO_VERSION_STRING; } //////////////////////////////////////////////////////////////////////// //#define WITHOUT_XLSX_SHAREDSTRINGS #define WITHOUT_XLSX_THEMES //#define WITHOUT_XLSX_FOLDERS //#define OPTIONAL_LINE_BREAK "\r\n" #define OPTIONAL_LINE_BREAK "" #define XML_HEADER "\r\n" #define XML_FOLDER_RELS "_rels/" #ifndef WITHOUT_XLSX_FOLDERS #define XML_FOLDER_DOCPROPS "docProps/" #define XML_FOLDER_XL "xl/" #define XML_FOLDER_THEMES "theme/" #define XML_FOLDER_WORKSHEETS "worksheets/" #else #define XML_FOLDER_DOCPROPS "" #define XML_FOLDER_XL "" #define XML_FOLDER_WORKSHEETS "" #endif #define XML_FILENAME_CONTENTTYPES "[Content_Types].xml" #define XML_FILENAME_RELS ".rels" #define XML_FILENAME_DOCPROPS_CORE "core.xml" #define XML_FILENAME_DOCPROPS_APP "app.xml" #define XML_FILENAME_XL_WORKBOOK_RELS "workbook.xml.rels" #define XML_FILENAME_XL_WORKBOOK "workbook.xml" #define XML_FILENAME_XL_STYLES "styles.xml" #define XML_FILENAME_XL_THEME1 "theme1.xml" #define XML_FILENAME_XL_SHAREDSTRINGS "sharedStrings.xml" #define XML_FILENAME_XL_WORKSHEET1 "sheet1.xml" #define XML_SHEETNAME_MAXLEN 31 #define XML_RELID_DOCPROPS_CORE "rId2" #define XML_RELID_DOCPROPS_APP "rId3" #define XML_RELID_XL_WORKBOOK "rId1" #define XML_WORKBOOK_RELID_XL_STYLES "rId1" #define XML_WORKBOOK_RELID_XL_WORKSHEET1 "rId2" #define XML_WORKBOOK_RELID_XL_SHAREDSTRINGS "rId3" #define XML_WORKBOOK_RELID_XL_THEME1 "rId4" const char* content_types_xml = XML_HEADER "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK #ifndef WITHOUT_XLSX_STYLES "" OPTIONAL_LINE_BREAK #endif #ifndef WITHOUT_XLSX_THEMES "" OPTIONAL_LINE_BREAK #endif "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK #ifndef WITHOUT_XLSX_SHAREDSTRINGS "" OPTIONAL_LINE_BREAK #endif "" OPTIONAL_LINE_BREAK; const char* docprops_core_xml = XML_HEADER "" OPTIONAL_LINE_BREAK //"" XLSXIOWRITE_FULLNAME "" OPTIONAL_LINE_BREAK "" XLSXIOWRITE_FULLNAME "" OPTIONAL_LINE_BREAK //"2016-04-24T17:50:35Z" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK; const char* docprops_app_xml = XML_HEADER "" OPTIONAL_LINE_BREAK "" XLSXIOWRITE_NAME "" OPTIONAL_LINE_BREAK "" XLSXIO_VERSION_STRING "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK; const char* rels_xml = XML_HEADER "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK; #ifndef WITHOUT_XLSX_STYLES const char* styles_xml = XML_HEADER "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK #define STYLE_HEADER 1 "" OPTIONAL_LINE_BREAK #define STYLE_GENERAL 2 "" OPTIONAL_LINE_BREAK #define STYLE_TEXT 3 "" OPTIONAL_LINE_BREAK #define STYLE_INTEGER 4 "" OPTIONAL_LINE_BREAK #define STYLE_DATETIME 5 "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK; #endif #ifndef WITHOUT_XLSX_THEMES const char* theme_xml = XML_HEADER "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK; #endif #ifndef WITHOUT_XLSX_SHAREDSTRINGS const char* sharedstrings_xml = XML_HEADER "" OPTIONAL_LINE_BREAK; //"" OPTIONAL_LINE_BREAK; //"" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK; #endif const char* workbook_rels_xml = XML_HEADER "" OPTIONAL_LINE_BREAK #ifndef WITHOUT_XLSX_STYLES "" OPTIONAL_LINE_BREAK #endif "" OPTIONAL_LINE_BREAK #ifndef WITHOUT_XLSX_SHAREDSTRINGS "" OPTIONAL_LINE_BREAK #endif #ifndef WITHOUT_XLSX_THEMES "" OPTIONAL_LINE_BREAK #endif "" OPTIONAL_LINE_BREAK; const char* workbook_xml = XML_HEADER "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK; const char* worksheet_xml_begin = XML_HEADER "" OPTIONAL_LINE_BREAK; //" OPTIONAL_LINE_BREAK" const char* worksheet_xml_freeze_top_row = "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK; const char* worksheet_xml_start_data = "" OPTIONAL_LINE_BREAK; const char* worksheet_xml_end = "" OPTIONAL_LINE_BREAK //"" OPTIONAL_LINE_BREAK "" OPTIONAL_LINE_BREAK; //////////////////////////////////////////////////////////////////////// #ifdef USE_LIBZIP zip_int64_t zip_file_add_custom (ZIPFILETYPE* zip, const char* filename, zip_source_t* zipsrc) { zip_int64_t index; if ((index = zip_file_add(zip, filename, zipsrc, ZIP_FL_OVERWRITE | ZIP_FL_ENC_UTF_8)) >= 0) { zip_set_file_compression(zip, index, ZIP_CM_DEFLATE, 9); //zip_set_file_compression(zip, index, ZIP_CM_DEFLATE, 5); //zip_set_file_compression(zip, index, ZIP_CM_STORE, 0); //zip_file_set_external_attributes(zip, index, 0, ZIP_OPSYS_DOS, 0); zip_file_set_external_attributes(zip, index, 0, ZIP_OPSYS_VFAT, 0); //zip_file_set_comment(zip, index, "Test", 4, ZIP_FL_ENC_UTF_8); //zip_file_set_mtime(zip, index, time(NULL), 0); //zip_file_extra_field_delete(zip, index, ZIP_EXTRA_FIELD_ALL, ZIP_FL_CENTRAL | ZIP_FL_LOCAL); } return index; } #endif int zip_add_content_buffer (ZIPFILETYPE* zip, const char* filename, const char* buf, size_t buflen, int mustfree) { #ifdef USE_MINIZIP zip_fileinfo zipinfo; time_t now = time(NULL); struct tm* newtm = localtime(&now); zipinfo.tmz_date.tm_sec = newtm->tm_sec; zipinfo.tmz_date.tm_min = newtm->tm_min; zipinfo.tmz_date.tm_hour = newtm->tm_hour; zipinfo.tmz_date.tm_mday = newtm->tm_mday; zipinfo.tmz_date.tm_mon = newtm->tm_mon; zipinfo.tmz_date.tm_year = newtm->tm_year; zipinfo.dosDate = 0; zipinfo.internal_fa = 0; zipinfo.external_fa = 0; if (zipOpenNewFileInZip(zip, filename, &zipinfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, 9) != ZIP_OK) { fprintf(stderr, "Error creating file \"%s\" inside zip file\n", filename);///// return 1; } if (zipWriteInFileInZip(zip, buf, buflen) != ZIP_OK) { fprintf(stderr, "Error writing to file \"%s\" inside zip file\n", filename);///// return 1; } zipCloseFileInZip(zip); if (mustfree) free((char*)buf); #else zip_source_t* zipsrc; if ((zipsrc = zip_source_buffer(zip, buf, buflen, mustfree)) == NULL) { fprintf(stderr, "Error creating file \"%s\" inside zip file\n", filename);///// return 1; } if (zip_file_add_custom(zip, filename, zipsrc) < 0) { fprintf(stderr, "Error in zip_file_add for file %s\n", filename);///// zip_source_free(zipsrc); return 2; } #endif return 0; } int zip_add_static_content_string (ZIPFILETYPE* zip, const char* filename, const char* data) { return zip_add_content_buffer(zip, filename, data, strlen(data), 0); } int zip_add_dynamic_content_string (ZIPFILETYPE* zip, const char* filename, const char* data, ...) { int result; char* buf; int buflen; va_list args; va_start(args, data); buflen = vsnprintf(NULL, 0, data, args); if (buflen < 0 || (buf = (char*)malloc(buflen + 1)) == NULL) { result = -1; } else { va_end(args); va_start(args, data); vsnprintf(buf, buflen + 1, data, args); result = zip_add_content_buffer(zip, filename, buf, buflen, 1); } va_end(args); return result; } //////////////////////////////////////////////////////////////////////// //replace part of a string char* str_replace (char** s, size_t pos, size_t len, char* replacement) { if (!s || !*s) return NULL; size_t totallen = strlen(*s); size_t replacementlen = strlen(replacement); if (pos > totallen) pos = totallen; if (pos + len > totallen) len = totallen - pos; if (replacementlen > len) if ((*s = (char*)realloc(*s, totallen - len + replacementlen + 1)) == NULL) return NULL; memmove(*s + pos + replacementlen, *s + pos + len, totallen - pos - len + 1); memcpy(*s + pos, replacement, replacementlen); return *s; } //fix string for use as XML data char* fix_xml_special_chars (char** s) { int pos = 0; while (*s && (*s)[pos]) { switch ((*s)[pos]) { case '&' : str_replace(s, pos, 1, "&"); pos += 5; break; case '\"' : str_replace(s, pos, 1, """); pos += 6; break; case '\'' : str_replace(s, pos, 1, "'"); pos += 6; break; case '<' : str_replace(s, pos, 1, "<"); pos += 4; break; case '>' : str_replace(s, pos, 1, ">"); pos += 4; break; case '\r' : str_replace(s, pos, 1, ""); break; default: pos++; break; } } return *s; } /* //add data to a null-terminated buffer and update the length counter int vappend_data (char** pdata, size_t* pdatalen, const char* format, va_list args) { int len; va_list args2; va_copy(args2, args); //va_start(args, format); if ((len = vsnprintf(NULL, 0, format, args)) < 0) return -1; va_end(args); if ((*pdata = (char*)realloc(*pdata, *pdatalen + len + 1)) == NULL) return -1; vsnprintf(*pdata + *pdatalen, len + 1, format, args2); va_end(args2); *pdatalen += len; return len; } //add formatted data to a null-terminated buffer and update the length counter int append_data (char** pdata, size_t* pdatalen, const char* format, ...) { int result; va_list args; va_start(args, format); result = vappend_data(pdata, pdatalen, format, args); va_end(args); return result; } */ //add formatted data to a null-terminated buffer and update the length counter int append_data (char** pdata, size_t* pdatalen, const char* format, ...) { int len; va_list args; va_start(args, format); len = vsnprintf(NULL, 0, format, args); va_end(args); if (len < 0) return -1; if ((*pdata = (char*)realloc(*pdata, *pdatalen + len + 1)) == NULL) return -1; va_start(args, format); vsnprintf(*pdata + *pdatalen, len + 1, format, args); va_end(args); *pdatalen += len; return len; } #ifndef NO_COLUMN_NUMBERS /* //insert formatted data into a null-terminated buffer at the specified position and update the length counter int insert_data (char** pdata, size_t* pdatalen, size_t pos, const char* format, ...) { int len; va_list args; va_start(args, format); len = vsnprintf(NULL, 0, format, args); va_end(args); if (len < 0) return -1; if ((*pdata = (char*)realloc(*pdata, *pdatalen + len + 1)) == NULL) return -1; if (pos > *pdatalen) pos = *pdatalen; if (pos < *pdatalen) memmove(*pdata + pos + len, *pdata + pos, *pdatalen - pos + 1); else (*pdata)[pos + len] = 0; va_start(args, format); len = vsnprintf(*pdata + pos, len, format, args); va_end(args); *pdatalen += len; return len; } char* get_A1col (uint64_t col) { char* result = NULL; size_t resultlen = 0; if (col > 0) { do { col--; insert_data(&result, &resultlen, 0, "%c", 'A' + (col % 26)); col = col / 26; } while (col > 0); } return result; } */ char* get_A1col (uint64_t col) { char* result = NULL; size_t resultlen = 0; //allocate 19 bytes as the maximum value for 64-bit devided by 26 has 18 digits if (col > 0 && (result = (char*)malloc(19)) != NULL) { result[0] = 0; do { col--; memmove(result + 1, result, ++resultlen); result[0] = 'A' + (col % 26); col = col / 26; } while (col > 0); } return result; } #endif #define need_space_preserve_attr(value) 1 /* int need_space_preserve_attr (const char* value) { /////TO DO: return non-zero only if space at beginning or end, or contains multiple consecutive spaces return 1; } */ //////////////////////////////////////////////////////////////////////// struct column_info_struct { int width; int maxwidth; struct column_info_struct* next; }; struct xlsxio_write_struct { char* filename; char* sheetname; ZIPFILETYPE* zip; #ifdef USE_WINTHREADS HANDLE thread; #else pthread_t thread; #endif FILE* pipe_read; FILE* pipe_write; struct column_info_struct* columninfo; struct column_info_struct** pcurrentcolumn; char* buf; size_t buflen; size_t rowstobuffer; size_t rowheight; int freezetop; int sheetopen; int rowopen; #ifndef NO_ROW_NUMBERS uint64_t rownr; #ifndef NO_COLUMN_NUMBERS uint64_t colnr; #endif #endif }; #ifndef NO_ROW_NUMBERS #define ROWNRTAG " r=\"%" PRIu64 "\"" #define ROWNRPARAM(handle) , handle->rownr #ifndef NO_COLUMN_NUMBERS #define COLNRTAG " r=\"%s%" PRIu64 "\"" #endif #endif //thread function used for creating .xlsx file from pipe #ifdef USE_WINTHREADS DWORD WINAPI thread_proc (LPVOID arg) #else void* thread_proc (void* arg) #endif { xlsxiowriter handle = (xlsxiowriter)arg; //generate required files zip_add_static_content_string(handle->zip, XML_FILENAME_CONTENTTYPES, content_types_xml); zip_add_static_content_string(handle->zip, XML_FOLDER_DOCPROPS XML_FILENAME_DOCPROPS_CORE, docprops_core_xml); zip_add_static_content_string(handle->zip, XML_FOLDER_DOCPROPS XML_FILENAME_DOCPROPS_APP, docprops_app_xml); zip_add_static_content_string(handle->zip, XML_FOLDER_RELS XML_FILENAME_RELS, rels_xml); #ifndef WITHOUT_XLSX_STYLES zip_add_static_content_string(handle->zip, XML_FOLDER_XL XML_FILENAME_XL_STYLES, styles_xml); #endif #ifndef WITHOUT_XLSX_THEMES zip_add_static_content_string(handle->zip, XML_FOLDER_XL XML_FOLDER_THEMES XML_FILENAME_XL_THEME1, theme_xml); #endif zip_add_static_content_string(handle->zip, XML_FOLDER_XL XML_FOLDER_RELS XML_FILENAME_XL_WORKBOOK_RELS, workbook_rels_xml); { //TO DO: this crashes on Linux char* sheetname = NULL; if (handle->sheetname) { sheetname = strdup(handle->sheetname); if (sheetname) { if (strlen(sheetname) > XML_SHEETNAME_MAXLEN) sheetname[XML_SHEETNAME_MAXLEN] = 0; fix_xml_special_chars(&sheetname); } } zip_add_dynamic_content_string(handle->zip, XML_FOLDER_XL XML_FILENAME_XL_WORKBOOK, workbook_xml, (sheetname ? sheetname : "Sheet1")); free(sheetname); } #ifndef WITHOUT_XLSX_SHAREDSTRINGS zip_add_static_content_string(handle->zip, XML_FOLDER_XL XML_FILENAME_XL_SHAREDSTRINGS, sharedstrings_xml); #endif //add sheet content file with pipe data #ifdef USE_MINIZIP #define MINIZIP_PIPE_BUFFER_SIZE 1024 //#error TO DO: if (zipOpenNewFileInZip(handle->zip, XML_FOLDER_XL XML_FOLDER_WORKSHEETS XML_FILENAME_XL_WORKSHEET1, NULL, NULL, 0, NULL, 0, NULL, Z_DEFLATED, 9) != ZIP_OK) { fprintf(stderr, "Error adding file"); } else { char* buf; size_t buflen; if ((buf = (char*)malloc(MINIZIP_PIPE_BUFFER_SIZE)) == NULL) { fprintf(stderr, "Memory allocation error");///// } else { while ((buflen = fread(buf, 1, MINIZIP_PIPE_BUFFER_SIZE, handle->pipe_read)) > 0) { if (zipWriteInFileInZip(handle->zip, buf, buflen) != ZIP_OK) { fprintf(stderr, "Error writing file inside archive");///// break; } } free(buf); } fclose(handle->pipe_read); zipCloseFileInZip(handle->zip); zipClose(handle->zip, NULL); } #else zip_source_t* zipsrc = zip_source_filep(handle->zip, handle->pipe_read, 0, -1); if (zip_file_add_custom(handle->zip, XML_FOLDER_XL XML_FOLDER_WORKSHEETS XML_FILENAME_XL_WORKSHEET1, zipsrc) < 0) { zip_source_free(zipsrc); fprintf(stderr, "Error adding file"); } #ifdef ZIP_RDONLY zip_file_set_mtime(handle->zip, zip_get_num_entries(handle->zip, 0) - 1, time(NULL), 0); #endif //close zip file (processes all data, will block until pipe is closed) if (zip_close(handle->zip) != 0) { int ze, se; #ifdef ZIP_RDONLY zip_error_t* error = zip_get_error(handle->zip); ze = zip_error_code_zip(error); se = zip_error_code_system(error); #else zip_error_get(handle->zip, &ze, &se); #endif fprintf(stderr, "zip_close failed (%i,%i)\n", ze, se);///// fprintf(stderr, "can't close zip archive : %s\n", zip_strerror(handle->zip)); } #endif handle->zip = NULL; handle->pipe_read = NULL; #ifdef USE_WINTHREADS return 0; #else return NULL; #endif } //////////////////////////////////////////////////////////////////////// DLL_EXPORT_XLSXIO xlsxiowriter xlsxiowrite_open (const char* filename, const char* sheetname) { xlsxiowriter handle; if (!filename) return NULL; if ((handle = (xlsxiowriter)malloc(sizeof(struct xlsxio_write_struct))) != NULL) { int pipefd[2]; //initialize handle->filename = strdup(filename); handle->sheetname = (sheetname ? strdup(sheetname) : NULL); handle->zip = NULL; //handle->pipe_read = NULL; //handle->pipe_write = NULL; handle->columninfo = NULL; handle->pcurrentcolumn = &handle->columninfo; handle->buf = NULL; handle->buflen = 0; handle->rowstobuffer = DEFAULT_BUFFERED_ROWS; handle->rowheight = 0; handle->freezetop = 0; handle->sheetopen = 0; handle->rowopen = 0; #ifndef NO_ROW_NUMBERS handle->rownr = 0; #ifndef NO_COLUMN_NUMBERS handle->colnr = 0; #endif #endif //remove filename first if it already exists unlink(filename); //initialize zip file object #ifdef USE_MINIZIP if ((handle->zip = zipOpen(handle->filename, 0)) == NULL) { #else if ((handle->zip = zip_open(handle->filename, ZIP_CREATE, NULL)) == NULL) { #endif fprintf(stderr, "Error writing to file %s\n", filename);///// //unlink(filename); free(handle->filename); free(handle); return NULL; } //create pipe if (pipe(pipefd) != 0) { fprintf(stderr, "Error creating pipe\n");///// free(handle); return NULL; } handle->pipe_read = fdopen(pipefd[0], "rb"); handle->pipe_write = fdopen(pipefd[1], "wb"); //create and start thread that will receive data via pipe #ifdef USE_WINTHREADS if ((handle->thread = CreateThread(NULL, 0, thread_proc, handle, 0, NULL)) == NULL) { #else if (pthread_create(&handle->thread, NULL, thread_proc, handle) != 0) { #endif fprintf(stderr, "Error creating thread\n");///// #ifdef USE_MINIZIP zipClose(handle->zip, NULL); #else zip_close(handle->zip); #endif //unlink(filename); free(handle->filename); fclose(handle->pipe_read); fclose(handle->pipe_write); free(handle); return NULL; } //write initial worksheet data fprintf(handle->pipe_write, "%s", worksheet_xml_begin); } return handle; } void flush_buffer (xlsxiowriter handle); DLL_EXPORT_XLSXIO int xlsxiowrite_close (xlsxiowriter handle) { struct column_info_struct* colinfo; struct column_info_struct* colinfonext; if (!handle) return -1; //finalize data if (handle->pipe_write) { //check if buffer should be flushed if (!handle->sheetopen) flush_buffer(handle); //close row if needed if (handle->rowopen) fprintf(handle->pipe_write, "" OPTIONAL_LINE_BREAK); //write worksheet data fprintf(handle->pipe_write, "%s", worksheet_xml_end); //close pipe fclose(handle->pipe_write); } //wait for thread to finish #ifdef USE_WINTHREADS WaitForSingleObject(handle->thread, INFINITE); #else pthread_join(handle->thread, NULL); #endif //clean up colinfo = handle->columninfo; while (colinfo) { colinfonext = colinfo->next; free(colinfo); colinfo = colinfonext; } free(handle->filename); free(handle->sheetname); if (handle->zip) #ifdef USE_MINIZIP zipClose(handle->zip, NULL); #else zip_close(handle->zip); #endif if (handle->pipe_read) fclose(handle->pipe_read); free(handle); return 0; } #ifndef WITHOUT_XLSX_STYLES #define STYLE_ATTR_HELPER(x) #x #define STYLE_ATTR(style) " s=\"" STYLE_ATTR_HELPER(style) "\"" #else #define STYLE_ATTR(style) "" #endif //output start of row void write_row_start (xlsxiowriter handle, const char* rowattr) { #ifndef NO_ROW_NUMBERS handle->rownr++; #ifndef NO_COLUMN_NUMBERS handle->colnr = 0; #endif #endif if (handle->sheetopen) { if (!handle->rowheight) fprintf(handle->pipe_write, "", (rowattr ? rowattr : "") ROWNRPARAM(handle)); else fprintf(handle->pipe_write, "", CALCULATE_COLUMN_HEIGHT(handle->rowheight), (rowattr ? rowattr : "") ROWNRPARAM(handle)); } else { if (!handle->rowheight) append_data(&handle->buf, &handle->buflen, "", (rowattr ? rowattr : "") ROWNRPARAM(handle)); else append_data(&handle->buf, &handle->buflen, "", CALCULATE_COLUMN_HEIGHT(handle->rowheight), (rowattr ? rowattr : "") ROWNRPARAM(handle)); } handle->rowopen = 1; } //output cell data void write_cell_data (xlsxiowriter handle, const char* rowattr, const char* prefix, const char* suffix, const char* format, ...) { va_list args; #if !defined(NO_ROW_NUMBERS) && !defined(NO_COLUMN_NUMBERS) char* cellcoord; #endif if (!handle) return; //start new row if needed if (!handle->rowopen) write_row_start(handle, rowattr); //get formatted data int datalen; char* data; va_start(args, format); if (format && (datalen = vsnprintf(NULL, 0, format, args)) >= 0 && (data = (char*)malloc(datalen + 1)) != NULL) { va_end(args); va_start(args, format); vsnprintf(data, datalen + 1, format, args); //prepare data for XML output fix_xml_special_chars(&data); } else { data = NULL; datalen = 0; } va_end(args); //determine cell coordinate #if !defined(NO_ROW_NUMBERS) && !defined(NO_COLUMN_NUMBERS) cellcoord = get_A1col(++handle->colnr); #define COLNRPARAM(handle) , cellcoord, handle->rownr #else #define COLNRPARAM(handle) #endif //add cell data if (handle->sheetopen) { //write cell data if (prefix) fprintf(handle->pipe_write, prefix COLNRPARAM(handle)); if (data) fprintf(handle->pipe_write, "%s", data); if (suffix) fprintf(handle->pipe_write, "%s", suffix); } else { //add cell data to buffer if (prefix) append_data(&handle->buf, &handle->buflen, prefix COLNRPARAM(handle)); if (data) append_data(&handle->buf, &handle->buflen, "%s", data); if (suffix) append_data(&handle->buf, &handle->buflen, suffix); //collect cell information if (!handle->sheetopen) { if (!*handle->pcurrentcolumn) { //create new column information structure struct column_info_struct* colinfo; if ((colinfo = (struct column_info_struct*)malloc(sizeof(struct column_info_struct))) != NULL) { colinfo->width = 0; colinfo->maxwidth = 0; colinfo->next = NULL; *handle->pcurrentcolumn = colinfo; } } //keep track of biggest column width if (data) { //only count first line in multiline data char* p = strchr(data, '\n'); if (p) datalen = p - data; //remember this length if it is the longest one so far if (datalen > 0 && datalen > (*handle->pcurrentcolumn)->maxwidth) (*handle->pcurrentcolumn)->maxwidth = datalen; } //prepare for the next column handle->pcurrentcolumn = &(*handle->pcurrentcolumn)->next; } } #if !defined(NO_ROW_NUMBERS) && !defined(NO_COLUMN_NUMBERS) free(cellcoord); #endif free(data); } //output buffered data and stop buffering void flush_buffer (xlsxiowriter handle) { //write section to freeze top row if (handle->freezetop > 0) fprintf(handle->pipe_write, "%s", worksheet_xml_freeze_top_row); //default to row height of 1 line //fprintf(handle->pipe_write, " OPTIONAL_LINE_BREAK", (double)12.75); //write column information if (handle->columninfo) { int col = 0; int len; struct column_info_struct* colinfo = handle->columninfo; fprintf(handle->pipe_write, ""); while (colinfo) { ++col; //determine column width len = colinfo->width; if (len == 0) { //use detected maximum length if column width specified was zero if (colinfo->maxwidth > 0) len = colinfo->maxwidth; } else if (len < 0) { //use detected maximum length if column width specified was negative and the detected maximum length is larger than the absolute value of the specified width len = -len; if (colinfo->maxwidth > len) len = colinfo->maxwidth; } if (len) fprintf(handle->pipe_write, "" OPTIONAL_LINE_BREAK, col, col, CALCULATE_COLUMN_WIDTH(len)); else fprintf(handle->pipe_write, "" OPTIONAL_LINE_BREAK, col, col); colinfo = colinfo->next; } fprintf(handle->pipe_write, "" OPTIONAL_LINE_BREAK); } //write initial data fprintf(handle->pipe_write, "%s", worksheet_xml_start_data); //write buffer and clear it if (handle->buf) { if (handle->buflen > 0) fwrite(handle->buf, 1, handle->buflen, handle->pipe_write); free(handle->buf); handle->buf = NULL; } handle->buflen = 0; handle->sheetopen = 1; } DLL_EXPORT_XLSXIO void xlsxiowrite_set_detection_rows (xlsxiowriter handle, size_t rows) { //abort if currently not buffering if (!handle->rowstobuffer || handle->sheetopen) return; //set number of rows to buffer handle->rowstobuffer = rows; //flush when zero was specified if (!rows) flush_buffer(handle); } DLL_EXPORT_XLSXIO void xlsxiowrite_set_row_height (xlsxiowriter handle, size_t height) { handle->rowheight = height; } DLL_EXPORT_XLSXIO void xlsxiowrite_add_column (xlsxiowriter handle, const char* value, int width) { struct column_info_struct** pcolinfo = handle->pcurrentcolumn; if (value) { if (need_space_preserve_attr(value)) write_cell_data(handle, STYLE_ATTR(STYLE_HEADER), "", "", "%s", value); else write_cell_data(handle, STYLE_ATTR(STYLE_HEADER), "", "", "%s", value); } else { write_cell_data(handle, STYLE_ATTR(STYLE_HEADER), "", NULL, NULL); } if (*pcolinfo) (*pcolinfo)->width = width; if (handle->freezetop == 0) handle->freezetop = 1; } DLL_EXPORT_XLSXIO void xlsxiowrite_add_cell_string (xlsxiowriter handle, const char* value) { if (value) { if (need_space_preserve_attr(value)) write_cell_data(handle, NULL, "", "", "%s", value); else write_cell_data(handle, NULL, "", "", "%s", value); } else { write_cell_data(handle, NULL, "", NULL, NULL); } } DLL_EXPORT_XLSXIO void xlsxiowrite_add_cell_int (xlsxiowriter handle, int64_t value) { write_cell_data(handle, NULL, "", "", "%" PRIi64, value); } DLL_EXPORT_XLSXIO void xlsxiowrite_add_cell_float (xlsxiowriter handle, double value) { write_cell_data(handle, NULL, "", "", "%.32G", value); } DLL_EXPORT_XLSXIO void xlsxiowrite_add_cell_datetime (xlsxiowriter handle, time_t value) { double timestamp = ((double)(value) + .499) / 86400 + 25569; //conversion from Unix to Excel timestamp write_cell_data(handle, NULL, "", "", "%.16G", timestamp); } /* Windows (And Mac Office 2011+): Unix Timestamp = (Excel Timestamp - 25569) * 86400 Excel Timestamp = (Unix Timestamp / 86400) + 25569 MAC OS X (pre Office 2011): Unix Timestamp = (Excel Timestamp - 24107) * 86400 Excel Timestamp = (Unix Timestamp / 86400) + 24107 */ DLL_EXPORT_XLSXIO void xlsxiowrite_next_row (xlsxiowriter handle) { if (!handle) return; //check if buffer should be flushed if (!handle->sheetopen) { if (handle->rowstobuffer > 0) { if (--handle->rowstobuffer == 0) { flush_buffer(handle); } else { } } } //start new row if needed if (!handle->rowopen) write_row_start(handle, NULL); //end row if (handle->rowstobuffer == 0) fprintf(handle->pipe_write, "" OPTIONAL_LINE_BREAK); else append_data(&handle->buf, &handle->buflen, "" OPTIONAL_LINE_BREAK); handle->rowopen = 0; handle->pcurrentcolumn = &handle->columninfo; }