diff --git a/site/parser.py b/site/parser.py index f456564..f4892e2 100644 --- a/site/parser.py +++ b/site/parser.py @@ -123,21 +123,22 @@ def _parse_doc(file_bytes: bytes) -> dict: def _parse_pdf(file_bytes: bytes) -> dict: result = {"elements": [], "errors": []} - import tempfile, os as _os - # Сохранить PDF во временный файл (camelot требует путь) - tmp_path = None + # Попробовать libreoffice → docx (точные таблицы) try: - with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp: - tmp.write(file_bytes) - tmp_path = tmp.name + docx_result = _pdf_via_libreoffice(file_bytes) + if docx_result["elements"]: + return docx_result + result["errors"].extend(docx_result.get("errors", [])) + except Exception as e: + result["errors"].append(f"libreoffice pdf: {e}") + # Fallback: pdfplumber + try: import pdfplumber - with pdfplumber.open(io.BytesIO(file_bytes)) as pdf: for page in pdf.pages: pn = page.page_number - # Текст страницы text = page.extract_text() if text: for line in text.split("\n"): @@ -149,53 +150,55 @@ def _parse_pdf(file_bytes: bytes) -> dict: "text": line, "page": pn, }) - - # Таблицы — camelot (точнее), иначе pdfplumber - tables_found = False - try: - import camelot - # Сначала lattice (таблицы с рамками) - for flavor in ("lattice", "stream"): - try: - ctables = camelot.read_pdf(tmp_path, pages="all", flavor=flavor) - for ct in ctables: - rows = [list(ct.df.columns)] + ct.df.values.tolist() + tables = page.extract_tables() + for tbl in tables: + if tbl: result["elements"].append({ "type": "table", - "rows": rows, - "page": ct.page, + "rows": tbl, + "page": pn, }) - tables_found = True - except Exception: - pass - except ImportError: - result["errors"].append("camelot not installed, using pdfplumber") - except Exception as e: - result["errors"].append(f"camelot error: {e}") - - # Fallback: pdfplumber таблицы если camelot ничего не нашёл - if not tables_found: - with pdfplumber.open(io.BytesIO(file_bytes)) as pdf: - for page in pdf.pages: - pn = page.page_number - tables = page.extract_tables() - for tbl in tables: - if tbl: - result["elements"].append({ - "type": "table", - "rows": tbl, - "page": pn, - }) - except Exception as e: - result["errors"].append(f"pdf parse error: {e}") - finally: - if tmp_path and _os.path.exists(tmp_path): - _os.unlink(tmp_path) + result["errors"].append(f"pdfplumber: {e}") return result +def _pdf_via_libreoffice(file_bytes: bytes) -> dict: + """Конвертировать PDF → DOCX через libreoffice, затем python-docx.""" + result = {"elements": [], "errors": []} + tmp_pdf = None + tmp_dir = None + try: + with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp: + tmp.write(file_bytes) + tmp_pdf = tmp.name + + tmp_dir = tempfile.mkdtemp() + subprocess.run( + ["libreoffice", "--headless", "--convert-to", "docx", "--outdir", tmp_dir, tmp_pdf], + timeout=60, capture_output=True, + ) + docx_files = [f for f in os.listdir(tmp_dir) if f.endswith(".docx")] + if docx_files: + with open(os.path.join(tmp_dir, docx_files[0]), "rb") as f: + result = _parse_docx(f.read()) + else: + result["errors"].append("libreoffice PDF→DOCX: no output") + except FileNotFoundError: + result["errors"].append("libreoffice not installed") + except Exception as e: + result["errors"].append(f"libreoffice PDF→DOCX: {e}") + finally: + if tmp_pdf and os.path.exists(tmp_pdf): + os.unlink(tmp_pdf) + if tmp_dir and os.path.exists(tmp_dir): + for f in os.listdir(tmp_dir): + os.unlink(os.path.join(tmp_dir, f)) + os.rmdir(tmp_dir) + return result + + # ── ZIP ── def _parse_zip(file_bytes: bytes) -> dict: diff --git a/site/templates/upload.html b/site/templates/upload.html index 1281640..2552f4c 100644 --- a/site/templates/upload.html +++ b/site/templates/upload.html @@ -59,7 +59,7 @@