[AccessD] Writing raw RTF document using VBA - Solved

Gustav Brock gustav at cactus.dk
Sat Nov 1 13:27:13 CST 2003


Hi William, Tom

> The client has got new budgets and is again asking for this:

>> My simple need (which I never got solved) was how to assemble an
>> rtf-formatted field from many records (essentially each an rtf
>> document) into one rtf document, stripping headers etc. from each
>> record and adding a header etc. to the final document.

> Does anybody have a clue how to attack it?

> It is for assembling a catalogue where each item in Access has it own
> small description in formatted text (bold, underline, sub/sup script
> only) into one rtf document which are read by the dtp people.

> Basically the task is to strip the header and the tail from the small
> rtf strings, concatenation these, then adding one header and one tail
> to create one finished document.

I did a Google search on "concatenate" and "rtf" and located some new
hits, though mostly from nice people without a proven solution.

However, I noticed you can use the RichTextBox on its own without
having a control on a form:

<code snip>

  Dim rtf As RichTextBox
  Dim strRTF  As String
  
  Set rtf = New RichTextBox
  
  rtf.TextRTF = "Gustav"
  
  Debug.Print rtf.TextRTF
  
  Set rtf = Nothing

</code snip>

This will output:

<rtf>

{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 MS Sans Serif;}}
\viewkind4\uc1\pard\lang1030\f0\fs17 Gustav
\par }

</rtf>

The nice thing with this control is that is has a much wider selection
of text controls than the ocx, for example, superscript and subscript.

By playing around with it I found out that the key to locate the
beginning of the body is the code \pard which seems to always exists;
then locate the following Space. This will skip the leading font
tables and coloursets and bunches of other useless info wrapped in
sets of curly brackets, {...}.

This works fine except if the first char of the body is a non-ascii
char; if so, RTF uses a single byte hex representation of the char and
omits the Space. As an example, this is the string for "€240":

<rtf\

{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 MS Sans Serif;}}
\viewkind4\uc1\pard\lang1030\f0\fs17\'80240
\par }

</rtf>

Note the missing Space.

The escape sequence for such hex bytes is "\'". Thus you'll have
locate first the \pard control code then either a Space or the hex
escape sequence whatever comes first.

Following the body are some closing chars which may be "}" only or
this followed by a CrLf and perhaps a Chr(0). To remove these, one
must travel from the end of the document up to the last "}" and chop
the tail off.

The remaining part is the body which can be concatenated like any
other string with other RTF body strings. When done, apply a minimum
header and a closing "}" and write this to a file and your RTF
document is ready! And, as this is pure code with no ocx or fancy
utils it runs at flashing speed!

First, here's one solution for extracting the RTF body:

<code>

Public Function TrimTextBodyRTF(strTextRTF As String) As String

' Extract RTF body of full RTF formatted string.
'
' 2003-11-01. Gustav Brock. Cactus Data ApS. CPH.

  ' RTF escape char.
  Const cstrRTFEscape       As String = "\"
  ' RTF code identifying RTF control codes leading RTF body.
  Const cstrRTFBodyPointer  As String = cstrRTFEscape + "pard"
  ' Char identifying hex code for non-ascii char.
  Const cstrRTFByteChar     As String = "'"
  ' Header string for hex code for non-ascii char.
  Const cstrRTFByteHeader   As String = cstrRTFEscape + cstrRTFByteChar
  ' Char identifying start of RTF body if first RTF char is an ascii char.
  Const cstrRTFBodyHeader   As String = " "
  ' Char closing RTF body.
  Const cstrRTFBodyEnd      As String = "}"
  
  Dim strRTF                As String
  Dim strEnd                As String
  Dim lngPos                As Long
  Dim lngPosAsc             As Long
  Dim lngPosHex             As Long
  Dim lngEnd                As Long
  Dim lngLen                As Long
  Dim lngChr                As Long
  
  If Len(strTextRTF) > 0 Then
    ' Locate RTF body pointer.
    lngPos = InStr(strTextRTF, cstrRTFBodyPointer)
    If lngPos > 0 Then
      ' Locate start of body if first char is an ascii char.
      lngPosAsc = InStr(lngPos, strTextRTF, cstrRTFBodyHeader)
      ' Check if first char in RTF body is a non-ascii char.
      lngPosHex = InStr(lngPos, strTextRTF, cstrRTFByteHeader) - 1
      lngPos = 0
      If lngPosAsc > 0 Then
        If lngPosHex > 0 Then
          If lngPosHex < lngPosAsc Then
            lngPos = lngPosHex
          Else
            lngPos = lngPosAsc
          End If
        Else
          lngPos = lngPosAsc
        End If
      Else
        lngPos = lngPosHex
      End If
      If lngPos > 0 Then
        strRTF = Mid(strTextRTF, 1 + lngPos)
        ' Locate position of RTF end marker (closing bracket).
        lngChr = Asc(cstrRTFBodyEnd)
        lngLen = Len(strRTF)
        lngEnd = 1
        While Asc(Right(strRTF, lngEnd)) <> lngChr And lngEnd < lngLen
          lngEnd = lngEnd + 1
        Wend
        If lngEnd = lngLen Then
          ' RTF end marker was not found.
          lngPos = 0
        Else
          ' Calculate length of RTF body.
          lngPos = lngLen - lngEnd
        End If
        ' Trim RTF body.
        strRTF = Left(strRTF, lngPos)
      End If
    End If
  End If
  
  TrimTextBodyRTF = strRTF
  
End Function

</code>

Having reached this point it's a simple matter to assemble and write
the full RTF document (watch for line breaks):

<code>

Public Function ConcatenateTextRTF() As Boolean

' Concatenate RTF body strings to one fully formatted RTF file.
'
' Error handling is missing.
'
' 2003-11-01. Gustav Brock. Cactus Data ApS. CPH.

  ' RTF header.
  ' Adjust codepage and language code and font as needed.
  ' Append fontsize to \f0 if needed. "\fs16" for 8 points.
  Const cstrRTFBodyHeader As String = "{\rtf1\ansi\ansicpg1252\deflang1030\deff0{\fonttbl{\f0\fnil\fcharset0 Arial;}}\viewkind4\uc1\pard\f0 "
  ' RTF closing bracket.
  Const cstrRTFBodyEnd    As String = "}"
  ' Filename of finished RTF document.
  Const cstrRTFFile       As String = "c:\winnt\temp\test.rtf"
  
  Dim dbs                 As Database
  Dim rst                 As Recordset
  
  Dim strRTF              As String
  Dim intFile             As Integer
  
  Set dbs = CurrentDb
  Set rst = dbs.OpenRecordset("Tabel1")

  ' Set RTF header.
  strRTF = cstrRTFBodyHeader
  ' Concatenate RTF bodies.
  With rst
    While .EOF = False
      If Not IsNull(!MemoRTF) Then
        strRTF = strRTF + TrimTextBodyRTF(!MemoRTF)
      End If
      .MoveNext
    Wend
    .Close
  End With
  ' Append RTF closing bracket.
  strRTF = strRTF + cstrRTFBodyEnd
  
  ' Write RTF file.
  intFile = FreeFile
  Open cstrRTFFile For Output As #intFile
  Print #intFile, strRTF
  Close #intFile

  Set rst = Nothing
  Set dbs = Nothing
  
  ConcatenateTextRTF = True
  
End Function

</code>

I'm pretty sure this won't be able concatenate any collection of RTF
documents whatever the size. But until further testing has been
carried out, I guess it will be able to concatenate a vast amount of
small RTF docs like those found and produced in an ocx controlled RTF
memobox.

/gustav



More information about the AccessD mailing list