// utf-8

/*	Copyright  (c)	Günter Woigk   2001-2003
  					mailto:kio@little-bat.de

	This file is free software

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

	Permission to use, copy, modify, distribute, and sell this software and
	its documentation for any purpose is hereby granted without fee, provided
	that the above copyright notice appear in all copies and that both that
	copyright notice and this permission notice appear in supporting
	documentation, and that the name of the copyright holder not be used
	in advertising or publicity pertaining to distribution of the software
	without specific, written prior permission. The copyright holder makes no
	representations about the suitability of this software for any purpose.
	It is provided "as is" without express or implied warranty.

	THE COPYRIGHT HOLDER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
	INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
	EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, INDIRECT OR
	CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
	DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
	TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
	PERFORMANCE OF THIS SOFTWARE.


	=============================================================
		class String
	=============================================================

	2003-08-01 kio	finished work and testing on this string lib variant
*/


#define	LOG		3
#define	SAFE	3

#include	"config.h"
#include	<string.h>
#include	<math.h>
#include	"sstring.h"




/* ********************************************************************************
					CLASSIFICATION for 1st byte of utf-8 character
******************************************************************************** */

inline	bool	utf8_is_null	( char c )		{ return c==0; }
inline	bool	utf8_is_7bit	( char c )		{ return c>=0; }
inline	bool	utf8_no_7bit	( char c )		{ return c<0;  }
inline	bool	utf8_is_fup		( char c )		{ return c< char(0xc0);  }
inline	bool	utf8_no_fup		( char c )		{ return c>=char(0xc0);  }
inline	bool	utf8_is_c1		( char c )		{ return c>=0; }			// == utf8_is_7bit
inline	bool	utf8_is_c2		( char c )		{ return (c&0xe0)==0xc0; }
inline	bool	utf8_is_c3		( char c )		{ return (c&0xf0)==0xe0; }
inline	bool	utf8_is_c4		( char c )		{ return (c&0xf8)==0xf0; }
inline	bool	utf8_is_c5		( char c )		{ return (c&0xfc)==0xf8; }
inline	bool	utf8_is_c6		( char c )		{ return (c&0xfe)==0xfc; }



/* ===================================================================================
			UTILITIES
=================================================================================== */

// char -> digit value: ['0'..'9'] ---> [0..9]; non-digits ---> [10..255]
inline	uchar	_digit_val(UCS1Char c)	 	{ return uchar (c-'0'); }
inline	ushort	_digit_val(UCS2Char c)	 	{ return ushort(c-'0'); }
inline	ulong	_digit_val(UCS4Char c)	 	{ return ulong (c-'0'); }

// char -> digit value: ['0'..'9']['A'..'Z']['a'..'z'] ---> [0...35]; non-digits ---> [36..233]
inline	uchar	_digit_value(UCS1Char c)	{ return c<='9' ? uchar (c-'0') : uchar ((c|0x20)-'a')+10; }
inline	ushort	_digit_value(UCS2Char c)	{ return c<='9' ? ushort(c-'0') : ushort((c|0x20)-'a')+10; }
inline	ulong	_digit_value(UCS4Char c)	{ return c<='9' ? ulong (c-'0') : ulong ((c|0x20)-'a')+10; }




inline void ucs4_from_ucs1 ( void* z, const void* q, long n )
{
	while (--n>=0) { ((UCS4Char*)z)[n] = ((cUCS1Char*)q)[n]; }
}

inline void ucs4_from_ucs2 ( void* z, const void* q, long n )
{
	while (--n>=0) { ((UCS4Char*)z)[n] = ((cUCS2Char*)q)[n]; }
}

inline void ucs4_from_ucs4 ( void* z, const void* q, long n )
{
	while (--n>=0) { ((UCS4Char*)z)[n] = ((cUCS4Char*)q)[n]; }
}

inline void ucs2_from_ucs1 ( void* z, const void* q, long n )
{
	while (--n>=0) { ((UCS2Char*)z)[n] = ((cUCS1Char*)q)[n]; }
}

inline void ucs2_from_ucs2 ( void* z, const void* q, long n )
{
	if (n&1) ((UCS2Char*)z)[n-1] = ((cUCS2Char*)q)[n-1];
	ucs4_from_ucs4(z,q,n>>1);
}

inline void ucs2_from_ucs4 ( void* z, const void* q, long n )
{
	while (--n>=0) { ((UCS2Char*)z)[n] = ((cUCS4Char*)q)[n]; }
}

inline void ucs1_from_ucs1 ( void* z, const void* q, long n )
{
	if (n&1) ((UCS1Char*)z)[n-1] = ((cUCS1Char*)q)[n-1];
	ucs2_from_ucs2(z,q,n>>1);
}

inline void ucs1_from_ucs2 ( void* z, const void* q, long n )
{
	while (--n>=0) { ((UCS1Char*)z)[n] = ((cUCS2Char*)q)[n]; }
}

inline void ucs1_from_ucs4 ( void* z, const void* q, long n )
{
	while (--n>=0) { ((UCS1Char*)z)[n] = ((cUCS4Char*)q)[n]; }
}

static void ucs_from_ucs ( int zcsz, void* z, int qcsz, const void* q, long n )
{
	switch ( zcsz*4 + qcsz )
	{
	case 4*4+1:	ucs4_from_ucs1(z,q,n); return;
	case 4*4+2:	ucs4_from_ucs2(z,q,n); return;
	case 4*4+4:	ucs4_from_ucs4(z,q,n); return;
	case 2*4+1:	ucs2_from_ucs1(z,q,n); return;
	case 2*4+2:	ucs2_from_ucs2(z,q,n); return;
	case 2*4+4:	ucs2_from_ucs4(z,q,n); return;
	case 1*4+1:	ucs1_from_ucs1(z,q,n); return;
	case 1*4+2:	ucs1_from_ucs2(z,q,n); return;
	case 1*4+4: ucs1_from_ucs4(z,q,n); return;
	default:	IERR();
	}
}

static void ucs_copy ( int csz, void* z, const void* q, long n )
{
	if (csz==1) { if(n&1) ((UCS1Char*)z)[n-1] = ((cUCS1Char*)q)[n-1]; n>>=1; csz=2; }
	if (csz==2) { if(n&1) ((UCS2Char*)z)[n-1] = ((cUCS2Char*)q)[n-1]; n>>=1; csz=4; }
	ucs4_from_ucs4(z,q,n);
}


inline void ucs4_clear ( UCS4Char* z, long n, UCS4Char filler )
{
	UCS4Char* e = z+n;
	while (z<e) *z++ = filler;
}

inline void ucs2_clear ( UCS2Char* z, long n, UCS2Char filler )
{
	if (n&1) *z++ = filler;
	ucs4_clear((UCS4Char*)z,n>>1,filler*0x00010001);
}

inline void ucs1_clear ( UCS1Char* z, long n, UCS1Char filler )
{
	if (n&1) *z++ = filler;
	ucs2_clear((UCS2Char*)z,n>>1,filler*0x0101);
}

static void ucs_clear ( int csz, void* z, long n, UCS4Char filler )
{
	if (csz==1) { if(n&1) ((UCS1Char*)z)[n-1] = filler; n>>=1; csz=2; filler = UCS1Char(filler) * 0x0101; }
	if (csz==2) { if(n&1) ((UCS2Char*)z)[n-1] = filler; n>>=1; csz=4; filler = UCS2Char(filler) * 0x00010001; }
	ucs4_clear((UCS4Char*)z,n,filler);
}



/* ----	test if UTF8 can be converted lossless to UCS1 --------------------
		assumes any 1-byte character and any 2-byte start code <0xc4 is ok
		does not check for bogus characters (ill.overlong, truncated)
		especially does not check for 0xFE + 0xFF: --> return no
*/
inline bool utf8_fits_in_ucs1 ( cptr a, cptr e )
{
	while ( a<e ) if (uchar(*a++) >= 0xC4) return no; return yes;
}

