/Nachtrag 09.03.2016: Eine lauffähige Version des „Tika Page Extractors“ kann heruntergeladen werden: Download. Der Quellcode steht auf Github zur Verfügung./

Volltextsuche ist eine relativ häufige Anforderung in der Programmierung. Häufig sollen dabei beliebige Dokumente durchsuchbar gemacht werden, also z.B. PDF-Dateien oder Office-Dokumente (Word, Excel, Open-/LibreOffice, RTF, was auch immer). Apache Tika ist dabei häufig das Tool der Wahl. Tika beherrscht über 1.000 verschiedene Formate, kann Metadaten extrahieren, die Sprache eines Dokuments erraten und wird auch von Suchservern wie Solr oder ElasticSearch eingesetzt.

Der Volltext wird bei der Extraktion komplett als Text zurückgegeben. In einem meiner letzten Projekte wollte ich jedoch mehr: Der Text sollte pro Seite zurückgegeben werden, um bei der Suche bestimmen zu können, auf welchen Seiten der gesuchte Text gefunden wurde. Die Lösung war das Erstellen einer eigenen ContentHandler-Klasse, welche die Aufgabe übernimmt.

Wichtig dabei ist jedoch, dass dies nur bei PDF-Dateien funktioniert! Andere Office-Formate enthalten in der Regel keine Informationen zu den Seiten. Die hier vorgestellte Klasse arbeitet also nur mit PDFs:

Tika erstellt beim Extrahieren von PDFs Seitentags und zeichnet diese als div aus. Diese können wir in der obigen Klasse (einem SAX-Parser) abfangen und eine Liste der Seiten bauen. Inspiriert wurde die obige Klasse von einer JRuby-Implementierung. Statt einer HashMap verwende ich jedoch eine Liste, die ebenfalls recht zuverlässig die Seiten wiedergeben und geringfügig schneller sein sollte (meine Tests zeigten einen Faktor von ca. 5-10% bei 300 Seiten). Als Option kann man dem Konstruktor einen Parameter mitgeben, der bestimmt, ob die extrahierte Information komprimiert werden soll oder nicht. Voreingestellt ist die Kompression, die Zeilenumbrüche, Tabulatoren und doppelte Leerzeichen (also Whitespace) entfernt. Diese sind in der Volltextsuche in der Regel nicht relevant.

Zum Testen kann man folgende Klasse verwenden:

Es wird ein Kommandozeilenparameter erwartet, der Dateiname der zu extrahierenden Datei. Dieser wird in Tika bzw. dessen Parser eingelesen und extrahiert. Die Zeit wird dabei gemessen und bei Erfolg die Seiten ausgegeben. Die ArrayList ermöglicht auch ein effektives Zugreifen auf einzelne Seiten innerhalb der Liste.

Alternativen

Es gibt auch einige Alternativen, die ich vorstellen möche:

  • Möglich ist eine Kombination von PDFTK und pdftotext. PDFTK splittet PDF-Dateien dabei in einzelne Dokumente auf, die pdftotext dann in Text umwandeln kann.
  • Das Github-Projekt pdf-extract verwendet beispielsweise diese Methode. Dabei handelt es sich um einen NodeJS-Wrapper, der auch noch OCR beherrscht (mit Hilfe von tesseract). Ich verwende für OCR übrigens mein eigenes Sandy-Skript.
  • Die oben erwähnte JRuby-Implementierung ermöglicht ebenfalls eine Extraktion pro Seite – nur eben in JRuby.

Wer weitere Alternativen weiß, ich freue mich auf Hinweise und Kommentare.