検索エンジンを作る

第16回テキスト情報の抽出その3]

今回は、Windows環境のテキスト抽出方法の代表格といえるIFilterについてとりあげます。

IFilterの構造

現在、さまざまなWindowsのシステム上では、Windows Indexing Service, Windows Desktop Search, SharePoint Server 2003/2007, Microsoft Search Server 2008などのMicrosoftの全文検索エンジンが動作します。これらのソフトウェアでは、各ファイルからテキスト情報を抽出するためにIFilterと呼ぶフィルタを用いています。最近では、Microsoft SQL ServerのフルテキストインデックスもIFilterを利用しており、IFilterは多くのMicrosoft製品で利用されています。

また、Microsoft以外の多くのベンダの開発する検索エンジンや、文書管理ソフトウェアもIFilterを利用するものがあります。このように多くの製品で利用されている理由は、IFilterが比較的高い汎用性を持った設計になっているためといえます。

IFilterはファイル形式ごとに登録されたCOMのインターフェースを通じて、文書ファイルからテキスト情報を抽出します。たとえば、Microsoft Wordの.doc拡張子のファイルならば、WordのIFilterを通じてテキスト情報を抽出し、HTMLの拡張子のファイルならば、HTMLのIFilterを通じてテキスト情報を抽出するという具合です。

現在、IFilterは、Microsoftが自社製品の文書ファイル用のフィルタを配布、あるいはMicrosoft Officeなどの製品に同梱している他、多くのベンダが自社製品の文書ファイルのIFilterを提供しています。

IFilterを独自のファイル型式に対応させるには、そのファイル形式用のIFilterのCOM規約に従ったプログラムを開発し、システムに組み込むことで実現できます。独自のIFilterを開発する際の資料としては次のドキュメントがあります。

IFilterの種類

現在、WindowsやMicrosoft Officeに添付されるIFilter以外にも数多くのIFilterが利用できます。以下のダウンロードページからこのようなIFilterが入手できます。

  • MicrosoftのSharePointのIFilterダウンロードページ
    SharePoint製品のサポートページ内に設けられたIFilterのダウンロードページです。Visio 2003/2002、一太郎、Adobe PDF、DocuWorks 4.01のIFilterが紹介されています。

  • 一太郎 IFilter
    前述のMicrosoftのSharePointのIFilterダウンロードページからもこのリンクが参照されていますが、一太郎Ver.5, 6, 一太郎7, 8, 9, 10, 2007のファイル用のIFilterがダウンロードできます。

  • Adobe PDF IFilter
    Adobeの提供するPDF用のIFilter v6.0です。
    一方、Adobe LabsのページからはAcrobat 8に対応したAdobe PDF iFilter 8 - 64-bit support(プレリリース版)がダウンロードできます。

  • IFilterShop
    このサイトから、商用/非商用の沢山のIFilterがダウンロードできます。トップページには次のIFilterが紹介されています。

    • ASPX IFilter indexes ASPX files and associated Master Page files
    • CHM IFilter for Compiled HTML Help files
    • DWF IFilter for design documents in Autodesk Design Web Format
    • Inventor IFilter for Autodesk Inventor documents
    • MindManager IFilter for Mindjet MindManager documents
    • Msg IFilter for emails stored in MSG format
    • MS Project IFilter for Microsoft Project documents
    • PDF+ IFilter enhanced IFilter for Adobe PDF files
    • PostScript IFilter for PostScript files
    • RAR IFilter indexes content of RAR archives
    • SHTML IFilter indexes files in HTML format and their Server-Side Includes (SSI)
    • StarOffice/OpenOffice IFilter for StarOffice, OpenOffice and OASIS OpenDocument files
    • vCard IFilter for Electronic Business Card files
    • WMV/WMA IFilter for Windows Media Video and Windows Media Audio files
    • XMP IFilter indexes Adobe XMP metadata in JPEG, GIF, TIFF, PNG, PS, EPS, PSD, AI and SVG files
    • Zip IFilter searches content of ZIP archives

  • IFilter.Org
    こちらも多くのIFilterが紹介されています。以下のようなIFilterがあります。こちらのJPEG IFilterを使うとEXIFのメタ情報を取得できるそうです。

    • Archive Files (like CAB, ZIP, RAR or self-extracting EXE)
    • CHM Compiled HTML files
    • CSF Content Sealed Format
    • DjVu
    • Email
    • HLP Help Files
    • Image Files (digital photos, JPEG, etc.)
    • MHT MIME Encapsulation of Aggregate HTML Documents
    • Palm Desktop
    • PDF
    • RTF
    • Office Suites (Microsoft, WordPerfect, StarOffice, OpenOffice)
    • Visio

IFilterの関連ツール

IFilterの開発や、IFilterを運用に便利な2つのfiltreg.exe、filtdump.exeというコマンドが用意されています。この2つのコマンドはMicrosoft Plathome SDKのbinディレクトリや、Visual Studio 2005/2008のCommon7\Tools\Binディレクトリに格納されています。

filtreg.exe

