-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdocument_processor.py
More file actions
197 lines (155 loc) · 6.75 KB
/
Copy pathdocument_processor.py
File metadata and controls
197 lines (155 loc) · 6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import os
import logging
from typing import List, Optional
import fitz # PyMuPDF
from docx import Document as DocxDocument
import pytesseract
from PIL import Image
import io
# Simple text splitter implementation
class SimpleTextSplitter:
def __init__(self, chunk_size=1000, chunk_overlap=200):
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
def split_text(self, text: str) -> List[str]:
"""Split text into chunks"""
if not text:
return []
chunks = []
start = 0
while start < len(text):
end = start + self.chunk_size
# Try to break at sentence boundary
if end < len(text):
last_period = text[:end].rfind('.')
last_newline = text[:end].rfind('\n')
break_point = max(last_period, last_newline)
if break_point > start + self.chunk_size // 2:
end = break_point + 1
chunk = text[start:end].strip()
if len(chunk) > 50: # Only include meaningful chunks
chunks.append(chunk)
start = end - self.chunk_overlap
if start >= len(text):
break
return chunks
class DocumentProcessor:
"""Handles document text extraction and chunking"""
def __init__(self):
self.text_splitter = SimpleTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
def extract_text(self, file_path: str, file_type: str) -> str:
"""Extract text from document based on file type"""
try:
if file_type == 'pdf':
return self._extract_from_pdf(file_path)
elif file_type == 'docx':
return self._extract_from_docx(file_path)
elif file_type == 'txt':
return self._extract_from_txt(file_path)
else:
raise ValueError(f"Unsupported file type: {file_type}")
except Exception as e:
logging.error(f"Text extraction failed for {file_path}: {e}")
raise
def _extract_from_pdf(self, file_path: str) -> str:
"""Extract text from PDF with OCR fallback for scanned documents"""
try:
# Open PDF with PyMuPDF
doc = fitz.open(file_path)
text = ""
for page_num in range(len(doc)):
page = doc[page_num]
# First try to extract text directly
page_text = page.get_text()
# If very little text found, use OCR
if len(page_text.strip()) < 50:
logging.info(f"Page {page_num + 1} has minimal text, using OCR")
# Get page as image
pix = page.get_pixmap()
img_data = pix.tobytes("png")
# Convert to PIL Image
image = Image.open(io.BytesIO(img_data))
# Use OCR to extract text
ocr_text = pytesseract.image_to_string(image)
text += f"\n--- Page {page_num + 1} (OCR) ---\n{ocr_text}\n"
else:
text += f"\n--- Page {page_num + 1} ---\n{page_text}\n"
doc.close()
if not text.strip():
raise ValueError("No text could be extracted from PDF")
return text.strip()
except Exception as e:
logging.error(f"PDF extraction failed: {e}")
raise ValueError(f"Failed to extract text from PDF: {str(e)}")
def _extract_from_docx(self, file_path: str) -> str:
"""Extract text from DOCX file"""
try:
doc = DocxDocument(file_path)
text = ""
for paragraph in doc.paragraphs:
if paragraph.text.strip():
text += paragraph.text + "\n"
# Extract text from tables
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
if cell.text.strip():
text += cell.text + " "
text += "\n"
if not text.strip():
raise ValueError("No text could be extracted from DOCX")
return text.strip()
except Exception as e:
logging.error(f"DOCX extraction failed: {e}")
raise ValueError(f"Failed to extract text from DOCX: {str(e)}")
def _extract_from_txt(self, file_path: str) -> str:
"""Extract text from TXT file"""
try:
with open(file_path, 'r', encoding='utf-8') as file:
return file.read()
except UnicodeDecodeError:
# Try with different encoding
with open(file_path, 'r', encoding='latin-1') as file:
return file.read()
except Exception as e:
logging.error(f"TXT processing error: {e}")
raise
def create_chunks(self, text: str) -> List[str]:
"""Split text into chunks for embedding"""
try:
# Clean and preprocess text
text = self._preprocess_text(text)
# Split into chunks
chunks = self.text_splitter.split_text(text)
# Filter out very short chunks
chunks = [chunk for chunk in chunks if len(chunk.strip()) > 50]
logging.info(f"Created {len(chunks)} chunks from text")
return chunks
except Exception as e:
logging.error(f"Text chunking error: {e}")
raise
def _preprocess_text(self, text: str) -> str:
"""Clean and preprocess text"""
# Remove excessive whitespace
text = " ".join(text.split())
# Remove special characters that might interfere
text = text.replace('\x00', '') # Remove null characters
return text
def get_document_info(self, file_path: str, file_type: str) -> dict:
"""Get basic information about the document"""
info = {
'pages': 1,
'word_count': 0,
'char_count': 0
}
try:
# Extract text to count words and characters
text = self.extract_text(file_path, file_type)
info['word_count'] = len(text.split())
info['char_count'] = len(text)
except Exception as e:
logging.error(f"Document info extraction error: {e}")
return info