Spaces:
Sleeping
Sleeping
wang.lingxiao commited on
Commit ·
0151299
1
Parent(s): 221d13b
merge
Browse files- README.md +24 -39
- __pycache__/app.cpython-313.pyc +0 -0
- extractor.py +0 -242
- extractor_simple.py +0 -241
- requirements.txt +6 -7
- run_server.py +0 -91
- test_launch.py +0 -128
- test_sample.docx +0 -0
- test_sample.pdf +0 -68
- test_server.py +0 -111
README.md
CHANGED
|
@@ -13,66 +13,51 @@ tags:
|
|
| 13 |
- mcp-server-track
|
| 14 |
---
|
| 15 |
|
| 16 |
-
# Document Extraction Tool
|
| 17 |
|
| 18 |
-
|
| 19 |
|
| 20 |
## Features
|
| 21 |
|
| 22 |
-
-
|
| 23 |
-
-
|
| 24 |
-
-
|
| 25 |
-
-
|
| 26 |
-
-
|
| 27 |
-
-
|
| 28 |
-
- **Enhanced Error Handling** - Automatic fallback for problematic documents
|
| 29 |
-
- **Font Issue Resolution** - Special handling for PDFs with font parsing problems
|
| 30 |
-
- **MCP Integration** - Compatible with Claude Desktop via Model Context Protocol
|
| 31 |
|
| 32 |
-
##
|
| 33 |
|
| 34 |
```bash
|
| 35 |
# Install dependencies
|
| 36 |
pip install -r requirements.txt
|
| 37 |
|
| 38 |
-
# Run the application
|
| 39 |
python app.py
|
| 40 |
```
|
| 41 |
|
| 42 |
-
|
| 43 |
-
- Web Interface: `http://localhost:7860`
|
| 44 |
-
- MCP Server: `http://localhost:7860/gradio_api/mcp/sse`
|
| 45 |
|
| 46 |
-
|
| 47 |
-
## Usage Instructions
|
| 48 |
|
| 49 |
1. Upload a PDF or DOCX document
|
| 50 |
-
2. Click
|
| 51 |
3. View the extracted Markdown content
|
| 52 |
-
4. Download the
|
| 53 |
-
|
| 54 |
-
### Troubleshooting
|
| 55 |
-
|
| 56 |
-
For detailed troubleshooting information, see [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
| 57 |
-
|
| 58 |
-
**Common Issues:**
|
| 59 |
|
| 60 |
-
|
| 61 |
-
- **Document Corruption**: Ensure files are complete and not corrupted
|
| 62 |
-
- **Password Protection**: Remove password protection before uploading
|
| 63 |
-
- **Large Files**: Processing time increases with file size - please be patient
|
| 64 |
|
| 65 |
-
**
|
| 66 |
-
-
|
| 67 |
-
-
|
| 68 |
-
-
|
| 69 |
-
- User-friendly error messages with specific guidance
|
| 70 |
|
| 71 |
-
##
|
| 72 |
|
| 73 |
-
|
| 74 |
-
-
|
| 75 |
-
-
|
|
|
|
| 76 |
|
| 77 |
## License
|
| 78 |
|
|
|
|
| 13 |
- mcp-server-track
|
| 14 |
---
|
| 15 |
|
| 16 |
+
# Document Extraction Tool - Simplified Version
|
| 17 |
|
| 18 |
+
A streamlined tool that extracts text from PDF and DOCX files and converts it to Markdown format. The extractor is initialized at startup and ready to process documents on demand.
|
| 19 |
|
| 20 |
## Features
|
| 21 |
|
| 22 |
+
- **Fast startup** - Extractor pre-initialized and ready to use
|
| 23 |
+
- **PDF & DOCX support** - Extract text from common document formats
|
| 24 |
+
- **Markdown output** - Clean, structured markdown format
|
| 25 |
+
- **Enhanced extraction** - Uses Docling when available, PyPDF2 as fallback
|
| 26 |
+
- **Error handling** - Graceful handling of corrupted or problematic files
|
| 27 |
+
- **Simple interface** - Clean, easy-to-use web interface
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
+
## Quick Start
|
| 30 |
|
| 31 |
```bash
|
| 32 |
# Install dependencies
|
| 33 |
pip install -r requirements.txt
|
| 34 |
|
| 35 |
+
# Run the application
|
| 36 |
python app.py
|
| 37 |
```
|
| 38 |
|
| 39 |
+
Access the tool at: `http://localhost:7860`
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
## Usage
|
|
|
|
| 42 |
|
| 43 |
1. Upload a PDF or DOCX document
|
| 44 |
+
2. Click "Extract Text"
|
| 45 |
3. View the extracted Markdown content
|
| 46 |
+
4. Download the results if needed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
+
## Technology
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
+
- **Gradio** - Web interface
|
| 51 |
+
- **Docling** - Advanced document extraction (optional)
|
| 52 |
+
- **PyPDF2** - PDF processing fallback
|
| 53 |
+
- **python-docx** - DOCX processing
|
|
|
|
| 54 |
|
| 55 |
+
## Architecture
|
| 56 |
|
| 57 |
+
The tool uses a singleton pattern with a pre-initialized extractor:
|
| 58 |
+
- Faster response times (no initialization delay)
|
| 59 |
+
- Automatic fallback between extraction methods
|
| 60 |
+
- Enhanced error handling for edge cases
|
| 61 |
|
| 62 |
## License
|
| 63 |
|
__pycache__/app.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
|
|
|
extractor.py
DELETED
|
@@ -1,242 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
简化的文档提取器 - 专为Hugging Face Spaces优化
|
| 4 |
-
支持PDF和DOCX文件转换为Markdown格式
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import logging
|
| 8 |
-
import os
|
| 9 |
-
import io
|
| 10 |
-
import sys
|
| 11 |
-
from pathlib import Path
|
| 12 |
-
from typing import Union, Optional
|
| 13 |
-
|
| 14 |
-
# 配置日志
|
| 15 |
-
logging.basicConfig(level=logging.INFO)
|
| 16 |
-
logger = logging.getLogger(__name__)
|
| 17 |
-
|
| 18 |
-
# 尝试导入依赖库
|
| 19 |
-
try:
|
| 20 |
-
import PyPDF2
|
| 21 |
-
|
| 22 |
-
PDF_AVAILABLE = True
|
| 23 |
-
except ImportError:
|
| 24 |
-
PDF_AVAILABLE = False
|
| 25 |
-
logger.warning("PyPDF2 not available - PDF processing disabled")
|
| 26 |
-
|
| 27 |
-
try:
|
| 28 |
-
import docx
|
| 29 |
-
|
| 30 |
-
DOCX_AVAILABLE = True
|
| 31 |
-
except ImportError:
|
| 32 |
-
DOCX_AVAILABLE = False
|
| 33 |
-
logger.warning("python-docx not available - DOCX processing disabled")
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
class DocumentExtractor:
|
| 37 |
-
"""简化的文档提取器类"""
|
| 38 |
-
|
| 39 |
-
def __init__(self):
|
| 40 |
-
"""初始化提取器"""
|
| 41 |
-
self.supported_formats = []
|
| 42 |
-
|
| 43 |
-
if PDF_AVAILABLE:
|
| 44 |
-
self.supported_formats.append(".pdf")
|
| 45 |
-
if DOCX_AVAILABLE:
|
| 46 |
-
self.supported_formats.append(".docx")
|
| 47 |
-
|
| 48 |
-
if not self.supported_formats:
|
| 49 |
-
raise RuntimeError("没有可用的文档处理库。请安装PyPDF2和python-docx。")
|
| 50 |
-
|
| 51 |
-
logger.info(f"文档提取器初始化完成,支持格式: {self.supported_formats}")
|
| 52 |
-
|
| 53 |
-
def validate_document(self, file_path: Union[str, Path]) -> bool:
|
| 54 |
-
"""
|
| 55 |
-
验证文档是否可以处理
|
| 56 |
-
|
| 57 |
-
Args:
|
| 58 |
-
file_path: 文件路径
|
| 59 |
-
|
| 60 |
-
Returns:
|
| 61 |
-
bool: 如果文档有效返回True
|
| 62 |
-
"""
|
| 63 |
-
try:
|
| 64 |
-
path = Path(file_path)
|
| 65 |
-
|
| 66 |
-
# 检查文件是否存在
|
| 67 |
-
if not path.exists():
|
| 68 |
-
logger.error(f"文件不存在: {file_path}")
|
| 69 |
-
return False
|
| 70 |
-
|
| 71 |
-
# 检查文件扩展名
|
| 72 |
-
file_ext = path.suffix.lower()
|
| 73 |
-
if file_ext not in self.supported_formats:
|
| 74 |
-
logger.error(f"不支持的文件格式: {file_ext}")
|
| 75 |
-
return False
|
| 76 |
-
|
| 77 |
-
# 检查文件大小
|
| 78 |
-
file_size = path.stat().st_size
|
| 79 |
-
if file_size == 0:
|
| 80 |
-
logger.error("文件为空")
|
| 81 |
-
return False
|
| 82 |
-
|
| 83 |
-
if file_size > 100 * 1024 * 1024: # 100MB限制
|
| 84 |
-
logger.warning(
|
| 85 |
-
f"文件较大 ({file_size / 1024 / 1024:.2f} MB),处理可能需要较长时间"
|
| 86 |
-
)
|
| 87 |
-
|
| 88 |
-
return True
|
| 89 |
-
|
| 90 |
-
except Exception as e:
|
| 91 |
-
logger.error(f"文件验证失败: {str(e)}")
|
| 92 |
-
return False
|
| 93 |
-
|
| 94 |
-
def extract_pdf_text(self, file_path: Union[str, Path]) -> str:
|
| 95 |
-
"""
|
| 96 |
-
从PDF文件提取文本
|
| 97 |
-
|
| 98 |
-
Args:
|
| 99 |
-
file_path: PDF文件路径
|
| 100 |
-
|
| 101 |
-
Returns:
|
| 102 |
-
str: 提取的文本内容
|
| 103 |
-
"""
|
| 104 |
-
if not PDF_AVAILABLE:
|
| 105 |
-
raise RuntimeError("PDF处理不可用 - 请安装PyPDF2")
|
| 106 |
-
|
| 107 |
-
try:
|
| 108 |
-
with open(file_path, "rb") as file:
|
| 109 |
-
pdf_reader = PyPDF2.PdfReader(file)
|
| 110 |
-
text_content = []
|
| 111 |
-
|
| 112 |
-
for page_num, page in enumerate(pdf_reader.pages):
|
| 113 |
-
try:
|
| 114 |
-
page_text = page.extract_text()
|
| 115 |
-
if page_text.strip():
|
| 116 |
-
text_content.append(
|
| 117 |
-
f"## 第 {page_num + 1} 页\n\n{page_text}\n"
|
| 118 |
-
)
|
| 119 |
-
except Exception as e:
|
| 120 |
-
logger.warning(f"无法提取第 {page_num + 1} 页内容: {str(e)}")
|
| 121 |
-
continue
|
| 122 |
-
|
| 123 |
-
if not text_content:
|
| 124 |
-
return "# PDF文档\n\n无法从此PDF文件中提取文本内容。"
|
| 125 |
-
|
| 126 |
-
return "# PDF文档\n\n" + "\n".join(text_content)
|
| 127 |
-
|
| 128 |
-
except Exception as e:
|
| 129 |
-
logger.error(f"PDF提取失败: {str(e)}")
|
| 130 |
-
return f"# PDF提取错误\n\n提取过程中出现错误: {str(e)}"
|
| 131 |
-
|
| 132 |
-
def extract_docx_text(self, file_path: Union[str, Path]) -> str:
|
| 133 |
-
"""
|
| 134 |
-
从DOCX文件提取文本
|
| 135 |
-
|
| 136 |
-
Args:
|
| 137 |
-
file_path: DOCX文件路径
|
| 138 |
-
|
| 139 |
-
Returns:
|
| 140 |
-
str: 提取的文本内容
|
| 141 |
-
"""
|
| 142 |
-
if not DOCX_AVAILABLE:
|
| 143 |
-
raise RuntimeError("DOCX处理不可用 - 请安装python-docx")
|
| 144 |
-
|
| 145 |
-
try:
|
| 146 |
-
doc = docx.Document(file_path)
|
| 147 |
-
text_content = []
|
| 148 |
-
|
| 149 |
-
for paragraph in doc.paragraphs:
|
| 150 |
-
if paragraph.text.strip():
|
| 151 |
-
# 简单的格式检测
|
| 152 |
-
text = paragraph.text.strip()
|
| 153 |
-
|
| 154 |
-
# 检测标题(简单规则)
|
| 155 |
-
if (
|
| 156 |
-
len(text) < 100
|
| 157 |
-
and not text.endswith(".")
|
| 158 |
-
and not text.endswith("。")
|
| 159 |
-
):
|
| 160 |
-
text_content.append(f"## {text}\n")
|
| 161 |
-
else:
|
| 162 |
-
text_content.append(f"{text}\n")
|
| 163 |
-
|
| 164 |
-
# 处理表格
|
| 165 |
-
for table in doc.tables:
|
| 166 |
-
text_content.append("\n### 表格\n")
|
| 167 |
-
for row in table.rows:
|
| 168 |
-
row_text = " | ".join([cell.text.strip() for cell in row.cells])
|
| 169 |
-
if row_text.strip():
|
| 170 |
-
text_content.append(f"| {row_text} |")
|
| 171 |
-
text_content.append("\n")
|
| 172 |
-
|
| 173 |
-
if not text_content:
|
| 174 |
-
return "# Word文档\n\n无法从此DOCX文件中提取文本内容。"
|
| 175 |
-
|
| 176 |
-
return "# Word文档\n\n" + "\n".join(text_content)
|
| 177 |
-
|
| 178 |
-
except Exception as e:
|
| 179 |
-
logger.error(f"DOCX提取失败: {str(e)}")
|
| 180 |
-
return f"# DOCX提取错误\n\n提取过程中出现错误: {str(e)}"
|
| 181 |
-
|
| 182 |
-
def extract(self, file_path: Union[str, Path]) -> str:
|
| 183 |
-
"""
|
| 184 |
-
提取文档内容并转换为Markdown格式
|
| 185 |
-
|
| 186 |
-
Args:
|
| 187 |
-
file_path: 文档文件路径
|
| 188 |
-
|
| 189 |
-
Returns:
|
| 190 |
-
str: Markdown格式的文档内容
|
| 191 |
-
"""
|
| 192 |
-
logger.info(f"开始提取文档: {file_path}")
|
| 193 |
-
|
| 194 |
-
# 验证文档
|
| 195 |
-
if not self.validate_document(file_path):
|
| 196 |
-
raise ValueError(f"文档验证失败: {file_path}")
|
| 197 |
-
|
| 198 |
-
# 确定文件类型并提取
|
| 199 |
-
file_ext = Path(file_path).suffix.lower()
|
| 200 |
-
|
| 201 |
-
try:
|
| 202 |
-
if file_ext == ".pdf":
|
| 203 |
-
content = self.extract_pdf_text(file_path)
|
| 204 |
-
elif file_ext == ".docx":
|
| 205 |
-
content = self.extract_docx_text(file_path)
|
| 206 |
-
else:
|
| 207 |
-
raise ValueError(f"不支持的文件格式: {file_ext}")
|
| 208 |
-
|
| 209 |
-
logger.info(f"文档提取完成,内容长度: {len(content)} 字符")
|
| 210 |
-
return content
|
| 211 |
-
|
| 212 |
-
except Exception as e:
|
| 213 |
-
logger.error(f"文档提取失败: {str(e)}")
|
| 214 |
-
raise RuntimeError(f"文档提取失败: {str(e)}")
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
# 为了兼容性创建别名
|
| 218 |
-
Extractor = DocumentExtractor
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
def main():
|
| 222 |
-
"""命令行测试函数"""
|
| 223 |
-
import sys
|
| 224 |
-
|
| 225 |
-
if len(sys.argv) != 2:
|
| 226 |
-
print("使用方法: python extractor.py <文档文件路径>")
|
| 227 |
-
return 1
|
| 228 |
-
|
| 229 |
-
file_path = sys.argv[1]
|
| 230 |
-
|
| 231 |
-
try:
|
| 232 |
-
extractor = DocumentExtractor()
|
| 233 |
-
content = extractor.extract(file_path)
|
| 234 |
-
print(content)
|
| 235 |
-
return 0
|
| 236 |
-
except Exception as e:
|
| 237 |
-
print(f"错误: {str(e)}")
|
| 238 |
-
return 1
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
if __name__ == "__main__":
|
| 242 |
-
sys.exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
extractor_simple.py
DELETED
|
@@ -1,241 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
简化的文档提取器 - 专为Hugging Face Spaces优化
|
| 4 |
-
支持PDF和DOCX文件转换为Markdown格式
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import logging
|
| 8 |
-
import os
|
| 9 |
-
import io
|
| 10 |
-
from pathlib import Path
|
| 11 |
-
from typing import Union, Optional
|
| 12 |
-
|
| 13 |
-
# 配置日志
|
| 14 |
-
logging.basicConfig(level=logging.INFO)
|
| 15 |
-
logger = logging.getLogger(__name__)
|
| 16 |
-
|
| 17 |
-
# 尝试导入依赖库
|
| 18 |
-
try:
|
| 19 |
-
import PyPDF2
|
| 20 |
-
|
| 21 |
-
PDF_AVAILABLE = True
|
| 22 |
-
except ImportError:
|
| 23 |
-
PDF_AVAILABLE = False
|
| 24 |
-
logger.warning("PyPDF2 not available - PDF processing disabled")
|
| 25 |
-
|
| 26 |
-
try:
|
| 27 |
-
import docx
|
| 28 |
-
|
| 29 |
-
DOCX_AVAILABLE = True
|
| 30 |
-
except ImportError:
|
| 31 |
-
DOCX_AVAILABLE = False
|
| 32 |
-
logger.warning("python-docx not available - DOCX processing disabled")
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
class DocumentExtractor:
|
| 36 |
-
"""简化的文档提取器类"""
|
| 37 |
-
|
| 38 |
-
def __init__(self):
|
| 39 |
-
"""初始化提取器"""
|
| 40 |
-
self.supported_formats = []
|
| 41 |
-
|
| 42 |
-
if PDF_AVAILABLE:
|
| 43 |
-
self.supported_formats.append(".pdf")
|
| 44 |
-
if DOCX_AVAILABLE:
|
| 45 |
-
self.supported_formats.append(".docx")
|
| 46 |
-
|
| 47 |
-
if not self.supported_formats:
|
| 48 |
-
raise RuntimeError("没有可用的文档处理库。请安装PyPDF2和python-docx。")
|
| 49 |
-
|
| 50 |
-
logger.info(f"文档提取器初始化完成,支持格式: {self.supported_formats}")
|
| 51 |
-
|
| 52 |
-
def validate_document(self, file_path: Union[str, Path]) -> bool:
|
| 53 |
-
"""
|
| 54 |
-
验证文档是否可以处理
|
| 55 |
-
|
| 56 |
-
Args:
|
| 57 |
-
file_path: 文件路径
|
| 58 |
-
|
| 59 |
-
Returns:
|
| 60 |
-
bool: 如果文档有效返回True
|
| 61 |
-
"""
|
| 62 |
-
try:
|
| 63 |
-
path = Path(file_path)
|
| 64 |
-
|
| 65 |
-
# 检查文件是否存在
|
| 66 |
-
if not path.exists():
|
| 67 |
-
logger.error(f"文件不存在: {file_path}")
|
| 68 |
-
return False
|
| 69 |
-
|
| 70 |
-
# 检查文件扩展名
|
| 71 |
-
file_ext = path.suffix.lower()
|
| 72 |
-
if file_ext not in self.supported_formats:
|
| 73 |
-
logger.error(f"不支持的文件格式: {file_ext}")
|
| 74 |
-
return False
|
| 75 |
-
|
| 76 |
-
# 检查文件大小
|
| 77 |
-
file_size = path.stat().st_size
|
| 78 |
-
if file_size == 0:
|
| 79 |
-
logger.error("文件为空")
|
| 80 |
-
return False
|
| 81 |
-
|
| 82 |
-
if file_size > 100 * 1024 * 1024: # 100MB限制
|
| 83 |
-
logger.warning(
|
| 84 |
-
f"文件较大 ({file_size / 1024 / 1024:.2f} MB),处理可能需要较长时间"
|
| 85 |
-
)
|
| 86 |
-
|
| 87 |
-
return True
|
| 88 |
-
|
| 89 |
-
except Exception as e:
|
| 90 |
-
logger.error(f"文件验证失败: {str(e)}")
|
| 91 |
-
return False
|
| 92 |
-
|
| 93 |
-
def extract_pdf_text(self, file_path: Union[str, Path]) -> str:
|
| 94 |
-
"""
|
| 95 |
-
从PDF文件提取文本
|
| 96 |
-
|
| 97 |
-
Args:
|
| 98 |
-
file_path: PDF文件路径
|
| 99 |
-
|
| 100 |
-
Returns:
|
| 101 |
-
str: 提取的文本内容
|
| 102 |
-
"""
|
| 103 |
-
if not PDF_AVAILABLE:
|
| 104 |
-
raise RuntimeError("PDF处理不可用 - 请安装PyPDF2")
|
| 105 |
-
|
| 106 |
-
try:
|
| 107 |
-
with open(file_path, "rb") as file:
|
| 108 |
-
pdf_reader = PyPDF2.PdfReader(file)
|
| 109 |
-
text_content = []
|
| 110 |
-
|
| 111 |
-
for page_num, page in enumerate(pdf_reader.pages):
|
| 112 |
-
try:
|
| 113 |
-
page_text = page.extract_text()
|
| 114 |
-
if page_text.strip():
|
| 115 |
-
text_content.append(
|
| 116 |
-
f"## 第 {page_num + 1} 页\n\n{page_text}\n"
|
| 117 |
-
)
|
| 118 |
-
except Exception as e:
|
| 119 |
-
logger.warning(f"无法提取第 {page_num + 1} 页内容: {str(e)}")
|
| 120 |
-
continue
|
| 121 |
-
|
| 122 |
-
if not text_content:
|
| 123 |
-
return "# PDF文档\n\n无法从此PDF文件中提取文本内容。"
|
| 124 |
-
|
| 125 |
-
return "# PDF文档\n\n" + "\n".join(text_content)
|
| 126 |
-
|
| 127 |
-
except Exception as e:
|
| 128 |
-
logger.error(f"PDF提取失败: {str(e)}")
|
| 129 |
-
return f"# PDF提取错误\n\n提取过程中出现错误: {str(e)}"
|
| 130 |
-
|
| 131 |
-
def extract_docx_text(self, file_path: Union[str, Path]) -> str:
|
| 132 |
-
"""
|
| 133 |
-
从DOCX文件提取文本
|
| 134 |
-
|
| 135 |
-
Args:
|
| 136 |
-
file_path: DOCX文件路径
|
| 137 |
-
|
| 138 |
-
Returns:
|
| 139 |
-
str: 提取的文本内容
|
| 140 |
-
"""
|
| 141 |
-
if not DOCX_AVAILABLE:
|
| 142 |
-
raise RuntimeError("DOCX处理不可用 - 请安装python-docx")
|
| 143 |
-
|
| 144 |
-
try:
|
| 145 |
-
doc = docx.Document(file_path)
|
| 146 |
-
text_content = []
|
| 147 |
-
|
| 148 |
-
for paragraph in doc.paragraphs:
|
| 149 |
-
if paragraph.text.strip():
|
| 150 |
-
# 简单的格式检测
|
| 151 |
-
text = paragraph.text.strip()
|
| 152 |
-
|
| 153 |
-
# 检测标题(简单规则)
|
| 154 |
-
if (
|
| 155 |
-
len(text) < 100
|
| 156 |
-
and not text.endswith(".")
|
| 157 |
-
and not text.endswith("。")
|
| 158 |
-
):
|
| 159 |
-
text_content.append(f"## {text}\n")
|
| 160 |
-
else:
|
| 161 |
-
text_content.append(f"{text}\n")
|
| 162 |
-
|
| 163 |
-
# 处理表格
|
| 164 |
-
for table in doc.tables:
|
| 165 |
-
text_content.append("\n### 表格\n")
|
| 166 |
-
for row in table.rows:
|
| 167 |
-
row_text = " | ".join([cell.text.strip() for cell in row.cells])
|
| 168 |
-
if row_text.strip():
|
| 169 |
-
text_content.append(f"| {row_text} |")
|
| 170 |
-
text_content.append("\n")
|
| 171 |
-
|
| 172 |
-
if not text_content:
|
| 173 |
-
return "# Word文档\n\n无法从此DOCX文件中提取文本内容。"
|
| 174 |
-
|
| 175 |
-
return "# Word文档\n\n" + "\n".join(text_content)
|
| 176 |
-
|
| 177 |
-
except Exception as e:
|
| 178 |
-
logger.error(f"DOCX提取失败: {str(e)}")
|
| 179 |
-
return f"# DOCX提取错误\n\n提取过程中出现错误: {str(e)}"
|
| 180 |
-
|
| 181 |
-
def extract(self, file_path: Union[str, Path]) -> str:
|
| 182 |
-
"""
|
| 183 |
-
提取文档内容并转换为Markdown格式
|
| 184 |
-
|
| 185 |
-
Args:
|
| 186 |
-
file_path: 文档文件路径
|
| 187 |
-
|
| 188 |
-
Returns:
|
| 189 |
-
str: Markdown格式的文档内容
|
| 190 |
-
"""
|
| 191 |
-
logger.info(f"开始提取文档: {file_path}")
|
| 192 |
-
|
| 193 |
-
# 验证文档
|
| 194 |
-
if not self.validate_document(file_path):
|
| 195 |
-
raise ValueError(f"文档验证失败: {file_path}")
|
| 196 |
-
|
| 197 |
-
# 确定文件类型并提取
|
| 198 |
-
file_ext = Path(file_path).suffix.lower()
|
| 199 |
-
|
| 200 |
-
try:
|
| 201 |
-
if file_ext == ".pdf":
|
| 202 |
-
content = self.extract_pdf_text(file_path)
|
| 203 |
-
elif file_ext == ".docx":
|
| 204 |
-
content = self.extract_docx_text(file_path)
|
| 205 |
-
else:
|
| 206 |
-
raise ValueError(f"不支持的文件格式: {file_ext}")
|
| 207 |
-
|
| 208 |
-
logger.info(f"文档提取完成,内容长度: {len(content)} 字符")
|
| 209 |
-
return content
|
| 210 |
-
|
| 211 |
-
except Exception as e:
|
| 212 |
-
logger.error(f"文档提取失败: {str(e)}")
|
| 213 |
-
raise RuntimeError(f"文档提取失败: {str(e)}")
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
# 为了兼容性创建别名
|
| 217 |
-
Extractor = DocumentExtractor
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
def main():
|
| 221 |
-
"""命令行测试函数"""
|
| 222 |
-
import sys
|
| 223 |
-
|
| 224 |
-
if len(sys.argv) != 2:
|
| 225 |
-
print("使用方法: python extractor.py <文档文件路径>")
|
| 226 |
-
return 1
|
| 227 |
-
|
| 228 |
-
file_path = sys.argv[1]
|
| 229 |
-
|
| 230 |
-
try:
|
| 231 |
-
extractor = DocumentExtractor()
|
| 232 |
-
content = extractor.extract(file_path)
|
| 233 |
-
print(content)
|
| 234 |
-
return 0
|
| 235 |
-
except Exception as e:
|
| 236 |
-
print(f"错误: {str(e)}")
|
| 237 |
-
return 1
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
if __name__ == "__main__":
|
| 241 |
-
sys.exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
|
|
| 1 |
gradio>=4.0.0
|
| 2 |
PyPDF2>=3.0.0
|
| 3 |
python-docx>=1.0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
reportlab>=4.0.0
|
| 5 |
Pillow>=9.0.0
|
| 6 |
lxml>=4.9.0
|
| 7 |
-
docling
|
| 8 |
-
|
| 9 |
-
# Optional advanced PDF processing library (may fail on some systems)
|
| 10 |
-
# If docling fails to install, the app will fallback to PyPDF2
|
| 11 |
-
|
| 12 |
-
# MCP support (only needed for local environment, not for Hugging Face Spaces)
|
| 13 |
-
# fastmcp>=0.1.0
|
|
|
|
| 1 |
+
# Core dependencies
|
| 2 |
gradio>=4.0.0
|
| 3 |
PyPDF2>=3.0.0
|
| 4 |
python-docx>=1.0.0
|
| 5 |
+
|
| 6 |
+
# Optional enhanced PDF processing (fallback to PyPDF2 if not available)
|
| 7 |
+
docling
|
| 8 |
+
|
| 9 |
+
# Additional dependencies for document processing
|
| 10 |
reportlab>=4.0.0
|
| 11 |
Pillow>=9.0.0
|
| 12 |
lxml>=4.9.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
run_server.py
DELETED
|
@@ -1,91 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Server startup script for Document Extraction Tool
|
| 4 |
-
Optimized for server deployment with better error handling
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import sys
|
| 8 |
-
import os
|
| 9 |
-
import logging
|
| 10 |
-
|
| 11 |
-
# Configure logging for server environment
|
| 12 |
-
logging.basicConfig(
|
| 13 |
-
level=logging.INFO,
|
| 14 |
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
| 15 |
-
handlers=[
|
| 16 |
-
logging.StreamHandler(sys.stdout),
|
| 17 |
-
logging.FileHandler("app.log")
|
| 18 |
-
if os.access(".", os.W_OK)
|
| 19 |
-
else logging.StreamHandler(sys.stdout),
|
| 20 |
-
],
|
| 21 |
-
)
|
| 22 |
-
|
| 23 |
-
logger = logging.getLogger(__name__)
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
def check_dependencies():
|
| 27 |
-
"""Check if all required dependencies are available"""
|
| 28 |
-
missing_deps = []
|
| 29 |
-
|
| 30 |
-
try:
|
| 31 |
-
import gradio
|
| 32 |
-
|
| 33 |
-
logger.info(f"Gradio version: {gradio.__version__}")
|
| 34 |
-
except ImportError:
|
| 35 |
-
missing_deps.append("gradio")
|
| 36 |
-
|
| 37 |
-
try:
|
| 38 |
-
import PyPDF2
|
| 39 |
-
|
| 40 |
-
logger.info("PyPDF2 available")
|
| 41 |
-
except ImportError:
|
| 42 |
-
missing_deps.append("PyPDF2")
|
| 43 |
-
|
| 44 |
-
try:
|
| 45 |
-
import docx
|
| 46 |
-
|
| 47 |
-
logger.info("python-docx available")
|
| 48 |
-
except ImportError:
|
| 49 |
-
missing_deps.append("python-docx")
|
| 50 |
-
|
| 51 |
-
try:
|
| 52 |
-
from docling.document_converter import DocumentConverter
|
| 53 |
-
|
| 54 |
-
logger.info("Docling available - will use advanced PDF extraction")
|
| 55 |
-
except ImportError as e:
|
| 56 |
-
logger.warning(f"Docling not available - will use PyPDF2 fallback: {e}")
|
| 57 |
-
|
| 58 |
-
if missing_deps:
|
| 59 |
-
logger.error(f"Missing required dependencies: {missing_deps}")
|
| 60 |
-
logger.error(
|
| 61 |
-
"Please install missing dependencies with: pip install "
|
| 62 |
-
+ " ".join(missing_deps)
|
| 63 |
-
)
|
| 64 |
-
return False
|
| 65 |
-
|
| 66 |
-
return True
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
def main():
|
| 70 |
-
"""Main startup function"""
|
| 71 |
-
logger.info("=" * 50)
|
| 72 |
-
logger.info("Starting Document Extraction Tool Server")
|
| 73 |
-
logger.info("=" * 50)
|
| 74 |
-
|
| 75 |
-
# Check dependencies
|
| 76 |
-
if not check_dependencies():
|
| 77 |
-
sys.exit(1)
|
| 78 |
-
|
| 79 |
-
# Import and run the main app
|
| 80 |
-
try:
|
| 81 |
-
from app import main as app_main
|
| 82 |
-
|
| 83 |
-
logger.info("Starting application...")
|
| 84 |
-
app_main()
|
| 85 |
-
except Exception as e:
|
| 86 |
-
logger.error(f"Failed to start application: {e}")
|
| 87 |
-
raise
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
if __name__ == "__main__":
|
| 91 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_launch.py
DELETED
|
@@ -1,128 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Quick launch test for the Document Extraction Tool
|
| 4 |
-
Tests if the application can start without errors
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import sys
|
| 8 |
-
import os
|
| 9 |
-
import time
|
| 10 |
-
import threading
|
| 11 |
-
import requests
|
| 12 |
-
from pathlib import Path
|
| 13 |
-
|
| 14 |
-
# Add current directory to path
|
| 15 |
-
sys.path.insert(0, str(Path(__file__).parent))
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
def test_launch():
|
| 19 |
-
"""Test if the application launches successfully"""
|
| 20 |
-
print("🧪 Testing application launch...")
|
| 21 |
-
|
| 22 |
-
try:
|
| 23 |
-
# Import the app
|
| 24 |
-
from app import create_interface
|
| 25 |
-
|
| 26 |
-
print("✅ App import successful")
|
| 27 |
-
|
| 28 |
-
# Create interface
|
| 29 |
-
app = create_interface()
|
| 30 |
-
print("✅ Interface creation successful")
|
| 31 |
-
|
| 32 |
-
# Test launch with minimal config
|
| 33 |
-
def launch_test():
|
| 34 |
-
try:
|
| 35 |
-
app.launch(
|
| 36 |
-
server_name="127.0.0.1",
|
| 37 |
-
server_port=7861, # Different port to avoid conflicts
|
| 38 |
-
share=False,
|
| 39 |
-
prevent_thread_lock=True,
|
| 40 |
-
quiet=True,
|
| 41 |
-
)
|
| 42 |
-
except Exception as e:
|
| 43 |
-
print(f"❌ Launch failed: {e}")
|
| 44 |
-
return False
|
| 45 |
-
return True
|
| 46 |
-
|
| 47 |
-
# Run in separate thread
|
| 48 |
-
launch_thread = threading.Thread(target=launch_test)
|
| 49 |
-
launch_thread.daemon = True
|
| 50 |
-
launch_thread.start()
|
| 51 |
-
|
| 52 |
-
# Wait a bit for server to start
|
| 53 |
-
time.sleep(3)
|
| 54 |
-
|
| 55 |
-
# Test if server is responding
|
| 56 |
-
try:
|
| 57 |
-
response = requests.get("http://127.0.0.1:7861", timeout=5)
|
| 58 |
-
if response.status_code == 200:
|
| 59 |
-
print("✅ Server is responding successfully")
|
| 60 |
-
print("🎉 Launch test PASSED")
|
| 61 |
-
return True
|
| 62 |
-
else:
|
| 63 |
-
print(f"⚠️ Server returned status {response.status_code}")
|
| 64 |
-
except requests.RequestException as e:
|
| 65 |
-
print(f"⚠️ Server connection test failed: {e}")
|
| 66 |
-
print(" (This might be normal if using prevent_thread_lock)")
|
| 67 |
-
|
| 68 |
-
print("✅ Basic launch test completed without errors")
|
| 69 |
-
return True
|
| 70 |
-
|
| 71 |
-
except Exception as e:
|
| 72 |
-
print(f"❌ Launch test failed: {e}")
|
| 73 |
-
return False
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
def test_imports():
|
| 77 |
-
"""Test critical imports"""
|
| 78 |
-
print("📦 Testing imports...")
|
| 79 |
-
|
| 80 |
-
try:
|
| 81 |
-
import gradio
|
| 82 |
-
|
| 83 |
-
print(f"✅ Gradio {gradio.__version__}")
|
| 84 |
-
|
| 85 |
-
try:
|
| 86 |
-
from docling.document_converter import DocumentConverter
|
| 87 |
-
|
| 88 |
-
print("✅ Docling available")
|
| 89 |
-
except ImportError:
|
| 90 |
-
print("⚠️ Docling not available (fallback mode)")
|
| 91 |
-
|
| 92 |
-
import PyPDF2
|
| 93 |
-
|
| 94 |
-
print("✅ PyPDF2 available")
|
| 95 |
-
|
| 96 |
-
import docx
|
| 97 |
-
|
| 98 |
-
print("✅ python-docx available")
|
| 99 |
-
|
| 100 |
-
return True
|
| 101 |
-
|
| 102 |
-
except ImportError as e:
|
| 103 |
-
print(f"❌ Import failed: {e}")
|
| 104 |
-
return False
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
def main():
|
| 108 |
-
"""Run all tests"""
|
| 109 |
-
print("🔍 Document Extraction Tool - Launch Test")
|
| 110 |
-
print("=" * 50)
|
| 111 |
-
|
| 112 |
-
# Test imports first
|
| 113 |
-
if not test_imports():
|
| 114 |
-
return 1
|
| 115 |
-
|
| 116 |
-
print()
|
| 117 |
-
|
| 118 |
-
# Test launch
|
| 119 |
-
if not test_launch():
|
| 120 |
-
return 1
|
| 121 |
-
|
| 122 |
-
print("\n" + "=" * 50)
|
| 123 |
-
print("✅ All tests passed! Application should work correctly.")
|
| 124 |
-
return 0
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
if __name__ == "__main__":
|
| 128 |
-
sys.exit(main())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_sample.docx
DELETED
|
Binary file (36.8 kB)
|
|
|
test_sample.pdf
DELETED
|
@@ -1,68 +0,0 @@
|
|
| 1 |
-
%PDF-1.3
|
| 2 |
-
%���� ReportLab Generated PDF document http://www.reportlab.com
|
| 3 |
-
1 0 obj
|
| 4 |
-
<<
|
| 5 |
-
/F1 2 0 R
|
| 6 |
-
>>
|
| 7 |
-
endobj
|
| 8 |
-
2 0 obj
|
| 9 |
-
<<
|
| 10 |
-
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
| 11 |
-
>>
|
| 12 |
-
endobj
|
| 13 |
-
3 0 obj
|
| 14 |
-
<<
|
| 15 |
-
/Contents 7 0 R /MediaBox [ 0 0 612 792 ] /Parent 6 0 R /Resources <<
|
| 16 |
-
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 17 |
-
>> /Rotate 0 /Trans <<
|
| 18 |
-
|
| 19 |
-
>>
|
| 20 |
-
/Type /Page
|
| 21 |
-
>>
|
| 22 |
-
endobj
|
| 23 |
-
4 0 obj
|
| 24 |
-
<<
|
| 25 |
-
/PageMode /UseNone /Pages 6 0 R /Type /Catalog
|
| 26 |
-
>>
|
| 27 |
-
endobj
|
| 28 |
-
5 0 obj
|
| 29 |
-
<<
|
| 30 |
-
/Author (anonymous) /CreationDate (D:20250604161223+02'00') /Creator (ReportLab PDF Library - www.reportlab.com) /Keywords () /ModDate (D:20250604161223+02'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
| 31 |
-
/Subject (unspecified) /Title (untitled) /Trapped /False
|
| 32 |
-
>>
|
| 33 |
-
endobj
|
| 34 |
-
6 0 obj
|
| 35 |
-
<<
|
| 36 |
-
/Count 1 /Kids [ 3 0 R ] /Type /Pages
|
| 37 |
-
>>
|
| 38 |
-
endobj
|
| 39 |
-
7 0 obj
|
| 40 |
-
<<
|
| 41 |
-
/Filter [ /ASCII85Decode /FlateDecode ] /Length 350
|
| 42 |
-
>>
|
| 43 |
-
stream
|
| 44 |
-
GasbU;+ne\'SYH=/'amseu!4Ah4o.IJYHNnHjsr4W_5+1>M[Ua9ff2V'ko)'j?#BZ%7BD&(>cG#mQ#/7#*shD_=AiVJ&ILGr-pV@ASI$IOb,)gPc!l5e:cqTXJi*U)Vg]3\9)g!rXOC"$I9pA2m%^>!p'+9V6&#PM91$hFr0BXTf/?-Wu[^-&l,C]<r/m\@;RRV:T:V+=\O1-Ys)B"V9,1'$f>S^0//kbn/=TFHL4o"_l6!4CK0ca7WQpE:)*PhQ@.IW#.o>oAFLl27q98Tn"mtDBT)[nSSWjL$L4]4WDMXf@S+(^d/5l9&WT:492If:]_rqrD^.59Nb5/`?2*(^PAdcg[%m~>endstream
|
| 45 |
-
endobj
|
| 46 |
-
xref
|
| 47 |
-
0 8
|
| 48 |
-
0000000000 65535 f
|
| 49 |
-
0000000073 00000 n
|
| 50 |
-
0000000104 00000 n
|
| 51 |
-
0000000211 00000 n
|
| 52 |
-
0000000404 00000 n
|
| 53 |
-
0000000472 00000 n
|
| 54 |
-
0000000768 00000 n
|
| 55 |
-
0000000827 00000 n
|
| 56 |
-
trailer
|
| 57 |
-
<<
|
| 58 |
-
/ID
|
| 59 |
-
[<b95b80aec21480de24245e72127d8d0a><b95b80aec21480de24245e72127d8d0a>]
|
| 60 |
-
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
| 61 |
-
|
| 62 |
-
/Info 5 0 R
|
| 63 |
-
/Root 4 0 R
|
| 64 |
-
/Size 8
|
| 65 |
-
>>
|
| 66 |
-
startxref
|
| 67 |
-
1267
|
| 68 |
-
%%EOF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_server.py
DELETED
|
@@ -1,111 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
"""
|
| 3 |
-
Test script to verify the document extraction functionality
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
import sys
|
| 7 |
-
import os
|
| 8 |
-
import tempfile
|
| 9 |
-
import logging
|
| 10 |
-
|
| 11 |
-
# Add current directory to path to import app modules
|
| 12 |
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 13 |
-
|
| 14 |
-
logging.basicConfig(level=logging.INFO)
|
| 15 |
-
logger = logging.getLogger(__name__)
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
def test_extractor():
|
| 19 |
-
"""Test the document extractor functionality"""
|
| 20 |
-
try:
|
| 21 |
-
from app import get_extractor, extract_document
|
| 22 |
-
|
| 23 |
-
logger.info("Testing extractor initialization...")
|
| 24 |
-
extractor = get_extractor()
|
| 25 |
-
logger.info(f"Extractor initialized: {type(extractor)}")
|
| 26 |
-
logger.info(f"Using docling: {extractor.use_docling}")
|
| 27 |
-
|
| 28 |
-
# Test with sample files if they exist
|
| 29 |
-
test_files = ["test_sample.pdf", "test_sample.docx"]
|
| 30 |
-
|
| 31 |
-
for test_file in test_files:
|
| 32 |
-
if os.path.exists(test_file):
|
| 33 |
-
logger.info(f"Testing with {test_file}...")
|
| 34 |
-
|
| 35 |
-
# Mock file object for testing
|
| 36 |
-
class MockFile:
|
| 37 |
-
def __init__(self, path):
|
| 38 |
-
self.name = path
|
| 39 |
-
|
| 40 |
-
mock_file = MockFile(test_file)
|
| 41 |
-
content, status = extract_document(mock_file)
|
| 42 |
-
|
| 43 |
-
logger.info(f"Status: {status}")
|
| 44 |
-
logger.info(f"Content length: {len(content) if content else 0}")
|
| 45 |
-
|
| 46 |
-
if content:
|
| 47 |
-
logger.info("✅ Extraction successful")
|
| 48 |
-
else:
|
| 49 |
-
logger.warning("⚠️ No content extracted")
|
| 50 |
-
else:
|
| 51 |
-
logger.info(f"Test file {test_file} not found - skipping")
|
| 52 |
-
|
| 53 |
-
return True
|
| 54 |
-
|
| 55 |
-
except Exception as e:
|
| 56 |
-
logger.error(f"Test failed: {e}")
|
| 57 |
-
return False
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
def test_interface():
|
| 61 |
-
"""Test the Gradio interface creation"""
|
| 62 |
-
try:
|
| 63 |
-
from app import create_interface
|
| 64 |
-
|
| 65 |
-
logger.info("Testing interface creation...")
|
| 66 |
-
app = create_interface()
|
| 67 |
-
logger.info("✅ Interface created successfully")
|
| 68 |
-
return True
|
| 69 |
-
|
| 70 |
-
except Exception as e:
|
| 71 |
-
logger.error(f"Interface test failed: {e}")
|
| 72 |
-
return False
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
def main():
|
| 76 |
-
"""Run all tests"""
|
| 77 |
-
logger.info("=" * 50)
|
| 78 |
-
logger.info("Running Document Extractor Tests")
|
| 79 |
-
logger.info("=" * 50)
|
| 80 |
-
|
| 81 |
-
tests = [
|
| 82 |
-
("Extractor functionality", test_extractor),
|
| 83 |
-
("Interface creation", test_interface),
|
| 84 |
-
]
|
| 85 |
-
|
| 86 |
-
passed = 0
|
| 87 |
-
total = len(tests)
|
| 88 |
-
|
| 89 |
-
for test_name, test_func in tests:
|
| 90 |
-
logger.info(f"\nRunning test: {test_name}")
|
| 91 |
-
if test_func():
|
| 92 |
-
passed += 1
|
| 93 |
-
logger.info(f"✅ {test_name} - PASSED")
|
| 94 |
-
else:
|
| 95 |
-
logger.error(f"❌ {test_name} - FAILED")
|
| 96 |
-
|
| 97 |
-
logger.info("\n" + "=" * 50)
|
| 98 |
-
logger.info(f"Test Results: {passed}/{total} tests passed")
|
| 99 |
-
logger.info("=" * 50)
|
| 100 |
-
|
| 101 |
-
if passed == total:
|
| 102 |
-
logger.info("🎉 All tests passed! Ready for server deployment.")
|
| 103 |
-
return True
|
| 104 |
-
else:
|
| 105 |
-
logger.error("❌ Some tests failed. Please check the issues above.")
|
| 106 |
-
return False
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
if __name__ == "__main__":
|
| 110 |
-
success = main()
|
| 111 |
-
sys.exit(0 if success else 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|