這個庫是在看其他網頁時,作為和功能豐富的xlsxio庫的對比來的,按照xlsx_drone github頁面介紹,
特征
- 不使用任何外部應用程序來解析它們。
- 注重速度而不是功能。
- 簡單的接口。
- UTF-8 支持。
安裝
直接將 src 和 ext 文件夾復制并粘貼到項目根文件夾和源代碼中。您可能希望以不同的方式容納文件是可以理解的,但請注意 xlsx_drone.h 使用相對路徑調用其名稱:#include “xlsx_drone.h”
// external libraries
#include "../ext/zip.h"
#include "../ext/sxmlc.h"
#include "../ext/sxmlsearch.h"
除此以外,連一個安裝步驟都沒有寫,只給出了幾個代碼片段,沒法直接使用,文檔也就讓你參考 src/xlsx_drone.h。
還好源代碼包中有CMakelists.txt,那就表示能用cmake & make來生成。實際結果確實生成了動態庫, 而且幾乎瞬間生成。
/# cd par/xlsx_drone
/par/xlsx_drone# mkdir build
/par/xlsx_drone# cd build
/par/xlsx_drone/build# cmake ..
-- The C compiler identification is GNU 14.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /par/xlsx_drone/build
/par/xlsx_drone/build# make
[ 20%] Building C object CMakeFiles/xlsx_drone.dir/ext/zip.c.o
In file included from /par/xlsx_drone/ext/zip.c:37:
/par/xlsx_drone/ext/miniz.h:5106:9: note: '#pragma message: Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.'5106 | #pragma message( \| ^~~~~~~
[ 40%] Building C object CMakeFiles/xlsx_drone.dir/ext/sxmlc.c.o
[ 60%] Building C object CMakeFiles/xlsx_drone.dir/ext/sxmlsearch.c.o
[ 80%] Building C object CMakeFiles/xlsx_drone.dir/src/xlsx_drone.c.o
[100%] Linking C shared library libxlsx_drone.so
[100%] Built target xlsx_drone
瀏覽源碼目錄樹,有個test目錄下面有個xlsx_drone.test.c源文件,其中還有個helpers目錄,裝了幾個看上去用來測試的文件。
上手編譯它,很多鏈接找不到引用錯誤,其中一些像單元測試函數,一些像xlsx_drone中的函數。
/par/xlsx_drone/test# ls
helpers xlsx_drone.test.c
/par/xlsx_drone/test# gcc xlsx_drone.test.c -o xlsx_drone
/usr/bin/ld: /tmp/ccqiKV5x.o: in function `test_xlsx_open':
xlsx_drone.test.c:(.text+0x1c): undefined reference to `xlsx_set_print_err_messages'
/usr/bin/ld: xlsx_drone.test.c:(.text+0x2d): undefined reference to `xlsx_open'
/usr/bin/ld: xlsx_drone.test.c:(.text+0x4c): undefined reference to `UnityAssertEqualNumber'
/usr/bin/ld: xlsx_drone.test.c:(.text+0x51): undefined reference to `xlsx_get_xlsx_errno'
注意到,此文件開頭包含了#include "…/ext/unity.h"語句,而ext目錄下除了有unity的頭文件,還有c源代碼,在Makefile中沒有用到,那就加上unity.c一起編譯,一下子,單元測試引用的錯誤全消失了,只留下xlsx類錯誤。
gcc xlsx_drone.test.c ../ext/unity.c -o xlsx_drone/usr/bin/ld: /tmp/ccD6n0u6.o: in function `test_xlsx_open':
xlsx_drone.test.c:(.text+0x1c): undefined reference to `xlsx_set_print_err_messages'
/usr/bin/ld: xlsx_drone.test.c:(.text+0x2d): undefined reference to `xlsx_open'
再加上-L 和-l參數,就鏈接成功了,把共享動態庫目錄加入LD_LIBRARY_PATH,就能執行了。執行報錯,好像是打開文件失敗,
/par/xlsx_drone/test# gcc xlsx_drone.test.c ../ext/unity.c -o xlsx_drone -L /par/xlsx_drone/build -lxlsx_drone
/par/xlsx_drone/test# ./xlsx_drone
./xlsx_drone: error while loading shared libraries: libxlsx_drone.so: cannot open shared object file: No such file or directory/par/xlsx_drone/test# export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/par/xlsx_drone/build
/par/xlsx_drone/test# ./xlsx_drone
xlsx_drone.test.c:19:test_xlsx_open:FAIL: Expected 1 Was 0
xlsx_drone.test.c:370:test_xlsx_load_sheet:FAIL: Expected 0 Was -13
Segmentation fault (core dumped)
看xlsx_drone.test.c源碼,里面的路徑是"test\helpers\non_existent.xlsx"之類,于是再在父目錄執行,結果還是報錯。最后把Windows的\目錄分隔符全都替換成Linux的/,重新編譯,執行成功,如下所示;
/par/xlsx_drone# gcc test/xlsx_drone.test2.c ext/unity.c -o xlsx_drone2 -L /par/xlsx_drone/build -lxlsx_drone
/par/xlsx_drone# ./xlsx_drone2
test/xlsx_drone.test2.c:16:test_xlsx_open:FAIL: Expected 0 Was 1
test/xlsx_drone.test2.c:1231:test_xlsx_load_sheet:PASS
test/xlsx_drone.test2.c:1232:test_xlsx_unload_sheet:PASS
test/xlsx_drone.test2.c:1233:test_xlsx_get_last_column:PASS
test/xlsx_drone.test2.c:476:test_xlsx_read_cell:FAIL: Unity Double Precision Disabled
test/xlsx_drone.test2.c:1235:test_xlsx_close:PASS-----------------------
6 Tests 2 Failures 0 Ignored
FAIL
其中第一個測試是測不存在的文件,我故意放了一個non_existent.xlsx在helpers,導致期望0的測試執行結果為1,也報錯了。
再把首頁的代碼片段放入一個main函數,并包含頭文件,
#include "src/xlsx_drone.h"
int main()
{// open *.xlsxxlsx_workbook_t wb;xlsx_open("test/helpers/sample.xlsx", &wb);// be free to inspect some wb dataint number_of_sheets = wb.n_sheets;// load sheetxlsx_sheet_t *sheet_1 = xlsx_load_sheet(&wb, 1, NULL);// be free to inspect some sheet datachar *sheet_name = sheet_1->name;int last_row = sheet_1->last_row; // valued 0 if the sheet is empty// as of version 0.2.0 you can retrieve the last column (more info below)char *last_column = xlsx_get_last_column(sheet_1); // i.e.: "FB" or "R", etc.// read cellxlsx_cell_t cell_data_holder; xlsx_read_cell(sheet_1, 4, "B", &cell_data_holder);// inspect resultswitch(cell_data_holder.value_type) {case XLSX_POINTER_TO_CHAR:printf("Cell 4B has value: %s", cell_data_holder.value.pointer_to_char_value);break;case XLSX_INT:printf("Cell 4B has value: %d", cell_data_holder.value.int_value);break;case XLSX_LONG_LONG:printf("Cell 4B has value: %lld", cell_data_holder.value.long_long_value);break;case XLSX_DOUBLE:printf("Cell 4B has value: %f", cell_data_holder.value.double_value);break;default:printf("Cell 4B has no value");}// you can also inspect the cell categoryint cell_category = cell_data_holder.style->related_category; /*typedef enum xlsx_cell_category {XLSX_NUMBER, // int, long long, or doubleXLSX_TEXT, // stringXLSX_DATE, // intXLSX_TIME, // doubleXLSX_DATE_TIME, // doubleXLSX_UNKNOWN} xlsx_cell_category;*/char cat[7][10]={"NUMBER", "TEXT", "DATE", "TIME", "DATE_TIME", "UNKNOWN"};printf("Cell 4B has category XLSX_%s\n", cat[cell_category]);// when you're done reading the XLSX, close the workbook to properly free resourcesxlsx_close(&wb);
}
編譯運行結果如下
gcc example.c -o example -L /par/xlsx_drone/build -lxlsx_drone
/par/xlsx_drone# ./example
Cell 4B has value: -1000Cell 4B has category XLSX_NUMBER
再讓DeepSeek照樣編寫一個把xlsx轉為csv的程序。
#include "src/xlsx_drone.h"
#include <stdio.h> // 添加標準IO頭文件用于CSV操作int main()
{// 打開 *.xlsx 文件xlsx_workbook_t wb;xlsx_open("test/helpers/sample.xlsx", &wb);// 加載第一個工作表xlsx_sheet_t *sheet = xlsx_load_sheet(&wb, 1, NULL);// 創建并打開CSV文件用于寫入FILE *csv_file = fopen("output.csv", "w");if (csv_file == NULL) {printf("無法創建CSV文件\n");xlsx_close(&wb);return 1;}// 獲取工作表尺寸信息int last_row = sheet->last_row;char *last_column = xlsx_get_last_column(sheet);// 遍歷所有行和列for (int row = 1; row <= last_row; row++) {for (char col_char = 'A'; col_char <= last_column[0]; col_char++) {// 處理多字母列名(如AA, AB等)char col_str[3] = {col_char, '\0'};if (last_column[1] != '\0') {// 如果需要處理兩字母列名,這里需要擴展邏輯}// 讀取單元格數據xlsx_cell_t cell;xlsx_read_cell(sheet, row, col_str, &cell);// 根據數據類型寫入CSVswitch(cell.value_type) {case XLSX_POINTER_TO_CHAR:fprintf(csv_file, "\"%s\"", cell.value.pointer_to_char_value);break;case XLSX_INT:fprintf(csv_file, "%d", cell.value.int_value);break;case XLSX_LONG_LONG:fprintf(csv_file, "%lld", cell.value.long_long_value);break;case XLSX_DOUBLE:fprintf(csv_file, "%f", cell.value.double_value);break;default:// 空單元格留空break;}// 添加逗號分隔符(最后一列后不加逗號)if (col_char < last_column[0]) {fprintf(csv_file, ",");}}// 換行表示新的一行fprintf(csv_file, "\n");}// 關閉CSV文件fclose(csv_file);// 關閉工作簿并釋放資源xlsx_close(&wb);printf("XLSX文件已成功轉換為CSV格式\n");return 0;
}
編譯執行成功,前6行的內容如下,可見它支持UTF-8, 包括漢字希臘文。
gcc xlsx2csv.c -o xlsx2csv -L /par/xlsx_drone/build -lxlsx_drone
/par/xlsx_drone# ./xls2csv
bash: ./xls2csv: No such file or directory
/par/xlsx_drone# ./xlsx2csv
XLSX文件已成功轉換為CSV格式head -6 output.csv
"General","Number","Currency","Accounting","Date","Time","Percentage","Fraction (1.5)","Scientific (0.001)","Text","Special","Custom"
"Foo",1000,1000,147,43458,0.104792,0.500000,1.500000,0.001000,"1875",2000,12
235,1000,-14562.740000,1200.874000,43458,0.104792,0.450000,1.500000,0.001000,"Just text",2000,40955
17.890000,-1000,584,,43458,0.104792,1.600000,1.500000,0.001000,"𐐀34",543415635644,
,-1000,,,43458,0.104792,,1.500000,,"foo你bar好qaz",34580585,
,1200.561000,,,43458,0.104792,,1.500000,,,,
再用這個代碼讀取8*1M行的xlsx文件,看它的性能。
gcc xlsx2csv.c -o xlsx2csv -L /par/xlsx_drone/build -lxlsx_drone -O3
root@6ae32a5ffcde:/par/xlsx_drone# time ./xlsx2csv
XLSX文件已成功轉換為CSV格式real 0m14.381s
user 0m10.321s
sys 0m3.695s
盡管用了-O3編譯,還是不太快。