ちょっと、PDFファイルをJavaで読む必要にかられまして。
JavaでPDFを読むには、Apache Tikaを使用するのが良さそうだったので、こちらを試してみました。
Apache Tikaとは?
様々なファイルフォーマットから、メタデータやテキストを抽出することのできるライブラリ/Toolkitのようです。
The Apache Tika toolkit detects and extracts metadata and text from over a thousand different file types (such as PPT, XLS, and PDF). All of these file types can be parsed through a single interface, making Tika useful for search engine indexing, content analysis, translation, and much more.
https://tika.apache.org/
Apache Tikaがサポートしているファイルフォーマットは、こちら。
Apache Tika – Supported Document Formats
かなりたくさんのファイルフォーマットを、サポートしていますね。
使い方は、このあたりを参考にしてみました。
Apache Tika – The Parser interface
Apache Tika – Tika API Usage Examples
http://lab.bizreach.co.jp/949/
Apache Tika その2 Javaクラスからの使用 - 技術メモと時々猫
準備
まずは、Maven依存関係。
<dependency> <groupId>org.apache.tika</groupId> <artifactId>tika-parsers</artifactId> <version>1.10</version> </dependency>
大量の依存関係が追加されます…。
あとは、テスト用にJUnitとAssertJを。
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.2.0</version> <scope>test</scope> </dependency>
使ってみる
それでは、参考サイトなどを見ながら使ってみます。
以下のコードを雛形とします。使っているimport文なども、こちらを参照してください。
src/test/java/org/littlewings/tika/ParsePdfTest.java
package org.littlewings.tika; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URLConnection; import java.util.Arrays; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.TikaCoreProperties; import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.Parser; import org.apache.tika.parser.pdf.PDFParser; import org.apache.tika.sax.BodyContentHandler; import org.junit.Test; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import static org.assertj.core.api.Assertions.assertThat; public class ParsePdfTest { // ここに、テストを書く! }
で、解析するPDFファイルですが、とりあえずネット上で探してみました。
うちで使っているPCのマニュアルにしましょう。
dynabook TB87/PG Webオリジナルモデル PTB87PG-HHA
http://dynabook.com/pc/catalog/dynabook/manupdf/gx1c00177210.pdf
作成したコードは、こちら。
@Test public void testSimpleParsePdf() throws IOException, TikaException, SAXException { URLConnection conn = URI.create("http://dynabook.com/pc/catalog/dynabook/manupdf/gx1c00177210.pdf").toURL().openConnection(); try (InputStream is = new BufferedInputStream(conn.getInputStream())) { Parser parser = new PDFParser(); ContentHandler contentHandler = new BodyContentHandler(); Metadata metadata = new Metadata(); ParseContext context = new ParseContext(); parser.parse(is, contentHandler, metadata, context); assertThat(metadata.get(TikaCoreProperties.TITLE)) .isEqualTo("dynabookガイド"); assertThat(metadata.get("producer")) .isEqualTo("Adobe PDF Library 9.0"); assertThat(contentHandler.toString()) .contains("TB97/P*シリーズ、TB87/P*シリーズ、TB77/P*シリーズ、TB67/P*シリーズ、TB57/P*シリーズ"); } }
PDFを取得しているところは置いておいて、以下のようにParser、ContentHandler、Metadata、ParserContextを作成します。
Parser parser = new PDFParser(); ContentHandler contentHandler = new BodyContentHandler(); Metadata metadata = new Metadata(); ParseContext context = new ParseContext();
Parserは、まずはPDF用のものを直接作成しました。ContentHandler(SAXのものです)は、BodyContentHandlerを使用。
そして、パースしてみます。
parser.parse(is, contentHandler, metadata, context);
特にエラーにならなければ、ContentHandlerおよびMetadataから情報が取得できます。
Metadataから、タイトルやPDFを作成したソフトウェアの情報を取ってみます。
assertThat(metadata.get(TikaCoreProperties.TITLE)) .isEqualTo("dynabookガイド"); assertThat(metadata.get("producer")) .isEqualTo("Adobe PDF Library 9.0");
なお、タイトルは「title」としても同じ意味だったりします。
解析した結果、どのような名前のプロパティを文書が保持しているのかの一覧は、Metadata#namesで取得することができます。戻り値はStringの配列です。
文書そのものの内容は、今回はBodyContentHandler#toStringで取得します。
assertThat(contentHandler.toString())
.contains("TB97/P*シリーズ、TB87/P*シリーズ、TB77/P*シリーズ、TB67/P*シリーズ、TB57/P*シリーズ");
意外と簡単に使えました。
先ほどは、PDFParserを直接指定しましたが、自動判定させることもできるようです。
@Test public void testSimpleAutoDetectParsePdf() throws IOException, TikaException, SAXException { URLConnection conn = URI.create("http://dynabook.com/pc/catalog/dynabook/manupdf/gx1c00177210.pdf").toURL().openConnection(); try (InputStream is = new BufferedInputStream(conn.getInputStream())) { Parser parser = new AutoDetectParser(); ContentHandler contentHandler = new BodyContentHandler(); Metadata metadata = new Metadata(); ParseContext context = new ParseContext(); parser.parse(is, contentHandler, metadata, context); assertThat(metadata.get(TikaCoreProperties.TITLE)) .isEqualTo("dynabookガイド"); assertThat(metadata.get("producer")) .isEqualTo("Adobe PDF Library 9.0"); assertThat(contentHandler.toString()) .contains("TB97/P*シリーズ、TB87/P*シリーズ、TB77/P*シリーズ、TB67/P*シリーズ、TB57/P*シリーズ"); } }
AutoDetectParserを使えばよい、と。
Parser parser = new AutoDetectParser();
それ以外は、先ほどのコードと同じです。
あと、実はタイトルが入っているPDFを探すのになかなか困りまして、タイトルが取得できない場合は値がnullになります。
@Test public void testSimpleParsePdf2() throws IOException, TikaException, SAXException { URLConnection conn = URI.create("http://www.vmware.com/files/jp/pdf/vmware-cloud-computing-BR-JP.pdf").toURL().openConnection(); try (InputStream is = new BufferedInputStream(conn.getInputStream())) { Parser parser = new PDFParser(); ContentHandler contentHandler = new BodyContentHandler(); Metadata metadata = new Metadata(); ParseContext context = new ParseContext(); parser.parse(is, contentHandler, metadata, context); assertThat(metadata.get(TikaCoreProperties.TITLE)) .isNull(); assertThat(metadata.get("producer")) .isEqualTo("Adobe PDF Library 9.9"); assertThat(contentHandler.toString()) .contains("VMware と") .contains("クラウド コンピューティング"); } }
ここでは、VMwareのドキュメントを使用しました。
http://info.vmware.com/content/apac_jp_co_techresources
http://www.vmware.com/files/jp/pdf/vmware-cloud-computing-BR-JP.pdf
タイトルがnullです…。
assertThat(metadata.get(TikaCoreProperties.TITLE)) .isNull();
依存関係がちょっと巨大なのが気になりますが、使うのはけっこうあっさりで良いですね。