/* ----	test if UTF8 can be converted lossless to UCS2 --------------------
		assumes any 1-, 2- and 3-byte character is ok
		does not check for bogus characters (ill.overlong, truncated)
		especially does not check for 0xFE + 0xFF: --> return no
*/
inline bool utf8_fits_in_ucs2 ( cptr a, cptr e )
{
	while ( a<e ) if (uchar(*a++) >= 0xF0) return no; return yes;
}

/* ----	calculate required UCS variant for lossless conversion --------------------
		returns 1, 2 or 4
		assumes any 1-byte character and any 2-byte start code <0xc4 is ok
		assumes any 1-, 2- and 3-byte character is ok for UCS2
		does not check for bogus characters (ill.overlong, truncated)
		especially does not check for 0xFE + 0xFF: --> return 4
*/
static CharSize utf8_req_charsize ( cptr a, cptr e )
{
	while ( a<e && uchar(*a)<0xC4 ) a++; if (a==e) return ucs1;
	while ( a<e && uchar(*a)<0xF0 ) a++; if (a==e) return ucs2; else return ucs4;
}

inline CharSize req_charsize ( UCS4Char n )
{
	return n>>16 ? ucs4 : n>>8 ? ucs2 : ucs1;
}

inline CharSize req_charsize ( UCS2Char n )
{
	return n>>8 ? ucs2 : ucs1;
}

/*static CharSize req_charsize ( cUCS4Char* a, long n )
{
	cUCS4Char* e = a+n;
	while (a<e)
	{
		if (*--e>>8)
		{
			e++; while (a<e) if (*--e>>16) return ucs4;
			return ucs2;
		}
	}
	return ucs1;
}*/

/*static CharSize req_charsize ( cUCS2Char* a, long n )
{
	cUCS2Char* e = a+n;
	while (a<e) { if (*--e>>8) return ucs2; }
	return ucs1;
}*/



/* ----	count characters in UTF-8 string ----------------------------
		golden rule: every non_fup makes a character!
*/
static long utf8_charcount ( cptr a, cptr e )
{
	long n = e-a;
	while (a<e) n -= utf8_is_fup(*a++);
	return n;
}


static UCS1Char* ucs1_from_utf8 ( UCS1Char* z, cUTF8CharPtr a, cUTF8CharPtr e )
{	uchar c1,c2;

	while(a<e)
	{
		if (utf8_is_7bit(c1=*a++))						{ *z++=c1; continue; }
		if (utf8_is_fup(c1))  							{ continue; }
		if (a>=e||(c1&~1)!=0xc2||utf8_no_fup(c2=*a))	{ *z++='?'; continue; }
		a++; *z++ = (c1<<6) + (c2&0x3f);
	}
	return z;
}


static UCS2Char* ucs2_from_utf8 ( UCS2Char* z, cUTF8CharPtr a, cUTF8CharPtr e )
{	uchar c1,c2,c3;

	while(a<e)
	{
		if (utf8_is_7bit(c1=*a++))				{ *z++=c1; continue; }	// us-ascii
		if (utf8_is_fup(c1))					{ continue; }			// fup
		if (c1<0xc2||a>=e||utf8_no_fup(c2=*a))	{ *z++='?'; continue; }
		a++; c2 &= 0x3f; if (c1<0xe0)			{ *z++ = (ushort(c1&0x1f)<<6) + c2; continue; }
		if ((c1==0xe0&&c2<0xa0)||a>=e||utf8_no_fup(c3=*a))	{ *z++='?'; continue; }
		a++; c3 &= 0x3f; if (c1<0xf0)			{ *z++ = ((ushort)c1<<12) + ((ushort)c2<<6) + c3; continue; }
		*z++='?';
	}
	return z;
}


static UCS4Char* ucs4_from_utf8 ( UCS4Char*z, cUTF8CharPtr a, cUTF8CharPtr e )
{	UCS4Char c0,c1,n; uint i;

	while ( a<e )
	{
		c0 = uchar(*a++);
		if (utf8_is_7bit(c0)) { *z++ = c0; continue; }		// 7-bit ascii char
		if (utf8_is_fup(c0)) continue;		// 0x80 - 0xBF: skip unexpected fups
		if (c0>=0xfe) goto x;				// ill. codes

	// longish character:
	// 0xC0.. => 2 bytes:	110xxxxx 10xxxxxx
	// 0xE0.. => 3 bytes:	1110xxxx 10xxxxxx 10xxxxxx
	// 0xF0.. => 4 bytes:	11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
	// 0xF8.. => 5 bytes:	111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
	// 0xFC.. => 6 bytes:	1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
		n = 0;		// UCS-4 char code akku
		i = 0;		// UTF-8 character size

		while( c0 & (0x80u>>(++i)) )
		{
			if(a>=e) goto x;
			c1 = uchar(*a); if( utf8_no_fup(c1) ) goto x;
			a++; n = (n<<6) + (c1&0x3F);
		}

	// now: i = total number of digits
	//      n = UCS4 char code without c0 bits
		n += (c0&(0x7f>>i)) << (6*i-6);							// n := UCS4 char code

	// ill. overlong encodings:
	// i=2:	1100000x 10xxxxxx										< (1<<6)
	// i=3:	11100000 100xxxxx 10xxxxxx 								< (1<<11)
	// i=4:	11110000 1000xxxx 10xxxxxx 10xxxxxx						< (1<<16)
	// i=5:	11111000 10000xxx 10xxxxxx 10xxxxxx 10xxxxxx			< (1<<21)
	// i=6:	11111100 100000xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx	< (1<<26)
		if ( n < (1u<<(i*5-4)) ) goto x;						// ill. overlong encoding

	// ok => store code
		*z++ = n; continue;

	// error => store replacement char
	x:	*z++ = '?'; continue;
	}
	return z;
}


static long utf8_bytecount ( cUCS4Char* p, long n )
{
	long bytes = n;
	while(n)
	{
		UCS4Char c = p[--n]; if (c<(1<<7)) continue;
		c>>=6; do { bytes++; c>>=5; } while(c);
	}
	return bytes;
}

static long utf8_bytecount ( cUCS2Char* p, long n )
{
	long bytes = n;
	while(n)
	{
		UCS2Char c = p[--n]; if (c<(1<<7)) continue;
		bytes += c>>=11 ? 2 : 1;
	}
	return bytes;
}

static long utf8_bytecount ( cUCS1Char* p, long n )
{
	long bytes = n;
	while(n) { bytes += p[--n]>>7; }
	return bytes;
}


static ptr utf8_from_ucs1 ( ptr z, cUCS1Char* q, long n )
{
	cUCS1Char* e = q+n;
	while (q<e)
	{
		char c = *q++;
		if (c>=0)	*z++ = c;
		else		*z++ = 0xc0 + ((uchar)c>>6),
					*z++ = 0x80 + (c&0x3F);
	}
	return z;
}

static ptr utf8_from_ucs2 ( ptr z, cUCS2Char* q, long n )
{
	cUCS2Char* e = q+n;
	while (q<e)
	{
		UCS2Char c = *q++;
		if ( c<(1<<7) ) { *z++ = c; continue; }
		if ( c<(1<<11)) { *z++ = 0xC0+(c>>6); *z++ = 0x80+(c&0x3f); continue; }
		*z++ = 0xE0+(c>>12); *z++ = 0x80+((c>>6)&0x3f);  *z++ = 0x80+(c&0x3f);
	}
	return z;
}

