ftp.nice.ch/pub/next/developer/resources/classes/misckit/MiscKit.1.10.0.s.gnutar.gz#/MiscKit/Temp/HTMLText/Text.HTMLExtensions.m

This is Text.HTMLExtensions.m in view mode; [Download] [Up]

// Tue Oct 15 06:52:10 MET 1996

// toDo: · restore for form-objects after loadFromStream (esp. write formSession for every radioCell)
//		 · print- distinction for all the TFCells using e.g. the dingbats symbols
//		 · textarea
//		 · check-button
//		 · background-support: cell has to overrride DrawALine-func
//		 · replaceSelWithHTML:(char*)
//		 · </body> should finish all open attributes like bold et al.
//		 · matrices should draw in NXImages, not in the view-hierarchy (printing problems)
//		 · split matrices into individual lines (every line is a separate cell-> pagination of large tables)
//		 · whitespace problems
//		 · intra-word-bolding impossible w/out inserting spaces (solution: delete the added space)
//		 · problems with /daniel/tmp/html/msql/lite.htm

//		

#import "Text.HTMLExtensions.h"
#import "MiscString.DExtensions.h"

// hidden NeXT-utility class uncovered (MiscClass-decoded)
@interface NXGraphicCell : Cell
{
}

+ initialize;

- replaceGraphic: arg;
- initFromImage: theImage name: (const char *) theName;

- readRichText:  (NXStream *) theStrean forView: theView;
- writeRichText: (NXStream *) theStrean forView: theView;
- free;
- calcCellSize: (NXSize *) theSize;
- drawSelf: (NXRect *) aRect inView: theView;
- image;
- setImageNamed: (const char *) theName forView: theView;
-(BOOL) trackMouse: (NXEvent *) theEvnt inRect: (const NXRect *) drawRect ofView: theView;
- highlight: (NXRect *) aRect inView: theView lit: (BOOL) flag;

@end



// recognized HTML-directives¼
// Warning: every END-token must exactly be below its start token!
typedef enum
{	HTML_HTML,
	HTML_ENDHTML,
	HTML_TITLE,
	HTML_ENDTITLE,
	HTML_HEAD,
	HTML_ENDHEAD,
	HTML_BODY,
	HTML_ENDBODY,
	HTML_H1,
	HTML_ENDH1,
	HTML_H2,
	HTML_ENDH2,
	HTML_H3,
	HTML_ENDH3,
	HTML_H4,
	HTML_ENDH4,
	HTML_H5,
	HTML_ENDH5,
	HTML_H6,
	HTML_ENDH6,
	HTML_HR,
	HTML_P,
	HTML_ENDP,
	HTML_SUB,
	HTML_ENDSUB,
	HTML_STRONG,
	HTML_ENDSTRONG,
	HTML_DL,	// definitionlist
	HTML_ENDDL,
	HTML_DT,	// word to be defined
	HTML_DD,	// definition
	HTML_UL,	// unordered list
	HTML_ENDUL,
	HTML_LI,	// listitem
	HTML_ENDLI,
	HTML_OL,	// ordered list
	HTML_ENDOL,
	HTML_SAMP,	// contrast font
	HTML_ENDSAMP,	
	HTML_FORMSESSION,
	HTML_ENDFORMSESSION,
	HTML_FORMINPUT,
	HTML_IMG,
	HTML_EM,
	HTML_ENDEM,
	HTML_HREF,
	HTML_ENDHREF,
	HTML_NAME,
	HTML_SELECT,
	HTML_ENDSELECT,
	HTML_OPTION,
	HTML_ENDOPTION,
	HTML_CENTER,
	HTML_ENDCENTER,
	HTML_TABLE,
	HTML_ENDTABLE,
	HTML_TR,		// new tablerow
	HTML_ENDTR,		// end of new tablerow
	HTML_TH,		// tableheader
	HTML_TD,		// new tablecell
	HTML_ENDTD,		// end of new tablecell
	HTML_PRE,		// preformatted text
	HTML_ENDPRE,		// preformatted text
	HTML_BLOCKQUOTE,
	HTML_ENDBLOCKQUOTE,

	HTML_endOfHTMLList
} HTML_t;



@implementation Text(HTMLExtensions)

-(int) charAt:(int) pos
{	char text[2];
	if([self getSubstring:text start:pos length:1] != 1) return 0;
	return text[0];
}

#define MAX_TABS 50
- removeAllTabs
{/*	int i;
	NXTabStop tabs[MAX_TABS];
	[self getTabs:&tabs num:MAX_TABS];
	for(i=0; tabs[i].kind>= 0;i++)
	{	[self setSelProp:NX_REMOVETAB to:tabs[i].x];
	}
*/	return self;
}

#define LEFT_MARGIN 1

