Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

HTML Property PR_BODY_HTML or PR_HTML ?

1,337 views
Skip to first unread message

Microsoft

unread,
May 5, 2004, 11:45:18 AM5/5/04
to
Hi all,
I write C++ client extension, for 2000-2003 outlook.
I have a little problem:

On office 2000:
When I created body section by PR_BODY_HTML it's ok, till I try it with
server exchange 2003.
In exchange 2003 with PR_BODY_HTML, it's doesn't work (I got error message
from outlook preview pane).
But with PR_HTML (0x10130102) its work.

What the difference between them (except the value :text or binary) ?
When to use PR_BODY_HTML and when PR_HTML ?

thanks


Dmitry Streblechenko (MVP)

unread,
May 5, 2004, 12:52:15 PM5/5/04
to
HTML is is stored in
1. Outlook 98/2000 IMO mode - PR_HTML_BODY
2. Outlook 98/2000 C/W, Outlook 2002/2003 - PR_RTF_COMPRESSED
3. Outlook 2003 used with a message store that supports HTML natively
(Unicode PST and Exchange 2003) - PR_HTML. By Outlook still uses
PR_RTF_COMPRESSED even if PR_HTML is available.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool


"Microsoft" <1...@2.com> wrote in message
news:eFYlD$qMEHA...@tk2msftngp13.phx.gbl...

Volker Bartheld (SPAM only)

unread,
May 5, 2004, 1:52:06 PM5/5/04
to
Hi!

Must be a twist of fate... Only fortuna knows that I'm just fiddling
with programmatically creating a HTML message (containing inline
images), trying to write PR_HTML and add some attachments with
PR_ATTACH_CONTENT_ID set to the argument of <img
src="cid:##CONTENT_ID_HERE##">. *D-O-H*

On Wed, 5 May 2004 09:52:15 -0700, "Dmitry Streblechenko \(MVP\)"
<dmi...@dimastr.com> wrote :


>HTML is is stored in
>1. Outlook 98/2000 IMO mode - PR_HTML_BODY
>2. Outlook 98/2000 C/W, Outlook 2002/2003 - PR_RTF_COMPRESSED
>3. Outlook 2003 used with a message store that supports HTML natively
>(Unicode PST and Exchange 2003) - PR_HTML. By Outlook still uses
>PR_RTF_COMPRESSED even if PR_HTML is available.

So what is that supposed to mean? Opening a stream interface on either
PR_HTML_BODY and/or PR_HTML (depending on the type of message store) is
easy but - what if I should also generate PR_RTF_COMPRESSED from a
HTML-Source? I consider myself out of luck.

Or is the whole thing easier as I think?

Cheers,
Volker

P.S.: If a Messagestore doesn't support one or more of the
{PR_HTML_BODY, PR_HTML, PR_RTF_COMPRESSED} - it will complain in
HrSetOneProp() or IMAPIProp::SetProps() I hope...? Doesn't look like so
when the error first occurs in the preview pane... *ARGH*

>"Microsoft" <1...@2.com> wrote in message
>news:eFYlD$qMEHA...@tk2msftngp13.phx.gbl...

Dmitry Streblechenko (MVP)

unread,
May 5, 2004, 2:40:59 PM5/5/04
to
Yes, you will need to set the PR_RTF_COMPRESSED property, which is very easy
to do - you just need to encode HTML into RTF and use
WrapCompressedRTFStream to write it to PR_RTF_COMPRESSED. The code below
will not convert HTML formatting into RTF formatting, but Outlook never uses
it anyway.
Here is what I do (Delphi):

function HrHTML2RTF(const Value : string; CodePage : integer):string;
var i, p, j : integer;
strTag, strExtra : string;
ch : Char;
bInTag : boolean;
begin
Result:=Value;
if (CodePage = CP_UTF8) then CodePage:=0;
//
bInTag:=false;
for i:=Length(Result) downto 1 do begin
if Result[i] = '>' then bInTag:=true else if Result[i] = '<' then
bInTag:=false; //since we go length downto 1

