import logging import os import re import subprocess from pathlib import Path from docx import Document from docx.oxml import parse_xml from docx.oxml.ns import qn from fastapi import HTTPException START_SPECIALS = re.escape(r'$$$$$SVB396') END_SPECIALS = re.escape(r'$$18 Текст') class FileService: def __init__(self): self.documents_path = Path(os.environ.get('DOCUMENTS_PATH', '/app/data/xmls_processed')) self.source_path = Path(os.environ.get('SOURCE_PATH', '/app/data/SEND')) def prepare_file(self, filename: str) -> Path: """ Получает содержимое xml-файла. Если он не обработан, файл берётся из директории SOURCE_PATH, обрабатывается и сохраняется в директорию DOCUMENTS_PATH. Args: filename (str): Имя файла Returns: Path: Путь к файлу """ file_path = self.documents_path / filename if not file_path.exists(): source_file_path = self.source_path / filename logging.info(f"Process file: {source_file_path}") if (not source_file_path.exists()) or (not source_file_path.is_file()): logging.error(f"File not found: {source_file_path}") logging.error(f"Directory: {self.source_path} exists: {self.source_path.exists()}") raise HTTPException(status_code=404, detail="File not found") with open(source_file_path, "r", encoding="utf-8") as source_file: file_content = source_file.read() file_content = self._prettify_xml(file_content) file_path.parent.mkdir(parents=True, exist_ok=True) with open(file_path, "w", encoding="utf-8") as file: file.write(file_content) logging.info(f"File saved: {file_path}") return file_path def prepare_pdf(self, filename: str) -> Path: """ Получает содержимое docx-файла. """ prepared_file = self.prepare_file(filename) docx_path = prepared_file.with_suffix('.docx') pdf_path = prepared_file.with_suffix('.pdf') if not pdf_path.exists(): if self._convert_to_docx(prepared_file) != 0: raise HTTPException(status_code=400, detail="Failed to convert xml to docx") self._fix_style_table_docx(docx_path) if self._convert_to_pdf(docx_path) != 0: raise HTTPException(status_code=400, detail="Failed to convert docx to pdf") docx_path.unlink() return pdf_path @staticmethod def _prettify_xml(file_content: str) -> str: """ Удаляет спецсимволы из начала xml файла, чтобы документ смотрелся красиво. Args: file_content (str): Содержимое xml файла Returns: str: Содержимое xml файла без спецсимволов """ start = re.search(START_SPECIALS, file_content) end = re.search(END_SPECIALS, file_content) if start and end: return file_content[:start.start()] + file_content[end.end() + 1:] return file_content def _fix_style_table_docx(self, file_path: Path) -> None: """ Исправляет отображение таблиц и удаляет спецсимволы. Args: filename (str): Название docx файла. """ source_doc = Document(str(file_path)) output_doc = Document() for block in source_doc.element.body: if block.tag.endswith('p'): clear_text = self._remove_curly_braces_content(block.text).replace('w:r>', '') clear_text = clear_text.replace('См. документ в MS-Word', '') output_doc.add_paragraph(clear_text) elif block.tag.endswith('tbl'): old_table = parse_xml(block.xml) old_rows = old_table.findall(qn('w:tr')) len_old_rows = len(old_rows) arr_lens_cell_in_rows = [len(row.findall(qn('w:tc'))) for row in old_rows] table = output_doc.add_table(rows=len_old_rows, cols=max(arr_lens_cell_in_rows)) table.style = 'TableGrid' table.autofit = False for ind_old_row, old_row in enumerate(old_rows): old_cells = old_row.findall(qn('w:tc')) for ind_old_cell, old_cell in enumerate(old_cells): texts = old_cell.findall(f".//{qn('w:t')}") for text in texts: if '{' in text.text: continue else: try: table.rows[ind_old_row].cells[ind_old_cell].text = ( table.rows[ind_old_row].cells[ind_old_cell].text + text.text ) except IndexError: logging.warning('Ошибка в индексе, таблица не правильной формы') continue output_doc.save(str(file_path)) @staticmethod def _remove_curly_braces_content(text: str) -> str: """Удаляет все содержимое внутри фигурных скобок, включая сами скобки.""" return re.sub(r'\{[^{}]*\}', '', text) def _convert_to_pdf( self, file_path: Path, ) -> int: """ Конвертирует docx-файл в pdf. Returns: int: Код выхода. 0 - если конвертация прошла успешно. """ directory = str(file_path.parent) path = str(file_path) command = ['libreoffice', '--headless', '--convert-to', 'pdf', '--outdir', directory, path] running = subprocess.Popen(command) return running.wait() def _convert_to_docx( self, file_path: Path, ) -> int: """ Конвертирует xml-файл в docx. Returns: int: Код выхода. 0 - если конвертация прошла успешно. """ directory = str(file_path.parent) path = str(file_path) command = ['libreoffice', '--headless', '--convert-to', 'docx', '--outdir', directory, path] running = subprocess.Popen(command) return running.wait()