-(void) normalizeRulerAt:(int) pos
{	[self setSel:pos:pos+1];
	[self setSelProp:NX_FIRSTINDENT to:LEFT_MARGIN];
	[self setSelProp:NX_INDENT		to:LEFT_MARGIN];
	//[self pasteRuler:self];
}

// some lexical analysis¼
-(HTML_t) HTMLTokenForString:(MiscString *) str
{	if([str charAt:0] == '!')			return HTML_endOfHTMLList;	// sort of comment
		 if([str casestreq:"HTML"])		return HTML_HTML;
	else if([str casestreq:"/HTML"])	return HTML_ENDHTML;
	else if([str casestreq:"TITLE"])	return HTML_TITLE;
	else if([str casestreq:"/TITLE"])	return HTML_ENDTITLE;
	else if([str casestreq:"HEAD"])		return HTML_HEAD;
	else if([str casestreq:"/HEAD"])	return HTML_ENDHEAD;
	else if([str casestreq:"/BODY"])	return HTML_ENDBODY;
	else if([str strstr:"BODY" caseSensitive:NO])		return HTML_BODY;	// below!!!
	else if([str casestreq:"H1"])		return HTML_H1;
	else if([str casestreq:"/H1"])		return HTML_ENDH1;
	else if([str casestreq:"H2"])		return HTML_H2;
	else if([str casestreq:"/H2"])		return HTML_ENDH2;
	else if([str casestreq:"H3"])		return HTML_H2;
	else if([str casestreq:"/H3"])		return HTML_ENDH2;
	else if([str casestreq:"H4"])		return HTML_H4;
	else if([str casestreq:"/H4"])		return HTML_ENDH4;
	else if([str casestreq:"H5"])		return HTML_H4;
	else if([str casestreq:"/H5"])		return HTML_ENDH4;
	else if([str casestreq:"H6"])		return HTML_H6;
	else if([str casestreq:"/H6"])		return HTML_ENDH6;
	else if([str casestreq:"HR"])		return HTML_HR;
	else if([str casestreq:"P"])		return HTML_P;
	else if([str casestreq:"/P"])		return HTML_ENDP;
	else if([str casestreq:"STRONG"])	return HTML_STRONG;
	else if([str casestreq:"/STRONG"])	return HTML_ENDSTRONG;
	else if([str casestreq:"SUB"])		return HTML_SUB;
	else if([str casestreq:"/SUB"])		return HTML_ENDSUB;
	else if([str casestreq:"SAMP"])		return HTML_SAMP;
	else if([str casestreq:"/SAMP"])	return HTML_ENDSAMP;
	else if([str casestreq:"CODE"])		return HTML_SAMP;
	else if([str casestreq:"/CODE"])	return HTML_ENDSAMP;
	else if([str casestreq:"B"])		return HTML_STRONG;
	else if([str casestreq:"/B"])		return HTML_ENDSTRONG;
	else if([str casestreq:"EM"])		return HTML_EM;
	else if([str casestreq:"/EM"])		return HTML_ENDEM;
	else if([str casestreq:"I"])		return HTML_EM;
	else if([str casestreq:"/I"])		return HTML_ENDEM;
	else if([str casestreq:"CITE"])		return HTML_EM;
	else if([str casestreq:"/CITE"])	return HTML_ENDEM;
	else if([str casestreq:"VAR"])		return HTML_EM;
	else if([str casestreq:"/VAR"])		return HTML_ENDEM;
	else if([str casestreq:"UL"])		return HTML_UL;
	else if([str casestreq:"/UL"])		return HTML_ENDUL;
	else if([str casestreq:"/DL"])		return HTML_ENDDL;	//above
	else if([str strstr:"DL" caseSensitive:NO])		return HTML_DL;
	else if([str casestreq:"LI"])		return HTML_LI;
	else if([str casestreq:"/LI"])		return HTML_ENDLI;
	else if([str casestreq:"DD"])		return HTML_DD;
	else if([str casestreq:"/DD"])		return HTML_ENDLI;
	else if([str casestreq:"DT"])		return HTML_DT;
	else if([str casestreq:"OL"])		return HTML_OL;
	else if([str casestreq:"/OL"])		return HTML_ENDOL;
	else if([str casestreq:"BR"])		return HTML_P;
	else if([str casestreq:"CENTER"])	return HTML_CENTER;
	else if([str casestreq:"/CENTER"])	return HTML_ENDCENTER;
	else if([str casestreq:"/TABLE"])	return HTML_ENDTABLE;
	else if([str strstr:"TABLE" caseSensitive:NO])	return HTML_TABLE;
	else if([str casestreq:"TR"])		return HTML_TR;
	else if([str casestreq:"/TR"])		return HTML_ENDTR;
	else if([str casestreq:"/TD"])		return HTML_ENDTD;
	else if([str strstr:"TD" caseSensitive:NO])		return HTML_TD;
	else if([str casestreq:"PRE"])		return HTML_PRE;
	else if([str casestreq:"/PRE"])		return HTML_ENDPRE;
	else if([str casestreq:"BLOCKQUOTE"])		return HTML_BLOCKQUOTE;
	else if([str casestreq:"/BLOCKQUOTE"])		return HTML_ENDBLOCKQUOTE;

	else if([str strstr:"IMG SRC" caseSensitive:NO])			return HTML_IMG;
	else if([str strstr:"A HREF" caseSensitive:NO])				return HTML_HREF;
	else if([str strstr:"A NAME" caseSensitive:NO])				return HTML_NAME;
	else if([str casestreq:"/A"])								return HTML_ENDHREF;

// Forms-stuff
	else if([str strstr:"FORM METHOD" caseSensitive:NO])		return HTML_FORMSESSION;
	else if([str casestreq:"/FORM"])							return HTML_ENDFORMSESSION;
	else if([str strstr:"INPUT TYPE" caseSensitive:NO])			return HTML_FORMINPUT;
	else if([str strstr:"INPUT ID" caseSensitive:NO])			return HTML_FORMINPUT;
	else if([str casestreq:"/OPTION"])							return HTML_ENDOPTION;	// above select !! (SELECTED)
	else if([str strstr:"OPTION" caseSensitive:NO])				return HTML_OPTION;
	else if([str casestreq:"/SELECT"])							return HTML_ENDSELECT;	// reihenfolge!
	else if([str strstr:"SELECT" caseSensitive:NO])				return HTML_SELECT;
	else if([str strstr:"TH" caseSensitive:NO])					return HTML_TH;			//Lowest!

	return HTML_endOfHTMLList;
}
-(MiscString*) contentFor:(HTML_t) theT inArr:(int*) modeArray in:(MiscString *)parseString right:(int)dirIndex
{	MiscString *contentString=[parseString midFrom:modeArray[theT] to:dirIndex];
	modeArray[theT]=-1;
	return contentString;
}


