text_matcher.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. """
  2. 文本匹配工具模块
  3. 负责文本标准化、相似度计算等
  4. """
  5. import re
  6. from typing import Optional, List, Dict
  7. from fuzzywuzzy import fuzz
  8. class TextMatcher:
  9. """文本匹配器"""
  10. def __init__(self, similarity_threshold: int = 90):
  11. """
  12. Args:
  13. similarity_threshold: 文本相似度阈值
  14. """
  15. self.similarity_threshold = similarity_threshold
  16. def normalize_text(self, text: str) -> str:
  17. """标准化文本(去除空格、标点等)"""
  18. # 移除所有空白字符
  19. text = re.sub(r'\s+', '', text)
  20. # 转换全角数字和字母为半角
  21. text = self._full_to_half(text)
  22. return text.lower()
  23. def _full_to_half(self, text: str) -> str:
  24. """全角转半角"""
  25. result = []
  26. for char in text:
  27. code = ord(char)
  28. if code == 0x3000: # 全角空格
  29. code = 0x0020
  30. elif 0xFF01 <= code <= 0xFF5E: # 全角字符
  31. code -= 0xFEE0
  32. result.append(chr(code))
  33. return ''.join(result)
  34. def find_matching_bbox(self, target_text: str, text_boxes: List[Dict],
  35. start_index: int, last_match_index: int,
  36. look_ahead_window: int = 10) -> tuple[Optional[Dict], int, int]:
  37. """
  38. 查找匹配的文字框
  39. Args:
  40. target_text: 目标文本
  41. text_boxes: 文字框列表
  42. start_index: 起始索引
  43. last_match_index: 上次匹配成功的索引
  44. look_ahead_window: 向前查找窗口
  45. Returns:
  46. (匹配的文字框信息, 新的指针位置, last_match_index)
  47. """
  48. target_text = self.normalize_text(target_text)
  49. # 过滤过短的目标文本
  50. if len(target_text) < 2:
  51. return None, start_index, last_match_index
  52. # 确定搜索范围
  53. search_start = self._find_search_start(
  54. text_boxes, last_match_index, start_index, look_ahead_window
  55. )
  56. search_end = min(start_index + look_ahead_window, len(text_boxes))
  57. # 在搜索范围内查找最佳匹配
  58. for i in range(search_start, search_end):
  59. if text_boxes[i]['used']:
  60. continue
  61. box_text = self.normalize_text(text_boxes[i]['text'])
  62. # 精确匹配优先
  63. if target_text == box_text:
  64. return self._return_match(text_boxes[i], i, start_index)
  65. # 过滤过短的候选文本
  66. if len(box_text) < 2:
  67. continue
  68. # 长度比例检查
  69. if not self._check_length_ratio(target_text, box_text):
  70. continue
  71. # 计算相似度
  72. if self._is_similar(target_text, box_text):
  73. return self._return_match(text_boxes[i], i, start_index)
  74. return None, start_index, last_match_index
  75. def _find_search_start(self, text_boxes: List[Dict], last_match_index: int,
  76. start_index: int, look_ahead_window: int) -> int:
  77. """确定搜索起始位置"""
  78. search_start = last_match_index - 1
  79. unused_count = 0
  80. while search_start >= 0:
  81. if not text_boxes[search_start]['used']:
  82. unused_count += 1
  83. if unused_count >= look_ahead_window:
  84. break
  85. search_start -= 1
  86. if search_start < 0:
  87. search_start = 0
  88. while search_start < start_index and text_boxes[search_start]['used']:
  89. search_start += 1
  90. return search_start
  91. def _check_length_ratio(self, text1: str, text2: str) -> bool:
  92. """检查长度比例"""
  93. length_ratio = min(len(text1), len(text2)) / max(len(text1), len(text2))
  94. return length_ratio >= 0.3
  95. def _is_similar(self, text1: str, text2: str) -> bool:
  96. """判断两个文本是否相似"""
  97. # 子串检查
  98. shorter = text1 if len(text1) < len(text2) else text2
  99. longer = text2 if len(text1) < len(text2) else text1
  100. is_substring = shorter in longer
  101. # 计算相似度
  102. partial_ratio = fuzz.partial_ratio(text1, text2)
  103. if is_substring:
  104. partial_ratio += 10 # 子串时提升相似度
  105. return partial_ratio >= self.similarity_threshold
  106. def _return_match(self, text_box: Dict, index: int, start_index: int) -> tuple:
  107. """返回匹配结果"""
  108. if index >= start_index:
  109. return text_box, index + 1, index
  110. else:
  111. return text_box, start_index, index