2011-02-05 13 views
33

Ho sentito parlare di Lucene.Net e ho sentito parlare di Apache Tika. La domanda è: come indicizzare questi documenti utilizzando C# rispetto a Java? Penso che il problema è che non esiste un equivalente Netto di Tika che estrae il testo pertinente da questi tipi di documenti.Indicizzazione .PDF, .XLS, .DOC, .PPT utilizzando Lucene.NET

AGGIORNAMENTO - Feb 05 2011

Sulla base di dati delle risposte, sembra che il non è attualmente un nativo .Net equivalente di Tika. 2 progetti interessanti sono stati citati che sono ogni interessanti di per sé:

  1. Xapian Progetto (http://xapian.org/) - Un'alternativa al Lucene scritto in codice non gestito. Il progetto sostiene di supportare "swig" che consente le associazioni C#. All'interno del progetto Xapian c'è un motore di ricerca pronto all'uso chiamato Omega. Omega utilizza una varietà di componenti open source per estrarre il testo da vari tipi di documenti.
  2. IKVM.NET (http://www.ikvm.net/) - Consente a Java di essere eseguito da .Net. Un esempio di utilizzo di IKVM per eseguire Tika può essere trovato here.

Visto i 2 progetti precedenti, vedo un paio di opzioni. Per estrarre il testo, potrei a) utilizzare gli stessi componenti utilizzati da Omega o b) utilizzare IKVM per eseguire Tika. Per me, l'opzione b) sembra più pulita in quanto ci sono solo 2 dipendenze.

La parte interessante è che ora ci sono diversi motori di ricerca che potrebbero probabilmente essere utilizzati da .Net. C'è Xapian, Lucene.Net o persino Lucene (usando IKVM).

AGGIORNAMENTO - Feb 07 2011

Un'altra risposta è arrivata nel raccomandare che ho check out IFilters. A quanto pare, questo è ciò che MS usa per la ricerca di Windows in modo che i ifilters di Office siano prontamente disponibili. Inoltre, ci sono alcuni ifilters PDF là fuori. Lo svantaggio è che sono implementati in codice non gestito, quindi l'interoperabilità COM è necessaria per utilizzarli. Ho trovato lo snippit sotto codice su un archivio DotLucene.NET (non è più un progetto attivo):

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(); 
     } 
    } 
} 

Al momento, questo mi sembra il modo migliore per estrarre il testo da documenti che utilizzano la piattaforma .NET su un computer Windows server. Grazie a tutti per il vostro aiuto.

AGGIORNAMENTO - giu 08 2011

Mentre io continuo a pensare che IFilters sono un buon modo per andare, penso che se si sta cercando di indicizzare i documenti utilizzando Lucene da NET, una buona alternativa sarebbe quella di utilizzare Solr. Quando ho iniziato a ricercare questo argomento, non avevo mai sentito parlare di Solr. Quindi, per quelli di voi che non hanno nemmeno, Solr è un servizio di ricerca indipendente, scritto in Java su Lucene. L'idea è che puoi avviare Solr su una macchina con firewall e comunicare con essa via HTTP dalla tua applicazione .NET. Solr è veramente scritto come un servizio e può fare tutto ciò che Lucene può fare, (incluso l'uso del testo estratto di Tika da .PDF, .XLS, .DOC, .PPT, ecc.), E poi alcuni. Solr sembra avere anche una comunità molto attiva, che è una cosa di cui non sono sicuro riguardo a Lucene.NET.

risposta

6

È possibile anche controllare IFilter - ci sono una serie di risorse, se si esegue una ricerca per IFilters ASP.NET:

Ovviamente, è necessario aggiungere problemi se si distribuisce questo ai sistemi client, perché sarà necessario includere i ifilters con la propria distribuzione e installare quelli con l'app sul proprio computer, oppure non avranno la possibilità di estrarre il testo da qualsiasi file per il quale non sono presenti ifilters.

+0

Non lo sto distribuendo a nessuno. Sarebbe per un'applicazione che la mia azienda avrebbe ospitato. Questo sembra interessante in quanto sembra essere la tecnologia di base per la ricerca di Windows, quindi sai che MS supporterà i formati di Office. L'unico svantaggio è che stai usando l'interoperabilità COM. – dana

+0

