windows-nt/Source/XPSP1/NT/shell/ext/docprop/propio.c
2020-09-26 16:20:57 +08:00

2256 lines
74 KiB
C

////////////////////////////////////////////////////////////////////////////////
//
// 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 <stdio.h> // for sprintf
#include <shlwapi.h>
#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