今回は、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)
.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;
int s1_len = s1.Length;
char[] x = new char[s1_len * 6 + 1];
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);
}
}
}