/* for now, just for testing purposes */ #define DEBUG 1 #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #if 0 #include #endif #include "wvexporter-priv.h" #include "ms-ole-summary.h" /* the temporary storage struct for our summay data * for important reasons, this gets written out * at the end of the export, after the other streams * are closed */ typedef struct _OleSummaryData OleSummaryData; struct _OleSummaryData { char *title; char *subject; char *author; char *keywords; char *comments; char *template; char *lastauthor; char *revnumber; char *appname; U32 pagecount; U32 wordcount; U32 charcount; U32 security; U32 thumbnail; time_t total_edittime; time_t lastprinted; time_t created; time_t lastsaved; }; struct _wvExporter { /* consider all of this data to be private * the only reliable way to get/manipulate * data is through the exposed API */ /* our toplevel OLE Document * from which all of our following streams * are created */ wvDocument *ole; /* the open OLE streams within the toplevel OLE document */ wvStream *documentStream; wvStream *table1Stream; wvStream *table0Stream; wvStream *dataStream; /* more accounting structures */ OleSummaryData *summary; wvVersion ver; FIB fib; }; /* document stream names */ #define DOCUMENT_STREAM "WordDocument" #define TABLE1_STREAM "1Table" #define TABLE0_STREAM "0Table" #define DATA_STREAM "Data" #define ASSIGN_STRING(a, b) assign_string(&a, b) #define SUMMARY_SET_STRING(a, b, c) if(c) ms_ole_summary_set_string (a, b, c) #define ASSERT_STREAM_CREATED(s) \ if(!s) { \ wvError(("Error creating %s stream\n", #s));\ return NULL;\ } #define WVSTREAM_CLOSE(s) \ ms_ole_stream_close(&((s)->stream.libole_stream)) /* some static functions */ static void assign_string (char **a, const char *b); static void exporter_close_word8 (wvExporter * exp); static wvExporter *exporter_create_word8 (const char *filename); static void write_ole_summary (OleSummaryData * data, MsOleSummary * strm); /**************************************************************************/ /** Public implementation functions **/ /**************************************************************************/ /** * wvExporter_queryVerionSupported * * @returns 1 if we can export to version # @v * Of the MSWord format or 0 if not * * Currently Supported Versions: WORD8 * * @v - version */ S8 wvExporter_queryVersionSupported (wvVersion v) { switch (v) { case WORD8: return 1; default: return 0; } } /** * wvExporter_getVersion * * @exp - a valid exporter created by wvExporter_create * * @returns the version type of the wvExporter @exp, 0 on error */ wvVersion wvExporter_getVersion (wvExporter * exp) { if (!exp) { wvError (("Attempt to get version from a NULL exporter\n")); return (wvVersion) 0; } return exp->ver; } /** * wvExporter_create_version * * Creates a MSWord exporter object, with the version @v * If version @v isn't supported, return NULL. Call * wvExporter_queryVersionSupported(@v) to see * If @v is a supported version * * @filename - file on disk to create * @v - version of Word Format to create. Only valid for * versions where wvExporter_queryVersionSupporter (@v) == 1 * * @returns NULL on error, or a valid wvExporter on success */ wvExporter * wvExporter_create_version (const char *filename, wvVersion v) { if (!wvExporter_queryVersionSupported (v)) { wvError (("wvExporter: unsupported version Word%d", (int) v)); return NULL; } switch (v) { case WORD8: return exporter_create_word8 (filename); default: wvError (("Cannot create unsupported version: %d\n", (int) v)); return NULL; } } /** * wvExporter_create * * Creates a Word exporter object. Guaranteed to return * an exporter for the most recent supported MSWord DOC version. * This means that this function currently creates a Word97/2000 * exporter object (WORD8) * * @filename - file on disk to create * @returns NULL on error, or a valid wvExporter on success */ wvExporter * wvExporter_create (const char *filename) { return wvExporter_create_version (filename, WORD8); } /** * wvExporter_close * * Closes and saves the MSWord document * * @exp - an exporter created by wvExporter_create() */ void wvExporter_close (wvExporter * exp) { if (exp == NULL) { wvError (("Exporter can't be null\n")); return; } switch (wvExporter_getVersion (exp)) { case WORD8: exporter_close_word8 (exp); break; default: wvError (("Closing wvExporter with an invalid version\n")); break; } wvTrace (("Word Document Written!\n")); } /**************************************************************************/ /** All of the wvExporter_summaryXXX functions share 1 common **/ /** Characteristic: only the final call for any given value is of any **/ /** Importance. This means that one could call **/ /** wvExporter_summaryPutString() for PID_TITLE 35 times, but only the **/ /** Final call means anything **/ /**************************************************************************/ /** * wvExporter_summaryPutString * * @exp - a valid wvExporter created by wvExporter_create() * @field - summary stream id key PID_XXX from "wv.h" * @str - string to put into the summary stream * * If field isn't a valid value or isn't valid for the given * type (i.e. string), this function will return 0 * * @returns 1 on success, 0 on failure */ S8 wvExporter_summaryPutString (wvExporter * exp, U32 field, const char *str) { if (exp == NULL) { wvError (("Exporter can't be null\n")); return 0; } if (str == NULL) { wvError (("String can't be null\n")); return 0; } switch (field) { /* summary stream */ case PID_TITLE: ASSIGN_STRING (exp->summary->title, str); break; case PID_SUBJECT: ASSIGN_STRING (exp->summary->subject, str); break; case PID_AUTHOR: ASSIGN_STRING (exp->summary->author, str); break; case PID_KEYWORDS: ASSIGN_STRING (exp->summary->keywords, str); break; case PID_COMMENTS: ASSIGN_STRING (exp->summary->comments, str); break; case PID_TEMPLATE: ASSIGN_STRING (exp->summary->template, str); break; case PID_LASTAUTHOR: ASSIGN_STRING (exp->summary->lastauthor, str); break; case PID_REVNUMBER: ASSIGN_STRING (exp->summary->revnumber, str); break; case PID_APPNAME: ASSIGN_STRING (exp->summary->appname, str); break; default: wvError (("Unhandled type: %d\n", field)); return 0; } return 1; } /** * wvExporter_summaryPutLong * * @exp - a valid wvExporter created by wvExporter_create() * @field - summary stream id key PID_XXX from "wv.h" * @l - long value * * If field isn't a valid value or isn't valid for the given * type (i.e. long), this function will return 0 * * @returns 1 on success, 0 on failure */ S8 wvExporter_summaryPutLong (wvExporter * exp, U32 field, U32 l) { if (exp == NULL) { wvError (("Exporter can't be null\n")); return 0; } switch (field) { /* summary stream */ case PID_PAGECOUNT: exp->summary->pagecount = l; break; case PID_WORDCOUNT: exp->summary->wordcount = l; break; case PID_CHARCOUNT: exp->summary->charcount = l; break; case PID_SECURITY: exp->summary->security = l; break; case PID_THUMBNAIL: exp->summary->thumbnail = l; break; default: wvError (("Unhandled type: %d\n", field)); return 0; } return 1; } /** * wvExporter_summaryPutTime * * @exp - a valid wvExporter created by wvExporter_create() * @field - summary stream id key PID_XXX from "wv.h" * @t - UNIX time_t value * * If @field isn't a valid value or isn't valid for the given * type (i.e. time_t), this function will return 0 * * @returns 1 on success, 0 on failure */ S8 wvExporter_summaryPutTime (wvExporter * exp, U32 field, time_t t) { if (exp == NULL) { wvError (("Exporter can't be null\n")); return 0; } switch (field) { /* summary stream only */ case PID_TOTAL_EDITTIME: exp->summary->total_edittime = t; break; case PID_LASTPRINTED: exp->summary->lastprinted = t; break; case PID_CREATED: exp->summary->created = t; break; case PID_LASTSAVED: exp->summary->lastsaved = t; break; default: wvError (("Unhandled type: %d\n", field)); return 0; } return 1; } /**************************************************************************/ /**************************************************************************/ /** * wvExporter_writeChars * * If you're worried, use wvExporter_writeBytes instead. * * Writes the string @chars to the Word document @exp * You should be passing UTF8 here * * @returns number of chars written */ size_t wvExporter_writeChars (wvExporter * exp, const U8 * chars) { if (exp == NULL) { wvError (("Exporter can't be NULL\n")); return 0; } if (chars == NULL) { wvError (("I won't write a NULL string\n")); return 0; } return wvExporter_writeBytes (exp, sizeof (U8), strlen ((const char *)chars), (const void *) chars); } /** * wvExporter_writeBytes * * Should be UTF-safe * * Writes @nmemb members from the array of @bytes of * in size @sz chunks to the @exp word document * * @returns number of bytes written */ size_t wvExporter_writeBytes (wvExporter * exp, size_t sz, size_t nmemb, const void *bytes) { size_t nwr = 0; if (exp == NULL) { wvError (("Exporter can't be NULL\n")); return 0; } if (sz == 0) { wvError (("Attempting to write an array of zero size items? WTF?\n")); return 0; } if (nmemb == 0) { /* not so bad I guess */ wvTrace (("Zero bytes passed to writeBytes\n")); return 0; } if (bytes == 0) { /* TODO: is this an error? */ wvTrace (("NULL array passed to writeBytes\n")); return 0; } /* write the bytes and update the FIB */ nwr = wvStream_write ((void *) bytes, sz, nmemb, exp->documentStream); exp->fib.fcMac = wvStream_tell (exp->documentStream) + 1; wvTrace (("Wrote %d byte(s)\n", nwr)); return nwr; } /** * wvExporter_flush * * Flushes any data possibly stored in the exporter's * Internal buffers. * * @exp - an exporter created by wvExporter_create */ void wvExporter_flush (wvExporter * exp) { if (!exp) { wvError (("Cannot flush a null exporter object\n")); return; } /* this will be a noop indefinitely */ } /**************************************************************************/ /**************************************************************************/ /** * wvExporter_pushPAP * * This function closes any previous paragraph, if any are * Open, and begins a new paragraph with the properties * Contained in @apap * * @returns 1 on success, 0 if not */ S8 wvExporter_pushPAP (wvExporter * exp, PAP * apap) { if (!exp) { wvError (("NULL exporter\n")); return 0; } if (!apap) { wvError (("NULL PAP!\n")); return 0; } /* noop for now */ return 1; } /** * wvExporter_pushCHP * * This function closes any previous character run, if any are * Open, and begins a new character run with the properties * Contained in @achp * * @returns 1 on success, 0 if not */ S8 wvExporter_pushCHP (wvExporter * exp, CHP * achp) { if (!exp) { wvError (("NULL exporter\n")); return 0; } if (!achp) { wvError (("NULL CHP!\n")); return 0; } /* noop for now */ return 1; } /** * wvExporter_pushSEP * * This function closes any previous section, if any are * Open, and begins a new section with the properties * Contained in @asep * * @returns 1 on success, 0 if not */ S8 wvExporter_pushSEP (wvExporter * exp, SEP * asep) { if (!exp) { wvError (("NULL exporter\n")); return 0; } if (!asep) { wvError (("NULL SEP!\n")); return 0; } /* noop for now */ return 1; } /**************************************************************************/ /** Static implementation functions **/ /**************************************************************************/ static void assign_string (char **a, const char *b) { int len = 0; if (!b) return; if (*a) wvFree (*a); len = strlen (b); (*a) = (char *) wvMalloc (sizeof (char) * (len + 1)); strcpy (*a, b); (*a)[len] = 0; } static void write_ole_summary (OleSummaryData * data, MsOleSummary * sum) { SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_TITLE, data->title); SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_SUBJECT, data->subject); SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_AUTHOR, data->author); SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_KEYWORDS, data->keywords); SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_COMMENTS, data->comments); SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_TEMPLATE, data->template); SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_LASTAUTHOR, data->lastauthor); SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_REVNUMBER, data->revnumber); SUMMARY_SET_STRING (sum, MS_OLE_SUMMARY_APPNAME, data->appname); ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_PAGECOUNT, data->pagecount); ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_WORDCOUNT, data->wordcount); ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_CHARCOUNT, data->charcount); ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_SECURITY, data->security); ms_ole_summary_set_long (sum, MS_OLE_SUMMARY_THUMBNAIL, data->thumbnail); #if 0 /* TODO: the time types are not currently supported */ #endif } static wvExporter * exporter_create_word8 (const char *filename) { wvExporter *exp = NULL; MsOle *ole = NULL; if (filename == NULL) { wvError (("Error: file name can't be null\n")); return NULL; } /* first allocate the exporter object, initialized to 0's */ exp = (wvExporter *) calloc (1, sizeof (wvExporter)); if (!exp) { wvError (("Error allocating memory for the exporter\n")); return NULL; } if (ms_ole_create ((MsOle **) (&ole), filename) != MS_OLE_ERR_OK) { wvError (("Error creating OLE docfile %s\n", filename)); wvFree (ole); wvFree (exp); return NULL; } wvTrace (("Created OLE\n")); exp->ole = (wvDocument *) ole; /* now to initialize the streams */ exp->documentStream = wvStream_new (ole, DOCUMENT_STREAM); ASSERT_STREAM_CREATED (exp->documentStream); exp->table0Stream = wvStream_new (ole, TABLE0_STREAM); ASSERT_STREAM_CREATED (exp->table0Stream); exp->table1Stream = wvStream_new (ole, TABLE1_STREAM); ASSERT_STREAM_CREATED (exp->table1Stream); exp->dataStream = wvStream_new (ole, DATA_STREAM); ASSERT_STREAM_CREATED (exp->dataStream); wvTrace (("Created all relevant OLE streams\n")); /* initialize the FIB and put it into the document stream * this is fine to do, since we're going to rewind the * stream, and put an updated FIB in the stream on * wvExporter_close() anyway */ wvInitFIBForExport (&(exp->fib)); wvPutFIB (&(exp->fib), exp->documentStream); wvTrace ( ("Initial FIB inserted at: %d (%d)\n", wvStream_tell (exp->documentStream), (wvStream_tell (exp->documentStream) - sizeof (FIB)))); /* in all of the document's i've run into, the fcMin == 1024 */ exp->fib.fcMin = wvStream_tell (exp->documentStream); exp->ver = WORD8; exp->summary = (OleSummaryData *) calloc (1, sizeof (OleSummaryData)); return exp; } static void exporter_close_word8 (wvExporter * exp) { MsOleSummary *sum; wvExporter_flush (exp); /* rewind and put the updated FIB in its proper place */ /* last character's position + 1 */ /* exp->fib.cbMac = wvStream_tell(exp->documentStream) + 1; */ exp->fib.ccpText = exp->fib.cbMac - exp->fib.fcMin; wvStream_rewind (exp->documentStream); wvPutFIB (&(exp->fib), exp->documentStream); wvTrace (("Re-inserted FIB into document at: %d\n", wvStream_tell (exp->documentStream))); /* * Close all of the streams * TODO: make this a function instead of a macro */ WVSTREAM_CLOSE ((exp->documentStream)); WVSTREAM_CLOSE ((exp->table1Stream)); WVSTREAM_CLOSE ((exp->table0Stream)); WVSTREAM_CLOSE ((exp->dataStream)); wvTrace (("Closed all of the main streams\n")); /* * Close the summary streams */ sum = ms_ole_summary_create (exp->ole); write_ole_summary (exp->summary, sum); ms_ole_summary_close (sum); wvTrace (("Wrote summary stream(s)\n")); /* close the document */ ms_ole_destroy (&(exp->ole)); wvTrace (("Closed all of the streams and OLE\n")); wvFree (exp->summary); wvFree (exp); exp = NULL; }