ftp.nice.ch/pub/next/unix/text/rtf2TeX.s.tar.gz#/rtf2TeX/reader.c

This is reader.c in view mode; [Download] [Up]

/*
	Need to document error code meanings.
	Change document to reflect RTFFuncPtr.

	reader.c - RTF file reader.  Distribution 1.06a2.

	ASCII 10 (\n) and 13 (\r) are ignored and silently discarded
	(although the read hook will still get a look at them.)

	"\:" is not a ":", it's a control symbol.  But some versions of
	Word seem to write "\:" for ":".  This reader treats "\:" as a
	plain text ":"

	This reader needs to catch \cf and \cb in the color table
	reader?  (Doesn't yet.)
*/

# include	<stdio.h>
# include	<ctype.h>
# ifdef	VARARGS
# include	<varargs.h>
# endif	/* VARARGS */

# include	"rtf.h"


/*
	Return pointer to new element of type t, or NULL
*/

# define	New(t)	((t *) RTFAlloc ((int) sizeof (t)))


# ifdef	SYSV
# define	index	strchr
# endif

extern char	*index ();
extern char	*strcpy ();
extern char	*malloc ();


static void	_RTFGetToken ();
static int	GetChar ();
static int	HexVal ();
static void	ReadFontTbl ();
static void	ReadColorTbl ();
static void	ReadStyleSheet ();
static void	ReadInfoGroup ();
static void	ReadPictGroup ();
static void	LookupInit ();
static void	Lookup ();
static int	Hash ();
static void	Error ();


/*
	Public variables (listed in rtf.h)
*/

int	rtfClass;
int	rtfMajor;
int	rtfMinor;
int	rtfParam;
char	rtfTextBuf[rtfBufSiz];
int	rtfTextLen;


/*
	Private stuff
*/

static int	pushedChar;	/* pushback char if read too far */

static int	pushedClass;	/* pushed token info for RTFUngetToken() */
static int	pushedMajor;
static int	pushedMinor;
static int	pushedParam;
static char	pushedTextBuf[rtfBufSiz];


static RTFFont	*fontList = (RTFFont *) NULL;	/* these lists MUST be */
static RTFColor	*colorList = (RTFColor *) NULL;	/* initialized to NULL */
static RTFStyle	*styleList = (RTFStyle *) NULL;


static FILE	*rtffp = stdin;


/*
	Initialize the reader.  This may be called multiple times,
	to read multiple files.  The only thing not reset is the input
	stream; that must be done with RTFSetStream().
*/

void RTFInit ()
{
int	i;
RTFColor	*cp;
RTFFont		*fp;
RTFStyle	*sp;
RTFStyleElt	*eltList, *ep;

	/* initialize lookup table */
	LookupInit ();

	for (i = 0; i < rtfMaxClass; i++)
		RTFSetClassCallback (i, (RTFFuncPtr) NULL);
	for (i = 0; i < rtfMaxDestination; i++)
		RTFSetDestinationCallback (i, (RTFFuncPtr) NULL);

	/* install built-in destination readers */
	RTFSetDestinationCallback (rtfFontTbl, ReadFontTbl);
	RTFSetDestinationCallback (rtfColorTbl, ReadColorTbl);
	RTFSetDestinationCallback (rtfStyleSheet, ReadStyleSheet);
	RTFSetDestinationCallback (rtfInfo, ReadInfoGroup);
	RTFSetDestinationCallback (rtfPict, ReadPictGroup);

	RTFSetReadHook ((RTFFuncPtr) NULL);

	/* dump old lists if necessary */

	while (fontList != (RTFFont *) NULL)
	{
		fp = fontList->rtfNextFont;
		RTFFree (fontList->rtfFName);
		RTFFree ((char *) fontList);
		fontList = fp;
	}
	while (colorList != (RTFColor *) NULL)
	{
		cp = colorList->rtfNextColor;
		RTFFree ((char *) colorList);
		colorList = cp;
	}
	while (styleList != (RTFStyle *) NULL)
	{
		sp = styleList->rtfNextStyle;
		eltList = styleList->rtfSSEList;
		while (eltList != (RTFStyleElt *) NULL)
		{
			ep = eltList->rtfNextSE;
			RTFFree (eltList->rtfSEText);
			RTFFree ((char *) eltList);
			eltList = ep;
		}
		RTFFree (styleList->rtfSName);
		RTFFree ((char *) styleList);
		styleList = sp;
	}

	rtfClass = -1;
	pushedClass = -1;
	pushedChar = EOF;
}


/*
	Set the reader's input stream to the given stream.  Can
	be used to redirect to other than the default (stdin).
*/

void RTFSetStream (stream)
FILE	*stream;
{
	rtffp = stream;
}


/* ---------------------------------------------------------------------- */

/*
	Callback table manipulation routines
*/


/*
	Install or return a writer callback for a token class
*/


static RTFFuncPtr	ccb[rtfMaxClass];		/* class callbacks */

void RTFSetClassCallback (class, callback)
int		class;
RTFFuncPtr	callback;
{
	if (class >= 0 && class < rtfMaxClass)
		ccb[class] = callback;
}


RTFFuncPtr RTFGetClassCallback (class)
int	class;
{
	if (class >= 0 && class < rtfMaxClass)
		return (ccb[class]);
	return ((RTFFuncPtr) NULL);
}


/*
	Install or return a writer callback for a destination type
*/

static RTFFuncPtr	dcb[rtfMaxDestination];	/* destination callbacks */

void RTFSetDestinationCallback (dest, callback)
int		dest;
RTFFuncPtr	callback;
{
	if (dest >= 0 && dest < rtfMaxDestination)
		dcb[dest] = callback;
}


RTFFuncPtr RTFGetDestinationCallback (dest)
int	dest;
{
	if (dest >= 0 && dest < rtfMaxDestination)
		return (dcb[dest]);
	return ((RTFFuncPtr) NULL);
}


/* ---------------------------------------------------------------------- */

/*
	Token reading routines
*/


/*
	Read the input stream, invoking the writer's callbacks
	where appropriate.
*/

void RTFRead ()
{
	while (RTFGetToken () != rtfEOF)
		RTFRouteToken ();
}


/*
	Route a token.  If it's a destination for which a reader is
	installed, process the destination internally, otherwise
	pass the token to the writer's class callback.
*/