このツールを実行すると、現在のシステムでのIFilterの登録状況が表示されます。Microsoft Office 2007がインストールされているWindows 2003 Server Standard Editionでの動作結果は次のようになります。

C:\> filtreg
.386 --> Null filter (query.dll)
.aif --> Null filter (query.dll)
... 以降 Null filterは省略 ...
.doc --> Microsoft Office Filter (OffFilt.dll)
.docm --> Office Open XML Format Word Filter (C:\PROGRA~1\COMMON~1\MICROS~1\Filters\offfiltx.dll)
.docx --> Office Open XML Format Word Filter (C:\PROGRA~1\COMMON~1\MICROS~1\Filters\offfiltx.dll)
.dot --> Microsoft Office Filter (OffFilt.dll)
.msg --> Office Outlook MSG IFilter (C:\PROGRA~1\COMMON~1\MICROS~1\Filters\msgfilt.dll)
.pdf --> PDF Filter (C:\Program Files\Adobe\Acrobat 8.0\Acrobat\AcroIF.dll)
.pot --> Microsoft Office Filter (OffFilt.dll)
.pps --> Microsoft Office Filter (OffFilt.dll)
.ppt --> Microsoft Office Filter (OffFilt.dll)
.pptm --> Office Open XML Format PowerPoint Filter (C:\PROGRA~1\COMMON~1\MICROS~1\Filters\offfiltx.dll)
.pptx --> Office Open XML Format PowerPoint Filter (C:\PROGRA~1\COMMON~1\MICROS~1\Filters\offfiltx.dll)
.tif --> MODI Document Filter class (C:\PROGRA~1\COMMON~1\MICROS~1\MODI\11.0\MSPFILT.DLL)
.tiff --> MODI Document Filter class (C:\PROGRA~1\COMMON~1\MICROS~1\MODI\11.0\MSPFILT.DLL)
.txt --> Plain Text filter (query.dll)
.xlb --> Microsoft Office Filter (OffFilt.dll)
.xlc --> Microsoft Office Filter (OffFilt.dll)
.xls --> Microsoft Office Filter (OffFilt.dll)
.xlsm --> Office Open XML Format Excel Filter (C:\PROGRA~1\COMMON~1\MICROS~1\Filters\offfiltx.dll)
.xlsx --> Office Open XML Format Excel Filter (C:\PROGRA~1\COMMON~1\MICROS~1\Filters\offfiltx.dll)
.xlt --> Microsoft Office Filter (OffFilt.dll)


Filters loaded by class:

	Filter: Plain Text filter (query.dll)

... 以降抜粋 ...

Adobe Acrobat Document
	Filter: PDF Filter (C:\Program Files\Adobe\Acrobat 8.0\Acrobat\AcroIF.dll)
	Extensions: .xfdf (AcroExch.XFDFDoc) .rmf (AcroExch.RMFFile) 
	Extensions: .fdf (AcroExch.FDFDoc) 

Microsoft Office OneNote Section
	Filter: Microsoft Office OneNote Filter (C:\PROGRA~1\MI69DF~1\Office12\ONFILTER.DLL)
	Extensions: .one (OneNote.Section.1) 

Microsoft Office Word 
	Filter: Microsoft Office Filter (OffFilt.dll)
	Extensions: .dotx (Word.Template.12) 

Microsoft Office PowerPoint 
	Filter: Microsoft Office Filter (OffFilt.dll)
	Extensions: .potx (PowerPoint.Template.12) 


	Filter: Plain Text filter (query.dll)
	Extensions: .wri (wrifile) 

