A Sample RTF Reader Implementation

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

 

The Microsoft Word Processing Conversions group uses a table-driven approach to reading RTF. This approach allows the most flexibility in reading RTF, with the corresponding problem that it's difficult to detect incorrect RTF. An RTF reader that is based on this approach is presented below. This reader works exactly as described in the RTF specification and uses the principles of operation described in the RTF specification. This reader is designed to be simple to understand but is not intended to be very efficient. This RTF reader also implements the three design principles listed in the previous section.

The RTF reader consists of four files:

  • Rtfdecl.h, which contains the prototypes for all the functions in the RTF reader
  • Rtftype.h, which contains the types used in the RTF reader
  • Rtfreadr.c, which contains the main program, the main loop of the RTF reader, and the RTF control parser
  • Rtfactn.c, which contains the dispatch routines for the RTF reader

Rtfdecl.h and Rtfreadr.c

Rtfdecl.h is straightforward and requires little explanation.

rtfreadr.c is also reasonably straightforward; the function ecRtfParse separates text from RTF controls and handles text, and the function ecParseRtfKeyword parses an RTF control and also collects any parameter that follows the RTF control.

Rtftype.h

Rtftype.h begins by declaring a sample set of character, paragraph, section, and document properties. These structures are present to demonstrate how the dispatch routines can modify any particular property and are not actually used to format text.

For example, the following enumeration describes which destination text should be routed to:

typedef enum { rdsNorm, rdsSkip } RDS;

Because this is just a sample RTF reader, there are only two destinations; a more complicated reader would add an entry to this enumeration for each destination supported [for example, headers, footnotes, endnotes, comments (annotations), bookmarks, and pictures].

The following enumeration describes the internal state of the RTF parser:

typedef enum { risNorm, risBin, risHex } RIS;

This is entirely separate from the state of the dispatch routines and the destination state; other RTF readers may not necessarily have anything similar to this.

The following structure encapsulates the state that must be saved at a group start and restored at a group end:

typedef struct save
{
struct save *pNext;
CHP chp;
PAP pap;
SEP sep;
DOP dop;
RDS rds;
RIS ris;
} SAVE;

The following enumeration describes a set of classes for RTF controls:

typedef enum {kwdChar, kwdDest, kwdProp, kwdSpec} KWD;

Use kwdChar for controls that represent special characters (such as \-, \{, or \}).

Use kwdDest for controls that introduce RTF destinations.

Use kwdProp for controls that modify some sort of property.

Use kwdSpec for controls that need to run some specialized code.

The following enumeration defines the number of PROP structures (described below) that will be used. There will typically be an iprop for every field in the character, paragraph, section, and document properties.

typedef enum {ipropBold, ipropItalic, ipropUnderline, ipropLeftInd,
ipropRightInd, ipropFirstInd, ipropCols, ipropPgnX, ipropPgnY,
ipropXaPage, ipropYaPage, ipropXaLeft, ipropXaRight,
ipropYaTop, ipropYaBottom, ipropPgnStart, ipropSbk, 
ipropPgnFormat, ipropFacingp, ipropLandscape, ipropJust,
ipropPard, ipropPlain,
ipropMax} IPROP;

The following structure is a very compact way to describe how to locate the address of a particular value in one of the property structures:

typedef enum {actnSpec, actnByte, actnWord} ACTN;
typedef enum {propChp, propPap, propSep, propDop} PROPTYPE;

typedef struct propmod
{
ACTN actn;
PROPTYPE prop;
int offset;
} PROP;

The actn field describes the width of the value being described: if the value is a byte, then actn is actnByte; if the value is a word, then actn is actnWord; if the value is neither a byte nor a word, then you can use actnSpec to indicate that some C code needs to be run to set the value. The prop field indicates which property structure is being described; propChp indicates that the value is located within the CHP structure; propPap indicates that the value is located within the PAP structure, and so on. Finally, the offset field contains the offset of the value from the start of the structure. The offsetof() macro is usually used to initialize this field.

The following structure describes how to parse a particular RTF control:

typedef enum {ipfnBin, ipfnHex, ipfnSkipDest } IPFN;
typedef enum {idestPict, idestSkip } IDEST;

typedef struct symbol
{
char *szKeyword;
int dflt;
bool fPassDflt;
KWD kwd;
int idx;
} SYM;

szKeyword points to the RTF control being described; kwd ** describes the class of the particular RTF control (described above); dflt is the default value for this control, and fPassDflt should be nonzero if the value in dflt should be passed to the dispatch routine. (fPassDflt is only nonzero for control words that normally set a particular value. For example, the various section break controls typically have nonzero fPassDflt controls, but controls that take parameters should not.)

Idx is a generalized index; its use depends on the kwd being used for this control.

  • If kwd is kwdChar, then idx is the character that should be output.
  • If kwd is kwdDest, then idx is the idest for the new destination.
  • If kwd is kwdProp, then idx is the iprop for the appropriate property.
  • If kwd is kwdSpec, then idx is an ipfn for the appropriate function.

With this structure, it is very simple to dispatch an RTF control word. Once the reader isolates the RTF control word and its (possibly associated) value, the reader then searches an array of SYM structures to find the RTF control word. If the control word is not found, the reader ignores it, unless the previous control was \*, in which case the reader must scan past an entire group.

If the control word is found, the reader then uses the kwd value from the SYM structure to determine what to do. This is, in fact, exactly what the function ecTranslateKeyword in the file RTFACTN.C does.

Rtfactn.c

Rtfactn.c contains the tables describing the properties and control words, and the routines to evaluate properties (ecApplyPropChange) and to dispatch control words (ecTranslateKeyword).

The tables are the keys to understanding the RTF dispatch routines. The following are some sample entries from both tables, together with a brief explanation of each entry.

The Property Table. This table must have an entry for every iprop.

  • The property below says that the ipropBold property is a byte parameter bound to chp.fBold.

    actnByte,   propChp,    offsetof(CHP, fBold),       // ipropBold
    
  • This property says that ipropRightInd is a word parameter bound to pap.xaRight.

    actnWord,   propPap,    offsetof(PAP, xaRight),     // ipropRightInd
    
  • This property says that ipropCols is a word parameter bound to sep.cCols.

    actnWord,   propSep,    offsetof(SEP, cCols),       // ipropCols
    
  • This property says that ipropPlain is a special parameter. Instead of directly evaluating it, ecApplyPropChange will run some custom C code to apply a property change.

    actnSpec,   propChp,    0,                          // ipropPlain
    

The Control Word Table

  • This structure says that the control \b sets the ipropBold property. Because fPassDflt is False, the reader only uses the default value if the control does not have a parameter. If no parameter is provided, the reader uses a value of 1.

    "b",        1,      fFalse,     kwdProp,    ipropBold,
    
  • This entry says that the control \sbknone sets the ipropSbk property. Because fPassDflt is True, the reader always uses the default value of sbkNon, even if the control has a parameter.

    "sbknone",  sbkNon, fTrue,      kwdProp,    ipropSbk,
    
  • This entry says that the control \par is equivalent to a 0x0a (linefeed) character.

    "par",      0,      fFalse,     kwdChar,    0x0a,
    
  • This entry says that the control \tab is equivalent to a 0x09 (tab) character.

    "tab",      0,      fFalse,     kwdChar,    0x09,
    
  • This entry says that the control \bin should run some C code. The particular piece of C code can be located by the ipfnBin parameter.

    "bin",      0,      fFalse,     kwdSpec,    ipfnBin,
    
  • This entry says that the control \fonttbl should change to the destination idestSkip.

    "fonttbl",  0,      fFalse,     kwdDest,    idestSkip,