washutil.py 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011
  1. #!/usr/bin/env/python
  2. # -*- coding:utf-8 -*-
  3. # 本文件包含以下函数
  4. # table_label_cleal:去掉表格中的换行符
  5. # html_cleal :html文件清洗
  6. # wash_after: 处理最终结果多余的换行符
  7. import datetime
  8. import re
  9. import shutil
  10. # from operator import itemgetter
  11. # from itertools import groupby
  12. # from PIL import Image
  13. import base64, os, random
  14. import time
  15. import requests
  16. import hashlib
  17. from pprint import pprint
  18. # from bs4 import BeautifulSoup
  19. # UPLOAD_FOLDER = config.UPLOAD_FOLDER
  20. import configs
  21. from utils.equation_extract import get_equation_instr, get_simpstr2eqn
  22. from utils.field_eq2latex import get_latex
  23. from utils.html_again_parse import css_label_wash
  24. # from structure.structure_main import WordParseStructure
  25. logger = configs.myLog(__name__, log_cate="ruku_log").getlog()
  26. def table_label_cleal(con):
  27. """
  28. 去掉表格中的【换行符】
  29. """
  30. # print(con)
  31. # print('------------------------------------------')
  32. con = re.sub(r"\n(\s|\n|\t)+", "\n", con)
  33. count = 1
  34. while re.search(r"</?[a-z]+>\n(</?[a-z]+>|<td\s+\n*[a-z=\"\d]+>)", con, re.S) and count <= 10:
  35. con = re.sub("(</?t[dr]>|</?table>|</?tbody>|</?div>)\n(</?t[dr]>|</div>|</?table>|</?tbody>|<p>)",
  36. r"\1\2", con, flags=re.S)
  37. con = re.sub(r'(</?t[rd]>)\n(<td\s.+?>)', r'\1\2', con, flags=re.S)
  38. count += 1
  39. # if re.search(r"<table>(.|\n)+?</table>", con, re.S|re.M):
  40. # aa = re.search(r"(<table>(.|\n)+?</table>)", con, re.S|re.M)
  41. # con = con.replace(aa.group(1),aa.group(1).replace("\n",""))
  42. # 将空表格的情况去掉
  43. con = re.sub(r'<table>[\s\n\t]*?<tbody>[\s\n\t]*?(<tr>[\s\n\t]*?<td[^<>]*?>[\s\n\t]*?<p>[\s\n\t]*?</p>'
  44. r'[\s\n\t]*?</td>[\s\n\t]*?</tr>[\s\n\t]*?)+</tbody>[\s\n\t]*?</table>[\s\n\t]*?<p>', "", con,
  45. flags=re.S)
  46. con = re.sub(r'(</table><p>)\s*([((]\s*\d\s*[))])', r'\1\n\2', con)
  47. return con
  48. def base642img(html_data, wordid):
  49. """
  50. 【基于mathjax渲染输出是css-html格式】
  51. 将base64编码的图片保存到本地
  52. :return:
  53. """
  54. # 二进制图片进行转化, 按“word_id”建立文件夹
  55. # time_str = datetime.datetime.strftime(datetime.datetime.now(), '%Y_%m_%d')
  56. # file_path = configs.IMG_FOLDER + '/' + str(self.wordid)
  57. # if not os.path.exists(file_path):
  58. # os.makedirs(file_path)
  59. # else:
  60. # 思路1:删除图片,重建文件夹,【所有的新图片都是以base64格式传过来的】
  61. # shutil.rmtree(file_path)
  62. # os.makedirs(file_path)
  63. # 思路2:每一次再解析都将base64图片保存到本地再以路径形式返回
  64. # st = len(os.listdir(file_path)) # 不要以序号索引的形式命名
  65. # 统计所有base64编码
  66. all_base64_image = re.findall(r'(<img ([a-z]+="[^"]*?" )?src="(data:image[^>"]+?)"(.*?)\s*/?>)', str(html_data), flags=re.S)
  67. if all_base64_image:
  68. file_path = configs.IMG_FOLDER + '/' + str(wordid)
  69. if not os.path.exists(file_path):
  70. os.makedirs(file_path)
  71. # 新图片命名
  72. name_list = random.sample(range(100000, 999999), len(all_base64_image))
  73. for n, img in enumerate(all_base64_image):
  74. img1 = img[2].split(",", maxsplit=1)
  75. img_type_info = re.search("data:image/(.+?);base64", img1[0])
  76. img_type = img_type_info.group(1) if img_type_info else ""
  77. # 可能还有alt和style的属性,暂时先不要
  78. w_info = re.search('( width="\d+")', img[3])
  79. h_info = re.search('( height="\d+")', img[3])
  80. img_data = base64.b64decode(str(img1[-1]))
  81. if img_type:
  82. # save_path = os.path.join(configs.new_img_ip, get_md5(n)+"."+img_tape)
  83. img_name = "new_image" + str(int(time.time())) + str(name_list[n]) + "." + img_type
  84. save_path = os.path.join(file_path, img_name)
  85. with open(save_path, 'wb') as f:
  86. f.write(img_data)
  87. # self.localnewpic_list.append(save_path)
  88. # put_key = "/zyk/uploadfiles/wording/" + str(self.wordid) + "/{}".format(img_name)
  89. # self.put_key_list.append(save_path)
  90. flag_behind = '" />'
  91. if w_info and h_info:
  92. flag_behind = '"' + w_info.group(1) + h_info.group(1) + ' />'
  93. temp_img = '<img src="' + configs.new_img_ip + '/' + str(wordid) + '/' + img_name + flag_behind
  94. # new_img = '<img src="http://' + configs.public_bucket_addr + put_key + '" />'
  95. html_data = html_data.replace(img[0], temp_img)
  96. return html_data
  97. class HtmlWash():
  98. def __init__(self, html, wordid, is_reparse=0, img_url="", must_latex=0):
  99. """
  100. html文本清洗
  101. 批量再解析中,新增图片信息替换的文本返回作为ocr保存文本,
  102. 继续往下清洗的文本,则进入结构化解析逻辑中
  103. """
  104. # super().__init__(html, wordid, is_reparse, must_latex)
  105. self.html = html
  106. self.img_url = img_url
  107. self.wordid = wordid
  108. self.is_reparse = is_reparse
  109. self.must_latex = must_latex
  110. # self.put_key_list = []
  111. # self.localnewpic_list =[]
  112. self.sub_list = ["</?div>", "</?b>", "</?caption>", "</?center>", "</?cite>", "</?code>", "</?colgroup>",
  113. "</?menu>", "</?dd>", "</?dir>", "</?li>", "</?em>", "</?article>", "</?header>", "</?ruby>",
  114. "</?summary>", "</?details>", "</?strong>", "</?strike>", "</?small>", "</?select>",
  115. "</?section>", "</?script>", "</?[su]>", "</?var>", "</?ul>", "</?tt>", "</?title>", "</?thead>",
  116. "</?tfoot>", "<hr />", "<hr>", ""]
  117. self.sub_dd = {'&times;': '×',
  118. '&divide;': '÷',
  119. '&deg;': '°',
  120. '&middot;': '·',
  121. '&plusmn;': '±',
  122. '&ordm;': 'º',
  123. '&sup1;': '¹',
  124. '&sup2;': '²',
  125. '&sup3;': '³',
  126. '&frac12;': '1/2',
  127. '&frac14;': '¼',
  128. '&frac34;': '¾',
  129. '&yen;': '¥',
  130. 'm&sup3;': 'm³',
  131. # '&lt;': '<',
  132. '&pound;': '£',
  133. # '∠&lt;': '&lt;',
  134. '&gt;': '>',
  135. "A": "A",
  136. "А": "A",
  137. "Α": "A",
  138. "B": "B",
  139. "В": "B",
  140. "в": "B",
  141. "Β": "B",
  142. "C": "C",
  143. "С": "C",
  144. "c": "c",
  145. "с": "c",
  146. "D": "D",
  147. "Ε": "E",
  148. "E": "E",
  149. "F": "F",
  150. "G": "G",
  151. "g": "g",
  152. "m": "m",
  153. "N": "N",
  154. "s": "s",
  155. "t": "t",
  156. "/": "/",
  157. "=": "=",
  158. "-": "-",
  159. "2": "2", "3": "3", "4":"4", "5":"5", "6":"6",
  160. "7": "7", "8": "8", "9":"9", "1":"1", "0":"0",
  161. '&nbsp;&nbsp;': ' ',
  162. '&nbsp;': ' ',
  163. "〖": '【',
  164. "〗": '】',
  165. "題": '题',
  166. "单项选择": '单选',
  167. "多项选择": '多选',
  168. # "不定项选择": '选择',
  169. "双项选择": '多选',
  170. "实验与探究题": '实验',
  171. "原理综合题": '原理题',
  172. }
  173. def new_pic_sub(self):
  174. """
  175. 针对base64图片先保存到本地,入库时再换成腾讯云线上地址
  176. # 第一版:再解析中,将二进制图片进行转化,图片怎么保存比较好,先再“天数”建立文件夹
  177. 第一版:再解析中,根据“word_id”建立文件夹
  178. :return:
  179. """
  180. if self.is_reparse:
  181. # css 标签清洗
  182. self.html = css_label_wash(self.html)
  183. # 保存base64编码的图片
  184. self.html = base642img(self.html, self.wordid)
  185. self.new_html = self.html
  186. def html_cleal(self):
  187. # =======清洗mathjax标签========
  188. if "MathJax" in self.html: # 再解析中存在mathjax公式渲染的标签
  189. all_mathjax = re.findall('(<span class="MathJax_Preview".*?</script>(</span>)*)', self.html)
  190. for jax in all_mathjax:
  191. latex = re.findall('<script .+?">(((?!(</)).)*?)</script>(</span>)*', jax[0])
  192. if latex:
  193. latex = "${}$".format(latex[0][0])
  194. self.html = self.html.replace(jax[0], latex)
  195. else:
  196. self.html = self.html.replace(jax[0], "")
  197. # ======再解析中的新图片处理=====
  198. self.new_pic_sub()
  199. # =====特殊符号处理=====
  200. html2txt = re.sub(r"|".join(self.sub_list), "", str(self.html)) # ("", " ") #2020/4/7
  201. html2txt = re.sub("|".join(self.sub_dd.keys()), lambda x: self.sub_dd[x.group()], html2txt) # 2020/4/1,4/7,4/20
  202. html2txt = re.sub("[不非]定[向项]选择", "不定选择", html2txt)
  203. html2txt = html2txt.replace(r"\\[{\\text{V}}V\]", "Ⓥ").replace(r"\\[{\\text{A}}A\]", "Ⓐ") \
  204. .replace(r"\\[{\\text{W}}W\]", "Ⓦ").replace(r"\\[{\\text{X}}X\]", "Ⓧ").replace(r"\\[{\\text{G}}G\]", "Ⓖ") \
  205. .replace("\uf067", "γ").replace('', "γ").replace('\uf020', "").replace("\u3000", " ")\
  206. .replace("\u2003", " ").replace("\x7f", " ").replace("\xa0", "")
  207. html2txt = re.sub(r"(<p>\s*)【例题(\d+)】", r"\1\2、", html2txt)
  208. html2txt = re.sub(r"\\\(|\\\)", "$", html2txt)
  209. # 域公式的转化处理;<sub>\<sup>可以在前端显示,不需要用latex渲染
  210. try:
  211. html2txt, newhml = get_latex(html2txt, self.is_reparse, self.wordid, self.must_latex)
  212. if newhml: # 存在域公式转图片时,需要将原文本的域公式也转为图片信息
  213. self.new_html = newhml
  214. html2txt = html2txt.replace("【omml-latex】", "")
  215. except:
  216. html2txt = html2txt.replace("【omml-latex】", "")
  217. # 字符串公式的处理:如Fe<sub>2</sub>O<sub>3</sub>, 在结构化之后处理比较好
  218. # <br/>处理
  219. html2txt = re.sub(r"<br\s*/?>", "\n", html2txt)
  220. html2txt = re.sub(r"[((]\s*(\d)\s*\$分\s*[))]", r"$(\1分)", html2txt)
  221. # =====题型行的统一处理=====
  222. # ---->>>>>题型行可能放在表格中
  223. if len(re.findall("</table>", html2txt)) >= 8: # 这个限制还不太严谨
  224. for tt in re.finditer('<tr>(((?!(</?tr>)).)*)</tr>', html2txt, re.S):
  225. tt_list = re.split(r'^\s*<td[^<>]*?>|</p></td>|</td>[\n\s]*?<td[^<>]*?>'
  226. r'|</td>\s*\n|</td>\s*$|\n\s*<td[^<>]*?>|<td[^<>]*?><p>',
  227. tt.group(1).strip()) # </td>\s*[$\n]这样无效
  228. tt_list = [col for col in tt_list if col.strip()]
  229. if " ".join(tt_list).replace(" ", "") in ['得分评卷人', '评卷人得分']:
  230. html2txt = html2txt.replace(tt.group(0), "")
  231. else:
  232. pass
  233. # html2txt = html2txt.replace(tt.group(0), "<p>" + " ".join(tt_list) + "</p>")
  234. # html2txt = re.sub(r"</?tbody>|</?table>|</?div>", "", html2txt)
  235. # ---->>>>>end
  236. html2txt = re.sub(r"(</table>)\s*([一二三四五六七八九十]\s*[、..、::]?.{2,6}题)", r"\1</p>\2", html2txt)
  237. html2txt = re.sub(r'([一二三四五六七八九十])\s*[、..、,,::]\s*(论述|填空|探究)题?[与和、、,,\s]*?(计算题|实验题)', r"\1、\3", html2txt)
  238. html2txt = re.sub(r'<td[^<>]*?><p>(([一二三四五六七八九十])\s*[、..、,,::]\s*(.{2,4}题)\s*</p>)</td>[^p]*?<p>', r"\1",
  239. str(html2txt), flags=re.S)
  240. html2txt = re.sub(r"<p>\s*([一二三四五六七八九十])\s*[、..、,,::]?\s*(计算|[解简]答|实验|作图)题?[与和、、,,\s]*?(计算|[解简]答|实验|作图)",
  241. r"<p>\1、\2题", html2txt)
  242. html2txt = re.sub(r'<p>\s*[((]\s*[一二三四五六]\s*[))]\s*必考题\s*(.?|.+?分\s*[.。.]?)\s*</p>', "", html2txt)
  243. html2txt = re.sub(r'<p>\s*[((]\s*[一二三四五六]\s*[))]\s*选考题\s*.?\s*.{,4}(?<!\d)(\d+分)\s*[,,。].{,50}</p>',
  244. r"<p>【选做题】:'\1'</p>", html2txt)
  245. html2txt = re.sub(r'<p>\s*[((]\s*[一二三四五六]\s*[))]\s*选考题\s*(.?|.+?分\s*[.。.]?)\s*</p>', "<p>【选做题】</p>", html2txt)
  246. html2txt = re.sub(r'<p>\s*([一二三四五六七八九十])\s*[、..、,,::]?\s*(单项?选择?|非?选择|多项?选择?|不定选择|填空|计算|[解简]答|实验|作图)题?\s*</p>',
  247. r"<p>\1、\2题</p>", html2txt)
  248. html2txt = re.sub(r'([一二三四五六七八九十])\s*[、..、,,::]?\s*(单选|单项选择|选择|不定选择|多选|多项选择|填空|计算|[解简]答|实验|作图)\s*(?!题)'
  249. r'([((]\s*本题|.*?\d分)', r"\1" + "、" + r'\2' + "题" + r"\3", html2txt)
  250. html2txt = re.sub(r'([一二三四五六])\s*[、..、,,::]?\s*(单选|单项选择|非?选择|不定选择|多选|多项选择|填空|计算|[解简]答|实验|作图)题',
  251. r"\1" + "、" + r'\2' + "题", html2txt)
  252. html2txt = re.sub(r'([一二三四五六七八九十])\s*[、..、,,::]?\s*[((]\s*本大题(.*?选项中)', r"\1" + "、" + "选择题", html2txt) # + r"\2"
  253. html2txt = re.sub(r'<p>\s*([一二三四五六七八九十])\s*[、..、,,]?\s*[((本大题]*?(.*?选项中)', r"\1" + "、" + "选择题", html2txt)
  254. html2txt = re.sub(r'([一二三四五六七八九十])\s*[、..、,,::]?\s*([((]\s*(每小题|本大?题)((?!(选项)).)+?[))]|综合题)',
  255. r"\1" + "、" + "解答题", html2txt)
  256. html2txt = re.sub(r'(?<!<p>)\s*([一二三四五六七八九十]\s*[、..、,,::]?\s*(单项?选择?|选择|不定选择|多项?选择?|填空|计算|[解简]答|实验|作图)题)',
  257. r'</p>\n<p>\1', html2txt)
  258. html2txt = re.sub(r'<p>\s*([一二三四五六七八九十])\s*[、..、,,::]?\s*[((]?本?大?题((?!(选项)).)+?[))]?\s*</p>', r"<p>\1、本大题</p>",
  259. html2txt)
  260. # html2txt = re.sub(r'<p>\s*[^一二三四五六七八九十]{,3}\s*[、..、]\s*(选择|不定选择|单选|多选|计算|[解简]答|实验|作图)题', r"<p>一、\1题", html2txt)
  261. # =====答案解析关键字的统一处理=====
  262. html2txt = re.sub(r'【\s*(<img src=((?!/>).)+?/>\s*)*?([解答])\s*(<img src=((?!/>).)+?/>\s*)*?([析案])\s*'
  263. r'(<img src=((?!/>).)+?/>\s*)*?】', r"【\3\6】", str(html2txt)) # 2022/4/28
  264. html2txt = re.sub(r'<p>\s*(解\s*[::])', r"<p>【解答】", str(html2txt))
  265. html2txt = re.sub(r'【[^【】]*?(答案|[解分][析答]|详解|点[评睛])[^【】]*?】', r"【\1】", str(html2txt))
  266. html2txt = re.sub(r'(\n\s*|<p>\s*|\s{2,}|\n\s*\d{,2}\s*[、..、]\s*)(答案|解析|解答|详解|点评|点睛|考点|专题)\s*[::]', r"\1【\2】", str(html2txt))
  267. html2txt = re.sub(r'(\n|^|<p>)\s*(([1-9]|[1-9][0-9])\s*[..、、])?\s*\[\s*(答案|解析|解答|详解|点评|点睛|考点|专题)\s*\]',
  268. r"\1\2【\4】", str(html2txt))
  269. html2txt = re.sub(r'([A-D])\s*\[\s*(解析|解答|详解|点评|点睛|考点|专题)\s*\]', r"\1\n【\2】", str(html2txt))
  270. html2txt = re.sub(r'(\n|^|<p>)\s*(分析)\s*[::]', r"【\2】", str(html2txt))
  271. if "【解析】" not in html2txt and "【解答】" in html2txt and "【分析】" not in html2txt:
  272. html2txt = re.sub(r'【解答】', "【解析】", str(html2txt))
  273. # =====其他关键字的处理=====
  274. html2txt = re.sub(r'<p>\s*(类型|知识点|考查角度|拔尖角度)[一二三四五六七八九十\d+][^p]*?</p>', "", str(html2txt))
  275. html2txt = re.sub(r'<p>\s*(选修[\d-]*?[::].{2,15})\s*</p>', r"<p>【章节】\1</p>", html2txt)
  276. html2txt = re.sub(r'<p>\s*([一二三四五六]\s*[、..、]?)?\s*(\[.{2}-*?选修[\d-]*?.*?\])\s*([((]\d+分[))])?\s*</p>',
  277. r"<p>【章节】\2</p>", html2txt)
  278. html2txt = re.sub(r'<p>\s*(基础|中档|综合)题[^p题]*?</p>|<p>\s*【(考点|专题)】[^p]*?</p>', "", str(html2txt))
  279. html2txt = re.sub(r'<p>\s*(基础训练|提升训练|探究培优)</p>', "", str(html2txt))
  280. html2txt = re.sub(r'<p>注意事项[::]\s*</p>(\n+\s*<p>\s*\d\s*[、..、][^/]+?</p>){1,}', "", html2txt, flags=re.S)
  281. html2txt = re.sub(r'<p>注意事项[::]\s*\d\s*[、..、][^/]+?</p>(\n+\s*<p>\s*\d\s*[、..、][^/]+?</p>){1,}', "", html2txt,
  282. flags=re.S)
  283. html2txt = re.sub(r'[((]\s*([A-Z\dⅠⅡⅢⅣⅤ]+|IV)\s*[))]', r"(\1)".replace(" ", "").replace("(IV)", "Ⅳ"), html2txt)
  284. html2txt = re.sub(r'[((](\s*\d\s*\d?\s*分?\s*)[))]', "(" + r'\1'.replace(" ", "") + ")", html2txt)
  285. html2txt = re.sub(r'\[来源:.*?\]', "", html2txt)
  286. html2txt = re.sub('<p>欢迎访问.*?</p>', '', html2txt)
  287. html2txt = re.sub('w\s*w\s*w\..*?(\.\s*c\s*o\s*m|\.cn)+|(?<!["“=\'])http:.*?\.(com|cn|org)', "",
  288. html2txt) # ww w.gkstk.c om
  289. html2txt = re.sub(r'<(table|tr) [a-z]+="\d+">', r'<\1>', html2txt) # <td rowspan="2">保留
  290. html2txt = re.sub(r'<(table)( [a-z]+=".*?")+>', r'<\1>', html2txt)
  291. html2txt = re.sub(r'<p>\s*第\s*[二三四ⅡⅢⅣ]\s*(卷|部分)\s*([((].*?[))]|非?选择题.{,8})?\s*</p>', "<p>【非选择题】</p>", html2txt)
  292. # == == =对可能的题型行的处理 == ==
  293. html2txt = re.sub("<p>【非选择题】</p>((\s|\n|<p>|</p>)*\d{1,2}\s*[..、、].+?)", r"<p>二、解答题</p>\1", html2txt)\
  294. .replace("【非选择题】", "")
  295. # =====选项的处理=====
  296. html2txt = re.sub(r'(<p>\s*([1-9]|[1-9][0-9])\s*[..、、].+?[((]\s*[))])\s*(A\s*[..、、][^/]*?</p>)',
  297. r"\1</p>\n<p>\3", str(html2txt))
  298. # =====题号的处理=====
  299. html2txt = re.sub(r'([ED]\s*[、..、].*?(\s|</su[pb]>\s*))(([1-9]|[1-9][0-9])\s*[、..、])',
  300. r"\1</p>\n<p>\3", html2txt)
  301. html2txt = re.sub(r'((</?p>|\n)\s*(<img src=.*?"\s*/?>\s*)?([1-9]|[1-9][0-9]))\s*'
  302. r'([((]\s*(\d{1,2}[.\s\d]*?分|.{2,3}题?)\s*[))]|解析?\s*[::]|【解析】)', r"</p>\1、\5", html2txt)
  303. html2txt = re.sub(r"<p>\s*([1-9]|[1-9][0-9])\s*([((]20\d{2}\s*[\u4e00-\u9fa5、、]{2,9}[))])", r"<p>\1、\2",
  304. html2txt)
  305. html2txt = re.sub(r"<p>\s*([1-9]|[1-9][0-9])\s*(【(解析?|答案?)】|(解析?|答案?)\s*[::]|\[(答案|解析)\])", r"<p>\1、\2",
  306. html2txt)
  307. html2txt = re.sub(r"<p>\s*([1-9]|[1-9][0-9])\s*([((]\s*\d+\s*分?\s*[))])?(【(解析?|答案?)】|(解析?|答案?)\s*[::]"
  308. r"|\[(答案|解析)\])", r"<p>\1、\2\3", html2txt)
  309. html2txt = re.sub(r"(</?p>|\n)\s*(<img src=((?!/>).)+?/>)\s*([1-9]|[1-9][0-9])\s*"
  310. r"([((]20\d{2}\s*[\u4e00-\u9fa5、、]{2,9}[))])", r"<p>\2</p>" + "\n" + r"<p>\4、\5", html2txt) # 【susp_img】
  311. html2txt = re.sub(r'(</?p>|\n)((\s*<su[bp]>\s*)?<img src=.*? height="[\d.]+p[tx]"\s*/?>(\s*</su[bp]>)?\s*)'
  312. r'(([1-9]|[1-9][0-9])\s*[、..、])', r"</p>\2</p>" + "\n" + r"\5", html2txt)
  313. html2txt = re.sub(r"(<p>((?!<p>).)+?(\s|[/\"]>))(([1-9]|[1-9][0-9])\s*[、..、].{,20}本[大小]?题\d+分)",
  314. r"\1</p>" + "\n<p>" + r"\4", html2txt)
  315. html2txt = re.sub(r"</?p>((\s*<su[bp]>\s*)?<img src=.*?/>(\s*</su[bp]>)?"
  316. r"((\s*<su[bp]>\s*)?<img src=((?!/>).)+?/>(\s*</su[bp]>)?)*?\s*)\s*(([1-9]|[1-9][0-9])\s*[、..、])",
  317. r"</p>\1</p>" + "\n<p>" + r"\8", html2txt, flags=re.S)
  318. html2txt = re.sub(r'(<p>\s*[一二三四五六七八九十].*?题\s*\(.+?分.*?\))\s*(([1-9]|[1-9][0-9])\s*[、..、].*?)</p>',
  319. r"\1</p>\n<p>\2</p>", html2txt)
  320. html2txt = re.sub(r'(<p>\s*[一二三四五六七八九十].*?题\s*\(.+?分.*?\))\s*(([1-9]|[1-9][0-9])\s*[、..、].*?)</p>',
  321. r"\1</p>\n<p>\2</p>", html2txt)
  322. html2txt = re.sub(r'(<p>.*?[..]{6,}\s*\d+分)\s*(([1-9]|[1-9][0-9])\s*[、..、].*?)</p>', r"\1</p>\n<p>\2</p>", html2txt)
  323. html2txt = re.sub(r'([1-9]|[1-9][0-9])\s*([((]\s*\d{1,2}[.\s\d]*?分\s*[))])\s*[、..、]', r"\1" + "、" + r"\2", html2txt)
  324. # =====图片的处理=====
  325. # 1>>根据图片宽高的异常值判断删除隐藏图片
  326. def sub1(ss):
  327. if float(ss.group(1)) <= 3 and float(ss.group(2)) <= 3:
  328. return ""
  329. else:
  330. return ss.group(0)
  331. html2txt = re.sub(r'<img src=.*? width="([\d.]+)p[xt]" height="([\d.]+)p[xt]"\s*/?>', sub1, html2txt)
  332. # 2>>将图片中带有的汉字去掉
  333. html2txt = re.sub(r'(<img src=.*?) alt=".+?"', r"\1", html2txt)
  334. # html2txt = re.sub(r'(<img src=.+?(?<!\\)\")>', r"\1 />", html2txt) # 将">换为" />
  335. html2txt = re.sub(r'(<img src=(?!\sstyle=)+?(?<!\\)\")>', r"\1 />", html2txt) # 将">换为" />
  336. # 3>>建立图片id字典,对原图片信息第一次替换
  337. html2txt = re.sub(r'( src=".*?files)\\image', r"\1/image", html2txt)
  338. all_image = re.findall(r'<img src=".*?image[\da-z]+\..*?[/\"]>', html2txt)
  339. src2subs = {}
  340. subs2src = {}
  341. for src in all_image:
  342. # 校本题库上传的图片名称是随机数,故设置映射
  343. # kk = re.search('(<img src=".*?image\d+\.(png|gif|jpg|jpeg))', src)
  344. # new_src = src.replace(kk.group(1), self.img_url[kk.group(1)]) if type(self.img_url) == dict and kk else src
  345. # 图片信息简化替换
  346. print(src)
  347. new_src = re.sub(r'( data-latex)="\s*\\\[(.*?)\\\]\s*"', r'\1="$\2$"', src)
  348. new_src = re.sub(r'( data-latex="\$[^"]+?\$")',
  349. lambda x: x.group(1).replace("<", " \lt ").replace(" ", " "), new_src)
  350. latex_info = re.search(r'<img src=".*?/(new_)?image([\da-z]+)\..*?(data-latex=".*?")', src)
  351. mathpix = " " + latex_info.group(3).replace("\n", "").strip().replace(" ", " ") if latex_info else ""
  352. if mathpix and len(mathpix) > 20:
  353. mathpix = ""
  354. w_h_info = re.search(r'<img src=".*?/(new_)?image([\da-z]+)\..*?width="([\d.]+)[pxt]*?"\s*height="([\d.]+)[pxt]*?"', src)
  355. w_h = " w_h=" + w_h_info.group(3).split('.')[0] + "*" + w_h_info.group(4).split('.')[0] \
  356. if w_h_info and not mathpix else "" # w_h 和 mathpix只存在一个
  357. # image_id = re.search(r'<img src=".*?/(new_)?image([\da-z]+)\.', src).group(2)
  358. image_info = re.search(r'<img src=".*?/([^/]+?)/(new_)?image([\da-z]+)\.', src) # 2023.12.1
  359. print(image_info.groups())
  360. image_id = image_info.group(1) + image_info.group(3)
  361. if len(image_id) > 10:
  362. image_id = image_id[-10:]
  363. src2subs[src] = '<imgsrc' + image_id + w_h + mathpix + "/>"
  364. subs2src['<imgsrc' + image_id + w_h + mathpix + "/>"] = new_src
  365. for k, v in src2subs.items():
  366. html2txt = html2txt.replace(k, v)
  367. # ------------------------------------------------------------------------
  368. # ========html 转 list=========
  369. html2txt = re.sub(r'(</?div>|</table>|</?body>)(\n\s*)*?<p>', r"\1</p>" + "\n<p>", html2txt, flags=re.S)
  370. # >>>>>> <table>先替换后再切割
  371. # 不能简单按 \n 切割,表格里面也可能有换行,应该先替换后再切割
  372. subs2table = {}
  373. all_table = re.findall(r'<table>.*?</table>', html2txt, flags=re.S)
  374. for k, v in enumerate(all_table):
  375. html2txt = html2txt.replace(v, "<t{}b>".format(k))
  376. # 将表格中的换行去掉
  377. v = re.sub(r'<p>\s*(</?t[drh]( .*?")?>|</?table>|</?tbody>)\s*</p>', r"\1", v)
  378. v = re.sub(r'</td></p>[\n\s]*<p><td>', "</td><td>", v)
  379. v = re.sub(r'<td>(<p>|\s|</p>|\n)*</td>', "<td> </td>", v)
  380. v = re.sub(r'</tbody></?p></table>', "</tbody></table>", v)
  381. v = re.sub(r'(</?t[drh]( .*?")?>|</?table>|</?tbody>)(\s*<p>\s*</p>)[\s\n]*?(<br\s*/?>|\n)+', r"\1", v, flags=re.S)
  382. v = re.sub(r'(</?t[drh]( .*?")?>|</?table>|</?tbody>)(<br\s*/?>|\n|</p>|\s)+', r"\1", v, flags=re.S)
  383. v = re.sub(r'(</t[drh]( .*?")?>|</table>|</tbody>)(<br\s*/?>|\n|<p>|\s)+', r"\1", v, flags=re.S)
  384. # 暂时还有table标签首尾的换行没去掉
  385. subs2table["<t{}b>".format(str(k))] = v
  386. # <造成的css标签冲突处理 2021-10-13
  387. def sub2(ss):
  388. if re.search(r'^(img|/?h[123456]|/?su[bp]>|t\d+b>|br\s*/?>'
  389. r'|/?(p|span|font|article|ul|ol|div|table|t?body|html|head|t[drh])(\s*|\s+style=.*?")>'
  390. r'|/?[a-z]+ style=.*?">)', ss.group(1)) is None:
  391. return "&lt;{}".format(ss.group(1))
  392. else:
  393. return "<{}".format(ss.group(1))
  394. html2txt = re.sub("<([^<]{1,30})", sub2, html2txt)
  395. # print(html2txt)
  396. # >>>>>> html 切割
  397. con_list = sum([re.split('<p>|<h[12345]>', i) if len(re.findall("<p>|<h[12345]>", i)) > 1 else [i] for i in
  398. re.split(r"\n+|</p>(?!</td>)|</h[12345]>", html2txt)], []) # html2txt)[:-1]
  399. con_list = [re.sub(r"^\n*\s*(<p>|<h[12345]>)+", "", ii) for ii in con_list]
  400. # >>>>>> <table> 替换回去
  401. if subs2table:
  402. con_list = [re.sub(r"|".join(subs2table.keys()), lambda x: subs2table[x.group()], ii) for ii in con_list]
  403. # 剩余个别标签处理
  404. con_list = [re.sub(r"^<([a-z]+)>[\s\t\n]*</\1>$", "", i.strip()) for i in con_list] # 2020/4/7,14
  405. con_list = [re.sub(r"^(<table>|</td>|<td[^<>]*?>|</?tr>)+?(.|\n)+?([一二三四五六七八九十])\s*[、..、]\s*(.{2,4}题)(.|\n)+?</table>",
  406. r"\3、\4", i.strip())
  407. for i in con_list]
  408. # 把最后可能还存在的</?p>或考号信息去掉
  409. con_list = [re.sub("</?p>|[…O•.\s]*?密[…O•.\s]*?封[….O•\s]*?装?[…O•.\s]*?订?[….O•\s]*?线?[….O•\s]*?$"
  410. "|((学校|班级|姓名|座位号|准考号|[学考]号)[\s::_]*?){2,}$", "", i.strip()) for i in con_list]
  411. # =====答案行格式处理====
  412. temp_list = [re.split(r"^((\s*<imgsrcw_h=[^/\"]*?(data-latex=.*?)?\s*[/\"]>\s*)+)", v.strip(), maxsplit=1)[1::3]
  413. if re.match(r'(\s*<imgsrcw_h=[^/\"]*?(data-latex=.*?)?\s*[/\"]>\s*)+?(参考|考试|试[题卷]|物理|理综|数学|化学|生物)(答案|解析|答案[及与和]评分(标准|意见|细则))\s*$'
  414. r'|(\s*<imgsrcw_h=[^/\"]*?(data-latex=.*?)?\s*[/\"]>\s*)+?评分标准'
  415. r'|(\s*<imgsrcw_h=[^/\"]*?(data-latex=.*?)?\s*[/\"]>\s*)+?(参考|考试|试[题卷])(答案|解析|答案[及与和]评分(标准|意见|细则))\s*(物理|理综|数学|化学|生物)?\s*$',
  416. re.sub(r"[上下]?学[年期]|[\d—【】..、、::(())年\s]|[中大]学|模拟|[中高]考|年级|[学期][末中]|[高初][一二三]", "",
  417. v.strip())) else [v] for v in con_list]
  418. con_list = sum(temp_list, [])
  419. # =====对可能的题号的处理==== 如2、3、4、5、 加了【fei】 # 重新修改!!!!!!!!!!
  420. con_list = [re.sub(r"^\s*([1-9][0-9]?\s*[..、、])", r"【fei】\1", i.strip())
  421. if (len(re.findall(r"(^|\s*[..、、])\s*[1-9][0-9]?\s*[..、、]", i)) >= 3
  422. and len(re.sub(r"[\d..、、\s]", "", i)) < 2) else i for i in con_list]
  423. # =====头尾清除没用的信息=====
  424. if con_list and re.search(r"[\u4e00-\u9fa5]|<img ", con_list[0]) is None:
  425. con_list = con_list[1:]
  426. while con_list and re.search(r"声明[::].*?著作权属.*?所有|(邮箱|用户|日期|QQ)\s*[::].+?", con_list[-1]):
  427. con_list = con_list[:-1]
  428. return con_list, subs2src, self.new_html
  429. def del_no(item, item_no_type=1):
  430. """去开头的题号"""
  431. if item_no_type == 2:
  432. item = re.sub(r'^\n*\s*[((]\s*([1-9]|[1-9][0-9])\s*[))]\s*[..、、::]?', "", item)
  433. return item
  434. item = re.sub(r'^\n*\s*([1-9]|[1-9][0-9])\s*[..、、::]', "", item)
  435. return item
  436. def html_cleal_test(htmlf): # 不用
  437. html2txt = re.sub(r"&nbsp;", "", htmlf.read()) # ("", " ")
  438. # html2txt.replace("①", "(1).").replace("②", "(2).").replace("③", "(3).")
  439. con_list = [re.sub(r"^\n+\s+<p>", "", ii) for ii in html2txt.split("</p>")[:-1]]
  440. # pprint(con_list)
  441. if re.search(r"[\u4e00-\u9fa5]", con_list[0]) is None:
  442. con_list = con_list[1:]
  443. return con_list
  444. def get_md5(image_id):
  445. """
  446. 由于hash不处理unicode编码的字符串(python3默认字符串是unicode)
  447. 所以这里判断是否字符串,如果是则进行转码
  448. 初始化md5、将image_name进行加密、然后返回加密字串
  449. """
  450. image_name = str(image_id) + str(time.time()) + str(random.random())
  451. image_name = image_name.encode("utf-8")
  452. md = hashlib.md5()
  453. md.update(image_name)
  454. return str(md.hexdigest())
  455. def wash_after(res_dict, paperid,subject="数学"):
  456. """
  457. 1.处理最终结果多余的换行符;2.对题文中已给答案的选择填空进行替换;3.选择题的细分
  458. :param res_dict:
  459. :return:
  460. """
  461. pattern1 = re.compile(
  462. r"([是为点]|等于|=|=|有|存在)\s*_+((<img src=((?!/>).)+?[/\"]>|[^_;;。?!,\n])+?)(?<![==_])_+([cdkm上]?m?\s*.?[。.?]?\s*"
  463. r"($|<br/>|<img src|……))")
  464. pattern2 = re.compile(r"((有|存在|[是为])[\u4e00-\u9fa5]{0,2})\s*_+(\d+)_+\s*([\u4e00-\u9fa5,,;;。..])")
  465. chapter_no = {}
  466. option_st = 0
  467. is_optional = False
  468. option_score = 0
  469. select_type_id = []
  470. all_content_str_list = []
  471. topic_type_list = []
  472. for num, sr in enumerate(res_dict):
  473. sr["stem"] = re.sub(r"\n[_\-\s]*密[…O•.\s]*封[….O•\s]*装?[…O•.\s]*订?[….O•\s]*线?"
  474. r"|\n\s*((学校|班级|姓名|座位?号|准考号|学号)[\s::_]*){2,}", "", sr["stem"])
  475. sr["stem"] = re.sub(r'\n\s*(第\s*[^\s]\s*卷|第[一二三四]部分)\s*([((].*?[))]|非?选择题.{,8})?\s*\n', "\n", sr["stem"])
  476. if num == len(res_dict) - 1: # 对拆分后的最后一道题进行特殊判断
  477. end_con = sr["stem"] + sr["parse"]
  478. if len(re.findall(r"[\u4e00-\u9fa5]", end_con)) > 1000 and (
  479. len(re.findall(r"\n\s*([1-9]|1[0-9])\s*[..、、].+?",
  480. end_con)) > 4 or len(re.findall(r"[((]\s*[))]|_{2,}", end_con)) > 6):
  481. sr['errmsgs'].append("原试卷格式有问题,导致本题可能包含了很多非本题的题文")
  482. if not re.sub(r"[(())\n\s]", "", sr["stem"]):
  483. sr['errmsgs'].append("本题没有题干,请检查题干格式是否正确")
  484. if "-" in str(sr["item_id"]) and sr['type'] in ["选择题", "填空题"]:
  485. if (not sr["key"] or sr["key"]=="见解析") and re.search("[A-H]+", re.sub("[;;、、\n(())\s]|\d+分", "", sr["parse"])):
  486. sr["key"] = re.sub("[;;、、\n(())\s]|\d+分", "", sr["parse"])
  487. sr["parse"] = ""
  488. # 把首尾的换行都去掉
  489. # sr["stem"] = table_label_cleal(re.sub(r"\n\s*","<br/>",sr.get("stem", "").lstrip()))
  490. # 将选择题和填空题中的题干中出现答案的情况 去掉答案
  491. kuo_con1 = re.search(r'([是为]|等于|[==有]|表示)\s*[((]\s*([A-Zc][A-Zc;;和与、、\s]*?)[))]\s*(.?($|\n|<br/>|<img))',
  492. sr["stem"])
  493. kuo_con2 = re.search("[((]\s*([A-Zc][A-Zc;;和与、、\s]*?)[))]\s*(.?($|\n|<br/>))", sr["stem"])
  494. if sr['type'].replace("题", "") in ["单选", "多选", "选择", "不定选择"]:
  495. # sr["type"] = "选择"
  496. # 针对选择题在题文中已给出答案的处理
  497. if kuo_con1:
  498. sr["stem"] = sr["stem"].replace(kuo_con1.group(0), kuo_con1.group(1) + "( )" + kuo_con1.group(3))
  499. sr["key"] = kuo_con1.group(2).replace("c", "C") if not sr["key"] else sr["key"]
  500. elif kuo_con2:
  501. sr["stem"] = sr["stem"].replace(kuo_con2.group(0), "( )" + kuo_con2.group(2))
  502. sr["key"] = kuo_con2.group(1).replace("c", "C") if not sr["key"] else sr["key"]
  503. # sr['options_text'] = ""
  504. elif sr['type'] == '填空题':
  505. # sr["type"] = "填空"
  506. ans_list = []
  507. # 针对填空题在题文中已给出答案的处理
  508. sub_n = 0
  509. while re.search(pattern1, sr["stem"]):
  510. blank_con1 = re.search(pattern1, sr["stem"])
  511. sr["stem"] = sr["stem"].replace(blank_con1.group(0),
  512. blank_con1.group(1) + "____" + blank_con1.group(5))
  513. ans_list.append(blank_con1.group(2))
  514. sub_n += 1
  515. if sub_n > 5:
  516. break
  517. while re.search(pattern2, sr["stem"]):
  518. blank_con2 = re.search(pattern2, sr["stem"])
  519. # 这里的限制条件易出错,可以再判断一下
  520. sr["stem"] = sr["stem"].replace(blank_con2.group(0),
  521. blank_con2.group(1) + "____" + blank_con2.group(4))
  522. ans_list.append(blank_con2.group(2))
  523. if re.findall(r"_{2,}", sr["stem"]):
  524. sr["blank_num"] = len(re.findall(r"_{2,}", sr["stem"]))
  525. if not sr["key"] and ans_list:
  526. sr["key"] = "; ".join(ans_list)
  527. # 已知题型是错误的情况,如解答题,放在填空题中
  528. if 'blank_num' not in sr and re.search("_+([^_]*?)_+", sr['stem']) is None:
  529. sr['errmsgs'].append("填空题题干中没有下划线(__),与题型(填空题)不符")
  530. # stem_c = re.sub("<img src=.*?/>|[,,.。.、、]", "", sr["stem"])
  531. # if len(stem_c) > 2: # 不自动纠错
  532. # sr["type"] = "解答题"
  533. # sr["type"] = "解答"
  534. # else: # 大题题型先不做范围判断
  535. # if sr['type'] and sr['type'].replace("题", "") not in ["解答", "计算", "实验", "作图"]:
  536. # sr["type1"] = "解答"
  537. # else:
  538. # sr["type1"] = sr['type'].replace("题", "")
  539. # if "is_optional" not in sr:
  540. # sr["is_optional"] = is_optional
  541. # sr["option_str"] = ""
  542. # 换行符处理!
  543. sr["stem"] = sr.get("stem", "").strip().replace("\n\n", "\n").replace("\n", "<br/>") # 2020/4/10 gai
  544. # sr["stem"] = get_equation_instr(sr["stem"])
  545. if "options" in sr: # 对选项部分进行格式处理
  546. for i in range(len(sr['options'])):
  547. sr['options'][i] = get_simpstr2eqn(sr['options'][i].strip()).replace("\n\n", "\n").replace("\n", "<br/>")
  548. # sr['options'][i] = get_equation_instr(sr['options'][i].strip()).replace("\n\n", "\n").replace("\n", "<br/>")
  549. if "slave" in sr and sr["slave"]:
  550. # 带小题的大题,格式处理,高中数学没有这一功能
  551. for s in sr["slave"]:
  552. s["stem"] = s.get("stem", "").strip().replace("\n\n", "\n").replace("\n", "<br/>")
  553. # 已分小问了的题号,是不会带小题号的,故不需要替换
  554. # s["stem"] = re.sub(r"[((]\s*(\d|ⅰⅱⅲⅳ|i{1,3})\s*[))]|[①②③④]\s*(?![+-])", "", s["stem"][:5]) + s["stem"][5:]
  555. s["parse"] = s.get("parse", "").strip().replace("\n\n", "\n").replace("\n", "<br/>")\
  556. .replace("解答:解:", "解答:").replace("解答:解:", "解答:")
  557. s["key"] = s.get("key", "").strip().replace("\n\n", "\n").replace("\n", "<br/>")
  558. # sr["slave"] = sr.get("slave", "").replace("\n", "<br>")
  559. if "answer_type" in s:
  560. s["answer_type"] = configs.answer_type[s["answer_type"]]
  561. else:
  562. # s["parse"] = css_conflict_deal(s["parse"]) # "css 冲突标签处理"
  563. sr["parse"] = sr.get("parse", "").lstrip().replace("\n\n", "\n").replace("\n", "<br/>")
  564. sr["parse"] = re.sub("^【解[答析]】\s*", "", sr["parse"])
  565. # sr["parse"] = get_equation_instr(sr["parse"])
  566. sr["key"] = sr.get("key", "").lstrip().replace("\n\n", "\n").replace("\n", "<br/>")
  567. # sr["key"] = get_equation_instr(sr["key"])
  568. if "answer_type" in sr:
  569. sr["answer_type"] = configs.answer_type[sr["answer_type"]]
  570. if not sr["parse"] and not sr["key"]: # 答案和解析都没有
  571. # sr["parse"] = "略"
  572. # sr["key"] = "略"
  573. sr['errmsgs'].append("本题缺少答案和解析")
  574. elif not sr["key"] and sr["parse"]:
  575. sr["key"] = "" # 见解析
  576. elif re.sub("见解析|略|空|无|没有|答案", "", sr["key"]) and not sr["parse"]:
  577. sr["parse"] = "略"
  578. # if "本选做题缺少解析" not in sr['errmsgs'] and "本题缺少解析" not in sr['errmsgs']:
  579. # sr['errmsgs'].append("本题缺少解析")
  580. # 辅助标签处理
  581. # sr["analysis"] = ""
  582. if "analy" in sr: # 存在题目分析时,将其放在解析里
  583. sr["analy"] = sr.get("analy", "").strip().replace("\n\n", "\n")
  584. if len(sr["analy"].replace(" ", "")) >= 10:
  585. sr["parse"] = "【分析】"+sr["analy"].replace("\n", "<br/>") + "<br/>【详解】" + sr["parse"]
  586. del sr["analy"]
  587. if "chapter" in sr: # 如选修4-5:不等式选讲
  588. if sr['item_id'] + 1 <= len(res_dict):
  589. chapter_no[sr['item_id']] = sr["chapter"]
  590. del sr["chapter"]
  591. # 是否为选做题"is_optional",两种形式不会同时出现
  592. if "option_st" in sr: # 带有此标签的后面的题目都是选做题option_score
  593. # option_st = sr['item_id']
  594. # is_optional = True
  595. # if "," in sr["option_st"]:
  596. # option_score = int(sr["option_st"].split(",")[-1])
  597. del sr["option_st"]
  598. # elif sr['type'] == '选做题': # 题型是选做题 如五、选做题
  599. # select_type_id.append(sr['item_id'])
  600. # sr['is_optional'] = 'true'
  601. # sr['score'] = option_score
  602. # elif "type1" in sr and sr["type1"] == "解答" and "is_optional" not in sr:
  603. # sr["is_optional"] = is_optional
  604. # if is_optional:
  605. # sr['score'] = option_score
  606. # if "type1" in sr:
  607. # del sr["type1"]
  608. # 题型纠正
  609. # 将选择题改为单选或多选,"is_multiple_choice"
  610. sr['type'] = re.sub("([单多])项选择题?", r"\1选题", sr['type'])
  611. sr['type'] = sr['type'].replace("题题", "题") # .replace("简答", "解答")
  612. # sr['type'] = re.sub("(计算|简答)题?", "解答题", sr['type'])
  613. if sr['type'] in ["选择", "选择题"]: # 有的科目只有选择题,不分单选和多选
  614. if len(re.findall("[A-Z]", sr["key"])) > 1:
  615. sr['type'] = '多选题'
  616. elif len(re.findall("[A-Z]", sr["key"])) == 1:
  617. sr['type'] = '单选题'
  618. elif "数学" in subject or "物理" in subject:
  619. sr['type'] = '单选题'
  620. info_x = re.search("^[((](多)选题?[))]", sr["stem"].replace(" ", ""))
  621. if info_x:
  622. sr['type'] = '{}选题'.format(info_x.group(1))
  623. if sr['type'] == '多选题':
  624. if len(re.findall("[A-Z]", sr["key"])) == 1:
  625. sr['errmsgs'].append("本题答案个数与题型(多选题)不符")
  626. # sr["is_multiple_choice"] = 'true'
  627. elif sr['type'] == '单选题':
  628. # sr["is_multiple_choice"] = 'false'
  629. if "options" in sr and len(sr["options"]) > 4:
  630. sr['errmsgs'].append("选项个数多于4个,与题型(单选题)不符")
  631. if len(re.findall("[A-Z]", sr["key"])) > 1:
  632. sr['errmsgs'].append("本题答案个数与题型(单选题)不符")
  633. elif sr['type'] == '不定选择题':
  634. if len(re.findall("[A-Z]", sr["key"])) > 1:
  635. sr['type'] = '多选题'
  636. elif len(re.findall("[A-Z]", sr["key"])) == 1:
  637. sr['type'] = '单选题'
  638. elif "数学" in subject or "物理" in subject:
  639. sr['type'] = '单选题'
  640. else:
  641. sr['type'] = '选择题'
  642. if "缺少答案" not in "".join(sr['errmsgs']):
  643. sr['errmsgs'].append("本题缺少答案")
  644. elif "数学" in subject:
  645. if sr['type'].replace("题", "") == "填空":
  646. if sr['blank_num'] > 1:
  647. sr['type'] = "多空题"
  648. else:
  649. sr['type'] = "单空题"
  650. elif sr['type'].replace("题", "") not in ["单空", "多空"]:
  651. sr['type'] = "解答题"
  652. # elif "物理" in subject:
  653. # # 用第一版模型预测
  654. # content = sr['stem']
  655. # if "options" in sr and sr["options"]:
  656. # content+= "\n" + "\n".join(["{}、{}".format(chr(ord('@') + idm + 1), option)
  657. # for idm, option in enumerate(sr["options"])])
  658. # try:
  659. # r = requests.post(url=configs.phy_topicType_ip,
  660. # json={"content": content, "period": "高中",
  661. # "topic_type": sr['type']})
  662. # sr['type'] = r.json()["res"]
  663. # if sr['type'] == "简答题":
  664. # sr['type'] = "解答题"
  665. # except Exception as e:
  666. # print(e)
  667. # if sr['type'].replace("题", "") in ["单空", "多空", "填空"]:
  668. # sr['type'] = "填空题"
  669. # else:
  670. # sr['type'] = "解答题"
  671. elif sr['type'].replace("题", "") in ["单空", "多空", "填空"]:
  672. sr['type'] = "填空题"
  673. elif sr['type'] not in ["选择", "选择题"]:
  674. sr['type'] = "解答题"
  675. content = sr['stem']
  676. if "options" in sr and sr["options"]:
  677. content += "\n" + "\n".join(["{}、{}".format(chr(ord('@') + idm + 1), option)
  678. for idm, option in enumerate(sr["options"])])
  679. all_content_str_list.append(content)
  680. topic_type_list.append(sr['type'])
  681. # """按照原先高中数学解析的最后输出格式整理输出"""
  682. # sr["type"] = sr['type'].replace("非选择", "解答").replace("题题", "题") #
  683. sr["topic_num"] = sr['item_id']
  684. sr['errmsgs'] = ";".join(sr['errmsgs'])
  685. sr["parse"] = re.sub(r"试题【([分解]析)】", r"试题\1:", sr["parse"]) # 解析
  686. sr["key"] = re.sub("([;;]|<br/>)\s*$", "", sr["key"])
  687. if 'susp_pic' in sr:
  688. del sr['susp_pic']
  689. if 'is_optional' in sr:
  690. del sr['is_optional']
  691. if 'spliterr_point' in sr:
  692. del sr['spliterr_point']
  693. if 'score' in sr:
  694. del sr['score']
  695. del sr['item_id']
  696. # ---------------------字符串公式处理--------------------------------
  697. # sr["stem"] = get_equation_instr(sr["stem"])
  698. # sr["key"] = get_equation_instr(sr["key"])
  699. # sr["parse"] = get_equation_instr(sr["parse"])
  700. # if "options" in sr:
  701. # sr["options"] = list(map(get_equation_instr, sr["options"]))
  702. # ----------------------------------------------------------------
  703. # 物理题型批量调接口:节约时间
  704. if "物理" in subject:
  705. t1 = time.time()
  706. epoches = int(len(all_content_str_list) / 10)
  707. pred_topic_types = []
  708. if epoches > 0:
  709. last = 0
  710. for epoch in range(epoches):
  711. input_data = {"content": all_content_str_list[last:(epoch+1)*10], "period": "高中",
  712. "topic_type": topic_type_list[last:(epoch+1)*10]}
  713. last = (epoch+1)*10
  714. try:
  715. r = requests.post(url=configs.phy_topicType_ip, json=input_data)
  716. pred_topic_types.extend(r.json()["res"])
  717. except Exception as e:
  718. print(e)
  719. pred_topic_types.extend([""]*10)
  720. rest_con = all_content_str_list[last:]
  721. rest_topic_type = topic_type_list[last:]
  722. else:
  723. rest_con = all_content_str_list
  724. rest_topic_type = topic_type_list
  725. if rest_con:
  726. input_data = {"content": rest_con, "period": "高中", "topic_type": rest_topic_type}
  727. try:
  728. r = requests.post(url=configs.phy_topicType_ip, json=input_data)
  729. pred_topic_types.extend(r.json()["res"])
  730. except Exception as e:
  731. print(e)
  732. pred_topic_types.extend([""] * len(rest_con))
  733. # 将预测题型替换到res_dict中
  734. if any([True for i in pred_topic_types if i]) and len(pred_topic_types) == len(res_dict):
  735. for idx, pred_type in enumerate(pred_topic_types):
  736. if pred_type and res_dict[idx]['type'] in ["填空题", "解答题"]:
  737. if pred_type == "简答题":
  738. pred_type = "解答题"
  739. res_dict[idx]['type'] = pred_type
  740. logger.info("----【paper_id:{}】采用题型预测服务花费time:{}".format(paperid, time.time() - t1))
  741. # --------------------------------------------------------------
  742. # 换行符替换
  743. convert_huanhang(res_dict)
  744. # ------------------------------------------------------------------------
  745. # if chapter_no: # 章节标签下移一位
  746. # for c, v in chapter_no.items():
  747. # res_dict[c]["chapter"] = v
  748. # 选做题"option_str"处理
  749. # if select_type_id:
  750. # for s in select_type_id:
  751. # if len(select_type_id) == 2:
  752. # res_dict[s - 1]['option_str'] = "2选1"
  753. # elif len(select_type_id) == 4:
  754. # res_dict[s - 1]['option_str'] = "4选2"
  755. # else:
  756. # res_dict[s - 1]['errmsgs'] += ";<br/>选做题不是“2选1”和“4选2”类型"
  757. # if option_st:
  758. # print("option_st:", option_st)
  759. # for s in range(option_st, len(res_dict)):
  760. # if (len(res_dict) - option_st) == 2:
  761. # res_dict[s]['option_str'] = "2选1"
  762. # elif (len(res_dict) - option_st) == 4:
  763. # res_dict[s]['option_str'] = "4选2"
  764. # else:
  765. # res_dict[s]['errmsgs'] += ";<br/>选做题不是“2选1”和“4选2”类型"
  766. # 再解析中的新图片上传腾讯云
  767. # 再设置一个入库接口,点击入库,才开始从本地上传图片
  768. return res_dict
  769. def convert_huanhang(items_list):
  770. """
  771. 递归 换行符替换:\n --> <br/>
  772. :param items_list:
  773. :return:
  774. """
  775. if isinstance(items_list, list):
  776. for k, one_i in enumerate(items_list):
  777. items_list[k] = convert_huanhang(one_i)
  778. elif isinstance(items_list, dict):
  779. for k, v in items_list.items():
  780. if k == "answer_type" and type(v) == str:
  781. items_list[k] = configs.answer_type[v]
  782. else:
  783. items_list[k] = convert_huanhang(v)
  784. if "answer_type" in items_list and items_list["answer_type"] == 2:
  785. if ("slave" not in items_list or not items_list["slave"]) and "stem" in items_list:
  786. items_list["stem"] = re.sub(r"(__{2,})", r'<span style="color:RGB(50%,40%,30%); blank space">\1</span>',
  787. items_list["stem"])
  788. elif isinstance(items_list, str):
  789. item_str = items_list.strip().replace("\n\n", "\n")
  790. item_str = re.sub(r'(</table>)(<br\s*/?>|\n)+', r"\1", item_str)
  791. return item_str.replace("\n", "<br/>")
  792. else:
  793. return items_list
  794. return items_list
  795. def css_conflict_deal(item):
  796. """
  797. 针对<a, <p 符号 在前端显示被过滤掉的问题:对“<”左右加$, 注意条件:“<”前$为双数时加,
  798. :return: str
  799. """
  800. # item = item.replace("<", "&lt;").replace(">", "&gt;") # 2021-8-24
  801. # item = re.sub("<(?!img src)", "&lt;", item) # 还有表格
  802. item = item.replace("$<$", "【*_*】") # 多次单题解析时会出现$<$
  803. item = re.sub(r"<(/?su[bp]|br\s*/?|/?table( .*?)?|/?tbody( .*?)?|/?t[rhd]( .*?)?)>", r"【\1】", item)
  804. if re.search(r"(?<!\\\()<", item):
  805. n1=0
  806. n2=0
  807. for i in re.finditer("<(?!img)", item):
  808. if item[:i.start()+2*n1+4*n2].count("$") % 2 == 0:
  809. item = item[:i.start()+2*n1+4*n2] + "$<$" + item[i.start()+1+2*n1+4*n2:]
  810. n1 += 1
  811. else:
  812. item = item[:i.start() + 2 * n1+4*n2]+" \lt " + item[i.start()+1+2*n1+4*n2:]
  813. n2 += 1
  814. # -----------------------------------------------------------
  815. item = item.replace("【*_*】", "$<$")
  816. item = re.sub(r"\\\)\s*\$<\$", r"\) &lt;", item)
  817. # while re.search(r"\\\(((?!\\\().)*?\$<\$((?!\\\().)*?\\\)", item): # 这个
  818. # item = re.sub(r"(\\\(((?!\\\().)*?)\$<\$(((?!\\\().)*?\\\))", r"\1 \lt \3", item) # 线上r"\1 &lt; \2"
  819. while re.search(r"\\\(.*?\$<\$.*?\\\)", item):
  820. item = re.sub(r"(\\\(.*?)\$<\$(.*?\\\))", r"\1 &lt; \2", item) # r"\1 \t \2"
  821. item = re.sub(r"【(/?su[bp]|br\s*/?|/?table( .*?)?|/?tbody( .*?)?|/?t[rhd]( .*?)?)】", r"<\1>", item)
  822. item = re.sub(r"(<br\s*/?>\s*|\n\s*)+<(/?table( .*?)?|/?tbody( .*?)?|/?t[rhd]( .*?)?)>\s*(<br\s*/?>\s*|\n\s*)+",
  823. r"<\2>", item)
  824. item = item.replace("$<$span class=", "<span class=")
  825. return item
  826. def insert_sort2get_idx(item_list, num):
  827. """
  828. :param item_list: 拍好序的列表
  829. :param num: 插入的数值
  830. :return: 插入的位置
  831. """
  832. add_n = 0
  833. for i in range(len(item_list)):
  834. if num > item_list[i]:
  835. add_n += 1
  836. else:
  837. break
  838. return add_n
  839. # def find_seq_num(num_list):
  840. # """
  841. # 针对切分题号时切错的序号进行纠正,考虑序号是连续且正常的情况下
  842. # 将连续的数字进行分组
  843. # :param num_list:输入[3, 4, 8, 9, 12, 13, 14]
  844. # :return: [[3, 4],[8, 9],[12, 13, 14]]
  845. # """
  846. # seq_ranges = []
  847. # for k, g in groupby(enumerate(num_list), lambda x: x[0] - x[1]):
  848. # group = (map(itemgetter(1), g))
  849. # group = list(map(int, group))
  850. # seq_ranges.append(group)
  851. # return seq_ranges
  852. # def del_exception_value(item_list):
  853. # """
  854. # 去列表中的异常值,题目越多,越容易突出异常值
  855. # :return:
  856. # """
  857. # import numpy as np
  858. # max_v = max(item_list)
  859. # arr_mean = np.mean(item_list) # 均值
  860. # arr_var = np.var(item_list) # 方差
  861. # while max_v > len(item_list)+4:
  862. # item_list.remove(max_v)
  863. # print(item_list)
  864. # arr_mean = np.mean(item_list) # 去最大值后的均值
  865. # arr_var = np.var(item_list) # 去最大值后的方差
  866. # max_v = max(item_list)
  867. # # print("均值与方差:",arr_mean,arr_var)
  868. # if abs((item_list[-1] - item_list[0] + 1) - len(item_list)) <= 3:
  869. # return item_list
  870. # else:
  871. # exception_value = []
  872. # for i in item_list:
  873. # # print(abs((i - arr_mean) / arr_var), i)
  874. # if(abs((i - arr_mean)/arr_var)) > 0.3:
  875. # exception_value.append(i)
  876. # right_seq = [i for i in item_list if i not in exception_value]
  877. # return right_seq
  878. def pic_transfer(con_list):
  879. aft_opt = [] # 针对选项后是题目图片的情况,进行移位
  880. if "\n" in con_list[-1]:
  881. ccon = re.split("\n+", con_list[-1])
  882. while re.match("<img src=", ccon[-1]) and len(ccon) > 1:
  883. aft_opt.insert(0, ccon[-1])
  884. ccon = ccon[:-1]
  885. if aft_opt:
  886. con_list[0] += "\n" + "\n".join(aft_opt)
  887. con_list[-1] = "\n".join(ccon)
  888. con_list[0] = re.sub(r"\(\d+分\)", "", con_list[0][:9]) + con_list[0][9:]
  889. return con_list
  890. def judge_split_error(item_list):
  891. """
  892. 转对试卷切分后的小题判断是否存在切分错误的情况,能纠错就纠错,不能则删除
  893. :return:
  894. """
  895. # for k, v in enumerate(item_list):
  896. # if k>0 and v['item_id'] - item_list[k-1]['item_id']>1:
  897. # if
  898. if __name__ == '__main__':
  899. # -------------生成requirements.txt---------------
  900. # pip freeze > requirements.txt
  901. # import os, sys
  902. #
  903. # project_root = os.path.dirname(os.path.realpath(__file__)) # 找到当前目录
  904. # print(project_root)
  905. #
  906. # # 找到解释器,虚拟环境目录
  907. # python_root = sys.exec_prefix
  908. # print(python_root)
  909. #
  910. # # 拼接生成requirements命令
  911. # command = python_root + '\Scripts\pip freeze > ' + project_root + '\\requirements.txt'
  912. # print(command)
  913. #
  914. # # 执行命令。
  915. # os.system(command)
  916. # ----------------一键安装 requirements.txt------------
  917. # pip install -r requirement.txt
  918. # python_root + '\Scripts\' + pip install -r requirements.txt
  919. # import os
  920. # rrr=os.path.basename(r"http:/pstatic.dev.xueping.com/data/word/2020/08/12/5f338d18e2cce.docx")
  921. # print(rrr)
  922. # item = "<a 我没发你的接口 $2366<a$ <a 我没发你的接口 $2366<a$ <img 我没发你的接口 $2366<a$ <a 我没发你的接口 $2366<a$ <a 我没发你的接口 $2366<a$"
  923. # item = r"2.下列选项中,使不等式\( x<\frac{1}{x}< x_{2} \)"
  924. # ww = css_conflict_deal(item)
  925. # print(ww)
  926. p1 = r"C:\Users\Python\Desktop\123\62314b31a7d375f4518b9afd.html"
  927. t1 = open(p1, 'r', encoding="utf8").read()
  928. res = HtmlWash(t1, '11111111',must_latex=1).html_cleal()
  929. print(res)
  930. # html, wordid, is_reparse=0, img_url="", must_latex=0)