void RTFRouteToken ()
{
RTFFuncPtr	p;

	if (rtfClass < 0 || rtfClass >= rtfMaxClass)	/* watchdog */
	{
		Error ("Unknown class %d: %s (reader malfunction)",
							rtfClass, rtfTextBuf);
	}
	if (RTFCheckCM (rtfControl, rtfDestination))
	{
		/* invoke destination-specific callback if there is one */
		if ((p = RTFGetDestinationCallback (rtfMinor))
							!= (RTFFuncPtr) NULL)
		{
			(*p) ();
			return;
		}
	}
	/* invoke class callback if there is one */
	if ((p = RTFGetClassCallback (rtfClass)) != (RTFFuncPtr) NULL)
		(*p) ();
}


/*
	Skip to the end of the current group.  When this returns,
	writers that maintain a state stack may want to call their
	state unstacker; global vars will still be set to the group's
	closing brace.
*/

void RTFSkipGroup ()
{
int	level = 1;

	while (RTFGetToken () != rtfEOF)
	{
		if (rtfClass == rtfGroup)
		{
			if (rtfMajor == rtfBeginGroup)
				++level;
			else if (rtfMajor == rtfEndGroup)
			{
				if (--level < 1)
					break;	/* end of initial group */
			}
		}
	}
}


/*
	Read one token.  Call the read hook if there is one.  The
	token class is the return value.  Returns rtfEOF when there
	are no more tokens.
*/

int RTFGetToken ()
{
RTFFuncPtr	p;

	for (;;)
	{
		_RTFGetToken ();
		if ((p = RTFGetReadHook ()) != (RTFFuncPtr) NULL)
			(*p) ();	/* give read hook a look at token */

		/* Silently discard newlines and carriage returns.  */
		if (!(rtfClass == rtfText
			&& (rtfMajor == '\n' || rtfMajor == '\r')))
			break;
	}
	return (rtfClass);
}


/*
	Install or return a token reader hook.
*/

static RTFFuncPtr	readHook;

void RTFSetReadHook (f)
RTFFuncPtr	f;
{
	readHook = f;
}


RTFFuncPtr RTFGetReadHook ()
{
	return (readHook);
}


void RTFUngetToken ()
{
	if (pushedClass >= 0)	/* there's already an ungotten token */
		Error ("cannot unget two tokens");
	if (rtfClass < 0)
		Error ("no token to unget");
	pushedClass = rtfClass;
	pushedMajor = rtfMajor;
	pushedMinor = rtfMinor;
	pushedParam = rtfParam;
	(void) strcpy (pushedTextBuf, rtfTextBuf);
}


int RTFPeekToken ()
{
	_RTFGetToken ();
	RTFUngetToken ();
	return (rtfClass);
}


static void _RTFGetToken ()
{
int	sign;
int	c;

	/* check for pushed token from RTFUngetToken() */

	if (pushedClass >= 0)
	{
		rtfClass = pushedClass;
		rtfMajor = pushedMajor;
		rtfMinor = pushedMinor;
		rtfParam = pushedParam;
		(void) strcpy (rtfTextBuf, pushedTextBuf);
		rtfTextLen = strlen (rtfTextBuf);
		pushedClass = -1;
		return;
	}

	/* initialize token vars */

	rtfClass = rtfUnknown;
	rtfParam = rtfNoParam;
	rtfTextBuf[rtfTextLen = 0] = '\0';

	/* get first character, which may be a pushback from previous token */

	if (pushedChar != EOF)
	{
		c = pushedChar;
		rtfTextBuf[rtfTextLen] = c;
		rtfTextBuf[++rtfTextLen] = '\0';
		pushedChar = EOF;
	}
	else if ((c = GetChar ()) == EOF)
	{
		rtfClass = rtfEOF;
		return;
	}

	if (c == '{')
	{
		rtfClass = rtfGroup;
		rtfMajor = rtfBeginGroup;
		return;
	}
	if (c == '}')
	{
		rtfClass = rtfGroup;
		rtfMajor = rtfEndGroup;
		return;
	}
	if (c != '\\')
	{
		/*
			Two possibilities here:
			1) ASCII 9, effectively like \tab control symbol
			2) literal text char
		*/
		if (c == '\t')			/* ASCII 9 */
		{
			rtfClass = rtfControl;
			rtfMajor = rtfSpecialChar;
			rtfMinor = rtfTab;
		}
		else
		{
			rtfClass = rtfText;
			rtfMajor = c;
		}
		return;
	}
	if ((c = GetChar ()) == EOF)
	{
		/* early eof, whoops (class is rtfUnknown) */
		return;
	}
	if (!isalpha (c))
	{
		/*
			Three possibilities here:
			1) hex encoded text char, e.g., \'d5, \'d3
			2) special escaped text char, e.g., \{, \}
			3) control symbol, e.g., \_, \-, \|, \<10>
		*/
		if (c == '\'')				/* hex char */
		{
		int	c2;

			if ((c = GetChar ()) != EOF && (c2 = GetChar ()) != EOF)
			{
				/* should do isxdigit check! */
				rtfClass = rtfText;
				rtfMajor = HexVal (c) * 16 + HexVal (c2);
				return;
			}
			/* early eof, whoops (class is rtfUnknown) */
			return;
		}

		if (index (":{}\\", c) != (char *) NULL) /* escaped char */
		{
			rtfClass = rtfText;
			rtfMajor = c;
			return;
		}

		/* control symbol */
		Lookup (rtfTextBuf);	/* sets class, major, minor */
		return;
	}
	/* control word */
	while (isalpha (c))
	{
		if ((c = GetChar ()) == EOF)
			break;
	}

	/*
		At this point, the control word is all collected, so the
		major/minor numbers are determined before the parameter
		(if any) is scanned.  There will be one too many characters
		in the buffer, though, so fix up before and restore after
		looking up.
	*/

	if (c != EOF)
		rtfTextBuf[rtfTextLen-1] = '\0';
	Lookup (rtfTextBuf);	/* sets class, major, minor */
	if (c != EOF)
		rtfTextBuf[rtfTextLen-1] = c;

	/*
		Should be looking at first digit of parameter if there
		is one, unless it's negative.  In that case, next char
		is '-', so need to gobble next char, and remember sign.
	*/

	sign = 1;
	if (c == '-')
	{
		sign = -1;
		c = GetChar ();
	}
	if (c != EOF && isdigit (c))
	{
		rtfParam = 0;
		while (isdigit (c))	/* gobble parameter */
		{
			rtfParam = rtfParam * 10 + c - '0';
			if ((c = GetChar ()) == EOF)
				break;
		}
		rtfParam *= sign;
	}
	/*
		If control symbol delimiter was a blank, gobble it.
		Otherwise the character is first char of next token, so
		push it back for next call.  In either case, delete the
		delimiter from the token buffer.
	*/
	if (c != EOF)
	{
		if (c != ' ')
			pushedChar = c;
		rtfTextBuf[--rtfTextLen] = '\0';
	}
	return;
}


