////////////////////////////////////////////////////////////////////////////////
//
// UserDef.c
//
// MS Office User Defined Property Information
//
// Notes:
//  To make this file useful for OLE objects, define OLE_PROPS.
//
//  The macro lpDocObj must be used for all methods to access the
//  object data to ensure that this will compile with OLE_PROPS defined.
//
// Data structures:
//  The dictionary is stored internally as a map, mapping PIDs
//  to the string names.
//
//  The properties themselves are stored internally as a linked list
//
// Change history:
//
// Date         Who             What
// --------------------------------------------------------------------------
// 06/27/94     B. Wentz        Created file
// 07/03/94     B. Wentz        Added Iterator support
// 07/20/94              M. Jansson               Updated include statements, due to changes in PDK
// 07/26/94     B. Wentz        Changed Load & Save to use Document Summary stream
// 07/08/96     MikeHill        Ignore UserDef properties that aren't UDTYPEs.
//
////////////////////////////////////////////////////////////////////////////////

#include "priv.h"
#pragma hdrstop


static void PASCAL RemoveFromList (LPUDOBJ lpUDObj, LPUDPROP lpudprop);
static void PASCAL DeallocNode (LPUDOBJ lpUDObj, LPUDPROP lpudp);

static void PASCAL VUdpropFreeString (LPUDPROP lpudp, BOOL fName);
static BOOL PASCAL FUdpropUpdate (LPUDPROP lpudp, LPUDOBJ  lpUDObj, LPTSTR lpszPropName, LPTSTR lpszLinkMonik, LPVOID lpvValue, UDTYPES udtype, BOOL fLink);
static BOOL PASCAL FUdpropSetString (LPUDPROP lpudp, LPTSTR lptstr, BOOL fLimitLength, BOOL fName);
static BOOL PASCAL FUdpropMakeVisible (LPUDPROP lpudprop);
static BOOL PASCAL FUserDefMakeHidden (LPUDOBJ lpUDObj, LPTSTR lpsz);
static BOOL PASCAL FUdpropMakeHidden (LPUDPROP lpudprop);

#define lpDocObj  (lpUDObj)
#define lpData    ((LPUDINFO) lpUDObj->m_lpData)

////////////////////////////////////////////////////////////////////////////////
//
// OfficeDirtyUDObj
//
// Purpose:
//  Sets object state to dirty or clean.
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT VOID OfficeDirtyUDObj
    (LPUDOBJ lpUDObj,             // The object
     BOOL fDirty)                 // Flag indicating if the object is dirty.
{
    Assert(lpUDObj != NULL);
    lpUDObj->m_fObjChanged = fDirty;
} // OfficeDirtyUDObj


////////////////////////////////////////////////////////////////////////////////
//
// FreeUDData
//
// Purpose:
//  Deallocates all the member data for the object
//
// Note:
//  Assumes object is valid.
//
////////////////////////////////////////////////////////////////////////////////
void PASCAL
FreeUDData
    (LPUDOBJ lpUDObj,                   // Pointer to valid object
     BOOL fTmp)                         // Indicates tmp data should be freed
{
    LPUDPROP lpudp;
    LPUDPROP lpudpT;


    lpudp = (fTmp) ? lpData->lpudpTmpHead : lpData->lpudpHead;

    while (lpudp != NULL)
    {
        lpudpT = lpudp;
        lpudp = (LPUDPROP) lpudp->llist.lpllistNext;
        VUdpropFree(&lpudpT);
    }

    if (fTmp)
    {
        lpData->lpudpTmpCache = NULL;
        lpData->lpudpTmpHead = NULL;
        lpData->dwcTmpProps = 0;
        lpData->dwcTmpLinks = 0;
    }
    else
    {
        lpData->lpudpCache = NULL;
        lpData->lpudpHead = NULL;
        lpData->dwcProps = 0;
        lpData->dwcLinks = 0;
    }

} // FreeUDData

////////////////////////////////////////////////////////////////////////////////
//
// FUserDefCreate
//
// Purpose:
//  Create a User-defined property exchange object.
//
////////////////////////////////////////////////////////////////////////////////
BOOL
FUserDefCreate
    (LPUDOBJ FAR *lplpUDObj,              // Pointer to pointer to object
     void *prglpfn[])                     // Pointer to functions
{
    LPUDOBJ lpUDObj;  // Hack - a temp, must call it "lpUdObj" for macros to work!

    if (lplpUDObj == NULL)
        return(TRUE);

      // Make sure we get valid args before we start alloc'ing
    if ((prglpfn == NULL) || (prglpfn[ifnCPConvert] == NULL) ||
        ((prglpfn[ifnFSzToNum] == NULL) && (prglpfn[ifnFNumToSz] != NULL)) ||
        ((prglpfn[ifnFSzToNum] != NULL) && (prglpfn[ifnFNumToSz] == NULL))
       )
    {
        return FALSE;
    }

    if ((*lplpUDObj = PvMemAlloc(sizeof(USERPROP))) == NULL)
    {
        // REVIEW: Add alert
        return FALSE;
    }

    lpDocObj = *lplpUDObj;

    //
    // If alloc fails, free the original object too.
    //
    if ((lpData = PvMemAlloc(sizeof (UDINFO))) == NULL)
    {
        //
        // REVIEW: Add alert
        //
        VFreeMemP(*lplpUDObj, sizeof(USERPROP));
        return FALSE;
    }

    FillBuf ((void *) lpData, (int) 0, sizeof(UDINFO) );

    //
    // Save the fnc's for code page convert, SzToNum, NumToSz
    //
    lpData->lpfnFCPConvert = (BOOL (*)(LPTSTR, DWORD, DWORD, BOOL)) prglpfn[ifnCPConvert];
    lpData->lpfnFSzToNum = (BOOL (*)(NUM *, LPTSTR)) prglpfn[ifnFSzToNum];
    lpData->lpfnFNumToSz = (BOOL (*)(NUM *, LPTSTR, DWORD)) prglpfn[ifnFNumToSz];

    OfficeDirtyUDObj (*lplpUDObj, FALSE);
    (*lplpUDObj)->m_hPage = NULL;

    return TRUE;

} // FUserDefCreate


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefDestroy
//
// Purpose:
//  Destroy a User-defined property exchange object.
//
////////////////////////////////////////////////////////////////////////////////
BOOL
FUserDefDestroy
    (LPUDOBJ FAR *lplpUDObj)              // Pointer to pointer to object
{
#define lpUDData    ((LPUDINFO)(((LPUDOBJ) *lplpUDObj)->m_lpData))

    DWORD irg;

    if ((lplpUDObj == NULL) || (*lplpUDObj == NULL))
        return TRUE;

    if (lpUDData != NULL)
    {
        FreeUDData (*lplpUDObj, FALSE);
        FreeUDData (*lplpUDObj, TRUE);

        //
        // Invalidate any OLE Automation DocumentProperty objects we might have
        //
        InvalidateVBAObjects(NULL, NULL, *lplpUDObj);

        VFreeMemP((*lplpUDObj)->m_lpData, sizeof(UDINFO));
    }

    VFreeMemP(*lplpUDObj, sizeof(USERPROP));
    *lplpUDObj = NULL;
    return TRUE;

#undef lpUDData
} // FUserDefDestroy


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefClear
//
// Purpose:
//  Clears a User-defined property object without destroying it.  All stored
//  data is lost.
//
////////////////////////////////////////////////////////////////////////////////
BOOL
FUserDefClear(LPUDOBJ lpUDObj)                     // Pointer to object
{

    if ((lpDocObj == NULL) || (lpData == NULL))
        return TRUE;

    FreeUDData (lpDocObj, FALSE);
    FreeUDData (lpDocObj, TRUE);

    //
    // Invalidate any OLE Automation DocumentProperty objects we might have
    //
    InvalidateVBAObjects(NULL, NULL, lpUDObj);

    //
    // Clear the data, don't blt over the fn's stored at the end.
    //
    FillBuf ((void *) lpData, (int) 0, (sizeof (UDINFO) - ifnUDMax*(sizeof (void *))));

    OfficeDirtyUDObj (lpUDObj, TRUE);
    return TRUE;

} // FUserDefClear


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefShouldSave
//
// Purpose:
//  Indicates if the data has changed, meaning a write is needed.
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefShouldSave
    (LPUDOBJ lpUDObj)             // Pointer to object
{
    if (lpUDObj == NULL)
        return FALSE;

    return lpDocObj->m_fObjChanged;

} // FUserDefShouldSave




