CLOVER🍀

That was when it all began.

Apache TikaでPDFを読む

ちょっと、PDFファイルをJavaで読む必要にかられまして。

JavaでPDFを読むには、Apache Tikaを使用するのが良さそうだったので、こちらを試してみました。

Apache Tika – 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クラスからの使用 - 技術メモと時々猫

Apache Tika で "なんでもファイル展開" してみる。 - よしだのブログ

Content Extraction with Tika | Lucidworks

準備

まずは、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();

依存関係がちょっと巨大なのが気になりますが、使うのはけっこうあっさりで良いですね。