DEEPAN-C's picture
Upload folder using huggingface_hub
e4f4981 verified
Raw
History Blame Contribute Delete
12.8 kB
import os
import sys
from pathlib import Path
from typing import Optional, List, Dict, Any
import logging
# Word document processing libraries
try:
from docx import Document
DOCX_AVAILABLE = True
except ImportError:
DOCX_AVAILABLE = False
print("python-docx not found. Install with: pip install python-docx")
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class WordExtractor:
"""Advanced Word document text extractor with error handling."""
def __init__(self, docx_path: str):
self.docx_path = Path(docx_path)
self.document = None
self.text_content = {}
def validate_file(self) -> bool:
"""Validate Word document file exists and is accessible."""
if not self.docx_path.exists():
logger.error(f"Word document not found: {self.docx_path}")
return False
if not self.docx_path.is_file():
logger.error(f"Path is not a file: {self.docx_path}")
return False
if self.docx_path.stat().st_size == 0:
logger.error(f"Word document is empty: {self.docx_path}")
return False
# Check if it's a .docx file
if self.docx_path.suffix.lower() not in ['.docx', '.doc']:
logger.warning(f"File may not be a Word document: {self.docx_path}")
return True
def load_document(self) -> bool:
"""Load Word document with error handling."""
try:
self.document = Document(self.docx_path)
logger.info(f"Word document loaded successfully. Paragraphs: {len(self.document.paragraphs)}")
return True
except Exception as e:
logger.error(f"Failed to load Word document: {e}")
return False
def extract_text_from_paragraphs(self) -> str:
"""Extract text from all paragraphs."""
text = ""
try:
for paragraph in self.document.paragraphs:
if paragraph.text.strip():
text += paragraph.text + "\n"
logger.info(f"Extracted text from {len(self.document.paragraphs)} paragraphs")
return text.strip()
except Exception as e:
logger.error(f"Failed to extract text from paragraphs: {e}")
return ""
def extract_text_from_tables(self) -> str:
"""Extract text from all tables."""
text = ""
try:
for table in self.document.tables:
for row in table.rows:
row_text = []
for cell in row.cells:
if cell.text.strip():
row_text.append(cell.text.strip())
if row_text:
text += " | ".join(row_text) + "\n"
text += "\n" # Add space between tables
logger.info(f"Extracted text from {len(self.document.tables)} tables")
return text.strip()
except Exception as e:
logger.error(f"Failed to extract text from tables: {e}")
return ""
def extract_document_properties(self) -> Dict[str, str]:
"""Extract document properties/metadata."""
properties = {
"title": "",
"author": "",
"subject": "",
"keywords": "",
"comments": "",
"category": "",
"created": "",
"modified": ""
}
try:
core_props = self.document.core_properties
if core_props.title:
properties["title"] = core_props.title
if core_props.author:
properties["author"] = core_props.author
if core_props.subject:
properties["subject"] = core_props.subject
if core_props.keywords:
properties["keywords"] = core_props.keywords
if core_props.comments:
properties["comments"] = core_props.comments
if core_props.category:
properties["category"] = core_props.category
if core_props.created:
properties["created"] = str(core_props.created)
if core_props.modified:
properties["modified"] = str(core_props.modified)
except Exception as e:
logger.warning(f"Failed to extract document properties: {e}")
return properties
def extract_all_text(self) -> Dict[str, Any]:
"""Extract all text from Word document with comprehensive metadata."""
if not self.validate_file():
return {"error": "Invalid Word document file"}
if not self.load_document():
return {"error": "Failed to load Word document"}
# Extract text from different sources
paragraph_text = self.extract_text_from_paragraphs()
table_text = self.extract_text_from_tables()
# Combine all text
full_text = ""
if paragraph_text:
full_text += paragraph_text + "\n\n"
if table_text:
full_text += "--- TABLES ---\n" + table_text + "\n\n"
full_text = full_text.strip()
result = {
"file_path": str(self.docx_path),
"total_paragraphs": len(self.document.paragraphs),
"total_tables": len(self.document.tables),
"paragraphs": {},
"tables": {},
"full_text": full_text,
"metadata": self.extract_document_properties()
}
# Extract individual paragraphs with formatting info
for i, paragraph in enumerate(self.document.paragraphs):
result["paragraphs"][i + 1] = {
"text": paragraph.text,
"style": paragraph.style.name if paragraph.style else "Normal",
"has_text": bool(paragraph.text.strip()),
"runs": len(paragraph.runs)
}
# Extract individual tables
for i, table in enumerate(self.document.tables):
table_data = []
for row in table.rows:
row_data = []
for cell in row.cells:
row_data.append(cell.text.strip())
table_data.append(row_data)
result["tables"][i + 1] = {
"rows": len(table.rows),
"columns": len(table.columns) if table.rows else 0,
"data": table_data
}
return result
def save_extracted_text(self, output_path: Optional[str] = None) -> str:
"""Save extracted text to a file."""
result = self.extract_all_text()
if "error" in result:
logger.error(f"Cannot save: {result['error']}")
return ""
if not output_path:
output_path = self.docx_path.with_suffix('.txt')
try:
with open(output_path, 'w', encoding='utf-8') as f:
f.write(f"Word Document Text Extraction Results\n")
f.write(f"File: {result['file_path']}\n")
f.write(f"Paragraphs: {result['total_paragraphs']}\n")
f.write(f"Tables: {result['total_tables']}\n")
f.write("=" * 50 + "\n\n")
f.write(result['full_text'])
logger.info(f"Text saved to: {output_path}")
return str(output_path)
except Exception as e:
logger.error(f"Failed to save text: {e}")
return ""
def extract_word_text(file_path: str) -> Dict[str, Any]:
"""
Extract text from a single Word document file.
Args:
file_path: Path to the Word document file
Returns:
Dict containing extraction results with keys:
- success: Boolean indicating if extraction was successful
- file_path: Original file path
- text: Extracted text content
- metadata: Document metadata if available
- paragraphs: Paragraph-by-paragraph extraction details
- tables: Table extraction details
- error: Error message if extraction failed
"""
try:
extractor = WordExtractor(file_path)
result = extractor.extract_all_text()
if "error" in result:
return {
"success": False,
"file_path": file_path,
"error": result["error"]
}
return {
"success": True,
"file_path": file_path,
"text": result["full_text"],
"metadata": result["metadata"],
"paragraphs": result["paragraphs"],
"tables": result["tables"],
"total_paragraphs": result["total_paragraphs"],
"total_tables": result["total_tables"]
}
except Exception as e:
logger.error(f"Failed to extract text from {file_path}: {e}")
return {
"success": False,
"file_path": file_path,
"error": str(e)
}
def process_batch_word_docs(file_paths: List[str]) -> List[Dict[str, Any]]:
"""
Process multiple Word document files in batch.
Args:
file_paths: List of file paths to process
Returns:
List of extraction results for each file
"""
results = []
total_files = len(file_paths)
logger.info(f"Starting batch processing of {total_files} Word documents")
for i, file_path in enumerate(file_paths, 1):
logger.info(f"Processing file {i}/{total_files}: {file_path}")
result = extract_word_text(file_path)
results.append(result)
if result["success"]:
logger.info(f"✓ Successfully processed: {file_path}")
else:
logger.warning(f"✗ Failed to process: {file_path} - {result['error']}")
# Summary statistics
successful = sum(1 for r in results if r["success"])
failed = total_files - successful
logger.info(f"Batch processing complete: {successful} successful, {failed} failed")
return results
def extract_resume_sections(text: str) -> Dict[str, str]:
"""
Extract structured sections from resume text.
Args:
text: Raw resume text
Returns:
Dict with structured sections (skills, experience, education, etc.)
"""
sections = {
"contact_info": "",
"skills": "",
"experience": "",
"education": "",
"summary": "",
"other": ""
}
# Simple section extraction using keywords
lines = text.split('\n')
current_section = "other"
for line in lines:
line_lower = line.lower().strip()
# Detect sections based on keywords
if any(keyword in line_lower for keyword in ['skill', 'technology', 'programming', 'framework']):
current_section = "skills"
elif any(keyword in line_lower for keyword in ['experience', 'work', 'employment', 'job']):
current_section = "experience"
elif any(keyword in line_lower for keyword in ['education', 'degree', 'university', 'college', 'school']):
current_section = "education"
elif any(keyword in line_lower for keyword in ['summary', 'profile', 'objective', 'about']):
current_section = "summary"
elif any(keyword in line_lower for keyword in ['email', 'phone', '@', 'linkedin', 'github']):
current_section = "contact_info"
# Add line to current section
if line.strip():
sections[current_section] += line + "\n"
# Clean up sections
for key in sections:
sections[key] = sections[key].strip()
return sections
def main():
"""Main function for command line usage."""
if len(sys.argv) > 1:
docx_path = sys.argv[1]
result = extract_word_text(docx_path)
if result["success"]:
print(f"✓ Successfully extracted text from: {docx_path}")
print(f"Text length: {len(result['text'])} characters")
print(f"Paragraphs: {result['total_paragraphs']}")
print(f"Tables: {result['total_tables']}")
else:
print(f"✗ Failed to extract text: {result['error']}")
else:
print("Usage: python word_parser.py <file_path>")
print("For batch processing, use the programmatic functions directly.")
if __name__ == "__main__":
main()