- insertBackground:(MiscString*)tmpString
{//	NXRect newFrame;
	id theCell;
	const char *theFile;
	List *keyVal=[tmpString tokenize:"=" into:nil];
	MiscString *key=[keyVal objectAt:0],*val=[keyVal objectAt:1];
	if([key strstr:"background" caseSensitive:NO])
	{//	MiscString *filename;
		int			end;
		if((end=[val spotOfChars:"\"" occurrenceNum:1]) >=0) [val removeFrom:end to:[val length]];
		[val trimChars:"\""];
		theFile=[val stringValue];
		theCell=[[TextBackgroundCell alloc] initFromImage:[[NXImage alloc] initFromFile:theFile]];
		[[keyVal freeObjects] free];
#if 1
		[self replaceSelWithCell:theCell];
#else
		[self getBounds:&newFrame];
		{	Button *background=[[Button alloc] initFrame:&newFrame];
			[background setSelectable:NO];
			[[background setCell:[[TextBackgroundCell alloc] init]] free];
			[self addSubview:background];
		}
#endif
		return theCell;
	} return nil;
}
- insertImage:(MiscString*)tmpString
{	Cell	 *theCell=nil;
	NXImage	 *theImage=nil;
	const char *theFile;
	List *keyVal=[tmpString tokenize:"=" into:nil];
	MiscString /**key=[keyVal objectAt:0],*/*val=[keyVal objectAt:1];
	theFile=[val stringValue];
	if(delegate && [delegate respondsTo:@selector(getImageForFile:)])
			theImage=[delegate getImageForFile:theFile];
	else	theImage=[[NXImage alloc] initFromFile:theFile];
	if(theImage)
	{	theCell=[[NXGraphicCell alloc] initFromImage:theImage name:theFile];
		[self replaceSelWithCell:theCell];
	}
	[[keyVal freeObjects] free];
	return theCell;
}
- insertLink:(MiscString*)tmpString
{	id theCell=nil;
	const char *theFile;
	List *keyVal=[tmpString tokenize:"=" into:nil fieldsQuotatedBy:'\"'];
	MiscString *key=[keyVal objectAt:0],*val=[keyVal objectAt:1];
	[val trimChars:" \""];
	theFile=[val stringValue];
	if([key strstr:"HREF" caseSensitive:NO])
	{	theCell=[[NXLinkCell alloc] initForView:self];
		if(strchr([val stringValue],'#'))
		{	List *fileMarker=[val tokenize:"#" into:nil fieldsQuotatedBy:'\"'];
			if([fileMarker count]> 1)
			{	[theCell setLinkMarkername:[[fileMarker objectAt:1] stringValue]];
				[theCell setLinkFilename:[[fileMarker objectAt:0] stringValue]];
			} else
			{	[theCell setLinkFilename:"///SameFile///"];
				[theCell setLinkMarkername:[[fileMarker objectAt:0] stringValue]];
			}
			[[fileMarker freeObjects] free];
		} else [theCell setLinkFilename:theFile];
		[theCell setOpenInNewWin:NO];
	} else if([key strstr:"NAME" caseSensitive:NO])
	{	theCell=[[NXMarkerCell alloc] initForView:self];
		[theCell setMarkername:theFile];
		[theCell setVisible:NO];
	} [[keyVal freeObjects] free];
	[self replaceSelWithCell:theCell];
	return self;
}

