Реализация полнотекстового поиска с SoftInform Search SDK
Мы с вами уже достаточно много и подробно говорили об использовании такого программного продукта, как SoftInform Search SDK, при создании приложений для Windows, в которых должны присутствовать возможности полнотекстового поиска. Сегодня продолжим (а когда-нибудь, может, даже и сейчас, завершим) этот долгий, но от этого не менее интересный разговор.
Казалось бы, мы поговорили обо всем, о чем только возможно. Простые поисковые запросы? Обсудили в первую очередь. Сложные поисковые запросы? Да сразу же после них. Работу с поисковыми индексами? Да тоже про неё вполне подробно было, даже узнали, что их можно дефрагментировать. Что же еще можно рассказать про SoftInform Search SDK? Например, до сих пор мы не говорили о работе с источниками данных. Оказывается, SoftInform Search SDK позволяет реализовывать собственные источники в виде COM-объектов, которые будут использоваться поисковым движком при получении информации для поиска.
На самом деле тема работы с источниками документов в SoftInform Search SDK достаточно обширная и не сказать чтобы совсем уж простая, поэтому некоторые детали придется опустить. Но, думаю, благодаря примерам, идущим в комплекте поставки SDK, вы сможете разобраться с теми вопросами, на которые не удастся пролить свет в этой статье.
Самый важный интерфейс, с которым мы в данном случае будем иметь дело, называется ISIDocumentSource. Помимо этого, нам также понадобятся интерфейсы ISIDocumentSourceClass, ISIDocumentsItterator и ISIDocument. Думаю, что их предназначение, по большей части, ясно из названий. Тем не менее, уточнить, что именно скрывается за каждым из них, хотя бы в двух словах, думаю, будет не лишним. Первый из них - это, собственно говоря, и есть сам источник документов, с которым будет работать поисковый движок. Второй по списку (ISIDocumentSourceClass) - это интерфейс, описывающий тип документов, входящих в состав нашего источника документов. Интерфейс ISIDocumentsItterator нужен для работы с документами в цикле. Думаю, если вы работали с современными языками программирования или библиотеками, в которых используются итераторы (как, например, в стандартной библиотеке Java), то данный интерфейс и реализующие его классы будут абсолютно понятны и прозрачны для вас в логике своей работы. Последний по счету интерфейс, ISIDocument, описывает конкретный документ, поставляемый реализуемым нами источником документов.
Конечно, о том, какие методы должны реализовываться в классах на основе данных интерфейсов, можно почитать и в справке, посвященной SoftInform Search SDK. Но куда полезнее будет посмотреть на код, описывающий хотя бы интерфейсную часть этих классов. Первый из них вы можете увидеть в листинге 1.
Листинг 1
TSampleDocumentSource = class(TAutoIntfObject, ISIDocumentSource) private // Какие-то приватные поля, нужные для внутренней логики класса protected // ISIDocumentSource function Get_Version: WideString; safecall; function Get_PartialUpdateAllowed: WordBool; safecall; function AllDocuments: ISIDocumentsItterator; safecall; function ModifiedDocuments(const ASinceVersion: WideString): ISIDocumentsItterator; safecall; function DocumentByID(const ADocumentID: WideString): ISIDocument; safecall; public constructor Create(const AParameters: WideString); end;
Как вы, может быть, знаете, класс TAutoIntfObject, который является базовым предлагаемым в первом листинге классом, обеспечивает поддержку интерфейса IDispatch вместе с библиотекой типов, так что нам не надо благодаря этому вовсе волноваться по поводу поддержки IDispatch. Второй интерфейс, который имплементирует наш класс, как раз тот самый интерфейс из SoftInform Search SDK, который дает возможность взаимодействовать с поисковым движком. Обратите внимание на то, что многие методы, которые объявлены в данном классе, должны реализовываться с использованием safecall-вызовов. Сложно сказать, почему программисты SoftInform реализовали все именно так, но нужно придерживаться этого соглашения, чтобы наши расширения работали так, как надо. Что касается реализуемых данных классом функций, то, думаю, все их названия вполне четко характеризуют суть их работы.
Что ж, с тем, как выглядит интерфейсная секция класса, реализующего интерфейс ISIDocumentSource, мы познакомились. Теперь давайте двигаться в сторону следующего интерфейса - ISIDocumentSourceClass. Интерфейсную секцию класса, реализующего этот интерфейс, вы можете увидеть во втором листинге.
Листинг 2
TDSSampleClass = class(TComObject, ISIDocumentSourceClass) protected // ISIDocumentSourceClass function Get_Caption: WideString; safecall; function Get_DocumentSourceClassID: WideString; safecall; function OpenDocumentSource(const AParameters: WideString): ISIDocumentSource; safecall; function OpenDocumentSourceFromFile(const AFileName: WideString): ISIDocumentSource; safecall; function Get_Configurable: WordBool; safecall; function AcceptsExtention(const AExtentionInUpperWithoutDot: WideString): WordBool; safecall; function Configure(hWnd: LongWord; var AParameters: WideString; var ACaption: WideString; var ADescription: WideString; const ATranslate: ISITranslate): WordBool; safecall; end;
Кстати говоря, хочу обратить ваше внимание на то, что все строковые параметры, которые передаются этим методам, имеют тип WideString - то есть, содержат в себе Unicode-строки. Благодаря этому SoftInform Search SDK, собственно говоря, и позволяет осуществлять поиск и по русскоязычным, и по англоязычным, и по любым другим документам. Заметьте, что здесь уже базовым классом является TComObject. Таким образом, наш класс является COM-объектом, имплементирующим, помимо указанного интерфейса из рассматриваемого нами поискового SDK, интерфейсы IUnknown и ISupportErrorInfo.
Теперь давайте взглянем на класс, который реализует интерфейс ISIDocumentsItterator. Его вы можете увидеть в листинге 3, который, в отличие от двух предыдущих, включает в себя и приватные поля данного класса, реализованные в примере. Они нужны здесь для того, чтобы можно было лучше уяснить себе смысл параметров, передаваемых конструктору данного класса.
Листинг 3
TSampleDocumentItterator = class(TAutoIntfObject, ISIDocumentsItterator) private FItem: Integer; FNow: TDateTime; FCount: Integer; FType: Integer; protected // ISIDocumentsItterator function Get_Count: Integer; safecall; function NextDocument(out ADocumentID: WideString; out ADocument: ISIDocument): WordBool; fecall; function NextFolder(out AFolderName: WideString; out AFolder: ISIDocumentsItterator): WordBool; safecall; public constructor Create(const ANow, ASince: TDateTime; ACount, AType: Integer); end;
Легко заметить, что в листинге 3 есть две функции, помогающие итератору при циклическом использовании элементов источника данных. Первая из них - это NextDocument, вторая - NextFolder. Для навигации по документам к какой-либо конкретной папке используется первый из методов, ну а для навигации по папкам, соответственно, второй. Что касается конструктора и его параметров и private-полей класса, то пока что не обращайте на них внимания - все станет понятно, когда мы будем говорить об имплементировании данного класса.
Наконец, у нас с вами остался четвертый по счету интерфейс, с которым вы можете ознакомиться в листинге 4.
Листинг 4
TSampleDocument = class(TAutoIntfObject, ISIDocument) protected // ISIDocument function ExtractText(out AText: WideString; const AProgress: ISIProgress): WordBool; safecall; function Get_Version: WideString; safecall; function Get_FileExtention: WideString; safecall; function Get_RawDataSize: Integer; safecall; procedure RawData(const AStream: ISIOutputStream; const AProgress: ISIProgress); safecall; function Get_SubDocuments: ISIDocumentSource; safecall; function IsDiskFile(out AFileName: WideString): WordBool; safecall; procedure ExtractAttributes(const AAttributes: ISIDocumentAttributeList; const AProgress: ISIProgress); safecall; public constructor Create(const ADate: TDateTime; AType: Integer); end;
Как видите, в плане именования методов и их параметров этот класс мало отличается от всех остальных, которые мы бегло рассмотрели выше.
Что ж, на сегодня достаточно, ведь газетная площадь ограничена - будь это не так, мы бы закончили разговор о SoftInform Search SDK уже давно. В следующий раз поговорим о том, как именно реализуются приведенные в листингах к статье классы - если не все, то хотя бы половина из них.
Вадим СТАНКЕВИЧ,
dreamdrusch@tut.by