if Result[i] = #9 then begin
System.Delete(Result, i, 1);
System.Insert('\tab', Result, i);
end
else if (byte(Result[i]) > 127) {or (Result[i] < ' ')} then begin
ch:=Result[i];
System.Delete(Result, i, 1);
System.Insert('\'''+IntToHex(byte(ch), 2), Result, i);
end
else if (Result[i] in ['{', '}', '\']) then begin
System.Insert('\', Result, i);
end
else if {(not bInTag) and} (Result[i] = #13) and (i < Length(Result))
and (Result[i+1] = #10) then begin
System.Insert(' ', Result, i);
end
end;
//wrap the HTML tags
for i:=Length(Result)-1 downto 1 do begin
if Result[i] = '<' then begin
//found start of a tag. now find its end
p:=0;
for j:=i+1 to Length(Result) do begin
if Result[j] = '>' then begin
p:=j;
Break;
end;
end;
if p = 0 then begin
//invalid HTML?
Result:=Value; //oh well...
Break;
end;
strTag:=Copy(Result, i, p-i+1);
//is it a line break?
if (CompareText('P>', Copy(strTag, 2, 2)) = 0) or
(CompareText('P ', Copy(strTag, 2, 2)) = 0) or
(CompareText('BR>', Copy(strTag, 2, 3)) = 0) or
(CompareText('BR ', Copy(strTag, 2, 3)) = 0)
then strExtra:='\par '
else strExtra:='';
//wrap the HTML tag into RTF
if strTag[2] = '/'
then strTag:='{\*\htmltag8 ' + strExtra + strTag+'}' //closing tag
else strTag:='{\*\htmltag0 ' + strExtra + strTag+'}'; //opening tag
System.Delete(Result, i, p-i+1);
System.Insert(strTag, Result, i);
end;
end;
Result:='{\rtf1\ansi\ansicpg'+IntToStr(CodePage)+'\fromhtml1
\deff0{\fonttbl'#13#10+
'{\f0\fswiss\fcharset'+IntToStr(CodePage)+' Arial;}'#13#10+
'{\f1\fmodern\fcharset'+IntToStr(CodePage)+' Courier New;}'#13#10+
'{\f2\fnil\fcharset'+IntToStr(CodePage)+' Symbol;}'#13#10+
'{\f3\fmodern\fcharset'+IntToStr(CodePage)+' Courier
New;}}'#13#10+

//'{\colortbl\red0\green0\blue0;\red0\green0\blue255;\red255\green0\blue0;\r
ed0\green0\blue234;}'#13#10+
'\uc1\pard\plain\deftab360 \f0\fs24'#13#10+
Result+
'}';
end;


Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool


"Volker Bartheld (SPAM only)" <dr_ve...@freenet.de> wrote in message
news:c7b9h6$1thke$1...@ID-78102.news.uni-berlin.de...

ronen

unread,
May 6, 2004, 7:47:39 AM5/6/04
to
First of all thanks,

So you recommended to decide which property by outlook version and mode.
I have some questions about it:
1. How to get outlook version, I prefer the document way.
2. How to get outlook mode (IMO/ CW) ?
3. PR_HTML is available because message store of exchange 2003 ?.

Thanks,

"Dmitry Streblechenko (MVP)" <dmi...@dimastr.com> wrote in message
news:eeXUDCtM...@TK2MSFTNGP10.phx.gbl...

Volker Bartheld (SPAM only)

unread,
May 6, 2004, 6:54:29 AM5/6/04
to
Hi Dmitry!

On Wed, 5 May 2004 11:40:59 -0700, "Dmitry Streblechenko \(MVP\)"
<dmi...@dimastr.com> wrote :


>Yes, you will need to set the PR_RTF_COMPRESSED property, which is very easy
>to do - you just need to encode HTML into RTF and use
>WrapCompressedRTFStream to write it to PR_RTF_COMPRESSED. The code below
>will not convert HTML formatting into RTF formatting, but Outlook never uses
>it anyway.
>Here is what I do (Delphi):

>[snip]

Cool! Since it's probably "Example Code Giveaway Day" today, ;-) I gonna
keep you updated with my findings in creating HTLM/RTF mail with
embedded pics.

Back later...


THX,
Volker

>"Volker Bartheld (SPAM only)" <dr_ve...@freenet.de> wrote in message
>news:c7b9h6$1thke$1...@ID-78102.news.uni-berlin.de...

Volker Bartheld (SPAM only)

unread,
May 6, 2004, 8:19:11 AM5/6/04
to
Hi!

On Thu, 6 May 2004 13:47:39 +0200, "ronen" <1...@2.com> wrote :
>So you recommended to decide which property by outlook version and mode.
>I have some questions about it:
>1. How to get outlook version, I prefer the document way.

Since it's "Code Sample Giveaway Day" today, here we go: ;-)

1) Find outlook.exe

<getoutlookpath.cpp>
#include <windows.h>
#include <io.h>
#include <tchar.h>
#include <wintools/tchardefs.h>
#include "getoutlookpath.h"

// defines to retrieve the path to outlook.exe
#define OL_CLSID "SOFTWARE\\Classes\\Outlook.Application\\CLSID"
#define SW_CLSID "SOFTWARE\\Classes\\CLSID\\"
#define LOCALSERVER32 "\\LocalServer32"
#define OL_STD "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\OUTLOOK.EXE"


const TCHARSTRING StrVec(TCHARVEC& v)
{
TCHARSTRING s=TCHARSTRING(v.begin(), v.end());
s.resize(_tcslen(s.c_str()));
return s;
}


//
// get path to outlook.exe app
//
const TCHARSTRING I_GetOutlookPath()
{
TCHARVEC v, vDefault;
HKEY hKey;
DWORD dwBufLen, dwValues;
LONG lRet;
TCHARSTRINGSTREAM os;

// First, look at SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths as a fallback option
if(
ERROR_SUCCESS==RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(OL_STD), 0, KEY_QUERY_VALUE, &hKey) &&
ERROR_SUCCESS==RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwBufLen, NULL, NULL))
{
vDefault.resize(dwBufLen);
lRet=RegQueryValueEx(hKey, NULL, NULL, NULL, (LPBYTE)&vDefault[0], &dwBufLen); // query the registry value
RegCloseKey(hKey);
if(ERROR_SUCCESS!=lRet || _access(&vDefault[0], 0)) vDefault.clear(); // if unsuccessful, delete the default path
}

// Find Microsoft's CLSID for Outlook. You can also use here Excel.Application, Word.Application, etc. Whatever you find in the registry.
//if(ERROR_SUCCESS!=RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(OL_CLSID), 0, KEY_QUERY_VALUE, &hKey)) return sDefault;
if(ERROR_SUCCESS!=RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(OL_CLSID), 0, KEY_QUERY_VALUE, &hKey)) return StrVec(vDefault);
if(ERROR_SUCCESS!=RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &dwValues, NULL, &dwBufLen, NULL, NULL)) return StrVec(vDefault);
if(1!=dwValues) return StrVec(vDefault);