static id sharedField;
- sharedField {return sharedField;}
static id sharedButton;
- setSharedButton:theButton
{	if(sharedButton)
	{	[sharedButton removeFromSuperview];
		[sharedButton free];
	}
	return sharedButton=theButton;
}

// cursor has to be set in advance
- insertFormElement:(MiscString*) tmpString forSession:(int)formSession radioList:currRadioList
{	List		*attribs;
	MiscString	*currStr;
	int			 i;
	id			 gadget=nil;

	[tmpString replaceEveryOccurrenceOf:"INPUT TYPE" with:"INPUT_TYPE" caseSensitive:NO];
	[tmpString replaceEveryOccurrenceOf:"INPUT ID" with:"INPUT_ID" caseSensitive:NO];
	attribs=[tmpString tokenize:" " into:nil fieldsQuotatedBy:'\"'];
	for(i=0; currStr=[attribs objectAt:i];i++)
	{	List *keyVal=[currStr tokenize:"=" into:nil];
		MiscString *key=[keyVal objectAt:0],*val=[keyVal objectAt:1];
		[val trimChars:"\""];[val collapseHTMLQuotedChars];
		if([key casestreq:"INPUT_TYPE"] || [key casestreq:"INPUT_ID"])
		{	if([val casestreq:"radio"])
			{	gadget=[[MiscTFRadioCell alloc] initInText:self];
				[currRadioList addObject:gadget];
			} else if([val casestreq:"text"] || [key casestreq:"INPUT_ID"])
			{	if (!sharedField)
				{	 sharedField=[[TextField alloc] init];
					[sharedField setBezeled:YES];
					[sharedField setBackgroundGray:NX_WHITE];
					[sharedField setTag:2000];
					[sharedField setTextDelegate:[self delegate]];
				}
				 gadget=[[MiscTFTextFieldCell alloc] initTextCell:""];
				[gadget setBezeled:YES];
				[gadget setBackgroundGray:NX_WHITE];
				[gadget setStringValue:""];
				[gadget setSelectable:YES];
				[gadget setEditable:YES];
			} else if([val casestreq:"SUBMIT"] || [val casestreq:"RESET"])
			{	gadget=[[MiscTFButtonCell alloc] initInText:self];
			} 
		} else if([key casestreq:"NAME"])
		{
		} else if([key casestreq:"TYPE"])
		{	if([val casestreq:"hidden"])
			{	gadget=[gadget free];
			}
		} else if([key casestreq:"VALUE"])
		{	if(gadget && [gadget isKindOf:[MiscTFButtonCell class]])
				[gadget setTitle:[val stringValue]];
			else [gadget setStringValue:[val stringValue]];
		} else if([key casestreq:"SIZE"])
		{	NXCoord newWidth=8*[val intValue];
			if([gadget respondsTo:@selector(setWidth:)]) [gadget setWidth:newWidth];
			else
			{	NXRect lr;
				[gadget getFrame:&lr];
				[gadget sizeTo:8*[val intValue] :NX_HEIGHT(&lr)];
			}
		} [[keyVal freeObjects] free];
	} [[attribs freeObjects] free];
	if(gadget)
	{	[self replaceSelWithCell:gadget];
		// now register it with the delegate (including name, value etc.)
	} return gadget;
}

#define LEFT_MARGIN  1
#define INDENT_WIDTH  30
#define MAX_NESTING  8
#define LIST_START_POS listStarts[liNest]
#define LIST_COUNT liCount[liNest]
#define LIST_MODE liMode[liNest]

//#define SIMPLE_CELL

