Определение типа файла по содержимому

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Trezub
новенький
Сообщения: 52
Зарегистрирован: 17.09.2005 21:23:04

Сообщение Trezub »

Проблема в следующем. Необходимо определять тип файла по его содержимому. Файлы Microsoft Word и Microsoft Excel. И это все усложняет. Вариант с просмотром файла и поиском строк "Microsoft Word" и аналогично "Microsoft Excel" не подходит, потому как иногда срабатывает неверно (например, при вставках Ворда в Ексель и наоборот).

Определение типа файла возможно по его сигнатуре. Но проблема в том, что начальные биты файлов Ворда и Екселя одинаковые (во всяком случае так "считываю" я).

В *nix'е есть программа file, которая определяет тип. Существует два портированных вариана под Windows, так вот программа filetype, определяет файлы и Ворд'а и Ексель'а как file Microsoft Office, без уточнения :(

А вторая прога не запускается... просит библиотеки pcre.dll и zlib1.dll (их я еще не скачал:). И потом прийдеться мучаться с подключением сишного кода к freepascal'у.

Мне то, всего и нужно - определять три типа - Ворд, Ексель и другие. Не хочется использовать код монстра, который определяет все типы (сомневаюсь, что смогу вытащить нужный мне код "под себя")

Что подскажете?
Аватара пользователя
alexs
долгожитель
Сообщения: 4074
Зарегистрирован: 15.05.2005 23:17:07
Откуда: г.Ставрополь
Контактная информация:

Сообщение alexs »

a ShellExecute (стандартная виндовая функция) не подойдёт? (это если для запуска соответсвующей программы)
Trezub
новенький
Сообщения: 52
Зарегистрирован: 17.09.2005 21:23:04

Сообщение Trezub »

alexs писал(а): a ShellExecute (стандартная виндовая функция) не подойдёт? (это если для запуска соответсвующей программы)

хм, мне кажеться, что ShellExecute документ "blabla" без расширения не запустит, даже если он будет Word'овским. Разве нет?...

Но в любом случае мне это не подойдет, потому что нужно только определить тип - без запуска соответ. программы.
bw

Сообщение bw »

Смотри StgOpenStorageEx.
Правда в winapi для fp нет описания этой функции, а так же необходимых интерфейсов. Постараюсь сегодня все что необходимо перенести из delphi. Исходник выложу здесь. Если нужно.

..bw
Trezub
новенький
Сообщения: 52
Зарегистрирован: 17.09.2005 21:23:04

Сообщение Trezub »

bw писал(а): Постараюсь сегодня все что необходимо перенести из delphi. Исходник выложу здесь. Если нужно.

Очень (подчеркиваю красным) нужно. А если сегодня - то это просто великолепно. :)

а пока смотрю в инете что это вобще такое - StgOpenStorageEx.
Аватара пользователя
noch
постоялец
Сообщения: 145
Зарегистрирован: 07.06.2005 09:45:49
Откуда: Armenia
Контактная информация:

Сообщение noch »

А все-таки человек советующий shellexecute не имел в виду запускать документы ;)
Он имел в виду что лучше использовать вызов для запуска программы file а я бы посовтеовал использовать для этого popen ;)
Открываешь pipe и читаешь из него ответ программы file, а что казается zlib.dll разве это проблема?
Trezub
новенький
Сообщения: 52
Зарегистрирован: 17.09.2005 21:23:04

Сообщение Trezub »

noch, аааа... :)

Кто подскажет, программа file под *nix как определяет файлы Оффиса? Отличает Ворд и Ексель? Ибо прога под винду - на все документы Оффиса говорит - "документ Майкрософт Оффис". И разделения не делает на Ворд и Ексель..
bw

Сообщение bw »

Код: Выделить всё

{$apptype console}
{$mode delphi}

uses
  SysUtils, Windows, ActiveX;