/*
	Distributions up through 1.04 assumed high bit could be set in
	RTF file characters.  Beginning with 1.05, that's not true, but
	still check and ignore such characters.  (Cope with things like
	WriteNow on NeXT, which generates bad RTF by writing 8-bit
	characters.)
*/

static int GetChar ()
{
int	c;

	if ((c = getc (rtffp)) != EOF)
	{
		if (c & 0x80)
		{
			fprintf (stderr, "Character found with high bit set");
			fprintf (stderr, " (%#x) -> changed to '?'\n", c);
			c = '?';
		}
		rtfTextBuf[rtfTextLen] = c;
		rtfTextBuf[++rtfTextLen] = '\0';
	}
	return (c);
}


static int HexVal (c)
char	c;
{
	if (isupper (c))
		c = tolower (c);
	if (isdigit (c))
		return (c - '0');	/* '0'..'9' */
	return (c - 'a' + 10);		/* 'a'..'f' */
}


/*
	Synthesize a token by setting the global variables to the
	values supplied.  Typically this is followed with a call
	to RTFRouteToken().

	If param is non-negative, it becomes part of the token text.
*/

void RTFSetToken (class, major, minor, param, text)
int	class, major, minor, param;
char	*text;
{
	rtfClass = class;
	rtfMajor = major;
	rtfMinor = minor;
	rtfParam = param;
	if (param == rtfNoParam)
		(void) strcpy (rtfTextBuf, text);
	else
		sprintf (rtfTextBuf, "%s%d", text, param);
	rtfTextLen = strlen (rtfTextBuf);
}


/* ---------------------------------------------------------------------- */

/*
	Special destination readers.  They gobble the destination so the
	writer doesn't have to deal with them.  That's wrong for any
	translator that wants to process any of these itself.  In that
	case, these readers should be overridden by installing a different
	destination callback.

	NOTE: The last token read by each of these reader will be the
	destination's terminating '}', which will then be the current token.
	That '}' token is passed to RTFRouteToken() - the writer has already
	seen the '{' that began the destination group, and may have pushed a
	state; it also needs to know at the end of the group that a state
	should be popped.

	It's important that rtf.h and the control token lookup table list
	as many symbols as possible, because these readers unfortunately
	make strict assumptions about the input they expect, and a token
	of class rtfUnknown will throw them off easily.
*/


/*
	Read { \fonttbl ... } destination.  Old font tables don't have
	braces around each table entry; try to adjust for that.
*/

static void ReadFontTbl ()
{
RTFFont	*fp;
char	buf[rtfBufSiz], *bp;
int	old = -1;

	for (;;)
	{
		(void) RTFGetToken ();
		if (RTFCheckCM (rtfGroup, rtfEndGroup))
			break;
		if (old < 0)		/* first entry - determine tbl type */
		{
			if (RTFCheckCMM (rtfControl, rtfCharAttr, rtfFontNum))
				old = 1;	/* no brace */
			else if (RTFCheckCM (rtfGroup, rtfBeginGroup))
				old = 0;	/* brace */
			else			/* can't tell! */
				Error ("FTErr - Cannot determine format");
		}
		if (old == 0)		/* need to find "{" here */
		{
			if (!RTFCheckCM (rtfGroup, rtfBeginGroup))
				Error ("FTErr - missing \"{\"");
			(void) RTFGetToken ();	/* yes, skip to next token */
		}
		if ((fp = New (RTFFont)) == (RTFFont *) NULL)
			Error ("FTErr - cannot allocate font entry");
		fp->rtfNextFont = fontList;
		fontList = fp;
		if (!RTFCheckCMM (rtfControl, rtfCharAttr, rtfFontNum))
			Error ("FTErr - missing font number");
		fp->rtfFNum = rtfParam;
		(void) RTFGetToken ();
		if (!RTFCheckCM (rtfControl, rtfFontFamily))
			Error ("FTErr - missing font family");
		fp->rtfFFamily = rtfMinor;
		bp = buf;
		while (RTFGetToken () == rtfText)
		{
			if (rtfMajor == ';')
				break;
			*bp++ = rtfMajor;
		}
		*bp = '\0';
		if (buf[0] == '\0')
			Error ("FTErr - missing font name");
		if ((fp->rtfFName = RTFStrSave (buf)) == (char *) NULL)
			Error ("FTErr - cannot allocate font name");
		if (old == 0)	/* need to see "}" here */
		{
			(void) RTFGetToken ();
			if (!RTFCheckCM (rtfGroup, rtfEndGroup))
				Error ("FTErr - missing \"}\"");
		}
	}
	RTFRouteToken ();	/* feed "}" back to router */
}


/*
	The color table entries have color values of -1 if
	the default color should be used for the entry (only
	a semi-colon is given in the definition, no color values).
	There will be a problem if a partial entry (1 or 2 but
	not 3 color values) is given.  The possibility is ignored
	here.
*/

static void ReadColorTbl ()
{
RTFColor	*cp;
int		cnum = 0;

	for (;;)
	{
		(void) RTFGetToken ();
		if (RTFCheckCM (rtfGroup, rtfEndGroup))
			break;
		if ((cp = New (RTFColor)) == (RTFColor *) NULL)
			Error ("CTErr - cannot allocate color entry");
		cp->rtfCNum = cnum++;
		cp->rtfCRed = cp->rtfCGreen = cp->rtfCBlue = -1;
		cp->rtfNextColor = colorList;
		colorList = cp;
		for (;;)
		{
			if (!RTFCheckCM (rtfControl, rtfColorName))
				break;
			switch (rtfMinor)
			{
			case rtfRed:	cp->rtfCRed = rtfParam; break;
			case rtfGreen:	cp->rtfCGreen = rtfParam; break;
			case rtfBlue:	cp->rtfCBlue = rtfParam; break;
			}
			RTFGetToken ();
		}
		if (!RTFCheckCM (rtfText, (int) ';'))
			Error ("CTErr - malformed entry");
	}
	RTFRouteToken ();	/* feed "}" back to router */
}


