大约对话了5次,得出了正确的结果,一开始他用二分找结束位置,导致解析为空。以及未考虑自闭合的单元格标签,导致坐标和值对应错误。
提示词
用C语言实现使用二分查找法优化xlsx的sheet.xml解析,快速定位要解析的数据范围,并跳过无需解析部分。具体是:在1/2文件长度部分读取
<row
标签,解析r="坐标"
,与用户输入的范围左上角坐标或首行行号比较,二分定位,直到找到相等的行,开始解析,一边检查是否到达末行,到达文件末或行末结束解析。单元格标签也有r="坐标"
属性,如果在用户输入的范围内则解析,否则跳过。示例数据如下:
<row r="3">
<c r="B3" s="1" t="s">
<v>0</v>
</c>
<c r="D3" s="4" t="s">
<v>3</v>
</c>
<c r="F3" s="5" t="s">
<v>4</v>
</c>
<c r="H3" s="6" t="s">
<v>5</v>
</c>
</row>
<row r="4">
<c r="D4" s="4"/>
<c r="F4" s="5"/>
不考虑二分找结束位置,找到首行就直接开始解析输出。
<c r="D13" s="13"/>
这种形式的单元格,虽然没有</c>
,但/>
也表示单元格结束,请增加这个逻辑。
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_LINE_LENGTH 4096
#define MAX_CELL_CONTENT 1024
#define MAX_SHEET_ROWS 1048576 // Excel最大行数
// 用户输入范围
typedef struct {
int start_row;
int end_row;
char start_col; // 列字母,如'A'
char end_col; // 列字母,如'Z'
} ParseRange;
// 解析结果
typedef struct {
int row;
char col;
char value[MAX_CELL_CONTENT];
int is_empty; // 空单元格标记
} CellData;
// 全局变量
CellData *results = NULL;
int result_count = 0;
int result_capacity = 0;
// 函数声明
int binary_search_start(FILE *file, ParseRange range);
int find_row_by_binary_search(FILE *file, int target_row, long *start_pos, long *end_pos);
void parse_row_data(FILE *file, ParseRange range, long start_pos, long end_pos);
void add_cell_result(int row, char col, const char *value, int is_empty);
int is_cell_in_range(int row, char col, ParseRange range);
int compare_row_col(int row1, char col1, int row2, char col2);
void free_results();
/**
* 主解析函数 - 修正版本
* @param filename XML文件路径
* @param range 解析范围
* @return 0成功,-1失败
*/
int parse_sheet_xml(const char *filename, ParseRange range) {
FILE *file = fopen(filename, "r");
if (!file) {
perror("无法打开文件");
return -1;
}
// 初始化结果数组
result_count = 0;
result_capacity = 1024;
results = (CellData *)malloc(result_capacity * sizeof(CellData));
if (!results) {
fclose(file);
return -1;
}
// 二分查找起始行
if (binary_search_start(file, range)) {
// 直接开始解析,从当前位置开始
char buffer[MAX_LINE_LENGTH];
long row_start_pos = ftell(file);
// 向前查找最近的<row标签开始位置
for (long pos = row_start_pos; pos >= 0; pos--) {
fseek(file, pos, SEEK_SET);
if (fgetc(file) == '<') {
// 检查是否是<row标签
int is_row_tag = 1;
for (int i = 1; i < 4; i++) {
if (fgetc(file) != "row"[i]) {
is_row_tag = 0;
break;
}
}
if (is_row_tag) {
row_start_pos = pos; // 记录<row标签的起始位置
fseek(file, pos, SEEK_SET); // 定位到<row标签开始
break;
}
}
}
// 获取文件大小作为结束边界
long file_size;
fseek(file, 0, SEEK_END);
file_size = ftell(file);
// 定位到<row标签开始位置,准备解析
fseek(file, row_start_pos, SEEK_SET);
// 直接解析数据 - 从<row标签开始到文件末尾
parse_row_data(file, range, row_start_pos, file_size);
}
fclose(file);
return 0;
}
/*
// 直接解析数据
printf("ftell(file)=%d ", ftell(file));
parse_row_data(file, range, ftell(file), file_size);
}
fclose(file);
return 0;
}
*/
/**
* 二分查找定位起始行
* @param file 文件指针
* @param range 解析范围
* @return 是否找到起始行
*/
int binary_search_start(FILE *file, ParseRange range) {
long file_size = 0;
long low, high, mid;
// 获取文件大小
fseek(file, 0, SEEK_END);
file_size = ftell(file);
fseek(file, 0, SEEK_SET);
low = 0;
high = file_size;
int last_found_row = -1;
long last_found_pos = -1;
while (low <= high) {
mid = (low + high) / 2;
fseek(file, mid, SEEK_SET);
// 向前查找最近的<row标签
char buffer[MAX_LINE_LENGTH];
long row_start_pos = -1;
int row_num = -1;
// 从mid位置向前扫描,找到前一个<row标签
for (long pos = mid; pos >= low && pos >= 0; pos--) {
fseek(file, pos, SEEK_SET);
if (fgetc(file) == '<') {
if (pos + 4 <= file_size && fgetc(file) == 'r' &&
fgetc(file) == 'o' && fgetc(file) == 'w') {
row_start_pos = pos;
break;
}
}
}
// 如果向前没找到,从mid向后找
if (row_start_pos == -1) {
for (long pos = mid; pos <= high && pos < file_size - 4; pos++) {
fseek(file, pos, SEEK_SET);
if (fgetc(file) == '<') {
if (pos + 4 <= file_size && fgetc(file) == 'r' &&
fgetc(file) == 'o' && fgetc(file) == 'w') {
row_start_pos = pos;
break;
}
}
}
}
if (row_start_pos == -1) {
// 没有找到<row标签
if (mid == low) break;
high = mid - 1;
continue;
}
// 解析行号
fseek(file, row_start_pos, SEEK_SET);
while (fgets(buffer, MAX_LINE_LENGTH, file)) {
if (strstr(buffer, "<row")) {
char *row_attr = strstr(buffer, "r=\"");
if (row_attr) {
row_attr += 3; // 跳过r="
row_num = atoi(row_attr);
break;
}
}
}
if (row_num == -1) {
// 解析行号失败,调整搜索范围
if (row_start_pos < range.start_row) low = mid + 1;
else high = mid - 1;
continue;
}
if (row_num == range.start_row) {
// 找到精确匹配
last_found_row = row_num;
last_found_pos = row_start_pos;
break;
} else if (row_num < range.start_row) {
// 当前行小于目标行
if (row_num > last_found_row) {
last_found_row = row_num;
last_found_pos = row_start_pos;
}
low = mid + 1;
} else {
// 当前行大于目标行
high = mid - 1;
}
}
// 如果找到了合适的起始位置
if (last_found_row != -1) {
//printf(" last_found_pos=%d\n", last_found_pos);
fseek(file, last_found_pos, SEEK_SET);
return 1;
}
return 0;
}
/**
* 解析行数据,包括单元格
* @param file 文件指针
* @param range 解析范围
* @param start_pos 起始位置
* @param end_pos 结束位置
*/
void parse_row_data(FILE *file, ParseRange range, long start_pos, long end_pos) {
char buffer[MAX_LINE_LENGTH];
char temp_value[MAX_CELL_CONTENT];
int in_row = 0;
int current_row = -1;
char current_col = '\0';
fseek(file, start_pos, SEEK_SET);
while (fgets(buffer, MAX_LINE_LENGTH, file) &&
(long)ftell(file) <= end_pos) {
char *pos = buffer;
// 处理每行中的标签
while ((pos = strchr(pos, '<')) != NULL) {
if (strncmp(pos, "<row", 4) == 0) {
// 解析行号
char *row_attr = strstr(pos, "r=\"");
if (row_attr) {
row_attr += 3;
current_row = atoi(row_attr);
}
in_row = 1;
pos += 4;
}
else if (strncmp(pos, "</row>", 6) == 0) {
// 行结束
if (current_row >= range.end_row) {
// 超过用户指定范围,停止解析
return;
}
in_row = 0;
current_row = -1;
pos += 6;
}
// 在parse_row_data函数中,修改单元格解析部分如下:
else if (in_row && strncmp(pos, "<c ", 3) == 0) {
// 解析单元格
char *col_attr = strstr(pos, "r=\"");
char *value_start = NULL;
int is_empty = 0;
int cell_has_value = 0;
int is_self_closing = 0; // 新增:标记是否是自闭合标签
char current_cell_col = '\0';
if (col_attr) {
col_attr += 3; // 跳过r="
current_cell_col = col_attr[0]; // 获取当前单元格的列字母
// 检查是否是自闭合标签 <c ... />
char *self_close = strstr(pos, "/>");
if (self_close) {
is_self_closing = 1;
}
// 跳过列字母和数字分隔符
while (isdigit(col_attr[0])) col_attr++;
// 检查单元格值
char *v_tag = NULL;
char *cell_content = pos;
v_tag = strstr(cell_content, "<v>");
if (v_tag) {
//printf("<v> ");
value_start = v_tag + 3;
char *v_end = strstr(v_tag, "</v>");
if (v_end) {
*v_end = '\0';
strncpy(temp_value, value_start, MAX_CELL_CONTENT - 1);
temp_value[MAX_CELL_CONTENT - 1] = '\0';
cell_has_value = 1;
}
} else if (!is_self_closing) {
// 只有非自闭合标签才尝试读取更多内容
int need_more_data = 0;
int cell_content_len = strlen(cell_content);
if (cell_content_len < MAX_LINE_LENGTH - 1) {
need_more_data = 1;
} else {
if (cell_content[cell_content_len - 1] == '<' ||
(cell_content[cell_content_len - 1] == 'v' &&
cell_content[cell_content_len - 2] == '<')) {
need_more_data = 1;
}
}
if (need_more_data) {
long current_file_pos = ftell(file);
char extra_buffer[MAX_LINE_LENGTH];
int extra_found = 0;
while (fgets(extra_buffer, MAX_LINE_LENGTH, file) &&
!strstr(extra_buffer, "</c>") &&
!strstr(extra_buffer, "</row>")) {
v_tag = strstr(extra_buffer, "<v>");
if (v_tag) {
//printf("<v> ");
value_start = v_tag + 3;
char *v_end = strstr(v_tag, "</v>");
if (v_end) {
*v_end = '\0';
strncpy(temp_value, value_start, MAX_CELL_CONTENT - 1);
temp_value[MAX_CELL_CONTENT - 1] = '\0';
} else {
strncpy(temp_value, value_start, MAX_CELL_CONTENT - 1);
temp_value[MAX_CELL_CONTENT - 1] = '\0';
while (fgets(extra_buffer, MAX_LINE_LENGTH, file) &&
!strstr(extra_buffer, "</v>")) {
// 继续读取
}
}
cell_has_value = 1;
extra_found = 1;
break;
}
}
if (!extra_found) {
fseek(file, current_file_pos, SEEK_SET);
}
}
}
// 自闭合标签一定是空单元格
if (is_self_closing || !cell_has_value) {
is_empty = 1;
temp_value[0] = '\0';
}
if (is_cell_in_range(current_row, current_cell_col, range)) {
add_cell_result(current_row, current_cell_col, temp_value, is_empty);
}
}
pos += 3;
}
else if (strncmp(pos, "</c>", 4) == 0) {
// 单元格结束
current_col = '\0';
pos += 4;
}
else {
pos++;
}
}
}
}
/**
* 添加单元格结果到结果数组
*/
void add_cell_result(int row, char col, const char *value, int is_empty) {
// 扩展结果数组
if (result_count >= result_capacity) {
result_capacity *= 2;
results = (CellData *)realloc(results, result_capacity * sizeof(CellData));
if (!results) {
fprintf(stderr, "内存分配失败\n");
return;
}
}
results[result_count].row = row;
results[result_count].col = col;
strncpy(results[result_count].value, value, MAX_CELL_CONTENT - 1);
results[result_count].value[MAX_CELL_CONTENT - 1] = '\0';
results[result_count].is_empty = is_empty;
result_count++;
}
/**
* 检查单元格是否在用户指定范围内
*/
int is_cell_in_range(int row, char col, ParseRange range) {
if (row < range.start_row || row > range.end_row) return 0;
if (col < range.start_col || col > range.end_col) return 0;
return 1;
}
/**
* 比较两个行列坐标
* @return -1: row1<col1 < row2<col2, 0: 相等, 1: row1<col1 > row2<col2
*/
int compare_row_col(int row1, char col1, int row2, char col2) {
if (row1 != row2) return (row1 < row2) ? -1 : 1;
if (col1 != col2) return (col1 < col2) ? -1 : 1;
return 0;
}
/**
* 释放结果内存
*/
void free_results() {
if (results) {
free(results);
results = NULL;
}
result_count = 0;
result_capacity = 0;
}
/**
* 打印解析结果
*/
void print_results() {
printf("解析结果:\n");
for (int i = 0; i < result_count; i++) {
if (results[i].is_empty) {
printf("单元格 %c%d: (空)\n", results[i].col, results[i].row);
} else {
printf("单元格 %c%d: %s\n", results[i].col, results[i].row, results[i].value);
}
}
}
/**
* 以Excel的A1表示法打印解析范围
* 例如:A1:H7
* @param range 要打印的解析范围
*/
void print_parse_range(ParseRange range) {
printf("解析范围: %c%d:%c%d\n",
range.start_col, range.start_row,
range.end_col, range.end_row);
}
// 使用示例
int main() {
ParseRange range = {6, 18, 'B', 'H'}; // 解析第3-5行,B-H列
const char *filename = "sheet.xml"; // XML文件路径
print_parse_range(range); // 输出: 解析范围
if (parse_sheet_xml(filename, range) == 0) {
print_results();
} else {
printf("解析失败\n");
}
free_results();
return 0;
}
测试用例如下
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheetViews>
<sheetView showGridLines="false" workbookViewId="0"/>
</sheetViews>
<cols>
<col min="2" max="2" width="30.7109375" customWidth="true"/>
<col min="4" max="4" width="12.11328125" customWidth="true"/>
<col min="5" max="5" width="2.7109375" customWidth="true"/>
<col min="6" max="6" width="15.7109375" customWidth="true"/>
<col min="7" max="7" width="2.7109375" customWidth="true"/>
<col min="8" max="8" width="16.11328125" customWidth="true"/>
</cols>
<sheetData>
<row r="3">
<c r="B3" s="1" t="s">
<v>0</v>
</c>
<c r="D3" s="4" t="s">
<v>3</v>
</c>
<c r="F3" s="5" t="s">
<v>4</v>
</c>
<c r="H3" s="6" t="s">
<v>5</v>
</c>
</row>
<row r="4">
<c r="D4" s="4"/>
<c r="F4" s="5"/>
<c r="H4" s="6"/>
</row>
<row r="5">
<c r="B5" s="2" t="s">
<v>1</v>
</c>
<c r="D5" s="4"/>
<c r="F5" s="5"/>
<c r="H5" s="6"/>
</row>
<row r="6">
<c r="D6" s="4"/>
<c r="F6" s="5"/>
<c r="H6" s="6"/>
</row>
<row r="7">
<c r="B7" s="3" t="s">
<v>2</v>
</c>
<c r="D7" s="4"/>
<c r="F7" s="5"/>
<c r="H7" s="6"/>
</row>
<row r="13">
<c r="B13" s="7" t="s">
<v>6</v>
</c>
<c r="D13" s="13"/>
<c r="F13" s="19"/>
<c r="H13" s="25" t="s">
<v>12</v>
</c>
</row>
<row r="15">
<c r="B15" s="8" t="s">
<v>7</v>
</c>
<c r="D15" s="14"/>
<c r="F15" s="20"/>
<c r="H15" s="26" t="s">
<v>13</v>
</c>
</row>
<row r="17">
<c r="B17" s="9" t="s">
<v>8</v>
</c>
<c r="D17" s="15"/>
<c r="F17" s="21"/>
<c r="H17" s="27" t="s">
<v>14</v>
</c>
</row>
<row r="19">
<c r="B19" s="10" t="s">
<v>9</v>
</c>
<c r="D19" s="16"/>
<c r="F19" s="22"/>
<c r="H19" s="28" t="s">
<v>15</v>
</c>
</row>
<row r="21">
<c r="B21" s="11" t="s">
<v>10</v>
</c>
<c r="D21" s="17"/>
<c r="F21" s="23"/>
<c r="H21" s="29" t="s">
<v>16</v>
</c>
</row>
<row r="23">
<c r="B23" s="12" t="s">
<v>11</v>
</c>
<c r="D23" s="18"/>
<c r="F23" s="24"/>
<c r="H23" s="30" t="s">
<v>17</v>
</c>
</row>
</sheetData>
<mergeCells count="3">
<mergeCell ref="D3:D7"/>
<mergeCell ref="F3:F7"/>
<mergeCell ref="H3:H7"/>
</mergeCells>
</worksheet>