type
  EOleError = class(Exception);
  EOleSysError = class(EOleError)
  private
    FErrorCode: HRESULT;
  public
    constructor Create(const Message: String; ErrorCode: HRESULT; HelpContext: Integer);
    property ErrorCode: HRESULT read FErrorCode write FErrorCode;
  end;

constructor EOleSysError.Create(const Message: String; ErrorCode: HRESULT; HelpContext: Integer);
var
  S: String;
begin
  S:=Message;
  if S='' then
  begin
    S:=SysErrorMessage(ErrorCode);
    if S='' then FmtStr(S,'OLE error %.8x',[ErrorCode]);
  end;
  inherited CreateHelp(S,HelpContext);
  FErrorCode:=ErrorCode;
end;

procedure OleCheck(Result: HResult);
begin
  if Failed(Result) then raise EOleSysError.Create('',Result,0);
end;

{function LCIDToCodePage(ALcid: LongWord): Integer;
const
  CP_ACP = 0;
  LOCALE_IDEFAULTANSICODEPAGE = $00001004;
var
  ResultCode: Integer;
  Buffer: array [0..6] of Char;
begin
  GetLocaleInfo(ALcid, LOCALE_IDEFAULTANSICODEPAGE, Buffer, SizeOf(Buffer));
  Val(Buffer, Result, ResultCode);
  if ResultCode<>0 then Result:=CP_ACP;
end;}

function WCharFromChar(WCharDest: PWideChar; DestChars: Integer; const CharSource: PChar; SrcBytes: Integer): Integer;
begin
  {if GetVersion() and $80000000<>$80000000 then
  begin
    if Lo(GetVersion())>4 then
      DefaultUserCodePage:=3 else
      DefaultUserCodePage:=LCIDToCodePage(GetThreadLocale);
  end
  else
    DefaultUserCodePage := LCIDToCodePage(GetThreadLocale);
  Result:=MultiByteToWideChar(DefaultUserCodePage, 0, CharSource, SrcBytes, WCharDest, DestChars);}
  Result:=MultiByteToWideChar(3,0,CharSource,SrcBytes,WCharDest,DestChars);
end;

function StringToWideChar(const Source: String; Dest: PWideChar; DestSize: Integer): PWideChar;
begin
  Dest[WCharFromChar(Dest,DestSize-1,PChar(Source),Length(Source))]:=#0;
  Result:=Dest;
end;

function StringToOleStr(const Source: String): POleStr;
var
  SourceLen: Integer;
  Buffer: PWideChar;
begin
  SourceLen:=Length(Source);
  Buffer:=CoTaskMemAlloc((SourceLen+1)*SizeOf(WideChar));
  StringToWideChar(Source,Buffer,SourceLen+1);
  Result:=POleStr(Buffer);
end;