/*
	The "Normal" style definition doesn't contain any style number
	(why?), all others do.  Normal style is given style 0.
*/

static void ReadStyleSheet ()
{
RTFStyle	*sp;
RTFStyleElt	*sep, *sepLast;
char		buf[rtfBufSiz], *bp;

	for (;;)
	{
		(void) RTFGetToken ();
		if (RTFCheckCM (rtfGroup, rtfEndGroup))
			break;
		if ((sp = New (RTFStyle)) == (RTFStyle *) NULL)
			Error ("SSErr - cannot allocate stylesheet entry");
		sp->rtfSNum = -1;
		sp->rtfSBasedOn = rtfBasedOnNone;
		sp->rtfSNextPar = -1;
		sp->rtfSSEList = sepLast = (RTFStyleElt *) NULL;
		sp->rtfNextStyle = styleList;
		sp->rtfExpanding = 0;
		styleList = sp;
		if (!RTFCheckCM (rtfGroup, rtfBeginGroup))
			Error ("SSErr - missing \"{\"");
		while (RTFGetToken () == rtfControl)
		{
			if (RTFCheckMM (rtfParAttr, rtfStyleNum))
			{
				sp->rtfSNum = rtfParam;
				continue;
			}
			if (RTFCheckMM (rtfStyleAttr, rtfBasedOn))
			{
				sp->rtfSBasedOn = rtfParam;
				continue;
			}
			if (RTFCheckMM (rtfStyleAttr, rtfNext))
			{
				sp->rtfSNextPar = rtfParam;
				continue;
			}
			if ((sep = New (RTFStyleElt)) == (RTFStyleElt *) NULL)
				Error ("SSErr - cannot allocate style element");
			sep->rtfSEClass = rtfClass;
			sep->rtfSEMajor = rtfMajor;
			sep->rtfSEMinor = rtfMinor;
			sep->rtfSEParam = rtfParam;
			if ((sep->rtfSEText = RTFStrSave (rtfTextBuf))
							== (char *) NULL)
				Error ("SSErr - cannot allocate style element text");
			if (sepLast == (RTFStyleElt *) NULL)
				sp->rtfSSEList = sep;	/* first element */
			else				/* add to end */
				sepLast->rtfNextSE = sep;
			sep->rtfNextSE = (RTFStyleElt *) NULL;
			sepLast = sep;
		}
		if (sp->rtfSNextPar == -1)		/* \snext not given */
			sp->rtfSNextPar = sp->rtfSNum;	/* next is itself */
		if (rtfClass != rtfText)
			Error ("SSErr - missing style name");
		bp = buf;
		while (rtfClass == rtfText)
		{
			if (rtfMajor == ';')
			{
				(void) RTFGetToken ();
				break;
			}
			*bp++ = rtfMajor;
			(void) RTFGetToken ();
		}
		*bp = '\0';
		if (sp->rtfSNum < 0)	/* no style number was specified */
		{			/* (only legal for Normal style) */
			if (strcmp (buf, "Normal") != 0)
				Error ("SSErr - missing style number");
			sp->rtfSNum = 0;
		}
		if ((sp->rtfSName = RTFStrSave (buf)) == (char *) NULL)
			Error ("SSErr - cannot allocate style name");
		if (!RTFCheckCM (rtfGroup, rtfEndGroup))
			Error ("SSErr - missing \"}\"");
	}
	RTFRouteToken ();	/* feed "}" back to router */
}


static void ReadInfoGroup ()
{
	RTFSkipGroup ();
	RTFRouteToken ();	/* feed "}" back to router */
}


static void ReadPictGroup ()
{
	RTFSkipGroup ();
	RTFRouteToken ();	/* feed "}" back to router */
}


/* ---------------------------------------------------------------------- */

/*
	Routines to return pieces of stylesheet, or font or color tables
*/


RTFStyle *RTFGetStyle (num)
int	num;
{
RTFStyle	*s;

	if (num == -1)
		return (styleList);
	for (s = styleList; s != (RTFStyle *) NULL; s = s->rtfNextStyle)
	{
		if (s->rtfSNum == num)
			break;
	}
	return (s);		/* NULL if not found */
}


RTFFont *RTFGetFont (num)
int	num;
{
RTFFont	*f;

	if (num == -1)
		return (fontList);
	for (f = fontList; f != (RTFFont *) NULL; f = f->rtfNextFont)
	{
		if (f->rtfFNum == num)
			break;
	}
	return (f);		/* NULL if not found */
}


RTFColor *RTFGetColor (num)
int	num;
{
RTFColor	*c;

	if (num == -1)
		return (colorList);
	for (c = colorList; c != (RTFColor *) NULL; c = c->rtfNextColor)
	{
		if (c->rtfCNum == num)
			break;
	}
	return (c);		/* NULL if not found */
}


/* ---------------------------------------------------------------------- */


/*
	Expand style n, if there is such a style.
*/

void RTFExpandStyle (n)
int	n;
{
RTFStyle	*s;
RTFStyleElt	*se;

	if (n == -1 || (s = RTFGetStyle (n)) == (RTFStyle *) NULL)
		return;
	if (s->rtfExpanding != 0)
		Error ("Style expansion loop, style %d", n);
	s->rtfExpanding = 1;	/* set expansion flag for loop detection */
	/*
		Expand "based-on" style.  This is done by synthesizing
		the token that the writer needs to see in order to trigger
		another style expansion, and feeding to token back through
		the router so the writer sees it.
	*/
	RTFSetToken (rtfControl, rtfParAttr, rtfStyleNum, s->rtfSBasedOn, "\\s");
	RTFRouteToken ();
	/*
		Now route the tokens unique to this style.  RTFSetToken()
		isn't used because it would add the param value to the end
		of the token text, which already has it in.
	*/
	for (se = s->rtfSSEList; se != (RTFStyleElt *) NULL; se = se->rtfNextSE)
	{
		rtfClass = se->rtfSEClass;
		rtfMajor = se->rtfSEMajor;
		rtfMinor = se->rtfSEMinor;
		rtfParam = se->rtfSEParam;
		(void) strcpy (rtfTextBuf, se->rtfSEText);
		rtfTextLen = strlen (rtfTextBuf);
		RTFRouteToken ();
	}
	s->rtfExpanding = 0;	/* done - clear expansion flag */
}


