SRC: QA Извлечение текста документов (IFilter)
От: Kaa Украина http://blog.meta.ua/users/kaa/
Дата: 08.08.02 10:39
Оценка: 135 (14)
Часто спрашивают, как получить текст из Ворда, Екселя, PDF.

Часто отвечаю, что сложно, но есть способ простой, и зовут его IFilter. Давно думаю, что надо QA написать, но постоянно руки не доходят. Посему, пока руки не доходят и в этот раз, публикую текст, который даст представление о том, как этот механизм можно использовать. Сразу скажу, что использовать его можно только на Win2000 или WinNT, но для последней нужны некие приблуды, коими она по умолчанию не обладает (Option Pack).

Данная утилита вынимает текст в UNICODE из файла, переданного в качестве параметра (если для класса файлов, к которому документ принадлежит, установлен фильтр), и выдает его либо в файл, либо в стандартный вывод.


////////////////////////////////////////////////////////////////////////
//[] Filename   : filterDoc.h
////////////////////////////////////////////////////////////////////////

# ifndef _filterDoc_h_
#   define _filterDoc_h_

# include <windows.h>
# include <ntquery.h>
# include <filter.h>
# include <filterr.h>
# include <atlbase.h>
# include <atlconv.h>

bool ReallocBuffer( LPVOID& lpDst, ULONG ulDst, LPVOID lpSrc, ULONG ulSrc )
{
  if ( 0 == ulDst )
    lpDst = NULL;
  else
  {
    lpDst = CoTaskMemAlloc( ulDst );
    if ( NULL == lpDst )
      return false;
  }
  
  if ( NULL != lpSrc ) 
  {
    if ( 0 != ulSrc )
      CopyMemory( lpDst, lpSrc, (ulDst < ulSrc) ? ulDst : ulSrc );
    CoTaskMemFree( lpSrc );
  }
  
  return true;
}

HRESULT FilterDocument( const WCHAR* lpwSrc, LPVOID& lpvData, ULONG& ccvData )
{
  lpvData = NULL;
  ccvData = 0;

  if ( NULL == lpwSrc || 0 == *lpwSrc )
    return E_INVALIDARG;

  CComPtr<IUnknown> pUnk    = NULL;
  CComPtr<IFilter>  pFilter = NULL;
  HRESULT           hr      = LoadIFilter( lpwSrc, NULL, (void**)&pUnk );
  LPVOID            lpv     = NULL;
  ULONG             ccv     = 0x10000;

  if ( S_OK != hr || NULL == (IUnknown*)pUnk )
    return E_FAIL;

  hr = pUnk->QueryInterface( IID_IFilter, (void**)&pFilter );
  if ( S_OK != hr || NULL == (IFilter*)pFilter )
    return hr;

  ULONG lFlags = 0;
  hr = pFilter->Init(
    IFILTER_INIT_CANON_PARAGRAPHS       |
    IFILTER_INIT_CANON_HYPHENS          | 
    IFILTER_INIT_CANON_SPACES           |
    IFILTER_INIT_APPLY_INDEX_ATTRIBUTES |
    IFILTER_INIT_INDEXING_ONLY, 
    0, 
    NULL,
    &lFlags );
  if ( S_OK != hr )
    return hr;

  if ( ! ReallocBuffer( lpv, ccv, NULL, 0 ) )
    return E_OUTOFMEMORY;

  STAT_CHUNK chunk = { 0 };
  chunk.breakType = CHUNK_EOP;

  WCHAR  wbBuf[0x1000]  = { 0 };
  ULONG  lSize          = sizeof(wbBuf)/sizeof(wbBuf[0]);
  ULONG  lRes           = 0;
  DWORD  dwWritten      = 0;

  USES_CONVERSION;

  for ( ; ; )
  {
    hr = pFilter->GetChunk( &chunk );

    if ( FILTER_E_END_OF_CHUNKS == hr )
      break;
    switch ( hr )
    { 
    case FILTER_E_EMBEDDING_UNAVAILABLE :
    case FILTER_E_LINK_UNAVAILABLE      : continue;
    case FILTER_E_PASSWORD              :
    case FILTER_E_ACCESS                : return E_FAIL;
    default                             : break;
    }

    for ( ; ; )
    {
      lRes = lSize;
      hr = pFilter->GetText( &lRes, wbBuf );

      if ( FILTER_E_NO_TEXT == hr         // the chunk contains no text
        || FILTER_E_NO_MORE_TEXT  == hr ) // the chunk contains no more text
        break;

      wbBuf[lRes] = 0;
      if ( ccv < ( lRes + dwWritten ) * 2 )
      {
        LPVOID  lpvT  = NULL;
        ULONG   ccvT  = ( lRes + dwWritten ) * 2 + 0x10000;
        if ( ! ReallocBuffer( lpvT, ccvT, lpv, dwWritten * 2 ) )
        {
          ReallocBuffer( lpv, 0, lpv, ccv );
          return E_FAIL;
        }
        lpv = lpvT;
        ccv = ccvT;
      }

      CopyMemory( (WCHAR*)lpv + dwWritten, wbBuf, lRes * 2 );
      dwWritten += lRes;
      ((WCHAR*)lpv)[dwWritten] = 0;

      if ( FILTER_S_LAST_TEXT == hr ) /// This was last text fragment in chunk
        break;
    }

  }

  lpvData = lpv;
  ccvData = dwWritten * 2;

  return S_OK;
}