////////////////////////////////////////////////////////////////////////////////
//
// UdtypesUserDefType
//
// Purpose:
//  Returns the type of the given Property Value from the string
//
// Returns wUDInvalid on error
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT UDTYPES
UdtypesUserDefType
    (LPUDOBJ lpUDObj,
     LPTSTR lpsz)
{
    LPUDPROP lpudprop;

    if ((lpUDObj == NULL)   ||
        (lpData == NULL)    ||
        (lpsz == NULL)
       )
    {
        return wUDinvalid;
    }

    //
    // Find the node that has this name.
    //
    lpudprop = LpudpropFindMatchingName (lpUDObj, lpsz);
    if (lpudprop == NULL || lpudprop->lppropvar == NULL)
        return wUDinvalid;

    // Return the VarType (which is a UDTYPE)

    return (lpudprop->lppropvar->vt);

} // UdtypesUserDefType


////////////////////////////////////////////////////////////////////////////////
//
// LpvoidUserDefGetPropVal
//
// Purpose:
//   This will return the Property Value for the given Property String.
//
////////////////////////////////////////////////////////////////////////////////

DLLEXPORT LPVOID
LpvoidUserDefGetPropVal
    (LPUDOBJ lpUDObj,             // Pointer to object
     LPTSTR lpszProp,             // Property string
     DWORD cbMax,                 // Size of lpv
     LPVOID lpv,                  // Buffer for prop val
     DWORD dwMask,                // Mask for what value is needed
     BOOL *pfLink,                // Indicates a link
     BOOL *pfLinkInvalid)         // Is the link invalid
{
    LPUDPROP lpudprop;

    if ((lpUDObj == NULL)           ||
        (lpData == NULL)            ||
        (lpszProp == NULL)          ||
        (cbMax == 0 && lpv != NULL) ||
        (pfLink == NULL)            ||
        (pfLinkInvalid == NULL)     ||
        ((lpv == NULL) && (!(dwMask & UD_PTRWIZARD)))
       )
    {
        return NULL;
    }

    //
    // Find the node that has this name.
    //

    lpudprop = LpudpropFindMatchingName (lpUDObj, lpszProp);
    if (lpudprop == NULL)
        return NULL;
    Assert (lpudprop->lppropvar != NULL);

    *pfLink = (lpudprop->lpstzLink != NULL);

    // Links are always invalid in the shell (there's no app to update the data).

#ifdef SHELL
    *pfLinkInvalid = lpudprop->fLinkInvalid = TRUE;
#else
    *pfLinkInvalid = lpudprop->fLinkInvalid;
#endif

    //
    // Return based on the type and flags
    //

    if (dwMask & UD_LINK)
    {
        if (dwMask & UD_PTRWIZARD)
        {
            if (lpudprop->lpstzLink != NULL)
                return((LPVOID) (PSTR (lpudprop->lpstzLink)));
            return(NULL);
        }

        if (lpudprop->lpstzLink != NULL)
        {
            PbSzNCopy (lpv, lpudprop->lpstzLink, cbMax);
            return (lpv);
        }
        else
          return(NULL);
    }

    if (dwMask & UD_PTRWIZARD)
    {
        // If this is a string, return it's pointer from the
        // PropVariant.  Otherwise, return a pointer into
        // the data of the PropVariant.

        return (lpudprop->lppropvar->vt == VT_LPTSTR) ?
               (LPVOID) lpudprop->lppropvar->pszVal :
               (LPVOID) &lpudprop->lppropvar->lVal;
    }

    // Copy the property from the PropVariant to the caller-provided
    // buffer.

    return( FPropVarCopyToBuf( lpudprop->lppropvar,
                               cbMax,
                               lpv
                             ) ? lpv : NULL
           );

} // LpvoidUserDefGetPropVal


////////////////////////////////////////////////////////////////////////////////
//
// LppropvarUserDefAddProp
//
// Purpose:
//  This will add a new Property to the set, with the given
//  Property string, type, and data.
//
////////////////////////////////////////////////////////////////////////////////

DLLEXPORT LPPROPVARIANT
LppropvarUserDefAddProp
    (LPUDOBJ lpUDObj,             // Pointer to object
     LPTSTR lpszPropName,         // Property string
     LPVOID lpvVal,               // Property value
     UDTYPES udtype,              // Property type
     LPTSTR lpszLinkMonik,        // Link name
     BOOL fLink,                  // Indicates the property is a link
     BOOL fHidden)                // Indicates the property is hidden
{
    LPUDPROP lpudprop;
    LPUDPROP lpudpropMatch;
    BOOL     fCreated;

    if ((lpUDObj == NULL)   ||
        (lpData == NULL)    ||
        (lpszPropName == NULL) ||
        (*lpszPropName == 0) ||
        (lpvVal == NULL) ||
        (!ISUDTYPE(udtype)) ||
        (fLink && (lpszLinkMonik == NULL))
       )
    {
      return FALSE;
    }

    // Create a UDPROP to be added to the linked-list.

    lpudprop = LpudpropCreate();
    if (lpudprop == NULL)
        return FALSE;

    // Put the data into the UDPROP.

    if (!FUdpropUpdate( lpudprop,
                        lpUDObj,
                        lpszPropName,
                        lpszLinkMonik,
                        lpvVal,
                        udtype,
                        fLink)
        )
    {
        VUdpropFree (&lpudprop);
        return(FALSE);
    }

    //
    // Find this node
    //

    lpudpropMatch = LpudpropFindMatchingName (lpUDObj, lpszPropName);
    if (lpudpropMatch==NULL)
    {
        //
        // Create a node and put it in the list
        // If a new node was created, it must be added to the list...
        //

        if (fLink)
           lpData->dwcLinks++;

        lpData->dwcProps++;
        AddNodeToList (lpUDObj, lpudprop);

    }   // if (lpudpropMatch==NULL)

    else
    {
        // We must replace the existing UDPROP with the new
        // value.

        // Free any existing property name and link name in this
        // UDPROP, and free its value.

        VUdpropFreeString (lpudpropMatch, TRUE);
        VUdpropFreeString (lpudpropMatch, FALSE);
        PropVariantClear (lpudpropMatch->lppropvar);
        CoTaskMemFree (lpudpropMatch->lppropvar);
        lpudpropMatch->lppropvar = NULL;

        // Put the linked-list pointer in the existing UDPROP into
        // the new UDPROP, then copy the new UDPROP back over
        // the matching PROP (this way, we don't have to
        // update the UDPROP that points to the match).

        lpudprop->llist=lpudpropMatch->llist;
        PbMemCopy(lpudpropMatch, lpudprop, sizeof(UDPROP));

        // Clear out the caller-provided UDPROP, free it, but
        // then set the pointer to the matching entry and clear
        // the match pointer.  Thus, after we're done and whether
        // there was a match or not, lpudprop will point to the
        // correct UDPROP.

        FillBuf (lpudprop, 0, sizeof(UDPROP));
        VUdpropFree (&lpudprop);
        lpudprop = lpudpropMatch;
        lpudpropMatch = NULL;

    }   // if (lpudpropMatch==NULL) ... else

    //
    // If the client asked for a hidden property, do it if
    // the name was the real name, not a link
    //

    if (fHidden && !fLink)
    {
        fCreated=FUserDefMakeHidden (lpUDObj, lpszPropName);      // Should never return false
        Assert(fCreated);
    }

    OfficeDirtyUDObj (lpUDObj, TRUE);

    // If successful, return a pointer to the PropVariant with the value.
    if (lpudprop)
        return lpudprop->lppropvar;
    else
        return NULL;

} // LppropvarUserDefAddProp


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefDeleteProp
//
// Purpose:
//  This will delete a Property from the set given a Property string.
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefDeleteProp
  (LPUDOBJ lpUDObj,             // Pointer to object
   LPTSTR lpsz)                  // String to delete
{
  LPUDPROP lpudprop;

  if ((lpUDObj == NULL)   ||
      (lpData == NULL)    ||
      (lpsz == NULL))
    return FALSE;

    // Find the node
  lpudprop = LpudpropFindMatchingName (lpUDObj, lpsz);
  if (lpudprop == NULL)
    return FALSE;

  lpData->dwcProps--;
  if (lpudprop->lpstzLink != NULL)
    lpData->dwcLinks--;

  RemoveFromList (lpUDObj, lpudprop);
  VUdpropFree (&lpudprop);

  OfficeDirtyUDObj (lpUDObj, TRUE);
  return TRUE;

} // FUserDefDeleteProp