/* ---------------------------------------------------------------------- */

/*
	Control symbol lookup routines
*/


typedef struct RTFKey	RTFKey;

struct RTFKey
{
	int	rtfKMajor;	/* major number */
	int	rtfKMinor;	/* minor number */
	char	*rtfKStr;	/* symbol name */
	int	rtfKHash;	/* symbol name hash value */
};

/*
	A minor number of -1 means the token has no minor number
	(all valid minor numbers are >= 0).
*/

static RTFKey	rtfKey[] =
{
	rtfSpecialChar,	rtfCurHeadPict,		"chpict",	0,	/* ?? */

	rtfSpecialChar,	rtfCurHeadDate,		"chdate",	0,
	rtfSpecialChar,	rtfCurHeadTime,		"chtime",	0,
	rtfSpecialChar,	rtfCurHeadPage,		"chpgn",	0,
	rtfSpecialChar,	rtfCurFNote,		"chftn",	0,
	rtfSpecialChar,	rtfCurAnnotRef,		"chatn",	0,
	rtfSpecialChar,	rtfFNoteSep,		"chftnsep",	0,
	rtfSpecialChar,	rtfFNoteCont,		"chftnsepc",	0,
	rtfSpecialChar,	rtfFormula,		"|",		0,
	rtfSpecialChar,	rtfNoBrkSpace,		"~",		0,
	rtfSpecialChar,	rtfNoReqHyphen,		"-",		0,
	rtfSpecialChar,	rtfNoBrkHyphen,		"_",		0,
	rtfSpecialChar,	rtfCell,		"cell",		0,
	rtfSpecialChar,	rtfRow,			"row",		0,
	rtfSpecialChar,	rtfPar,			"par",		0,
	rtfSpecialChar,	rtfPar,			"\n",		0,
	rtfSpecialChar,	rtfPar,			"\r",		0,
	rtfSpecialChar,	rtfSect,		"sect",		0,
	rtfSpecialChar,	rtfPage,		"page",		0,
	rtfSpecialChar,	rtfColumn,		"column",	0,
	rtfSpecialChar,	rtfLine,		"line",		0,
	rtfSpecialChar,	rtfTab,			"tab",		0,
	rtfSpecialChar,	rtfOptDest,		"*",		0,
	rtfSpecialChar,	rtfIIntVersion,		"vern",		0,
	rtfSpecialChar,	rtfICreateTime,		"creatim",	0,
	rtfSpecialChar,	rtfIRevisionTime,	"revtim",	0,
	rtfSpecialChar,	rtfIPrintTime,		"printim",	0,
	rtfSpecialChar,	rtfIBackupTime,		"buptim",	0,
	rtfSpecialChar,	rtfIEditTime,		"edmins",	0,
	rtfSpecialChar,	rtfIYear,		"yr",		0,
	rtfSpecialChar,	rtfIMonth,		"mo",		0,
	rtfSpecialChar,	rtfIDay,		"dy",		0,
	rtfSpecialChar,	rtfIHour,		"hr",		0,
	rtfSpecialChar,	rtfIMinute,		"min",		0,
	rtfSpecialChar,	rtfINPages,		"nofpages",	0,
	rtfSpecialChar,	rtfINWords,		"nofwords",	0,
	rtfSpecialChar,	rtfINChars,		"nofchars",	0,
	rtfSpecialChar,	rtfIIntID,		"id",		0,

	rtfCharAttr,	rtfPlain,		"plain",	0,
	rtfCharAttr,	rtfBold,		"b",		0,
	rtfCharAttr,	rtfItalic,		"i",		0,
	rtfCharAttr,	rtfStrikeThru,		"strike",	0,
	rtfCharAttr,	rtfOutline,		"outl",		0,
	rtfCharAttr,	rtfShadow,		"shad",		0,
	rtfCharAttr,	rtfSmallCaps,		"scaps",	0,
	rtfCharAttr,	rtfAllCaps,		"caps",		0,
	rtfCharAttr,	rtfInvisible,		"v",		0,
	rtfCharAttr,	rtfFontNum,		"f",		0,
	rtfCharAttr,	rtfFontSize,		"fs",		0,
	rtfCharAttr,	rtfExpand,		"expnd",	0,
	rtfCharAttr,	rtfUnderline,		"ul",		0,
	rtfCharAttr,	rtfWUnderline,		"ulw",		0,
	rtfCharAttr,	rtfDUnderline,		"uld",		0,
	rtfCharAttr,	rtfDbUnderline,		"uldb",		0,
	rtfCharAttr,	rtfNoUnderline,		"ulnone",	0,
	rtfCharAttr,	rtfSuperScript,		"up",		0,
	rtfCharAttr,	rtfSubScript,		"dn",		0,
	rtfCharAttr,	rtfRevised,		"revised",	0,
	rtfCharAttr,	rtfForeColor,		"cf",		0,
	rtfCharAttr,	rtfBackColor,		"cb",		0,
	rtfCharAttr,	rtfGray,		"gray",		0,

	rtfParAttr,	rtfParDef,		"pard",		0,
	rtfParAttr,	rtfStyleNum,		"s",		0,
	rtfParAttr,	rtfQuadLeft,		"ql",		0,
	rtfParAttr,	rtfQuadRight,		"qr",		0,
	rtfParAttr,	rtfQuadJust,		"qj",		0,
	rtfParAttr,	rtfQuadCenter,		"qc",		0,
	rtfParAttr,	rtfFirstIndent,		"fi",		0,
	rtfParAttr,	rtfLeftIndent,		"li",		0,
	rtfParAttr,	rtfRightIndent,		"ri",		0,
	rtfParAttr,	rtfSpaceBefore,		"sb",		0,
	rtfParAttr,	rtfSpaceAfter,		"sa",		0,
	rtfParAttr,	rtfSpaceBetween,	"sl",		0,
	rtfParAttr,	rtfInTable,		"intbl",	0,
	rtfParAttr,	rtfKeep,		"keep",		0,
	rtfParAttr,	rtfKeepNext,		"keepn",	0,
	rtfParAttr,	rtfSideBySide,		"sbys",		0,
	rtfParAttr,	rtfPBBefore,		"pagebb",	0,
	rtfParAttr,	rtfNoLineNum,		"noline",	0,
	rtfParAttr,	rtfTabPos,		"tx",		0,
	rtfParAttr,	rtfTabRight,		"tqr",		0,
	rtfParAttr,	rtfTabCenter,		"tqc",		0,
	rtfParAttr,	rtfTabDecimal,		"tqdec",	0,
	rtfParAttr,	rtfTabBar,		"tb",		0,
	rtfParAttr,	rtfBorderTop,		"brdrt",	0,
	rtfParAttr,	rtfBorderBottom,	"brdrb",	0,
	rtfParAttr,	rtfBorderLeft,		"brdrl",	0,
	rtfParAttr,	rtfBorderRight,		"brdrr",	0,
	rtfParAttr,	rtfBorderBar,		"bar",		0,
	rtfParAttr,	rtfBorderBox,		"box",		0,
	rtfParAttr,	rtfBorderBetween,	"brdrbtw",	0,
	rtfParAttr,	rtfBorderSingle,	"brdrs",	0,
	rtfParAttr,	rtfBorderThick,		"brdrth",	0,
	rtfParAttr,	rtfBorderShadow,	"brdrsh",	0,
	rtfParAttr,	rtfBorderDouble,	"brdrdb",	0,
	rtfParAttr,	rtfBorderDot,		"brdrdot",	0,
	rtfParAttr,	rtfBorderHair,		"brdrhair",	0,
	rtfParAttr,	rtfLeaderDot,		"tldot",	0,
	rtfParAttr,	rtfLeaderHyphen,	"tlhyph",	0,
	rtfParAttr,	rtfLeaderUnder,		"tlul",		0,
	rtfParAttr,	rtfLeaderThick,		"tlth",		0,
	rtfParAttr,	rtfBorderSpace,		"brsp",		0,

	rtfSectAttr,	rtfSectDef,		"sectd",	0,
	/*rtfSectAttr,	rtfNoBreak,		"nobreak",	0,
	rtfSectAttr,	rtfColBreak,		"colbreak",	0,
	rtfSectAttr,	rtfPageBreak,		"pagebreak",	0,
	rtfSectAttr,	rtfEvenBreak,		"evenbreak",	0,
	rtfSectAttr,	rtfOddBreak,		"oddbreak",	0,*/
	rtfSectAttr,	rtfNoBreak,		"sbknone",	0,
	rtfSectAttr,	rtfColBreak,		"sbkcol",	0,
	rtfSectAttr,	rtfPageBreak,		"sbkpage",	0,
	rtfSectAttr,	rtfEvenBreak,		"sbkeven",	0,
	rtfSectAttr,	rtfOddBreak,		"sbkodd",	0,
	rtfSectAttr,	rtfPageCont,		"pgncont",	0,
	rtfSectAttr,	rtfPageStarts,		"pgnstarts",	0,
	rtfSectAttr,	rtfPageRestart,		"pgnrestart",	0,
	rtfSectAttr,	rtfPageDecimal,		"pgndec",	0,
	rtfSectAttr,	rtfPageURoman,		"pgnucrm",	0,
	rtfSectAttr,	rtfPageLRoman,		"pgnlcrm",	0,
	rtfSectAttr,	rtfPageULetter,		"pgnucltr",	0,
	rtfSectAttr,	rtfPageLLetter,		"pgnlcltr",	0,
	rtfSectAttr,	rtfPageNumLeft,		"pgnx",		0,
	rtfSectAttr,	rtfPageNumTop,		"pgny",		0,
	rtfSectAttr,	rtfHeaderY,		"headery",	0,
	rtfSectAttr,	rtfFooterY,		"footery",	0,
	rtfSectAttr,	rtfLineModulus,		"linemod",	0,
	rtfSectAttr,	rtfLineDist,		"linex",	0,
	rtfSectAttr,	rtfLineStarts,		"linestarts",	0,
	rtfSectAttr,	rtfLineRestart,		"linerestart",	0,
	rtfSectAttr,	rtfLineRestartPg,	"lineppage",	0,
	rtfSectAttr,	rtfLineCont,		"linecont",	0,
	rtfSectAttr,	rtfTopVAlign,		"vertalt",	0,
	rtfSectAttr,	rtfBottomVAlign,	"vertal",	0,
	rtfSectAttr,	rtfCenterVAlign,	"vertalc",	0,
	rtfSectAttr,	rtfJustVAlign,		"vertalj",	0,
	rtfSectAttr,	rtfColumns,		"cols",		0,
	rtfSectAttr,	rtfColumnSpace,		"colsx",	0,
	rtfSectAttr,	rtfColumnLine,		"linebetcol",	0,
	rtfSectAttr,	rtfENoteHere,		"endnhere",	0,
	rtfSectAttr,	rtfTitleSpecial,	"titlepg",	0,

	rtfDocAttr,	rtfPaperWidth,		"paperw",	0,
	rtfDocAttr,	rtfPaperHeight,		"paperh",	0,
	rtfDocAttr,	rtfLeftMargin,		"margl",	0,
	rtfDocAttr,	rtfRightMargin,		"margr",	0,
	rtfDocAttr,	rtfTopMargin,		"margt",	0,
	rtfDocAttr,	rtfBottomMargin,	"margb",	0,
	rtfDocAttr,	rtfFacingPage,		"facingp",	0,
	rtfDocAttr,	rtfGutterWid,		"gutter",	0,
	rtfDocAttr,	rtfDefTab,		"deftab",	0,
	rtfDocAttr,	rtfWidowCtrl,		"widowctrl",	0,
	rtfDocAttr,	rtfHyphHotZone,		"hyphhotz",	0,
	rtfDocAttr,	rtfFNoteEndSect,	"endnotes",	0,
	rtfDocAttr,	rtfFNoteEndDoc,		"enddoc",	0,
	rtfDocAttr,	rtfFNoteBottom,		"ftnbj",	0,
	rtfDocAttr,	rtfFNoteText,		"ftntj",	0,
	rtfDocAttr,	rtfFNoteStart,		"ftnstart",	0,
	rtfDocAttr,	rtfFNoteRestart,	"ftnrestart",	0,
	rtfDocAttr,	rtfPageStart,		"pgnstart",	0,
	rtfDocAttr,	rtfLineStart,		"linestart",	0,
	rtfDocAttr,	rtfLandscape,		"landscape",	0,
	rtfDocAttr,	rtfFracWidth,		"fracwidth",	0,
	rtfDocAttr,	rtfNextFile,		"nextfile",	0,
	rtfDocAttr,	rtfTemplate,		"template",	0,
	rtfDocAttr,	rtfMakeBackup,		"makeback",	0,
	rtfDocAttr,	rtfRTFDefault,		"defformat",	0,
	rtfDocAttr,	rtfRevisions,		"revisions",	0,
	rtfDocAttr,	rtfMirrorMargin,	"margmirror",	0,
	rtfDocAttr,	rtfRevDisplay,		"revprop",	0,
	rtfDocAttr,	rtfRevBar,		"revbar",	0,

	rtfStyleAttr,	rtfBasedOn,		"sbasedon",	0,
	rtfStyleAttr,	rtfNext,		"snext",	0,

	rtfPictAttr,	rtfMacQD,		"macpict",	0,
	rtfPictAttr,	rtfWinMetafile,		"wmetafile",	0,
	rtfPictAttr,	rtfWinBitmap,		"wbitmap",	0,
	rtfPictAttr,	rtfPicWid,		"picw",		0,
	rtfPictAttr,	rtfPicHt,		"pich",		0,
	rtfPictAttr,	rtfPicGoalWid,		"picwgoal",	0,
	rtfPictAttr,	rtfPicGoalWid,		"picwGoal",	0,
	rtfPictAttr,	rtfPicGoalHt,		"pichgoal",	0,
	rtfPictAttr,	rtfPicGoalHt,		"pichGoal",	0,
	rtfPictAttr,	rtfPicScaleX,		"picscalex",	0,
	rtfPictAttr,	rtfPicScaleY,		"picscaley",	0,
	rtfPictAttr,	rtfPicScaled,		"picscaled",	0,
	rtfPictAttr,	rtfPicCropTop,		"piccropt",	0,
	rtfPictAttr,	rtfPicCropBottom,	"piccropb",	0,
	rtfPictAttr,	rtfPicCropLeft,		"piccropl",	0,
	rtfPictAttr,	rtfPicCropRight,	"piccropr",	0,
	rtfPictAttr,	rtfPixelBits,		"wbmbitspixel",	0,
	rtfPictAttr,	rtfBitmapPlanes,	"wbmplanes",	0,
	rtfPictAttr,	rtfBitmapWid,		"wbmwidthbytes", 0,
	rtfPictAttr,	rtfPicBinary,		"bin",		0,

	rtfNeXTGrAttr,	rtfNeXTGWidth,		"width",	0,
	rtfNeXTGrAttr,	rtfNeXTGHeight,		"height",	0,

	rtfDestination,	rtfPict,		"pict",		0,
	rtfDestination,	rtfNeXTGraphic,		"NeXTGraphic",	0,
	rtfDestination,	rtfFootnote,		"footnote",	0,
	rtfDestination,	rtfHeader,		"header",	0,
	rtfDestination,	rtfHeaderLeft,		"headerl",	0,
	rtfDestination,	rtfHeaderRight,		"headerr",	0,
	rtfDestination,	rtfHeaderFirst,		"headerf",	0,
	rtfDestination,	rtfFooter,		"footer",	0,
	rtfDestination,	rtfFooterLeft,		"footerl",	0,
	rtfDestination,	rtfFooterRight,		"footerr",	0,
	rtfDestination,	rtfFooterFirst,		"footerf",	0,
	rtfDestination,	rtfFNSep,		"ftnsep",	0,
	rtfDestination,	rtfFNContSep,		"ftnsepc",	0,
	rtfDestination,	rtfFNContNotice,	"ftncn",	0,
	rtfDestination,	rtfInfo,		"info",		0,
	rtfDestination,	rtfStyleSheet,		"stylesheet",	0,
	rtfDestination,	rtfFontTbl,		"fonttbl",	0,
	rtfDestination,	rtfColorTbl,		"colortbl",	0,
	rtfDestination,	rtfAnnotation,		"annotation",	0,
	rtfDestination,	rtfAnnotID,		"atnid",	0,
	rtfDestination,	rtfField,		"field",	0,
	rtfDestination,	rtfFieldInst,		"fldinst",	0,
	rtfDestination,	rtfFieldResult,		"fldrslt",	0,
	rtfDestination,	rtfIndex,		"xe",		0,
	rtfDestination,	rtfIndexBold,		"bxe",		0,
	rtfDestination,	rtfIndexItalic,		"ixe",		0,
	rtfDestination,	rtfIndexText,		"txe",		0,
	rtfDestination,	rtfIndexRange,		"rxe",		0,
	rtfDestination,	rtfTOC,			"tc",		0,
	rtfDestination,	rtfBookmarkStart,	"bkmkstart",	0,
	rtfDestination,	rtfBookmarkEnd,		"bkmkend",	0,
	rtfDestination,	rtfITitle,		"title",	0,
	rtfDestination,	rtfISubject,		"subject",	0,
	rtfDestination,	rtfIAuthor,		"author",	0,
	rtfDestination,	rtfIOperator,		"operator",	0,
	rtfDestination,	rtfIKeywords,		"keywords",	0,
	rtfDestination,	rtfIComment,		"comment",	0,
	rtfDestination,	rtfIVersion,		"version",	0,
	rtfDestination,	rtfIDoccomm,		"doccomm",	0,

	rtfTOCAttr,	rtfTOCType,		"tcf",		0,
	rtfTOCAttr,	rtfTOCLevel,		"tcl",		0,

	rtfFontFamily,	rtfFFNil,		"fnil",		0,
	rtfFontFamily,	rtfFFRoman,		"froman",	0,
	rtfFontFamily,	rtfFFSwiss,		"fswiss",	0,
	rtfFontFamily,	rtfFFModern,		"fmodern",	0,
	rtfFontFamily,	rtfFFScript,		"fscript",	0,
	rtfFontFamily,	rtfFFDecor,		"fdecor",	0,
	rtfFontFamily,	rtfFFTech,		"ftech",	0,

	rtfColorName,	rtfRed,			"red",		0,
	rtfColorName,	rtfGreen,		"green",	0,
	rtfColorName,	rtfBlue,		"blue",		0,

	rtfCharSet,	rtfMacCharSet,		"mac",		0,
	rtfCharSet,	rtfAnsiCharSet,		"ansi",		0,
	rtfCharSet,	rtfPcCharSet,		"pc",		0,
	rtfCharSet,	rtfPcaCharSet,		"pca",		0,

	rtfTblAttr,	rtfCellBordBottom,	"clbrdrb",	0,
	rtfTblAttr,	rtfCellBordTop,		"clbrdrt",	0,
	rtfTblAttr,	rtfCellBordLeft,	"clbrdrl",	0,
	rtfTblAttr,	rtfCellBordRight,	"clbrdrr",	0,
	rtfTblAttr,	rtfRowDef,		"trowd",	0,
	rtfTblAttr,	rtfRowLeft,		"trql",		0,
	rtfTblAttr,	rtfRowRight,		"trqr",		0,
	rtfTblAttr,	rtfRowCenter,		"trqc",		0,
	rtfTblAttr,	rtfRowGapH,		"trgaph",	0,
	rtfTblAttr,	rtfRowHt,		"trrh",		0,
	rtfTblAttr,	rtfRowLeftEdge,		"trleft",	0,
	rtfTblAttr,	rtfCellPos,		"cellx",	0,
	rtfTblAttr,	rtfMergeRngFirst,	"clmgf",	0,
	rtfTblAttr,	rtfMergePrevious,	"clmrg",	0,

	rtfFieldAttr,	rtfFieldDirty,		"flddirty",	0,
	rtfFieldAttr,	rtfFieldEdited,		"fldedit",	0,
	rtfFieldAttr,	rtfFieldLocked,		"fldlock",	0,
	rtfFieldAttr,	rtfFieldPrivate,	"fldpriv",	0,

	rtfPosAttr,	rtfPosX,		"posx",		0,
	rtfPosAttr,	rtfPosXCenter,		"posxc",	0,
	rtfPosAttr,	rtfPosXInside,		"posxi",	0,
	rtfPosAttr,	rtfPosXLeft,		"posxl",	0,
	rtfPosAttr,	rtfPosXOutSide,		"posxo",	0,
	rtfPosAttr,	rtfPosXRight,		"posxr",	0,
	rtfPosAttr,	rtfPosY,		"posy",		0,
	rtfPosAttr,	rtfPosYInline,		"posyil",	0,
	rtfPosAttr,	rtfPosYTop,		"posyt",	0,
	rtfPosAttr,	rtfPosYCenter,		"posyc",	0,
	rtfPosAttr,	rtfPosYBottom,		"posyb",	0,
	rtfPosAttr,	rtfAbsWid,		"absw",		0,
	rtfPosAttr,	rtfTextDist,		"dxfrtext",	0,
	rtfPosAttr,	rtfRPosMargV,		"pvmrg",	0,
	rtfPosAttr,	rtfRPosPageV,		"pvpg",		0,
	rtfPosAttr,	rtfRPosMargH,		"phmrg",	0,
	rtfPosAttr,	rtfRPosPageH,		"phpg",		0,
	rtfPosAttr,	rtfRPosColH,		"phcol",	0,

	rtfVersion,	-1,			"rtf",		0,
	rtfDefFont,	-1,			"deff",		0,

	0,		-1,			(char *) NULL,	0
};