static ptr utf8_from_ucs4 ( ptr z, cUCS4Char* q, long n )
{
	cUCS4Char* e = q+n;
	while (q<e)
	{
		UCS4Char c = *q++;
		if (c<(1<<7))    { *z++ = c; continue; }
		if ( c<(1<<11) ) { *z++ = 0xC0+(c>>6); *z++ = 0x80+(c&0x3f); continue; }
		if ( (long)c<0 ) { *z++ = '?'; continue; }
		int i = 16; while (c>>i) i+=5; i=i/5-1;		// i = num fups; num bits stored in fups = i*6
		*z++ = (char(0x80)>>i) + (c>>(i*6));		// non_fup
		while(i--) *z++ = 0x80 + ((c>>(i*6))&0x3f);	// fups
	}
	return z;
}



/* ===================================================================================
			STRING CONSTANTS
=================================================================================== */


#define B1(n)		 n,n+1,n+2,n+3
#define B2(n)		 B1(n),B1(n+ 4),B1(n+ 8),B1(n+12)
#define B3(n)		 B2(n),B2(n+16),B2(n+32),B2(n+48)
static cUCS1Char abc[] = { B3(0),B3(  64) ,B3( 128),B3( 192) };

cString			emptyString;				// empty string
static cString	abcString(abc,NELEM(abc));	// string filled with char(0), char(1), ..
static cString	spaceString(40,' ');		// string filled with spaces



/* ===================================================================================
			PRIVATE MEMBER FUNCTIONS
=================================================================================== */


void String::_init_from_utf8 ( cptr p, cptr e )
{
	count  = utf8_charcount ( p, e );
	csz    = utf8_req_charsize(p,e);
	text   = new char[csz*count];
	data   = text;
	next   = this;
	prev   = this;

	switch( csz )
	{
	case 1:  ucs1_from_utf8( Ucs1(),p,e ); break;
	case 2:  ucs2_from_utf8( Ucs2(),p,e ); break;
	default: ucs4_from_utf8( Ucs4(),p,e ); break;
	}
}

inline void String::_init ( )
{
	text   = NULL;
	count  = 0;
	csz    = ucs1;
	data   = NULL;
	next   = this;
	prev   = this;
}

inline void String::_init ( cString& q )
{
	text   = q.text;
	count  = q.count;
	csz    = q.csz;
	data   = q.data;
	next   = q.next;
	prev   = (String*)&q;
	next->prev = prev->next = this;
}

inline void String::_init ( CharSize sz, long n )
{
	count  = n;
	csz    = sz;
	text   = new char[sz*n];
	data   = text;
	next   = this;
	prev   = this;
}

inline void String::_kill()
{
	if (next==this)
	{
		XXXTRAP((size_t)data&3);
		delete[] data;
	}
	else
	{
		next->prev = prev;
		prev->next = next;
	}
}


