ExportDocument.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import os
  2. from datetime import datetime
  3. from bs4 import BeautifulSoup
  4. from docx import Document
  5. from docx.shared import Pt
  6. from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
  7. from docx.shared import RGBColor
  8. from docx.oxml.ns import qn
  9. from docx.oxml import OxmlElement
  10. from DCbackend.settings import MEDIA_ROOT
  11. # 添加标题和书签
  12. def add_title_with_bookmark(doc, text, style, bookmark_id):
  13. paragraph = doc.add_paragraph(text, style=style)
  14. run = paragraph.add_run()
  15. tag = run._r
  16. start = OxmlElement('w:bookmarkStart')
  17. start.set(qn('w:id'), str(bookmark_id))
  18. start.set(qn('w:name'), text)
  19. tag.append(start)
  20. tr = OxmlElement('w:r')
  21. tr.text = ''
  22. tag.append(tr)
  23. end = OxmlElement('w:bookmarkEnd')
  24. end.set(qn('w:id'), str(bookmark_id))
  25. end.set(qn('w:name'), text)
  26. tag.append(end)
  27. # 获取文档文件名称
  28. def getDocxFileName():
  29. date = datetime.now().strftime("%Y%m%d%H%M%S")
  30. fileName = "output.docx"
  31. filename = f"{date}_{fileName}"
  32. path = os.path.join(MEDIA_ROOT, filename)
  33. return path
  34. def saveDocFile(title,htmlContent):
  35. doc = Document()
  36. # 标记目录的位置
  37. soup = BeautifulSoup(htmlContent, 'html.parser')
  38. paragraphs = soup.find_all(['h1'])
  39. # 将HTML内容转换为Word文档
  40. bookMarkId = 0
  41. if paragraphs:
  42. doc.add_paragraph(title)
  43. doc.add_paragraph("目录")
  44. catalog_p = doc.add_paragraph('')
  45. for p in paragraphs:
  46. text = p.get_text()
  47. style = p.name
  48. if style.startswith('h1'):
  49. # 添加标题
  50. bookMarkId=bookMarkId+1
  51. add_title_with_bookmark(doc, text, style='Heading 1', bookmark_id=bookMarkId)
  52. else:
  53. doc.add_paragraph(text)
  54. else:
  55. paragraphs = soup.find_all(['div'])
  56. for paragraph in paragraphs:
  57. doc.add_paragraph(paragraph.getText())
  58. if bookMarkId>0:
  59. # 开始写入目录
  60. # 目录开头增加值域,方便手动更新整个目录。自动生成的目录标题包含在值域中。
  61. toc_paragraph = catalog_p.insert_paragraph_before() # 在标记的目录位置前添加段落
  62. r1 = toc_paragraph.add_run()
  63. toc_field = OxmlElement('w:fldChar')
  64. toc_field.set(qn('w:fldCharType'), 'begin')
  65. r1._r.append(toc_field)
  66. r2 = toc_paragraph.add_run()
  67. toc_field = OxmlElement('w:instrText')
  68. toc_field.set(qn('xml:space'), 'preserve')
  69. toc_field.text = 'TOC \\o "1-3" \\h \\z '
  70. r2._r.append(toc_field)
  71. r3 = toc_paragraph.add_run()
  72. toc_field = OxmlElement('w:fldChar')
  73. toc_field.set(qn('w:fldCharType'), 'separate')
  74. r3._r.append(toc_field)
  75. # 自动生成目录内容,不包含页码
  76. for paragraph in doc.paragraphs:
  77. if 'Heading' in paragraph.style.name:
  78. b = paragraph._element.findall('.//' + qn('w:bookmarkStart'))
  79. bookmark_name = b[0].get(qn('w:name'))
  80. text = paragraph.text
  81. level = int(paragraph.style.name[-1])
  82. # print(text, bookmark_name)
  83. toc_paragraph = catalog_p.insert_paragraph_before(style='Normal')
  84. # 二级标题设置缩进
  85. if level == 2:
  86. toc_paragraph.paragraph_format.first_line_indent = Pt(24)
  87. # 设置制表符,可显示页码前的"…………"
  88. tabs = OxmlElement('w:tabs')
  89. tab1 = OxmlElement('w:tab')
  90. tab1.set(qn('w:val'), "left")
  91. tab1.set(qn('w:leader'), "dot")
  92. tab1.set(qn('w:pos'), "8400")
  93. tabs.append(tab1)
  94. toc_paragraph._p.pPr.append(tabs)
  95. # toc_paragraph若未设定style,toc_paragraph._p没有pPr属性,需注释前一句代码,使用以下语句
  96. # pPr = OxmlElement('w:pPr')
  97. # pPr.append(tabs)
  98. # toc_paragraph._p.append(pPr)
  99. hyperlink = OxmlElement('w:hyperlink')
  100. hyperlink.set(qn('w:anchor'), bookmark_name)
  101. hyperlink.set(qn('w:history'), '1')
  102. hr1 = OxmlElement('w:r')
  103. rPr = OxmlElement('w:rPr')
  104. rStyle = OxmlElement('w:rStyle')
  105. rStyle.set(qn('w:val'), "a4")
  106. rPr.append(rStyle)
  107. hr1.text = text
  108. hr1.append(rPr)
  109. hyperlink.append(hr1)
  110. hr2 = OxmlElement('w:r')
  111. rPr = OxmlElement('w:rPr')
  112. fldChar = OxmlElement('w:fldChar')
  113. fldChar.set(qn('w:fldCharType'), 'begin')
  114. hr2.append(rPr)
  115. hr2.append(fldChar)
  116. hyperlink.append(hr2)
  117. hr3 = OxmlElement('w:r')
  118. rPr = OxmlElement('w:rPr')
  119. instrText = OxmlElement('w:instrText')
  120. instrText.set(qn('xml:space'), 'preserve')
  121. instrText.text = ' PAGEREF {} \h '.format(bookmark_name)
  122. hr3.append(rPr)
  123. hr3.append(instrText)
  124. hyperlink.append(hr3)
  125. hr4 = OxmlElement('w:r')
  126. rPr = OxmlElement('w:rPr')
  127. fldChar = OxmlElement('w:fldChar')
  128. fldChar.set(qn('w:fldCharType'), 'separate')
  129. hr4.append(rPr)
  130. hr4.append(fldChar)
  131. hyperlink.append(hr4)
  132. hrt = OxmlElement('w:r')
  133. tab = OxmlElement('w:tab')
  134. hrt.append(tab)
  135. hyperlink.append(hrt)
  136. hr5 = OxmlElement('w:r')
  137. rPr = OxmlElement('w:rPr')
  138. hr5.text = ''
  139. hr5.append(rPr)
  140. hyperlink.append(hr5)
  141. hr6 = OxmlElement('w:r')
  142. rPr = OxmlElement('w:rPr')
  143. fldChar = OxmlElement('w:fldChar')
  144. fldChar.set(qn('w:fldCharType'), 'end')
  145. hr6.append(rPr)
  146. hr6.append(fldChar)
  147. hyperlink.append(hr6)
  148. toc_paragraph._p.append(hyperlink)
  149. # 目录结尾的值域
  150. toc_paragraph = catalog_p.insert_paragraph_before()
  151. r4 = toc_paragraph.add_run()
  152. toc_field = OxmlElement('w:fldChar')
  153. toc_field.set(qn('w:fldCharType'), 'end')
  154. r4._r.append(toc_field)
  155. # 分页
  156. break_page_p = catalog_p.insert_paragraph_before()
  157. break_page_p.add_run().add_break(WD_BREAK.PAGE)
  158. fileName=getDocxFileName()
  159. doc.save(fileName)
  160. return fileName