资讯动态

doc转文本与试题提取:Python正则和C语言实战

发布时间:2026/9/18 19:20:30 来源:尧图企业网站定制
简介这份长安大学C语言程序设计期末考试A卷试题即二〇〇六至二〇〇七学年第二学期的原卷适合正在备考C语言期末考的高校学生、自学C语言基础的人群也可供相关课程教师命题时参考。卷内为一整份doc格式原卷压缩包总共一个文档约八十六KB题目完整覆盖C语言程序结构、常量与变量、运算符优先级、自增自减、输入输出、流程控制、宏定义替换、联合体内存占用、数组初始化、字符串长度计算、字符数组操作、文件打开方式、静态局部变量、函数实参与形参等核心概念并以选择题、程序运行结果分析、程序阅读等常见题型呈现。此卷尤其注重对易混淆细节的考查例如整数除法与浮点数除法结果差异、宏展开时运算顺序、循环中continue与break对执行次数的影响等适合作为考前模拟训练、知识盲点排查的素材。目前已有九十八人学习使用学习者可借原卷深入体会期末考题风格与难度系统巩固C语言语法、运算规则和内存管理等核心能力。1. 一个老式 doc 文件为什么值得你写程序处理看到“长安大学0607c语言A卷试题(卷.doc”这个文件名如果你只想打印做题双击打开就行。但如果你需要把历年试卷揉进题库、按题型统计考点、或者批量转成 Markdown这个 .doc 就成了第一个拦路虎。老式 .doc 是二进制复合文档格式不是纯文本很多编辑器打开是一堆乱码。我一般会先把它转成纯文本再用脚本把试题拆开。这篇博客就按这个流程展开doc 转文本的两种可靠工具、Python 侧的正则提取参数、以及 C 语言处理文本时最容易出错的三个点。新手能照着跑通老手也能看看边界。2. 先把 doc 转成可读文本选对工具和命令2.1 doc 和 docx 的底层差异为什么不能直接读拿到 .doc 文件第一反应是用 python-docx 去读结果一跑就报错。原因很简单.doc是微软老版 Word 的二进制格式底层是 OLE2 复合文档正文内容压缩在一段叫 WordDocument 的流里而.docx是一个 ZIP 包正文是word/document.xml。python-docx 只认识 docx不认识 doc。这里有一个对比表维度.doc.docx容器结构OLE2 复合文档ZIP 压缩包正文存储WordDocument 流word/document.xml常见处理库antiword, catdoc, LibreOfficepython-docx, openpyxl文本提取难度需要解析二进制流解压后直接读 XML所以正确处理顺序是先把 doc 转成纯文本或 docx再做内容提取。网上很多人说“无法预览 doc”多半就是卡在这一层工具不匹配。2.2 用 LibreOffice 批量转换 doc 为 txt如果你的系统已经装了 LibreOffice最省事。命令如下soffice --headless --convert-to txt:Text --outdir ./txt 长安大学0607c语言A卷试题(卷.doc这个命令的逻辑是soffice是 LibreOffice 的主程序--headless表示无界面运行适合服务器和批处理--convert-to txt:Text指定输出格式冒号后的Text是 Writer 的文本过滤器名称用来保留尽量干净的纯文本--outdir ./txt指定输出目录。转换后的 txt 会按原始排版换行但图片、公式和部分表格线会丢失对试题文本提取来说够用。文件名里有空格或中文时务必用双引号包住。批量转换时我习惯用 find 配合 execfind . -name *.doc -exec soffice --headless --convert-to txt:Text --outdir ./txt {} \;这个命令会逐个启动 soffice 进程稳定但慢。如果文件很多建议先用上面单条命令试一个确认输出正常再全量跑。另外LibreOffice 转换出来的 txt 默认编码通常是 UTF-8但老文档可能带 BOM后面读取时要留意。2.3 用 antiword 处理纯文本场景如果只是临时看一份卷子的内容antiword 更轻量。在 Ubuntu 上安装sudo apt install antiword转换命令antiword -m UTF-8 长安大学0607c语言A卷试题(卷.doc c_lang.txt参数-m UTF-8是告诉 antiword 使用 UTF-8 映射表输出中文不加这个参数中文可能变成乱码。antiword 的好处是只输出文本没有多余标记缺点是遇到新版 doc 或复杂表格时支持不稳定有时会漏内容。另一个工具 catdoc 也类似但我更常用 antiword因为它对中文老文档的兼容性更好。在使用前先用 file 确认文件格式file 长安大学0607c语言A卷试题(卷.doc如果输出是Composite Document File说明确实是老 doc如果输出是Microsoft Word 2007其实里面是 docx 但后缀写成 .doc直接把文件改成 .docx 再处理省掉转换那一步。2.4 转换后先看一眼文本再写解析规则转换完成后不要急着写正则。先看文件前面几行head -50 c_lang.txt这一步能帮你发现三个问题页眉页脚混进了正文选择题选项被异常换行切成两半题号后面的顿号可能是中文全角或英文半角。这些细节直接决定后面的正则怎么写。我一般会再看一下文件末尾确认最后一道题有没有被截断。如果文件编码不对head 输出会带或乱码这时得用file或iconv再排查。3. 用 Python 把试题从文本里抠出来3.1 从转换后的文本中识别题型标题转换出的纯文本大概长这样一、选择题每题2分共20分 1. 以下哪个是合法标识符 A. int B. 1x C. _name D. char 2. 关于指针的说法错误的是。 二、填空题每空1分共10分我的思路是先用正则把“一、选择题”这类题型标题找出来再以它们为边界切分。正则这样写import re section_pattern re.compile( r^[一二三四五六七八九十][、.]\s* r(选择题|填空题|判断题|简答题|程序阅读题|程序设计题), re.M )这里re.M是多行模式^才能匹配每一行的开头。没有re.M^只匹配整个字符串的开始位置第一个题型标题之后的“二、填空题”全会被漏掉。[、.]同时兼容中文顿号、全角句点和英文句点老试卷里这三种符号混用很常见。\s*用来吃掉题型标题和题名之间的空白。3.2 按题型切分并保存为结构化数据找到标题后用切片把每个题型的内容拆开import re, json def parse_exam(text): section_pat re.compile( r^[一二三四五六七八九十][、.]\s* r(选择题|填空题|判断题|简答题|程序阅读题|程序设计题), re.M ) matches list(section_pat.finditer(text)) if not matches: return {error: no section found} sections {} for i, match in enumerate(matches): section_name match.group(0).strip() start match.end() end matches[i 1].start() if i 1 len(matches) else len(text) sections[section_name] text[start:end].strip() return sections with open(c_lang.txt, r, encodingutf-8) as f: content f.read() sections parse_exam(content) print(json.dumps(sections, ensure_asciiFalse, indent2))逻辑说明finditer返回所有匹配的位置当前题的结束位置match.end()到下一个题型的开始位置next_match.start()之间就是这道题型的所有内容。最后一个题型用len(text)作为截止点。这样切分不会受到题号的干扰因为题号属于题型内部内容。参数说明ensure_asciiFalse让json.dumps不把中文转成\uXXXX直接输出可读中文。输出到文件时建议用 UTF-8 编码保存方便后续用 C 语言或其他脚本处理。3.3 正则表达式里那几个值得注意的参数正则提取最容易翻车的三个点一是不加re.S。如果你用更简单的写法r一、选择题(.*?)二、去抓内容因为.默认不匹配换行而试题文本里选择题和下一题之间必然有换行所以模式永远匹配不到。不加re.S看到的现象就是切片结果为空。二是贪婪与懒惰混淆。.*是贪婪模式会一直吃到最后一个可能位置.*?是懒惰模式匹配到第一个结束位置就停。切题型时用懒惰模式更安全否则“二、”后面的所有内容会被吞掉。三是中文字符的匹配范围。[一二三四五六七八九十]比[1-9]更符合老试卷排版有些试卷用“一.”而不是“1.”。如果你不确定可以先用grep或python -c跑一个最小样例确认。我在写正式脚本前通常会用 regex101 把正则粘贴进去模拟一段转换后的文本先调试避免反复在完整脚本里试错。3.4 处理残留空白和编码问题转换后的 txt 经常带着 Windows 换行符\r\n和空行。读取时先统一清洗lines content.splitlines() clean_lines [line.strip() for line in lines if line.strip()] clean_text \n.join(clean_lines)splitlines会自动拆掉\r\n和\nstrip去掉每行首尾空格空行直接跳过。这样得到的clean_text干净很多正则匹配率也更高。如果open时报UnicodeDecodeError先别急着改编码用file c_lang.txt看下实际编码。一般老文档转出来是 UTF-8 带 BOM可以用utf-8-sig读如果是纯 GBK 输出就用gb18030with open(c_lang.txt, r, encodinggb18030, errorsreplace) as f: content f.read()errorsreplace会把无法解码的字节替换成至少保证程序不中断。这个参数在批量处理十几个文件时特别有用不会因为某个文件编码特殊导致整个任务崩掉。4. 用 C 语言做文本分析的三个关键点4.1 以二进制或文本方式打开文件的区别如果你更习惯用 C 语言做文本分析比如统计题型数量、查找关键词第一步就是打开文件。fopen的 mode 参数最常见的是r和rb。在 Windows 下r模式会在读取时把\r\n自动转成\n而rb不会做任何转换在 Linux 下两种模式没有区别。处理从 Windows 挪过来的 txt我统一用rb读进来再手动处理换行行为最可控。一个一次性读完整个文件的函数#include stdio.h #include stdlib.h char *read_file(const char *path, long *out_size) { FILE *fp fopen(path, rb); if (fp NULL) { perror(fopen); return NULL; } fseek(fp, 0, SEEK_END); long size ftell(fp); fseek(fp, 0, SEEK_SET); char *buf (char *)malloc(size 1); if (buf NULL) { fclose(fp); return NULL; } fread(buf, 1, size, fp); buf[size] \0; fclose(fp); if (out_size) *out_size size; return buf; }参数说明SEEK_END配合ftell拿到文件大小malloc(size 1)多出的一个字节用来放字符串结束符\0否则后面调用strlen或strstr会越界。这个函数对中等大小的试题文本完全够用。记住调用结束后free(buf)。4.2 字符数组与指针别在 EOF 和字符串结束符上翻车新手最容易把 EOF 和字符串结束符搞混。EOF 的值是-1类型是int字符串结束符是\0值是0。用fgetc逐字符读文件时循环变量必须声明为int不能是charint c; while ((c fgetc(fp)) ! EOF) { putchar(c); }这里有个 C 语言基础知识点char是否存在符号取决于实现如果char是无符号类型fgetc返回的-1被截断成255和 EOF 永远不等循环就死循环了。这也是面试里常考的指针与字符数组的边界问题。字符数组char buf[100]和指针char *p buf在文本处理中常能互换但sizeof(p)拿到的是指针大小不是缓冲区长度。处理字符串时strcpy、strcmp、strlen都依赖\0作停止条件所以在 4.1 里一定要给缓冲区追加\0。4.3 用 strstr 和 strtok 提取题目编号定位子串用strstr比如找“程序设计题”在哪个位置char *pos strstr(buf, 程序设计题); if (pos ! NULL) { printf(found at offset %ld\n, pos - buf); }strstr返回子串首地址pos - buf得到偏移量类型是ptrdiff_t打印用%ld。这里 buf 必须是以\0结尾的字符串否则strstr会越界读取。按行处理可以用strtok_rchar *save NULL; char *token strtok_r(buf, \r\n, save); while (token ! NULL) { // 处理每一行文本 token strtok_r(NULL, \r\n, save); }参数说明strtok_r的第三个参数是保存拆分状态的指针它是strtok的线程安全版本分隔符用\r\n同时兼容 Windows 和 Unix 换行。注意strtok系列会修改原字符串把分隔符替换成\0所以如果后续还要用原始内容先strdup一份。用strtok_r切完行之后再结合 4.2 的知识逐行判断题号一个简单的 C 语言统计分析程序就出来了。5. 把真题变成题库批量处理和最小验证技巧5.1 用 Python 循环批量转换 doc如果你手里不止一份试卷而是“长安大学0607c语言A卷”“B卷”“历年”等一排 doc逐个手动转换太傻。我在 2.2 里给过 find 命令但更好的方式是把转换和控制流放在 Python 里import subprocess, glob for doc_path in glob.glob(*.doc): subprocess.run([ soffice, --headless, --convert-to, txt:Text, --outdir, ./txt, doc_path ], checkTrue)这里glob.glob(*.doc)匹配所有 doc 文件subprocess.run调用外部命令。参数说明checkTrue表示返回码非 0 时抛出CalledProcessError否则某个文件转换失败会被静默跳过后面解析时才发现数据缺失排查成本高。5.2 用一个函数校验每次转换后的题数转换完成后光看文件大小不够。我习惯写一个校验函数统计每个题型题目编号的最大值看是否连续import re def check_questions(section_text): nums [int(m) for m in re.findall(r^(\d)[.、], section_text, re.M)] return max(nums) if nums else 0, nums逻辑说明这个函数用行首匹配\d[.、]提取题号然后返回最大题号和题号列表。如果题目编号不连续比如1,2,3,5说明转换丢行或者正则切分时漏掉了一道题。批量跑完后把所有文件的结果打印成一个对照表一眼就能看出哪份卷子有问题。试卷里有时会用括号包住题号比如 “1”这时正则要改成r^\((\d)\)。5.3 直接读入内存再逐行解析比 fgets 更省心最后一招也是我处理这种文本时最顺手的方式用 4.1 的read_file把整个文件读进内存再按行解析。不要总依赖fgets循环因为一行题目可能很长缓冲区大小不好定太小会截断题目。一次性读入后用strtok_r切行逻辑干净而且能复用同一个缓冲区做strstr搜索。char *buf read_file(exam.txt, NULL); if (buf NULL) return 1; char *save NULL; char *line strtok_r(buf, \r\n, save); while (line ! NULL) { // 你的分析逻辑 line strtok_r(NULL, \r\n, save); } free(buf);这个模式看似简单但能把 C 语言处理文本时 80% 的边界问题挡在门外不用管理动态增长的缓冲区不会因为某一行特别长而断行也不会忘记处理\r。把它封装成工具函数以后处理任何配置文件、日志文件都能复用。调用方记得free(buf)。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价