- insertTableFromString:(MiscString *) parseString atPos:(int *)currPosPtr modeArray:(int *) modeArray textPos:(int*) textPos
{	int currRow=0,currCol=0,maxRows=0,maxCols=0,
		currPos=*currPosPtr,dirIndex=-1,currColSpan=1;
	BOOL		  cont=YES;
	HTML_t		  currToken;
	MiscMatrix	 *currMatrix=[[MiscMatrix alloc] init];
	Cell		 *prototype=nil;
	BOOL		  currParagraphCentered=NO;

	if(!prototype)
	{	prototype=[[HTMLTextCell alloc] initWithHTML:"" inView:self];
	}// now setBordered et al¼
	[currMatrix setPrototype:prototype];
	[currMatrix addRow];[currMatrix addCol];

	for(;cont && (currPos< [parseString length]-1);)
	{	if([parseString charAt:currPos] !='<')		// plain text?
		{	currPos++;
		} else										// directive¼
		{	MiscString *tmpString;
			dirIndex=currPos+1;
			if(tmpString=[parseString extractBracketsContentWithPrefix:""
											positionInsideLeft:&dirIndex
												 positionRight:&currPos
												   leftBracket:'<'
												  rightBracket:'>'
												 deleteContent:NO]) currPos++;
			else
			{	// NXLogError("HTML error near %d",currPos);
				break;
			}
			switch(currToken=[self HTMLTokenForString:tmpString])
			{	case HTML_TD:
				{	int i;
					List *attribs=[tmpString tokenize:" " into:nil];
					MiscString *currStr;
					currColSpan=1;
					for(i=0; currStr=[attribs objectAt:i];i++)
					{	List *keyVal=[currStr tokenize:"=" into:nil];
						MiscString *key=[keyVal objectAt:0],*val=[keyVal objectAt:1];
						if([key casestreq:"COLSPAN"])
						{	currColSpan=[val intValue];
						} else if([key casestreq:"ALIGN"])
						{
						} [[keyVal freeObjects] free];
					} [[attribs freeObjects] free];
					if(modeArray) modeArray[currToken]=currPos;	// catch begin of run
					if(currCol++ > maxCols)
					{	[currMatrix addCol];
						maxCols=currCol;
					}
				} break;
				case HTML_TH:
					modeArray[currToken]=currPos;
				break;
				case HTML_CENTER: currParagraphCentered=YES;
				break;
				case HTML_ENDCENTER: currParagraphCentered=NO;
				break;
				case HTML_ENDTR:
					if(modeArray && modeArray[HTML_TH]>= 0)	// fake the tablehead via its own table
					{	int tmpPos=0;
						MiscString *contentString=[self contentFor:HTML_TH inArr:modeArray in:parseString
															 right:dirIndex-2],
									*tableString=[[MiscString alloc] initFromFormat:"<table><tr><td>%s</td></tr></table>",
													[contentString stringValue]];

						[self insertTableFromString:tableString atPos:&tmpPos modeArray:modeArray
							textPos:textPos];
						[contentString free];[tableString free];
						
					}
				break;
				case HTML_TR:
					if(currRow > maxRows)
					{	[currMatrix addRow];
						maxRows=currRow;
					} currRow++;currCol=0;
				break;
				case HTML_ENDTD:
				{	id	currCell=[currMatrix cellAt:currRow-1:currCol-1];
					MiscString *contentString=[self contentFor:currToken-1 inArr:modeArray in:parseString
															 right:dirIndex-2];
					if(![contentString indexOfChars:"<"])
					{	[contentString replaceEveryOccurrenceOfChar:'\r' with:""];
 						[contentString replaceEveryOccurrenceOfChar:'\n' with:""];
						[contentString collapseHTMLQuotedChars];[contentString trimSpaces];
					} [currCell setStringValue:[contentString stringValue]];
					[currCell setColumnsSpanning:currColSpan];
					[currCell setAlignment:currParagraphCentered? NX_CENTERALIGN:NX_LEFTALIGN];
					[contentString free];
				} break;
				case HTML_ENDTABLE:
					cont=NO;
				break;
				default: break;
			}
		}
	} (*currPosPtr)=currPos;
	[currMatrix sizeToFit];
	[self setSel:*textPos:*textPos];
	[self replaceSelWithCell:[[MiscTFMatrixCell alloc] initWithMatrix:currMatrix inView:self]];
	(*textPos)++;
	return self;
}


-(int) leftCp{	return sp0.cp;}