////////////////////////////////////////////////////////////////////////////////
//
// LpudiUserDefCreateIterator
//
// Purpose:
//  Create a User-defined Properties iterator
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT LPUDITER
LpudiUserDefCreateIterator
  (LPUDOBJ lpUDObj)                     // Pointer to object
{
  LPUDITER lpudi;

  if ((lpUDObj == NULL) ||
      (lpData == NULL) ||
                (lpData->lpudpHead == NULL))            // No custom props
    return NULL;


    // Create & Init the iterator
  lpudi = PvMemAlloc(sizeof(UDITER));
  if (lpudi == NULL)
     return(NULL);

  FillBuf (lpudi, 0, sizeof (UDITER));
  lpudi->lpudp = lpData->lpudpHead;

  return lpudi;

} // LpudiUserDefCreateIterator


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefDestroyIterator
//
// Purpose:
//  Destroy a User-defined Properties iterator
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefDestroyIterator
  (LPUDITER *lplpUDIter)                   // Pointer to iterator
{
    if ((lplpUDIter == NULL) || (*lplpUDIter == NULL))
        return TRUE;

      VFreeMemP(*lplpUDIter, sizeof(UDITER));
      *lplpUDIter = NULL;

      return TRUE;

} // FUserDefDestroyIterator


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefIteratorValid
//
// Purpose:
//  Determine if an iterator is still valid
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefIteratorValid
  (LPUDITER lpUDIter)                   // Pointer to iterator
{
    if (lpUDIter == NULL)
        return FALSE;

    return (lpUDIter->lpudp != NULL);

} // FUserDefIteratorValid


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefIteratorNext
//
// Purpose:
//  Iterate to the next element
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefIteratorNext
  (LPUDITER lpUDIter)                   // Pointer to iterator
{
    if (lpUDIter == NULL)
        return FALSE;

    // Move to the next node, if possible.
#ifdef OLD
    if (lpUDIter->lpudp != NULL)
        lpUDIter->lpudp = (LPUDPROP) lpUDIter->lpudp->llist.lpllistNext;

    return TRUE;
#endif

    if (lpUDIter->lpudp == NULL)
        return FALSE;

    lpUDIter->lpudp = (LPUDPROP) lpUDIter->lpudp->llist.lpllistNext;

    return(lpUDIter->lpudp != NULL);

} // FUserDefIteratorNext

////////////////////////////////////////////////////////////////////////////////
//
// FUserDefIteratorIsLink
//
// Purpose:
//  Returns TRUE if the iterator is a link, FALSE otherwise
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefIteratorIsLink
  (LPUDITER lpUDIter)                   // Pointer to iterator
{
  if ((lpUDIter == NULL) || (lpUDIter->lpudp == NULL))
    return FALSE;

  return(lpUDIter->lpudp->lpstzLink != NULL);

} // FUserDefIteratorIsLink

////////////////////////////////////////////////////////////////////////////////
//
// FUserDefIteratorIsLinkInvalid
//
// Purpose:
//  Returns TRUE if the iterator is an invalid link, FALSE otherwise
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefIteratorIsLinkInvalid
  (LPUDITER lpUDIter)                   // Pointer to iterator
{
  if ((lpUDIter == NULL) || (lpUDIter->lpudp == NULL))
    return(FALSE);

  if (lpUDIter->lpudp->lpstzLink == NULL)
    return(FALSE);

  return(lpUDIter->lpudp->fLinkInvalid);

} // FUserDefIteratorIsLinkInvalid

////////////////////////////////////////////////////////////////////////////////
//
// UdtypesUserDefIteratorType
//
// Purpose:
//  Returns the type of the given Property Value from the iterator
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT UDTYPES
UdtypesUserDefIteratorType
  (LPUDITER lpUDIter)                   // Pointer to iterator
{
   if ((lpUDIter == NULL)  ||
       (lpUDIter->lpudp == NULL) ||
       (lpUDIter->lpudp->lppropvar == NULL))
    return wUDinvalid;

  return (lpUDIter->lpudp->lppropvar->vt);

} // UdtypesUserDefIteratorType


////////////////////////////////////////////////////////////////////////////////
//
// LpvoidUserDefGetIteratorVal
//
// Purpose:
//  This will return the Property Value for the given iterator
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT LPVOID
LpvoidUserDefGetIteratorVal
  (LPUDITER lpUDIter,                   // Pointer to iterator
   DWORD cbMax,                         // Max size of lpv
   LPVOID lpv,                          // Buffer to copy data value to
   DWORD dwMask,                        // Mask indicating the value to get
   BOOL *pfLink,                        // Flag indicating the link is desired
   BOOL *pfLinkInvalid)                 // Flag indicating if the link is invalid
{
  if ((cbMax == 0)        ||
      ((lpv == NULL) && (!(dwMask & UD_PTRWIZARD))) ||
      (lpUDIter == NULL)  ||
      (pfLink == NULL)    ||
      (pfLinkInvalid == NULL) ||
      (lpUDIter->lpudp == NULL))
    return NULL;

  *pfLink = (lpUDIter->lpudp->lpstzLink != NULL);
  *pfLinkInvalid = lpUDIter->lpudp->fLinkInvalid;

      // Return based on the type and flags
  if (dwMask & UD_LINK)
  {
    if (dwMask & UD_PTRWIZARD)
    {
      if (lpUDIter->lpudp->lpstzLink != NULL)
         return((LPVOID) (PSTR (lpUDIter->lpudp->lpstzLink)));
      return(NULL);
    }

    if (lpUDIter->lpudp->lpstzLink != NULL)
    {
       PbSzNCopy (lpv, lpUDIter->lpudp->lpstzLink, cbMax);
       return (lpv);
    }
    else
      return(NULL);
  }

  if (dwMask & UD_PTRWIZARD)
  {
    Assert (lpUDIter->lpudp->lppropvar != NULL);
    Assert (lpUDIter->lpudp->lppropvar->vt == VT_LPTSTR
            ||
            lpUDIter->lpudp->lppropvar->vt == VT_INT_PTR);

    // If this is a string property, return a pointer to it.
    // Otherwise it is a Long/LongPtr, and we return its value.

    return ( lpUDIter->lpudp->lppropvar->vt == VT_LPTSTR
             ? (LPVOID) lpUDIter->lpudp->lppropvar->pszVal
#ifdef _WIN64
             : (LPVOID) lpUDIter->lpudp->lppropvar->hVal.QuadPart
#else
             : (LPVOID) lpUDIter->lpudp->lppropvar->lVal
#endif
           );
  }

  // Copy the value of this property into a caller-provided buffer.

  return(FPropVarCopyToBuf(lpUDIter->lpudp->lppropvar, cbMax, lpv) ? lpv : NULL);

} // LpvoidUserDefGetIteratorVal


