2011-02-05 28 views
33

He oído hablar de Lucene.Net y he oído hablar de Apache Tika. La pregunta es: ¿cómo indexo estos documentos usando C# frente a Java? Creo que el problema es que no existe un equivalente en .Net de Tika que extraiga el texto relevante de estos tipos de documentos.Indexando .PDF, .XLS, .DOC, .PPT usando Lucene.NET

ACTUALIZACIÓN - Feb 05 2011

Sobre la base de las respuestas dadas, parece que el no es actualmente un nativa .Net equivalente de Tika. 2 proyectos interesantes fueron mencionados que son cada uno interesante por derecho propio:

  1. Xapian Proyecto (http://xapian.org/) - Una alternativa a Lucene escrito en código no administrado. El proyecto afirma que es compatible con "swig", que permite enlaces C#. Dentro del Proyecto Xapian hay un motor de búsqueda llamado Omega. Omega usa una variedad de componentes de código abierto para extraer texto de varios tipos de documentos.
  2. IKVM.NET (http://www.ikvm.net/) - Permite que Java se ejecute desde .Net. Un ejemplo del uso de IKVM para ejecutar Tika se puede encontrar en here.

Dado los 2 proyectos anteriores, veo un par de opciones. Para extraer el texto, podría: a) usar los mismos componentes que usa Omega ob) usar IKVM para ejecutar Tika. Para mí, la opción b) parece más limpia ya que solo hay 2 dependencias.

La parte interesante es que ahora hay varios motores de búsqueda que probablemente podrían usarse desde .Net. Hay Xapian, Lucene.Net o incluso Lucene (usando IKVM).

ACTUALIZACIÓN - Feb 07 2011

Otra respuesta vino en la recomendación de que me la salida IFilters. Como resultado, esto es lo que usa MS para la búsqueda de Windows, por lo que los ifilters de Office están disponibles. Además, hay algunos ifilters PDF por ahí. La desventaja es que están implementados en código no administrado, por lo que la interoperabilidad COM es necesaria para usarlos. He encontrado el snippit continuación código en un archivo DotLucene.NET (ya no es un proyecto activo):

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

Por el momento, esto parece la mejor manera de extraer el texto de los documentos que utilizan la plataforma .NET en un equipo Windows servidor. Gracias a todos por su ayuda.

ACTUALIZACIÓN - marzo 08 2011

Aunque sigo pensando que IFilters son una buena manera de ir, creo que si usted está buscando para indexar documentos utilizando Lucene de .NET, una muy buena alternativa sería use Solr. Cuando comencé a investigar este tema, nunca había oído hablar de Solr. Entonces, para aquellos de ustedes que no tienen ninguno, Solr es un servicio de búsqueda independiente, escrito en Java sobre Lucene. La idea es que pueda arrancar Solr en una máquina cortafuegos y comunicarse con ella a través de HTTP desde su aplicación .NET. Solr está realmente escrito como un servicio y puede hacer todo lo que Lucene puede hacer (incluido el uso de texto extraído de Tika de .PDF, .XLS, .DOC, .PPT, etc.), y algo más. Solr parece tener una comunidad muy activa también, algo de lo que no estoy seguro con respecto a Lucene.NET.

Respuesta

6

También puede comprobar fuera de IFilters - hay una serie de recursos si usted hace una búsqueda de IFilters ASP.NET:

Por supuesto, existe una molestia adicional si está distribuyendo esto a los sistemas cliente, ya que tendrá que incluir los ifilters con su distribución e instalarlos con su aplicación en su máquina, o les faltará la capacidad de extraer texto de cualquier archivo para el que no tengan ifilters.

+0

No estoy distribuyendo esto a nadie. Sería para una aplicación que alojaría mi compañía. Esto parece interesante, ya que parece ser la tecnología subyacente para la búsqueda de Windows, para que sepa que MS admitirá los formatos de Office. El único inconveniente es que está utilizando interoperabilidad COM. – dana

+0

@dana Creo que el enlace del proyecto de código proporciona un contenedor. Creo que es EPocalipse.IFilter.dll – Prescott

