////////////////////////////////////////////////////////////////////////////////
//
// 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

