[英]Indexing .PDF, .XLS, .DOC, .PPT using Lucene.NET
我听说过Lucene.Net ,也听说过Apache Tika 。 问题是-如何使用C#和Java索引这些文档? 我认为问题在于,没有与Tika等效的.Net从这些文档类型中提取相关文本。
更新-2011年2月5日
根据给定的响应,似乎目前不是Tika的本机 .Net等效项。 提到了两个有趣的项目,每个项目本身都很有趣:
鉴于以上两个项目,我看到了两个选择。 要提取文本,我可以a)使用Omega使用的相同组件,或者b)使用IKVM运行Tika。 对我来说,选项b)看起来更干净,因为只有2个依赖项。
有趣的是,.Net现在可能会使用几个搜索引擎。 有Xapian,Lucene.Net甚至Lucene(使用IKVM)。
更新-2011年2月7日
另一个答案是建议我检查ifilters。 事实证明,这就是MS用于Windows搜索的用途,因此Office ifilter随时可用。 此外,那里还有一些PDF ifilter。 缺点是它们是在非托管代码中实现的,因此使用COM互操作是必需的。 我在DotLucene.NET归档文件(不再是活动项目)中找到了以下代码片段:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace IFilter
{
[Flags]
public enum IFILTER_INIT : uint
{
NONE = 0,
CANON_PARAGRAPHS = 1,
HARD_LINE_BREAKS = 2,
CANON_HYPHENS = 4,
CANON_SPACES = 8,
APPLY_INDEX_ATTRIBUTES = 16,
APPLY_CRAWL_ATTRIBUTES = 256,
APPLY_OTHER_ATTRIBUTES = 32,
INDEXING_ONLY = 64,
SEARCH_LINKS = 128,
FILTER_OWNED_VALUE_OK = 512
}
public enum CHUNK_BREAKTYPE
{
CHUNK_NO_BREAK = 0,
CHUNK_EOW = 1,
CHUNK_EOS = 2,
CHUNK_EOP = 3,
CHUNK_EOC = 4
}
[Flags]
public enum CHUNKSTATE
{
CHUNK_TEXT = 0x1,
CHUNK_VALUE = 0x2,
CHUNK_FILTER_OWNED_VALUE = 0x4
}
[StructLayout(LayoutKind.Sequential)]
public struct PROPSPEC
{
public uint ulKind;
public uint propid;
public IntPtr lpwstr;
}
[StructLayout(LayoutKind.Sequential)]
public struct FULLPROPSPEC
{
public Guid guidPropSet;
public PROPSPEC psProperty;
}
[StructLayout(LayoutKind.Sequential)]
public struct STAT_CHUNK
{
public uint idChunk;
[MarshalAs(UnmanagedType.U4)] public CHUNK_BREAKTYPE breakType;
[MarshalAs(UnmanagedType.U4)] public CHUNKSTATE flags;
public uint locale;
[MarshalAs(UnmanagedType.Struct)] public FULLPROPSPEC attribute;
public uint idChunkSource;
public uint cwcStartSource;
public uint cwcLenSource;
}
[StructLayout(LayoutKind.Sequential)]
public struct FILTERREGION
{
public uint idChunk;
public uint cwcStart;
public uint cwcExtent;
}
[ComImport]
[Guid("89BCB740-6119-101A-BCB7-00DD010655AF")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFilter
{
[PreserveSig]
int Init([MarshalAs(UnmanagedType.U4)] IFILTER_INIT grfFlags, uint cAttributes, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] FULLPROPSPEC[] aAttributes, ref uint pdwFlags);
[PreserveSig]
int GetChunk(out STAT_CHUNK pStat);
[PreserveSig]
int GetText(ref uint pcwcBuffer, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder buffer);
void GetValue(ref UIntPtr ppPropValue);
void BindRegion([MarshalAs(UnmanagedType.Struct)] FILTERREGION origPos, ref Guid riid, ref UIntPtr ppunk);
}
[ComImport]
[Guid("f07f3920-7b8c-11cf-9be8-00aa004b9986")]
public class CFilter
{
}
public class IFilterConstants
{
public const uint PID_STG_DIRECTORY = 0x00000002;
public const uint PID_STG_CLASSID = 0x00000003;
public const uint PID_STG_STORAGETYPE = 0x00000004;
public const uint PID_STG_VOLUME_ID = 0x00000005;
public const uint PID_STG_PARENT_WORKID = 0x00000006;
public const uint PID_STG_SECONDARYSTORE = 0x00000007;
public const uint PID_STG_FILEINDEX = 0x00000008;
public const uint PID_STG_LASTCHANGEUSN = 0x00000009;
public const uint PID_STG_NAME = 0x0000000a;
public const uint PID_STG_PATH = 0x0000000b;
public const uint PID_STG_SIZE = 0x0000000c;
public const uint PID_STG_ATTRIBUTES = 0x0000000d;
public const uint PID_STG_WRITETIME = 0x0000000e;
public const uint PID_STG_CREATETIME = 0x0000000f;
public const uint PID_STG_ACCESSTIME = 0x00000010;
public const uint PID_STG_CHANGETIME = 0x00000011;
public const uint PID_STG_CONTENTS = 0x00000013;
public const uint PID_STG_SHORTNAME = 0x00000014;
public const int FILTER_E_END_OF_CHUNKS = (unchecked((int) 0x80041700));
public const int FILTER_E_NO_MORE_TEXT = (unchecked((int) 0x80041701));
public const int FILTER_E_NO_MORE_VALUES = (unchecked((int) 0x80041702));
public const int FILTER_E_NO_TEXT = (unchecked((int) 0x80041705));
public const int FILTER_E_NO_VALUES = (unchecked((int) 0x80041706));
public const int FILTER_S_LAST_TEXT = (unchecked((int) 0x00041709));
}
///
/// IFilter return codes
///
public enum IFilterReturnCodes : uint
{
///
/// Success
///
S_OK = 0,
///
/// The function was denied access to the filter file.
///
E_ACCESSDENIED = 0x80070005,
///
/// The function encountered an invalid handle, probably due to a low-memory situation.
///
E_HANDLE = 0x80070006,
///
/// The function received an invalid parameter.
///
E_INVALIDARG = 0x80070057,
///
/// Out of memory
///
E_OUTOFMEMORY = 0x8007000E,
///
/// Not implemented
///
E_NOTIMPL = 0x80004001,
///
/// Unknown error
///
E_FAIL = 0x80000008,
///
/// File not filtered due to password protection
///
FILTER_E_PASSWORD = 0x8004170B,
///
/// The document format is not recognised by the filter
///
FILTER_E_UNKNOWNFORMAT = 0x8004170C,
///
/// No text in current chunk
///
FILTER_E_NO_TEXT = 0x80041705,
///
/// No more chunks of text available in object
///
FILTER_E_END_OF_CHUNKS = 0x80041700,
///
/// No more text available in chunk
///
FILTER_E_NO_MORE_TEXT = 0x80041701,
///
/// No more property values available in chunk
///
FILTER_E_NO_MORE_VALUES = 0x80041702,
///
/// Unable to access object
///
FILTER_E_ACCESS = 0x80041703,
///
/// Moniker doesn't cover entire region
///
FILTER_W_MONIKER_CLIPPED = 0x00041704,
///
/// Unable to bind IFilter for embedded object
///
FILTER_E_EMBEDDING_UNAVAILABLE = 0x80041707,
///
/// Unable to bind IFilter for linked object
///
FILTER_E_LINK_UNAVAILABLE = 0x80041708,
///
/// This is the last text in the current chunk
///
FILTER_S_LAST_TEXT = 0x00041709,
///
/// This is the last value in the current chunk
///
FILTER_S_LAST_VALUES = 0x0004170A
}
///
/// Convenience class which provides static methods to extract text from files using installed IFilters
///
public class DefaultParser
{
public DefaultParser()
{
}
[DllImport("query.dll", CharSet = CharSet.Unicode)]
private extern static int LoadIFilter(string pwcsPath, [MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter, ref IFilter ppIUnk);
private static IFilter loadIFilter(string filename)
{
object outer = null;
IFilter filter = null;
// Try to load the corresponding IFilter
int resultLoad = LoadIFilter(filename, outer, ref filter);
if (resultLoad != (int) IFilterReturnCodes.S_OK)
{
return null;
}
return filter;
}
public static bool IsParseable(string filename)
{
return loadIFilter(filename) != null;
}
public static string Extract(string path)
{
StringBuilder sb = new StringBuilder();
IFilter filter = null;
try
{
filter = loadIFilter(path);
if (filter == null)
return String.Empty;
uint i = 0;
STAT_CHUNK ps = new STAT_CHUNK();
IFILTER_INIT iflags =
IFILTER_INIT.CANON_HYPHENS |
IFILTER_INIT.CANON_PARAGRAPHS |
IFILTER_INIT.CANON_SPACES |
IFILTER_INIT.APPLY_CRAWL_ATTRIBUTES |
IFILTER_INIT.APPLY_INDEX_ATTRIBUTES |
IFILTER_INIT.APPLY_OTHER_ATTRIBUTES |
IFILTER_INIT.HARD_LINE_BREAKS |
IFILTER_INIT.SEARCH_LINKS |
IFILTER_INIT.FILTER_OWNED_VALUE_OK;
if (filter.Init(iflags, 0, null, ref i) != (int) IFilterReturnCodes.S_OK)
throw new Exception("Problem initializing an IFilter for:\n" + path + " \n\n");
while (filter.GetChunk(out ps) == (int) (IFilterReturnCodes.S_OK))
{
if (ps.flags == CHUNKSTATE.CHUNK_TEXT)
{
IFilterReturnCodes scode = 0;
while (scode == IFilterReturnCodes.S_OK || scode == IFilterReturnCodes.FILTER_S_LAST_TEXT)
{
uint pcwcBuffer = 65536;
System.Text.StringBuilder sbBuffer = new System.Text.StringBuilder((int)pcwcBuffer);
scode = (IFilterReturnCodes) filter.GetText(ref pcwcBuffer, sbBuffer);
if (pcwcBuffer > 0 && sbBuffer.Length > 0)
{
if (sbBuffer.Length < pcwcBuffer) // Should never happen, but it happens !
pcwcBuffer = (uint)sbBuffer.Length;
sb.Append(sbBuffer.ToString(0, (int) pcwcBuffer));
sb.Append(" "); // "\r\n"
}
}
}
}
}
finally
{
if (filter != null) {
Marshal.ReleaseComObject (filter);
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
}
return sb.ToString();
}
}
}
目前,这似乎是使用Windows服务器上的.NET平台从文档中提取文本的最佳方法。 谢谢大家的帮助。
更新-2011年3月8日
虽然我仍然认为ifilters是一个不错的选择,但我认为如果您希望使用.NET中的Lucene为文档建立索引,那么一个很好的替代方法是使用Solr 。 当我第一次开始研究这个主题时,我从未听说过Solr。 因此,对于没有一个人的人,Solr是一个独立的搜索服务,在Lucene之上用Java编写。 这个想法是,您可以在受防火墙保护的计算机上启动Solr,并通过.NET应用程序中的HTTP通过它进行通信。 Solr确实像服务一样编写,并且可以完成Lucene可以做的所有事情(包括使用Tika从.PDF,.XLS,.DOC,.PPT等中提取文本),然后再做一些。 Solr似乎也有一个非常活跃的社区,对于Lucene.NET,这是我不确定的一件事。
您还可以签出ifilters-如果您搜索asp.net ifilters,则有很多资源:
当然,如果将其分发到客户端系统,则会增加麻烦,因为您将需要在分发中包括ifilter并将其安装在其计算机上,否则它们将无法从任何文件中提取文本他们没有ifilters。
这是我对Lucene所做的项目不满意的原因之一。 Xapian是一种竞争产品,在某些情况下比Lucene快几个数量级,并具有其他引人注目的功能(嗯,当时它们对我来说是引人注目的)。 大问题? 它是用C ++编写的,您必须对其进行互操作。 那是用于索引和检索。 对于文本的实际解析,这是Lucene真正失败的地方-您必须自己做。 Xapian具有一个omega组件,该组件负责管理调用其他第三方组件以提取数据。 在我有限的测试中,它工作得很好。 我没有完成项目(比POC还多),但是我确实写下了将其编译为64位的经验。 当然,这已经快一年了,所以情况可能已经改变。
如果深入研究Omega文档 ,则可以看到它们用于解析文档的工具。
PDF(.pdf),如果pdftotext可用(xpdf附带)
如果ps2pdf(来自ghostscript)和pdftotext(xpdf附带)可用,则为PostScript(.ps,.eps,.ai)
OpenOffice / StarOffice文件(.sxc,.stc,.sxd,.std,.sxi,.sti,.sxm,.sxw,.sxg,.stw)
OpenDocument格式的文档(.odt,.ods,.odp,.odg,.odc,.odf,.odb,.odi,.odm,.ott,.ots,.otp,.otg,.otc,.otf 、. oti,.oth)(如果可用)
MS Word文档(.doc,.dot)(如果可用)
如果xls2csv可用,则MS Excel文档(.xls,.xlb,.xlt)(catdoc附带)
MS Powerpoint文档(.ppt,.pps)(如果有catppt可用)(catdoc附带)
如果解压缩可用,则MS Office 2007文档(.docx,.dotx,.xlsx,.xlst,.pptx,.potx,.ppsx)
如果有wpd2text可用,则为Wordperfect文档(.wpd)(libwpd附带)
如果wps2text可用,则MS Works文档(.wps,.wpt)(libwps附带)
如果有gzip,则压缩的AbiWord文档(.zabw)
RTF可用的RTF格式文档(.rtf)
如果可以使用pod2text,则可以使用Perl POD文档(.pl,.pm,.pod)
TeX DVI文件(.dvi)(如果有catdvi可用)
DjVu文件(.djv,.djvu)(如果djvutxt可用)
XPS文件(.xps)(如果存在解压缩)
显然,您可以从.net使用Tika( 链接 )
我自己还没有尝试过。
这里的另一个角度是Lucene索引在Java和.NET之间是二进制兼容的。 因此,您可以使用Tika编写索引并使用C#读取索引。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.