from typing import List, TypeAlias import re import shutil import os Hierarchy: TypeAlias = dict[str, str] class HierarchyParser: def __init__(self): self.appendix = False self.content_flag = False self.preface = False self.exclude_pattern = r'^Приложение [А-Я]' self.patterns = [ r'^\d+\.?\s', # Соответствует "1.", "2.", и т.д. r'^\d+\.\d+\.?\s', # Соответствует "1.1.", "2.1.", и т.д. r'^\d+\.\d+\.\d+\.?\s', # Соответствует "1.1.1", "2.1.1", и т.д. r'^\d+\.\d+\.\d+\.\d+\.?\s', # Соответствует "1.1.1.1", "2.1.1.1", и т.д. r'^\d+\.\d+\.\d+\.\d+\.\d+\.?\s', # Соответствует "1.1.1.1.1", "2.1.1.1.1", и т.д. r'^\d+\.\d+\.\d+\.\d+\.\d+\.\d+\.?\s', # Соответствует "1.1.1.1.1.1", "2.1.1.1.1.1", и т.д. r'^\d+\.\d+\.\d+\.\d+\.\d+\.\d+\.\d+\.?\s', # Соответствует "1.1.1.1.1.1.1", и т.д. ] self.russian_alphabet = "АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя" self.english_alphabet = "ABCDEFGHIJKabcdefghijk" self.table_name = '' def __init_parameters(self): """Сбросить найденные параграфы""" self._hierarchy = {} self.duplicate_marker_list = [] self.duplicate_marker_count, self.count, self.count_appendix = 0, 1, 1 self.appendix = False self.letter_ = None self.content_flag = False self.preface = False self.table_name = '' def _processing_without_paragraph(self, text: str, unique_doc_number): """ Обрабатывает ':' если в начале нет нумерации параграфа. Args: text: Текст """ last_key = list(self._hierarchy.keys())[-1] split_key = last_key.split('_') if len(split_key) != 2: last_lvl = split_key[1] pattern_text_last = split_key[2].replace(' ', '') key = f"{unique_doc_number}_{last_lvl}_{pattern_text_last}" if len(split_key) == 3: self._hierarchy[f'{key}_PartLevel{self.count}'] = f"{self._hierarchy[key]} {text}" else: ch = split_key[3] self._hierarchy[f'{key}_{ch}_PartLeveL{self.count}'] = f"{self._hierarchy[key]} {text}" else: self._hierarchy[f'{unique_doc_number}_{text.replace("_", " ")}--DoublePoint0'] = text def _processing_special_markers(self, text, pattern_text, unique_doc_number): """ Обрабатывает маркеры 'а)', 'б)' или '-' в строке. Args: text: Текст pattern_text: Паттерн для парсинга и кодирования """ last_key = list(self._hierarchy.keys())[-1] split_key = last_key.split('_') if len(split_key) != 2: last_lvl = split_key[1] pattern_text_last = split_key[2] key = f'{unique_doc_number}_{last_lvl}_{pattern_text_last}' if pattern_text is not None: pattern_text = pattern_text.replace(')', '') self._hierarchy[key + f'_PartLevel{pattern_text[0]}'] = f'{self._hierarchy[key]} {text}' else: if len(split_key) == 3: self._hierarchy[key + f'_PartLevel{self.count}'] = f'{self._hierarchy[key]} {text}' else: ch = split_key[3] if ch.replace('PartLevel', '')[-1] in self.russian_alphabet or ch.replace('PartLevel', '')[-1] in self.english_alphabet: if len(split_key) == 4: self.count = 1 self._hierarchy[key + f'_{ch}_PartLeveL{self.count}'] = f'{self._hierarchy[key]} {text}' elif re.search(r"<\d>", last_key): self._hierarchy[key + f'_{ch}_PartLeveL{self.count}'] = f'{self._hierarchy[f"{key}_{ch}"]} {text}' elif 'Table' in ch: self._hierarchy[f'{key}_PartLevel{self.count}'] = f'{self._hierarchy[key]} {text}' else: try: if int(ch.replace('PartLevel', '')) + 1 != self.count: self._hierarchy[f'{key}_PartLevel{int(ch[-1]) + 1}'] = f'{self._hierarchy[key]} {text}' else: self._hierarchy[f'{key}_PartLevel{self.count}'] = f'{self._hierarchy[key]} {text}' except ValueError: self._hierarchy[f'{key}_PartLevel{self.count}'] = f'{self._hierarchy[key]} {text}' else: split_key = last_key.split('^') self._hierarchy[f'{split_key[0]}^PartLevel{self.count}^UniqueNumber{self.unique_count}'] = f"{self._hierarchy[f'{split_key[0]}']} {text}" def _processing_appendix(self, text, level, pattern_text, unique_doc_number): """ Обрабатывает маркеры 'Приложение' и все, что находится внутри приложения Args: text: Текст level: Уровень параграфа pattern_text: Паттерн для парсинга и кодирования """ if pattern_text is not None: pattern_text = pattern_text.replace(')', '') if level == 13: self.letter_ = pattern_text[-1] self.count = 1 self.count_appendix = 1 if level == 13 or level is None and pattern_text is None: if level is None: last_key = list(self._hierarchy.keys())[-1] split_key = last_key.split('_') if len(split_key) == 4: self._hierarchy[f'{last_key}_PartLevel{self.count_appendix}'] = text self.count_appendix += 1 elif len(split_key) == 5: self._hierarchy[f'{"_".join(split_key[:-1])}_PartLevel{self.count_appendix}'] = text self.count_appendix += 1 else: self._hierarchy[f'{unique_doc_number}_Приложение{self.letter_}{self.count}'] = text self.count_appendix = 1 else: self._hierarchy[f'{unique_doc_number}_Приложение{self.letter_}{self.count}'] = text self.count_appendix = 1 self.count += 1 elif level is not None and pattern_text is not None and level != 11: key = f'{unique_doc_number}_Приложение{self.letter_}{self.count}_Level{level}_PatternText{pattern_text}' self._hierarchy[key] = text self.count += 1 self.count_appendix = 1 elif level == 10: last_key = list(self._hierarchy.keys())[-1] split_key = last_key.split('_') key = f'{last_key}_PartLevel{self.count_appendix}' if len(split_key) != 2: self._hierarchy[key] = f"{self._hierarchy[last_key]} {text}" else: self._hierarchy[key] = text self.count_appendix += 1 elif level == 11: last_key = list(self._hierarchy.keys())[-1] split_key = last_key.split('_') appendix = split_key[1] if len(split_key) == 2: key = f'{last_key}_PartLevel{self.count_appendix}' self._hierarchy[key] = f"{self._hierarchy[last_key]} {text}" elif len(split_key) != 3: last_lvl = split_key[2] pattern_text_last = split_key[3] key = f'{unique_doc_number}_{appendix}_{last_lvl}_{pattern_text_last}' self._hierarchy[f'{key}_PartLevel{self.count_appendix}'] = f"{self._hierarchy[key]} {text}" else: key = f'{unique_doc_number}_{appendix}' try: self._hierarchy[f'{key}_PartLevel{self.count_appendix}'] = f"{self._hierarchy[f'{key}_PartLevel1']} {text}" except KeyError: print('asdfasdf') self._hierarchy[f'{key}_PartLevel{self.count_appendix}'] = text self.count_appendix += 1 def _get_pattern(self, text): """ Метод находит паттерны в документе для дальнейшей обработки в соответствии с паттерном Args: text: Текст. Returns: Код паттерна или None, Паттерн или None Notes: 0-7 это разделы 1, 1.1., 1.1.1. и т.д 10 это паттерн для поиска ':' в конце предложения 11 это паттерн для поиска а), б) или строк начинающихся с '-', '' 12 это паттерн для поиска наименования таблиц 'Таблица 1', 'Таблица 2' в начале строки 13 это паттерн для поиска 'Приложение А', 'Приложение Б' в начале строки 14 это паттерн для поиска <1>, <2> в начале строки """ for i, pattern in enumerate(self.patterns): pattern = re.match(pattern, text) if pattern: self.preface = False return i, pattern.group(0).replace(' ', '').replace('\xa0', '') if re.match(r'^- ', text) or re.match(r'^– ', text): return 11, None if re.match(r'^ ', text): return 11, None if re.match(r'\d\)', text): return 11, None if re.match(r'\w\)', text): pattern = re.match(r'\w\)', text) return 11, pattern.group(0).replace('\xa0', '') if re.match(r"<\d>", text): pattern = re.match(r"<\d>", text) return 14, pattern.group(0).replace('\xa0', '') if re.search(r':$', text) or re.search(r':$', text): return 10, None if re.findall(r'^Таблица \d+\.?', text): pattern = re.match(r'^Таблица \d+\.?', text) return 12, pattern.group(0).replace('\xa0', '') if re.match(self.exclude_pattern, text): pattern = re.match(self.exclude_pattern, text) # if pattern.regs[0][1] + 2 < len(text): # return None, None self.appendix = True return 13, pattern.group(0).replace('\xa0', '') if re.match(r'^Содержание', text): self.content_flag = True return 15, None if re.match(r'^Предисловие', text): self.preface = True return 16, None return None, None def _find_duplicate_marker(self, level, pattern_text): """ Метод находит одинаковые параграфы в документе. И присваивает дубликату порядковый номер Args: level: Уровень параграфа pattern_text: Паттерн для парсинга и кодирования Returns: Название нового параграфа если дубликат был или название старого если не было дубликата """ if pattern_text is not None: pattern_text = pattern_text.replace(' ', '') if pattern_text in self.duplicate_marker_list and pattern_text[0] not in self.russian_alphabet: if level == 0: self.duplicate_marker_count += 1 pattern_text = f'{pattern_text}:Duplicate{self.duplicate_marker_count}' self.duplicate_marker_list.append(pattern_text) return pattern_text def __find_last_paragraph(self, section): for paragraph_ind in range(section.Paragraphs.Count): paragraph = section.Paragraphs[paragraph_ind] if paragraph.Text == '': continue if paragraph.ListText != '': text = f'{paragraph.ListText} {paragraph.Text}' else: text = paragraph.Text level_paragraph, pattern_text = self._get_pattern(text) if pattern_text and 350 < paragraph_ind < 490 and level_paragraph < 2: pattern_text_zxc = paragraph.Text try: return pattern_text_zxc except: return None def parse_table(self, doc: List, unique_doc_number): self.__init_parameters() flag = True for text in doc: text = text.strip() # удаляет пробелы в начале и конце текста параграфа. text = text.replace('------', '').replace('--', '').replace('\u000b', ' ').replace('\t', ' ') text = text.replace('_', ' ').replace('\u0007', ' ').replace(' ', ' ').replace('', '-') text = text.replace(' ', ' ') if not text: continue if re.match(r'^Т\d?\d$', text) or re.match(r'^T\d?\d$', text): try: last_key = list(self._hierarchy.keys())[-1] last_text = self._hierarchy[last_key] if re.search(r': \d?\d?: $', last_text) or re.search(r': \d?\d?:$', last_text) or last_text == '': self._hierarchy.pop(last_key, None) except IndexError: pass self.table_name = text flag = True elif flag: self._hierarchy[f'{unique_doc_number}_Table{self.table_name}_String{text}'] = '' flag = False else: last_key = list(self._hierarchy.keys())[-1] if text in self._hierarchy[last_key]: continue self._hierarchy[last_key] = f'{self._hierarchy[last_key]} {text}' try: last_key = list(self._hierarchy.keys())[-1] last_text = self._hierarchy[last_key] if re.search(r': \d?\d?: $', last_text) or re.search(r': \d?\d?:$', last_text): self._hierarchy.pop(last_key, None) except IndexError: pass def parse(self, doc: List, unique_doc_number, stop_appendix_list): self.__init_parameters() name_paragraph = None flag = True flag_appendix_stop = False self.unique_count = 0 for text in doc: text = text.strip() # удаляет пробелы в начале и конце текста параграфа. text = text.replace('------', '').replace('--', '').replace('\u000b', ' ').replace('\t', ' ').replace('\ufeff', '') text = text.replace('_', ' ').replace('\u0007', ' ').replace(' ', ' ').replace('', '-').replace('', '') if not text: continue if flag: self._hierarchy[f'{unique_doc_number}_{text.replace("_", " ")}'] = f'{text}' flag = False continue level_paragraph, pattern_text = self._get_pattern(text) # чтобы определить уровень текущего параграфа. if self.preface and not self.content_flag: if level_paragraph == 16: self._hierarchy[f'{unique_doc_number}_Предисловие'] = f'{text}' continue else: self._hierarchy[f'{unique_doc_number}_Предисловие:^PatternText{pattern_text}'] = f'{self._hierarchy[f"{unique_doc_number}_Предисловие"]} {text}' continue if self.content_flag: self.preface = False if level_paragraph == 15: self._hierarchy[f'{unique_doc_number}_Содержание'] = f'{text}' continue elif level_paragraph is not None: if level_paragraph <= 9 and '1' in pattern_text and not re.findall(r'\d+$', text): self.content_flag = False else: self._hierarchy[ f'{unique_doc_number}_Содержание'] = f'{self._hierarchy[f"{unique_doc_number}_Содержание"]} {text}' if level_paragraph == 13: self.appendix = False continue elif text not in self._hierarchy[f'{unique_doc_number}_Содержание']: self._hierarchy[f'{unique_doc_number}_Содержание'] = f'{self._hierarchy[f"{unique_doc_number}_Содержание"]} {text}' continue else: self.content_flag = False if self.appendix and not self.content_flag: if level_paragraph == 13: if pattern_text in stop_appendix_list or pattern_text[-1] in stop_appendix_list: flag_appendix_stop = True continue else: flag_appendix_stop = False if not flag_appendix_stop: self._processing_appendix(text, level_paragraph, pattern_text, unique_doc_number) continue # TODO Проверить работоспособность и перенести в случае чего обратно перед условием про appendix pattern_text = self._find_duplicate_marker(level_paragraph, pattern_text) if level_paragraph is not None: if level_paragraph <= 9: # Пункты 1. 1.1. 2.1 и так далее до 1.1.1.1.1.1.1 name_paragraph = text key = f'{unique_doc_number}_Level{level_paragraph}_PatternText{pattern_text}' self._hierarchy[key] = text self.count = 1 elif level_paragraph == 10: self._processing_without_paragraph(text, unique_doc_number) self.unique_count += 1 self.count = 1 elif level_paragraph == 11: self._processing_special_markers(text, pattern_text, unique_doc_number) self.count += 1 elif level_paragraph == 14: if name_paragraph is not None: level_, pattern_text_ = self._get_pattern(name_paragraph) self._hierarchy[f'{unique_doc_number}_Level{level_}_PatternText{pattern_text_}_PartLevel{pattern_text}'] = f'{text}' else: new_text = text.replace(f'{pattern_text}', '') self._hierarchy[f'{unique_doc_number}_{new_text}'] = f'{text}' self.count = 1 elif level_paragraph == 12: # Обработка Таблиц last_key = list(self._hierarchy.keys())[-self.count] split_key = last_key.split('_') last_lvl = split_key[1] numer_table = pattern_text.replace('Таблица', '').replace('.', '') if len(split_key) != 2: pattern_text_last = last_key.split('_')[2] key = f'{unique_doc_number}_{last_lvl}_{pattern_text_last}_Table{numer_table}' else: pattern_text_last = last_key.split('_')[1] key = f'{unique_doc_number}_{pattern_text_last}_Table{numer_table}' self._hierarchy[key] = text self.count = 1 else: last_key = list(self._hierarchy.keys())[-1] if name_paragraph is not None: split_key = last_key.split('_') if 'Table' in split_key[-1]: level_paragraph, pattern_text = self._get_pattern(name_paragraph) self._hierarchy[f'{unique_doc_number}_Level{level_paragraph}_PatternText{pattern_text}_PartLevel{self.count}'] = f'{text}' self.count += 1 else: self._hierarchy[f'{last_key}'] = f'{self._hierarchy[f"{last_key}"]} {text}' else: self._hierarchy[f'{last_key}'] = f'{self._hierarchy[last_key]} {text}' def hierarchy(self): """Вернуть иерархию документа""" return self._hierarchy @staticmethod def clear_tmp(root_path): # Проверяем, существует ли папка if not os.path.isdir(root_path): raise FileNotFoundError(f"Папка {root_path} не найдена.") # Перебираем все файлы и папки внутри указанной директории for item in os.listdir(root_path): item_path = os.path.join(root_path, item) # Удаляем папки и их содержимое рекурсивно if os.path.isdir(item_path): shutil.rmtree(item_path) else: # Удаляем файлы os.remove(item_path)