v.resize(dwBufLen);
lRet=RegQueryValueEx(hKey, NULL, NULL, NULL, (LPBYTE)&v[0], &dwBufLen);
RegCloseKey(hKey);
if(ERROR_SUCCESS!=lRet) return StrVec(vDefault);

os << TEXT(SW_CLSID) << StrVec(v) << TEXT(LOCALSERVER32);
// Now read registry for the CLSID we found. Default key under LocalServer32 is installation path.
if(ERROR_SUCCESS!=RegOpenKeyEx(HKEY_LOCAL_MACHINE, os.str().c_str(), 0, KEY_QUERY_VALUE, &hKey)) return StrVec(vDefault);
if(ERROR_SUCCESS!=RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwBufLen, NULL, NULL)) return StrVec(vDefault);
v.resize(dwBufLen);
lRet=RegQueryValueEx(hKey, NULL, NULL, NULL, (LPBYTE)&v[0], &dwBufLen);
RegCloseKey(hKey);
if(ERROR_SUCCESS!=lRet || _access(&v[0], 0)) return StrVec(vDefault);
return StrVec(v);
}
</getoutlookpath.cpp>

2) retrieve its file version. You can then check against to various know
OL file versions.

<fileversion.cpp>
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <malloc.h>
#include <string>
#include <sstream>

#include <wintools/tchardefs.h>

#include "fileversion.h"
// link with version.lib - needed for GetFileVersionInfoSize() and GetFileVersionInfo()
#pragma comment(lib, "version.lib")


//////////////////////////////////////////////////////////////////////
// return file version information
//////////////////////////////////////////////////////////////////////
//
// typedef struct tagVS_FIXEDFILEINFO
// {
// DWORD dwSignature; // e.g. 0xfeef04bd
// DWORD dwStrucVersion; // e.g. 0x00000042 = "0.42"
// DWORD dwFileVersionMS; // e.g. 0x00030075 = "3.75"
// DWORD dwFileVersionLS; // e.g. 0x00000031 = "0.31"
// DWORD dwProductVersionMS; // e.g. 0x00030010 = "3.10"
// DWORD dwProductVersionLS; // e.g. 0x00000031 = "0.31"
// DWORD dwFileFlagsMask; // = 0x3F for version "0.42"
// DWORD dwFileFlags; // e.g. VFF_DEBUG | VFF_PRERELEASE
// DWORD dwFileOS; // e.g. VOS_DOS_WINDOWS16
// DWORD dwFileType; // e.g. VFT_DRIVER
// DWORD dwFileSubtype; // e.g. VFT2_DRV_KEYBOARD
// DWORD dwFileDateMS; // e.g. 0
// DWORD dwFileDateLS; // e.g. 0
// } VS_FIXEDFILEINFO;
//////////////////////////////////////////////////////////////////////
//
// will return version information string of the file's version resource.
//
const std::string I_GetFileVersion(const TCHARSTRING& sPath, VS_FIXEDFILEINFO* pvs_FixedFileInfo)
{
DWORD dwSize, dwHandle;
void* pData=NULL;
UINT uiLen;
VS_FIXEDFILEINFO* pvs_FixedFileInfoT;
std::ostringstream os;

if(sPath.empty()) goto err; // bad path is an error
if(!(dwSize=GetFileVersionInfoSize(sPath.c_str(), &dwHandle))) goto err; // get size of file version info resource
if(!(pData=malloc(dwSize))) goto err; // allocate memory for it
if(!GetFileVersionInfo(sPath.c_str(), dwHandle, dwSize, pData)) goto err; // load the resource
if(!(VerQueryValue(pData, TEXT("\\"), (void**)&pvs_FixedFileInfoT, &uiLen))) goto err; // query the root block (VS_FIXEDFILEINFO)
if(pvs_FixedFileInfo) memcpy(pvs_FixedFileInfo, pvs_FixedFileInfoT, sizeof(VS_FIXEDFILEINFO)); // if desired, copy the whole block

os
<< (pvs_FixedFileInfoT->dwProductVersionMS>>16) << "." << (pvs_FixedFileInfoT->dwProductVersionMS&0xFFFF) << "."
<< (pvs_FixedFileInfoT->dwProductVersionLS>>16) << "." << (pvs_FixedFileInfoT->dwProductVersionLS&0xFFFF);

err:
if(pData) free(pData); // free data if necessary
return os.str();
}

//
// get the product version major and minor DWORDS from version string in sFileVer
//
const PRODUCT_VERSION_t I_GetFileVersionNum(const std::string sFileVer)
{
PRODUCT_VERSION_t ProdVer={0, 0};
unsigned long ul1, ul2, ul3, ul4;
if(4==sscanf(sFileVer.c_str(), "%u.%u.%u.%u", &ul1, &ul2, &ul3, &ul4))
{
ProdVer.m_dwProductVersionMS=(ul1<<16)+ul2;
ProdVer.m_dwProductVersionLS=(ul3<<16)+ul4;
} // if(4==sscanf(sFileVer.c_str(), "%u.%u.%u.%u", &ul1, &ul2, &ul3, &ul4))

return ProdVer;
}
</fileversion.cpp>

<fileversion.h>
// FILEVERSION.h: code for outlook FILEVERSION management
//
//////////////////////////////////////////////////////////////////////

#ifndef __FILEVERSION_H_INCLUDED_
#define __FILEVERSION_H_INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#pragma warning (disable : 4786) // disable warning for debug symbols overflowing 255 chars

#include <windows.h>
#include <string>
#include <wintools/tchardefs.h>

struct PRODUCT_VERSION_t
{
DWORD m_dwProductVersionMS;
DWORD m_dwProductVersionLS;
};

const std::string I_GetFileVersion(const TCHARSTRING& sPath, VS_FIXEDFILEINFO* pvs_FixedFileInfo=NULL);
const PRODUCT_VERSION_t I_GetFileVersionNum(const std::string sFileVer);


#endif // #ifndef __FILEVERSION_H_INCLUDED_
</fileversion.h>

<tchardefs.h>
// tchardefs.h:
//
//////////////////////////////////////////////////////////////////////

#if !defined(TCHARDEFS_H__INCLUDED_)
#define TCHARDEFS_H__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#pragma warning (disable : 4786) // disable warning for debug symbols overflowing 255 chars

#include <tchar.h>
#include <string>
#include <vector>
#include <sstream>

typedef std::basic_string < TCHAR > TCHARSTRING;
typedef std::vector < TCHAR > TCHARVEC;
typedef std::vector < TCHARSTRING > TCHARSTRINGVEC;
typedef std::basic_ostringstream< TCHAR > TCHARSTRINGSTREAM;

#endif // #if !defined(TCHARDEFS_H__INCLUDED_)
</tchardefs.h>

>2. How to get outlook mode (IMO/ CW) ?

Well, I'll leave the answer to Dmitry. Probably, it's just sufficient to
locate a message store with name PR_PROVIDER_DISPLAY=="Microsoft
Exchange Server":

<findexchange.cpp>
//////////////////////////////////////////////////////////////////////
// Locate an exchange server if there is any
// NB: For OL2000 (and probably 2002) this only works in *online mode*!
//////////////////////////////////////////////////////////////////////
HRESULT MAPI_FindExchange(LPEXCHEXTCALLBACK pEECB)
{
assert(pEECB);
HRESULT hr=S_OK;
IMAPISession* pSession=NULL;
IMAPITable* pMapiTable=NULL;
LPSRowSet lpRows=NULL;
IMsgStore* pMsgStore=NULL;

enum {ePR_ENTRYID, NUM_COLS};
static SizedSPropTagArray(NUM_COLS, PropEntryId)={NUM_COLS, {PR_ENTRYID}}; // we're interested in the entry-ID of the message store
SPropValue PropExchServer;
PropExchServer.ulPropTag=PT_STRING8;
PropExchServer.dwAlignPad=0;
PropExchServer.Value.lpszA=EX_SERVER; // save the Exchange Server provider info in this property

// build a restriction array with two elements for the query
SRestriction SResArr[2]={RES_EXIST, {0, PR_PROVIDER_DISPLAY, 0},
RES_PROPERTY, {RELOP_EQ, PR_PROVIDER_DISPLAY, (ULONG)&PropExchServer} }; // PR_PROVIDER_DISPLAY prop must exist and ==PropExchServer
SRestriction SRes={RES_AND, 2, (ULONG)&SResArr}; // the final restriction contains the array whose elements are combined with "AND"

if(FAILED(hr=pEECB->GetSession(&pSession, NULL))) goto err; // get a session pointer
if(FAILED(hr=pSession->GetMsgStoresTable(0, &pMapiTable))) goto err; // get the message-store table

if(FAILED(hr=HrQueryAllRows(pMapiTable, (LPSPropTagArray)&PropEntryId, &SRes, NULL, 0L, &lpRows))) goto err; // query entry ID of message-store
if(lpRows->cRows)
{
// if not connected to ExchServer, opening the message store will fail
hr=pSession->OpenMsgStore(0, lpRows->aRow[0].lpProps[ePR_ENTRYID].Value.bin.cb, (ENTRYID*)lpRows->aRow[0].lpProps[ePR_ENTRYID].Value.bin.lpb, NULL, MDB_WRITE|MDB_NO_DIALOG, &pMsgStore);
if(FAILED(hr)) hr=E_FAIL; // make it a nonfatal error (==warning) if the store couldn't be opened
} // if(lpRows->cRows)
else hr=E_FAIL; // no store with PR_PROVIDER_DISPLAY=="Microsoft Exchange Server"

err:
// try to free allocated buffers
if(pMsgStore) pMsgStore->Release();
if(lpRows) FreeProws(lpRows);
if(pMapiTable) pMapiTable->Release();
if(pSession) pSession->Release();

return hr; // return HRESULT (FAILED(MAPI_IsOnline()) means "offline")
}
</findexchange.cpp>

>3. PR_HTML is available because message store of exchange 2003 ?.

I would suggest just trying to write *THAT* property *AND* PR_BODY_HTML,
ignoring possible errors. Just to be sure.

Cheers,
Volker

Volker Bartheld (SPAM only)

unread,
May 7, 2004, 12:39:08 PM5/7/04
to
Hi Dmitry!

On Wed, 5 May 2004 11:40:59 -0700, "Dmitry Streblechenko \(MVP\)"
<dmi...@dimastr.com> wrote :