/*
	Initialize lookup table hash values.  Only need to do this the
	first time it's called.
*/

static void LookupInit ()
{
static int	inited = 0;
RTFKey	*rp;

	if (inited == 0)
	{
		for (rp = rtfKey; rp->rtfKStr != (char *) NULL; rp++)
			rp->rtfKHash = Hash (rp->rtfKStr);
		++inited;
	}
}


/*
	Determine major and minor number of control token.  If it's
	not found, the class turns into rtfUnknown.
*/

static void Lookup (s)
char	*s;
{
RTFKey	*rp;
int	hash;

	++s;			/* skip over the leading \ character */
	hash = Hash (s);
	for (rp = rtfKey; rp->rtfKStr != (char *) NULL; rp++)
	{
		if (hash == rp->rtfKHash && strcmp (s, rp->rtfKStr) == 0)
		{
			rtfClass = rtfControl;
			rtfMajor = rp->rtfKMajor;
			rtfMinor = rp->rtfKMinor;
			return;
		}
	}
	rtfClass = rtfUnknown;
}


/*
	Compute hash value of symbol
*/

static int Hash (s)
char	*s;
{
char	c;
int	val = 0;

	while ((c = *s++) != '\0')
		val += (int) c;
	return (val);
}


/*
	Print helpful error message and give up
*/