////////////////////////////////////////////////////////////////////////////////
//
// LpszUserDefIteratorName
//
// Purpose:
//  This will return the Property String (name) for the property
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT LPTSTR
LpszUserDefIteratorName
  (LPUDITER lpUDIter,                   // Pointer to iterator
   DWORD cbMax,                         // Max size of lpsz
   LPTSTR lpsz)                         // Buffer to copy into, or UD_PTRWIZARD
{
    if ((cbMax == 0)        ||
        (lpsz == NULL)      ||
        (lpUDIter == NULL)  ||
        (lpUDIter->lpudp == NULL))
    {
        return NULL;
    }

    // If the lpsz input is not actually a pointer, but the
    // UD_PTRWIZARD value, then the caller doesn't want
    // us to copy the name into a buffer, they just want
    // us to return a pointer to the name.

    if ((INT_PTR) lpsz == UD_PTRWIZARD)
    {
        AssertSz ((IsBadReadPtr (lpsz, sizeof(LPTSTR))), TEXT("UD_PTRWIZARD should be a bogus pointer value!"));
        return (lpUDIter->lpudp->lpstzName);
    }

    // The caller gave us a real buffer in lpsz.
    // Copy the name into it, and return it.

    PbSzNCopy (lpsz, lpUDIter->lpudp->lpstzName, cbMax-1);
    lpsz[cbMax-1] = TEXT('\0');

    return lpsz;

} // LpszUserDefIteratorName


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefIteratorSetPropString
//
// Purpose:
//  Sets the name of the iterator.
//
////////////////////////////////////////////////////////////////////////////////
DLLFUNC BOOL OFC_CALLTYPE
FUserDefIteratorSetPropString
  (LPUDOBJ lpUDObj,                     // Pointer to object
   LPUDITER lpUDIter,                   // Pointer to iterator
   LPTSTR lpszNew)                       // Pointer to new name
{
  if ((lpUDObj == NULL)   ||
      (lpData == NULL)    ||
      (lpszNew == NULL)   ||
      (lpUDIter == NULL)  ||
      (lpUDIter->lpudp == NULL))
    return FALSE;

    // Update the node
  if (!FUdpropSetString (lpUDIter->lpudp, lpszNew, TRUE, TRUE))
    return FALSE;

  OfficeDirtyUDObj (lpUDObj, TRUE);
  return TRUE;

} // FUserDefIteratorSetPropString


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefIteratorChangeVal
//
// Purpose:
//  Changes the value of the data stored.
//
////////////////////////////////////////////////////////////////////////////////
DLLFUNC BOOL OFC_CALLTYPE
FUserDefIteratorChangeVal
  (LPUDOBJ lpUDObj,                     // Pointer to object
   LPUDITER lpUDIter,                   // Pointer to iterator
   UDTYPES udtype,                      // The type
   LPVOID lpv,                          // New value.
   BOOL fLinkInvalid)                   // Is the link still valid?
{
  if ((lpUDObj == NULL)   ||
      (lpData == NULL)    ||
      (lpUDIter == NULL)  ||
      (!ISUDTYPE (udtype))   ||
      (lpUDIter->lpudp == NULL))
    return FALSE;

  if (fLinkInvalid)
  {
      if (lpUDIter->lpudp->lpstzLink == NULL)
          return FALSE;
      lpUDIter->lpudp->fLinkInvalid = TRUE;
      return TRUE;
  }
  else
      lpUDIter->lpudp->fLinkInvalid = FALSE;


  if (!FPropVarLoad (lpUDIter->lpudp->lppropvar, (VARTYPE)udtype, lpv))
      return FALSE;


  OfficeDirtyUDObj (lpUDObj, TRUE);
  return TRUE;

} // FUserDefIteratorChangeVal


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefIteratorSetLink
//
// Purpose:
//  Sets the link value for a property.  This is NOT a public API.
//
////////////////////////////////////////////////////////////////////////////////
BOOL PASCAL
FUserDefIteratorSetLink
  (LPUDOBJ lpUDObj,                     // Pointer to object
   LPUDITER lpUDIter,                   // Pointer to iterator
   LPTSTR lpszLink)                      // New link name
{
  if ((lpUDObj == NULL)   ||
      (lpData == NULL)    ||
      (lpUDIter == NULL)  ||
      (lpUDIter->lpudp == NULL))
    return FALSE;

   Assert(lpszLink != NULL);
   Assert(*lpszLink != TEXT('\0'));
   // Should already be a link
   Assert(lpUDIter->lpudp->lpstzLink != NULL);

   if (!FUdpropSetString (lpUDIter->lpudp, lpszLink, FALSE, FALSE))
       return FALSE;

   return TRUE;

} // FUserDefIteratorSetLink


////////////////////////////////////////////////////////////////////////////////
//
// LpudiUserDefCreateIterFromLpudp
//
// Purpose:
//  Creates an iterator object from a node.  This is not a public API.
//
////////////////////////////////////////////////////////////////////////////////
LPUDITER PASCAL
LpudiUserDefCreateIterFromLpudp
  (LPUDOBJ lpUDObj,                     // Pointer to object
   LPUDPROP lpudp)                      // Pointer to node
{
  LPUDITER lpudi;

  if ((lpUDObj == NULL) ||
      (lpData == NULL)  ||
      (lpudp == NULL))
    return NULL;

  lpudi = LpudiUserDefCreateIterator (lpUDObj);
  if (lpudi != NULL)
    lpudi->lpudp = lpudp;

  return lpudi;

} // LpudiUserDefCreateIterFromLpudp


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefIsHidden
//
// Purpose:
//  Determine if a Property string is hidden.
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefIsHidden
  (LPUDOBJ lpUDObj,             // Pointer to object
   LPTSTR lpsz)                  // Property string
{
   if (lpsz == NULL)
    return FALSE;

  // We don't really need the object, we can tell from the name
  return (lpsz[0] == HIDDENPREFIX);

} // FUserDefIsHidden


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefMakeVisible
//
// Purpose:
//  Make a property visible based on the Property string
//
////////////////////////////////////////////////////////////////////////////////
DLLEXPORT BOOL
FUserDefMakeVisible
  (LPUDOBJ lpUDObj,             // Pointer to object
   LPTSTR lpsz)                  // String to hide.
{
  LPUDPROP lpudprop;
  ULONG    cb;

  if ((lpUDObj == NULL)   ||
      (lpData == NULL)    ||
      (lpsz == NULL))
    return FALSE;

    // Find the name
  lpudprop = LpudpropFindMatchingName (lpUDObj, lpsz);
  if (lpudprop == NULL)
    return FALSE;


  if (!FUdpropMakeVisible (lpudprop))
    return FALSE;

  OfficeDirtyUDObj (lpUDObj, TRUE);
  return TRUE;

} // FUserDefMakeVisible


////////////////////////////////////////////////////////////////////////////////
//
// FUserDefMakeHidden
//
// Purpose:
//  Hide a Property based on the Property string.
//
////////////////////////////////////////////////////////////////////////////////
static BOOL PASCAL
FUserDefMakeHidden
  (LPUDOBJ lpUDObj,             // Pointer to object
   LPTSTR lpsz)                  // String to hide
{
  LPUDPROP lpudprop;
  LPTSTR lpstzT;

  if ((lpUDObj == NULL)   ||
      (lpData == NULL)    ||
      (lpsz == NULL))
    return FALSE;

    // Find the name
  lpudprop = LpudpropFindMatchingName (lpUDObj, lpsz);
  if (lpudprop == NULL)
    return FALSE;

  if (!FUdpropMakeHidden (lpudprop))
      return FALSE;

  OfficeDirtyUDObj (lpUDObj, TRUE);
  return TRUE;

} // FUserDefMakeHidden


////////////////////////////////////////////////////////////////////////////////
//
// LpudpropFindMatchingName
//
// Purpose:
//  Returns a node with a matching name, NULL otherwise.
//
////////////////////////////////////////////////////////////////////////////////
LPUDPROP PASCAL
LpudpropFindMatchingName
  (LPUDOBJ lpUDObj,             // Pointer to object
   LPTSTR lpsz)                  // String to search for
{
  LPUDPROP lpudprop;
  TCHAR sz[256];
  BOOL fCopy = FALSE;

  if ((lpUDObj == NULL) || (lpData == NULL))
     return(NULL);

  if (CchTszLen(lpsz) > 255)
     {
     PbSzNCopy(sz, lpsz, 255);
     sz[255] = 0;
     fCopy = TRUE;
     }

    // Check the cache first
  if (lpData->lpudpCache != NULL)
  {
    Assert ((lpData->lpudpCache->lpstzName != NULL));

      // lstrcmpi returns 0 if 2 strings are equal.....
    if (!(lstrcmpi (fCopy ? sz : lpsz, (LPTSTR)PSTR (lpData->lpudpCache->lpstzName))))
      return lpData->lpudpCache;
  }

  lpudprop = lpData->lpudpHead;

  while (lpudprop != NULL)
  {
    Assert ((lpudprop->lpstzName != NULL));

      // lstrcmpi returns 0 if 2 strings are equal.....
    if (!(lstrcmpi (fCopy ? sz : lpsz, (LPTSTR)PSTR (lpudprop->lpstzName))))
    {
        // Set the cache to the last node found
      lpData->lpudpCache = lpudprop;
      return lpudprop;
    }

    lpudprop = (LPUDPROP) lpudprop->llist.lpllistNext;

  } // while

  return NULL;

} // LpudpropFindMatchingName


////////////////////////////////////////////////////////////////////////////////
//
//  LpudpropFindMatchingPID
//
//  Purpose:
//      Searches the linked-list in the caller-provided UDINFO structure
//      for a UDPROP with the requested PropID.
//
//  Inputs:
//      LPUDOBJ - The UDINFO structure
//      PROPID  - The PID to search for.
//
//  Output:
//      The requested LPUDPROP, or NULL if not found.
//
////////////////////////////////////////////////////////////////////////////////
LPUDPROP PASCAL
LpudpropFindMatchingPID
  (LPUDOBJ lpUDObj,
   PROPID propid)
{
    //  ------
    //  Locals
    //  ------

    LPUDPROP lpudprop = NULL;
    BOOL fCopy = FALSE;

    //  -----
    //  Begin
    //  -----

    // Validate the inputs.

    if ((lpUDObj == NULL) || (lpData == NULL))
    {
        AssertSz (0, TEXT("Invalid inputs"));
        goto Exit;
    }


    // Check the cache first

    if (lpData->lpudpCache != NULL
        &&
        lpData->lpudpCache->propid == propid)
    {
        lpudprop = lpData->lpudpCache;
        goto Exit;
    }

    // Search the linked-list.

    lpudprop = lpData->lpudpHead;
    while (lpudprop != NULL)
    {
        if (lpudprop->propid == propid)
        {
            lpData->lpudpCache = lpudprop;
            goto Exit;
        }

        lpudprop = (LPUDPROP) lpudprop->llist.lpllistNext;

    }

    //  ----
    //  Exit
    //  ----

Exit:

  return lpudprop;


} // LpudpropFindMatchingPID



////////////////////////////////////////////////////////////////////////////////
//
// FAddPropToList
//
// Purpose:
//  Adds the given object to the list.  The type and value must
//  be filled in before calling this.
//
// The linked-list we're adding to has one entry for each of
// the user-defined properties.  Each entry has the property
// value, it's PID, and it's name.  If the property is linked
// to document content, the link name (e.g. a Bookmark name
// in Word) is also in this entry.  Note that the property set
// stores the property value as one property, with its name in the
// dictionary, and it stores the link name as a second property.
// Consequently, this routine will be called twice for such
// properties:  on the first call we'll create a new entry in the
// linked-list, adding the property ID, name, and value; on the
// second call we'll pull out that entry, and add the link name.
//
// On success, the input lppropvar & lpstatpropstg are cleared.
// On error, all inputs are left unmodified.
//
////////////////////////////////////////////////////////////////////////////////
BOOL PASCAL
FAddPropToList
  (LPUDOBJ lpUDObj,
   LPPROPVARIANT lppropvar,
   STATPROPSTG   *lpstatpropstg,
   LPUDPROP lpudprop)           // Property to add
{
    //  ------
    //  Locals
    //  ------

    BOOL                fSuccess = FALSE;
    LPTSTR              lpstz;
    LPUDPROP            lpudpT;
    BOOL                fLink;
    USES_CONVERSION ;

    Assert(lpUDObj != NULL);
    Assert(lpudprop != NULL);      // Is this a bogus assert?
    Assert(lppropvar != NULL && lpstatpropstg != NULL);

    // If the PId has one of the special masks, strip it off
    // so the PId will match the normal value.

    fLink = lpstatpropstg->propid & PID_LINKMASK;
    lpstatpropstg->propid &= ~PID_LINKMASK;


    //  ------------------------------------------------------------
    //  See if we can find this property already in the linked-list.
    //  If we have a name, use that, otherwise use the PID.
    //  ------------------------------------------------------------

    if (lpstatpropstg->lpwstrName != NULL)
    {
        // Search by name.

		//	[scotthan] Re: bogus cast to TCHAR in propio.c when this thing
        //  was read out of the file.  If this is an ANSI build, it's going to store
        //  a TCHAR* value!.  So we need to reciprocate the cast...
        lpudpT = LpudpropFindMatchingName (lpUDObj, (LPTSTR)lpstatpropstg->lpwstrName );
    }
    else
    {
        // Search by PID
        lpudpT = LpudpropFindMatchingPID (lpUDObj, lpstatpropstg->propid);
    }

    //  --------------------------------------------------------------
    //  If this property isn't already in the linked-list, add it now.
    //  --------------------------------------------------------------

    if (lpudpT == NULL)
    {
        // This should be a named property.  If it's not
        // named, then it should be a link, and the property
        // it links should have been in the linked-list already
        // (i.e., the lpudpT should have been non-NULL).

        if (lpstatpropstg->lpwstrName == NULL)
        {
            AssertSz (0, TEXT("Missing name in User-Defined properties"));
            goto Exit;
        }

        // Allocate memory for the property value.

        lpudprop->lppropvar = CoTaskMemAlloc (sizeof(PROPVARIANT));
        if (lpudprop->lppropvar == NULL)
        {
            AssertSz (0, TEXT("Couldn't allocate lpudprop->lppropvar"));
            goto Exit;
        }

        // Load the property ID, name, and value.
        // Note that if we had an error before here, we left
        // the caller's inputs un-touched.  Since no more errors
        // can occur, we'll never have half-modified data in an
        // error case.

        lpudprop->propid = lpstatpropstg->propid;

		//	[scotthan] Re: bogus cast to TCHAR in propio.c when this thing
        //  was read out of the file.  If this is an ANSI build, it's going to store
        //  a TCHAR* value!.  So we need to reciprocate the cast...
        lpudprop->lpstzName = (LPTSTR)lpstatpropstg->lpwstrName;
        lpstatpropstg->lpwstrName = NULL;

        *lpudprop->lppropvar = *lppropvar;
        PropVariantInit (lppropvar);

        lpData->dwcProps++;
        AddNodeToList (lpUDObj, lpudprop);

    } // if ((lpudpT = LpudpropFindMatchingName (lpUDInfo, lpstatpropstg->lpwsz)) == NULL)


    //  --------------------------------------------------------
    //  Otherwise (this property is already in the linked-list),
    //  add this new link name or value to the UDPROP.
    //  --------------------------------------------------------

    else
    {
        // If this is a link being added, then update the link-name in the
        // extant property.

        if (fLink)
        {
            // lpudpT points to the entry in our linked-list for this
            // property.  But it shouldn't already have a link-name (there
            // can only be one link-name per property).

            if (lpudpT->lpstzLink != NULL)
            {
                AssertSz (0, TEXT("Invalid property set - link name defined twice"));
                goto Exit;
            }

            // Since this is a link-name, it should be a string.

            if (lppropvar->vt != VT_LPTSTR)
            {
                AssertSz (0, TEXT("Invalid property set - link name isn't a string"));
                goto Exit;
            }

            // Point the UDPROP to the link name, and take ownership
            // of it by clearing the caller's pointer.

            Assert (lppropvar->pszVal != NULL);

            lpudpT->lpstzLink = (LPTSTR) lppropvar->pszVal;
            PropVariantInit (lppropvar);

            lpData->dwcLinks++;

        }   // if (fLink)

        // Otherwise, this isn't a link name, it's a value.  So point the
        // UDPROP to it's data.

        else
        {
            *lpudpT->lppropvar = *lppropvar;
            PropVariantInit (lppropvar);

        }   // if (fLink) ... else

    } // if ((lpudpT = LpudpropFindMatchingName ... else

    fSuccess = TRUE;

Exit:

    // Just in case we were given a name that we didn't
    // need, clear it now so that the caller knows that
    // on success, they needn't worry about the buffers
    // pointed to by lppropvar & lpstatpropstg.

    if (fSuccess)
    {
        if (lpstatpropstg->lpwstrName != NULL)
        {
            CoTaskMemFree (lpstatpropstg->lpwstrName);
            lpstatpropstg->lpwstrName = NULL;
        }
    }

    return(fSuccess);

} // FAddPropToList


////////////////////////////////////////////////////////////////////////////////
//
// AddNodeToList
//
// Purpose:
//  Adds the given node to the list.
//
////////////////////////////////////////////////////////////////////////////////

void PASCAL
AddNodeToList
  (LPUDOBJ lpUDObj,             // Pointer to object
   LPUDPROP lpudprop)           // Node to add
{
    // Put the new node at the end

    if (lpData->lpudpHead != NULL)
    {
        if (lpData->lpudpHead->llist.lpllistPrev != NULL)
        {
            ((LPUDPROP) lpData->lpudpHead->llist.lpllistPrev)->llist.lpllistNext = (LPLLIST) lpudprop;
            lpudprop->llist.lpllistPrev = lpData->lpudpHead->llist.lpllistPrev;
        }
        else
        {
            lpData->lpudpHead->llist.lpllistNext = (LPLLIST) lpudprop;
            lpudprop->llist.lpllistPrev = (LPLLIST) lpData->lpudpHead;
        }
        lpData->lpudpHead->llist.lpllistPrev = (LPLLIST) lpudprop;
    }
    else
    {
        lpData->lpudpHead = lpudprop;
        lpudprop->llist.lpllistPrev = NULL;
    }

    lpudprop->llist.lpllistNext = NULL;
    lpData->lpudpCache = lpudprop;

} // AddNodeToList


////////////////////////////////////////////////////////////////////////////////
//
// RemoveFromList
//
// Purpose:
//  Removes the given node from the list
//
////////////////////////////////////////////////////////////////////////////////
static void PASCAL
RemoveFromList
  (LPUDOBJ lpUDObj,                     // Pointer to object
   LPUDPROP lpudprop)                   // The node itself.
{
  AssertSz ((lpData->lpudpHead != NULL), TEXT("List is corrupt"));

    // If we're removing the cached node, invalidate the cache
  if (lpudprop == lpData->lpudpCache)
  {
    lpData->lpudpCache = NULL;
  }

    // Be sure the head gets updated, if the node is at the front
  if (lpudprop == lpData->lpudpHead)
  {
    lpData->lpudpHead = (LPUDPROP) lpudprop->llist.lpllistNext;

    if (lpData->lpudpHead != NULL)
    {
      lpData->lpudpHead->llist.lpllistPrev = lpudprop->llist.lpllistPrev;
    }
    return;
  }

    // Update the links
  if (lpudprop->llist.lpllistNext != NULL)
  {
    ((LPUDPROP) lpudprop->llist.lpllistNext)->llist.lpllistPrev = lpudprop->llist.lpllistPrev;
  }

  if (lpudprop->llist.lpllistPrev != NULL)
  {
    ((LPUDPROP) lpudprop->llist.lpllistPrev)->llist.lpllistNext = lpudprop->llist.lpllistNext;
  }

    // If it is the last node in the list, be sure the head is updated
  if (lpudprop == (LPUDPROP) lpData->lpudpHead->llist.lpllistPrev)
  {
    lpData->lpudpHead->llist.lpllistPrev = lpudprop->llist.lpllistPrev;
  }

} // RemoveFromList


////////////////////////////////////////////////////////////////////////////////
//
// DeallocValue
//
// Purpose:
//  Deallocates the value in the buffer.
//
////////////////////////////////////////////////////////////////////////////////
void PASCAL
DeallocValue
  (LPVOID *lplpvBuf,                    // Pointer to buffer to dealloc
   UDTYPES udtype)                      // Type stored in buffer
{
  DWORD cb=0;

  if (*lplpvBuf == NULL)
      return;

  switch (udtype)
  {
    case wUDdate  :
    case wUDfloat :
      Assert(sizeof(FILETIME) == sizeof(NUM));
                cb = sizeof(NUM);
                break;

    case wUDlpsz  :
      cb =  CBTSTR(*lplpvBuf);
                break;

         case   wUDbool:
         case   wUDdw:
                 AssertSz (((sizeof(LPVOID) >= sizeof(DWORD)) && (sizeof(LPVOID) >= sizeof(WORD))),
                TEXT("Sizes of basic types have changed!"));
                 return;

    default:
                Assert(fFalse);
                return;
  }


   VFreeMemP(*lplpvBuf,cb);
        *lplpvBuf = NULL;

} // DeallocValue


////////////////////////////////////////////////////////////////////////////////
//
//  VUdpropFree
//
//  Purpose:
//      Free a UDPROP (which is in a linked-list).
//
//  Inputs:
//      LPUDPROP * - A pointer-to-pointer-to a UDPROP object.
//
//  Output:
//      None.
//
////////////////////////////////////////////////////////////////////////////////
VOID
VUdpropFree
  (LPUDPROP *lplpudp)
{
    // Validate the inputs.

    if (lplpudp == NULL
        ||
        *lplpudp == NULL)
    {
        goto Exit;
    }

    // If this property has a name, free that buffer.

    if ((*lplpudp)->lpstzName)
    {
        CoTaskMemFree ((*lplpudp)->lpstzName);
    }

    // If this property has a link-name, free it too.

    if ((*lplpudp)->lpstzLink)
    {
        CoTaskMemFree ((*lplpudp)->lpstzLink);
    }

    // Clear the property value, which will free any associated
    // buffer.  Then free the PropVariant itself.

    PropVariantClear ((*lplpudp)->lppropvar);
    CoTaskMemFree ((*lplpudp)->lppropvar);
    CoTaskMemFree (*lplpudp);

    *lplpudp = NULL;

Exit:

    return;

} // VUdpropFree


////////////////////////////////////////////////////////////////////////////////
//
//  FUdpropUpdate
//
//  Purpose:
//      Updates the given node with the given data
//
//      It's the caller's responsibility to free lpudp if this function
//      fails.
//
//  Inputs:
//      LPUDPROP    - The node in the linked-list for this property.
//      LPUDOBJ     - All User-Defined data (including the properties)
//      LPTSTR      - The property name.
//      LPTSTR      - The link-name
//      LPVOID      - The new value
//      UDTYPES     - The type of the value.
//      BOOL        - TRUE if this is a link.
//
////////////////////////////////////////////////////////////////////////////////
static BOOL PASCAL
FUdpropUpdate
  (LPUDPROP lpudp,
   LPUDOBJ  lpUDObj,
   LPTSTR   lpszPropName,
   LPTSTR   lpszLinkMonik,
   LPVOID   lpvValue,
   UDTYPES  udtype,
   BOOL     fLink)
{
    //  ------
    //  Locals
    //  ------

    BOOL fSuccess = FALSE;

    //  -----
    //  Begin
    //  -----


    // Validate the inputs.

    if ((lpudp == NULL)  ||
        (lpszPropName == NULL) ||
        (lpvValue == NULL) ||
        (fLink && (lpszLinkMonik == NULL)) ||
        (!ISUDTYPE(udtype)))
    {
        goto Exit;
    }

    // Update the property name

    if (!FUdpropSetString (lpudp, lpszPropName, TRUE, TRUE))
        goto Exit;

    // If necessary, allocate a PropVariant for the UDPROPS

    if (lpudp->lppropvar == NULL)
    {
        lpudp->lppropvar = CoTaskMemAlloc (sizeof(PROPVARIANT));
        if (lpudp->lppropvar == NULL)
        {
            goto Exit;
        }
    }

    // Put the property value into the PropVariant

    PropVariantClear (lpudp->lppropvar);
    if (!FPropVarLoad (lpudp->lppropvar, (VARTYPE)udtype, lpvValue))
    {
        goto Exit;
    }

    // Update the link name if this is a link, otherwise
    // free any existing link name.

    if (fLink)
    {
        if(!FUdpropSetString (lpudp, lpszLinkMonik, FALSE, FALSE))
            goto Exit;
    }
    else
    {
        VUdpropFreeString (lpudp, FALSE);
        lpData->dwcLinks--;
    }

    //  ----
    //  Exit
    //  ----

    fSuccess = TRUE;

Exit:

    return(fSuccess);

} // FUdpropUpdate


////////////////////////////////////////////////////////////////////////////////
//
// FMakeTmpUDProps
//
// Purpose:
//  Create a temporary copy of the User-Defined property data
//
////////////////////////////////////////////////////////////////////////////////
BOOL
FMakeTmpUDProps
  (LPUDOBJ lpUDObj)                     // Pointer to object
{
    //  ------
    //  Locals
    //  ------

    BOOL     fSuccess = FALSE;

    LPUDPROP lpudpCur;
    LPUDPROP lpudpTmpCur;
    DWORD dw;
    LPVOID lpv;

    //  -----
    //  Begin
    //  -----

    // Validate the inputs.

    if ((lpUDObj == NULL) ||
        (lpData == NULL))
    {
        goto Exit;
    }

    FDeleteTmpUDProps (lpUDObj);

    // Move all the original list data to the tmp list
    lpData->dwcTmpLinks = lpData->dwcLinks;
    lpData->dwcTmpProps = lpData->dwcProps;
    lpData->lpudpTmpHead = lpData->lpudpHead;
    lpData->lpudpTmpCache = lpData->lpudpCache;

    // Reinitialize the object data
    lpData->dwcLinks = 0;
    lpData->dwcProps = 0;
    lpData->lpudpCache = NULL;
    lpudpTmpCur = lpData->lpudpHead = NULL;

    // Remember that we just put all the original data in the tmp ptrs.
    lpudpCur = lpData->lpudpTmpHead;

    // Loop through the old data and copy to the temp list

    while (lpudpCur != NULL)
    {
        // Create a new UDPROP

        lpudpTmpCur = LpudpropCreate();
        if (lpudpTmpCur == NULL)
            goto Exit;

        // Set the name in the UDPROP

        if (!FUdpropSetString (lpudpTmpCur, lpudpCur->lpstzName, FALSE, TRUE))
            goto Exit;

        // If we have a link-name, set it too in the UDPROP

        if (lpudpCur->lpstzLink != NULL)
        {
            if (!FUdpropSetString (lpudpTmpCur, lpudpCur->lpstzLink, FALSE, FALSE))
                goto Exit;
            lpData->dwcLinks++;
        }

        // Allocate a PropVariant to hold the property value.

        lpudpTmpCur->lppropvar = CoTaskMemAlloc (sizeof(PROPVARIANT));
        if (lpudpTmpCur->lppropvar == NULL)
        {
            goto Exit;
        }

        // Copy the PropVariant into the temporary UDPROP.

        PropVariantCopy (lpudpTmpCur->lppropvar, lpudpCur->lppropvar);

        // Also show if this is an invalid link or not.

        lpudpTmpCur->fLinkInvalid = lpudpCur->fLinkInvalid;

        // Add this new temporary UDPROP to the linked-list.

        AddNodeToList (lpUDObj, lpudpTmpCur);
        lpData->dwcProps++;

        // Move on to the next property.

        lpudpCur = (LPUDPROP) lpudpCur->llist.lpllistNext;

    }   // while (lpudpCur != NULL)


    //  ----
    //  Exit
    //  ----

    fSuccess = TRUE;

Exit:


    // If there was an error, put everything back and deallocate anything we created

    if (!fSuccess)
    {
        FSwapTmpUDProps (lpUDObj);
        FDeleteTmpUDProps (lpUDObj);
    }


    return fSuccess;

} // FMakeTmpUDProps


////////////////////////////////////////////////////////////////////////////////
//
// FSwapTmpUDProps
//
// Purpose:
//  Swap the "temp" copy with the real copy of User-Defined property data
//
////////////////////////////////////////////////////////////////////////////////
BOOL
FSwapTmpUDProps
  (LPUDOBJ lpUDObj)
{
    DWORD dwT;
    LPUDPROP lpudpT;

    if ((lpUDObj == NULL) ||
        (lpData == NULL))
        return FALSE;

    dwT = lpData->dwcLinks;
    lpData->dwcLinks = lpData->dwcTmpLinks;
    lpData->dwcTmpLinks = dwT;

    dwT = lpData->dwcProps;
    lpData->dwcProps = lpData->dwcTmpProps;
    lpData->dwcTmpProps = dwT;

    lpudpT = lpData->lpudpHead;
    lpData->lpudpHead = lpData->lpudpTmpHead;
    lpData->lpudpTmpHead = lpudpT;

    lpudpT = lpData->lpudpCache;
    lpData->lpudpCache = lpData->lpudpTmpCache;
    lpData->lpudpTmpCache = lpudpT;

    return TRUE;

} // FSwapTmpUDProps


////////////////////////////////////////////////////////////////////////////////
//
// FDeleteTmpUDProps
//
// Purpose:
//  Delete the "temp" copy of the data
//
////////////////////////////////////////////////////////////////////////////////
BOOL
FDeleteTmpUDProps
  (LPUDOBJ lpUDObj)
{
  if ((lpUDObj == NULL) ||
      (lpData == NULL))
    return FALSE;

  FreeUDData (lpUDObj, TRUE);

  return TRUE;

} // FDeleteTmpU


////////////////////////////////////////////////////////////////////////////////
//
//  FUdpropMakeVisible
//
//  Purpose:
//      Make a property in a UDPROP visibile by removing the
//      "_" that prepends the property name.
//
//  Inputs:
//      LPUDPROP - The UDPROP to convert.
//
//  Output:
//      TRUE if successful.
//
////////////////////////////////////////////////////////////////////////////////

static BOOL PASCAL
FUdpropMakeVisible (LPUDPROP lpudprop)
{
    //  ------
    //  Locals
    //  ------

    BOOL    fSuccess = FALSE;
    ULONG   cch;

    //  -----
    //  Begin
    //  -----

    Assert (lpudprop != NULL);

    // Is there anything to process?

    if (lpudprop->lpstzName == NULL)
        goto Exit;


    // Simply move the string up one character, thus overlaying
    // the prefix indicating a hidden property.  If it's already
    // a visible property we don't flag an error.

    if (*lpudprop->lpstzName == HIDDENPREFIX)
    {
        cch = CchTszLen (lpudprop->lpstzName) + 1;
        PbSzNCopy (lpudprop->lpstzName, &lpudprop->lpstzName[1], cch);
    }

    fSuccess = TRUE;

    //  ----
    //  Exit
    //  ----

Exit:

    return (fSuccess);

}   // FUdpropMakeVisible


////////////////////////////////////////////////////////////////////////////////
//
//  FUdpropMakeHidden
//
//  Purpose:
//      Convert a property in a UDPROP so that it is a hidden
//      property.  Properties are considered hidden if the
//      first character in their name is an "_".
//
//  Inputs:
//      LPUDPROP - The UDPROP to convert.
//
//  Output:
//      TRUE if successful.
//
////////////////////////////////////////////////////////////////////////////////

static BOOL PASCAL
FUdpropMakeHidden (LPUDPROP lpudprop)
{
    //  ------
    //  Locals
    //  ------

    BOOL    fSuccess = FALSE;
    ULONG   cch;
    LPTSTR  lpstzOld = NULL;

    //  -----
    //  Begin
    //  -----

    // Intialize

    Assert (lpudprop != NULL);

    if (lpudprop->lpstzName == NULL)
        goto Exit;

    // Keep the old name.

    lpstzOld = lpudprop->lpstzName;

    // How many characters do we need in the new string?

    cch = CchTszLen (lpstzOld) + 2; // Includes the NULL & prefix

    // Allocate the memory.

    lpudprop->lpstzName = CoTaskMemAlloc (cch * sizeof(TCHAR));
    if (lpudprop->lpstzName == NULL)
        goto Exit;

    // Set the "_" prefix to indicate this is a hidden property.

    lpudprop->lpstzName[0] = HIDDENPREFIX;

    // Copy the original property name after the prefix in the UDPROP.

    PbSzNCopy (&lpudprop->lpstzName[1],
               lpstzOld,
               cch - 1); // One chacter less than cch to accout for hidden prefix.

    // Free the old buffer

    CoTaskMemFree (lpstzOld);

    //  ----
    //  Exit
    //  ----

    fSuccess = TRUE;

Exit:

    // If there was an error, ensure that the UDPROP is left as
    // we found it.

    if (!fSuccess)
    {
        if (lpstzOld != NULL)
        {
            if (lpudprop->lpstzName != NULL)
            {
                CoTaskMemFree (lpudprop->lpstzName);
            }
            lpudprop->lpstzName = lpstzOld;
        }
    }

    return (fSuccess);

}   // FUdpropMakeHidden