# endif // _filterDoc_h_


////////////////////////////////////////////////////////////////////////
//[] Filename   : flttest.cpp
//[] Description: Contains an entry point of IFilter tester.
////////////////////////////////////////////////////////////////////////

# ifdef _MSC_VER
#   pragma comment ( lib, "ntquery.lib" )
# endif

# include <atlbase.h>
# include <atlconv.h>
# include <io.h>
# include <fcntl.h>
# include <stdio.h>
# include "filterDoc.h"

static char gszAbout[] = 
  "RSDN Group (R) QnA on IFilter Code Sample\n";
static char gszUsage[] = 
  "Usage: flttest [srcfile [dstfile]]\n";

int main( int argc, char* argv[] )
{
  FILE* os  = stdout;
  int   err = 0;

  fprintf( stderr, gszAbout );

  if ( argc == 1 || ( argc > 1 && 0 == *argv[1] ) )
  {
    fprintf( stderr, gszUsage );
    return 0;
  }
  if ( argc >= 3 && 0 != *argv[2] )
  {
    os = fopen( argv[2], "wb" );
    if ( NULL == os )
      os = stdout;
  }

  if ( os == stdout )
    setmode( fileno( stdout ), O_BINARY );

  USES_CONVERSION;

  WCHAR*  lpwName = A2W( argv[1] );
  LPVOID  lpvData = NULL;
  ULONG   ccvData = NULL;
  HRESULT hr      = S_OK;

  hr = FilterDocument( lpwName, lpvData, ccvData );
  if ( S_OK == hr )
  {
    fwrite( lpvData, sizeof(char), ccvData, os );
    
    ReallocBuffer( lpvData, 0, lpvData, 0 );
  } else {
    fprintf( stderr, "Filtering failed!\n" );
    err = 1;
  }

  if ( os != stdout )
    fclose( os );

  return err;
}


