/* * wordcnv: use the Microsoft Office converters for command-line conversion * * Copyright (C) 2002 Sean Young * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * Since it relies on closed-source commercial libraries (the converter * dll's) it cannot be released under GPL. Therefore it is LPGL. * * It only requires kernel32.lib and should compile under winelib (note the * #ifdef below). */ #include #include #include #include "convapi.h" #ifdef WINELIB #define stricmp strcasecmp #endif typedef struct converter { HINSTANCE handle; InitConverter32 *InitConverter; UninitConverter *FreeConverter; RegisterApp *RegisterApp; ForeignToRtf32 *ForeignToRtf; RtfToForeign32 *RtfToForeign; IsFormatCorrect32 *IsFormatCorrect; CchFetchLpszError *FetchError; GetReadNames *GetRead; GetWriteNames *GetWrite; char filename[MAX_PATH]; } converter; typedef struct ms_converter { char filename[MAX_PATH]; char class_name[MAX_PATH]; char description[MAX_PATH]; char extensions[MAX_PATH]; int type; struct ms_converter *next; } ms_converter; enum ms_conv_type { MS_OTHER_TO_RTF, MS_RTF_TO_OTHER, MS_LAST }; static struct { int list; int help; int identify; int files; char import_class[MAX_PATH], export_class[MAX_PATH]; char input_file[MAX_PATH], output_file[MAX_PATH]; } options; static char *wordcnv_dir = NULL, cnv_dir[MAX_PATH]; static int verbose = 0; static HANDLE fin, fout; static HGLOBAL hin, hout; static char *progname; static struct ms_converter *msconverters = NULL; static void strcpy_fix_double_null (char *des, const char *src) { char last = 1; do { last = *src; *des++ = *src++; } while (last || *src); *des = 0; } static void print_cnv_error (int err, struct converter *cnv) { char buf[512]; if (cnv->FetchError && cnv->FetchError (err, buf, sizeof (buf))) { fprintf (stderr, "%s: converter error %d: %s\n", cnv->filename, err, buf); } else { switch (err) { case -1: fprintf (stderr, "%s: error: converter could not open '%s'\n", progname, options.input_file); break; case -2: fprintf (stderr, "%s: error: read error\n", progname); break; case -4: fprintf (stderr, "%s: error: write error\n", progname); break; case -5: fprintf (stderr, "%s: error: %s: invalid input file\n", progname, options.input_file); break; case -8: fprintf (stderr, "%s: error: converter ran out of memory\n", progname); break; case -13: fprintf (stderr, "%s: error: converter cannot create '%s'\n", progname, options.output_file); break; case -14: fprintf (stderr, "%s: error: converter did not recognise input file '%s'\n", progname, options.input_file); break; default: fprintf (stderr, "%s: error: converter returned error %x\n", cnv->filename, err); } } } static int init_converter (struct converter *cnv) { char path[MAX_PATH]; int ret; sprintf (path, "%s/%s", cnv_dir, cnv->filename); cnv->handle = LoadLibrary (path); if (cnv->handle == (HINSTANCE)HINSTANCE_ERROR) { if (verbose > 1) fprintf (stderr, "%s: error: LoadLibrary failed\n", cnv->filename); return 1; } cnv->InitConverter = (InitConverter32*) GetProcAddress (cnv->handle, "InitConverter32"); cnv->IsFormatCorrect = (IsFormatCorrect32*) GetProcAddress (cnv->handle, "IsFormatCorrect32"); cnv->ForeignToRtf = (ForeignToRtf32*) GetProcAddress (cnv->handle, "ForeignToRtf32"); cnv->RtfToForeign = (RtfToForeign32*) GetProcAddress (cnv->handle, "RtfToForeign32"); cnv->FetchError = (CchFetchLpszError*) GetProcAddress (cnv->handle, "CchFetchLpszError"); cnv->FreeConverter = (UninitConverter*) GetProcAddress (cnv->handle, "UninitConverter"); cnv->GetRead = (GetReadNames*) GetProcAddress (cnv->handle, "GetReadNames"); cnv->GetWrite = (GetWriteNames*) GetProcAddress (cnv->handle, "GetWriteNames"); cnv->RegisterApp = (RegisterApp*) GetProcAddress (cnv->handle, "RegisterApp"); if (!cnv->InitConverter) { fprintf (stderr, "%s: error: InitConverter32 could not be resolved\n", path); FreeLibrary (cnv->handle); return 1; } ret = cnv->InitConverter ((HANDLE)NULL, "WORDCNV"); if (ret != 1) { print_cnv_error (ret, cnv); FreeLibrary (cnv->handle); return 1; } return 0; } static void free_converter (struct converter *cnv) { if (cnv->FreeConverter) cnv->FreeConverter (); FreeLibrary (cnv->handle); } static int register_app (struct converter *cnv) { HGLOBAL mem; BYTE *p, opcode, len; WORD minor, major; int size; if (verbose) { fprintf (stderr, "%s: info: calling RegisterApp\n", cnv->filename); } mem = cnv->RegisterApp (fRegAppSupportNonOem, NULL); if (mem == (HGLOBAL)NULL) { if (verbose) { fprintf (stderr, "%s: info: converter has no preferences\n", cnv->filename); } return 0; } if (!verbose) { GlobalFree (mem); return 0; } p = GlobalLock (mem); size = *((WORD*)p) - 2; p += 2; while (size >= 2) { len = *p++; opcode = *p++; size -= len; switch (opcode) { case RegAppOpcodeVer: major = *((WORD*)p)++; minor = *((WORD*)p)++; fprintf (stderr, "%s: info: converter supports Word %d.%d RTF\n", cnv->filename, (int)major, (int)minor); break; case RegAppOpcodeDocfile: major = *((WORD*)p)++; fprintf (stderr, "%s: info: converter does%ssupport OLE structured storage\n", cnv->filename, major & 1 ? " " : " not "); fprintf (stderr, "%s: info: converter does%ssupport non-OLE files\n", cnv->filename, major & 2 ? " " : " not "); break; case RegAppOpcodecharset: major = *p++; fprintf (stderr, "%s: info: converter expects filenames in charset %d\n", cnv->filename, (int)major); break; case RegAppOpcodeReloadOnSave: fprintf (stderr, "%s: info: converter expects caller to reload file\n", cnv->filename); break; case RegAppOpcodePicPlacehold: fprintf (stderr, "%s: info: converter expects picture placeholders\n", cnv->filename); break; case RegAppOpcodeFavourUnicode: fprintf (stderr, "%s: info: converter favours unicode\n", cnv->filename); break; case RegAppOpcodeNoClassifychars: fprintf (stderr, "%s: info: converter expects no classify characters\n", cnv->filename); break; default: fprintf (stderr, "%s: warning: converter passed unknown preference %02x\n", cnv->filename, (int)opcode); break; } } GlobalUnlock (mem); GlobalFree (mem); return 0; } static void print_spaces (int i) { while (i-- >= 0) putc (' ', stdout); } static void print_classes (void) { int type, max; int filename_max, class_name_max, description_max, extensions_max; struct ms_converter *ms; filename_max = class_name_max = description_max = extensions_max = -1; for (ms=msconverters; ms; ms=ms->next) { max = strlen (ms->filename); if (max > filename_max) filename_max = max; max = strlen (ms->class_name); if (max > class_name_max) class_name_max = max; max = strlen (ms->description); if (max > description_max) description_max = max; max = strlen (ms->extensions); if (max > extensions_max) extensions_max = max; } filename_max++; class_name_max++; description_max++; extensions_max++; for (type=MS_OTHER_TO_RTF; typenext) { if (ms->type == type) { printf (ms->filename); print_spaces (filename_max - strlen (ms->filename)); printf (ms->class_name); print_spaces (class_name_max - strlen(ms->class_name)); printf (ms->description); print_spaces (description_max - strlen(ms->description)); printf ("%s\n", ms->extensions); } } printf ("\n\n"); } } static int get_classes (struct converter *cnv, char *class_name, char *desc, char *extension, int type) { HGLOBAL hClass, hDesc, hExt; char *p; hClass = GlobalAlloc (GHND, 1024); hDesc = GlobalAlloc (GHND, 1024); hExt = GlobalAlloc (GHND, 1024); if (type == MS_OTHER_TO_RTF) { if (cnv->GetRead) { cnv->GetRead (hClass, hDesc, hExt); } else { return 1; } } else if (type == MS_RTF_TO_OTHER) { if (cnv->GetWrite) { cnv->GetWrite (hClass, hDesc, hExt); } else { return 1; } } else { fprintf (stderr, "%s: error: internal error\n", progname); return 2; } p = (char *) GlobalLock (hClass); strcpy_fix_double_null (class_name, p); GlobalUnlock (hClass); p = (char *) GlobalLock (hDesc); strcpy_fix_double_null (desc, p); GlobalUnlock (hDesc); p = (char *) GlobalLock (hExt); strcpy_fix_double_null (extension, p); GlobalUnlock (hExt); GlobalFree (hClass); GlobalFree (hDesc); GlobalFree (hExt); return 0; } static int read_converter_dlls (void) { char *cnv_tmp, *extension, *class_name, *desc; char extension_buf[MAX_PATH], class_name_buf[MAX_PATH]; char desc_buf[MAX_PATH], file_name_buf[MAX_PATH]; WIN32_FIND_DATA data; HANDLE hfind; struct converter cnv; struct ms_converter *ms_conv, *ms_conv_last; int type, msconverters_count; if (NULL != wordcnv_dir) cnv_tmp = wordcnv_dir; else if (NULL == (cnv_tmp = getenv ("WORDCNV_DIR"))) cnv_tmp = "C:\\Program Files\\Common Files\\Microsoft Shared\\Textconv"; strcpy (cnv_dir, cnv_tmp); strcpy (file_name_buf, cnv_dir); strcat (file_name_buf, "\\*"); if (verbose) { fprintf (stderr, "%s: info: searching '%s' for converter dlls...\n", progname, cnv_dir); } hfind = FindFirstFile (file_name_buf, &data); if (hfind == INVALID_HANDLE_VALUE) { fprintf (stderr, "%s: error: FindFirstFile failed with error %x\n", progname, (int)GetLastError ()); return 1; } msconverters_count = 0; ms_conv_last = NULL; while (1) { /* we're only interested in .cnv files */ if (strlen (data.cFileName) > 5) { extension = data.cFileName + strlen (data.cFileName) - 4; } else { extension = NULL; } if (extension && (!stricmp (extension, ".cnv") || !stricmp (extension, ".wpc"))) { if (verbose) { printf ("%s: info: found: %s\n", progname, data.cFileName); } memset (&cnv, 0, sizeof (struct converter)); strcpy (cnv.filename, data.cFileName); if (!init_converter (&cnv)) { for (type=MS_OTHER_TO_RTF;type!=MS_LAST;type++) { if (get_classes (&cnv, class_name_buf, desc_buf, extension_buf, type)) { continue; } class_name = class_name_buf; desc = desc_buf; extension = extension_buf; while (*class_name && *desc && *extension) { /* add them one by one */ ms_conv = (struct ms_converter*) LocalAlloc (LPTR, sizeof (struct ms_converter)); if (!ms_conv) { fprintf (stderr, "%s: error: out of memory\n", progname); return 1; } strcpy (ms_conv->filename, data.cFileName); strcpy (ms_conv->class_name, class_name); class_name += strlen (class_name) + 1; strcpy (ms_conv->description, desc); desc += strlen (desc) + 1; strcpy (ms_conv->extensions, extension); extension += strlen (extension) + 1; ms_conv->type = type; ms_conv->next = NULL; if (ms_conv_last == NULL) { msconverters = ms_conv; ms_conv_last = ms_conv; } else { ms_conv_last->next = ms_conv; ms_conv_last = ms_conv; } } } msconverters_count++; free_converter (&cnv); } } if (!FindNextFile (hfind, &data)) { int ret; ret = GetLastError (); if (ret != ERROR_NO_MORE_FILES) { fprintf (stderr, "%s: error: FindNextFile returned %x\n", progname, ret); } FindClose (hfind); break; } } if (verbose) { printf ("%s: info: %d converter dlls found\n", progname, msconverters_count); } return 0; } static HGLOBAL StringToHGLOBAL (const char *string) { char *p; HGLOBAL hMem = (HGLOBAL)NULL; if (string != NULL) { hMem = GlobalAlloc (GHND, strlen (string) + 1); p = GlobalLock (hMem); strcpy (p, string); GlobalUnlock (hMem); } return hMem; } static long pascal write_progress (int cch, int percent) { static int progress = -1; DWORD size; if (percent != progress) { printf ("\x08\x08\x08\x08%03d%%", percent); progress = percent; } if (cch) { char *p = GlobalLock (hout); if (!WriteFile (fout, p, cch, &size, NULL)) { fprintf (stderr, "%s: error: cannot write: %x\n", options.output_file, (int)GetLastError ()); GlobalUnlock (hout); return -1; } if (cch != (int)size) { fprintf (stderr, "%s: error: %d of %d bytes written\n", options.output_file, (int)size, cch); GlobalUnlock (hout); return -1; } GlobalUnlock (hout); } return 1; } static int convert_foreign_to_rtf (const char *filename, const char *class_name) { int ret; HGLOBAL hFilename, hClass, hSubset; struct converter cnv; char buf[MAX_PATH]; struct ms_converter *ms; for (ms=msconverters; ms; ms=ms->next) { if ((ms->type == MS_OTHER_TO_RTF) && !stricmp (class_name, ms->class_name)) { break; } } if (!ms) { fprintf (stderr, "%s: error: Import class %s not found\n", progname, class_name); return 1; } if (verbose) { fprintf (stderr, "%s: info: using %s\n", progname, ms->filename); } memset (&cnv, 0, sizeof (struct converter)); strcpy (cnv.filename, ms->filename); if (init_converter (&cnv)) return 1; register_app (&cnv); if (!cnv.ForeignToRtf) { fprintf (stderr, "%s: error: ForeignToRtf could not be resolved\n", cnv.filename); return 1; } strcpy (buf, filename); hFilename = StringToHGLOBAL (buf); hClass = StringToHGLOBAL (class_name); hSubset = StringToHGLOBAL (""); hout = GlobalAlloc (GHND, 1024); printf ("Converting from %s (%s to RTF) 000%%", filename, class_name); ret = cnv.ForeignToRtf (hFilename, NULL, hout, hClass, hSubset, write_progress); printf ("\n"); GlobalFree (hFilename); GlobalFree (hSubset); GlobalFree (hout); GlobalFree (hClass); if (ret != 0) { print_cnv_error (ret, &cnv); return 1; } return 0; } static long pascal read_progress (int flags, int percent) { DWORD size; char *p; p = GlobalLock (hin); if (!ReadFile (fin, p, 512, &size, NULL)) { fprintf (stderr, "%s: error: cannot read file: %x\n", options.input_file, (int)GetLastError ()); return -1; } GlobalUnlock (hin); return size; } static int convert_rtf_to_foreign (const char *filename, const char *class_name) { int ret; HGLOBAL hFilename, hClass; struct converter cnv; char buf[MAX_PATH]; struct ms_converter *ms; for (ms=msconverters; ms; ms=ms->next) { if ((ms->type == MS_RTF_TO_OTHER) && !stricmp (class_name, ms->class_name)) { break; } } if (!ms) { fprintf (stderr, "Export class %s not found\n", class_name); return 1; } if (verbose) { fprintf (stderr, "%s: info: using %s\n", progname, ms->filename); } memset (&cnv, 0, sizeof (struct converter)); strcpy (cnv.filename, ms->filename); if (init_converter (&cnv)) return 1; register_app (&cnv); if (!cnv.RtfToForeign) { fprintf (stderr, "%s: RtfToForeign could not be resolved\n", cnv.filename); return 1; } strcpy (buf, filename); hFilename = StringToHGLOBAL (buf); hClass = StringToHGLOBAL (class_name); hin = GlobalAlloc (GHND, 1024); printf ("Converting to %s (RTF to %s)\n", filename, ms->class_name); ret = cnv.RtfToForeign (hFilename, NULL, hin, hClass, read_progress); GlobalFree (hFilename); GlobalFree (hout); GlobalFree (hClass); if (ret != 0) { print_cnv_error (ret, &cnv); return 1; } return 0; } static int identify_file (const char *filename, char *class_name) { HGLOBAL hFile, hClass; char *p; int ret; struct converter cnv; HANDLE file; DWORD len; char buf[100]; struct ms_converter *ms; file = CreateFile (filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL); if (file == INVALID_HANDLE_VALUE) { fprintf (stderr, "%s: error: %x\n", filename, (int)GetLastError ()); return 2; } if (!ReadFile (file, buf, 5, &len, NULL)) { fprintf (stderr, "%s: error: %x\n", filename, (int)GetLastError ()); return 2; } CloseHandle (file); if (len == 5 && !strncmp ("{\\rtf", buf, 5)) { fprintf (stderr, "%s recognised as RTF\n", filename); if (class_name) strcpy (class_name, "RTF"); return 0; } hFile = GlobalAlloc (GHND, strlen (filename) + 1); p = GlobalLock (hFile); strcpy (p, filename); GlobalUnlock (hFile); hClass = GlobalAlloc (GHND, 1024); memset (&cnv, 0, sizeof (struct converter)); for (ms=msconverters; ms; ms=ms->next) { if (ms->type != MS_OTHER_TO_RTF) continue; if (stricmp (cnv.filename, ms->filename)) { strcpy (cnv.filename, ms->filename); if (init_converter (&cnv) ) continue; if (cnv.IsFormatCorrect) { ret = cnv.IsFormatCorrect (hFile, hClass); if (ret == 1) { fprintf (stderr, "%s: info: recognised %s as class %s, %s\n", ms->filename, filename, ms->class_name, ms->description); if (class_name) strcpy (class_name, ms->class_name); free_converter (&cnv); GlobalFree (hFile); GlobalFree (hClass); return 0; } else if (!(ret == 0 || ret == -1)) { print_cnv_error (ret, &cnv); } } free_converter (&cnv); } } GlobalFree (hFile); GlobalFree (hClass); fprintf (stderr, "%s: error: none of the converters recognised your file\n", progname); return -1; } static void usage (void) { printf ( "wordcnv 1.1, a non-interactive file converter\n" "\n" "Usage: %s [OPTION]... [FILE] [FILE]\n" "\n" " -h Print this help\n" " -l List the available converters\n" " -f FILE Use IsFormatCorrect32 to find the format\n" " -i CLASS Read input as format CLASS\n" " -x CLASS Write output in format CLASS\n", progname); } static void make_absolute (char *filename) { char buf[MAX_PATH], cwd[MAX_PATH]; if (filename[0] == '/' || filename[0] == '\\' || filename[1] == ':') return; if (GetCurrentDirectory (sizeof (cwd), cwd)) { sprintf (buf, "%s\\%s", cwd, filename); strcpy (filename, buf); if (verbose) { fprintf (stderr, "%s: info: output file %s\n", progname, buf); } } } int main (int argc, char **argv) { int ret, i, n, last; progname = argv[0]; memset (&options, 0, sizeof (options)); strcpy (options.export_class, "RTF"); for (i=1; i= argc) { usage (); return 1; } strcpy (options.input_file, argv[++i]); options.identify = 1; options.files++; last = 1; break; case 'i': if (argv[i][n+1] || (i + 1) >= argc) { usage (); return 1; } strcpy (options.import_class, argv[++i]); last = 1; break; case 'x': if (argv[i][n+1] || (i + 1) >= argc) { usage (); return 1; } strcpy (options.export_class, argv[++i]); last = 1; break; case 'v': verbose++; break; } } } else if (options.files == 0) { strcpy (options.input_file, argv[i]); options.files++; } else if (options.files == 1) { strcpy (options.output_file, argv[i]); options.files++; } else { usage (); return 1; } } if (verbose) fprintf (stderr, "%s: info: wordcnv 1.1\n", progname); if (read_converter_dlls ()) { return 1; } if (options.list) { print_classes (); } else if (options.identify) { if (options.files != 1) { usage (); return 2; } identify_file (options.input_file, NULL); } else { if (options.files != 2) { usage (); return 2; } if (options.output_file[0]) make_absolute (options.output_file); if (!options.import_class[0]) { if (identify_file (options.input_file, options.import_class)) { return 1; } } if (!stricmp (options.import_class, options.export_class)) { fprintf (stderr, "%s: error: Nothing to do; " "import and export class are the same\n", progname); return 1; } if (!stricmp (options.import_class, "RTF")) { fin = CreateFile (options.input_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL); if (fin == INVALID_HANDLE_VALUE) { fprintf (stderr, "%s: error: cannot open file: %x\n", options.input_file, (int)GetLastError ()); return 1; } convert_rtf_to_foreign (options.output_file, options.export_class); CloseHandle (fin); } else if (!stricmp (options.export_class, "RTF")) { fout = CreateFile (options.output_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL); if (fout == INVALID_HANDLE_VALUE) { fprintf (stderr, "%s: error: cannot open file: %x\n", options.input_file, (int)GetLastError ()); return 1; } convert_foreign_to_rtf (options.input_file, options.import_class); CloseHandle (fout); } else { char temp_path[MAX_PATH], temp_file[MAX_PATH]; /* why are we using the truly useless Win32 API for this? */ if (!GetTempPath (sizeof (temp_path), temp_path)) { fprintf (stderr, "%s: error: GetTempPath returned %x\n", progname, (int)GetLastError ()); return 1; } if (!GetTempFileName (temp_path, "cnv", rand (), temp_file)) { fprintf (stderr, "%s: error: GetTempFileName returned %x\n", progname, (int)GetLastError ()); return 1; } fout = CreateFile (temp_file, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL); if (fout == INVALID_HANDLE_VALUE) { fprintf (stderr, "%s: error: cannot open temporary file %x\n", temp_file, (int)GetLastError ()); return 1; } if (verbose) { fprintf (stderr, "%s: info: using '%s' as temporary file\n", progname, temp_file); } ret = convert_foreign_to_rtf (options.input_file, options.import_class); CloseHandle (fout); if (!ret) { fin = CreateFile (temp_file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL); if (fin == INVALID_HANDLE_VALUE) { fprintf (stderr, "%s: error: cannot open temporary file %x\n", temp_file, (int)GetLastError ()); DeleteFile (temp_file); return 1; } convert_rtf_to_foreign (options.output_file, options.export_class); CloseHandle (fin); } DeleteFile (temp_file); } } return 0; }