簡體   English   中英

使用Lucene.NET為.PDF,.XLS,.DOC,.PPT編制索引

[英]Indexing .PDF, .XLS, .DOC, .PPT using Lucene.NET

我聽說過Lucene.Net ,也聽說過Apache Tika 問題是-如何使用C#和Java索引這些文檔? 我認為問題在於,沒有與Tika等效的.Net從這些文檔類型中提取相關文本。

更新-2011年2月5日

根據給定的響應,似乎目前不是Tika的本機 .Net等效項。 提到了兩個有趣的項目,每個項目本身都很有趣:

  1. Xapian項目http://xapian.org/)-用非托管代碼編寫的Lucene的替代方法。 該項目聲稱支持允許C#綁定的“ swig”。 Xapian項目中有一個開箱即用的搜索引擎,稱為Omega。 Omega使用各種開源組件從各種文檔類型中提取文本。
  2. IKVM.NEThttp://www.ikvm.net/ )-允許從.Net運行Java。 此處可以找到使用IKVM運行Tika的示例。

鑒於以上兩個項目,我看到了兩個選擇。 要提取文本,我可以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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM