convert_docx.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. import inspect
  2. import os
  3. import sys
  4. sys.path.append(os.path.dirname(__file__) + "/../")
  5. from format_convert.convert_tree import _Document, _Sentence, _Page, _Image, _Table
  6. import logging
  7. import re
  8. import traceback
  9. import xml
  10. import zipfile
  11. import docx
  12. from format_convert.convert_image import picture2text
  13. from format_convert.utils import judge_error_code, add_div, get_logger, log, memory_decorator, get_garble_code
  14. from format_convert.wrapt_timeout_decorator import timeout
  15. def docx2text():
  16. return
  17. @timeout(50, timeout_exception=TimeoutError)
  18. def read_xml_order(path, save_path):
  19. log("into read_xml_order")
  20. try:
  21. try:
  22. f = zipfile.ZipFile(path)
  23. for file in f.namelist():
  24. if "word/document.xml" == str(file):
  25. f.extract(file, save_path)
  26. f.close()
  27. except Exception as e:
  28. log("docx format error!")
  29. return [-3]
  30. try:
  31. collection = xml_analyze(save_path + "word/document.xml")
  32. except TimeoutError:
  33. log("xml_analyze timeout")
  34. return [-4]
  35. body = collection.getElementsByTagName("w:body")[0]
  36. order_list = []
  37. text_list = []
  38. # 编号组记录
  39. num_pr_dict = {}
  40. last_node_level = 0
  41. for line in body.childNodes:
  42. # print(str(line))
  43. if "w:p" in str(line):
  44. # 文本的编号(如果有编号的话)
  45. text_no = ''
  46. # 提取编号 组-层级-序号
  47. num_pr = line.getElementsByTagName("w:numPr")
  48. if num_pr:
  49. num_pr = num_pr[0]
  50. group_id = int(num_pr.getElementsByTagName("w:numId")[0].getAttribute("w:val"))
  51. if group_id >= 1:
  52. node_level = num_pr.getElementsByTagName("w:ilvl")
  53. if node_level:
  54. node_level = int(node_level[0].getAttribute("w:val"))
  55. # print('node_level', node_level, 'last_node_level', last_node_level)
  56. if group_id in num_pr_dict.keys():
  57. if last_node_level != 0 and node_level < last_node_level:
  58. # print('重置', 'group_id', group_id, 'last_node_level', last_node_level)
  59. # 需循环重置node_level到last_node_level之间的level
  60. for l in range(node_level+1, last_node_level+1):
  61. num_pr_dict[group_id][l] = 0
  62. num_pr_dict[group_id][node_level] += 1
  63. elif node_level in num_pr_dict[group_id].keys():
  64. num_pr_dict[group_id][node_level] += 1
  65. else:
  66. num_pr_dict[group_id][node_level] = 1
  67. else:
  68. num_pr_dict[group_id] = {node_level: 1}
  69. # print(num_pr_dict[group_id])
  70. for level in range(node_level+1):
  71. # 当前level下有多少个node
  72. if level not in num_pr_dict[group_id]:
  73. continue
  74. level_node_cnt = num_pr_dict[group_id][level]
  75. # print('level_node_cnt', level_node_cnt)
  76. text_no += str(level_node_cnt) + '.'
  77. last_node_level = node_level
  78. # print('read_xml_order text_no', text_no)
  79. text = line.getElementsByTagName("w:t")
  80. picture = line.getElementsByTagName("wp:docPr")
  81. if text:
  82. order_list.append("w:t")
  83. temp_text = ""
  84. for t in text:
  85. if len(t.childNodes) > 0:
  86. temp_text += t.childNodes[0].nodeValue
  87. else:
  88. continue
  89. if text_no:
  90. temp_text = text_no + ' ' + temp_text
  91. text_list.append(temp_text)
  92. if picture:
  93. order_list.append("wp:docPr")
  94. for line1 in line.childNodes:
  95. if "w:r" in str(line1):
  96. # print("read_xml_order", "w:r")
  97. picture1 = line1.getElementsByTagName("w:pict")
  98. if picture1:
  99. order_list.append("wp:docPr")
  100. if "w:tbl" in str(line):
  101. order_list.append("w:tbl")
  102. # read_xml_table(path, save_path)
  103. return [order_list, text_list]
  104. except Exception as e:
  105. log("read_xml_order error!")
  106. print("read_xml_order", traceback.print_exc())
  107. # log_traceback("read_xml_order")
  108. return [-1]
  109. @timeout(50, timeout_exception=TimeoutError)
  110. def read_xml_table(path, save_path):
  111. def recursion_read_table(table):
  112. table_text = '<table border="1">'
  113. tr_index = 0
  114. tr_text_list = []
  115. # 直接子节点用child表示,所有子节点用all表示
  116. for table_child in table.childNodes:
  117. if 'w:tr' in str(table_child):
  118. tr = table_child
  119. tr_child_nodes = tr.childNodes
  120. tc_index = 0
  121. tc_text_list = []
  122. for tr_child in tr_child_nodes:
  123. if 'w:tc' in str(tr_child).split(' '):
  124. tc_text = ""
  125. tc = tr_child
  126. # 获取一格占多少列,相当于colspan
  127. col_span = tc.getElementsByTagName("w:gridSpan")
  128. if col_span:
  129. col_span = int(col_span[0].getAttribute("w:val"))
  130. else:
  131. col_span = 1
  132. # 获取是否是合并单元格的下一个空单元格,相当于rowspan
  133. is_merge = tc.getElementsByTagName("w:vMerge")
  134. if is_merge:
  135. is_merge = is_merge[0].getAttribute("w:val")
  136. if is_merge == "continue":
  137. col_span_index = 0
  138. real_tc_index = 0
  139. if 0 <= tr_index - 1 < len(tr_text_list):
  140. for tc_colspan in tr_text_list[tr_index - 1]:
  141. if col_span_index < tc_index:
  142. col_span_index += tc_colspan[1]
  143. real_tc_index += 1
  144. if real_tc_index < len(tr_text_list[tr_index - 1]):
  145. tc_text = tr_text_list[tr_index - 1][real_tc_index][0]
  146. # 设置colspan
  147. table_text = table_text + "<td colspan=" + str(col_span) + ">"
  148. # 放入文本
  149. tc_child_nodes = tc.childNodes
  150. for tc_child in tc_child_nodes:
  151. if 'w:tbl' in str(tc_child).split(' '):
  152. # 嵌套在tc中的表格
  153. tc_text += recursion_read_table(tc_child)
  154. if 'w:p' in str(tc_child).split(' '):
  155. tc_p_all_nodes = tc_child.getElementsByTagName("*")
  156. for tc_p_all in tc_p_all_nodes:
  157. if 'w:t' in str(tc_p_all).split(' '):
  158. # w:t必须加childNodes[0]才能读文本
  159. tc_text += tc_p_all.childNodes[0].nodeValue
  160. # 结束该tc
  161. table_text = table_text + tc_text + "</td>"
  162. tc_index += 1
  163. tc_text_list.append([tc_text, col_span])
  164. # 结束该tr
  165. table_text += "</tr>"
  166. tr_index += 1
  167. tr_text_list.append(tc_text_list)
  168. # 结束该table
  169. table_text += "</table>"
  170. return table_text
  171. log("into read_xml_table")
  172. try:
  173. try:
  174. f = zipfile.ZipFile(path)
  175. for file in f.namelist():
  176. if "word/document.xml" == str(file):
  177. f.extract(file, save_path)
  178. f.close()
  179. except Exception as e:
  180. # print("docx format error!", e)
  181. log("docx format error!")
  182. return [-3]
  183. log("xml_analyze%s"%(save_path))
  184. try:
  185. collection = xml_analyze(save_path + "word/document.xml")
  186. except TimeoutError:
  187. log("xml_analyze timeout")
  188. return [-4]
  189. log("xml_analyze done")
  190. body = collection.getElementsByTagName("w:body")[0]
  191. table_text_list = []
  192. body_nodes = body.childNodes
  193. for node in body_nodes:
  194. if 'w:tbl' in str(node).split(' '):
  195. _table = node
  196. _table_text = recursion_read_table(_table)
  197. table_text_list.append(_table_text)
  198. return table_text_list
  199. except Exception as e:
  200. log("read_xml_table error")
  201. print("read_xml_table", traceback.print_exc())
  202. return [-1]
  203. @timeout(25, timeout_exception=TimeoutError)
  204. def xml_analyze(path):
  205. # 解析xml
  206. DOMTree = xml.dom.minidom.parse(path)
  207. collection = DOMTree.documentElement
  208. return collection
  209. def read_docx_table(document):
  210. table_text_list = []
  211. for table in document.tables:
  212. table_text = "<table>"
  213. # print("==================")
  214. for row in table.rows:
  215. table_text += "<tr>"
  216. for cell in row.cells:
  217. table_text += "<td>" + re.sub("\s","",str(cell.text)) + "</td>"
  218. table_text += "</tr>"
  219. table_text += "</table>"
  220. # print(table_text)
  221. table_text_list.append(table_text)
  222. return table_text_list
  223. class DocxConvert:
  224. def __init__(self, path, unique_type_dir):
  225. self._doc = _Document(path)
  226. self.path = path
  227. self.unique_type_dir = unique_type_dir
  228. @memory_decorator
  229. def init_package(self):
  230. # 各个包初始化
  231. try:
  232. self.docx = docx.Document(self.path)
  233. self.zip = zipfile.ZipFile(self.path)
  234. except:
  235. log("cannot open docx!")
  236. traceback.print_exc()
  237. self._doc.error_code = [-3]
  238. def convert(self):
  239. self.init_package()
  240. if self._doc.error_code is not None:
  241. return
  242. order_and_text_list = self.get_orders()
  243. if judge_error_code(order_and_text_list):
  244. self._doc.error_code = order_and_text_list
  245. return
  246. order_list, text_list = order_and_text_list
  247. self._page = _Page(None, 0)
  248. # 乱码返回文件格式错误
  249. match1 = re.findall(get_garble_code(), ''.join(text_list))
  250. if len(match1) > 10:
  251. log("doc/docx garbled code!")
  252. # self._doc.error_code = [-3]
  253. _sen = _Sentence('文件乱码!', (0, 0, 0, 0))
  254. self._page.add_child(_sen)
  255. self._doc.add_child(self._page)
  256. return
  257. # test
  258. # for i in range(len(text_list)):
  259. # print(order_list[i], text_list[i])
  260. table_list = self.get_tables()
  261. if judge_error_code(table_list):
  262. self._doc.error_code = table_list
  263. return
  264. # paragraph_list = self.get_paragraphs()
  265. image_list = self.get_images()
  266. order_y = 0
  267. doc_pr_cnt = 0
  268. for tag in order_list:
  269. bbox = (0, order_y, 0, 0)
  270. if tag == "w:t":
  271. if len(text_list) > 0:
  272. _para = text_list.pop(0)
  273. _sen = _Sentence(_para, bbox)
  274. _sen.combine=False
  275. self._page.add_child(_sen)
  276. if tag == "wp:docPr":
  277. if len(image_list) > 0:
  278. temp_image_path = self.unique_type_dir + "docpr" + str(doc_pr_cnt) + ".png"
  279. _image = image_list.pop(0)
  280. with open(temp_image_path, "wb") as f:
  281. f.write(_image)
  282. _img = _Image(_image, temp_image_path, bbox)
  283. _img.is_from_docx = True
  284. self._page.add_child(_img)
  285. doc_pr_cnt += 1
  286. if tag == "w:tbl":
  287. if len(table_list) > 0:
  288. _table = table_list.pop(0)
  289. _table = _Table(_table, bbox)
  290. _table.is_html = True
  291. self._page.add_child(_table)
  292. order_y += 1
  293. if self._doc.error_code is None and self._page.error_code is not None:
  294. self._doc.error_code = self._page.error_code
  295. self._doc.add_child(self._page)
  296. def get_paragraphs(self):
  297. # 遍历段落
  298. paragraph_list = []
  299. for paragraph in self.docx.paragraphs:
  300. if paragraph.text != "":
  301. paragraph_list.append(paragraph.text)
  302. return paragraph_list
  303. @memory_decorator
  304. def get_tables(self):
  305. # 遍历表
  306. table_list = read_xml_table(self.path, self.unique_type_dir)
  307. return table_list
  308. def get_images(self):
  309. # 顺序遍历图片
  310. image_list = []
  311. pattern = re.compile('rId\d+')
  312. for graph in self.docx.paragraphs:
  313. for run in graph.runs:
  314. if run.text == '':
  315. try:
  316. if not pattern.search(run.element.xml):
  317. continue
  318. content_id = pattern.search(run.element.xml).group(0)
  319. content_type = self.docx.part.related_parts[content_id].content_type
  320. except Exception as e:
  321. print("docx no image!", e)
  322. continue
  323. if not content_type.startswith('image'):
  324. continue
  325. img_data = self.docx.part.related_parts[content_id].blob
  326. if img_data is not None:
  327. image_list.append(img_data)
  328. return image_list
  329. @memory_decorator
  330. def get_orders(self):
  331. # 解析document.xml,获取文字顺序
  332. order_and_text_list = read_xml_order(self.path, self.unique_type_dir)
  333. return order_and_text_list
  334. def get_doc_object(self):
  335. return self._doc
  336. def get_html(self):
  337. try:
  338. self.convert()
  339. except:
  340. traceback.print_exc()
  341. self._doc.error_code = [-1]
  342. if self._doc.error_code is not None:
  343. return self._doc.error_code
  344. return self._doc.get_html()
  345. if __name__ == '__main__':
  346. c = DocxConvert("C:/Users/Administrator/Downloads/1631944542835.docx", "C:/Users/Administrator/Downloads/1/")
  347. print(c.get_html())