#!/usr/bin/env/python # -*- coding:utf-8 -*- # paper3_process: 第三类word试卷模式, 题目和答案分开的情况 # split2one_item:将所有行文本 按题型分大类,再在每个大类中切分每个题目 # split2one_item_by_topicno:将所有行文本 按题型分大类,再在每个大类中按题号切分每个题目 """ 总共3种方案:1、教师用卷;2、按题号切分;3、划分试题和答案,再按题号切分 """ from structure.ans_structure import * from utils.insert_keywords import get_con from utils.item_resplit import resplit from utils.washutil import table_label_cleal from structure.stems_structure import stems_structure_byno from utils.item_type_line import get_item_head_info from utils.topic_no import judge_item_no_type, get_right_no from utils.stem_ans_split import stem_ans_split from collections import Counter from pprint import pprint def items_ans_reform(items_list, ans_list): """ 第三种word试卷格式, 题目和答案分开的情况 答案也有几种类型:带题型? :param sent_list: :param split_point: :return: """ con1 = list(filter(lambda x: x.strip() != "", items_list)) # 题目 anss1 = list(filter(lambda x: x.strip() != "", ans_list)) # 答案,list中的每个元素为一行 if re.match(".+?省.+?试[卷题]", con1[-1]): con1 = con1[:-1] if re.match(".+?省.+?试[卷题]|.*?答题?[卷卡页]", anss1[0]): anss1 = anss1[1:] print("-------答案页----------") # 答案页可能全是图片 # pprint(anss1) print("-------答案页--end--------") #--------------答案页也包含题目的情况----------但可能题目不存在----------------------- ans_n = re.findall("【答案】", "\n".join(anss1)) if ans_n and len(ans_n) == len(re.findall("【解析】", "\n".join(anss1)))>2: # 带相同个数的答案和解析 print("答案页中有相同个数的答案和解析") item_res = split_by_keywords(anss1) if type(item_res) != str and item_res[0]: # 还要判断题目是否为空 if len([i["item_id"] for i in item_res[0] if len(i["stem"].strip()) < 5]) < 2: return item_res # ----------------- 【解析 题目】---------------------------- print('---------------解析 题目-------------------') ress = stems_structure_byno(con1) if type(ress) == str: return ress else: item_res, all_type, item_type_classify, item_no_type, item_type_num, new_item_no = ress # 全题目(不含解析)的结构化 # 将空题目去掉 new_res = [] for k, sub_res in enumerate(item_res): if sub_res['stem'].strip(): sub_res['stem'] = del_no(sub_res['stem']) new_res.append(sub_res) item_res = new_res # pprint(item_res) # 先对题目的切分结果进行纠正!!!!! item_res = resplit(item_res) print("item_type_classify:", item_type_classify) print("item_type_num:", item_type_num) print('----------解析 答案---------------') # -------------解析 答案--------------------------- # 分两种情况:1>>答案中又按题型排列, 如一、选择题 1.答案 2.答案 # 2>>答案中不含题型关键字,只按序号排列 # 3>>答案中不含题型关键字,且题目中也没有,all_type, item_type_classify为空 # print(anss1) new_ans_no1 = [] rd1_is_fail = 0 have_type_line = re.search(r"[一二三四五六七八九十]\s*[、..、]\s*[^必考基础综合中等((\[]{2,5}题", "\n".join(anss1)) if have_type_line: # 这里的anss1的清洗不应该影响rd2_is_fail中的原始文本!!先不修改看看再说 anss1_cy = anss1.copy() # 复制一份,保证不能影响后面 while re.search(r"

[A-F]