filtreg.exeの出力を見れば、現在どの拡張子のファイルに対して、どのようなIFilterが設定されているか(あるいはいないか⁠⁠、が一目瞭然です。

filtdump.exe

filtdump.exeは、IFilterを呼び出してテキストを抽出するコマンドラインのプログラムです。システムに正しくIFIlterがセットされているかは、filtdump.exeを使うと容易に検証できます。

filtdump.exeには次のようなオプションがあります。

-b 詳細なテキスト以外の情報を出力しません
-o 出力ファイル名 出力先のファイル名を指定します。

実際の使い方は次のようになります。ここではWord 2007のdocx形式のファイルを指定しています。

C:\> filtdump -b -o C:\test\output.txt C:\test\sample.docx

ここで、UTF-16(リトルエンディアン)のUnicodeテキストファイルの形式で出力ファイルが生成されるので、少々注意が必要です。

IFilter経由でのテキスト情報の抽出

最後にIFilterを使って、テキスト情報を抽出するサンプルプログラムを紹介しましょう。 次のPreviewResults.csは、C#のプログラムからIFilterのインターフェースを呼び出して、文書ファイルからテキスト情報を抜き出すプログラムです。GetIFilterResults(string path_)というメソッドが、IFilterを通じてテキスト情報を抽出している箇所です。

このクラスはHTMLのレンダリング機能や検索したテキストの箇所を赤い色でハイライトする機能も持っており、検索結果をプレビューする機能としても利用できます。

リスト PreviewResults.cs
using System;
using System.Data;
using System.Text.RegularExpressions;
using System.Web.UI;

namespace Sample
{
  /// <summary>
  /// PreviewResults class exstructs the contents of a document with a proper IFilter.
  /// </summary>
  public class PreviewResults : Control
  {
    /// <summary>
    /// path of the document.
    /// </summary>
    private string path;
    /// <summary>
    /// text contents of the document.
    /// </summary>
    private string contents;
    /// <summary>
    /// search text
    /// </summary>
    private string search_text;
    /// <summary>
    /// set path of the document.
    /// </summary>
    /// <param name="x">
    /// path
    /// </param>
    void SetPath(string x)
    {
      path = x;
    }
    public void SetSearchText(string x)
    {
      search_text = x;
    }
    /// <summary>
    /// clear text contents.
    /// </summary>
    void ClearContents()
    {
      contents = "";
    }
    /// <summary>
    /// constructor.
    /// </summary>
    public PreviewResults()
    {
      path = new String(' ', 0);
      search_text = new String(' ', 0);
      ClearContents();
    }
    public bool GetIFilterResults(string path_)
    {
      path = path_;
      try
      {
        IFilter ifilt = (IFilter)(new CFilter());
        System.Runtime.InteropServices.UCOMIPersistFile ipf = 
          (System.Runtime.InteropServices.UCOMIPersistFile)(ifilt);
        ipf.Load(path, 0);
        uint i = 0;
        STAT_CHUNK ps = new STAT_CHUNK();
        ifilt.Init(0, 0, null, ref i);
        int hr = 0;                   
        while (hr == 0)
        {
          ifilt.GetChunk(out ps);
          if (ps.flags == CHUNKSTATE.CHUNK_TEXT)
          {
            uint pcwcBuffer = 1000;
            int hr2 = 0;
            while (hr2 == Constants.FILTER_S_LAST_TEXT || hr2 == 0)
            {
              pcwcBuffer = 1000;
              System.Text.StringBuilder sbBuffer = new System.Text.StringBuilder((int)pcwcBuffer);
              hr2 = ifilt.GetText(ref pcwcBuffer, sbBuffer);
              //Console.Write(sbBuffer.ToString(0, (int)pcwcBuffer));
              contents += sbBuffer.ToString(0, (int)pcwcBuffer);
            }
          }
        }
      }
      catch (System.Exception /*ex*/)
      {
        //Console.WriteLine(ex.Message);
        return false;
      }
      return true;
    }
    /// <summary>
    /// render the HTML contents.
    /// </summary>
    /// <param name="writer"></param>
    protected override void Render(HtmlTextWriter writer)
    {
      writer.Write("<h1>");
      writer.Write(path);
      writer.Write("</h1>");
      writer.Write("<table bgcolor=\"#e8e8e8\">");
      writer.Write("<td>");
      RenderContents(writer);
      writer.Write("</td>");
      writer.Write("</table>");
    }
    protected void RenderContents(HtmlTextWriter writer)
    {
      string s1 = new String(' ', 0);
      s1 = contents;
      // .NETの文字列メソッドが遅いので配列を使って回避します。
      int s1_len = s1.Length;
      char[] x = new char[s1_len * 6 + 1];  // MAXで の6倍の長さに展開されるため
      int xi = 0;
      int n;
      bool top_p = true;
      for (n = 0; n < s1_len; n++)
      {
        char c = s1[n];
        switch (c)
        {
          case '&':  // -> "&"
            x[xi++] = '&';
            x[xi++] = 'a';
            x[xi++] = 'm';
            x[xi++] = 'p';
            x[xi++] = ';';
            break;
          case '<':  // -> "<"
            x[xi++] = '&';
            x[xi++] = 'l';
            x[xi++] = 't';
            x[xi++] = ';';
            break;
          case '>':  // -> ">"
            x[xi++] = '&';
            x[xi++] = 'g';
            x[xi++] = 't';
            x[xi++] = ';';
            break;
          case '\r':
            break;
          case '\n':  // -> "<br>\n"
            top_p = true;
            x[xi++] = '<';
            x[xi++] = 'b';
            x[xi++] = 'r';
            x[xi++] = '>';
            x[xi++] = '\n';
            break;
          case '\t':
          case ' ':
            if (top_p)
            {
              // -> " "
              x[xi++] = '&';
              x[xi++] = 'n';
              x[xi++] = 'b';
              x[xi++] = 's';
              x[xi++] = 'p';
              x[xi++] = ';';
            }
            else
            {
              // -> " "
              x[xi++] = ' ';
            }
            break;
          default:
            top_p = false;
            x[xi++] = c;
            break;
        }
      }
      x[xi] = '\0';
      char[] y = new char[xi];
      for (n = 0; n < xi; n++)
      {
        y[n] = x[n];
      }
      string s2 = new String(y);
      if (search_text.Length > 0)
      {
        s2 = s2.Replace(search_text, "<span style=\"color:red; font-weight:bold\">" + search_text + "</span>");
      }
      writer.Write(s2);      
    }
  }
}

おすすめ記事

記事・ニュース一覧