# ifdef	VARARGS

/*
	This version is for systems that have varargs.
*/

static void Error (va_alist)
va_dcl
{
va_list	args;
char	*fmt;

	va_start (args);
	fmt = va_arg (args, char *);
	vfprintf (stderr, fmt, args);
	va_end (args);
	fprintf (stderr, "\nLast token read was \"%s\"\n", rtfTextBuf);
	exit (1);
}

# else	/* !VARARGS */

/*
	This version is for systems that don't have varargs.
*/

static void Error (fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
char	*fmt;
char	*a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9;
{
	fprintf (stderr, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
	fprintf (stderr, "\nLast token read was \"%s\"\n", rtfTextBuf);
	exit (1);
}

# endif	/* VARARGS */


/* ---------------------------------------------------------------------- */

/*
	Memory allocation routines
*/


/*
	Return pointer to block of size bytes, or NULL if there's
	not enough memory available.
*/

char *RTFAlloc (size)
int	size;
{
	return ((char *) malloc (size));
}


/*
	Saves a string on the heap and returns a pointer to it.
*/


char *RTFStrSave (s)
char	*s;
{
char	*p;

	if ((p = RTFAlloc (strlen (s) + 1)) == (char *) NULL)
		return ((char *) NULL);
	return (strcpy (p, s));
}


void RTFFree (p)
char	*p;
{
	if (p != (char *) NULL)
		free (p);
}


/* ---------------------------------------------------------------------- */


/*
	Token comparison routines
*/

int RTFCheckCM (class, major)
int	class, major;
{
	return (rtfClass == class && rtfMajor == major);
}


int RTFCheckCMM (class, major, minor)
int	class, major, minor;
{
	return (rtfClass == class && rtfMajor == major && rtfMinor == minor);
}


int RTFCheckMM (major, minor)
int	major, minor;
{
	return (rtfMajor == major && rtfMinor == minor);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.