3

Aparentemente se puede utilizar Tika de .NET (link)

No he probado esto por mí mismo.

+0

Interesante. Nunca escuché sobre IKVM antes, pero parece que podría funcionar. ¿Suponía que incluso podrías usar IKVM para ejecutar la versión Java de Lucene? En cualquier caso, gracias por el consejo :) – dana

4

Esta es una de las razones por las que no estaba satisfecho con Lucene para un proyecto en el que estaba trabajando. Xapian es un producto que compite, y es de una magnitud mucho mayor que Lucene en algunos casos y tiene otras características atractivas (bueno, fueron convincentes para mí en ese momento). ¿El gran problema? Está escrito en C++ y debes interoperar con él. Eso es para indexación y recuperación. Para el análisis real del texto, ahí es donde Lucene realmente se cae, tienes que hacerlo tú mismo. Xapian tiene un componente omega que gestiona llamar a otros componentes de terceros para extraer datos. En mis pruebas limitadas funcionó bastante bien. No terminé el proyecto (más que POC) pero hice write up mi experiencia compilando para 64 bit. Por supuesto, esto fue hace casi un año, así que las cosas podrían haber cambiado.

Si profundiza en el Omega documentation, puede ver las herramientas que utilizan para analizar documentos.

PDF (.pdf) si está disponible pdftotext (viene con xpdf)

PostScript (.ps, .eps, .ai) si ps2pdf (de Ghostscript) y pdftotext (viene con xpdf) están disponibles

OpenOffice documentos/StarOffice (.sxc, .stc, .sxd, .std, .sxi, .sti, .sxm, .sxw, .sxg, .stw) si está disponible descomprimir

documentos en formato OpenDocument (.odt, .ods, .odp, .odg, .odc, .odf, .odb, .odi, .odm, .ott, .ots, .otp, .otg, .otc, .otf, .oti,. oth) si descomprimir está disponible

documentos de MS Word (.doc, .dot) si se dispone de antiword

documentos de MS Excel (.xls, .xlb, .xlt) si está disponible xls2csv (viene con catdoc)

MS Powerpoint documentos (.ppt, .pps) si catppt está disponible, (viene con catdoc)

documentos de MS Office 2007 (.docx, .dotx, .xlsx, .xlst, .pptx, .potx, .ppsx) si descomprime está disponible

documentos de Wordperfect (.wpd) si wpd2text está disponible (viene con libwpd)

documentos de MS Works (.wps, .wpt) si está disponible wps2text (viene con libwps)

comprimido documentos AbiWord (.zabw) si está disponible gzip

ricos documentos con formato de texto enriquecido (.rtf) si está disponible UnRTF

documentación de Perl POD (.pl, .pm, .pod) si está disponible pod2text

archivos TeX DVI (.dvi) si está disponible catdvi

archivos DjVu (.djv, .djvu) si djvutxt está disponible

Archivos XPS (.XPS), si está disponible descomprimir

+0

excelente reseña. Parece que usan algo llamado "swig" para generar el enlace C#. ¿Es eso nuevo desde que hiciste tu reseña o es eso lo que usaste? Además, ¿tiene que descargar las herramientas de procesamiento de documentos por separado (como "antiword") o vienen con Xapian? – dana

+0

@dana Los archivos de construcción y enlaces de win32 están aquí: http://www.flax.co.uk/xapian_binaries. Estaban usando swig para Java cuando lo miré, pero no me metí con eso. Charlie Hull es un buen tipo y está aquí si te encuentras con algún problema. Puede descargar esas herramientas por separado, todas son de código abierto y usarlas con Lucene (que es con lo que estaba experimentando cuando se sacó mi proyecto). Por lo tanto, podría usar antiword para extraer los datos de un archivo .doc (desgranando un proceso en C#) y luego incluirlos en Lucene. – Sean

2

Otro ángulo aquí es que los índices de Lucene son binarios compatibles entre java y .NET. Entonces podrías escribir el índice con Tika y leerlo con C#.

Cuestiones relacionadas