"""Parse service — PDF (pdfplumber), DOCX (python-docx), plain text fallback.""" import io def parse_file(filename, data): """Parse file bytes → {status, elements, element_count}.""" ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else "" try: if ext == "pdf": return _parse_pdf(data) elif ext == "docx": return _parse_docx(data) elif ext == "doc": return _parse_docx(data) # python-docx handles most .doc else: return _parse_text(data) except Exception as e: return {"status": "error", "error": str(e), "element_count": 0} def _parse_pdf(data): """Parse PDF with pdfplumber — preserves table structure (unlike PyPDF2).""" import pdfplumber elements = [] with pdfplumber.open(io.BytesIO(data)) as pdf: for page in pdf.pages: # Tables first — preserves column structure critical for specs tables = page.extract_tables() for table in tables: if table: rows = [] for row in table: if row: cells = [str(cell or "").strip() for cell in row] if any(cells): rows.append(cells) if rows: elements.append({"type": "table", "rows": rows}) # Remaining text as paragraphs text = page.extract_text() if text: for line in text.split("\n"): line = line.strip() if line: elements.append({"type": "paragraph", "text": line, "style": ""}) return {"status": "parsed", "element_count": len(elements), "elements": elements} def _parse_docx(data): import docx doc = docx.Document(io.BytesIO(data)) elements = [] for block in doc.element.body: tag = block.tag.split("}")[-1] if "}" in block.tag else block.tag if tag == "p": text = _extract_paragraph_text(block) if text: elements.append({"type": "paragraph", "text": text, "style": ""}) elif tag == "tbl": rows = [] for tr in block.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tr"): cells = [] for tc in tr.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tc"): cell_text = "".join(t.text or "" for t in tc.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}t")) cells.append(cell_text.strip()) if cells: rows.append(cells) if rows: elements.append({"type": "table", "rows": rows}) return {"status": "parsed", "element_count": len(elements), "elements": elements} def _extract_paragraph_text(p_elem): texts = [] for t in p_elem.findall(".//{http://schemas.openxmlformats.org/wordprocessingml/2006/main}t"): if t.text: texts.append(t.text) return "".join(texts).strip() def _parse_text(data): text = data.decode("utf-8", errors="ignore")[:5000] lines = [l.strip() for l in text.split("\n") if l.strip()] return {"status": "parsed", "element_count": len(lines), "elements": [{"type": "paragraph", "text": l, "style": ""} for l in lines]}