html_generator.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. """
  2. HTML 生成器模块
  3. 提供 HTML 输出功能:
  4. - 表格 HTML 生成(带样式)
  5. - 单元格坐标展示
  6. """
  7. import json
  8. from pathlib import Path
  9. from typing import Dict, Any, List
  10. from loguru import logger
  11. class HTMLGenerator:
  12. """HTML 生成器类"""
  13. @staticmethod
  14. def save_table_htmls(
  15. results: Dict[str, Any],
  16. output_dir: Path,
  17. doc_name: str,
  18. is_pdf: bool = True
  19. ) -> Path:
  20. """
  21. 保存表格 HTML 文件
  22. 命名规则:
  23. - PDF输入: 文件名_table_1_page_001.html
  24. - 图片输入(单页): 文件名_table_1.html
  25. Args:
  26. results: 处理结果
  27. output_dir: 输出目录
  28. doc_name: 文档名称
  29. is_pdf: 是否为 PDF 输入
  30. Returns:
  31. 表格目录路径
  32. """
  33. tables_dir = output_dir / 'tables'
  34. tables_dir.mkdir(exist_ok=True)
  35. table_count = 0
  36. total_pages = len(results.get('pages', []))
  37. for page in results.get('pages', []):
  38. page_idx = page.get('page_idx', 0)
  39. for element in page.get('elements', []):
  40. if element.get('type') in ['table', 'table_body']:
  41. table_count += 1
  42. content = element.get('content', {})
  43. html = content.get('html', '')
  44. cells = content.get('cells', [])
  45. if html:
  46. full_html = HTMLGenerator._generate_table_html_with_styles(
  47. html, cells, doc_name, page_idx, table_count
  48. )
  49. # 根据输入类型决定命名
  50. if is_pdf or total_pages > 1:
  51. html_path = tables_dir / f"{doc_name}_table_{table_count}_page_{page_idx + 1:03d}.html"
  52. else:
  53. html_path = tables_dir / f"{doc_name}_table_{table_count}.html"
  54. with open(html_path, 'w', encoding='utf-8') as f:
  55. f.write(full_html)
  56. if table_count > 0:
  57. logger.info(f"📊 {table_count} tables saved to: {tables_dir}")
  58. return tables_dir
  59. @staticmethod
  60. def _generate_table_html_with_styles(
  61. table_html: str,
  62. cells: List[Dict],
  63. doc_name: str,
  64. page_idx: int,
  65. table_idx: int
  66. ) -> str:
  67. """
  68. 生成带样式的完整 HTML
  69. Args:
  70. table_html: 表格 HTML 内容
  71. cells: 单元格列表
  72. doc_name: 文档名称
  73. page_idx: 页码
  74. table_idx: 表格序号
  75. Returns:
  76. 完整的 HTML 字符串
  77. """
  78. cells_json = json.dumps(cells, ensure_ascii=False, indent=2) if cells else "[]"
  79. return f"""<!DOCTYPE html>
  80. <html lang="zh-CN">
  81. <head>
  82. <meta charset="UTF-8">
  83. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  84. <title>{doc_name} - Table {table_idx}</title>
  85. <style>
  86. body {{
  87. font-family: Arial, "Microsoft YaHei", sans-serif;
  88. margin: 20px;
  89. background-color: #f5f5f5;
  90. }}
  91. .container {{
  92. max-width: 1400px;
  93. margin: 0 auto;
  94. background-color: white;
  95. padding: 20px;
  96. box-shadow: 0 0 10px rgba(0,0,0,0.1);
  97. border-radius: 8px;
  98. }}
  99. .meta {{
  100. color: #666;
  101. font-size: 0.9em;
  102. margin-bottom: 20px;
  103. padding-bottom: 10px;
  104. border-bottom: 1px solid #ddd;
  105. }}
  106. table {{
  107. border-collapse: collapse;
  108. width: 100%;
  109. margin: 20px 0;
  110. }}
  111. th, td {{
  112. border: 1px solid #ddd;
  113. padding: 8px 12px;
  114. text-align: left;
  115. }}
  116. th {{
  117. background-color: #f2f2f2;
  118. font-weight: bold;
  119. }}
  120. tr:hover {{
  121. background-color: #f9f9f9;
  122. }}
  123. td[data-bbox], th[data-bbox] {{
  124. position: relative;
  125. }}
  126. td[data-bbox]:hover::after, th[data-bbox]:hover::after {{
  127. content: attr(data-bbox);
  128. position: absolute;
  129. bottom: 100%;
  130. left: 0;
  131. background: #333;
  132. color: white;
  133. padding: 2px 6px;
  134. font-size: 10px;
  135. border-radius: 3px;
  136. white-space: nowrap;
  137. z-index: 100;
  138. }}
  139. .cells-info {{
  140. margin-top: 30px;
  141. padding: 15px;
  142. background-color: #f8f9fa;
  143. border-radius: 5px;
  144. }}
  145. .cells-info summary {{
  146. cursor: pointer;
  147. font-weight: bold;
  148. color: #333;
  149. }}
  150. .cells-info pre {{
  151. background-color: #2d2d2d;
  152. color: #f8f8f2;
  153. padding: 15px;
  154. border-radius: 5px;
  155. overflow-x: auto;
  156. font-size: 12px;
  157. }}
  158. </style>
  159. </head>
  160. <body>
  161. <div class="container">
  162. <div class="meta">
  163. <p><strong>Document:</strong> {doc_name}</p>
  164. <p><strong>Page:</strong> {page_idx + 1}</p>
  165. <p><strong>Table:</strong> {table_idx}</p>
  166. <p><strong>Cells with coordinates:</strong> {len(cells)}</p>
  167. </div>
  168. {table_html}
  169. <div class="cells-info">
  170. <details>
  171. <summary>📍 单元格坐标数据 (JSON)</summary>
  172. <pre>{cells_json}</pre>
  173. </details>
  174. </div>
  175. </div>
  176. </body>
  177. </html>"""