{procedure WriteStorage(Storage: IStorage);
var
  Enum: IEnumSTATSTG;
  Elem: TSTATSTG;
  n: Integer;
  s: String;
begin
  OleCheck(Storage.EnumElements(0,nil,0,Enum));
  repeat
    Enum.Next(1,Elem,@n);
    if n>0 then
    begin
      case Elem.dwType of
        STGTY_STORAGE  : s:='Storage';
        STGTY_STREAM   : s:='Stream';
        STGTY_LOCKBYTES: s:='LockBytes';
        STGTY_PROPERTY : s:='Property';
        else s:='';
      end;
      if s<>'' then s:=#09'- '+s;
      WriteLn(WideCharToString(Elem.pwcsName)+s);
    end;
  until n=0;
end;

procedure WriteOffice(FileName: PWideChar);
var
  Storage: IStorage;
  Stream : IStream;
  Mode: DWORD;
begin
  Mode:=STGM_READ or STGM_SHARE_EXCLUSIVE;
  OleCheck(StgOpenStorage(FileName,nil,Mode,nil,0,Storage));
  WriteStorage(Storage);
  OleCheck(Storage.OpenStream(#05'SummaryInformation',nil,Mode,0,Stream));
end;}

function GetTypeDocument(FileName: String): String;
var
  Storage: IStorage;
  OleStr: POleStr;
  Mode: DWORD;
  hr: HRESULT;

  function ExistName(Name: PWideChar): Boolean;
  var
    Stream: IStream;
  begin
    hr:=Storage.OpenStream(Name,nil,Mode,0,Stream);
    Result:=Succeeded(hr);
    if not Result and (hr<>HRESULT($80030002)) then OleCheck(hr);
  end;

begin
  Result:='unk';
  Mode:=STGM_READ or STGM_SHARE_EXCLUSIVE;
  OleStr:=StringToOleStr(FileName);
  try
    hr:=StgOpenStorage(OleStr,nil,Mode,nil,0,Storage);
    if Succeeded(hr) then
    begin
      if ExistName('Workbook')     then Result:='xls' else
      if ExistName('WordDocument') then Result:='doc';
    end else
      if hr<>HRESULT($80030050) then OleCheck(hr);
  finally
    CoTaskMemFree(OleStr);
  end;
end;


var
  Handle: THandle;
  Search: TSearchRec;
begin
  Handle:=FindFirst('*',faAnyFile and not (faDirectory or faVolumeId or faHidden),Search);
  while Handle=S_OK do
  begin
    WriteLn(Format('%s'#09' - %s',[Search.Name,GetTypeDocument(Search.Name)]));
    Handle:=FindNext(Search);
  end;
  FindClose(Handle);
end.


..bw
Trezub
новенький
Сообщения: 52
Зарегистрирован: 17.09.2005 21:23:04

Сообщение Trezub »

bw, огромнейшее спасибо. Это восхитетельно, потому как уже над проблемой бьюсь давно.

Буду разбираться в коде, при первом запуске выдало ошибку:

"project raised exception class "EoleSyserror"
with message "недопустимое имя %1".


копошусь в коде. Может научусь умным вещам.
bw

Сообщение bw »

У меня fpc 2.0.0, ОСь w2kpro sp4, если что. Этот код только для win32. И (уверен) что под win9x и winMe он не пойдет.

..bw
Trezub
новенький
Сообщения: 52
Зарегистрирован: 17.09.2005 21:23:04

Сообщение Trezub »

bw, и схема работы проги для 98-ого не подходит? Нет возможности под 98 использовать?
bw

Сообщение bw »

Х.з. надо пробовать. Ты под win98 запускал? Это под ней ошибка вылезла? Это же все COM, а под win9x COM был очень слабенький и во многом не совместимый с win2k и далее. А зачем win98, пора уже его выбросить. Я года три как выбросил. Можно (и не сложно на самом деле) и без COM структуру "хранилищ разрулить", но у меня сейчас на это нет времени.

..bw
Trezub
новенький
Сообщения: 52
Зарегистрирован: 17.09.2005 21:23:04

Сообщение Trezub »

bw, пробывал по 98. У нас на работе везде 98-ой, поэтому вариант "онли с 2к" не сильно подходит.. Хотя можно сделать чтобы на 98-ом ругался, а на 2к работал.. посмотрим...
Alexander

Сообщение Alexander »

Странно. А не проще считать первые два байта из файла ? Для ворда
они такие: D0 CF.
Аватара пользователя
bw
постоялец
Сообщения: 359
Зарегистрирован: 01.12.2005 10:36:23
Откуда: Усть-Илимск
Контактная информация:

Сообщение bw »

Они и у Excel'я такие и еще у сотни других форматов посторенных на Mu$tDiE'вских контейнерах.
Сам Office, насколько я понял, не может распознать какой документ был ему предоставлен. Я определяю это по структуре контейнера. Хотя, наверное, правельныы это делать по ключевым свойствам. Но что это за свойства и какие значения они должны содержать мне не известно.
По структуре, это видно из моего кода формат определяется достаточно элементарно. Если в главном хранилище существует запись Workbook, то это Excel, если WordDocument, то это Word.

..bw
Ответить