美团龙猫(longcat.AI)编写的利用二分查找优化Excel的sheet.xml指定范围输出C程序

发布于:2025-09-03 ⋅ 阅读:(22) ⋅ 点赞:(0)

大约对话了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>

网站公告

今日签到

点亮在社区的每一天
去签到