// sorry for this much to large method, but categorys dot have ivars
- setFromHTML:(const char *)htmlString andDelete:(BOOL) doClear
{	MiscString  *parseString=[[MiscString alloc] initString:htmlString];
	int			 textPos=0,rightPos=0,currPos=0,dirIndex=-1;
	int			 modeArray[HTML_endOfHTMLList],posArray[HTML_endOfHTMLList];
	HTML_t		 currToken,
				 liMode[MAX_NESTING]={HTML_endOfHTMLList};	// stacking:(using liNest as index)
	int			 liCount[MAX_NESTING], listStarts[MAX_NESTING],liNest=0,formSession=0;
	List		*currRadioList=nil;
	PopUpList	*currPopupList=nil;
	id			 currPopupCell=nil;
	BOOL		 currParagraphCentered=NO,hasBackground=NO,preMode=NO;

	if(sharedField)		[sharedField  removeFromSuperview];
	if(sharedButton)	[sharedButton removeFromSuperview];

	[self setGraphicsImportEnabled:YES];
	[self setMonoFont:NO];

	memset(modeArray,-1,(HTML_endOfHTMLList-1)*sizeof(int));	// init stateMachineArray

	if(doClear)
	{	//[self selectText:self];[self replaceSel:""];	// clear
		[self setText:""];
		[self setSel:0:0];
	}
// here we have to clean the viewhierarchy from zombi-HTML-views (Tables, forms etc.¼ )


	for(textPos=[self leftCp];currPos< [parseString length]-1;)
	{	if([parseString charAt:currPos] !='<')		// plain text?
		{	MiscString *tmpString;
			dirIndex=[parseString spotOfChar:'<' startingAt:currPos];
			if(dirIndex>= 0) rightPos= dirIndex-1;
			else rightPos=[parseString length];
			tmpString=[parseString midFrom:currPos to:rightPos];
			currPos=rightPos+1;
			if(![tmpString length] || (!preMode && [tmpString isWhite]))
			{	[tmpString free];
				continue;
			}
			[tmpString replaceEveryOccurrenceOfChar:'\r' with:""];
			[tmpString collapseHTMLQuotedChars];
			if(!preMode) 
			{	BOOL	hadLeftWhite= ([tmpString charAt:0] == ' '),
						hadRightWhite=([tmpString charAt:[tmpString length]-1] == ' ');
				[tmpString replaceEveryOccurrenceOfChar:'\n' with:" "];
				[tmpString trimWhiteSpaces];
				if(hadLeftWhite)	[tmpString insertChar:' ' at:0];
				if(hadRightWhite)	[tmpString insertChar:' ' at:[tmpString length]];
				[tmpString replaceEveryOccurrenceOf:"  " with:" " overlap:NO];
			}

			if(modeArray[HTML_TITLE]< 0 && modeArray[HTML_OPTION]< 0)	// muted?
			{	[self setSel:textPos:textPos];[self replaceSel:[tmpString stringValue]];
				textPos+=[tmpString length];
			} [tmpString free];
		} else										// directive¼
		{	MiscString *tmpString;
			dirIndex=currPos+1;
			if(tmpString=[parseString extractBracketsContentWithPrefix:""
											positionInsideLeft:&dirIndex
												 positionRight:&currPos
												   leftBracket:'<'
												  rightBracket:'>'
												 deleteContent:NO]) currPos++;
			else
			{	NXLogError("HTML error near %d\n",currPos);
				break;
			}

			switch(currToken=[self HTMLTokenForString:tmpString])
			{	case HTML_BODY:
					[self setSel:textPos:textPos];
					if([self insertBackground:tmpString])
					{	textPos++;
						hasBackground=YES;
					}
				break;
				case HTML_TABLE:
					[self insertTableFromString:parseString atPos:&currPos modeArray:modeArray
						textPos:&textPos];
				break;
				case HTML_ENDTABLE:
				break;
				case HTML_LI:case HTML_DD:case HTML_DT:
				{	char buf[100]={0};
					if(currToken != HTML_LI && (modeArray[HTML_DT] >=0 || modeArray[HTML_DD] >= 0))
					{	[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
					}
					if(modeArray[currToken] >=0)
					{	[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
						switch(currToken)
						{	case HTML_DD:
								[self setSel:posArray[currToken]:textPos-1];
								[self setSelProp:NX_FIRSTINDENT to:INDENT_WIDTH];
								[self setSelProp:NX_INDENT		to:INDENT_WIDTH];
								[self setSel:textPos:textPos];[self replaceSel:"\n"];textPos++;
								[self normalizeRulerAt:textPos];
							break;
							default: break;
						}
					} if(currToken != HTML_LI)
					{	modeArray[HTML_DT]=modeArray[HTML_DD]=-1;	// in some cases superseded below
					}
					modeArray[currToken]=currPos;	// catch begin of run
					switch(LIST_MODE)	// calculate prefix
					{	case HTML_UL:strcpy (buf+strlen(buf),"·\t");						break;
						case HTML_OL:sprintf(buf+strlen(buf),"%d.\t", LIST_COUNT++);		break;
						case HTML_DL:if(currToken != HTML_DT) strcpy(buf+strlen(buf),"\t");	break;
						default:break;
					} [self setSel:textPos:textPos];[self replaceSel:buf];
					textPos+=strlen(buf);
				} // HTML_LI: fall thru to catch¼
				// for these remember the beginning of the element
				case HTML_TITLE: case HTML_H1: case HTML_H2: case HTML_H4: case HTML_H6: case HTML_SUB:
				case HTML_STRONG: case HTML_EM: case HTML_OPTION: case HTML_BLOCKQUOTE: case HTML_SAMP:
					switch(currToken)
					{	case HTML_H1:case HTML_H2:case HTML_H3:case HTML_H4:case HTML_H5:case HTML_H6:
						case HTML_BLOCKQUOTE:
							if(!([self charAt:textPos-1] == '\n'))
							{	[self setSel:textPos:textPos];[self replaceSel:"\n"];textPos++;
							} [self setSel:textPos:textPos];[self replaceSel:"\n"];textPos++;
						break;
						case HTML_OPTION:
							// if(modeArray[currToken] >=0)		[currPopupList addItem:[contentString stringValue]];
						break;
						default: break;
					}
					modeArray[currToken]=currPos;	// catch begin of run
					posArray [currToken]=textPos;
				break;
				case HTML_ENDTITLE: case HTML_ENDH2: case HTML_ENDH4: case HTML_ENDH1:case HTML_ENDH6:
				case HTML_ENDSUB:
				case HTML_ENDSTRONG: case HTML_ENDEM: case HTML_ENDLI: case HTML_ENDOPTION:	// apply emphasis¼
					if(modeArray[currToken-1]>= 0)
					{	MiscString *contentString=[self contentFor:currToken-1 inArr:modeArray in:parseString
															 right:dirIndex-2];
						[contentString trimWhiteSpaces];[contentString collapseHTMLQuotedChars];

						switch(currToken)
						{	case  HTML_ENDTITLE:
								if(delegate && [delegate respondsTo:@selector(setTitle:)])
									[delegate setTitle:[contentString stringValue]];
							break;
							case HTML_ENDOPTION:
								[currPopupList addItem:[contentString stringValue]];
							break;
							// here go the headers¼
							case HTML_ENDH1: case HTML_ENDH2: case HTML_ENDH4: case HTML_ENDH6:
							{	float size=18.0;
								switch(currToken)
								{	case HTML_ENDH1: size=25.0; break;
									case HTML_ENDH2: size=18.0; break;
									case HTML_ENDH4: size=12.0; break;
									case HTML_ENDH6: size= 8.0; break;
									default: break;
								}
								[self setSel:textPos:textPos];[self replaceSel:"\n"];
								[self setSel:posArray[currToken-1]:textPos];
								[self setSelFontStyle:NX_BOLD];
								[self setSelFontSize:size];
								textPos++;[self setSel:textPos:textPos];
							} break;
							case HTML_ENDSTRONG:
								[self setSel:textPos:textPos];[self replaceSel:" "];
								[self setSel:posArray[currToken-1]:textPos];
								[self setSelFontStyle:NX_BOLD];
								[self setSel:textPos:textPos+1];[self setSelFontStyle:NX_UNBOLD];
								textPos++;[self setSel:textPos:textPos];
							break;
							case HTML_ENDEM:
								[self setSel:textPos:textPos];[self replaceSel:" "];
								[self setSel:posArray[currToken-1]:textPos];
								[self setSelFontStyle:NX_ITALIC];
								[self setSel:textPos:textPos+1];[self setSelFontStyle:NX_UNITALIC];
								textPos++;[self setSel:textPos:textPos];
							break;
							case HTML_ENDSUB:
								[self setSel:textPos:textPos];[self replaceSel:" "];
								[self setSel:posArray[currToken-1]:textPos];
								[self subscript:self];
								[self setSel:textPos:textPos+1];[self unscript:self];
								textPos++;[self setSel:textPos:textPos];
							break;
							case HTML_ENDLI:
								[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
								modeArray[currToken-1]=-1;
							break;
							default:	break;	// yeah, here goes html3.0 :-(
						} [contentString free];
					} else
					{	// error
					}
				break;
				case HTML_HR:
					[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
					[self setSel:textPos:textPos];[self replaceSelWithCell:[[HRCell alloc] initForText:self]];textPos++;
					[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
				break;
				case HTML_OL:case HTML_UL:case HTML_DL:	// lists
					modeArray[currToken]=currPos; posArray[currToken]=textPos;
					if(modeArray[HTML_DT]>= 0)
					{
						modeArray[HTML_DT]=-1;
					}
					if(modeArray[HTML_DD]>= 0 && [self charAt:textPos-1]== '\t')
					{	[self setSel:textPos-1:textPos];[self replaceSel:""]; textPos--;
					}
					if(!liNest || modeArray[HTML_LI] >=0 || [self charAt:textPos-1]!= '\n')
					{	[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
						modeArray[HTML_LI]=-1;
					} if(liNest)
					{	[self setSel:LIST_START_POS:textPos-1];
						[self setSelProp:NX_FIRSTINDENT to:INDENT_WIDTH*liNest];
						[self setSelProp:NX_INDENT to:INDENT_WIDTH*liNest+20];
						[self setSelProp:NX_ADDTAB to:INDENT_WIDTH*liNest+20];
						[self setSel:textPos:textPos];
					}
					liNest++;
					LIST_MODE=currToken;
					if(currToken == HTML_OL) LIST_COUNT=1;
					LIST_START_POS=textPos;
				break;
				case HTML_ENDUL: case HTML_ENDOL: case HTML_ENDDL:
					if(modeArray[HTML_LI] >=0)
					{	[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
						modeArray[HTML_LI]=-1;
					}
					if(liNest > 0) listStarts[liNest-1]=textPos;
					[self setSel:LIST_START_POS:textPos-1];
					[self removeAllTabs];
					[self setSelProp:NX_FIRSTINDENT to:INDENT_WIDTH*liNest];
					[self setSelProp:NX_INDENT to:INDENT_WIDTH*liNest+20];
					[self setSelProp:NX_ADDTAB to:INDENT_WIDTH*liNest+20];
					[self setSel:textPos:textPos];
					if(liNest< 2)
					{	[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
						[self normalizeRulerAt:textPos];
					}
					modeArray[currToken]=-1;liNest--;
					liNest=MAX(liNest,0);
				break;
				case HTML_CENTER: currParagraphCentered=YES;
				break;
				case HTML_ENDCENTER: currParagraphCentered=NO;
				break;
				case HTML_P:case HTML_ENDP:	// div. pragraphs
					modeArray[currToken]=-1;
					if(liNest && (LIST_MODE== HTML_UL || LIST_MODE== HTML_OL)) {break;}	// no cr's in lists
					[self setSel:textPos:textPos];[self replaceSel:"\n"];textPos++;
					posArray [currToken]=textPos;
					if(currToken == HTML_ENDP)
					{	if(currParagraphCentered)
						{	[self setSel:posArray [currToken-1]:textPos-1];[self alignSelCenter:self];
							[self setSel:textPos-1:textPos];[self alignSelLeft:self];
							[self setSel:textPos:textPos];//  [self pasteRuler:self];
							currParagraphCentered=NO;
						}
					}
				break;
				case HTML_ENDBLOCKQUOTE:
					[self setSel:posArray[currToken-1]:textPos-1];
					[self setSelProp:NX_FIRSTINDENT to:INDENT_WIDTH];
					[self setSelProp:NX_INDENT		to:INDENT_WIDTH];
					[self setSel:textPos:textPos];[self replaceSel:"\n"];textPos++;
					[self normalizeRulerAt:textPos];

				break;
				case HTML_FORMSESSION:
					formSession++;
					if(!currRadioList) currRadioList=[[List alloc] init];
				break;
				case HTML_ENDFORMSESSION:
					if(currRadioList && [currRadioList count])
					{	[currRadioList makeObjectsPerform:@selector(setCollegues:) with:currRadioList];
						[currRadioList makeObjectsPerform:@selector(setState:) with:(id)NO];
					}
					currRadioList=nil;
				break;
				case HTML_FORMINPUT:
					if([self insertFormElement:tmpString forSession:formSession radioList:currRadioList])
						textPos++;
				break;
				case HTML_SELECT:
					currPopupList=[[PopUpList alloc] init];
					currPopupCell=[[MiscTFPopupCell alloc] initInText:self];

					if(!sharedButton)
					{	sharedButton=[[Button alloc] init];
					}
					[self setSel:textPos:textPos];[self replaceSelWithCell:currPopupCell];
					textPos++;
				break;
				case HTML_ENDSELECT:
					if(currPopupList && [currPopupList count])
					{	NXRect	aFrame;
						[currPopupCell setPopupList:currPopupList];
						[currPopupList sizeToFit];
						[currPopupList getFrame:&aFrame];
						[currPopupCell setWidth:NX_WIDTH(&aFrame)];
						[currPopupCell setTitle:[[[[currPopupList itemList] cellList] objectAt:0] title]];
						[[[currPopupList itemList] cellList] makeObjectsPerform:@selector(setTarget:) with:currPopupCell];
						[[[currPopupList itemList] cellList] makeObjectsPerform:@selector(setAction:)
																		   with:(id)@selector(takeTitleFrom:)];
					}
					currPopupList=nil;
				break;
				case HTML_PRE:
					[self setSel:textPos:textPos];[self replaceSel:"\n"]; textPos++;
					posArray [currToken]=textPos;
					preMode=YES;
				break;
				case HTML_ENDPRE:
					preMode=NO;		// fall thru
//				case HTML_ENDSAMP:
					[self setSel:posArray[currToken-1]:textPos-1];
					[self setSelFont:[Font userFixedPitchFontOfSize:10 matrix:NX_FLIPPEDMATRIX]];
					[self setSel:textPos:textPos];
					if(currToken == HTML_ENDPRE)
					{	[self replaceSel:"\n"]; textPos++;
					}
				break;
				case HTML_IMG:
					[self setSel:textPos:textPos];
					if([self insertImage:tmpString])
						textPos++;
				break;
				case HTML_HREF: case HTML_NAME:
					[self setSel:textPos:textPos];
					if([self insertLink:tmpString])
						textPos++;
				break;
				default: break;	// yeah, here goes html3.0 :-(
			} [tmpString free];
		}
	} if(!hasBackground)
	{	[self setBackgroundGray:NX_LTGRAY];
	}
	// sizeToFit bug- workaround
	if([self textLength] == 1 && [self charAt:0] == '¬')
	{	[self setSel:0:0];[self replaceSel:" "];
		[self setSel:2:2];[self replaceSel:" "];
	}
	return self;
}
- setFromHTML:(const char *)htmlString
{	return [self setFromHTML:htmlString andDelete:YES];
}
@end

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