> else if (byte(Result[i]) > 127) {or (Result[i] < ' ')} then begin
> ch:=Result[i];
> System.Delete(Result, i, 1);
> System.Insert('\'''+IntToHex(byte(ch), 2), Result, i);
> end

> else if {(not bInTag) and} (Result[i] = #13) and (i < Length(Result))
> and (Result[i+1] = #10) then begin
> System.Insert(' ', Result, i);
> end

Honestly, I don't understand that construct. With the first expression,
you catch escape sequences/nonprintables and replace them with their
hexadecimal value (two digits) prepended by a quote (" or '?) and a
backslash.

Given the case that this will also be triggered by the "\n" in a "\r\n"
sequence, the next condition (... else if {(not bInTag) ...)will never
be triggered because the "\n" has already been substituted by "\\\"0A"
(C-Notation) or \"0A (plaintext).

Also the "else if {(not bInTag) and}" looks unfamiliar to me (it has
been a long time since I coded pascal) - what does that mean?

<<If bInTag is false AND Result[i] is "\r" AND Result[i+1] is "\n" AND i
lower than Length(Result)>>?

Thanks for pointing out.

Volker

Dmitry Streblechenko (MVP)

unread,
May 7, 2004, 12:52:18 PM5/7/04
to
No, the
{or (Result[i] < ' ')}
piece (as well as {(not bInTag) and}) is commented out - {..}brackets are
block comments in Delphi.

Dmitry Streblechenko (MVP)
http://www.dimastr.com/
OutlookSpy - Outlook, CDO
and MAPI Developer Tool


"Volker Bartheld (SPAM only)" <dr_ve...@freenet.de> wrote in message

news:2g1sacF...@uni-berlin.de...

Volker Bartheld (SPAM only)

unread,
May 10, 2004, 5:27:01 AM5/10/04
to
Hi Dmitry!

On Fri, 7 May 2004 09:52:18 -0700, "Dmitry Streblechenko \(MVP\)"
<dmi...@dimastr.com> wrote :


>No, the
>{or (Result[i] < ' ')}
>piece (as well as {(not bInTag) and}) is commented out - {..}brackets are
>block comments in Delphi.

Ugh. *blush* OK, now that clarifies it. Thanks for pointing out... I
have to admit, that my Pascal/Delphi knowledge is a bit ... rusty...

Volker

Volker Bartheld (SPAM only)

unread,
May 10, 2004, 6:41:18 AM5/10/04
to
Hi!

OK, C++-Users, here is what I have come up with... I didn't test if it
actually works with WrapCompressedRTFStream() but I'll pass on that info
as soon as it is available.

Cheers,
Volker

#include <windows.h>
#include <string>
#include <sstream>


bool Html2Rtf(const std::string& sHtml, int iCodePage, std::string& sRtf)
{
int i, p, j;
std::string strTag, strExtra;
char ch, sz[5];
const char *pCmp;
bool bInTag=false;

sRtf=sHtml;
if(iCodePage==CP_UTF8) iCodePage=0;
for(i=sRtf.length()-1; i>=0; --i)
{
switch(ch=sRtf[i])
{
case '>':
{
bInTag=true;
break;
}
case '<':
{
bInTag=false;
break;
}
case '\t':
{
sRtf.erase(i, 1);
sRtf.insert(i, "/tab");
break;
}
case '{':
case '}':
case '\\':
{
sRtf.insert(i, "\\");
break;
}
case '\r':
{
if(
i<sRtf.length()-1 &&
sRtf[i+1]=='\n'
)
sRtf.insert(i, " ");
break;
}
}
if(ch&0x80)
{
sprintf(sz, "\\\"%02X", ch);
sRtf.erase(i, 1);
sRtf.insert(i, sz);
}
}

for(i=sRtf.length()-2; i>=0; --i)
{
if(sRtf[i]=='<')
{
p=0;
for(j=i+1; j<sRtf.length(); ++j)
{
if(sRtf[j]=='>')
{
p=j;
break;
}
}
if(!p)
{
sRtf=sHtml;
return false;
}
strTag=std::string(sRtf.begin()+i, sRtf.begin()+p+1);
pCmp=strTag.c_str()+1;
if(
!strnicmp(pCmp, "P>", 2) ||
!strnicmp(pCmp, "P ", 2) ||
!strnicmp(pCmp, "BR>", 3) ||
!strnicmp(pCmp, "BR ", 3)
) strExtra="/par ";
else strExtra="";

if(strTag[1]=='/') strTag="{\\*\\htmltag8 "+strExtra+strTag+"}";
else strTag="{\\*\\htmltag0 "+strExtra+strTag+"}";
sRtf.erase(i, p-i+1);
sRtf.insert(i, strTag.c_str());
}
}

std::ostringstream os;
os
<< "{\\rtf1\\ansi\\ansicpg"
<< iCodePage
<< "\\fromhtml1\\deff0{\\fonttbl\r\n"
<< "{\\f0\\fswiss\\fcharset"
<< iCodePage
<< " Arial;}\r\n"
<< "{\\f1\\fmodern\\fcharset"
<< iCodePage
<< " Courier New;}\r\n"
<< "{\\f2\\fnil\\fcharset"
<< iCodePage
<< " Symbol;}\r\n"
<< "{\\f3\\fmodern\\fcharset"
<< iCodePage
<< " Courier New;}}\r\n"
<< "\\uc1\\pard\\plain\\deftab360 \\f0\\fs24\r\n"
<< sRtf
<< "}";
sRtf=os.str();

return true;
}


On Wed, 5 May 2004 11:40:59 -0700, "Dmitry Streblechenko \(MVP\)"
<dmi...@dimastr.com> wrote :

Volker Bartheld (SPAM only)

unread,
May 11, 2004, 7:13:37 AM5/11/04
to
Hi!

On Mon, 10 May 2004 12:41:18 +0200, "Volker Bartheld (SPAM only)"
<dr_ve...@freenet.de> wrote :


>OK, C++-Users, here is what I have come up with... I didn't test if it
>actually works with WrapCompressedRTFStream() but I'll pass on that info
>as soon as it is available.

Found and fixed some bugs, now it works for me (and OL2000-2003 in both
C/W and IMO mode). If you need info on the MAPI* range of functions
(part of my own helper library), just tell me. I think, they should be
pretty straightforward.

HTH,

Volker


<tools_to_create_a_MS-RTF/HTML_enabled mail>

#include <string>
#include <sstream>

// some property defines that are missing in <*gui.h>
# ifndef PR_ATTACHMENT_HIDDEN
# define PR_ATTACHMENT_HIDDEN PROP_TAG(PT_BOOLEAN,0x7FFE)
# endif

# ifndef PR_ATTACHMENT_FLAGS
# define PR_ATTACHMENT_FLAGS PROP_TAG(PT_LONG,0x7FFD)
# endif // #ifndef PR_ATTACHMENT_FLAGS

# ifndef PR_ATTACH_FLAGS
# define PR_ATTACH_FLAGS PROP_TAG(PT_LONG,0x3714)
# endif // #ifndef PR_ATTACHMENT_FLAGS

# ifndef PR_ATTACH_CONTENT_ID
# define PR_ATTACH_CONTENT_ID PROP_TAG(PT_STRING8,0x3712)
# endif // #ifndef PR_ATTACHMENT_FLAGS

# ifndef PR_HTML
# define PR_HTML PROP_TAG(PT_BINARY,0x1013)
# endif

# ifndef PR_BODY_HTML
# define PR_BODY_HTML PROP_TAG(PT_STRING8,0x1013)
# endif

bool Html2Rtf(const std::string& sHtml, int iCodePage, std::string& sRtf)
{
int i, p, j;
std::string strTag, strExtra;
char ch, sz[5];
const char *pCmp;
bool bInTag=false;

sRtf=sHtml;
if(iCodePage==CP_UTF8) iCodePage=0;
for(i=sRtf.length()-1; i>=0; --i)
{
switch(ch=sRtf[i])
{
case '>':
{
bInTag=true;
break;
}
case '<':
{
bInTag=false;
break;
}
case '\t':
{
sRtf.erase(i, 1);

sRtf.insert(i, "\\tab");

) strExtra="\\par ";

HRESULT CreateHtmlMessage(LPEXCHEXTCALLBACK pEECB)
{
enum ePR_ATTACH_FLAGS {ATT_INVISIBLE_IN_HTML=0x1, ATT_INVISIBLE_IN_RTF=0x2, ATT_MHTML_REF=0x4};

# define CID "some.stupid...@company.com"
HRESULT hr=S_OK;
IAddrBook* pAddrBook=NULL;
IMAPIFolder* pFolder=NULL;
IMessage* pNewMsg=NULL;
IStream* pStreamRtfCompressed=NULL, *pStreamRtf=NULL;
IAttach* pAttach=NULL;
SPropValue props[20];
SPropValue* pSupMask=NULL;

unsigned long ulLen, ul, ulWritten;
const char* pc;
int i=0;
BOOL bUpdated;

std::string sHtml=
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\r\n"
"<HTML><HEAD><TITLE></TITLE>\r\n"
"<META http-equiv=Content-Type content=\"text/html; charset=us-ascii\">\r\n"
"<META content=\"MSHTML 6.00.2800.1106\" name=GENERATOR></HEAD>\r\n"
" <BODY TEXT=\"#000000\" BGCOLOR=\"#FFFFFF\">\n"
" \n"
" <TITLE>Body Title</TITLE>\n"
" \n"
" This is HTML with"
" [<IMG HEIGHT=\"51\" WIDTH=\"182\" ALT=\"Image\" SRC=\"cid:" CID "\">]\n"
" <FONT COLOR=\"#FF6600\">\n"
" <U><B><BIG><BIG><BIG><BIG><BIG><BIG><BIG>"
" an image"
" </BIG></BIG></BIG></BIG></BIG></BIG></BIG></B></U></FONT><BR>\n"
" \n"
" </BODY>\n"
"</HTML>\r\n";
std::string sRtf;
if(!Html2Rtf(sHtml, 0, sRtf))
{
hr=E_FAIL;
goto err;
}

if(FAILED(hr=pEECB->GetSession(NULL, &pAddrBook))) goto err;
// the function below will return an IMAPIFolder* for a given folder EID
if(FAILED(hr=MAPI_GetFolder(pEECB, &pFolder, PR_IPM_INBOX_ENTRYID))) goto err;
if(FAILED(hr=pFolder->CreateMessage(NULL, 0, &pNewMsg))) goto err;

props[i].ulPropTag =PR_SUBJECT;
props[i++].Value.lpszA ="HTML-TEST";
props[i].ulPropTag =PR_BODY;
props[i++].Value.lpszA ="HTML-TEST BODY";
props[i].ulPropTag =PR_BODY_HTML;
props[i++].Value.lpszA =(char*)sHtml.c_str();
props[i].ulPropTag =PR_DELETE_AFTER_SUBMIT;
props[i++].Value.b =TRUE;

if(FAILED(hr=pNewMsg->SetProps(i, props, NULL))) goto err;
if(FAILED(hr=pNewMsg->SaveChanges(KEEP_OPEN_READWRITE))) goto err;
// the function below will open a stream on a property and write data to it
// see the while(ulLen)... section below for a hint on how to do it
if(FAILED(hr=MAPI_WritePropStream(pNewMsg, PR_HTML, sHtml.c_str(), sHtml.length()))) goto err;

if(FAILED(hr=HrGetOneProp(pNewMsg, PR_STORE_SUPPORT_MASK, &pSupMask))) goto err;
if(FAILED(hr=pNewMsg->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream, STGM_WRITE, MAPI_CREATE|MAPI_MODIFY, (IUnknown**)&pStreamRtfCompressed))) goto err;
if(FAILED(hr=WrapCompressedRTFStream(pStreamRtfCompressed, MAPI_MODIFY|(pSupMask->Value.l&STORE_UNCOMPRESSED_RTF), &pStreamRtf))) goto err;
ulLen=sRtf.length();
pc=sRtf.c_str();
while(ulLen)
{
ul=min((unsigned long)1024, ulLen);
if(FAILED(hr=pStreamRtf->Write(pc, ul, &ulWritten))) goto err;
if(ulWritten!=ul)
{
hr=E_FAIL;
goto err;
}
pc+=ul;
ulLen-=ul;
} // while(ulLen)
if(FAILED(pStreamRtf->Commit(STGC_OVERWRITE))) goto err;
pStreamRtf->Release(); pStreamRtf=NULL;
pStreamRtfCompressed->Release(); pStreamRtfCompressed=NULL;
if(FAILED(hr=RTFSync(pNewMsg, RTF_SYNC_RTF_CHANGED, &bUpdated))) goto err;
if(bUpdated && FAILED(hr=pNewMsg->SaveChanges(KEEP_OPEN_READWRITE))) goto err;

// the function below will create a raw attachment, write some basic properties (MIME-type,
// filename, ...) to it and return a pointer of type IAttach*
if(FAILED(hr=MAPI_CreateRawAttach(pNewMsg, "nul.gif", "image/gif", 1, &pAttach))) goto err;
i=0;
props[i].ulPropTag =PR_ATTACH_FLAGS;
props[i++].Value.l =ATT_MHTML_REF;
props[i].ulPropTag =PR_ATTACH_CONTENT_ID;
props[i++].Value.lpszA =CID;
if(FAILED(hr=pAttach->SetProps(i, props, NULL))) goto err;
if(FAILED(hr=pAttach->SaveChanges(KEEP_OPEN_READWRITE))) goto err;
if(FAILED(hr=MAPI_WritePropStream(pAttach, PR_ATTACH_DATA_BIN, szDummyGifFile, sizeof(szDummyGifFile)))) goto err;
if(FAILED(hr=pAttach->SaveChanges(FORCE_SAVE))) goto err;

// the function below will add an One-Off entry to the recipients' collection
if(FAILED(hr=AddOneOff(pAddrBook, pNewMsg, "Someone", "som...@somewhere.com", "SMTP"))) goto err;
if(FAILED(hr=pNewMsg->SaveChanges(FORCE_SAVE))) goto err;

err:
if(pAddrBook) pAddrBook->Release();
if(pFolder) pFolder->Release();
if(pNewMsg) pNewMsg->Release();
if(pAttach) pAttach->Release();
if(pStreamRtf) pStreamRtf->Release();
if(pStreamRtfCompressed) pStreamRtfCompressed->Release();
if(pSupMask) MAPIFreeBuffer(pSupMask);

return hr;
}

</tools_to_create_a_MS-RTF/HTML_enabled mail>

0 new messages