Bullseye / document_reader.py
swethasingireddy
Initial commit
83818d8
Raw
History Blame Contribute Delete
4.53 kB
"""
document_reader.py
------------------
Handles text extraction from PDF and DOCX files.
Supports both single-file and batch (folder) reading.
"""
import os
import re
from pathlib import Path
from typing import Dict, List, Tuple, Optional
def extract_text_from_pdf(file_path: str) -> str:
"""Extract plain text from a PDF file using pdfplumber."""
try:
import pdfplumber
text_parts = []
with pdfplumber.open(file_path) as pdf:
for i, page in enumerate(pdf.pages):
page_text = page.extract_text()
if page_text:
text_parts.append(page_text)
return "\n\n".join(text_parts).strip()
except ImportError:
raise ImportError("pdfplumber is not installed. Run: pip install pdfplumber")
except Exception as e:
raise RuntimeError(f"Failed to read PDF '{file_path}': {e}")
def extract_text_from_docx(file_path: str) -> str:
"""Extract plain text from a DOCX file using python-docx."""
try:
from docx import Document
doc = Document(file_path)
paragraphs = []
# Extract paragraphs
for para in doc.paragraphs:
if para.text.strip():
paragraphs.append(para.text.strip())
# Extract text from tables too
for table in doc.tables:
for row in table.rows:
row_text = " | ".join(
cell.text.strip() for cell in row.cells if cell.text.strip()
)
if row_text:
paragraphs.append(row_text)
return "\n\n".join(paragraphs).strip()
except ImportError:
raise ImportError("python-docx is not installed. Run: pip install python-docx")
except Exception as e:
raise RuntimeError(f"Failed to read DOCX '{file_path}': {e}")
def read_document(file_path: str) -> str:
"""
Auto-detect file type and extract text content.
Supports .pdf and .docx files.
"""
path = Path(file_path)
if not path.exists():
raise FileNotFoundError(f"File not found: {file_path}")
suffix = path.suffix.lower()
if suffix == ".pdf":
return extract_text_from_pdf(file_path)
elif suffix in (".docx", ".doc"):
return extract_text_from_docx(file_path)
elif suffix in (".txt", ".md"):
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
return f.read()
else:
raise ValueError(
f"Unsupported file type '{suffix}'. Supported: .pdf, .docx, .txt, .md"
)
def load_student_submissions(folder_path: str) -> List[Dict]:
"""
Load all student submissions from a folder.
Returns a list of dicts:
[{"name": "Alice Johnson", "file": "alice_johnson.pdf", "text": "..."}, ...]
"""
folder = Path(folder_path)
if not folder.is_dir():
raise NotADirectoryError(f"Submissions folder not found: {folder_path}")
supported_extensions = {".pdf", ".docx", ".doc", ".txt", ".md"}
submissions = []
for file_path in sorted(folder.iterdir()):
if file_path.suffix.lower() not in supported_extensions:
continue
if file_path.name.startswith("."): # skip hidden files
continue
student_name = _infer_student_name(file_path.stem)
try:
text = read_document(str(file_path))
if not text.strip():
print(f" ⚠️ Warning: '{file_path.name}' appears to be empty. Skipping.")
continue
submissions.append(
{
"name": student_name,
"file": file_path.name,
"file_path": str(file_path),
"text": text,
}
)
except Exception as e:
print(f" ⚠️ Could not read '{file_path.name}': {e}")
return submissions
def _infer_student_name(stem: str) -> str:
"""
Turn a filename stem into a readable student name.
e.g. 'alice_johnson_assignment1' → 'Alice Johnson'
'john-doe' → 'John Doe'
"""
# Remove common suffixes like _assignment1, _submission, _hw1
stem = re.sub(
r"[_\-]?(assignment|submission|hw|homework|project|lab|task)\d*.*$",
"",
stem,
flags=re.IGNORECASE,
)
# Replace underscores/hyphens with spaces and title-case
name = stem.replace("_", " ").replace("-", " ").strip().title()
return name if name else stem