Indexer.PDF.,XLS,.DOC.,PPT utilisant Lucene.NET

j'ai entendu parler de Lucene.Net et J'ai entendu parler de Apache Tika . La question Est de savoir comment indexer ces documents en utilisant C# vs Java? Je pense que le problème est qu'il n'y a pas d'équivalent.net de Tika qui extrait le texte pertinent de ces types de documents.

mise à JOUR - Février 05 2011

sur la base des réponses données, il semble que la" native .Net équivalent de Tika. 2 projets intéressants ont été mentionnés qui sont chacun intéressants dans leur propre droit:

  1. Xapian Project ( http://xapian.org / ) - une alternative à Lucene écrite en code non géré. Le projet prétend soutenir "swig" qui permet des fixations C#. Dans le cadre du projet Xapian, il y a un moteur de recherche "out-of-the-box" appelé Omega. Omega utilise une variété de composants open source pour extraire le texte de divers types de documents.
  2. IKVM.NET ( http://www.ikvm.net / ) - permet D'exécuter Java à partir de .Net. Un exemple D'utilisation D'IKVM pour exécuter Tika peut être trouvé ici .

étant donné les 2 projets ci-dessus, je vois quelques options. Pour extraire le texte, je pourrais soit a) utiliser les mêmes composants Qu'Omega utilise ou b) utilisez IKVM pour exécuter Tika. Pour moi, l'option b) semble plus propre puisqu'il n'y a que 2 dépendances.

la partie intéressante est que maintenant il ya plusieurs moteurs de recherche qui pourraient probablement être utilisés à partir .Net. Il y a Xapian, Lucene.Net ou même Lucène (en utilisant IKVM).

mise à JOUR - Février 07 2011

une autre réponse est venue en recommandant que je vérifie ifilters. Comme il s'avère, c'est ce que MS utilise pour windows recherche Si les filtres de bureau sont facilement disponibles. Aussi, il y a quelques PDF ifilter. L'inconvénient est qu'ils sont mis en œuvre en code non géré, donc COM interop est nécessaire de les utiliser. J'ai trouvé l'extrait de code ci-dessous sur un DotLucene.NET archive (projet en cours):

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 + " nn");

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

                        }
                    }

                }
            }
            finally
            {
                if (filter != null) {
                    Marshal.ReleaseComObject (filter);
                    System.GC.Collect();
                    System.GC.WaitForPendingFinalizers();
                }
            }

            return sb.ToString();
        }
    }
}

à l'heure actuelle, cela semble être la meilleure façon d'extraire du texte à partir de documents en utilisant la plate-forme .NET sur un serveur Windows. Merci à tous pour votre aide.

UPDATE-Mar 08 2011

bien que je pense toujours que si les filtres sont une bonne façon d'aller, je pense que si vous cherchez à indexer les documents en utilisant Lucene de .NET, une très bonne alternative serait d'utiliser Solr . Lorsque j'ai commencé à faire des recherches sur ce sujet, je n'avais jamais entendu parler de Solr. Donc, pour ceux d'entre vous qui ne l'ont pas non plus fait, Solr est un service de recherche autonome, écrit en Java au-dessus de Lucene. L'idée est que vous pouvez démarrer Solr sur une machine pare-feu, et communiquer avec elle via HTTP à partir de votre application.Net. Solr est vraiment écrit comme un service et peut faire tout ce que Lucene peut faire, (y compris en utilisant le texte d'extrait de Tika .PDF. ,XLS,.DOC. ,PPT, etc), et puis certains. Le Solr semble aussi avoir une communauté très active, ce qui est une chose dont je ne suis pas sûr en ce qui concerne Lucene.NET.

33
demandé sur dana 2011-02-05 08:13:37

4 réponses

vous pouvez également consulter ifilters - Il ya un certain nombre de ressources si vous faites une recherche pour asp.net ifilters:

bien sûr, il y a des problèmes supplémentaires si vous distribuez ceci aux systèmes clients, parce que vous aurez besoin soit d'inclure les ifilters avec votre distribution et installer ceux avec votre application sur leur machine, ou ils manqueront la capacité d'extraire du texte à partir de n'importe quels fichiers qu'ils n'ont pas ifilters pour.

6
répondu Prescott 2017-05-23 12:24:43

C'est l'une des raisons pour lesquelles J'étais insatisfait de Lucene pour un projet sur lequel je travaillais. Xapian est un produit concurrent, et est des ordres de grandeur plus rapide que le Lucène dans certains cas et a d'autres caractéristiques convaincantes (bien, ils étaient convaincants pour moi à l'époque). La grande question? Il est écrit en C++ et vous devez interop à lui. C'est pour l'indexation et la recherche. Pour l'analyse réelle du texte, C'est là que Lucene tombe vraiment -- vous devez le faire vous-même. Xapian a un composant omega qui gère l'appel d'autres composants tiers pour extraire des données. Dans mes tests limités, ça a plutôt bien marché. Je n'ai pas terminé le projet (plus que POC) mais j'ai fait écrire mon expérience de compilation pour 64 bits. Bien sûr, ce fut presque un an, donc les choses ont peut être changé.

si vous creusez dans la documentation Omega vous pouvez voir les outils qu'ils utilisent pour analyser les documents.

PDF (.pdf) si pdftotext est disponible (fourni avec xpdf)

PostScript (.ps,.eps,.ai) si ps2pdf (de ghostscript) et pdftotext (livré avec xpdf) sont disponibles

OpenOffice / StarOffice documents (.sxc,.STC. ,sxd,.MST. ,sxi, .IST. ,sxm,.sxw,.sxg,.stw) si unzip est disponible

OpenDocument format documents (.odt, .ODS. ,odp,.odg, .l'odc, .odf, .pmo, .odi,.odm,.Ott. ,ots,.le bureau du procureur, .otg, .de gré à gré, .otf,.oti,.oth) si unzip est disponible

MS Word documents (.doc. ,dot) si antiword est disponible

MS Excel documents (.xls,.xlb,.xlt) si xls2csv est disponible (livré avec catdoc)

MS Powerpoint documents (.ppt,.pps) si catppt est disponible, (livré avec catdoc)

MS Office 2007 documents (.docx, .dotx,.xlsx,.xlst,.pptx,.potx, .ppsx) si unzip est disponible

les documents Wordperfect (.wpd) si wpd2text est disponible (fourni avec libwpd)

MS Works documents (.wps, .wpt) si wps2text est disponible (fourni avec libwps)

Compressed AbiWord documents (.zabw) si gzip est disponible

Rich Text Format documents (.rtf) si l'unrtf est disponible

Perl POD documentation (.pl, .pm,.pod) si pod2text est disponible

Tex DVI files (.dvi) si catdvi est disponible

DjVu files (.djv,.djvu) si djvutxt est disponible

XPS files (.xps) si unzip est disponible

4
répondu Sean 2011-02-05 06:57:41

apparemment, vous pouvez utiliser Tika de .net ( lien )

je n'ai pas essayé moi-même.

3
répondu Development 4.0 2011-02-05 05:59:25

autre angle ici est que les index de Lucene sont binaires compatibles entre java et .NET. Pour que tu puisses écrire L'index avec Tika et le lire avec C#.

2
répondu Wyatt Barnett 2011-03-08 14:15:36