3
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
"""DOCX 解析器(python-docx fallback)。"""
|
||||
|
||||
from app.core.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def parse_docx_with_python_docx(file_content: bytes) -> str | None:
|
||||
"""用 python-docx 解析 DOCX,返回 Markdown 或 None(失败时)。"""
|
||||
try:
|
||||
import io
|
||||
|
||||
from docx import Document
|
||||
|
||||
doc = Document(io.BytesIO(file_content))
|
||||
md_parts = []
|
||||
|
||||
for para in doc.paragraphs:
|
||||
text = para.text.strip()
|
||||
if not text:
|
||||
continue
|
||||
|
||||
style = para.style.name.lower()
|
||||
if style.startswith("heading 1"):
|
||||
md_parts.append(f"# {text}")
|
||||
elif style.startswith("heading 2"):
|
||||
md_parts.append(f"## {text}")
|
||||
elif style.startswith("heading 3"):
|
||||
md_parts.append(f"### {text}")
|
||||
elif style.startswith("heading"):
|
||||
md_parts.append(f"#### {text}")
|
||||
elif style.startswith("list"):
|
||||
md_parts.append(f"- {text}")
|
||||
else:
|
||||
md_parts.append(text)
|
||||
|
||||
# 处理表格
|
||||
for table in doc.tables:
|
||||
rows = []
|
||||
for row in table.rows:
|
||||
cells = [cell.text.strip() for cell in row.cells]
|
||||
rows.append("| " + " | ".join(cells) + " |")
|
||||
if rows:
|
||||
# 添加表头分隔行
|
||||
if len(rows) > 1:
|
||||
sep = "| " + " | ".join(["---"] * len(table.columns)) + " |"
|
||||
rows.insert(1, sep)
|
||||
md_parts.append("\n".join(rows))
|
||||
|
||||
markdown = "\n\n".join(md_parts)
|
||||
return markdown if markdown.strip() else None
|
||||
except Exception as e:
|
||||
logger.warning("python-docx 解析失败: %s", e)
|
||||
return None
|
||||
@@ -0,0 +1,30 @@
|
||||
"""MarkItDown 解析器(优先)。"""
|
||||
|
||||
from app.core.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def parse_with_markitdown(file_content: bytes, filename: str) -> str | None:
|
||||
"""用 MarkItDown 解析文档,返回 Markdown 或 None(失败时)。"""
|
||||
try:
|
||||
from markitdown import MarkItDown
|
||||
|
||||
md = MarkItDown()
|
||||
# MarkItDown 需要文件路径或文件对象,用临时文件
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
suffix = Path(filename).suffix.lower()
|
||||
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
|
||||
tmp.write(file_content)
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
result = md.convert(tmp_path)
|
||||
return result.text_content if result and result.text_content else None
|
||||
finally:
|
||||
Path(tmp_path).unlink(missing_ok=True)
|
||||
except Exception as e:
|
||||
logger.warning("MarkItDown 解析失败 (%s): %s", filename, e)
|
||||
return None
|
||||
@@ -0,0 +1,47 @@
|
||||
"""PDF 解析器(PyMuPDF fallback)。"""
|
||||
|
||||
from app.core.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def parse_pdf_with_pymupdf(file_content: bytes) -> str | None:
|
||||
"""用 PyMuPDF 解析 PDF,返回 Markdown 或 None(失败时)。"""
|
||||
try:
|
||||
import pymupdf # PyMuPDF >= 1.24
|
||||
|
||||
doc = pymupdf.open(stream=file_content, filetype="pdf")
|
||||
pages = []
|
||||
for page_num in range(len(doc)):
|
||||
page = doc.load_page(page_num)
|
||||
text = page.get_text()
|
||||
if text.strip():
|
||||
pages.append(text)
|
||||
doc.close()
|
||||
|
||||
if not pages:
|
||||
return None
|
||||
|
||||
# 组合成 Markdown(每页一个段落)
|
||||
markdown = "\n\n".join(pages)
|
||||
return markdown
|
||||
except Exception as e:
|
||||
logger.warning("PyMuPDF 解析失败: %s", e)
|
||||
return None
|
||||
|
||||
|
||||
def has_text_layer(file_content: bytes) -> bool:
|
||||
"""检查 PDF 是否有文本层。"""
|
||||
try:
|
||||
import pymupdf
|
||||
|
||||
doc = pymupdf.open(stream=file_content, filetype="pdf")
|
||||
for page_num in range(min(3, len(doc))): # 检查前 3 页
|
||||
page = doc.load_page(page_num)
|
||||
if page.get_text().strip():
|
||||
doc.close()
|
||||
return True
|
||||
doc.close()
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
Reference in New Issue
Block a user