PS: для извлечения текста из PDF-файлов нужно скачать и установить Adobe PDF IFilter v5.0 (находится здесь: http://www.adobe.com/support/downloads/detail.jsp?ftpID=1276)
Алексей Кирдин
Re: SRC: QA Извлечение текста документов (IFilter)
От: fuurin  
Дата: 10.02.07 09:14
Оценка:
Kaa>PS: для извлечения текста из PDF-файлов нужно скачать и установить Adobe PDF IFilter v5.0

А кто-нибудь пробовал это с PDF IFilter <b>v6.0</b>?

При выходе из приложения стабильно выкидывает исключение (см. ниже). Перерыл пол-инета, решения не нашёл

Font Capture: mid82418.exe - Application Error
The instruction at "0x012e61b3" referenced memory at "0x011523e8". The memory could not be "read".

В стеке это выглядит примерно так:
PDFL60.dll!012e61b3()<<
PDFL60.dll!011c884e()     // ASfree
PDFL60.dll!0120534e()     
PDFL60.dll!0127f53c()     // PDFLTermFriends
PDFL60.dll!0127f9d3()     // PDFLTerm
PDFFILT.dll!010a12ac()
PDFFILT.dll!010a63a7()  // DllMain(PROCESS_DETACH)
msvcr80d.dll!__crtExitProcess(int status=0)
mid82418.exe!__tmainCRTStartup()


Система: Windows 2003 SP1.
Garbage In Garbage Out
Re[2]: SRC: QA Извлечение текста документов (IFilter)
От: AlexZu Россия  
Дата: 18.02.07 23:15
Оценка:
Здравствуйте, fuurin, Вы писали:

F>А кто-нибудь пробовал это с PDF IFilter <b>v6.0</b>?


Это баг этой версии PDF IFilter, проблема усугубляется тем, что PDFFILT.dll (реализация IFilter) ссылается на PDFL60.dll исп-я механизм delay-load,
т.е. даже после выгрузки PDFFILT.dll проблемная dll (PDFL60.dll) остается "висеть" в памяти.
Решение в лоб:
— самому загружать\выгружать PDFFILT.dll (CoLoadLibrary\CoFreeLibrary),
— вручную создавать экземпляр IFilter (DllGetClassObject\IClassFactory, правда придется еще достать из реестра CLSID класса реализующего pdf IFilter, ну и путь к dll по этому CLSID),
— вручную инициализировать IFilter (IPersistFile, pdf IFilter реализует только его),
после выгрузки PDFFILT.dll принудительно выгружать проблемную dll (PDFL60.dll) (GetModuleHandle\FreeLibrary).
Re[3]: SRC: QA Извлечение текста документов (IFilter)
От: fuurin  
Дата: 19.02.07 10:17
Оценка:
AZ>Это баг этой версии PDF IFilter
AZ>Решение в лоб:
AZ>- самому загружать\выгружать PDFFILT.dll (CoLoadLibrary\CoFreeLibrary),

Хм, попробую. Как я понимаю, ключевой момент здесь — самому инициализировать iFilter? Ведь нет же разницы, как я выгружаю PDFFILT — в любом случае код уйдёт в
PDFFILT.dll!010a63a7() // DllMain(PROCESS_DETACH)
... и дальше по стеку — к багу.

Сейчас вот попробовал установить Adobe Reader 8 — он тоже идёт со (своим?) PDF iFilter. Такого бага не наблюдается. Пойду искать правды на адобовских форумах...
Garbage In Garbage Out
Re[4]: SRC: QA Извлечение текста документов (IFilter)
От: fuurin  
Дата: 19.02.07 16:36
Оценка:
F>Сейчас вот попробовал установить Adobe Reader 8 — он тоже идёт со (своим?) PDF iFilter. Такого бага не наблюдается.

Ещё одно замечание: проблемы с PDF IFilter v6.0 нет и при использовании утилиты filtdump из Platform SDK.
Garbage In Garbage Out
Re[4]: SRC: QA Извлечение текста документов (IFilter)
От: AlexZu Россия  
Дата: 19.02.07 23:21
Оценка:
Здравствуйте, fuurin, Вы писали:

F>Хм, попробую. Как я понимаю, ключевой момент здесь — самому инициализировать iFilter? Ведь нет же разницы, как я выгружаю PDFFILT — в любом случае код уйдёт в

F>PDFFILT.dll!010a63a7() // DllMain(PROCESS_DETACH)
F>... и дальше по стеку — к багу.

Нет, ключевой момент в контроле над выгрузкой pdf dlls. Насколько я понял у них проблема вот в чем: при выгрузке процесса DLL_PROCESS_DETACH получает сначала delay-loaded dll (PDFL60.dll), происходит освобождение каких-то ресурсов; затем DLL_PROCESS_DETACH получает та dll, которая загрузила delay-load dll (PDFFILT.dll) и в которой происходит обращение (вызов функции, опять же с целью освобождения ресурсов) к уже почти выгруженной dll(PDFL60.dll), проверки на то, что финализация уже выполнена нет, отсюда и AV. Т.е. порядок выгрузки не соотв. порядку загрузки.

Кстати, то решение которое я приводил можно упростить: работаетет с IFilter's как и прежде, но перед завершением процесса делаем хак, что-то типа:
FreeLibrary( GetModuleHandle(TEXT("PDFFILT.dll")) );
FreeLibrary( GetModuleHandle(TEXT("PDFL60.dll")) );



F>Сейчас вот попробовал установить Adobe Reader 8 — он тоже идёт со (своим?) PDF iFilter. Такого бага не наблюдается. Пойду искать правды на адобовских форумах...

Если есть возможность исп-ть конкретную (рабочую) версию PDF IFilter — используйте его, танцы с бубном никому не нужны.
Re[5]: SRC: QA Извлечение текста документов (IFilter)
От: AlexZu Россия  
Дата: 19.02.07 23:22
Оценка:
Здравствуйте, fuurin, Вы писали:

F>Ещё одно замечание: проблемы с PDF IFilter v6.0 нет и при использовании утилиты filtdump из Platform SDK.


Запустите ifilttst.exe с ключем "/stress" и проблемы проявятся
Re[5]: SRC: QA Извлечение текста документов (IFilter)
От: fuurin  
Дата: 20.02.07 12:23
Оценка:
AZ>Если есть возможность исп-ть конкретную (рабочую) версию PDF IFilter — используйте его, танцы с бубном никому не нужны.

Так выбирать-то особо и не из чего: на сайте PDF IFilter версии 5.0 и 6.0. Обе, как оказывается, с багами. Теперь вот разбираться с лицензиями на Reader — можно ли IFilter из его инсталляции использовать в своём приложении.
Garbage In Garbage Out
Re: PDF IFilter Hebrew
От: fuurin  
Дата: 16.08.07 10:48
Оценка:
Текст на иврите из PDF возвращается странным образом перевёрнутым: буквы в словах в обратном порядке, как и сами слова в обратном порядке между двумя разделителями. Проверял на DOC-файлах — те отображаются правильно. Для примера можно взять этот документ.
Написал в Foxit — те признали баг, но ничего не обещали. (Фильтр от Foxit хоть нормально форматирует текст, в отличие от Adobe, где весь текст возвращается в одну строку.)
Написал в Adobe — оттуда вряд ли будет ответ.

Кстати, на странице Adobe PDF IFilter появилась такая надпись:

IMPORTANT NOTE: Starting with Acrobat and Reader 7.0.5, iFilter functionality is now bundled within the Acrobat and Reader products. Improvements to iFilter in Acrobat and Reader 8 include support for Vista and Windows Desktop Search, as well as improved performance and stability. It is recommended that you update your copy of Adobe Acrobat or Adobe Reader in order to get the most current iFilter functionality, rather than download and install the stand-alone iFilter plug-in.


Теперь вот думаю, что делать — то ли переворачивать вывод из IFilter, то ли писать свой экстрактор непосредственно из PDF ): Второе удручает — беглый взгляд на доступные библиотеки (Xpdf, iTextSharp) показал, что никто с ивритом работать не умеет. Вероятно, как и с другими двунаправленными языками.
Garbage In Garbage Out
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.