field_eq2latex.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. #!/usr/bin/env/python
  2. # -*- coding:utf-8 -*-
  3. """
  4. 域公式转latex
  5. 域公式意义:\al 列内左对齐;\ac 列内居中对齐;\ar 列内右对齐
  6. \r(,):根号;
  7. \s(上标, 下标):设置上下标;
  8. """
  9. import re, os
  10. import Utils.train_configs as train_configs
  11. from pprint import pprint
  12. from func_timeout import func_set_timeout
  13. SUB = {"A":"Ⓐ",
  14. "V":"Ⓥ",
  15. "W":"Ⓦ",
  16. "X":"Ⓧ",
  17. "G":"Ⓖ",
  18. }
  19. def latex_wash(ltx, is_danti=0):
  20. """
  21. latex格式调整, 为了渲染效果
  22. :param ltx:
  23. :return:
  24. """
  25. # ltx = re.sub(r"(?<!\\)%", "\%", ltx)
  26. # word中phi和varphi的渲染与mathjax相反,需换一下
  27. if not is_danti:
  28. ltx = re.sub(r"\\phi(?!up)", "@#@\\varphi", ltx)
  29. ltx = re.sub(r"(?<!@#@)\\varphi(?!up)", "\\phi", ltx)
  30. ltx = ltx.replace("@#@\\varphi", "\\varphi")
  31. # 处理\left和\right单独出现的情况
  32. ltx = re.sub(r"\$\\left\s*[((]\$", "(", ltx)
  33. ltx = re.sub(r"\$\\right\s*[))]\$", ")", ltx)
  34. ltx = re.sub(r"\$\s*\\text{\s*\}\s*\$", "", ltx)
  35. def sub1(ss):
  36. if re.search(r"\\left.*?\\right|\\right.*?\\left", ss.group(1)) is None:
  37. # res = re.sub(r"\\left(?!right|arrow)|\\right(?!left|arrow)", "", ss.group(1))
  38. res = re.sub(r"\\left(?![a-z])|\\right(?![a-z])", "", ss.group(1))
  39. return res
  40. return ss.group(1)
  41. ltx = re.sub(r"(\$.*?(\\left(?![a-z])|\\right(?![a-z])).*?\$)", sub1, ltx)
  42. # -----------------------------------------------------------
  43. # \text{}中的 "<" 不换为"\lt"
  44. def get_posi(item, start): # 获取}的正确位置
  45. sign_stack = ["{"]
  46. for n, s in enumerate(item[start:]):
  47. if s == "{":
  48. sign_stack.append("{")
  49. elif s == "}":
  50. sign_stack.pop()
  51. if not sign_stack:
  52. return start + n
  53. return None
  54. res_p = ""
  55. tmp_item = ltx
  56. # 前端也会将小于号<替换成&lt;
  57. while "\\text" in tmp_item and (" \lt " in tmp_item or "\%" in tmp_item):
  58. st = re.search(r"\\text\s*{", tmp_item).end()
  59. ed = get_posi(tmp_item, st)
  60. if ed:
  61. res_p += tmp_item[:st] + tmp_item[st: ed + 1].replace(" \lt ", " &lt; ")\
  62. .replace("\%", "%")
  63. tmp_item = tmp_item[ed + 1:]
  64. else:
  65. break
  66. if res_p:
  67. ltx = res_p
  68. if tmp_item:
  69. ltx += tmp_item
  70. return ltx
  71. # def get_latex(item):
  72. # if r"$eq \\f(" in item:
  73. # item = re.sub(r"\$eq \\\\f\((.+?),(.+?)\)", r"$\\frac{\1}{\2}", item)
  74. #
  75. # if r"$eq \\r(" in item:
  76. # item = re.sub(r"\$eq \\\\r\((.+?)\)", r"$\sqrt{\1}", item)
  77. #
  78. # if "$eq \\\\o\\\\" in item:
  79. # while re.search(r"\$eq \\\\o\\\\al\((.+?),(.*?)\)", item):
  80. # ss = re.search(r"\$eq \\\\o\\\\al\((.+?),(.*?)\)", item)
  81. # # 将非变量的{}修改成{{}}
  82. # s1 = "$_{{{sub}}}^{{{sup}}}".format(sub=ss.group(2), sup=ss.group(1))
  83. # s1 = re.sub("</?su[bp]>|\s", "", s1)
  84. # if not ss.group(2):
  85. # eq_info = re.match(r"\$\s*<sub>(.+?)</sub>", item[ss.end():])
  86. # if eq_info:
  87. # s1 = "$_{{{sub}}}^{{{sup}}}$".format(sub=eq_info.group(1), sup=ss.group(1))
  88. # s1 = re.sub("</?su[bp]>|\s", "", s1)
  89. # item = item[:ss.start()] + s1 + item[ss.end()+eq_info.end():]
  90. # # return re.sub("</?su[bp]>|\s", "", s1)
  91. # else:
  92. # item = item[:ss.start()] + s1 + item[ss.end():]
  93. # else:
  94. # item = item[:ss.start()] + s1 + item[ss.end():]
  95. #
  96. # # s1 = "$_{{{sub}}}^{{{sup}}}".format(sub=ss.group(2), sup=ss.group(1))
  97. # # return re.sub("</?su[bp]>|\s", "", s1)
  98. # # item = re.sub(r"\$eq \\\\o\\\\al\((.+?),(.*?)\)", sub1, item)
  99. #
  100. # ac_info = re.search(r"\$eq \\\\o\\\\ac\(○,\s*([A-Z])\)", item)
  101. # if ac_info:
  102. # if ac_info.group(1) in SUB.keys():
  103. # item = item.replace(ac_info.group(0), SUB.get(ac_info.group(1)))
  104. #
  105. # return item
  106. # @func_set_timeout(3)
  107. def get_latex0(item):
  108. while "$eq \\\\f(" in item or "$eq \\\\r(" in item or re.search("【域公式】.*?\\\\o\\\\", item):
  109. if "$eq \\\\f(" in item:
  110. # item = re.sub(r"\$eq \\\\f\((((?!\\\\[fr]).)+?),(.+?)\)", r"$\\frac{\1}{\2}", item)
  111. item = re.sub(r"(【域公式】.*?)\\\\f\((((?!\\\\[fr]\().)+?),(((?!\\\\[fr]\().)+?)\)",
  112. r"\1\\frac{\2}{\4}", item)
  113. if "$eq \\\\r(" in item:
  114. item = re.sub(r"(【域公式】.*?)\\\\r\((((?!\\\\[fr]\().)+?)\)", r"\1\sqrt{\2}", item)
  115. if re.search("【域公式】.*?\\\\o\\\\", item): # if "$eq \\\\o\\\\" in item:
  116. while re.search(r"【域公式】.*?\\\\o\\\\al\((.+?),(.*?)\)", item):
  117. ss = re.search(r"(【域公式】.*?)\\\\o\\\\al\((.+?),(.*?)\)", item)
  118. # 将非变量的{}修改成{{}}
  119. s1 = "_{{{sub}}}^{{{sup}}}".format(sub=ss.group(3), sup=ss.group(2))
  120. s1 = re.sub("</?su[bp]>|\s", "", s1)
  121. if not ss.group(3):
  122. eq_info = re.match(r"\$\s*<sub>(.+?)</sub>", item[ss.end():])
  123. if eq_info:
  124. s1 = "_{{{sub}}}^{{{sup}}}$".format(sub=eq_info.group(1), sup=ss.group(2))
  125. s1 = re.sub("</?su[bp]>|\s", "", s1)
  126. item = item[:ss.start()] + ss.group(1) + s1 + item[ss.end()+eq_info.end():]
  127. # return re.sub("</?su[bp]>|\s", "", s1)
  128. else:
  129. item = item[:ss.start()] + ss.group(1) + s1 + item[ss.end():]
  130. else:
  131. item = item[:ss.start()] + ss.group(1) + s1 + item[ss.end():]
  132. # s1 = "$_{{{sub}}}^{{{sup}}}".format(sub=ss.group(2), sup=ss.group(1))
  133. # return re.sub("</?su[bp]>|\s", "", s1)
  134. # item = re.sub(r"\$eq \\\\o\\\\al\((.+?),(.*?)\)", sub1, item)
  135. ac_info = re.search(r"\$eq \\\\o\\\\ac\(○,\s*([A-Z])\)", item)
  136. if ac_info:
  137. if ac_info.group(1) in SUB.keys():
  138. item = item.replace(ac_info.group(0), SUB.get(ac_info.group(1)))
  139. return item.replace("【域公式】$eq ", "$")
  140. # @func_set_timeout(5)
  141. def zifu_match_combine(split_eq):
  142. """
  143. 递归函数,将成对括号进行组合,目前先按成对的括号进行转化
  144. :param split_eq:
  145. :return:
  146. """
  147. if len(split_eq) < 4 or ")" not in split_eq or "(" not in split_eq:
  148. return split_eq
  149. for k, i in enumerate(split_eq):
  150. if i == ")":
  151. for subk, j in enumerate(split_eq[:k][::-1]):
  152. if j == "(":
  153. # print(split_eq[k - subk - 1 - 1])
  154. bef_left_kuohao = split_eq[k - subk - 1 - 1]
  155. if bef_left_kuohao == "\\f":
  156. # dou_index = split_eq[k-subk-1-1:k+1].index(',')+k-subk-2
  157. # bb = split_eq[k - subk - 1 - 1:k + 1]
  158. info1 = re.search(r"\\f\((.*?),(.*?)\)$", "".join(split_eq[k - subk - 1 - 1:k + 1]))
  159. if info1:
  160. new_s = "\\frac{{{one}}}{{{two}}}".format(one=info1.group(1), two=info1.group(2))
  161. new_split_eq = split_eq[:k - subk - 1 - 1]
  162. if new_split_eq and new_split_eq[-1] == "(":
  163. new_s = "{" + new_s + "}"
  164. new_split_eq.append(new_s)
  165. new_split_eq.extend(split_eq[k + 1:])
  166. return zifu_match_combine(new_split_eq)
  167. elif bef_left_kuohao == "\\r":
  168. print(':::', "".join(split_eq[k - subk - 1 - 1:k + 1]))
  169. info1 = re.search(r"\\r\((.*?)\)$", "".join(split_eq[k - subk - 1 - 1:k + 1]))
  170. if info1:
  171. if re.search("<sup>\d</sup>\s*,", info1.group(1)):
  172. lft, right = re.split("(?<=</sup>)\s*,", info1.group(1))
  173. new_s = "\sqrt[{}]{{{}}}".format(re.search("<sup>(\d)</sup>", lft).group(1), right)
  174. else:
  175. new_s = "\sqrt{{{}}}".format(re.sub("^\s*,", "", info1.group(1)))
  176. new_split_eq = split_eq[:k - subk - 1 - 1]
  177. if new_split_eq and new_split_eq[-1] == "(":
  178. new_s = "{" + new_s + "}"
  179. new_split_eq.append(new_s)
  180. new_split_eq.extend(split_eq[k + 1:])
  181. return zifu_match_combine(new_split_eq)
  182. elif bef_left_kuohao in ['\\o\\al', '\\s']:
  183. info1 = re.search(r"(\\o\\al|\\s)\((.*?),(.*?)\)$", "".join(split_eq[k - subk - 1 - 1: k + 1]))
  184. if info1:
  185. new_s = "_{{{sub}}}^{{{sup}}}".format(sub=info1.group(3), sup=info1.group(2))
  186. if info1.group(1) == "\\o\\al" and (not info1.group(3) or not info1.group(2)):
  187. temp_s = info1.group(2) + info1.group(3)
  188. temp_s_info1 = re.search("<sub>(.*?)</sub>",temp_s)
  189. temp_s_info2 = re.search("<sup>(.*?)</sup>",temp_s)
  190. if temp_s_info1 and temp_s_info2:
  191. new_s = "_{{{}}}^{{{}}}".format(temp_s_info1.group(1), temp_s_info2.group(1))
  192. new_s = re.sub("</?su[bp]>|\s", "", new_s)
  193. new_split_eq = split_eq[:k - subk - 1 - 1]
  194. new_split_eq.append(new_s)
  195. new_split_eq.extend(split_eq[k + 1:])
  196. return zifu_match_combine(new_split_eq)
  197. elif bef_left_kuohao == '\\x\\to':
  198. info1 = re.search(r"\\x\\to\((.*?)\)$", "".join(split_eq[k - subk - 1 - 1:k + 1]))
  199. if info1:
  200. new_s = "\\bar{{{}}}".format(info1.group(1))
  201. new_split_eq = split_eq[:k - subk - 1 - 1]
  202. if new_split_eq and new_split_eq[-1] == "(":
  203. new_s = "{" + new_s + "}"
  204. new_split_eq.append(new_s)
  205. new_split_eq.extend(split_eq[k + 1:])
  206. return zifu_match_combine(new_split_eq)
  207. else:
  208. new_s = "".join(split_eq[k - subk - 1 - 1: k + 1])
  209. new_split_eq = split_eq[:k - subk - 1 - 1]
  210. new_split_eq.append(new_s)
  211. new_split_eq.extend(split_eq[k + 1:])
  212. return zifu_match_combine(new_split_eq)
  213. # @func_set_timeout(36)
  214. def get_latex(item, is_reparse=0, wordid="123456", must_latex=0):
  215. """
  216. 第一通道:
  217. 将文本中的域代码字符串能转化latex的先转化,不能转化的就暂时用域代码格式
  218. 第二通道:
  219. 再解析时,遇到域代码,将域代码转图片处理
  220. 考虑先转化:根式、分式、上下标、to、\s
  221. :param item:
  222. :return:
  223. """
  224. is_first = 1
  225. item = item.replace("\\uf028", "(").replace("\\uf029", ")") # 2020-6-21
  226. new_item = item
  227. # semi_succ_dict = {}
  228. while re.findall("(【域公式:[^【]*?】)", item):
  229. all_eqs1 = re.findall("(【域公式:[^【]*?】)", item) # 遇到嵌套的域公式,无法获取完整,故加【
  230. all_eqs = list(set(all_eqs1))
  231. all_eqs.sort(key=all_eqs1.index)
  232. print(all_eqs)
  233. new_eqs = []
  234. fail_n = 0
  235. for eq in all_eqs:
  236. raw_eq = eq.replace("\\\\", "\\").replace(" \R", " \\r")
  237. eq = raw_eq.replace("eq ", "").replace("【域公式:", "").replace("】", "")
  238. split_eq = re.split(r"(\\f|\(|\)|\\r|\\o\\al|\\x\\to|\\s|,)", eq)
  239. split_eq = [i for i in split_eq if i]
  240. res_eq = zifu_match_combine(split_eq)
  241. # print(res_eq, split_eq)
  242. try:
  243. if "".join(res_eq) == "".join(split_eq): # 转失败
  244. fail_n += 1
  245. new_eqs.append(raw_eq)
  246. elif re.search(r"\\[a-zA-Z\d]{1,5}\(", "".join(res_eq)): # 没有完全转成功
  247. fail_n += 1
  248. new_eqs.append(raw_eq)
  249. # semi_succ_dict[raw_eq] = "【域公式:eq {}】".format("".join(res_eq))
  250. # new_eqs.append("【域公式:eq {}】".format("".join(res_eq)))
  251. else:
  252. # mathjax不能渲染sub和sup
  253. new_eq = "".join(res_eq)
  254. def deal2(yy):
  255. new_y = yy.group(2)
  256. if yy.group(1) == "<sub>":
  257. new_y = "_{" + yy.group(2) + "}"
  258. if yy.group(1) == "<sup>":
  259. new_y = "^{" + yy.group(2) + "}"
  260. return new_y
  261. new_eq = re.sub("(<sub>)(.+?)</sub>", deal2, new_eq)
  262. new_eq = re.sub("(<sup>)(.+?)</sup>", deal2, new_eq).strip()
  263. if not is_first: # 如果不是第一轮转化,则将前面转化后的$去掉
  264. new_eq = re.sub(r"(?<!\\)\$", "", new_eq)
  265. new_eqs.append("${}$".format(new_eq))
  266. except:
  267. fail_n += 1
  268. new_eqs.append(raw_eq)
  269. if fail_n == len(all_eqs): # 防止死循环
  270. break
  271. eq_repl_dict = dict(zip(all_eqs, new_eqs))
  272. print('-------------',eq_repl_dict)
  273. for k, v in eq_repl_dict.items():
  274. # v = latex_wash(v)
  275. # print(v)
  276. item = item.replace(k, v)
  277. is_first = 0
  278. # 对于转latex失败的域公式走第二通道:转图片
  279. # 嵌套的情况,里层域公式转latex成功,外层转失败,怎么办
  280. if must_latex:
  281. return item, ""
  282. if is_reparse and "【域公式" in item:
  283. file_path = train_configs.IMG_FOLDER + '/' + str(wordid) + '/' + "field_eq"
  284. if not os.path.exists(file_path):
  285. os.makedirs(file_path)
  286. new_eqs2raw = {} # 域代码_原始文本
  287. for i in re.finditer("【域公式:(.*?)】", item):
  288. if re.search(r"\\sqrt|\\frac|\\bar", i.group(1)) is None: # 不能包含latex命令
  289. if "【" in i.group(1): # 嵌套,则按上面提取的域公式不完整
  290. cout = i.group(1).count("【") # 统计【个数
  291. try: # 根据嵌套的“【”找到最外层的“】”
  292. raw_eq = i.group(0)+"】".join(item[i.end():].split("】")[:cout])+"】" # 拿到完整样式
  293. eqs = i.group(1) + "".join(item[i.end():].split("】")[:cout])
  294. eqs = "eq " + eqs.replace("【域公式:", "").replace("【", "").replace("eq ", "")
  295. eqs = re.sub("<sub>(.+?)</sub>", r"\s(,\1)", eqs)
  296. eqs = re.sub("<sup>(.+?)</sup>", r"\s(\1,)", eqs)
  297. new_eqs2raw[eqs]=raw_eq
  298. except:
  299. pass
  300. else:
  301. eqs = re.sub("<sub>(.+?)</sub>", r"\s(,\1)", i.group(1))
  302. eqs = re.sub("<sup>(.+?)</sup>", r"\s(\1,)", eqs)
  303. new_eqs2raw[eqs] = i.group(0)
  304. else:
  305. print("域公式中含latex表达式!!!")
  306. new_eqs = list(new_eqs2raw.keys())
  307. new_eqs.append(file_path)
  308. eqcode = "】【".join(new_eqs)
  309. try:
  310. requests.get(r"http://localhost:9001/FieldEq/Eq2Png/?eqcode=" + eqcode, timeout=3)
  311. except:
  312. pass
  313. # 在生成图片的文件夹中对应判断图片再进行替换
  314. eq_imgs = os.listdir(file_path)
  315. if eq_imgs:
  316. raw_eqs2img = {}
  317. for img in eq_imgs:
  318. w_h_info = str(img.replace(".png", "").split("__")[-1]).split("_")
  319. w = int(int(w_h_info[0])/1.27+1)
  320. h = int(int(w_h_info[1])/1.27+1)
  321. name = str(img.replace(".png", "").split("__")[0])
  322. idn = int(name.split("_")[-1])
  323. new_name = name + ".png"
  324. os.rename(file_path + "/" + img, file_path + "/" + new_name)
  325. eq_img = '<img src="{}/{}/field_eq/{}" width="{}px" height="{}px" eq-code="{}" />'\
  326. .format(train_configs.new_img_ip, wordid, new_name, w, h, new_eqs[idn-1])
  327. raw_eqs2img[new_eqs2raw[new_eqs[idn-1]]] = eq_img
  328. if raw_eqs2img:
  329. for k, v in raw_eqs2img.items():
  330. item = item.replace(k, v)
  331. new_item = new_item.replace(k, v)
  332. else:
  333. new_item = ""
  334. else:
  335. new_item = ""
  336. return item, new_item
  337. if __name__ == '__main__':
  338. import requests,json
  339. # f = "t=【域公式】$eq \\\\f(v<sub>0</sub>,a)$=【域公式】$eq \\\\f(6,1)$ s=6s, $eq \\\\r(6)$ "
  340. # print(re.sub(r"\\\\o\\\\al\((.+?),.+?\)", r"\1",f))
  341. # p1 = r"C:\Users\Python\Desktop\test\24\25.html"
  342. # html = open(p1, 'r', encoding='utf-8').read()
  343. # # print(html)
  344. # print(get_latex(html))
  345. # f = "eq \\f(\\f(1,2)×0.82,0.2×10)】【eq \\f(6,1)】【eq \\f(\\x\\to(OC)-\\x\\to(OA),2T)】【C:/Users/Python/Desktop/test/temp"
  346. # res = requests.get(r"http://localhost:9001/FieldEq/Eq2Png/?eqcode=" + f, timeout=30).text
  347. # print(json.loads(res).replace("\r\n", ""))
  348. # f = "【解】解析 (1)因OB绳处于竖直方向,所以B球处于平衡状态,AB绳上的拉力为零,OB绳对小球的拉力F<sub>OB</sub>=mg. (3分)<br/>(2)A球在重力mg、水平拉力F和OA绳的拉力F<sub>OA</sub>三力作用下平衡,所以OA绳对小球的拉力F<sub>OA</sub>=【域公式:eq \\\\f(mg,cos 60°)】=2mg. (3分)<br/>(3)作用力F=mgtan 60°=【域公式:eq \\\\r(3)】mg. (3分)<br/>答案 (1)mg (2)2mg (3)【域公式:eq \\\\r(3)】mg"
  349. # f = "B.【域公式:eq \\r(<sup>3</sup>,\\f(1,4))】"
  350. # f1 = "由动能定理得-W<sub>克</sub><sub>f</sub>-mgh=0-【域公式:eq \\f(1,2)】mv【域公式:eq \\o\\al(<sub>B</sub><sup>2</sup>,)】"
  351. # aa = get_latex(f1, 1)
  352. # print(aa)
  353. # tt = r"${ } _ { n H C H O } \rightarrow f H _ { 2 } C - O _ { n }$"
  354. # def sub1(ss):
  355. # if re.search(r"\\left.*?\\right|\\right.*?\\left", ss.group(1)) is None:
  356. # # res = re.sub(r"\\left(?!right|arrow)|\\right(?!left|arrow)", "", ss.group(1))
  357. # res = re.sub(r"\\left(?![a-z])|\\right(?![a-z])", "", ss.group(1))
  358. # return res
  359. # return ss.group(1)
  360. # tt = re.sub(r"(\$.*?(\\left(?![a-z])|\\right(?![a-z])).*?\$)", sub1, tt)
  361. # # re1 = latex_wash(tt)
  362. # print(tt)
  363. # item = "【域公式:eq \\f(【域公式:eq \\f(6,1)】,3)】geeghe】threthtrh"
  364. # new_eqs2raw = {} # 域代码_原始文本
  365. # for i in re.finditer("【域公式:(.*?)】", item):
  366. # print(i.group(0))
  367. # if re.search(r"\\sqrt|\\frac|\\bar", i.group(1)) is None: # 不能包含latex命令
  368. # if "【" in i.group(1): # 嵌套
  369. # cout = i.group(1).count("【")
  370. # try: # 根据嵌套的【找到最外层的】
  371. # raw_eq = i.group(0) + "】".join(item[i.end():].split("】")[:cout]) + "】"
  372. # eqs = i.group(1) + "".join(item[i.end():].split("】")[:cout])
  373. # eqs = "eq " + eqs.replace("【域公式:", "").replace("【", "").replace("eq ", "")
  374. # eqs = re.sub("<sub>(.+?)</sub>", r"\s(,\1)", eqs)
  375. # eqs = re.sub("<sup>(.+?)</sup>", r"\s(\1,)", eqs)
  376. # new_eqs2raw[eqs] = raw_eq
  377. # except:
  378. # pass
  379. # else:
  380. # eqs = re.sub("<sub>(.+?)</sub>", r"\s(,\1)", i.group(1))
  381. # eqs = re.sub("<sup>(.+?)</sup>", r"\s(\1,)", eqs)
  382. # new_eqs2raw[eqs] = i.group(0)
  383. # 导入comtypes模块