|[A-F]|([A-F]\s*){3,}", anss1_cy[0]) is None and \ (re.search(r"[\u4e00-\u9fa5]", anss1_cy[0]) is None or re.search(r"[一二三四五六七八九十]\s*[、..、]\s*()?\s*.{2,5}题", anss1_cy[0]) is None): del anss1_cy[0] # 答案中的题型 all_type2 = re.findall(r"\n\s*[一二三四五六七八九十]\s*[、..、::]\s*([^必考基础综合中共等::((\[]{2,5}题)|" r"\n\s*[、..、::]?\s*(单选题|非?选择题|不定选择题|多选题|填空题|计算题|[解简]答题|实验题|作图题|论述题|探究题)", "\n" + "\n".join(anss1_cy)) all_type2 = ["".join(a) for a in all_type2] # '本大题' 后面处理 print("答案中的题型:", all_type2) ans_str = "\n" + "\n".join(anss1_cy) # try: item_res, rd1_is_fail = anss_structure_with_type(item_res, ans_str, all_type, all_type2, item_type_num, item_type_classify) # except: # rd1_is_fail = 1 # 没有题型行或第一次解析失败 rd2_is_fail = 0 if not have_type_line or rd1_is_fail: # 答案中没有题型行 或题型行名称不规范 print('没有题型行或题目和答案的题型个数不一致或第一次解析失败') anss1 = list( map(lambda x: re.sub(r"(\n|^)\s*[一二三四五六七八九十]\s*[、..、::]?\s*(

)?" r"(\s*.{2,5}题.+?分\s*[.。]?\s*$|.*?[((].+?[得共]\d+分.*?[))].*?$" r"|\s*.{2,5}题\s*([((].+?[))])?).*?$|(\n|^)\s*[^\d]{2,5}题(.+?分\s*[))])?\s*$", "", x), anss1)) # print("anss1:", anss1) raw_item_res = item_res # try: item_res = ans_structure_step1(anss1, item_type_classify, item_res) # 答案整体结构化 if str(raw_item_res) != str(item_res): rd2_is_fail = 1 # except: # rd2_is_fail = 1 # for i, one_item in enumerate(item_res): # item_res[i].update({'key': "", 'parse': ""}) # return item_res, item_no_type, rd2_is_fail for i, one_item in enumerate(item_res): if 'key' not in one_item: item_res[i]['key'] = "" if 'parse' not in one_item: item_res[i]['parse'] = "" return item_res, item_no_type, rd2_is_fail def ans_block_split(ans_list, item_res): anss1 = list(filter(lambda x: x.strip() != "", ans_list)) if re.match(".+?省.+?试[卷题]|.*?答题?[卷卡页]", anss1[0]): anss1 = anss1[1:] rd1_is_fail = 0 have_type_line = re.search(r"[一二三四五六七八九十]\s*[、..、]\s*[^必考基础综合中等((\[]{2,5}题", "\n".join(anss1)) if have_type_line: # 这里的anss1的清洗不应该影响rd2_is_fail中的原始文本!!先不修改看看再说 anss1_cy = anss1.copy() # 复制一份,保证不能影响后面 while re.search(r"

[A-F]

|[A-F]|([A-F]\s*){3,}", anss1_cy[0]) is None and \ (re.search(r"[\u4e00-\u9fa5]", anss1_cy[0]) is None or re.search(r"[一二三四五六七八九十]\s*[、..、]\s*()?\s*.{2,5}题", anss1_cy[0]) is None): del anss1_cy[0] # 答案中的题型 all_type2 = re.findall(r"\n\s*[一二三四五六七八九十]\s*[、..、::]\s*([^必考基础综合中共等::((\[]{2,5}题)|" r"\n\s*[、..、::]?\s*(单选题|非?选择题|不定选择题|多选题|填空题|计算题|[解简]答题|实验题|作图题|论述题|探究题)", "\n" + "\n".join(anss1_cy)) all_type2 = ["".join(a) for a in all_type2] # '本大题' 后面处理 print("答案中的题型:", all_type2) ans_str = "\n" + "\n".join(anss1_cy) item_res, rd1_is_fail = anss_structure_with_type(item_res, ans_str, [], all_type2, [], {}) # 没有题型行或第一次解析失败 rd2_is_fail = 0 if not have_type_line or rd1_is_fail: # 答案中没有题型行 或题型行名称不规范 print('没有题型行或题目和答案的题型个数不一致或第一次解析失败') anss1 = list( map(lambda x: re.sub(r"(\n|^)\s*[一二三四五六七八九十]\s*[、..、::]?\s*(

)?" r"(\s*.{2,5}题.+?分\s*[.。]?\s*$|.*?[((].+?[得共]\d+分.*?[))].*?$" r"|\s*.{2,5}题\s*([((].+?[))])?).*?$|(\n|^)\s*[^\d]{2,5}题(.+?分\s*[))])?\s*$", "", x), anss1)) # print("anss1:", anss1) raw_item_res = item_res # try: item_res = ans_structure_step1(anss1, {}, item_res) # 答案整体结构化 if str(raw_item_res) != str(item_res): rd2_is_fail = 1 return item_res def split_by_keywords(con_list): """ 第一种试卷格式:教师用卷,含答案和解析关键字 切分思路: 1.根据大题型分,再按【答案|解析】初步拆分题目,再在‘解析’和‘答案’间细分‘题干’和‘解析’ :param con_list: :return: 每个切分后的题目组成的dict """ # items_con = "\n" + "\n".join(con_list) # judge_item_no_type(items_con) # item_no_type = 1 # all_con = table_label_cleal() # item_no = [int(no) for no in re.findall(r'\n+\s*([1-9][0-9]?)\s*[..、、]', all_con)] # if len(item_no) <= 2: # item_no_type = 2 # item_no = [int(no) for no in re.findall(r'\n+\s*[((]\s*([1-9][0-9]?)\s*[))]\s*[..、、]?', all_con)] # if len(item_no) > 3: # 去掉多余空格,作用不大 con2 = ["【delete】" if (k < len(con_list) - 1 and v.strip() == "" and ( re.match(r"【(答案|解析)】|(答案|解析)\s*[::]| 0 and v.strip() == "" and ( re.match(r"【(答案|解析)】$|(答案|解析)\s*[::]", con_list[k - 1].strip()) or re.match(r"[a-z<>/\s]*?[一二三四五六七八九十]\s*[、..、]\s*[^必考基础综合中等]{2,4}题", con_list[k - 1].strip()))) else v for k, v in enumerate(con_list)] con3 = list(filter(lambda x: x != "【delete】", con2)) while con3 and con3[-1].strip() == "": del con3[-1] while con3 and con3[0].strip() == "": del con3[0] con3.append("") # 不然最后一个题就漏掉了 # 开头没用信息处理 con3[0] = re.sub(r"([一二三四五六七八九十]\s*[、..、]\s*[^必考基础综合中等]{2,4}题)", r"\n\1", con3[0]) while con3 and (re.search(r"[\u4e00-\u9fa5]", con3[0]) is None or (re.search(r"[一二三四五六七八九十]\s*[、..、]\s*[^必考基础综合中等]{2,4}题", con3[0]) is None and re.match("\s*[1-9]\s*[、..、].+?", con3[0]) is None)): del con3[0] # ----------------------------------开始结构化--------------------------------------------- items_con = "\n" + "\n".join(con3) # 初步获取题号,题号类型 items_con, item_no_info, item_no_type = judge_item_no_type(items_con) # 1、获取题型行信息、按题型行切分 con4, title_info_dict, choice_class = get_item_head_info(items_con) all_type = title_info_dict["all_type"] select_type_id = title_info_dict["select_type_id"] each_item_score, each_item_score2 = title_info_dict["each_item_score"], title_info_dict["each_item_score2"] # 2、据是否有题型行分两步进行 # 没有做拆图处理 res = [] if not all_type: print("不存在大题题型行或题型行格式有问题") if len(re.findall(r"\n\s*【答案】", items_con)) != len(re.findall(r"\n\s*【解析】", items_con)): return "不存在大题题型行或题型行格式有问题" else: item_no = [] subcon = re.split(r"((?<=\n)\s*【答案】|(?<=\n)\s*【解析】)\n?", items_con.strip()) pattern1 = re.compile(r"([1-9]|[1-9][0-9])\s*[..、、].+?") if re.match(pattern1, subcon[0].strip()): st_id = re.match(pattern1, subcon[0].strip()).group(1) if int(st_id) > 1: item_no.append(int(st_id)) else: item_no.append(1) else: item_no.append(1) if len(subcon) == 5: # 只有1道题 dd = dict(zip(["stem", "key", "parse"], re.split(r"(?<=\n)\s*【答案】|(?<=\n)\s*【解析】", table_label_cleal(items_con)))) dd["type"] = "" dd["stem"] = re.sub(r"^\d+\s*[..、、]", "", dd["stem"][:5]) + dd["stem"][5:] dd["score"] = 0 dd["errmsgs"] = [] dd["item_id"] = item_no[0] # 要用实际id 不是索引序号 res.append(dd) else: # ------在下一题【解析】在本题【答案】之间找到下一题【stem】的位置-------- all_item, item_no, errmsg_dict, count = get_con(subcon, item_no_type, item_no, index=0) # item_no.extend(local_item_no) for idk, one_item in enumerate(all_item): if one_item: dd = dict(zip(["stem", "key", "parse"], re.split(r"(?<=\n)\s*【答案】\n?|(?<=\n)\s*【解析】\n?", table_label_cleal(one_item)))) dd["type"] = "" dd["stem"] = re.sub(r"\d+\s*[..、、]", "", dd["stem"][:5]) + dd["stem"][5:] dd["score"] = 0 dd["errmsgs"] = [errmsg_dict[idk]] if idk in errmsg_dict else [] dd["item_id"] = item_no[idk] res.append(dd) else: if len(all_type) != len(con4): print("存在题型行没有换行") return "存在题型行末尾没有换行,请在所有题型行末尾重新换行" # 放第【2】种方案中进行处理 else: # if "非选择题" in all_type: # return "第" + str(all_type.index("非选择题")+1) + "大题的题型不明确" index = 0 # 每个大题的第一题的题号索引位置 for num, one_type in enumerate(con4): count = 1 if len(re.findall(r"\n\s*【答案】", one_type)) == len(re.findall(r"\n\s*【解析】", one_type)): subcon = re.split(r"((?<=\n)\s*【答案】|(?<=\n)\s*【解析】)\n?", one_type.strip()) # index根据第一道题的题号进行纠正 item_no = [] pattern1 = re.compile(r"([1-9]|[1-9][0-9])\s*[..、、].+?") if re.match(pattern1, subcon[0].strip()): st_id = re.match(pattern1, subcon[0].strip()).group(1) if num == 0 and int(st_id) != 1: index = int(st_id) - 1 item_no.append(int(st_id)) else: item_no.append(index+1) if len(subcon) == 5: # 只有1道题 dd = dict(zip(["stem", "key", "parse"], re.split(r"(?<=\n)\s*【答案】|(?<=\n)\s*【解析】", table_label_cleal(one_type)))) dd["type"] = all_type[num] dd["stem"] = re.sub(r"^\d+\s*[..、、]", "", dd["stem"][:5]) + dd["stem"][5:] dd["score"] = each_item_score[num] dd["errmsgs"] = [] dd["item_id"] = item_no[0] # 要用实际id 不是索引序号 if not dd["score"] and each_item_score2 and str(dd["item_id"]) in each_item_score2.keys(): dd["score"] = each_item_score2[str(dd["item_id"])] if select_type_id and dd["item_id"] in select_type_id: dd['is_optional'] = 'true' if dd["score"] == 0.0 and title_info_dict["total_score"][num] > 0.0: dd["score"] = title_info_dict["total_score"][num] res.append(dd) else: # ------在下一题【解析】在本题【答案】之间找到下一题【stem】的位置,再按此3个关键字进行 切分-------- all_item, item_no, errmsg_dict, count = get_con(subcon, item_no_type, item_no, all_type=all_type, num=num, index=index) # item_no.extend(local_item_no) for idk, one_item in enumerate(all_item): dd = dict(zip(["stem", "key", "parse"], re.split(r"(?<=\n)\s*【答案】\n?|(?<=\n)\s*【解析】\n?", table_label_cleal(one_item)))) dd["type"] = all_type[num] dd["stem"] = re.sub(r"\d+\s*[..、、]", "", dd["stem"][:5]) + dd["stem"][5:] dd["score"] = each_item_score[num] dd["errmsgs"] = [errmsg_dict[idk]] if idk in errmsg_dict else [] dd["item_id"] = item_no[idk] # idk+1+index 为序号 if choice_class: for k, v in choice_class.items(): if dd["item_id"] in v: dd["type"] = k + "选题" # elif len(choice_class) == 1: # dd["type"] = "多选题" if k == "单" else "单选题" if not dd["score"] and each_item_score2 and str(dd["item_id"]) in each_item_score2.keys(): dd["score"] = each_item_score2[str(dd["item_id"])] if select_type_id and dd["item_id"] in select_type_id: dd['is_optional'] = 'true' res.append(dd) # pprint(res) else: return "第" + str(num + 1) + "大题《" + all_type[num] + "》中【答案】或【解析】格式有误或其中某道题中出现多个相同关键字或漏关键字" index += count for i, one_item in enumerate(res): if 'key' not in one_item: res[i]['key'] = "" if 'parse' not in one_item: res[i]['parse'] = "" return res, item_no_type def split_by_topicno(con_list): """ 第二种试卷格式: 不同时或都不含有{答案}和{解析}关键字 按题号切分每个题目 将所有行文本 按题型分大类,再在每个大类中切分每个题目 :param con_list: 所有行文本组成的list :return: [{},{}] """ con1 = list(filter(lambda x: x.strip() != "", con_list)) ress = stems_structure_byno(con1) # 按题号切分后的初步结构化 if type(ress) == str: return ress else: res, all_type, item_type_classify, item_no_type, item_type_num, new_item_no = ress # res, all_type, item_type_classify = stems_structure_byno(con1) print("item_type_num:", item_type_num) # pprint(res) # 可能存在有的题目有解析,有的没有 for k, one_res in enumerate(res): if re.search('\n【(答案|[解分][析答]|详解|点[评睛]|考点|专题)】', one_res["stem"]): case = "case1" # 默认有“答案”关键字 if re.search(r'\n【答案】|[\n】]\s*答案\s*[::]', one_res["stem"]) is None: # 没“答案”关键字 case = "case0" dd1 = stem_ans_split(one_res, case) # 对切分后的每道题再细分 one_res["stem"] = dd1["stem"] del dd1["stem"] one_res.update(dd1) else: # 没有解析的情况 one_res.update({"key": "", "parse": ""}) one_res["stem"] = del_no(one_res["stem"], item_no_type) if 'pic' in one_res: one_res["stem"] += "\n" + "\n".join(one_res["pic"]) del one_res["pic"] # 先对题目的切分结果进行纠正!!!!! res = resplit(res) # 对最后一个题后面带个别答案(无答案页) if res: pattern1 = re.search('\n\s*([1-9]|[1-9][0-9])\s*[..、、]\s*(解\s*[::]|【解析|【答案)', res[-1]["stem"]) if pattern1: breakp = pattern1.start() ans_str = res[-1]["stem"][breakp:] ans_no_info = pre_get_item_no(ans_str, item_no_type) ans_no, ans_no_idx = get_right_no(ans_no_info) all_ans = [del_no(ans_str[i:j]) for i, j in zip(ans_no_idx, ans_no_idx[1:] + [None])] res[-1]["stem"] = res[-1]["stem"][:breakp] res = get_ans_match(res, all_ans, ans_no) else: ans_str = res[-1]["stem"] + res[-1]["parse"] ans_no_info = pre_get_item_no(ans_str, item_no_type) ans_no, ans_no_idx = get_right_no(ans_no_info) if len(ans_no) == len(res): all_ans = [del_no(ans_str[i:j]) for i, j in zip(ans_no_idx, ans_no_idx[1:] + [None])] res[-1]["stem"] = res[-1]["stem"][:ans_no_idx[0]] res = get_ans_match(res, all_ans, ans_no) elif ans_no_idx: try: ans_no1, table_ans, st = get_table_ans(res[-1]["stem"][:ans_no_idx[0]], [], flag=1) if table_ans and 0 < ans_no[0] - ans_no1[-1] < 3: all_ans = table_ans all_ans.extend([del_no(ans_str[i:j]) for i, j in zip(ans_no_idx, ans_no_idx[1:] + [None])]) new_ans_no = ans_no1 new_ans_no.extend(ans_no) if st >= 0: res[-1]["stem"] = res[-1]["stem"][:st] else: res[-1]["stem"] = res[-1]["stem"][:ans_no_idx[0]] res = get_ans_match(res, all_ans, new_ans_no) except: if len(ans_no)>4 and all([True if not one_res["key"] and not one_res["parse"] else False for one_res in res[:-1]]): all_ans = [del_no(ans_str[i:j]) for i, j in zip(ans_no_idx, ans_no_idx[1:] + [None])] res[-1]["stem"] = res[-1]["stem"][:ans_no_idx[0]] res = get_ans_match(res, all_ans, ans_no) # 没有识别出答案切分点的情况,很可能答案里的部分也当成题文进行拆分,所以先判断下是否有相同的id all_no = [one_res['item_id'] for one_res in res] if len(list(set(all_no))) - len(all_no) < -2: Count_no = sorted(dict(Counter(all_no)).items(), key=lambda d: d[1], reverse=True) if Count_no[0][1] > 1: split_idx = [i for i, no in enumerate(all_no) if no == Count_no[0][0]][1] for one_res in res[split_idx:]: if re.search("[((]\s+[))]|(等于|存在|[是有为])多少|求.*?[??]", one_res["stem"] + "\n" + one_res["parse"]) is None: bef_no = [k for k, j in enumerate(res[:split_idx]) if j["item_id"]==one_res["item_id"]] if bef_no and not res[:split_idx][bef_no[0]]["parse"]: res[:split_idx][bef_no[0]]["parse"] = one_res["stem"] + "\n" + one_res["parse"] return res[:split_idx],item_no_type return res, item_no_type