abstract_template.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. # -*- coding: utf-8 -*-
  2. from abc import ABC, abstractmethod
  3. from typing import Dict, Optional, Any
  4. import re
  5. # from lxml import etree
  6. def table2list(table):
  7. """
  8. 解析HTML表格,处理rowspan/colspan合并单元格,返回完整的二维列表
  9. Args:
  10. table: lxml.etree._Element 表格对象
  11. Returns:
  12. 二维列表表示的表格数据
  13. """
  14. def _check_cell_validity(i: int, j: int) -> bool:
  15. """检查单元格(i, j)是否可以放入_output"""
  16. if i >= len(_output):
  17. return True
  18. if j >= len(_output[i]):
  19. return True
  20. if _output[i][j] == "#$#":
  21. return True
  22. return False
  23. def _insert(i: int, j: int, height: int, width: int, val: str):
  24. """将值val插入到以(i, j)为起点、跨height行width列的矩形区域"""
  25. for ii in range(i, i + height):
  26. for jj in range(j, j + width):
  27. _insert_cell(ii, jj, val)
  28. def _insert_cell(i: int, j: int, val: str):
  29. """在特定位置(i, j)插入值,自动扩展_output矩阵"""
  30. while i >= len(_output):
  31. _output.append([])
  32. while j >= len(_output[i]):
  33. _output[i].append("#$#")
  34. if _output[i][j] == "#$#":
  35. _output[i][j] = val
  36. # 初始化输出矩阵
  37. _output = []
  38. row_ind = 0
  39. col_ind = 0
  40. # 使用lxml.etree解析HTML
  41. try:
  42. # parser = etree.HTMLParser(remove_blank_text=True, remove_comments=True)
  43. # tree = etree.fromstring(html_content, parser)
  44. #
  45. # # 获取第一个表格(使用XPath)
  46. # tables = tree.xpath('//table')
  47. # if not tables:
  48. # return []
  49. # table = tables[0]
  50. # 获取所有行
  51. rows = table.xpath('./tr | ./tbody/tr')
  52. for row in rows:
  53. # 记录最小row_span,确定需要跳过多少行
  54. smallest_row_span = 1
  55. # 获取所有单元格(td和th)
  56. cells = row.xpath('./td | ./th')
  57. for cell in cells:
  58. # 处理rowspan
  59. row_span_attr = cell.get('rowspan')
  60. if row_span_attr and row_span_attr.isdigit():
  61. row_span = int(row_span_attr)
  62. if row_span == 0: # 修复rowspan为0的情况
  63. row_span = 1
  64. else:
  65. row_span = 1
  66. # 更新最小row_span
  67. smallest_row_span = min(smallest_row_span, row_span)
  68. # 处理colspan
  69. col_span_attr = cell.get('colspan')
  70. if col_span_attr and col_span_attr.isdigit():
  71. col_span = int(col_span_attr)
  72. if col_span > 20: # 限制过大的colspan
  73. col_span = 20
  74. elif col_span == 0: # 修复colspan为0的情况
  75. col_span = 1
  76. else:
  77. col_span = 1
  78. # 找到合适的列索引
  79. while True:
  80. if _check_cell_validity(row_ind, col_ind):
  81. break
  82. col_ind += 1
  83. # 提取单元格文本
  84. text = ''.join(cell.itertext()).strip() # 获取所有文本内容
  85. # 处理省略号情况:如果有title属性且文本以...结尾,使用title内容
  86. title_attr = cell.get('title')
  87. if (title_attr and text.replace(' ', '').endswith('...') and
  88. title_attr.replace(' ', '').startswith(text.replace(' ', '')[:-3])):
  89. text = title_attr
  90. text = re.sub(r'\s+', '', text) # 合并多余空格
  91. # 插入值到_output
  92. _insert(row_ind, col_ind, row_span, col_span, text.replace('(', '(').replace(')', ')'))
  93. # 更新列索引
  94. col_ind += col_span
  95. # 更新行索引
  96. row_ind += smallest_row_span
  97. col_ind = 0
  98. # except etree.ParseError as e:
  99. # print(f"HTML解析错误: {e}")
  100. # return []
  101. except Exception as e:
  102. print(f"处理表格时发生错误: {e}")
  103. return []
  104. return _output
  105. class AbstractTemplate(ABC):
  106. """
  107. 抽象模板基类(遵循文档中“模板调用”的统一接口类设计)
  108. 定义所有模板必须实现的通用方法,约束模板结构与调用逻辑
  109. """
  110. def __init__(self, template_id: str, priority: int = 1):
  111. """
  112. 初始化模板核心属性(对应文档“模板存在方式”的字段定义)
  113. :param template_id: 模板唯一标识ID
  114. :param priority: 模板优先级(数值越大优先级越高,用于结果融合)
  115. """
  116. self.template_id = template_id
  117. self.priority = priority
  118. @abstractmethod
  119. def check_call_timing(self, preprocessed_data: Dict[str, Any]) -> bool:
  120. """
  121. 抽象方法:判断模板调用时机(文档核心要求,避免无效模板遍历)
  122. 需子类实现具体维度的调用条件判断
  123. :param preprocessed_data: 要素提取预处理后的结果(含输入参数)
  124. :return: True=满足调用条件,False=不满足
  125. """
  126. pass
  127. @abstractmethod
  128. def extract(self, preprocessed_data: Dict[str, Any]) -> Dict[str, Any]:
  129. """
  130. 抽象方法:执行模板提取逻辑(文档核心功能)
  131. 需子类实现具体的提取规则(关键词定位/分隔符拆分/正则等)
  132. :param preprocessed_data: 要素提取预处理后的结果(需包含input_params指定的参数)
  133. :return: 提取结果字典(键为要素名,值为提取内容;空字典表示提取失败)
  134. """
  135. pass
  136. def get_template_info(self) -> Dict[str, Any]:
  137. """
  138. 通用方法:获取模板基本信息(映射文档“模板存在方式”的表格结构)
  139. :return: 模板信息字典
  140. """
  141. return {
  142. "id": self.template_id,
  143. # "维度": self.dimension,
  144. # "代码路径": self.code_path,
  145. # "输入参数": self.input_params,
  146. # "输出格式": self.output_format,
  147. # "隶属id": self.parent_id,
  148. "优先级": self.priority
  149. }