@dana Credo che il collegamento al codice del progetto fornisca un wrapper. Penso che sia EPocalipse.IFilter.dll – Prescott

3

A quanto pare è possibile utilizzare Tika da .net (link)

Non ho provato io stesso.

+0

Interessante. Non ho mai sentito parlare di IKVM prima, ma sembra che potrebbe funzionare. Suppongo che potresti persino usare IKVM per eseguire la versione Java di Lucene? In ogni caso, grazie per il suggerimento :) – dana

4

Questo è uno dei motivi per cui sono rimasto insoddisfatto di Lucene per un progetto su cui stavo lavorando. Xapian è un prodotto concorrente, e in alcuni casi è di ordine di grandezza più veloce di Lucene e ha altre caratteristiche interessanti (beh, a quel tempo erano irresistibili per me). Il grande problema? È scritto in C + + e devi farvi riferimento. Questo è per l'indicizzazione e il recupero. Per l'attuale analisi del testo, è lì che Lucene cade davvero - devi farlo da solo. Xapian ha un componente omega che gestisce chiamando altri componenti di terze parti per estrarre i dati. Nei miei test limitati ha funzionato abbastanza bene. Non ho terminato il progetto (più di POC) ma ho fatto il write up la mia esperienza la compila per 64 bit. Ovviamente questo era quasi un anno fa, quindi le cose potrebbero essere cambiate.

Se si scava nel Omega documentation è possibile visualizzare gli strumenti che utilizzano per analizzare i documenti.

PDF (.pdf) se pdftotext è disponibile (viene fornito con xpdf)

PostScript (.ps, .eps, .ai) se ps2pdf (da Ghostscript) e pdftotext (viene fornito con xpdf) sono disponibili

OpenOffice documenti/StarOffice (.sxc, .stc, .sxd, .std, .sxi, .sti, .sxm, .sxw, .sxg, .stw) se Unzip è disponibile

documenti in formato OpenDocument (.odt, .ods, .odp, .odg, .odc, .odf, .odb, .odi, .odm, .ott, .ot, .otp, .otg, .otc, .otf, .oti,. oth) se unzip è disponibile

documenti MS Word (doc, dot) se antiword è disponibile

documenti MS Excel (.xls, .xlb, .xlt) se xls2csv è disponibile (viene fornito con catdoc)

MS Powerpoint documenti (.ppt, .pps) se catppt è disponibile, (fornito con catdoc)

Documenti di MS Office 2007 (.docx, .dotx, .xlsx, .xlst, .pptx, .potx, .ppsx) se unzip è disponibile

Documenti Wordperfect (.wpd) se wpd2text è disponibile (fornito con libwpd)

documenti MS Works (wps, .wpt) se wps2text è disponibile (viene fornito con libwps)

documenti AbiWord compressa (.zabw) se gzip è disponibile

Rich Text Format documenti (.rtf) se unrtf è disponibile

documentazione Perl POD (.pl, .pm, .pod) se pod2text è disponibile

file TeX DVI (dvi) se catdvi è disponibile

File DjVu (.djv, .djvu) se djvutxt è disponibile

File XPS (.XPS) se Unzip è disponibile

+0

ottimo write-up. Sembra che usino qualcosa chiamato "swig" per generare l'associazione C#. È nuovo visto che hai scritto il tuo articolo o è quello che hai usato? Inoltre, devi scaricare separatamente gli strumenti di elaborazione dei documenti (come "antiword") o vengono con Xapian? – dana

+0

@dana I collegamenti win32 e i file di build sono qui: http://www.flax.co.uk/xapian_binaries. Stavano usando lo swig per Java quando l'ho guardato, ma non ho combinato niente. Charlie Hull è un bravo ragazzo ed è qui se ti trovi nei guai. Puoi semplicemente scaricare questi strumenti separatamente - sono tutti open source e li usano con Lucene (che è quello che stavo sperimentando quando il mio progetto è stato tirato). Quindi è possibile utilizzare antiword per estrarre i dati da un file .doc (eseguendo il bombardamento di un processo in C#) e quindi inserendolo in Lucene. – Sean

2

Un altro angolo qui è che gli indici di Lucene sono binari compatibili tra java e .NET. Quindi potresti scrivere l'indice con Tika e leggerlo con C#.

Problemi correlati