//////////////////////////////////////////////////////////////////////////////// // // Propio.c // // MS Office Properties IO // // Notes: // Because the Document Summary and User-defined objects both store // their data in one stream (different sections though), one of these // needs to also be responsible for saving any other sections that // we don't understand at this time. The rule used here is that // if the Document Summary object exists, it will store the // unknown data, otherwise the User-defined object will. // // Change history: // // Date Who What // -------------------------------------------------------------------------- // 07/26/94 B. Wentz Created file // 07/08/96 MikeHill Add all properties to the UDProp list // (not just props that are UDTYPEs). // //////////////////////////////////////////////////////////////////////////////// #include "priv.h" #pragma hdrstop #include // for sprintf #include #ifdef DEBUG #define typSI 0 #define typDSI 1 #define typUD 2 typedef struct _xopro { int typ; union{ LPSIOBJ lpSIObj; LPDSIOBJ lpDSIObj; LPUDOBJ lpUDObj; }; } XOPRO; // Plex of xopros DEFPL (PLXOPRO, XOPRO, ixoproMax, ixoproMac, rgxopro); #endif // The constant indicating that the object uses Intel byte-ordering. #define wIntelByteOrder 0xFFFE #ifndef CP_WINUNICODE #define CP_WINUNICODE 1200 #endif // The name of the Document Summary Information stream. const GUID FMTID_SummaryInformation = {0xf29f85e0L,0x4ff9,0x1068,0xab,0x91,0x08,0x00,0x2b,0x27,0xb3,0xd9}; const GUID FMTID_DocumentSummaryInformation = {0xd5cdd502L,0x2e9c,0x101b,0x93,0x97,0x08,0x00,0x2b,0x2c,0xf9,0xae}; const GUID FMTID_UserDefinedProperties = {0xd5cdd505L,0x2e9c,0x101b,0x93,0x97,0x08,0x00,0x2b,0x2c,0xf9,0xae}; // Internal prototypes static DWORD PASCAL DwLoadDocAndUser (LPDSIOBJ lpDSIObj, LPUDOBJ lpUDObj, LPSTORAGE lpStg, DWORD dwFlags, BOOL fIntOnly); static DWORD PASCAL DwSaveDocAndUser (LPDSIOBJ lpDSIObj, LPUDOBJ lpUDObj, LPSTORAGE lpStg, DWORD dwFlags); static DWORD PASCAL DwLoadPropSetRange (LPPROPERTYSETSTORAGE lpPropertySetStorage, REFFMTID pfmtid, UINT FAR * lpuCodePage, PROPID propidFirst, PROPID propidLast, PROPVARIANT rgpropvar[], DWORD grfStgMode); static DWORD PASCAL DwSavePropSetRange (LPPROPERTYSETSTORAGE lpPropertySetStorage, UINT uCodePage, REFFMTID pfmtid, PROPID propidFirst, PROPID propidLast, PROPVARIANT rgpropvarOriginal[], PROPID propidSkip, DWORD grfStgMode); static BOOL PASCAL FReadDocParts(LPSTREAM lpStm, LPDSIOBJ lpDSIObj); static BOOL PASCAL FReadAndInsertDocParts(LPSTREAM lpStm, LPDSIOBJ lpDSIObj); static BOOL PASCAL FReadHeadingPairs(LPSTREAM lpStm, LPDSIOBJ lpDSIObj); static BOOL PASCAL FReadAndInsertHeadingPairs(LPSTREAM lpStm, LPDSIOBJ lpDSIObj); static BOOL PASCAL FLoadUserDef(LPUDOBJ lpUDObj, LPPROPERTYSETSTORAGE lpPropertySetStorage, UINT *puCodePage, BOOL fIntOnly, DWORD grfStgMode); static BOOL PASCAL FSaveUserDef(LPUDOBJ lpUDObj, LPPROPERTYSETSTORAGE lpPropertySetStorage, UINT uCodePage, DWORD grfStgMode ); BOOL OFC_CALLBACK FCPConvert( LPTSTR lpsz, DWORD dwFrom, DWORD dwTo, BOOL fMacintosh ) { return TRUE; } BOOL OFC_CALLBACK FSzToNum(double *lpdbl, LPTSTR lpsz) { LPTSTR lpDec; LPTSTR lpTmp; double mult; // // First, find decimal point // for (lpDec = lpsz; *lpDec && *lpDec!=TEXT('.'); lpDec++) { ; } *lpdbl = 0.0; mult = 1.0; // // Do integer part // for (lpTmp = lpDec - 1; lpTmp >= lpsz; lpTmp--) { // // check for negative sign // if (*lpTmp == TEXT('-')) { // // '-' sign should only be at beginning of string // if (lpTmp == lpsz) { if (*lpdbl > 0.0) { *lpdbl *= -1.0; } continue; } else { *lpdbl = 0.0; return FALSE; } } // // check for positive sign // if (*lpTmp == TEXT('+')) { // // '+' sign should only be at beginning of string // if (lpTmp == lpsz) { if (*lpdbl < 0.0) { *lpdbl *= -1.0; } continue; } else { *lpdbl = 0.0; return FALSE; } } if ( (*lpTmp < TEXT('0')) || (*lpTmp > TEXT('9')) ) { *lpdbl = 0.0; return FALSE; } *lpdbl += (mult * (double)(*lpTmp - TEXT('0'))); mult *= 10.0; } // // Do decimal part // mult = 0.1; if (*lpDec) { for (lpTmp = lpDec + 1; *lpTmp; lpTmp++) { if ((*lpTmp < TEXT('0')) || (*lpTmp > TEXT('9'))) { *lpdbl = 0.0; return FALSE; } *lpdbl += (mult * (double)(*lpTmp - TEXT('0'))); mult *= 0.1; } } return TRUE; } BOOL OFC_CALLBACK FNumToSz(double *lpdbl, LPTSTR lpsz, DWORD cbMax) { #ifdef UNICODE swprintf(lpsz, TEXT("%g"), *(double *) lpdbl); #else sprintf(lpsz, TEXT("%g"), *(double *) lpdbl); #endif return TRUE; } BOOL OFC_CALLBACK FUpdateStats(HWND hwndParent, LPSIOBJ lpSIObj, LPDSIOBJ lpDSIObj) { return TRUE; } const void *rglpfnProp[] = { (void *) FCPConvert, (void *) FSzToNum, (void *) FNumToSz, (void *) FUpdateStats }; //////////////////////////////////////////////////////////////////////////////// // // FOfficeCreateAndInitObjects // // Purpose: // Creates and initializes all non-null args. // //////////////////////////////////////////////////////////////////////////////// DLLFUNC BOOL OFC_CALLTYPE FOfficeCreateAndInitObjects(LPSIOBJ *lplpSIObj, LPDSIOBJ *lplpDSIObj, LPUDOBJ *lplpUDObj) { #ifdef _ABBREVIATED_DOCPROP_ if (!FUserDefCreate (lplpUDObj, rglpfnProp)) #else //_ABBREVIATED_DOCPROP_ if (!FSumInfoCreate (lplpSIObj, rglpfnProp) || !FDocSumCreate (lplpDSIObj, rglpfnProp) || !FUserDefCreate (lplpUDObj, rglpfnProp)) #endif //_ABBREVIATED_DOCPROP_ { FOfficeDestroyObjects(lplpSIObj, lplpDSIObj, lplpUDObj); return FALSE; } return TRUE; } // FOfficeCreateAndInitObjects //////////////////////////////////////////////////////////////////////////////// // // FOfficeClearObjects // // Purpose: // Clear any non-null objects // //////////////////////////////////////////////////////////////////////////////// DLLFUNC BOOL OFC_CALLTYPE FOfficeClearObjects ( LPSIOBJ lpSIObj, LPDSIOBJ lpDSIObj, LPUDOBJ lpUDObj) { #ifndef _ABBREVIATED_DOCPROP_ FSumInfoClear (lpSIObj); FDocSumClear (lpDSIObj); #endif //_ABBREVIATED_DOCPROP_ FUserDefClear (lpUDObj); return TRUE; } // FOfficeClearObjects #ifdef DEBUG int CmpXOpro(XOPRO *pxopro1, XOPRO *pxopro2) { if(pxopro1->typ==pxopro2->typ) { switch(pxopro1->typ) { case typSI: if(pxopro1->lpSIObj==pxopro2->lpSIObj) return(sgnEQ); break; case typDSI: if(pxopro1->lpDSIObj==pxopro2->lpDSIObj) return(sgnEQ); break; case typUD: if(pxopro1->lpUDObj==pxopro2->lpUDObj) return(sgnEQ); break; default: Assert(fFalse); break; } } return(sgnNE); } #endif //////////////////////////////////////////////////////////////////////////////// // // FOfficeDestroyObjects // // Purpose: // Destroy any non-null objects // //////////////////////////////////////////////////////////////////////////////// DLLFUNC BOOL OFC_CALLTYPE FOfficeDestroyObjects ( LPSIOBJ *lplpSIObj, LPDSIOBJ *lplpDSIObj, LPUDOBJ *lplpUDObj) { #ifndef _ABBREVIATED_DOCPROP_ FSumInfoDestroy (lplpSIObj); // We don't care what these guys return FDocSumDestroy (lplpDSIObj); #endif //_ABBREVIATED_DOCPROP_ FUserDefDestroy (lplpUDObj); return TRUE; } // FOfficeDestroyObjects //////////////////////////////////////////////////////////////////////////////// // // DwOfficeLoadProperties // // Purpose: // Populate the objects with data. lpStg is the root stream. // //////////////////////////////////////////////////////////////////////////////// UINT gdwFileCP = CP_ACP; DLLFUNC DWORD OFC_CALLTYPE DwOfficeLoadProperties ( LPSTORAGE lpStg, // Pointer to root storage LPSIOBJ lpSIObj, // Pointer to Summary Obj LPDSIOBJ lpDSIObj, // Pointer to Document Summary obj LPUDOBJ lpUDObj, // Pointer to User-defined Obj DWORD dwFlags, // Flags DWORD grfStgMode) // STGM flags with which to open the property set { HRESULT hr = E_FAIL; BOOL fSuccess = FALSE; LPPROPERTYSETSTORAGE lpPropertySetStorage = NULL; // Validate the inputs. if (lpStg == NULL) goto Exit; // Get the IPropertySetStorage from the IStorage. hr = lpStg->lpVtbl->QueryInterface( lpStg, &IID_IPropertySetStorage, &lpPropertySetStorage ); if (FAILED (hr)) { AssertSz (0, TEXT("Couldn't query for IPropertySetStorage")); goto Exit; } #ifndef _ABBREVIATED_DOCPROP_ if (lpSIObj != NULL) { // Make sure we start with an empty object. FSumInfoClear (lpSIObj); // This will set the save flag OfficeDirtySIObj(lpSIObj, FALSE); // so clear it. Bug 1068 // Load the properties into an array of PropVariants if (MSO_IO_SUCCESS != DwLoadPropSetRange (lpPropertySetStorage, &FMTID_SummaryInformation, &gdwFileCP, PID_SIFIRST, PID_SILAST, GETSINFO(lpSIObj)->rgpropvar, grfStgMode)) { AssertSz (0, TEXT("Could not load SummaryInformation property set")); goto Exit; } if (GETSINFO(lpSIObj)->rgpropvar[ PVSI_THUMBNAIL ].vt == VT_CF) { GETSINFO(lpSIObj)->fSaveSINail = TRUE; #ifdef SHELL // We don't need the thumbnail, we just want to know if it exists. // So, we can free the memory. PropVariantClear( &GETSINFO(lpSIObj)->rgpropvar[ PVSI_THUMBNAIL ] ); #endif } OfficeDirtySIObj (lpSIObj, FALSE); } #endif //_ABBREVIATED_DOCPROP_ #ifndef _ABBREVIATED_DOCPROP_ if (lpDSIObj != NULL) { // Make sure we start with an empty object. FDocSumClear (lpDSIObj); OfficeDirtyDSIObj(lpDSIObj, FALSE); if (MSO_IO_SUCCESS != DwLoadPropSetRange (lpPropertySetStorage, &FMTID_DocumentSummaryInformation, &gdwFileCP, PID_DSIFIRST, PID_DSILAST, GETDSINFO(lpDSIObj)->rgpropvar, grfStgMode)) { AssertSz (0, TEXT("Could not load DocSumInfo")); goto Exit; } OfficeDirtyDSIObj (lpDSIObj, FALSE); } #endif // _ABBREVIATED_DOCPROP_ if (lpUDObj != NULL) { // Make sure we start with an empty object. FUserDefClear (lpUDObj); OfficeDirtyUDObj(lpUDObj, FALSE); // Load the properties into a linked-list. if (!FLoadUserDef (lpUDObj, lpPropertySetStorage, &gdwFileCP, FALSE, // Not integers only. grfStgMode)) { MESSAGE (TEXT("Could not load User-Defined properties")); goto Exit; } OfficeDirtyUDObj (lpUDObj, FALSE); } // If none of the property sets had a code-page property, set it to // the current system default. if (gdwFileCP == CP_ACP) gdwFileCP = GetACP(); fSuccess = TRUE; Exit: RELEASEINTERFACE( lpPropertySetStorage ); if (fSuccess) { return (MSO_IO_SUCCESS); } else { DebugHr (hr); FOfficeClearObjects (lpSIObj, lpDSIObj, lpUDObj); #ifndef _ABBREVIATED_DOCPROP_ OfficeDirtySIObj (lpSIObj, FALSE); OfficeDirtyDSIObj (lpDSIObj, FALSE); #endif //_ABBREVIATED_DOCPROP_ OfficeDirtyUDObj (lpUDObj, FALSE); return (MSO_IO_ERROR); } } // DwOfficeLoadProperties //////////////////////////////////////////////////////////////////////////////// // // DwOfficeLoadIntProperties // // Purpose: // Populate the objects with integer data. lpStg is the root stream. // //////////////////////////////////////////////////////////////////////////////// #ifdef UNUSED DLLFUNC DWORD OFC_CALLTYPE DwOfficeLoadIntProperties ( LPSTORAGE lpStg, // Pointer to root storage LPSIOBJ lpSIObj, // Pointer to Summary Obj LPDSIOBJ lpDSIObj, // Pointer to Document Summary obj LPUDOBJ lpUDObj, // Pointer to User-defined Obj DWORD dwFlags) // Flags { DWORD dwLoad1 = MSO_IO_ERROR; DWORD dwLoad2 = MSO_IO_ERROR; if (lpStg == NULL) { return FALSE; } #ifndef _ABBREVIATED_DOCPROP_ if (lpSIObj != NULL) { dwLoad1 = DwOfficeLoadSumInfo (lpSIObj, lpStg, dwFlags, TRUE); if (dwLoad1 == MSO_IO_ERROR) { return(MSO_IO_ERROR); } } #endif //_ABBREVIATED_DOCPROP_ #ifndef _ABBREVIATED_DOCPROP_ if (lpDSIObj != NULL && lpUDObj != NULL) { dwLoad2 = DwLoadDocAndUser (lpDSIObj, lpUDObj, lpStg, dwFlags, TRUE); if (dwLoad2 == MSO_IO_ERROR) { if (lpSIObj != NULL) { FSumInfoClear(lpSIObj); } return(MSO_IO_ERROR); } } #endif //_ABBREVIATED_DOCPROP_ return(max(dwLoad1, dwLoad2)); } // DwOfficeLoadIntProperties #endif //////////////////////////////////////////////////////////////////////////////// // // DwOfficeSaveProperties // // Purpose: // Write the data in the given objects. lpStg is the root stream. // //////////////////////////////////////////////////////////////////////////////// DLLFUNC DWORD OFC_CALLTYPE DwOfficeSaveProperties ( LPSTORAGE lpStg, // Pointer to root storage LPSIOBJ lpSIObj, // Pointer to Summary Obj LPDSIOBJ lpDSIObj, // Pointer to Document Summary obj LPUDOBJ lpUDObj, // Pointer to User-defined Obj DWORD dwFlags, // Flags DWORD grfStgMode) // STGM flags with which to open the property set { // ------ // Locals // ------ HRESULT hr = E_FAIL; BOOL fSuccess = FALSE; LPPROPERTYSETSTORAGE lpPropertySetStorage = NULL; // Validate the inputs. if (lpStg == NULL) { AssertSz (0, TEXT("Invalid inputs to DwOfficeSaveProperties")); goto Exit; } // Get the IPropertySetStorage from the IStorage. hr = lpStg->lpVtbl->QueryInterface( lpStg, &IID_IPropertySetStorage, &lpPropertySetStorage ); if (FAILED (hr)) { AssertSz (0, TEXT("Couldn't query for IPropertySetStorage")); goto Exit; } #ifndef _ABBREVIATED_DOCPROP_ // --------------------------------------- // Save the SummaryInformation properties. // --------------------------------------- if (lpSIObj != NULL) { // Only save if the user didn't specify the change only flag, // or if they did, only save if we need to. if ( !(dwFlags & OIO_SAVEIFCHANGEONLY) || FSumInfoShouldSave(lpSIObj) ) { if (MSO_IO_SUCCESS != DwSavePropSetRange ( lpPropertySetStorage, // The property set GetACP(), // The code page &FMTID_SummaryInformation, // The format ID PID_SIFIRST, // The first PID to use PID_SILAST, // The last PID to use GETSINFO(lpSIObj)->rgpropvar, // The properties // Skip the thumbnail if not saving GETSINFO(lpSIObj)->fSaveSINail ? 0 : PID_THUMBNAIL, grfStgMode)) // STGM flags { AssertSz (0, TEXT("Could not save SummaryInformation property set")); goto Exit; } } } #endif //_ABBREVIATED_DOCPROP_ #ifndef _ABBREVIATED_DOCPROP_ // ----------------------------------------------- // Save the DocumentSummaryInformation properties. // ----------------------------------------------- if (lpDSIObj != NULL) { if (((dwFlags & OIO_SAVEIFCHANGEONLY) && (FDocSumShouldSave (lpDSIObj))) || !(dwFlags & OIO_SAVEIFCHANGEONLY)) { if (MSO_IO_SUCCESS != DwSavePropSetRange (lpPropertySetStorage, // The property set GetACP(), // The code page // The format ID &FMTID_DocumentSummaryInformation, PID_DSIFIRST, // The first PID to use PID_DSILAST, // The last PID to use // The properties GETDSINFO(lpDSIObj)->rgpropvar, 0, // Don't skip any properties grfStgMode)) // STGM flags { AssertSz (0, TEXT("Could not save DocSumInfo")); goto Exit; } } } #endif _ABBREVIATED_DOCPROP_ // --------------------------------- // Save the User-Defined properties. // --------------------------------- if (lpUDObj != NULL) { if (((dwFlags & OIO_SAVEIFCHANGEONLY) && (FUserDefShouldSave (lpUDObj))) || !(dwFlags & OIO_SAVEIFCHANGEONLY)) { if (!FSaveUserDef (lpUDObj, lpPropertySetStorage, GetACP(), grfStgMode)) { AssertSz (0, TEXT("Could not save UserDefined properties")); goto Exit; } } } // // Exit // fSuccess = TRUE; Exit: RELEASEINTERFACE( lpPropertySetStorage ); if (fSuccess) { #ifndef _ABBREVIATED_DOCPROP_ OfficeDirtySIObj (lpSIObj, FALSE); OfficeDirtyDSIObj (lpDSIObj, FALSE); #endif //_ABBREVIATED_DOCPROP_ OfficeDirtyUDObj (lpUDObj, FALSE); return (TRUE); } else { DebugHr (hr); return (FALSE); } } // DwOfficeSaveProperties /////////////////////////////////////////////////////// // // DwLoadPropSetRange // // Purpose: // Load a range of properties (specified by the first and // last property ID) from a given PropertySetStorage. All // strings are converted to the appropriate system format // (LPTSTRs). // // Inputs: // LPPROPERTYSETSTORAGE - The set of property storage objects. // REFFMTID - The Format ID of the desired property set // UINT * - A location to put the PID_CODEPAGE. This // should be initialized by the caller to a // valid default, in case the PID_CODEPAGE // does not exist. // PROPID - The first property in the range. // PROPID - The last property in the range. // PROPVARIANT[] - An array of PropVariants, large enough // for the (pidLast-pidFirst+1) properties. // DWORD - Flags from the STGM enumeration to use when // opening the property storage. // // Output: // An MSO error code. // // Note: // When strings are converted to the system format, their // VarTypes are converted too. E.g., if an ANSI VT_LPSTR is // read from a property set, the string will be converted // to Unicode, and the VarType will be changed to VT_LPWSTR. // //////////////////////////////////////////////////////////////////////////////// static DWORD PASCAL DwLoadPropSetRange ( LPPROPERTYSETSTORAGE lpPropertySetStorage, REFFMTID pfmtid, UINT FAR * lpuCodePage, PROPID propidFirst, PROPID propidLast, PROPVARIANT rgpropvar[], DWORD grfStgMode) { // ------ // Locals // ------ DWORD dwResult = MSO_IO_ERROR; // The return code. HRESULT hr; // OLE errors ULONG ulIndex; // Index into the rgpropvar // The requested IPropertyStorage LPPROPERTYSTORAGE lpPropertyStorage; PROPSPEC FAR * rgpropspec; // The PropSpecs for the ReadMultiple PROPVARIANT propvarCodePage; // A PropVariant with which to read the PID_CODEPAGE // The total number of properties to read. ULONG cProps = propidLast - propidFirst + 1; // ---------- // Initialize // ---------- Assert (lpPropertySetStorage != NULL); Assert (lpPropertySetStorage->lpVtbl != NULL); Assert (propidLast >= propidFirst); lpPropertyStorage = NULL; PropVariantInit( &propvarCodePage ); // Initialize the PropVariants, so that if we // early-exit, we'll return VT_EMPTY for all the properties. for (ulIndex = 0; ulIndex < cProps; ulIndex++) PropVariantInit (&rgpropvar[ulIndex]); // Allocate an array of PropSpecs. rgpropspec = PvMemAlloc ( cProps * sizeof (*rgpropspec)); if (rgpropspec == NULL) { AssertSz (0, TEXT("Couldn't alloc rgpropspec")); goto Exit; } // ---------------------- // Open the property set. // ---------------------- hr = lpPropertySetStorage->lpVtbl->Open( lpPropertySetStorage, // this pointer pfmtid, // Identifies propset grfStgMode, // STGM_ flags &lpPropertyStorage ); // Result if (FAILED(hr)) { // We couldn't open the property set. if( hr == STG_E_FILENOTFOUND ) { // No problem, it just didn't exist. dwResult = MSO_IO_SUCCESS; goto Exit; } else { AssertSz (0, TEXT("Couldn't open property set")); goto Exit; } } // ------------------- // Read the properties // ------------------- // Initialize the local PropSpec array in preparation for a ReadMultiple. // The PROPIDs range from propidFirst to propidLast. for (ulIndex = 0; ulIndex < cProps; ulIndex++) { rgpropspec[ ulIndex ].ulKind = PRSPEC_PROPID; rgpropspec[ ulIndex ].propid = ulIndex + propidFirst; } // Read in the properties hr = lpPropertyStorage->lpVtbl->ReadMultiple ( lpPropertyStorage, // 'this' pointer cProps, // count rgpropspec, // Props to read rgpropvar); // Buffers for props // Did we fail to read anything? if (hr != S_OK) { // If S_FALSE, no problem; none of the properties existed. if (hr == S_FALSE) { dwResult = MSO_IO_SUCCESS; goto Exit; } else { // Otherwise, we have a problem. AssertSz (0, TEXT("Couldn't read from property set")); goto Exit; } } // ----------------- // Get the Code-Page // ----------------- rgpropspec[0].ulKind = PRSPEC_PROPID; rgpropspec[0].propid = PID_CODEPAGE; hr = lpPropertyStorage->lpVtbl->ReadMultiple ( lpPropertyStorage, // 'this' pointer 1, // count rgpropspec, // Props to read &propvarCodePage); // Buffer for prop // We only set the code page if we actually read it. if (hr == S_OK && propvarCodePage.vt == VT_I2) { *lpuCodePage = propvarCodePage.iVal; } //*lpuCodePage = GetACP() ; // --------------------------- // Correct the string formats. // --------------------------- // E.g., if this is a Unicode system, convert LPSTRs to LPWSTRs. for (ulIndex = 0; ulIndex < cProps; ulIndex++) { // Is this is vector of Variants? if (rgpropvar[ ulIndex ].vt == (VT_VARIANT | VT_VECTOR)) { // Loop through each element of the vector, converting // any elements which are strings. ULONG ulVectorIndex; for (ulVectorIndex = 0; ulVectorIndex < rgpropvar[ ulIndex ].capropvar.cElems; ulVectorIndex++) { if (PROPVAR_STRING_CONVERSION_REQUIRED ( &rgpropvar[ulIndex].capropvar.pElems[ulVectorIndex], *lpuCodePage )) { // Convert the PropVariant string, putting it in a new // PropVariant. PROPVARIANT propvarConvert; PropVariantInit (&propvarConvert); if (!FPropVarConvertString (&propvarConvert, &rgpropvar[ulIndex].capropvar.pElems[ulVectorIndex], *lpuCodePage )) { AssertSz (0, TEXT("Couldn't convert string")); goto Exit; } // Clear the old PropVar, and copy in the new one. PropVariantClear (&rgpropvar[ulIndex].capropvar.pElems[ulVectorIndex]); rgpropvar[ulIndex].capropvar.pElems[ulVectorIndex] = propvarConvert; } } // for (ulVectorIndex = 0; ... } // if ((rgpropvar[ ulIndex ].vt == (VT_VARIANT | VT_VECTOR)) // This isn't a Variant Vector, but is it a string // of some kind which requires conversion? else if (PROPVAR_STRING_CONVERSION_REQUIRED ( &rgpropvar[ ulIndex ], *lpuCodePage)) { // Convert the PropVariant string into a new PropVariant // buffer. The string may be a singleton, or a vector. PROPVARIANT propvarConvert; PropVariantInit (&propvarConvert); if (!FPropVarConvertString (&propvarConvert, &rgpropvar[ ulIndex ], *lpuCodePage )) { AssertSz (0, TEXT("Couldn't convert string")); goto Exit; } // Free the old PropVar and load the new one. PropVariantClear (&rgpropvar[ ulIndex ]); rgpropvar[ ulIndex ] = propvarConvert; } // else if (PROPVAR_STRING_CONVERSION_REQUIRED ( ... } // for (ulIndex = 0; ulIndex < cProps; ulIndex++) // ---- // Exit // ---- dwResult = MSO_IO_SUCCESS; Exit: // Release the code-page just in case somebody put the wrong type // there (like a blob). PropVariantClear (&propvarCodePage); // Release the PropSpecs and the IPropertyStorage if (rgpropspec != NULL) { VFreeMemP (rgpropspec, cProps * sizeof (*rgpropspec)); } RELEASEINTERFACE (lpPropertyStorage); // If we failed, free the PropVariants. if (dwResult != MSO_IO_SUCCESS) { FreePropVariantArray( cProps, rgpropvar ); DebugHr( hr ); } return (dwResult); } // DwLoadPropSetRange //////////////////////////////////////////////////////////////////////////////// // // Wrap of IPropertySetStorage::Create // // Each new ANSI property set created by docprop must set PID_CODEPAGE to CP_UTF8 // to avoid ansi<->unicode roundtripping issues. // HRESULT _CreatePropertyStorage( LPPROPERTYSETSTORAGE psetstg, REFFMTID rfmtid, CLSID* pclsid, DWORD grfMode, UINT* /*IN OUT*/ puCodePage, IPropertyStorage** ppstg ) { DWORD grfFlags = (CP_WINUNICODE == (*puCodePage)) ? PROPSETFLAG_DEFAULT : PROPSETFLAG_ANSI; HRESULT hr = psetstg->lpVtbl->Create( psetstg, rfmtid, pclsid, grfFlags, grfMode, ppstg ); if( SUCCEEDED( hr ) ) { if( PROPSETFLAG_ANSI == grfFlags ) { PROPSPEC propspec = { PRSPEC_PROPID, PID_CODEPAGE }; PROPVARIANT varCP; varCP.vt = VT_I2; varCP.iVal = (SHORT)CP_UTF8; if( SUCCEEDED( (*ppstg)->lpVtbl->WriteMultiple( *ppstg, 1, &propspec, &varCP, PID_UDFIRST ) ) ) *puCodePage = (UINT)MAKELONG(varCP.iVal, 0); } } return hr; } /////////////////////////////////////////////////////// // // DwSavePropSetRange // // Purpose: // Save a range of properties to a Property Set Storage. // The properties to be saved are provided in an // array of PropVariants, and their property IDs are // specified by the first and last PID for the range. // The caller may also specify that a property be // "skipped", i.e., not written. // // Inputs: // LPPROPERTYSETSTORAGE - The Property Set Storage // UINT - The code page with which the strings // should be written. // REFFMTID - The GUID identifying the Property Storage // within the Property Set Storage. // PROPID - The PID to assign to the first property. // PROPID - The PID to assign to the last property // PROPVARIANT [] - The propeties to write. All strings // are assumed to be in the system format // (e.g. VT_LPWSTRs for NT). This array // is returned un-modified to the caller. // PROPID - If non-zero, identifies a property // which should not be written, even if // it is non-empty. If the property exists // in the property set, it will be deleted. // (This was added to provide a way to skip // the PID_THUMBNAIL.) // DWORD - Flags from the STGM enumeration to use when // opening the property storage. // // Output: // An MSO error code. // // Notes: // - If the code page is Unicode, all strings are written as LPWSTRs, // otherwise, they are written as LPSTRs. // - Only non-empty properties are written. // // Implementation: // This routine creates a new PropVariant array which is the // subset of the caller's PropVariant array which must actually // be written (i.e, it doesn't include the VT_EMPTY properties // or the 'propidSkip'). // // We allocate as little extra memory as possible. For example, // if we have to write a string, we'll copy the pointer to the // string into the subset PropVariant array. Thus we'll have // two pointers to the string. // // If a string to be written must be converted first (to another // code-page), then the original PropVariant array will continue // pointing to the original string, and the subset PropVariant // array will point to the converted string (and must consequently // be freed). // //////////////////////////////////////////////////////////////////////////////// static DWORD PASCAL DwSavePropSetRange ( LPPROPERTYSETSTORAGE lpPropertySetStorage, UINT uCodePage, REFFMTID pfmtid, PROPID propidFirst, PROPID propidLast, PROPVARIANT rgpropvarOriginal[], PROPID propidSkip, DWORD grfStgMode) { // ------ // Locals // ------ DWORD dwResult = MSO_IO_ERROR; // The functions return code. HRESULT hr; // OLE results. // The Property Storage to write to LPPROPERTYSTORAGE lpPropertyStorage = NULL; ULONG cOriginal; // The size of rgpropvarOriginal, ULONG cNew; // and the number which must actually be written. ULONG ulIndex; // Index into rgpropvarOriginal PROPSPEC FAR * rgpropspecNew = NULL;// PropSpecs for the WriteMultiple LPPROPVARIANT rgpropvarNew = NULL; // The sub-set of rgpropvarOrigianl we must write. // The following array has an entry for each entry in rgpropvarNew. // Each entry identifies the corresponding entry in rgpropvarOriginal. // E.g. rgMapNewToOriginal[0] is the index in rgpropvarOriginal of // the first property to be written. ULONG *rgMapNewToOriginal = NULL; // ---------- // Initialize // ---------- cOriginal = propidLast - propidFirst + 1; cNew = 0; Assert (cOriginal <= max(NUM_SI_PROPERTIES, NUM_DSI_PROPERTIES)); Assert (lpPropertySetStorage != NULL); Assert (lpPropertySetStorage->lpVtbl != NULL); Assert (propidLast >= propidFirst); Assert (rgpropvarOriginal != NULL); // Allocate an array of PropSpecs for the WriteMultiple. rgpropspecNew = PvMemAlloc ( cOriginal * sizeof (*rgpropspecNew)); if (rgpropspecNew == NULL) { AssertSz (0, TEXT("Couldn't alloc rgpropspecNew")); goto Exit; } // Allocate an array of PropVariants which will hold the subset // of the caller's properties which must be written. // Initialize to zeros so that we don't think we have memory // to free in the error path. rgpropvarNew = PvMemAlloc ( cOriginal * sizeof (*rgpropvarNew)); if (rgpropvarNew == NULL) { AssertSz (0, TEXT("Couldn't alloc rgpropvarNew")); goto Exit; } FillBuf (rgpropvarNew, 0, cOriginal * sizeof (*rgpropvarNew)); // Allocate the look-up-table which maps entries in rgpropvarNew // to rgpropvarOriginal rgMapNewToOriginal = PvMemAlloc (cOriginal * sizeof(*rgMapNewToOriginal)); if (rgMapNewToOriginal == NULL) { AssertSz (0, TEXT("Couldn't alloc rgMapNewToOriginal")); goto Exit; } // ------------------------- // Open the Property Storage // ------------------------- hr = lpPropertySetStorage->lpVtbl->Open( lpPropertySetStorage, // this pointer pfmtid, grfStgMode, &lpPropertyStorage ); // If it didn't exist, create it. if( hr == STG_E_FILENOTFOUND ) { hr = _CreatePropertyStorage( lpPropertySetStorage, pfmtid, NULL, STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE, &uCodePage, &lpPropertyStorage ); } // Check the result of the open/create. if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't open property set")); goto Exit; } // --------------------------------------------------- // Copy the properties to be written into rgpropvarNew // --------------------------------------------------- // Loop through all the properties in rgpropvarOriginal for (ulIndex = 0; ulIndex < cOriginal; ulIndex++) { // Is this property extant and not the one to skip? if (rgpropvarOriginal[ ulIndex ].vt != VT_EMPTY && ( propidSkip == 0 || propidSkip != propidFirst + ulIndex ) ) { // We have a property which must be written. BOOL fVector; VARTYPE vt; // Record a mapping from the new index to the original. rgMapNewToOriginal[ cNew ] = ulIndex; // Add an entry to the PropSpec array. rgpropspecNew[ cNew ].ulKind = PRSPEC_PROPID; rgpropspecNew[ cNew ].propid = propidFirst + ulIndex; // Get the underlying VarType. fVector = (rgpropvarOriginal[ ulIndex ].vt & VT_VECTOR) ? TRUE : FALSE; vt = rgpropvarOriginal[ ulIndex ].vt & ~VT_VECTOR; // If this property is a vector of variants, some of those // elements may be strings which need to be converted. if ((vt == VT_VARIANT) && fVector) { ULONG ulVectorIndex; // We'll inintialize the capropvar.pElems in rgpropvarNew // so that it points to the one in rgpropvarOriginal. We'll // only allocate if a conversion is necessary. I.e., we handle // pElems as a copy-on-write. rgpropvarNew[ cNew ] = rgpropvarOriginal[ ulIndex ]; // Loop through the elements of the vector. for (ulVectorIndex = 0; ulVectorIndex < rgpropvarNew[ cNew ].capropvar.cElems; ulVectorIndex++) { // Is this a string requiring a code-page conversion? if (PROPVAR_STRING_CONVERSION_REQUIRED( &rgpropvarOriginal[ulIndex].capropvar.pElems[ulVectorIndex], uCodePage )) { // We must convert this string. Have we allocated a pElems yet? if (rgpropvarNew[cNew].capropvar.pElems == rgpropvarOriginal[ulIndex].capropvar.pElems) { // Allocate a new pElems for rgpropvarNew rgpropvarNew[cNew].capropvar.pElems = CoTaskMemAlloc (rgpropvarNew[cNew].capropvar.cElems * sizeof(*rgpropvarNew[cNew].capropvar.pElems)); if (rgpropvarNew[cNew].capropvar.pElems == NULL) { AssertSz (0, TEXT("Couldn't allocate pElems")); goto Exit; } // Initialize it to match that in rgpropvarOriginal PbMemCopy (rgpropvarNew[cNew].capropvar.pElems, rgpropvarOriginal[ulIndex].capropvar.pElems, rgpropvarNew[cNew].capropvar.cElems * sizeof(*rgpropvarNew[cNew].capropvar.pElems)); } // Now, we can convert this string from rgpropvarOriginal into // rgpropvarNew. PropVariantInit (&rgpropvarNew[cNew].capropvar.pElems[ulVectorIndex]); if (!FPropVarConvertString(&rgpropvarNew[cNew].capropvar.pElems[ulVectorIndex], &rgpropvarOriginal[ulIndex].capropvar.pElems[ulVectorIndex], uCodePage)) { AssertSz(0, TEXT("Couldn't convert code page of string")); goto Exit; } } // if (PROPVAR_STRING_CONVERSION_REQUIRED( ... } // for (ulVectorIndex = 0; ... } // if (vt == VT_VARIANT && fVector) // This isn't a variant vector, but is it some type of string // property for which we must make a conversion? else if (PROPVAR_STRING_CONVERSION_REQUIRED ( &rgpropvarOriginal[ ulIndex ], uCodePage)) { PropVariantInit (&rgpropvarNew[cNew]); if (!FPropVarConvertString (&rgpropvarNew[cNew], &rgpropvarOriginal[ulIndex], uCodePage)) { AssertSz (0, TEXT("Couldn't convert string")); goto Exit; } } // else if (PROPVAR_STRING_CONVERSION_REQUIRED ( ... // If neither of the above special-cases were triggered, // then simply copy the PropVariant structure (but not // any referred-to data). We save memory by not duplicating // the referred-to data, but we must be careful in the exit // not to free it. else { rgpropvarNew[cNew] = rgpropvarOriginal[ulIndex]; } // if ((vt == VT_VARIANT) && fVector) ... else // We're done copying/converting this property from rgpropvarOriginal // into rgpropvarNew. cNew++; } // if (rgpropvarOriginal[ ulIndex ].vt != VT_EMPTY ... } // for (ulIndex = 0; ulIndex < cProps; ulIndex++) // ------------------------ // Write out the properties // ------------------------ // Write out properties if we found any. if (cNew > 0) { hr = lpPropertyStorage->lpVtbl->WriteMultiple ( lpPropertyStorage, // 'this' pointer cNew, // Count rgpropspecNew, // Props to write rgpropvarNew, // The props PID_UDFIRST); if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't write properties")); goto Exit; } } // if (cNew > 0) // --------------------- // Delete the propidSkip // --------------------- // If the caller specified a PID to skip, then it should // be deleted from the property set as well. if (propidSkip != 0) { rgpropspecNew[0].ulKind = PRSPEC_PROPID; rgpropspecNew[0].propid = propidSkip; hr = lpPropertyStorage->lpVtbl->DeleteMultiple ( lpPropertyStorage, // this pointer 1, // Delete one property rgpropspecNew ); // The prop to delete if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't delete the propidSkip")); goto Exit; } } // ---- // Exit // ---- dwResult = MSO_IO_SUCCESS; Exit: // Clear any of the properties in rgpropvarNew for which new // buffers were allocated. Then free the rgpropvarNew array itself. // We know that buffers were allocated for rgpropvarNew if it's contents // don't match rgpropvarOriginal. if (rgpropvarNew != NULL) { // Loop through rgpropvarNew for (ulIndex = 0; ulIndex < cNew; ulIndex++) { // Was memory allocated for this rgpropvarNew? if (memcmp (&rgpropvarNew[ ulIndex ], &rgpropvarOriginal[ rgMapNewToOriginal[ulIndex] ], sizeof(rgpropvarNew[ ulIndex ]))) { // Is this a variant vector? if (rgpropvarNew[ulIndex].vt == (VT_VECTOR | VT_VARIANT)) { ULONG ulVectIndex; // Loop through the variant vector and free any PropVariants // that were allocated. We follow the same principle, if the // entry in rgpropvarNew doesn't match the entry in // rgpropvarOriginal, we must have allocated new memory. for (ulVectIndex = 0; ulVectIndex < rgpropvarNew[ulIndex].capropvar.cElems; ulVectIndex++) { if (memcmp(&rgpropvarNew[ ulIndex ].capropvar.pElems[ ulVectIndex ], &rgpropvarOriginal[ rgMapNewToOriginal[ulIndex] ].capropvar.pElems[ ulVectIndex ], sizeof(rgpropvarNew[ ulIndex ].capropvar.pElems[ ulVectIndex ]))) { PropVariantClear (&rgpropvarNew[ulIndex].capropvar.pElems[ulVectIndex]); } } // Unconditionally free the pElems buffer. CoTaskMemFree (rgpropvarNew[ulIndex].capropvar.pElems); } // if (rgpropvarNew[ulIndex].vt == (VT_VECTOR | VT_VARIANT)) // This isn't a variant vector else { // But does the rgpropvarNew have private memory (i.e. // a converted string buffer)? if (memcmp (&rgpropvarNew[ ulIndex ], &rgpropvarOriginal[ rgMapNewToOriginal[ulIndex] ], sizeof(rgpropvarNew[ ulIndex ]))) { PropVariantClear (&rgpropvarNew[ulIndex]); } } // if (rgpropvarNew[ulIndex].vt == (VT_VECTOR | VT_VARIANT)) ... else } // if (rgpropvarNew[ulIndex] ... } // for (ulIndex = 0; ulIndex < cNew; ulIndex++) // Free the rgpropvarNew array itself. VFreeMemP (rgpropvarNew, cOriginal * sizeof (*rgpropvarNew)); } // if (rgpropvarNew != NULL) // Free the remaining arrays and release the Property Storage interface. if (rgpropspecNew != NULL) { VFreeMemP (rgpropspecNew, cOriginal * sizeof (*rgpropspecNew)); } if (rgMapNewToOriginal != NULL) { VFreeMemP (rgMapNewToOriginal, cOriginal * sizeof(*rgMapNewToOriginal)); } RELEASEINTERFACE (lpPropertyStorage); // And we're done. return (dwResult); } // DwSavePropSetRange //////////////////////////////////////////////////////////////////////////////// // // FLoadUserDef // // Purpose: // Load the User-Defined properties (those in the second section of // the DocumentSummaryInformation property set). There can be any number // of these properties, and the user specifies they're name, value, and // type (from a limited subset of the VarTypes). Since this is // variable-sized, the properties are loaded into a linked-list. // // Inputs: // LPUDOBJ - All User-Defined data (including the properties). // Its m_lpData must point to a valid UDINFO structure. // LPPROPERTYSETSTORAGE - The Property Set Storage in which we'll find the // UD property storage. // UINT* - The PID_CODEPAGE, if it exists. Left unmodified // if it doesn't exist. All string properties will // converted to this format. This must be intialized // by the caller to a valid default. // BOOL - Only load integer values. // DWORD - Flags from the STGM enumeration to use when opening // the property storage. // //////////////////////////////////////////////////////////////////////////////// static BOOL PASCAL FLoadUserDef ( LPUDOBJ lpUDObj, LPPROPERTYSETSTORAGE lpPropertySetStorage, UINT *puCodePage, BOOL fIntOnly, // Load Int Properties only? DWORD grfStgMode) { // ------ // Locals // ------ BOOL fSuccess = FALSE; // Return code to the caller. HRESULT hr; // Error codes for OLE calls. LPPROPERTYSTORAGE lpPropertyStorage = NULL; // The UD property storage LPENUMSTATPROPSTG lpEnum = NULL; // Enumerates the UD property storage STATPROPSETSTG statpropsetstg; // Holds the ClassID from the property storage // Used in ReadMultiple call. PROPSPEC rgpropspec[ DEFAULT_IPROPERTY_COUNT ]; // A subset of the UD properties PROPVARIANT rgpropvar[ DEFAULT_IPROPERTY_COUNT ]; // Stats on a subset of the UD properties STATPROPSTG rgstatpropstg[ DEFAULT_IPROPERTY_COUNT ]; ULONG ulIndex; // Index into the above arrays. PROPSPEC propspec; // PropSpec for reading the code-page LPUDPROP lpudprop = NULL; // A single UD property (points to the PropVariant) ULONG cEnumerated = 0; // Number of properties found in an enumeration // -------------- // Initialization // -------------- Assert (!fIntOnly); // No longer used. Assert (lpUDObj != NULL && GETUDINFO(lpUDObj) != NULL); Assert (puCodePage != NULL); // We need to zero-out the PropVariant and StatPropStg // arrays so that we don't think they need to be freed // in the Exit block. FillBuf (rgpropvar, 0, sizeof (rgpropvar)); FillBuf (rgstatpropstg, 0, sizeof (rgstatpropstg)); // ----------------------------------------- // Get the PropertyStorage and an Enumerator // ----------------------------------------- // Open the IPropertyStorage and check for errors. hr = lpPropertySetStorage->lpVtbl->Open( lpPropertySetStorage, // this pointer &FMTID_UserDefinedProperties, grfStgMode, &lpPropertyStorage ); if (FAILED(hr)) { // We couldn't open the property set. if( hr == STG_E_FILENOTFOUND ) { // No problem, it just didn't exist. fSuccess = TRUE; goto Exit; } else { AssertSz (0, TEXT("Couldn't open property set")); goto Exit; } } // Save the property storage's class ID (identifying the application // which is primarily responsible for it). We do this because // we may later delete the existing property set. hr = lpPropertyStorage->lpVtbl->Stat (lpPropertyStorage, &statpropsetstg); if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't Stat the Property Storage")); goto Exit; } GETUDINFO(lpUDObj)->clsid = statpropsetstg.clsid; // Get the IEnum interface and check for errors. hr = lpPropertyStorage->lpVtbl->Enum( lpPropertyStorage, &lpEnum ); if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't enumerate the PropertyStorage")); goto Exit; } // ------------------ // Read the Code Page // ------------------ propspec.ulKind = PRSPEC_PROPID; propspec.propid = PID_CODEPAGE; hr = lpPropertyStorage->lpVtbl->ReadMultiple (lpPropertyStorage, 1, &propspec, &rgpropvar[0]); if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't get property set")); } // If this is a valid PID_CODEPAGE, give it to the caller. if (hr == S_OK && rgpropvar[0].vt == VT_I2) { *puCodePage = (UINT)MAKELONG(rgpropvar[0].iVal, 0); } PropVariantClear (&rgpropvar[0]); // ------------------------------------------------------------- // Loop through the properties and add to the UDPROPS structure. // ------------------------------------------------------------- // This loop executes once for each enumeration. Each enumeration // gets multiple STATPROPSTGs, so within this loop an inner loop // will process each property. This two-level looping mechanism is // used in order to reduce the number of ReadMultiples. // Use the IEnum to load the first set of STATPROPSTGs. hr = lpEnum->lpVtbl->Next (lpEnum, DEFAULT_IPROPERTY_COUNT, rgstatpropstg, &cEnumerated); if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't get next StatPropStg")); goto Exit; } Assert (cEnumerated <= DEFAULT_IPROPERTY_COUNT); // If the last IEnum returned properties, process them here. // At the end of this while loop, we re-call the IEnum, thus continuing // until no properties are left to be enumerated. while (cEnumerated) { // ------------------------------ // Read this batch of properties. // ------------------------------ for (ulIndex = 0; ulIndex < cEnumerated; ulIndex++) { rgpropspec[ ulIndex ].ulKind = PRSPEC_PROPID; rgpropspec[ ulIndex ].propid = rgstatpropstg[ ulIndex ].propid; } // Read the properties. hr = lpPropertyStorage->lpVtbl->ReadMultiple( lpPropertyStorage, cEnumerated, rgpropspec, rgpropvar ); if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't read from property set")); goto Exit; } // ------------------------------------------------------ // Loop through the properties, adding them to the UDOBJ. // ------------------------------------------------------ for (ulIndex = 0; ulIndex < cEnumerated; ulIndex++) { // Convert string PropVariants to the right code page. // We won't worry about Variants which are strings, because // this is not a legal type for the UD properties. if (PROPVAR_STRING_CONVERSION_REQUIRED ( &rgpropvar[ ulIndex ], *puCodePage)) { // Convert the string in the PropVariant, putting the // result in a temporary PropVariant. PROPVARIANT propvarConvert; PropVariantInit (&propvarConvert); if (!FPropVarConvertString (&propvarConvert, &rgpropvar[ulIndex], *puCodePage)) { AssertSz (0, TEXT("Couldn't convert string")); goto Exit; } // Free the old PropVariant, and load in the converted // one. PropVariantClear (&rgpropvar[ ulIndex ]); rgpropvar[ ulIndex ] = propvarConvert; } #ifndef UNICODE // Convert the property name to the right code page. if (rgstatpropstg[ ulIndex ].lpwstrName != NULL) { LPSTR lpsz; if (!FCoWStrToStr (&lpsz, rgstatpropstg[ ulIndex ].lpwstrName, *puCodePage)) { goto Exit; } CoTaskMemFree (rgstatpropstg[ ulIndex ].lpwstrName); // [scotthan] HACK ALERT: watch this wacky cast; // it'll bite you if you don't reciprocate when reading // the string back out! (see userdef.c, FAddPropToList() impl). rgstatpropstg[ ulIndex ].lpwstrName =(LPWSTR)lpsz; } #endif // Allocate a new UDPROP structure, which will be added to the // linked-list. lpudprop = LpudpropCreate(); if (lpudprop == NULL) { goto Exit; } // Add this UDPROP to the linked-list. On success, this will assume // responsibility for the PropVariant and STATPROPSTG buffers, and // will NULL out our pointers accordingly. if (!FAddPropToList (lpUDObj, &rgpropvar[ ulIndex ], &rgstatpropstg[ ulIndex ], lpudprop)) { goto Exit; } lpudprop = NULL; } // for (ulIndex = 0; ulIndex < cEnumerated; ulIndex++) // --------------------- // Get a new enumeration // --------------------- // We've processed all the properties in the last enumeration, let's get // a new set (if there are any). If there are no more, cEnumerated, will be // zero, and we'll break out of the outer while loop. FreePropVariantArray( cEnumerated, rgpropvar ); hr = lpEnum->lpVtbl->Next (lpEnum, DEFAULT_IPROPERTY_COUNT, rgstatpropstg, &cEnumerated); if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't get next StatPropStg")); goto Exit; } } // while (cEnumerated) // ---- // Exit // ---- fSuccess = TRUE; Exit: // Free any properties with buffers. This will only happen // if there was an error. if (cEnumerated > 0) { FreePropVariantArray (cEnumerated, rgpropvar); } // Again if there was an error, we must free the UDPROP object. if (lpudprop) { VUdpropFree (&lpudprop); } // Free any name buffers we still have from the enumerations. // Once again, this is only necessary if there was an error. for (ulIndex = 0; ulIndex < cEnumerated; ulIndex++) { if (rgstatpropstg[ ulIndex ].lpwstrName != NULL) { CoTaskMemFree (rgstatpropstg[ ulIndex ].lpwstrName); } } // Release the Property Storage and Enumeration interfaces. RELEASEINTERFACE (lpEnum); RELEASEINTERFACE (lpPropertyStorage); return fSuccess; } // FLoadUserDef //////////////////////////////////////////////////////////////////////////////// // // FSaveUserDef // // Purpose: // Save the User-Defined properties to the second section of // the DocumentSummaryInformation property set. // // Inputs: // LPUDOBJ - All UD data (including the properties) // It's m_lpData must point to a valid UDINFO structure. // LPPROPERTYSETSTORAGE - The Property Set Storage // UINT - The code page in which strings should be // written. If Unicode, all strings are // written as LPWSTRs, otherwise all strings // are written as LPSTRs. // DWORD - Flags from the STGM enumeration to use when // opening the property storage. // // Outputs: // TRUE if successful. // // Pre-conditions: // The properties to be written are all from the UDTYPES // enumeration. // // Implementation: // Properties which are links to application data require special // handling. First, the property value is written (along with its // name). Then, the application-defined link name is // written (e.g. the Bookmark name in Word). The link name // is written using the same PID as was the link value, except that // the PID_LINKMASK is ORed in. The link name property has no name // in the property set dictionary. // //////////////////////////////////////////////////////////////////////////////// static BOOL PASCAL FSaveUserDef ( LPUDOBJ lpUDObj, LPPROPERTYSETSTORAGE lpPropertySetStorage, UINT uCodePage, DWORD grfStgMode) { // ------ // Locals // ------ BOOL fSuccess = FALSE; // What to return to the caller. HRESULT hr; // OLE result codes. BOOL fLink, fLinkInvalid; // The UD Property Storage LPPROPERTYSTORAGE lpPropertyStorage = NULL; LPUDITER lpudi = NULL; // Iterates the linked-list of UDPROPs LPPROPVARIANT lppropvar = NULL; // A property from the linked-list ULONG ulIndex; // Generic index into arrays PROPID propid; // The PID to assign to the next property // Arrays to be used in the WriteMultiple. The array of BOOLs // indicate which elements of the PropVariant array must be freed. ULONG ulPropIndex = 0; PROPSPEC rgpropspec[ DEFAULT_IPROPERTY_COUNT ]; PROPVARIANT rgpropvar[ DEFAULT_IPROPERTY_COUNT ]; BOOL rgfFreePropVar[ DEFAULT_IPROPERTY_COUNT ]; // Arrays to be used in the WritePropertyNames. ULONG ulNameIndex = 0; PROPID rgpropidName[ DEFAULT_IPROPERTY_COUNT ]; LPWSTR rglpwstrName[ DEFAULT_IPROPERTY_COUNT ]; // ---------- // Initialize // ---------- Assert (lpUDObj != NULL && GETUDINFO(lpUDObj) != NULL); Assert (lpPropertySetStorage != NULL && lpPropertySetStorage->lpVtbl != NULL); // Initialize the necessary arrays, so that we don't unnecessarily // free something in the Error path. FillBuf (rgpropvar, 0, sizeof(rgpropvar)); FillBuf (rgfFreePropVar, 0, sizeof(rgfFreePropVar)); FillBuf (rglpwstrName, 0, sizeof(rglpwstrName)); // Delete the existing property set and create a new empty one. // We must do this because we don't know which of the // existing properties need to be deleted, we only know what // the current set of properties should be. hr = lpPropertySetStorage->lpVtbl->Delete( lpPropertySetStorage, &FMTID_UserDefinedProperties ); if (FAILED(hr)) { if (hr != STG_E_FILENOTFOUND) { AssertSz (0, TEXT("Couldn't remove old properties")); goto Exit; } } hr = _CreatePropertyStorage( lpPropertySetStorage, &FMTID_UserDefinedProperties, &GETUDINFO(lpUDObj)->clsid, grfStgMode, &uCodePage, &lpPropertyStorage ); if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't open User-Defined property set")); goto Exit; } // Create an iterator which we use to enumerate the properties // (UDPROPs) in the linked-list. lpudi = LpudiUserDefCreateIterator (lpUDObj); // ------------------------------------------------------------------ // Loop through the properties and write them to the UD property set. // ------------------------------------------------------------------ // We use a two-layer loop. The inner loop batches a group of properties // in a PropVariant array, and then writes them to the Property Storage. // The outer loop repeats this process until there are no more properties. // This two-layer mechanism is desirable so that we reduce the number // of WriteMultiple calls. propid = PID_UDFIRST; fLink = FALSE; while (TRUE) { // ------------------------------------------ // Batch up a set of properties to be written // ------------------------------------------ ulPropIndex = ulNameIndex = 0; // We will break out of this loop when we have no more properties // or if we have enough for a WriteMultiple. while (FUserDefIteratorValid (lpudi)) { Assert (lpudi->lpudp != NULL); // ---------------------------------------------------------------------- // Create entries in the arrays for WriteMultiple and WritePropertyNames. // ---------------------------------------------------------------------- // If fLink is TRUE, it means that we've written out the // property, and now we need to write out the link name // (with the PID_LINKMASK ORed into the propid). if (!fLink) { // We aren't writing a link. So let's get the // property from the linked-list (we know it exists because // FUserDefIteratorValid was true). lppropvar = LppropvarUserDefGetIteratorVal (lpudi, NULL, NULL); if (lppropvar == NULL) { AssertSz (0, TEXT("Invalid PropVariant in iterator")); goto Exit; } // Copy this propvariant into the array which will be used for // the WriteMultiple. Note that we do not copy any referenced // buffer (e.g. we don't copy the string buffer if this is a string). rgpropvar[ ulPropIndex ] = *lppropvar; // If this property has a name, prepare to write it. if (lpudi->lpudp->lpstzName != NULL) { // Add this name to rglpwstrName & rgpropidName. #ifndef UNICODE { // Convert the ANSI name to Unicode (all OLE calls require // Unicode strings). if (!FCoStrToWStr (&rglpwstrName[ ulNameIndex ], lpudi->lpudp->lpstzName, uCodePage )) { AssertSz (0, TEXT("Couldn't convert name to Unicode")); goto Exit; } } #else // Add this name to the list of those to be written. rglpwstrName[ ulNameIndex ] = lpudi->lpudp->lpstzName; #endif // UNICODE // Add this propid to the list of those with names. rgpropidName[ ulNameIndex ] = propid; } // if (lpudi->lpudp->lpstzName != NULL) } // if (!fLink) else { // We are processing a link name. I.e., we've written the // property value, now we need to write the name of the link, // as a property, with the PID_LINKSMASK bit set in the PID. Assert (lpudi->lpudp->lpstzLink != NULL); // Create a entry in the PropVariant. rgpropvar[ ulPropIndex ].vt = VT_LPTSTR; (LPTSTR) rgpropvar[ ulPropIndex ].pszVal = lpudi->lpudp->lpstzLink; } // rgpropvar[ulPropIndex] now holds the property to be written, // whether it is a real property or a link name. // ------------------------------------ // Convert strings to the proper format. // ------------------------------------- // (This could also convert the type from LPWSTR to LPSTR, or vice-versa). // We don't have to worry about strings in vectors or in // variant vectors, because these are illegal types for this // property set. if (rgpropvar[ ulPropIndex ].vt == VT_LPTSTR) { // If this string needs to be converted do so, putting the converted // string in a new buffer. So, // the caller's PropVariant still points to the old buffer, // and our rgpropvar points to the new buffer. if (PROPVAR_STRING_CONVERSION_REQUIRED ( &rgpropvar[ ulPropIndex ], uCodePage)) { // Convert the string into a temporary PropVariant. PROPVARIANT propvarConvert; PropVariantInit (&propvarConvert); if (!FPropVarConvertString (&propvarConvert, &rgpropvar[ ulPropIndex ], uCodePage )) { AssertSz (0, TEXT("Couldn't convert string")); goto Exit; } // Load this new PropVariant into rgpropvar, but don't // delete the old buffer (so that we leave the linked-list // of UDPROPs intact). rgpropvar[ ulPropIndex ] = propvarConvert; // Since we just created a new buffer, we must remember to free it. rgfFreePropVar[ ulPropIndex ] = TRUE; } // if (PROPVAR_STRING_CONVERSION_REQUIRED ( ... } // if (rgpropvar[ ulPropIndex ].vt == VT_LPTSTR) // -------------------------- // Finish this loop iteration // -------------------------- // Set up the PropSpec. rgpropspec[ ulPropIndex ].ulKind = PRSPEC_PROPID; rgpropspec[ ulPropIndex ].propid = propid; // If this is a link name, set the bit in the PID. if (fLink) { rgpropspec[ ulPropIndex ].propid |= PID_LINKMASK; } // Advance the property index. And if we set a name, advance // the name index. ulPropIndex++; if (rglpwstrName[ ulNameIndex ] != NULL) { ulNameIndex++; } // If we've just processed a link, or this is a property // which is not linked to application content, then move on to the next property // in the iterator. If we've just processed a property value that // is linked, set fLink so that on the next pass through // this loop, we'll write out the link name. if (fLink || !FUserDefIteratorIsLink (lpudi)) { fLink = FALSE; propid++; FUserDefIteratorNext (lpudi); } else { fLink = TRUE; } // If there's no more room in the WriteMultiple arrays, // then write out the properties. We'll return to this // inner loop when that's complete. if (ulPropIndex >= DEFAULT_IPROPERTY_COUNT) { break; } } // while (FUserDefIteratorValid (lpudi)) // If broke out of the previous loop becuase there were no // more properties, then we can break out of the outer loop // as well -- we're done. if (ulPropIndex == 0) { break; } // --------------------- // Write the properties. // --------------------- hr = lpPropertyStorage->lpVtbl->WriteMultiple ( lpPropertyStorage, // 'this' pointer ulPropIndex, // Number of properties rgpropspec, // Property specifiers rgpropvar, // The properties PID_UDFIRST); // Not used. if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't write properties")); goto Exit; } // If we created any new buffers during string conversion, // free them now. for (ulIndex = 0; ulIndex < ulPropIndex; ulIndex++) { if (rgfFreePropVar[ ulIndex ]) { PropVariantClear (&rgpropvar[ ulIndex ]); rgfFreePropVar[ ulIndex ] = FALSE; } } // ---------------- // Write the Names. // ---------------- if (ulNameIndex != 0) { hr = lpPropertyStorage->lpVtbl->WritePropertyNames ( lpPropertyStorage, // 'this' pointer ulNameIndex, // Number of names rgpropidName, // PIDs for these names rglpwstrName ); // The names if (FAILED(hr)) { AssertSz (0, TEXT("Couldn't write property names")); goto Exit; } } // if (ulNameIndex != 0) // Clear the names array. for (ulIndex = 0; ulIndex < ulNameIndex; ulIndex++) { #ifndef UNICODE // Free the memory which was allocated for this name. CoTaskMemFree (rglpwstrName[ ulIndex ]); #endif rglpwstrName[ ulIndex ] = NULL; } // for (ulIndex = 0; ulIndex < ulNameIndex; ulIndex++) } // while (TRUE) // ---- // Exit // ---- fSuccess = TRUE; Exit: // Free the iterator if (lpudi) { FUserDefDestroyIterator (&lpudi); } // Free any memory that was allocated for PropVariants. for (ulIndex = 0; ulIndex < ulPropIndex; ulIndex++) { if (rgfFreePropVar[ ulIndex ]) { PropVariantClear (&rgpropvar[ ulIndex ]); } } #ifndef UNICODE // Free any memory that was allocated for name. for (ulIndex = 0; ulIndex < ulNameIndex; ulIndex++) { CoTaskMemFree (rglpwstrName[ ulIndex ]); } #endif // Release the UD Property Storage. RELEASEINTERFACE (lpPropertyStorage); return (fSuccess); } // FSaveUserDef