/* ----	compare strings -------------------------------------------------------------------
		return values:
		>0	this >  q
		 0	this == q
		<0	this <  q
*/
long String::_compare ( cString& s ) const
{	long d;
	long n = Min(count,s.count);
	long i = -1;

	CharSize csz1 =   Csz();
	CharSize csz2 = s.Csz();

	switch(4*csz1+csz2)
	{
	case 4*1+1: { cUCS1Char*p=Ucs1(); cUCS1Char*q=s.Ucs1(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	case 4*1+2: { cUCS1Char*p=Ucs1(); cUCS2Char*q=s.Ucs2(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	case 4*1+4: { cUCS1Char*p=Ucs1(); cUCS4Char*q=s.Ucs4(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	case 4*2+1: { cUCS2Char*p=Ucs2(); cUCS1Char*q=s.Ucs1(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	case 4*2+2: { cUCS2Char*p=Ucs2(); cUCS2Char*q=s.Ucs2(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	case 4*2+4: { cUCS2Char*p=Ucs2(); cUCS4Char*q=s.Ucs4(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	case 4*4+1: { cUCS4Char*p=Ucs4(); cUCS1Char*q=s.Ucs1(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	case 4*4+2: { cUCS4Char*p=Ucs4(); cUCS2Char*q=s.Ucs2(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	case 4*4+4: { cUCS4Char*p=Ucs4(); cUCS4Char*q=s.Ucs4(); while(++i<n) if((d=p[i]-q[i])) return d; } break;
	default:	IERR();
	}
	return count - s.count;
}


/* ----	resize string ---------------------------------------------------------------
		grow or shrink string to new len
		does not clear padded character positions
*/
void String::_resize ( long newlen )
{
	ptr newdata = new char[csz*newlen];
	ucs_copy ( csz, newdata, text, Min(count,newlen) );

	_kill();
	count  = newlen;
	text   = newdata;
	data   = newdata;
	next   = this;
	prev   = this;
}



/* ===================================================================================
			PUBLIC MEMBER FUNCTIONS
=================================================================================== */


/* ----	Destructor -------------------------------------------------------------------
*/
String::~String ( )
{
	_kill();
}


/* ----	default creator --------------------------------------------------------------
		creates an empty string
		sharing StrMem empty_strmem
*/
String::String ( )
{
	_init();
}


/* ----	create string from single character ------------------------------------------
		charsize is choosen to fit
*/
String::String ( UCS4Char c )
{
	if (c>>16) { csz=ucs4; text = new char[ucs4*1]; Ucs4()[0] = c; } else
	if (c>>8 ) { csz=ucs2; text = new char[ucs2*1]; Ucs2()[0] = c; } else
			   { csz=ucs1; text = new char[ucs1*1]; Ucs1()[0] = c; }
	count = 1;
	data  = text;
	next  = this;
	prev  = this;
}

/* ----	create string from UCS-1 buffer data ----------------------------------------
		the buffer data is copied.
		to avoid this, use strings for your buffers!
*/
String::String ( cUCS1Char* p, long n )
{
	if(n>0)
	{
		_init(ucs1,n);
		memcpy ( text,p,n*ucs1 );
	}
	else _init();
}

/* ----	create string from UCS-2 buffer data ----------------------------------------
		the buffer data is copied.
		to avoid this, use strings for your buffers!
*/
String::String ( cUCS2Char* p, long n )
{
	if(n>0)
	{
		_init(ucs2,n);
		memcpy ( text,p,n*ucs2 );
	}
	else _init();
}

/* ----	create string from UCS-4 buffer data ----------------------------------------
		the buffer data is copied.
		to avoid this, use strings for your buffers!
*/
String::String ( cUCS4Char* p, long n )
{
	if(n>0)
	{
		_init(ucs4,n);
		memcpy ( text,p,n*ucs4 );
	}
	else _init();
}

/* ----	create string from UTF-8 buffer data ----------------------------------------
		the UTF-8 encoded text is converted
		to UCS-1, -2 or -4 as required.
*/
String::String ( cUTF8CharPtr p, long n )
{
	XXTRAP(!p);
	_init_from_utf8(p,p+n);
}


/* ----	create uncleared string ---------------------------------------------------
*/
String::String ( long n, CharSize csz )
{
	if(n>0)	_init(csz,n);
	else	_init();
}


/* ----	create cleared string -----------------------------------------------------
*/
String::String ( long n, UCS4Char c )
{
	if(n>0)
	{
		switch(req_charsize(c))
		{
		case 1:	 _init(ucs1,n); ucs1_clear(Ucs1(),n,c); return;
		case 2:	 _init(ucs2,n); ucs2_clear(Ucs2(),n,c); return;
		default: _init(ucs4,n); ucs4_clear(Ucs4(),n,c); return;
		}
	}
	else	_init();
}


/* ----	create string from c-style text literal ------------------------------------
*/
String::String ( cUTF8Str s )
{
	if (s&&*s)	_init_from_utf8( s,strchr(s,0) );
	else		_init();
}


/* ----	copy creator ---------------------------------------------------------------
*/
String::String ( cString& q )
{
	_init(q);
}


/* ---- create substring -----------------------------------------------------------
		the original string and the substring share a single StrMem
		=> both become not_writable!
		this can be purposely exploited for left-side substrings in assignments
*/
String::String ( cString& q, long a, long e )
{
	if (a<0)	   a = 0;
	if (e>q.Len()) e = q.Len();
	if (a>=e)
	{
		_init();
	}
	else
	{
		text   = q.text + a*q.csz;
		count  = e - a;
		csz    = q.csz;
		data   = q.data;
		next   = q.next;
		prev   = (String*)&q;
		next->prev = prev->next = this;
	}
}


/* ----	truncate string ------------------------------------------------------------
*/
void String::Truncate ( long newlen )
{
	if (newlen<=0)
	{
		_kill();
		_init();
	}
	else if (newlen<count)
	{
		count = newlen;
	}
}


/* ----	resize string ---------------------------------------------------------------
		shrink or grow string to new len
		if string grows, use the supplied character for padding
*/
void String::Resize ( long newlen, UCS4Char padding )
{
	if (newlen<=count)
	{
		Truncate(newlen);
	}
	else
	{
		CharSize ocsz = Csz();
		CharSize ncsz = Max( ocsz, req_charsize(padding) );
		ptr newdata = new char[ncsz*newlen];
		ucs_from_ucs ( ncsz, newdata, ocsz, text, count );
		ucs_clear    ( ncsz, newdata+count*ncsz, newlen-count, padding );
		_kill();
		count = newlen;
		csz   = ncsz;
		text  = newdata;
		data  = newdata;
		next  = this;
		prev  = this;
	}
}


/* ----	access character at given position ---------------------------------------------
		the index must be in range
*/
UCS4Char String::operator[] ( long i ) const
{
	XXTRAP ((ulong)i>=(ulong)count);

	switch(Csz())
	{
	case 1:  return Ucs1()[i];
	case 2:  return Ucs2()[i];
	default: return Ucs4()[i];
	}
}


/* ----	make writable ------------------------------------------------------------
*/
void String::MakeWritable ( )
{
	if (NotWritable())
	{
		ptr newdata = new char[count*csz];
		memcpy ( newdata, text, csz*count );
		_kill();
		text = newdata;
		data = newdata;
		next = this;
		prev = this;
	}
}


/* ----	assignment ----------------------------------------------------------------------
*/
String& String::operator= ( cString& q )
{
	if(this!=&q)
	{
		_kill();
		_init(q);
	}
	return *this;
}


/* ----	concatenate ----------------------------------------------------------------------
*/
String String::operator+ ( cString& q ) const
{
	CharSize qcsz = q.Csz();
	CharSize  csz =   Csz();
	CharSize zcsz = Max(csz,qcsz);
	String z(count+q.count,zcsz);

	ucs_from_ucs ( zcsz, z.text,             csz,   text,   count );
	ucs_from_ucs ( zcsz, z.text+count*zcsz, qcsz, q.text, q.count );
	return z;
}


/* ----	repetition -----------------------------------------------------------------------
*/
String String::operator* ( long n ) const
{
	if (n<=1) { return n==1 ? *this : emptyString; }

	XXTRAP( double(n)*csz*count != (n*csz*count)&0x7fffffff );

	String s ( n*count, csz );
	long l = count * csz;
	cptr q = text;
	ptr  z = s.text;
	while(n--) { memcpy(z,q,l); z+=l; }

	return s;
}


/* ----	create c-string from string ---------------------------------------------------------
		the returned c-string is allocated
		in the temporary c-string pool
*/
str String::CString ( ) const
{	long n;
	str s;
	ptr p;

	switch(csz)
	{
	case 1:	n = utf8_bytecount( Ucs1(), count );
			s = QString(n);
			p = utf8_from_ucs1( s, Ucs1(), count );
			XXXTRAP(p>s+n); *p=0; return s;
	case 2:	n = utf8_bytecount( Ucs2(), count );
			s = QString(n);
			p = utf8_from_ucs2( s, Ucs2(), count );
			XXXTRAP(p>s+n); *p=0; return s;
	default:n = utf8_bytecount( Ucs4(), count );
			s = QString(n);
			p = utf8_from_ucs4( s, Ucs4(), count );
			XXXTRAP(p>s+n); *p=0; return s;
	}
}


double String::NumVal ( ) const
{	cuptr  q, qe, a;
	uchar  c, bu[41];
	int    n  = Min((long)NELEM(bu)-1,count);
	double f  = 0.0;
	bool   neg;

	switch(csz)
	{
	default:
	{	int i; cUCS4Char* p4 = Ucs4();
		for (i=0;i<n;i++) { if(p4[i]>>8) break; }
		q=bu; n=i; ucs1_from_ucs4(bu,p4,n); break;
	}
	case 2:
	{	int i; cUCS2Char* p2 = Ucs2();
		for (i=0;i<n;i++) { if(p2[i]>>8) break; }
		q=bu; n=i; ucs1_from_ucs2(bu,p2,n); break;
	}
	case 1:
	{	int i; cUCS1Char* p1 = Ucs1();
		for (i=0;i<n;i++) { if(p1[i]>>8) break; }
		q=bu; n=i; ucs1_from_ucs1(bu,p1,n); break;
	}
	}

	qe  = q + n;
	*(uptr)qe = 0;

	c   = ' ';    while( q<qe && c<=' ' )     { c = *q++; }
	neg = c=='-'; if( q<qe && (neg||c=='+') ) { c = *q++; }

	if (_is_dec_digit(c))		// 123.456e78
	{
		f = strtod((cptr)q-1,NULL);
		if (errno==HUGE_VAL) errno=numbertoobig;
		return neg ? -f : f;
	}

	if (c=='$')			// $abc.def
	{
		a = q;
		while ( q<qe && (n=_digit_value(*q))<16 ) { q++; f = ldexp(f,4) + n; }
		if (q==a) goto x;

		if (q<qe&&*q=='.')
		{
			a = ++q;
			while ( q<qe && (n=_digit_value(*q))<16 ) { q++; f = ldexp(f,4) + n; }
			if(q>a) f = ldexp(f,4*(a-q));
		}
		return neg ? -f : f;
	}

	if (c=='%')			// %101010.101010
	{
		a = q;
		while ( q<qe && (n=_digit_val(*q))<2 ) { q++; f = ldexp(f,1) + n; }
		if (q==a) goto x;

		if (q<qe&&*q=='.')
		{
			a = ++q;
			while ( q<qe && (n=_digit_val(*q))<2 ) { q++; f = ldexp(f,1) + n; }
			if(q>a) f = ldexp(f,1*(a-q));
		}
		return neg ? -f : f;
	}

	if (c=='\'') 			// 'abcd'
	{
		a = q;
		bool esc=no;
		for(;;)
		{	if(q>=qe) goto x;				// not terminated
			c = *q;
			if(c=='\'') break;				// terminator
			if(c=='\\') { esc=yes; q++; }	// skip next byte. if multiple chars follow (e.g. octal digits) this will do fine too
			q++;
		}
		if(esc)
		{
			String z(a,q-a);	// digits without '
			z = z.FromQuoted();
			a = z.UCS1Text();
			q = a+z.Len();
			if(q==a||q-a>6) goto x;
			while(a<q) { f = ldexp(f,8) + *a++; }
		}
		else
		{
			if(q==a||q-a>6) goto x;
			while(a<q) { f = ldexp(f,8) + *a++; }
		}
		return neg ? -f : f;
	}

x:	errno = notanumber;
	return NAN;
}




/* ----	find character in string --------------------------------------------------------------------
		returns index of character found
		returns -1 if not found
*/
long String::Find ( UCS4Char c, long idx ) const
{
	if (idx<0) idx=0;
	switch(Csz())
	{
	case 1: if(c==UCS1Char(c)) { cUCS1Char* p1 = Ucs1(); while ( idx<count ) { if (p1[idx]==(UCS1Char)c) return idx; idx++; } } break;
	case 2:	if(c==UCS2Char(c)) { cUCS2Char* p2 = Ucs2(); while ( idx<count ) { if (p2[idx]==(UCS2Char)c) return idx; idx++; } } break;
	default:				   { cUCS4Char* p4 = Ucs4(); while ( idx<count ) { if (p4[idx]==(UCS4Char)c) return idx; idx++; } } break;
	}
	return -1;
}

long String::RFind ( UCS4Char c, long idx ) const
{
	if (idx>=count) idx=count-1;
	switch(Csz())
	{
	case 1:	if(c==UCS1Char(c)) { cUCS1Char* p1 = Ucs1(); while ( idx>=0 ) { if (p1[idx]==c) return idx; else idx--; } } break;
	case 2:	if(c==UCS2Char(c)) { cUCS2Char* p2 = Ucs2(); while ( idx>=0 ) { if (p2[idx]==c) return idx; else idx--; } } break;
	default:				   { cUCS4Char* p4 = Ucs4(); while ( idx>=0 ) { if (p4[idx]==c) return idx; else idx--; } } break;
	}
	return -1;  	// not found
}


/* ----	find substring in string --------------------------------------------------------------------
		returns index of substring found
		returns -1 if not found
*/
long String::Find ( cString& s, long idx ) const
{
	long qn = count;
	long sn = s.count;
	long n  = qn - sn;

	if (idx<0) idx=0;
	if (idx>n) return -1;
	if (sn==0) return idx;

	UCS4Char s0 = s[0];

	while ( idx<=n )
	{
		idx = Find(s0,idx); if(idx==-1) break;
		if (s==MidString(idx,sn)) return idx;
		idx++;
	}
	return -1;
}

long String::RFind ( cString& s, long idx ) const
{
	long qn = count;
	long sn = s.count;
	long n  = qn - sn;

	if (idx>n) idx=n;
	if (idx<0) return -1;
	if (sn==0) return idx;

	UCS4Char s0 = s[0];

	while ( idx>=0 )
	{
		idx = RFind(s0,idx); if(idx==-1) break;
		if (s==MidString(idx,sn)) return idx;
		idx--;
	}
	return -1;
}


/* ----	find c-string in string --------------------------------------------------------------------
		returns index of c-string found
		returns -1 if not found
*/
long String::Find ( cUTF8Str s, long idx ) const
{
	return Find(String(s),idx);		// to be optimized...
}

long String::RFind ( cUTF8Str s, long idx ) const
{
	return RFind(String(s),idx);		// to be optimized...
}


void String::Replace ( UCS4Char o, UCS4Char n )
{
	MakeWritable();
	if(req_charsize(o)>csz) return;
	CharSize rcsz = req_charsize(n);
	if (rcsz>csz)
	{
		ptr newdata = new char[rcsz*count];
		ucs_from_ucs ( rcsz, newdata, csz, text, count );
		_kill();
		csz  = rcsz;
		text = newdata;
		data = newdata;
		next = this;
		prev = this;
	}

	switch(csz)
	{
	case 1:
		{	UCS1Char* a = Ucs1();
			UCS1Char* e = a + Len();
			while ( --e>=a ) { if (*e==UCS1Char(o)) *e=n; }
		}	return;
	case 2:
		{	UCS2Char* a = Ucs2();
			UCS2Char* e = a + Len();
			while ( --e>=a ) { if (*e==UCS2Char(o)) *e=n; }
		}	return;
	default:
		{	UCS4Char* a = Ucs4();
			UCS4Char* e = a + Len();
			while ( --e>=a ) { if (*e==UCS4Char(o)) *e=n; }
		}	return;
	}
}

void String::Swap ( UCS4Char o, UCS4Char n )
{
	MakeWritable();
	CharSize rcsz = Max(req_charsize(n),req_charsize(o));
	if (rcsz>csz)
	{
		ptr newdata = new char[rcsz*count];
		ucs_from_ucs ( rcsz, newdata, csz, text, count );
		_kill();
		csz  = rcsz;
		text = newdata;
		data = newdata;
		next = this;
		prev = this;
	}
	UCS4Char x = o^n;

	switch(csz)
	{
	case 1:
		{	UCS1Char* a = Ucs1();
			UCS1Char* e = a + Len();
			UCS1Char c;
			while ( --e>=a ) { c=*e; if (c==UCS1Char(o)||c==UCS1Char(n)) *e=c^x; }
		}	return;
	case 2:
		{	UCS2Char* a = Ucs2();
			UCS2Char* e = a + Len();
			UCS2Char c;
			while ( --e>=a ) { c=*e; if (c==UCS2Char(o)||c==UCS2Char(n)) *e=c^x; }
		}	return;
	default:
		{	UCS4Char* a = Ucs4();
			UCS4Char* e = a + Len();
			UCS4Char c;
			while ( --e>=a ) { c=*e; if (c==UCS4Char(o)||c==UCS4Char(n)) *e=c^x; }
		}	return;
	}
}


// ----	uppercase/lowercase maps (created by "uc.txt,lc.txt.vs") -------
struct Pair { ushort n; short d; };	// ucs-2 codes
#include "utf-8/uc.txt"
#include "utf-8/lc.txt"


/* ----	convert string to uppercase ------------------------------------
		charsize is not changed except:
		if Ucs1()-size string contains "ÿ" charsize is changed to Ucs2()
		***TODO*** characters beyond Ucs2() are not yet converted
*/
String String::ToUpper ( ) const
{	int a,e,i;

	XXXTRAP( uc[NELEM(uc)-1].d != 0 );
	if(count<=0) return emptyString;
	String s(count,csz);
	memcpy ( s.text, text, count*csz );

	switch ( csz )
	{
	case 1:
		{
			UCS1Char* za = s.Ucs1();
			UCS1Char* ze = za + count;
			UCS1Char  c;

			while (za<ze)
			{
				c = *--ze;
				if ( c<'a'  ) continue;
				if ( c<='z' ) { *ze = c^0x20; continue; }
				if ( c<0xe0 ) continue;
				if ((c|8)!=0xff ) { *ze = c^0x20; continue; }		// if not ÷ or ÿ
				if ( c   ==0xff )									// if ÿ
				{
					ptr newdata = new char[ucs2*s.count];
					ucs2_from_ucs1 ( newdata, s.text, s.count );
					s._kill();
					s.csz  = ucs2;
					s.text = newdata;
					s.data = newdata;
					s.next = &s;
					s.prev = &s;
					goto c2;
				}
			}
			return s;
		}
	case 2:
		{
	c2:		UCS2Char* za = s.Ucs2();
			UCS2Char* ze = za + count;
			UCS2Char  c;

			while (za<ze)
			{
				c = *--ze;
				if ( c<'a'  ) continue;
				if ( c<='z' ) { *ze = c^0x20; continue; }
				if ( c<0xe0 ) continue;
				a = 0; e = NELEM(uc);
				while (a<e-1) { i=(a+e)/2; if (c<uc[i].n) e=i; else a=i; }
				*ze = c + uc[a].d;
			}
			return s;
		}
	default:		// ***TODO*** DESERET Letters 0x10400 .. 0x1044F have uc/lc
		{
			UCS4Char* za = s.Ucs4();
			UCS4Char* ze = za + count;
			UCS4Char  c;

			while (za<ze)
			{
				c = *--ze;
				if ( c<'a'  ) continue;
				if ( c<='z' ) { *ze = c^0x20; continue; }
				if ( c<0xe0 ) continue;
				a = 0; e = NELEM(uc);
				while (a<e-1) { i=(a+e)/2; if (c<uc[i].n) e=i; else a=i; }
				*ze = c + uc[a].d;
			}
			return s;
		}
	}
}


/* ----	convert string to lowercase ------------------------------------
		charsize is not changed
		***TODO*** characters beyond Ucs2() are not yet converted
*/
String String::ToLower ( ) const
{	int a,e,i;

	XXXTRAP( lc[NELEM(lc)-1].d != 0 );
	if(count<=0) return emptyString;
	String s(count,csz);
	memcpy ( s.text, text, count*csz );

	switch ( csz )
	{
	case 1:
		{
			UCS1Char* za = s.Ucs1();
			UCS1Char* ze = za + count;
			UCS1Char  c;

			while (za<ze)
			{
				c = *--ze ^ 0x20;
				if ( c<'a'  ) continue;
				if ( c<='z' ) { *ze = c; continue; }
				if ( c<0xe0 ) continue;
				if ((c|8)!=0xff ) { *ze = c; continue; }	 // if not × or ß
			}
			return s;
		}
	case 2:
		{
			UCS2Char* za = s.Ucs2();
			UCS2Char* ze = za + count;
			UCS2Char  c;

			while (za<ze)
			{
				c = *--ze ^ 0x20;
				if ( c<'a'  ) continue;
				if ( c<='z' ) { *ze = c; continue; }
				if ( c<0xe0 ) continue;
				c ^= 0x20;
				a = 0; e = NELEM(lc);
				while (a<e-1) { i=(a+e)/2; if (c<lc[i].n) e=i; else a=i; }
				*ze = c + lc[a].d;
			}
			return s;
		}
	default:
		{
			UCS4Char* za = s.Ucs4();
			UCS4Char* ze = za + count;
			UCS4Char  c;

			while (za<ze)
			{
				c = *--ze ^ 0x20;
				if ( c<'a'  ) continue;
				if ( c<='z' ) { *ze = c; continue; }
				if ( c<0xe0 ) continue;
				c ^= 0x20;
				a = 0; e = NELEM(lc);
				while (a<e-1) { i=(a+e)/2; if (c<lc[i].n) e=i; else a=i; }
				*ze = c + lc[a].d;
			}
			return s;
		}
	}
}


/* ----	convert string to html ------------------------------------------
		replaces <, >, & and " with names
		string may grow in length
		charsize is preserved
		note: no replacement of non-ucs-1 characters because
			  this would render non-Latin text unreadable.
*/
String String::ToHtml ( ) const
{	ptr t; long i;

	if ( count==0 ) return emptyString;
	long	 n   = 20;						// spare characters: string may grow!
	String   s   = String(count+n,csz);

	switch ( csz )
	{
	case 1:
		{
			UCS1Char   c;
			cUCS1Char* q  = Ucs1();
			cUCS1Char* qe = q + count;
			UCS1Char*  z  = s.Ucs1();
			UCS1Char*  ze = z + s.count -5;

			while ( q<qe )
			{
				switch(c=*q++)
				{
					default: *z++ = c; continue;
					case '<':	t = "&lt;";   break;
					case '>':	t = "&gt;";   break;
					case '&':	t = "&amp;";  break;
					case '"':	t = "&quot;"; break;
				}

				while( (c=*t++) ) *z++ = c;

				if (qe-q >= ze-z) continue;

				i = z - s.Ucs1();
				s._resize(s.count+(n*=2));
				z  = s.Ucs1() + i;
				ze = s.Ucs1() + s.count -5;
			}
			s.count = z - s.Ucs1();
			return s;
		}

	case 2:
		{
			UCS2Char   c;
			cUCS2Char* q  = Ucs2();
			cUCS2Char* qe = q + count;
			UCS2Char*  z  = s.Ucs2();
			UCS2Char*  ze = z + s.count -5;

			while ( q<qe )
			{
				switch(c=*q++)
				{
					default: *z++ = c; continue;
					case '<':	t = "&lt;";   break;
					case '>':	t = "&gt;";   break;
					case '&':	t = "&amp;";  break;
					case '"':	t = "&quot;"; break;
				}

				while( (c=*t++) ) *z++ = c;

				if (qe-q >= ze-z) continue;

				i = z - s.Ucs2();
				s._resize(s.count+(n*=2));
				z  = s.Ucs2() + i;
				ze = s.Ucs2() + s.count -5;
			}
			s.count = z - s.Ucs2();
			return s;
		}

	default:
		{
			UCS4Char   c;
			cUCS4Char* q  = Ucs4();
			cUCS4Char* qe = q + count;
			UCS4Char*  z  = s.Ucs4();
			UCS4Char*  ze = z + s.count -5;

			while ( q<qe )
			{
				switch(c=*q++)
				{
					default: *z++ = c; continue;
					case '<':	t = "&lt;";   break;
					case '>':	t = "&gt;";   break;
					case '&':	t = "&amp;";  break;
					case '"':	t = "&quot;"; break;
				}

				while( (c=*t++) ) *z++ = c;

				if (qe-q >= ze-z) continue;

				i = z - s.Ucs4();
				s._resize(s.count+(n*=2));
				z  = s.Ucs4() + i;
				ze = s.Ucs4() + s.count -5;
			}
			s.count = z - s.Ucs4();
			return s;
		}
	}
}


struct HtmlInfo { char name[10]; UCS2Char code; };
static HtmlInfo htmlinfo[] =
{
#define H(UTF8,BESCHR,NAME,UCS2) { NAME, UCS2 }
#include "utf-8/html.h"
#undef H
};


/* ----	convert string from Html ----------------------------------------
		expand entity names and character codes
		result string may have increased charsize
		result string length may shrink
*/
String String::FromHtml ( ) const
{	UCS4Char n,m;
	uint i,j;

	if ( count==0 ) return emptyString;
	String   s   = String(count,csz);

	switch ( csz )
	{
	case 1:
		{
			cUCS1Char* q  = Ucs1();
			cUCS1Char* qe = q + count;
			UCS1Char*  z  = s.Ucs1();
			UCS1Char   c;
			cUCS1Char  *q0,*qs;

			while ( q<qe )
			{
				c = *q++; if (c!='&') { *z++ = c; continue; }

				q0 = q;						// remember in case of invalid char ref.
				qs = q;						// ptr -> ';'
				while ( qs<qe && *qs!=';' ) qs++;
				if ( qs>=qe || qs>q+10 ) 	// unmasked '&' => copy 'as is'
				{ x1: q = q0; *z++ = '&'; continue; }

				if (*q=='#')				// UCS4 number
				{
					q++; n = 0;				// UCS4 code akku
					if ((*q|0x20)=='x')	for( q++; (m=_digit_value(*q))<16; q++ ) { n = (n<<4) + m; }
					else 				for(    ; (m=_digit_val(*q))<10;   q++ ) { n =  n*10  + m; }
					if (q!=qs) goto x1;
				}
				else						// named entity
				{
					for ( i=0; i<NELEM(htmlinfo); i++ )
					{
						cptr p = htmlinfo[i].name;
						for ( j=0; p[j] && p[j]==q[j]; j++ ) {}
						if (p[j]==0 && q+j==qs) break;
					}
					if (i==NELEM(htmlinfo)) goto x1;
					n = htmlinfo[i].code;
					q = qs;
				}
				if (n>>8)
				{
					String z(count,req_charsize(n));
					ucs_from_ucs ( z.csz,z.text, ucs1,text, count );
					return z.FromHtml();
				}
				q++; *z++ = n; continue;
			}

			s.count = z - s.Ucs1();
			return s;
		}

	case 2:
		{
			cUCS2Char* q  = Ucs2();
			cUCS2Char* qe = q + count;
			UCS2Char*  z  = s.Ucs2();
			UCS2Char   c;
			cUCS2Char  *q0,*qs;

			while ( q<qe )
			{
				c = *q++; if (c!='&') { *z++ = c; continue; }

				q0 = q;						// remember in case of invalid char ref.
				qs = q;						// ptr -> ';'
				while ( qs<qe && *qs!=';' ) qs++;
				if ( qs>=qe || qs>q+10 ) 	// unmasked '&' => copy 'as is'
				{ x2: q = q0; *z++ = '&'; continue; }

				if (*q=='#')			// UCS4 number
				{
					q++; n = 0;			// UCS4 code akku
					if ((*q|0x20)=='x')	for( q++; (m=_digit_value(*q))<16; q++ ) { n = (n<<4) + m; }
					else 				for(    ; (m=_digit_val(*q))<10;   q++ ) { n =  n*10  + m; }
					if (q!=qs) goto x2;
				}
				else						// named entity
				{
					for ( i=0; i<NELEM(htmlinfo); i++ )
					{
						cptr p = htmlinfo[i].name;
						for ( j=0; p[j] && (uchar)p[j]==q[j]; j++ ) {}
						if (p[j]==0 && q+j==qs) break;
					}
					if (i==NELEM(htmlinfo)) goto x2;
					n = htmlinfo[i].code;
					q = qs;
				}
				if (n>>8)
				{
					String z(count,ucs4);
					ucs4_from_ucs2 ( z.Ucs4(), Ucs2(), count );
					return z.FromHtml();
				}
				q++; *z++ = n; continue;
			}

			s.count = z - s.Ucs2();
			return s;
		}

	default:
		{
			cUCS4Char* q  = Ucs4();
			cUCS4Char* qe = q + count;
			UCS4Char*  z  = s.Ucs4();
			UCS4Char   c;
			cUCS4Char  *q0,*qs;

			while ( q<qe )
			{
				c = *q++; if (c!='&') { *z++ = c; continue; }

				q0 = q;						// remember in case of invalid char ref.
				qs = q;						// ptr -> ';'
				while ( qs<qe && *qs!=';' ) qs++;
				if ( qs>=qe || qs>q+10 ) 	// unmasked '&' => copy 'as is'
				{ x4: q = q0; *z++ = '&'; continue; }

				if (*q=='#')			// UCS4 number
				{
					q++; n = 0;			// UCS4 code akku
					if ((*q|0x20)=='x')	for( q++; (m=_digit_value(*q))<16; q++ ) { n = (n<<4) + m; }
					else 				for(    ; (m=_digit_val(*q))<10;   q++ ) { n =  n*10  + m; }
					if (q!=qs) goto x4;
				}
				else						// named entity
				{
					for ( i=0; i<NELEM(htmlinfo); i++ )
					{
						cptr p = htmlinfo[i].name;
						for ( j=0; p[j] && (uchar)p[j]==q[j]; j++ ) {}
						if (p[j]==0 && q+j==qs) break;
					}
					if (i==NELEM(htmlinfo)) goto x4;
					n = htmlinfo[i].code;
					q = qs;
				}
				q++; *z++ = n; continue;
			}

			s.count = z - s.Ucs4();
			return s;
		}
	}		// case
}


static const char cc[] = " \\\"\e\a\b\f\n\r\t\v";	// control codes
static const char ec[] = " \\\"eabfnrtv";			// escape characters


/* ----	escape control codes et.al. -----------------------------------------
		replace control codes by "\ooo" octal numbers
		replace "well known" control codes by "\x" names
		replace char(0x7f) by "\ooo" octal number
		replace ' ', '"' and "\" by '\ ', '\"' and "\\"
		resulting string may grow in length
		resulting string charsize preserved
		if "addquotes" is true then double quotes are added at both ends
		*NOTE*: only non-printable 8-bit characters are replaced (=> ANSI-C)
*/
String String::ToEscaped ( bool addquotes ) const
{
	if (count==0 && !addquotes) return emptyString;

	int		 n   = 20;						// spare bytes: result string may grow
	String	 s   = String(count+n,csz);


	switch ( csz )
	{
	case 1:
		{
			UCS1Char   c;
			cUCS1Char* q  = Ucs1();			// source pointer (q='quelle')
			cUCS1Char* qe = q+count;						// source end
			UCS1Char*  z  = s.Ucs1();				// target pointer (z='ziel')
			UCS1Char*  ze = z +s.count -3;					// target end

			if (addquotes) *z++ = '"';

			while(q<qe)
			{
				c = *q++;

				if( (c&0x60) && c!=0x7f && c!='\\' && c!='"' && c!=' ' )
				{
					*z++ = c; continue;
				}

				if (qe-q < ze-z)
				{
					long i = z - s.Ucs1();
					s._resize(s.count+(n*=2));
					z  =  s.Ucs1() +i;
					ze =  s.Ucs1() +s.count-3;
				}

				*z++ = '\\';
				ptr p = strchr(cc,c);
				if (p) { *z++ = ec[p-cc]; continue; }
				z[2] = '0'+(c&7); c>>=3;
				z[1] = '0'+(c&7); c>>=3;
				z[0] = '0'+ c;
				z+=3;
			}

			if (addquotes) *z++ = '"';

			s.count = z - s.Ucs1();
			return s;
		}

	case 2:
		{
			UCS2Char   c;
			cUCS2Char* q  = Ucs2();			// source pointer (q='quelle')
			cUCS2Char* qe = q+count;						// source end
			UCS2Char*  z  = s.Ucs2();				// target pointer (z='ziel')
			UCS2Char*  ze = z +s.count -3;					// target end

			if (addquotes) *z++ = '"';

			while(q<qe)
			{
				c = *q++;

				if( (c&0xFF60) && c!=0x7f && c!='\\' && c!='"' && c!=' ' )
				{
					*z++ = c; continue;
				}

				if (qe-q < ze-z)
				{
					long i = z - s.Ucs2();
					s._resize(s.count+(n*=2));
					z  =  s.Ucs2() +i;
					ze =  s.Ucs2() +s.count-3;
				}

				*z++ = '\\';
				ptr p = strchr(cc,c);
				if (p) { *z++ = ec[p-cc]; continue; }
				z[2] = '0'+(c&7); c>>=3;
				z[1] = '0'+(c&7); c>>=3;
				z[0] = '0'+ c;
				z+=3;
			}

			if (addquotes) *z++ = '"';

			s.count = z - s.Ucs2();
			return s;
		}

	default:
		{
			UCS4Char   c;
			cUCS4Char* q  = Ucs4();			// source pointer (q='quelle')
			cUCS4Char* qe = q+count;						// source end
			UCS4Char*  z  = s.Ucs4();				// target pointer (z='ziel')
			UCS4Char*  ze = z +s.count -3;					// target end

			if (addquotes) *z++ = '"';

			while(q<qe)
			{
				c = *q++;

				if( (c&0xFFFFFF60) && c!=0x7f && c!='\\' && c!='"' && c!=' ' )
				{
					*z++ = c; continue;
				}

				if (qe-q < ze-z)
				{
					long i = z - s.Ucs4();
					s._resize(s.count+(n*=2));
					z  =  s.Ucs4() +i;
					ze =  s.Ucs4() +s.count-3;
				}

				*z++ = '\\';
				ptr p = strchr(cc,c);
				if (p) { *z++ = ec[p-cc]; continue; }
				z[2] = '0'+(c&7); c>>=3;
				z[1] = '0'+(c&7); c>>=3;
				z[0] = '0'+ c;
				z+=3;
			}

			if (addquotes) *z++ = '"';

			s.count = z - s.Ucs4();
			return s;
		}
	}
}


/* ----	undo \-escape sequences ---------------------------------------
		undoes "\ooo" octal numbers
		undoes "\xXX" hexa-decimal numbers
		undoes "\cX" control codes
		discards "\<return><whitespace>"
		undoes "well known" control codes with "\x" names
		undoes '\"' and "\\"
		resulting string may shrink
		resulting string charsize is preserved
		if "rmvquotes" is true then double quotes at both ends are removed
		*NOTE*: octal and hexadecimal escape sequences are only expanded
		to characters in range of ucs-1 (max. 3 octal digits, max. 2 hex. digits)
*/
String String::FromEscaped ( bool rmvquotes ) const
{
	if(count==0) return emptyString;
	CharSize csz = Csz();
	String s  = String(count,csz);

	switch ( csz )
	{
	case 1:
		{
			UCS1Char   c,d;
			cUCS1Char* q  = Ucs1();
			cUCS1Char* qe = q+count;
			UCS1Char*  z  = s.Ucs1();

			if(rmvquotes&&count>=2&&*q=='"'&&qe[-1]=='"') { q++; qe--; }

			for ( ; q<qe; *z++=c )
			{
				c = *q++;
				if (c!='\\') continue;

			// '\' escaped byte:
				if (q>=qe) continue;	// at eof: just store the '\'
				c = *q++;
				if (c>>7) continue;		// no valid \-sequence => masked for unknown reason => store as is

			// \ooo => octal value for next byte. 9th bit discarded
			// \oo     allowed but not recommended.
			// \o	   allowed but not recommended.
				if (_is_oct_digit(c))
				{
					c = _digit_val(c);
					if (q<qe) { d=_digit_val(*q); if (d<8) { q++; c=(c<<3)+d;         } }
					if (q<qe) { d=_digit_val(*q); if (d<8) { q++; c=UCS1Char(c<<3)+d; } }
					continue;
				}

			// \xHH => hex coded value for next byte
			// \xH     allowed too but not recommended.
			// \x      'x' masked for unknown reason => stores 'x'
				if (c=='x')
				{
					if (q<qe) { d=_digit_value(*q); if (d<16) { q++; c=d;        } }
					if (q<qe) { d=_digit_value(*q); if (d<16) { q++; c=(c<<4)+d; } }
					continue;
				}

			// \cX => control code value for next byte
			// \c     (at eof) => stores 'c'
				if (c=='c')
				{
					if (q<qe) c = *q++ & 0x1f;
					continue;
				}

			// \<return> => discard return and following white space  ((control codes))
				if (c==13||c==10)
				{
					while( q<qe && *q<=' ' ) q++;
					continue;
				}

			// "well known abreviations" or '"' '\' or s.th. escaped for unknown reason:
				ptr p = strchr(ec,c);
				if (p) c = cc[p-ec];
			}

			s.count = z - s.Ucs1();
			return s;
		}

	case 2:
		{
			UCS2Char   c,d;
			cUCS2Char* q  = Ucs2();
			cUCS2Char* qe = q+count;
			UCS2Char*  z  = s.Ucs2();

			if(rmvquotes&&count>=2&&*q=='"'&&qe[-1]=='"') { q++; qe--; }

			for ( ; q<qe; *z++=c )
			{
				c = *q++;
				if (c!='\\') continue;

			// '\' escaped byte:
				if (q>=qe) continue;	// at eof: just store the '\'
				c = *q++;
				if (c>>7) continue;		// no valid \-sequence => masked for unknown reason => store as is

			// \ooo => octal value for next byte. 9th bit discarded
			// \oo     allowed but not recommended.
			// \o	   allowed but not recommended.
				if (_is_oct_digit(c))
				{
					c = _digit_val(c);
					if (q<qe) { d=_digit_val(*q); if (d<8) { q++; c=(c<<3)+d;         } }
					if (q<qe) { d=_digit_val(*q); if (d<8) { q++; c=UCS1Char(c<<3)+d; } }
					continue;
				}

			// \xHH => hex coded value for next byte
			// \xH     allowed too but not recommended.
			// \x      'x' masked for unknown reason => stores 'x'
				if (c=='x')
				{
					if (q<qe) { d=_digit_value(*q); if (d<16) { q++; c=d;        } }
					if (q<qe) { d=_digit_value(*q); if (d<16) { q++; c=(c<<4)+d; } }
					continue;
				}

			// \cX => control code value for next byte
			// \c     (at eof) => stores 'c'
				if (c=='c')
				{
					if (q<qe) c = *q++ & 0x1f;
					continue;
				}

			// \<return> => discard return and following white space  ((control codes))
				if (c==13||c==10)
				{
					while( q<qe && *q<=' ' ) q++;
					continue;
				}

			// "well known abreviations" or '"' '\' or s.th. escaped for unknown reason:
				ptr p = strchr(ec,c);
				if (p) c = cc[p-ec];
			}

			s.count = z - s.Ucs2();
			return s;
		}

	default:
		{
			UCS4Char   c,d;
			cUCS4Char* q  = Ucs4();
			cUCS4Char* qe = q+count;
			UCS4Char*  z  = s.Ucs4();

			if(rmvquotes&&count>=2&&*q=='"'&&qe[-1]=='"') { q++; qe--; }

			for ( ; q<qe; *z++=c )
			{
				c = *q++;
				if (c!='\\') continue;

			// '\' escaped byte:
				if (q>=qe) continue;	// at eof: just store the '\'
				c = *q++;
				if (c>>7) continue;		// no valid \-sequence => masked for unknown reason => store as is

			// \ooo => octal value for next byte. 9th bit discarded
			// \oo     allowed but not recommended.
			// \o	   allowed but not recommended.
				if (_is_oct_digit(c))
				{
					c = _digit_val(c);
					if (q<qe) { d=_digit_val(*q); if (d<8) { q++; c=(c<<3)+d;         } }
					if (q<qe) { d=_digit_val(*q); if (d<8) { q++; c=UCS1Char(c<<3)+d; } }
					continue;
				}

			// \xHH => hex coded value for next byte
			// \xH     allowed too but not recommended.
			// \x      'x' masked for unknown reason => stores 'x'
				if (c=='x')
				{
					if (q<qe) { d=_digit_value(*q); if (d<16) { q++; c=d;        } }
					if (q<qe) { d=_digit_value(*q); if (d<16) { q++; c=(c<<4)+d; } }
					continue;
				}

			// \cX => control code value for next byte
			// \c     (at eof) => stores 'c'
				if (c=='c')
				{
					if (q<qe) c = *q++ & 0x1f;
					continue;
				}

			// \<return> => discard return and following white space  ((control codes))
				if (c==13||c==10)
				{
					while( q<qe && *q<=' ' ) q++;
					continue;
				}

			// "well known abreviations" or '"' '\' or s.th. escaped for unknown reason:
				ptr p = strchr(ec,c);
				if (p) c = cc[p-ec];
			}

			s.count = z - s.Ucs4();
			return s;
		}
	}
}


/* ----	purge caches to free up memory -----------------------------------------------
*/
void String::PurgeCaches ( )
{
}





/* ===================================================================================
			NON MEMBER FUNCTIONS
=================================================================================== */


String CharString ( UCS4Char c )
{
	XXXTRAP( abcString.Len()!=256 );
	XXXTRAP( abcString[(uchar)c]!=(uchar)c );
	return c<256 ? abcString.MidString(c,1) : String(c);
}


String SpaceString ( long n, UCS4Char c )
{
	XXXTRAP( spaceString!=String(spaceString.Len(),' ') );
	return c==' '&&n<=spaceString.Len() ? spaceString.LeftString(n) : String(n,c);
}


String NumString ( double n )
{
//	max. 21 char
	String z(24,ucs1);
	z.Truncate( sprintf(z.Text(),"%.14g",n) );
	return z;
}

String NumString ( long n )
{
// max. 12 chars
	String z(12,ucs1);
	z.Truncate( sprintf(z.Text(),"%li",n) );
	return z;
}

String NumString ( ulong n )
{
// max. 11 chars
	String z(12,ucs1);
	z.Truncate( sprintf(z.Text(),"%lu",n) );
	return z;
}

String HexString ( ulong n, int digits )
{
	if(!digits) { digits = n>>16 ? 8 : n>>8 ? 4 : 2; }

	String z(digits,ucs1);
	while(digits>0)
	{
		z.Text()[--digits] = "0123456789ABCDEF"[n&0x0f];
		n >>= 4;
	}
	return z;
}









