////////////////////////////////////////////////////////////////////////////////
//
//  FUdpropSetString
//
//  Purpose:
//      Set the name or link-name string in a UDPROP.
//      If the UDPROP already contains the string, free
//      it.
//
//  Inputs:
//      LPUDPROP    - A UDPROP (a property in the linked-list)
//      LPTSTR      - The new name or link-name
//      BOOL        - True => limit the length of the string to BUFMAX characters
//                            (including the NULL terminator)
//      BOOL        - True => set the (property) name, False => set the link-name
//
////////////////////////////////////////////////////////////////////////////////

static BOOL PASCAL
FUdpropSetString
    (LPUDPROP   lpudp,
     LPTSTR     lptstr,
     BOOL       fLimitLength,
     BOOL       fName)
{
    //  ------
    //  Locals
    //  ------

    BOOL    fSuccess = FALSE;   // Return value
    LPTSTR  lptstrNew = NULL;   // Pointed to be the UDPROP.
    ULONG   cch, cb;            

    //  ----------
    //  Initialize
    //  ----------

    // Validate the inputs.

    if (lpudp == NULL
        ||
        lptstr == NULL)
    {
        goto Exit;
    }

    //  ----------------
    //  Set the new name
    //  ----------------

    // Calculate the sizes.

    cch = CchTszLen(lptstr);
    if (fLimitLength && cch >= BUFMAX)
    {
        cch = BUFMAX - 1;
    }
    cb = (cch + 1) * sizeof(TCHAR); // Leave room for the NULL.

    // Allocate new memory.

    lptstrNew = CoTaskMemAlloc (cb);
    if (lptstrNew == NULL)
    {
        goto Exit;
    }

    // Copy the buffer (the buffer size is cch+1 including the NULL)
    // Also, terminate the target string, since it may be a truncation
    // of the source string.

    PbSzNCopy (lptstrNew, lptstr, cch+1);
    lptstrNew[cch] = TEXT('\0');

    // Put this new buffer in the UDPROP.

    if (fName)
        lpudp->lpstzName = lptstrNew;
    else
        lpudp->lpstzLink = lptstrNew;

    lptstrNew = NULL;

    //  ----
    //  Exit
    //  ----

    fSuccess = TRUE;

Exit:

    if (lptstrNew != NULL)
        CoTaskMemFree (lptstrNew);

    return (fSuccess);

}   // FUdpropSetString



////////////////////////////////////////////////////////////////////////////////
//
//  VUdpropFreeString
//
//  Purpose:
//      Free one of the two strings in a UDPROP - the
//      name string or the link-name string.  It is not
//      considered an error if either the UDPROP or the
//      string doesn't exist.
//
//  Inputs:
//      LPUDPROP    - The UDPROP containing the strings.
//      BOOL        - TRUE indicates we should free the
//                    name, FALSE indicates we should free
//                    the link name.
//
//  Output:
//      None.
//
////////////////////////////////////////////////////////////////////////////////

static void PASCAL
VUdpropFreeString
    (LPUDPROP   lpudp,
     BOOL       fName)
{

    // Is this really a UDPROP?

    if (lpudp != NULL)
    {
        // Should we delete the name?

        if (fName && lpudp->lpstzName)
        {
            CoTaskMemFree (lpudp->lpstzName);
            lpudp->lpstzName = NULL;
        }

        // Should we delete the link name?

        else if (!fName && lpudp->lpstzLink)
        {
            CoTaskMemFree (lpudp->lpstzLink);
            lpudp->lpstzLink = NULL;
        }

    }   // if (lpudp != NULL)

    return;

}   // VUdpropFreeString


/////////////////////////////////////////////////////////////////////////////////////
//
//  LpudpropCreate
//
//  Purpose:
//      Create a new UDPROP structure (an element of a linked-
//      list, and holds information about a single property).
//
//  Inputs:
//      None
//
//  Output:
//      A LPUDPROP if successful, NULL otherwise.
//
/////////////////////////////////////////////////////////////////////////////////////

LPUDPROP
LpudpropCreate ( void )
{
    // Create a buffer for the UDPROP

    LPUDPROP lpudp = CoTaskMemAlloc (sizeof(UDPROP));

    // Zero the buffer.

    if (lpudp != NULL)
    {
        FillBuf (lpudp, 0, sizeof(UDPROP));
    }

    return (lpudp);

}   // LpudpropCreate


////////////////////////////////////////////////////////////////////////////////
//
//  LppropvarUserDefGetPropVal
//
//  Purpose:
//      Return a PropVariant pointer for the requested 
//      property (requested by property name).
//
//  Inputs:
//      LPUDOBJ     - All UD data (including properties)
//      LPTSTR      - The name of the desired property
//      BOOL *      - True if this is a link.
//      BOOL *      - True if this link is invalid.
//
//  Output:
//      An LPPROPVARINT, or NULL if there was an error.
//
////////////////////////////////////////////////////////////////////////////////

DLLEXPORT LPPROPVARIANT
LppropvarUserDefGetPropVal
    (LPUDOBJ lpUDObj,             // Pointer to object
     LPTSTR lpszProp,             // Property string
     BOOL *pfLink,                // Indicates a link
     BOOL *pfLinkInvalid)         // Is the link invalid
{
    //  ------
    //  Locals
    //  ------

    LPUDPROP lpudprop;
    LPPROPVARIANT lppropvar;

    //  --------------
    //  Initialization
    //  --------------

    if ((lpUDObj == NULL)   ||
        (lpData == NULL)    ||
        (lpszProp == NULL))
    {
        return NULL;
    }

    //  ---------------------------------
    //  Find the node that has this name.
    //  ---------------------------------

    lpudprop = LpudpropFindMatchingName (lpUDObj, lpszProp);
    if (lpudprop == NULL)
        return NULL;

    // Is this a link?
    if (pfLink != NULL)
    {
        *pfLink = (lpudprop->lpstzLink != NULL);
    }

    // Is this an invalid link?  (In the Shell, all properties are
    // invalid).

    if (pfLinkInvalid != NULL)
    {
#ifdef SHELL
        *pfLinkInvalid = lpudprop->fLinkInvalid = TRUE;
#else
        *pfLinkInvalid = lpudprop->fLinkInvalid;
#endif
    }

    //  ----
    //  Exit
    //  ----

    return (lpudprop->lppropvar);

} // LppropvarUserDefGetPropVal


////////////////////////////////////////////////////////////////////////////////
//
//  LppropvarUserDefGetIteratorVal
//
//  Purpose:
//      Given an iterator value, get the property value.
//
//  Inputs:
//      LPUDITER    - The Iterator value.
//      BOOL *      - Set to True if this value is a link.
//      BOLL *      - Set to True if this value is invalid link.
//
//  Outputs:
//      LPPROPVARIANT of the property value.
//
////////////////////////////////////////////////////////////////////////////////

DLLEXPORT LPPROPVARIANT
LppropvarUserDefGetIteratorVal
  (LPUDITER lpUDIter,
   BOOL *pfLink,
   BOOL *pfLinkInvalid )
{
  // Validate the inputs

  if ((lpUDIter == NULL)  ||
      (lpUDIter->lpudp == NULL))
    return NULL;

  // Is this a Link?

  if (pfLink != NULL)
  {
    *pfLink = (lpUDIter->lpudp->lpstzLink != NULL);
  }

  // Is this an invalid link?

  if (pfLinkInvalid != NULL)
  {
    *pfLinkInvalid = lpUDIter->lpudp->fLinkInvalid;
  }

  // Return a pointer to the PropVariant

  return (lpUDIter->lpudp->lppropvar);

} // LpvoidUserDefGetIteratorVal