779 lines
No EOL
29 KiB
Python
779 lines
No EOL
29 KiB
Python
"""
|
|
Modul zur Extraktion von Inhalten aus verschiedenen Dateiformaten.
|
|
Bietet spezialisierte Funktionen für die Verarbeitung von Text, PDF, Office-Dokumenten, Bildern usw.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import io
|
|
from typing import Dict, Any, List, Optional, Union, Tuple
|
|
import base64
|
|
|
|
# Logger konfigurieren
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Optional imports - only loaded when needed
|
|
pdf_extractor_loaded = False
|
|
office_extractor_loaded = False
|
|
image_processor_loaded = False
|
|
|
|
def get_document_contents(file_metadata: Dict[str, Any], file_content: bytes) -> List[Dict[str, Any]]:
|
|
"""
|
|
Hauptfunktion zur Extraktion von Inhalten aus einer Datei basierend auf dem MIME-Typ.
|
|
Delegiert an spezialisierte Extraktionsfunktionen.
|
|
|
|
Args:
|
|
file_metadata: Metadaten der Datei (Name, MIME-Typ, etc.)
|
|
file_content: Binärdaten der Datei
|
|
|
|
Returns:
|
|
Liste von Document-Content-Objekten mit metadata und is_text Flag
|
|
"""
|
|
try:
|
|
mime_type = file_metadata.get("mime_type", "application/octet-stream")
|
|
file_name = file_metadata.get("name", "unknown")
|
|
|
|
logger.info(f"Extrahiere Inhalte aus Datei '{file_name}' (MIME-Typ: {mime_type})")
|
|
|
|
# Inhalte basierend auf MIME-Typ extrahieren
|
|
contents = []
|
|
|
|
# Text-basierte Formate
|
|
if mime_type.startswith("text/") or mime_type in [
|
|
"application/json",
|
|
"application/xml",
|
|
"application/javascript",
|
|
"application/x-python"
|
|
]:
|
|
contents.extend(extract_text_content(file_name, file_content, mime_type))
|
|
|
|
# CSV Format
|
|
elif mime_type == "text/csv":
|
|
contents.extend(extract_csv_content(file_name, file_content))
|
|
|
|
# Bilder
|
|
elif mime_type.startswith("image/"):
|
|
contents.extend(extract_image_content(file_name, file_content, mime_type))
|
|
|
|
# PDF Dokumente
|
|
elif mime_type == "application/pdf":
|
|
contents.extend(extract_pdf_content(file_name, file_content))
|
|
|
|
# Word-Dokumente
|
|
elif mime_type in [
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"application/msword"
|
|
]:
|
|
contents.extend(extract_word_content(file_name, file_content, mime_type))
|
|
|
|
# Excel-Dokumente
|
|
elif mime_type in [
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
"application/vnd.ms-excel"
|
|
]:
|
|
contents.extend(extract_excel_content(file_name, file_content, mime_type))
|
|
|
|
# PowerPoint-Dokumente
|
|
elif mime_type in [
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
"application/vnd.ms-powerpoint"
|
|
]:
|
|
contents.extend(extract_powerpoint_content(file_name, file_content, mime_type))
|
|
|
|
# Binärdaten als Fallback für unbekannte Formate
|
|
else:
|
|
contents.extend(extract_binary_content(file_name, file_content, mime_type))
|
|
|
|
# Fallback, wenn keine Inhalte extrahiert werden konnten
|
|
if not contents:
|
|
logger.warning(f"Keine Inhalte aus Datei '{file_name}' extrahiert, verwende Binär-Fallback")
|
|
contents.append({
|
|
"sequence_nr": 1,
|
|
"name": '1_undefined',
|
|
"ext": os.path.splitext(file_name)[1][1:] if os.path.splitext(file_name)[1] else "bin",
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False
|
|
}
|
|
})
|
|
|
|
# Add generic attributes for all documents
|
|
|
|
for content in contents:
|
|
if isinstance(content.get("data"), bytes):
|
|
content["data"] = base64.b64encode(content["data"]).decode('utf-8')
|
|
# Add base64 flag
|
|
if "metadata" not in content:
|
|
content["metadata"] = {}
|
|
content["metadata"]["base64_encoded"] = True
|
|
|
|
logger.info(f"Erfolgreich {len(contents)} Inhalte aus Datei '{file_name}' extrahiert")
|
|
return contents
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der Inhaltsextraktion: {str(e)}")
|
|
# Fallback bei Fehler - Originaldaten zurückgeben
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": file_metadata.get("name", "unknown"),
|
|
"ext": os.path.splitext(file_metadata.get("name", ""))[1][1:] if os.path.splitext(file_metadata.get("name", ""))[1] else "bin",
|
|
"content_type": file_metadata.get("mime_type", "application/octet-stream"),
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False
|
|
}
|
|
}]
|
|
|
|
|
|
def _load_pdf_extractor():
|
|
"""Lädt die PDF-Extraktions-Bibliotheken bei Bedarf"""
|
|
global pdf_extractor_loaded
|
|
if not pdf_extractor_loaded:
|
|
try:
|
|
global PyPDF2, fitz
|
|
import PyPDF2
|
|
import fitz # PyMuPDF für umfangreichere PDF-Verarbeitung
|
|
pdf_extractor_loaded = True
|
|
logger.info("PDF-Extraktions-Bibliotheken erfolgreich geladen")
|
|
except ImportError as e:
|
|
logger.warning(f"PDF-Extraktions-Bibliotheken konnten nicht geladen werden: {e}")
|
|
|
|
def _load_office_extractor():
|
|
"""Lädt die Office-Dokument-Extraktions-Bibliotheken bei Bedarf"""
|
|
global office_extractor_loaded
|
|
if not office_extractor_loaded:
|
|
try:
|
|
global docx, openpyxl
|
|
import docx # python-docx für Word-Dokumente
|
|
import openpyxl # für Excel-Dateien
|
|
office_extractor_loaded = True
|
|
logger.info("Office-Extraktions-Bibliotheken erfolgreich geladen")
|
|
except ImportError as e:
|
|
logger.warning(f"Office-Extraktions-Bibliotheken konnten nicht geladen werden: {e}")
|
|
|
|
def _load_image_processor():
|
|
"""Lädt die Bild-Verarbeitungs-Bibliotheken bei Bedarf"""
|
|
global image_processor_loaded
|
|
if not image_processor_loaded:
|
|
try:
|
|
global PIL, Image
|
|
from PIL import Image
|
|
image_processor_loaded = True
|
|
logger.info("Bild-Verarbeitungs-Bibliotheken erfolgreich geladen")
|
|
except ImportError as e:
|
|
logger.warning(f"Bild-Verarbeitungs-Bibliotheken konnten nicht geladen werden: {e}")
|
|
|
|
def extract_text_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Extrahiert Text aus Textdateien.
|
|
|
|
Args:
|
|
file_name: Name der Datei
|
|
file_content: Binärdaten der Datei
|
|
mime_type: MIME-Typ der Datei
|
|
|
|
Returns:
|
|
Liste von Text-Content-Objekten mit metadata.is_text = True
|
|
"""
|
|
try:
|
|
# Originaldateiendung beibehalten
|
|
file_extension = os.path.splitext(file_name)[1][1:] if os.path.splitext(file_name)[1] else "txt"
|
|
|
|
# Text-Inhalt extrahieren
|
|
text_content = file_content.decode('utf-8')
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_text", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": "text",
|
|
"data": text_content,
|
|
"metadata": {
|
|
"is_text": True
|
|
}
|
|
}]
|
|
except UnicodeDecodeError:
|
|
logger.warning(f"Konnte Text aus Datei '{file_name}' nicht als UTF-8 decodieren, versuche andere Kodierungen")
|
|
try:
|
|
# Versuche alternative Kodierungen
|
|
for encoding in ['latin-1', 'cp1252', 'iso-8859-1']:
|
|
try:
|
|
text_content = file_content.decode(encoding)
|
|
logger.info(f"Text erfolgreich mit Kodierung {encoding} decodiert")
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_text", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": "text",
|
|
"data": text_content,
|
|
"metadata": {
|
|
"is_text": True,
|
|
"encoding": encoding
|
|
}
|
|
}]
|
|
except UnicodeDecodeError:
|
|
continue
|
|
|
|
# Fallback auf Binärdaten, wenn keine Kodierung funktioniert
|
|
logger.warning(f"Konnte Text nicht decodieren, verwende Binärdaten")
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_binary", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False
|
|
}
|
|
}]
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der alternativen Textdekodierung: {str(e)}")
|
|
# Binärdaten als Fallback zurückgeben
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_binary", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False
|
|
}
|
|
}]
|
|
|
|
def extract_csv_content(file_name: str, file_content: bytes) -> List[Dict[str, Any]]:
|
|
"""
|
|
Extrahiert Inhalt aus CSV-Dateien.
|
|
|
|
Args:
|
|
file_name: Name der Datei
|
|
file_content: Binärdaten der Datei
|
|
|
|
Returns:
|
|
Liste von CSV-Content-Objekten mit metadata.is_text = True
|
|
"""
|
|
try:
|
|
# Text-Inhalt extrahieren
|
|
csv_content = file_content.decode('utf-8')
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_csv", # Simplified naming
|
|
"ext": "csv",
|
|
"content_type": "csv",
|
|
"data": csv_content,
|
|
"metadata": {
|
|
"is_text": True,
|
|
"format": "csv"
|
|
}
|
|
}]
|
|
except UnicodeDecodeError:
|
|
logger.warning(f"Konnte CSV aus Datei '{file_name}' nicht als UTF-8 decodieren, versuche andere Kodierungen")
|
|
try:
|
|
# Versuche alternative Kodierungen für CSV
|
|
for encoding in ['latin-1', 'cp1252', 'iso-8859-1']:
|
|
try:
|
|
csv_content = file_content.decode(encoding)
|
|
logger.info(f"CSV erfolgreich mit Kodierung {encoding} decodiert")
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_csv", # Simplified naming
|
|
"ext": "csv",
|
|
"content_type": "csv",
|
|
"data": csv_content,
|
|
"metadata": {
|
|
"is_text": True,
|
|
"encoding": encoding,
|
|
"format": "csv"
|
|
}
|
|
}]
|
|
except UnicodeDecodeError:
|
|
continue
|
|
|
|
# Fallback auf Binärdaten
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_binary", # Simplified naming
|
|
"ext": "csv",
|
|
"content_type": "text/csv",
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False
|
|
}
|
|
}]
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der alternativen CSV-Dekodierung: {str(e)}")
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_binary", # Simplified naming
|
|
"ext": "csv",
|
|
"content_type": "text/csv",
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False
|
|
}
|
|
}]
|
|
|
|
def extract_image_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Extrahiert Inhalt aus Bilddateien und erzeugt ggf. Metadaten-Beschreibungen.
|
|
|
|
Args:
|
|
file_name: Name der Datei
|
|
file_content: Binärdaten der Datei
|
|
mime_type: MIME-Typ der Datei
|
|
|
|
Returns:
|
|
Liste von Image-Content-Objekten mit metadata.is_text = False
|
|
"""
|
|
|
|
# Dateiendung aus MIME-Typ oder Dateinamen extrahieren
|
|
file_extension = mime_type.split('/')[-1]
|
|
if file_extension == "jpeg":
|
|
file_extension = "jpg"
|
|
|
|
# Wenn möglich, Bild analysieren und Metadaten extrahieren
|
|
image_metadata = {
|
|
"is_text": False,
|
|
"format": "image"
|
|
}
|
|
image_description = None
|
|
|
|
try:
|
|
_load_image_processor()
|
|
if image_processor_loaded and file_content and len(file_content) > 0:
|
|
with io.BytesIO(file_content) as img_stream:
|
|
try:
|
|
img = Image.open(img_stream)
|
|
# Überprüfe, ob das Bild tatsächlich geladen wurde
|
|
img.verify()
|
|
# Um sicher weiterzuarbeiten, neu laden
|
|
img_stream.seek(0)
|
|
img = Image.open(img_stream)
|
|
image_metadata.update({
|
|
"format": img.format,
|
|
"mode": img.mode,
|
|
"width": img.width,
|
|
"height": img.height
|
|
})
|
|
# Extrahiere EXIF-Daten, falls vorhanden
|
|
if hasattr(img, '_getexif') and callable(img._getexif):
|
|
exif = img._getexif()
|
|
if exif:
|
|
exif_data = {}
|
|
for tag_id, value in exif.items():
|
|
exif_data[f"tag_{tag_id}"] = str(value)
|
|
image_metadata["exif"] = exif_data
|
|
|
|
# Erzeuge Bildbeschreibung
|
|
image_description = f"Image ({img.width}x{img.height}, {img.format}, {img.mode})"
|
|
except Exception as inner_e:
|
|
logger.warning(f"Fehler beim Verarbeiten des Bildes: {str(inner_e)}")
|
|
image_metadata["error"] = str(inner_e)
|
|
image_description = f"Image (unable to process: {str(inner_e)})"
|
|
except Exception as e:
|
|
logger.warning(f"Konnte Bildmetadaten nicht extrahieren: {str(e)}")
|
|
image_metadata["error"] = str(e)
|
|
|
|
|
|
# Bild-Inhalt zurückgeben
|
|
contents = [{
|
|
"sequence_nr": 1,
|
|
"name": "1_image", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": "image",
|
|
"data": file_content,
|
|
"metadata": image_metadata
|
|
}]
|
|
|
|
# Falls Bildbeschreibung vorhanden, als zusätzlichen Text-Content hinzufügen
|
|
if image_description:
|
|
contents.append({
|
|
"sequence_nr": 2,
|
|
"name": "2_text_image_info", # Simplified naming with label
|
|
"ext": "txt",
|
|
"content_type": "text",
|
|
"data": image_description,
|
|
"metadata": {
|
|
"is_text": True,
|
|
"image_description": True
|
|
}
|
|
})
|
|
|
|
return contents
|
|
|
|
def extract_pdf_content(file_name: str, file_content: bytes) -> List[Dict[str, Any]]:
|
|
"""
|
|
Extrahiert Text und Bilder aus PDF-Dateien.
|
|
|
|
Args:
|
|
file_name: Name der Datei
|
|
file_content: Binärdaten der Datei
|
|
|
|
Returns:
|
|
Liste von PDF-Content-Objekten (Text und Bilder) mit metadata.is_text Flag
|
|
"""
|
|
contents = []
|
|
extracted_content_found = False
|
|
|
|
try:
|
|
# PDF-Extraktions-Bibliotheken laden
|
|
_load_pdf_extractor()
|
|
if not pdf_extractor_loaded:
|
|
logger.warning("PDF-Extraktion nicht möglich: Bibliotheken nicht verfügbar")
|
|
# Originaldatei als binären Inhalt hinzufügen
|
|
contents.append({
|
|
"sequence_nr": 1,
|
|
"name": "1_pdf", # Simplified naming
|
|
"ext": "pdf",
|
|
"content_type": "application/pdf",
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"format": "pdf"
|
|
}
|
|
})
|
|
return contents
|
|
|
|
# Text mit PyPDF2 extrahieren
|
|
extracted_text = ""
|
|
pdf_metadata = {}
|
|
with io.BytesIO(file_content) as pdf_stream:
|
|
pdf_reader = PyPDF2.PdfReader(pdf_stream)
|
|
|
|
# Metadaten extrahieren
|
|
pdf_info = pdf_reader.metadata or {}
|
|
for key, value in pdf_info.items():
|
|
if key.startswith('/'):
|
|
pdf_metadata[key[1:]] = value
|
|
else:
|
|
pdf_metadata[key] = value
|
|
|
|
# Text aus allen Seiten extrahieren
|
|
for page_num in range(len(pdf_reader.pages)):
|
|
page = pdf_reader.pages[page_num]
|
|
page_text = page.extract_text()
|
|
if page_text:
|
|
extracted_text += f"--- Seite {page_num + 1} ---\n{page_text}\n\n"
|
|
|
|
# Wenn Text gefunden wurde, als eigenen Content hinzufügen
|
|
if extracted_text.strip():
|
|
extracted_content_found = True
|
|
contents.append({
|
|
"sequence_nr": len(contents) + 1,
|
|
"name": f"{len(contents) + 1}_text", # Simplified naming
|
|
"ext": "txt",
|
|
"content_type": "text",
|
|
"data": extracted_text,
|
|
"metadata": {
|
|
"is_text": True,
|
|
"source": "pdf",
|
|
"pages": len(pdf_reader.pages),
|
|
"pdf_metadata": pdf_metadata
|
|
}
|
|
})
|
|
|
|
# Bilder mit PyMuPDF (fitz) extrahieren
|
|
try:
|
|
with io.BytesIO(file_content) as pdf_stream:
|
|
doc = fitz.open(stream=pdf_stream, filetype="pdf")
|
|
image_count = 0
|
|
|
|
for page_num in range(len(doc)):
|
|
page = doc[page_num]
|
|
image_list = page.get_images(full=True)
|
|
|
|
for img_index, img_info in enumerate(image_list):
|
|
try:
|
|
image_count += 1
|
|
xref = img_info[0]
|
|
base_image = doc.extract_image(xref)
|
|
image_bytes = base_image["image"]
|
|
image_ext = base_image["ext"]
|
|
|
|
# Bild als Content hinzufügen
|
|
extracted_content_found = True
|
|
contents.append({
|
|
"sequence_nr": len(contents) + 1,
|
|
"name": f"{len(contents) + 1}_image_page{page_num+1}_{img_index+1}", # Simplified naming with label
|
|
"ext": image_ext,
|
|
"content_type": f"image/{image_ext}",
|
|
"data": image_bytes,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"source": "pdf",
|
|
"page": page_num + 1,
|
|
"index": img_index
|
|
}
|
|
})
|
|
except Exception as img_e:
|
|
logger.warning(f"Fehler bei der Extraktion von Bild {img_index} auf Seite {page_num + 1}: {str(img_e)}")
|
|
|
|
# Dokument schließen
|
|
doc.close()
|
|
|
|
except Exception as img_extract_e:
|
|
logger.warning(f"Fehler bei der Bildextraktion aus PDF: {str(img_extract_e)}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der PDF-Extraktion: {str(e)}")
|
|
|
|
# Wenn keine Inhalte extrahiert wurden, füge das Original-PDF hinzu
|
|
if not extracted_content_found:
|
|
contents.append({
|
|
"sequence_nr": 1,
|
|
"name": "1_pdf", # Simplified naming
|
|
"ext": "pdf",
|
|
"content_type": "application/pdf",
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"format": "pdf"
|
|
}
|
|
})
|
|
|
|
return contents
|
|
|
|
def extract_word_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Extrahiert Text und Bilder aus Word-Dokumenten.
|
|
|
|
Args:
|
|
file_name: Name der Datei
|
|
file_content: Binärdaten der Datei
|
|
mime_type: MIME-Typ der Datei
|
|
|
|
Returns:
|
|
Liste von Word-Content-Objekten (Text und ggf. Bilder) mit metadata.is_text Flag
|
|
"""
|
|
contents = []
|
|
extracted_content_found = False
|
|
|
|
# Dateiendung bestimmen
|
|
file_extension = "docx" if mime_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" else "doc"
|
|
|
|
try:
|
|
# Office-Extraktions-Bibliotheken laden
|
|
_load_office_extractor()
|
|
if not office_extractor_loaded:
|
|
logger.warning("Word-Extraktion nicht möglich: Bibliotheken nicht verfügbar")
|
|
# Originaldatei als binären Inhalt hinzufügen
|
|
contents.append({
|
|
"sequence_nr": 1,
|
|
"name": "1_word", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"format": "word"
|
|
}
|
|
})
|
|
return contents
|
|
|
|
# Unterstützt nur DOCX (neueres Format)
|
|
if mime_type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
with io.BytesIO(file_content) as docx_stream:
|
|
doc = docx.Document(docx_stream)
|
|
|
|
# Text extrahieren
|
|
full_text = []
|
|
for para in doc.paragraphs:
|
|
full_text.append(para.text)
|
|
|
|
# Tabellen extrahieren
|
|
for table in doc.tables:
|
|
for row in table.rows:
|
|
row_text = []
|
|
for cell in row.cells:
|
|
row_text.append(cell.text)
|
|
full_text.append(" | ".join(row_text))
|
|
|
|
extracted_text = "\n\n".join(full_text)
|
|
|
|
# Extrahierten Text als Content hinzufügen
|
|
if extracted_text.strip():
|
|
extracted_content_found = True
|
|
contents.append({
|
|
"sequence_nr": 1,
|
|
"name": "1_text", # Simplified naming
|
|
"ext": "txt",
|
|
"content_type": "text",
|
|
"data": extracted_text,
|
|
"metadata": {
|
|
"is_text": True,
|
|
"source": "docx",
|
|
"paragraph_count": len(doc.paragraphs),
|
|
"table_count": len(doc.tables)
|
|
}
|
|
})
|
|
else:
|
|
logger.warning(f"Extraktion aus altem Word-Format (DOC) nicht unterstützt")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der Word-Extraktion: {str(e)}")
|
|
|
|
# Wenn keine Inhalte extrahiert wurden, füge das Original-Dokument hinzu
|
|
if not extracted_content_found:
|
|
contents.append({
|
|
"sequence_nr": 1,
|
|
"name": "1_word", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"format": "word"
|
|
}
|
|
})
|
|
|
|
return contents
|
|
|
|
def extract_excel_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Extrahiert Tabellendaten aus Excel-Dateien.
|
|
|
|
Args:
|
|
file_name: Name der Datei
|
|
file_content: Binärdaten der Datei
|
|
mime_type: MIME-Typ der Datei
|
|
|
|
Returns:
|
|
Liste von Excel-Content-Objekten mit metadata.is_text Flag
|
|
"""
|
|
contents = []
|
|
extracted_content_found = False
|
|
|
|
# Dateiendung bestimmen
|
|
file_extension = "xlsx" if mime_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" else "xls"
|
|
|
|
try:
|
|
# Office-Extraktions-Bibliotheken laden
|
|
_load_office_extractor()
|
|
if not office_extractor_loaded:
|
|
logger.warning("Excel-Extraktion nicht möglich: Bibliotheken nicht verfügbar")
|
|
# Originaldatei als binären Inhalt hinzufügen
|
|
contents.append({
|
|
"sequence_nr": 1,
|
|
"name": "1_excel", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"format": "excel"
|
|
}
|
|
})
|
|
return contents
|
|
|
|
# Unterstützt nur XLSX (neueres Format)
|
|
if mime_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
|
with io.BytesIO(file_content) as xlsx_stream:
|
|
workbook = openpyxl.load_workbook(xlsx_stream, data_only=True)
|
|
|
|
# Jedes Arbeitsblatt als separaten CSV-Content extrahieren
|
|
for sheet_index, sheet_name in enumerate(workbook.sheetnames):
|
|
sheet = workbook[sheet_name]
|
|
|
|
# Daten als CSV formatieren
|
|
csv_rows = []
|
|
for row in sheet.iter_rows():
|
|
csv_row = []
|
|
for cell in row:
|
|
value = cell.value
|
|
if value is None:
|
|
csv_row.append("")
|
|
else:
|
|
csv_row.append(str(value).replace('"', '""'))
|
|
csv_rows.append(','.join(f'"{cell}"' for cell in csv_row))
|
|
|
|
csv_content = "\n".join(csv_rows)
|
|
|
|
# Als CSV-Content hinzufügen
|
|
if csv_content.strip():
|
|
extracted_content_found = True
|
|
sheet_safe_name = sheet_name.replace(" ", "_").replace("/", "_").replace("\\", "_")
|
|
contents.append({
|
|
"sequence_nr": len(contents) + 1,
|
|
"name": f"{len(contents) + 1}_csv_{sheet_safe_name}", # Simplified naming with sheet label
|
|
"ext": "csv",
|
|
"content_type": "csv",
|
|
"data": csv_content,
|
|
"metadata": {
|
|
"is_text": True,
|
|
"source": "xlsx",
|
|
"sheet": sheet_name,
|
|
"format": "csv"
|
|
}
|
|
})
|
|
else:
|
|
logger.warning(f"Extraktion aus altem Excel-Format (XLS) nicht unterstützt")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der Excel-Extraktion: {str(e)}")
|
|
|
|
# Wenn keine Inhalte extrahiert wurden, füge das Original-Dokument hinzu
|
|
if not extracted_content_found:
|
|
contents.append({
|
|
"sequence_nr": 1,
|
|
"name": "1_excel", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"format": "excel"
|
|
}
|
|
})
|
|
|
|
return contents
|
|
|
|
def extract_powerpoint_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Extrahiert Inhalte aus PowerPoint-Präsentationen.
|
|
|
|
Args:
|
|
file_name: Name der Datei
|
|
file_content: Binärdaten der Datei
|
|
mime_type: MIME-Typ der Datei
|
|
|
|
Returns:
|
|
Liste von PowerPoint-Content-Objekten mit metadata.is_text = False
|
|
"""
|
|
# Für PowerPoint geben wir aktuell nur die originale Binärdatei zurück
|
|
# Eine vollständige Extraktion würde mehr spezialisierte Bibliotheken erfordern
|
|
file_extension = "pptx" if mime_type == "application/vnd.openxmlformats-officedocument.presentationml.presentation" else "ppt"
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_powerpoint", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"format": "powerpoint"
|
|
}
|
|
}]
|
|
|
|
def extract_binary_content(file_name: str, file_content: bytes, mime_type: str) -> List[Dict[str, Any]]:
|
|
"""
|
|
Fallback für binäre Dateien, bei denen keine spezifische Extraktion möglich ist.
|
|
|
|
Args:
|
|
file_name: Name der Datei
|
|
file_content: Binärdaten der Datei
|
|
mime_type: MIME-Typ der Datei
|
|
|
|
Returns:
|
|
Liste mit einem binären Content-Objekt mit metadata.is_text = False
|
|
"""
|
|
file_extension = os.path.splitext(file_name)[1][1:] if os.path.splitext(file_name)[1] else "bin"
|
|
return [{
|
|
"sequence_nr": 1,
|
|
"name": "1_binary", # Simplified naming
|
|
"ext": file_extension,
|
|
"content_type": mime_type,
|
|
"data": file_content,
|
|
"metadata": {
|
|
"is_text": False,
|
|
"format": "binary"
|
|
}
|
|
}] |