// 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
	2003-08-02 kio	eliminated StrMem. added operator new() and delete()
	2003-08-05 kio	ToEscaped() no longer escapes spaces
	2003-08-14 kio	fixed bug in ToEscaped()
	2003-08-15 kio	extended NumVal(), new StrVal()
*/


#define	LOG		2
#define	SAFE	3

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




/* ********************************************************************************
					UTF-8 UTILITIES
******************************************************************************** */


/* ===================================================================================
			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 csz1;
	while ( a<e && uchar(*a)<0xF0 ) a++; if (a==e) return csz2; else return csz4;
}

inline CharSize req_charsize ( UCS4Char n )
{
	return n>>16 ? csz4 : n>>8 ? csz2 : csz1;
}

inline CharSize req_charsize ( UCS2Char n )
{
	return n>>8 ? csz2 : csz1;
}


/* ----	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;
}


/* ----	convert utf-8 to ucs-1 --------------------------------------
*/
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;
}


/* ----	convert utf-8 to ucs-2 --------------------------------------
*/
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;
}


/* ----	convert utf-8 to ucs-4 --------------------------------------
*/
static UCS4Char* ucs4_from_utf8 ( UCS4Char*z, cUTF8CharPtr a, cUTF8CharPtr e )
{	UCS4Char c0,c1,n; uint i;

	#if XXXLOG
	XXXLogIn("ucs4_from_utf8(UCS4Char*,%8lx,%8lx)",(ulong)a,(ulong)e);
	UCS4Char*z0=z;
	#endif

	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;
	}

	#if XXXLOG
	XXXLogOut("= %i chars",int(z-z0));
	#endif
	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 POOL
=================================================================================== */

#if 0

/*	version with sliding pool:
	memory is allocated and deallocated as usual,
	but requests and releases which occur within the pool sized window are served from the pool
	+ reducing calls to new[] and delete[]
	- still every existing String has it's own individually allocted memory
	+ slightly faster
*/
const int  pool_max	= 500;
static int pool_idx = 0;
static ptr pool[pool_max];

void* String::operator new ( size_t n )
{
	XXXTRAP(n!=sizeof(String));
	if(pool_idx>0) return pool[--pool_idx];
	return new char[sizeof(String)];
}

void String::operator delete ( void* p, size_t n )
{
	XXXTRAP(n!=sizeof(String));
	if (pool_idx<pool_max) { pool[pool_idx++] = (ptr)p; return; }
	delete[] (char*)p;
}

#else

/*	version with blocks:
	memory is allocated in chunks. requests are served from these chunks
	and releases go back into these chunks
	+ only few blocks allocated with new[]
	- chunks are never released
	+ faster
*/
static void**	free_pool	= NULL;		// array of indexes of unused Stings
static ulong	free_max	= 0;		// total size
static ulong	free_idx	= 0;		// used slots in free_pool[]

void* String::operator new ( size_t sz )
{
	XXXXTRAP(sz!=sizeof(String));

	if ( free_idx==0 )
	{
		const int n = 500;
		free_idx  = n;
		free_max += n;
	// grow free_pool[]:
		delete[] free_pool;				// currently empty!
		free_pool = new void*[free_max];
	// alloc block of Strings and store addresses in free_pool[]
		String* b = (String*)new char[n*sizeof(String)];
		for ( int i=free_idx;--i>=0; ) free_pool[i] = &b[i];
	}

	return free_pool[--free_idx];
}

void String::operator delete ( void* p, size_t sz )
{
	XXXXTRAP(sz!=sizeof(String));
	XXXXTRAP(free_idx>=free_max);
	free_pool[free_idx++] = p;
}

#endif



/* ===================================================================================
			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 );
	CharSize csz = utf8_req_charsize(p,e);
	text   = new char[csz*count];
	SetDataAndCsz(text,csz);
	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;
	}
}

void String::_move ( String& q )
{
	if(this!=&q)
	{
		text = q.text;
		count = q.count;
		SetDataAndCsz(q.DataAndCsz());
		next = q.next;
		prev = q.prev;
		next->prev = this;
		prev->next = this;
	}
}


/* ----	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 )
{
	CharSize csz = Csz();
	ptr newdata = new char[csz*newlen];
	ucs_copy ( csz, newdata, text, Min(count,newlen) );

	_kill();
	count  = newlen;
	text   = newdata;
	SetData(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) { text = new char[csz4*1]; SetDataAndCsz(text,csz4); Ucs4()[0] = c; } else
	if (c>>8 ) { text = new char[csz2*1]; SetDataAndCsz(text,csz2); Ucs2()[0] = c; } else
			   { text = new char[csz1*1]; SetDataAndCsz(text,csz1); Ucs1()[0] = c; }
	count = 1;
	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(csz1,n);
		memcpy ( text,p,n*csz1 );
	}
	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(csz2,n);
		memcpy ( text,p,n*csz2 );
	}
	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(csz4,n);
		memcpy ( text,p,n*csz4 );
	}
	else _init();
}

/* ----	create string from UCS-1, -2 or -4 buffer data ----------------------------------------
		the buffer data is copied.
		to avoid this, use strings for your buffers!
*/
String::String ( cptr p, long n, CharSize cs )
{
	if(n>0)
	{
		_init(cs,n);
		memcpy ( text,p,n*cs );
	}
	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 )
{
	if (n)
	{
		XXTRAP(!p);
		_init_from_utf8(p,p+n);
	}
	else
	{
		_init();
	}
}


/* ----	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(csz1,n); ucs1_clear(Ucs1(),n,c); return;
		case 2:	 _init(csz2,n); ucs2_clear(Ucs2(),n,c); return;
		default: _init(csz4,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;
		SetDataAndCsz(q.DataAndCsz());
		next   = q.next;
		prev   = (String*)&q;
		next->prev = prev->next = this;
	}
}


/* ---- create substring -----------------------------------------------------------
		the original string and the substring share a single StrMem
		=> both become not_writable!
		special re-interpret version for vipsi, used for string literals in proc bodies
*/
String::String ( cString& q, cuptr txt, long cnt, CharSize cs )
{
	if ( cnt )
	{
		XXTRAP( (ptr)txt        < q.text                 );
		XXTRAP( (ptr)txt+cnt*cs > q.text+q.count*q.Csz() );

		text   = (ptr)txt;
		count  = cnt;
		SetDataAndCsz(q.Data(),cs);
		next   = q.next;
		prev   = (String*)&q;
		next->prev = prev->next = this;
	}
	else
	{
		_init();
	}
}




CharSize String::ReqCsz()
{
	switch(Csz())
	{
	case csz1:
		break;

	case csz2:
		{	cUCS2Char* a = Ucs2();
			cUCS2Char* e = a+Len();
			while (a<e) { if (*--e>>8) return csz2; }
		}	break;

	case csz4:
		{	cUCS4Char* a = Ucs4();
			cUCS4Char* e = a+Len();
			while (a<e)
			{
				if (*--e>>8)
				{
					e++; while (a<e) if (*--e>>16) return csz4;
					return csz2;
				}
			}
		}	break;
	}

	return csz1;
}




/* ----	quick clear string ------------------------------------------------------------
*/
void String::Clear ( )
{
	_kill();
	_init();
}


/* ----	quick clear string ------------------------------------------------------------
*/
void String::Swap ( String& that )
{
	char z[sizeof(String)];
	String& temp = *(String*)z;

	temp._move(*this);
	this->_move(that);
	that._move(temp);
}


/* ----	quick copy string ------------------------------------------------------------
*/
void String::Copy ( cString& q )
{
	if(this!=&q)
	{
		_kill();
		_init(q);
	}
}


/* ----	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;
		text  = newdata;
		SetDataAndCsz(newdata,ncsz);
		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())
	{
		CharSize csz=Csz();
		ptr newdata = new char[count*csz];
		memcpy ( newdata, text, csz*count );
		_kill();
		text = newdata;
		SetDataAndCsz(newdata,csz);
		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; }

	CharSize csz = Csz();
	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 ( long* idx_io ) const
{	cuptr  q, qe, a;
	uchar  c, bu[41];
	long   idx = idx_io ? *idx_io : 0;
	int    n   = Min((long)NELEM(bu)-1,count-idx);
	double f   = 0.0;
	bool   neg;

	switch(Csz())
	{
	default:
	{	int i; cUCS4Char* p4 = Ucs4()+idx;
		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()+idx;
		for (i=0;i<n;i++) { if(p2[i]>>8) break; }
		q=bu; n=i; ucs1_from_ucs2(bu,p2,n); break;
	}
	case 1:
	{	cUCS1Char* p1 = Ucs1()+idx;
		q=bu; 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
	{
		ptr endptr;
		f = strtod((cptr)q-1,&endptr);
		if (Error()==HUGE_VAL) ForceError(numbertoobig);
		if (idx_io) *idx_io += endptr-(ptr)bu;
		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));
		}
		if (idx_io) *idx_io += q-bu;
		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));
		}
		if (idx_io) *idx_io += q-bu;
		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++; }
		}
		if (idx_io) *idx_io += q+1-bu;
		return neg ? -f : f;
	}

x:	SetError(notanumber);
	return NAN;
}




String String::StrVal ( long* idx_io ) const
{
	long idx = idx_io ? *idx_io : 0;	// loop index
	long end = count;					// source end index

	while( idx<end && operator[](idx)<=' ' ) { idx++; }
	if( end-idx<2 || operator[](idx)!='"' ) { SetError(notastring); return emptyString; }

	long anf = ++idx;					// string start index
	bool esc = no;						// escaped characters present flag

	while(idx<end)
	{
		UCS4Char c = operator[](idx++);

		if (c=='"')
		{
			if (idx_io) *idx_io = idx;
			String s = SubString(anf,idx-1);
			if (esc) s = s.FromEscaped();
			CharSize csz  = s.Csz();
			CharSize rcsz = s.ReqCsz();
			if (rcsz==csz) return s;
			String z(s.Len(),rcsz);
			ucs_from_ucs(rcsz,z.Text(),csz,s.Text(),s.Len());
			return z;
		}

		if (c!='\\') continue;
		if (idx==end) break;
		c = operator[](idx++);
		if (idx==end) break;
		esc = yes;

// --- handling of escaped characters must match String::FromEscaped():
	// \o..  =>  o = 0..7
	// \xH.. =>  H = 0..F
	// \cX   =>  X = <any_char>
	// \<return> => discard return ((10 or 13)) and following white space  ((control codes))
	// \"  or  \\  or well known abreviation  or s.th. escaped for unknown reason

		if (c=='c') { idx++; continue; }		// \cX  and  X = <any_char>

		if (c==10||c==13)						// \<return>
		{
			while (idx<end && operator[](idx)<=' ') idx++; 
			continue;
		}

		// others: just skip 1 char: already done.
	}

	SetError(nofinalquotes);
	return emptyString;
}




/* ----	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();
		text = newdata;
		SetDataAndCsz(newdata,rcsz);
		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();
		text = newdata;
		SetDataAndCsz(newdata,rcsz);
		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;
	}
}


/* ----	convert string to uppercase ------------------------------------
		charsize is not changed except:
		if Ucs1()-size string contains "ÿ" charsize is changed to Ucs2()
*/
String String::ToUpper ( ) const
{
	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[csz2*s.count];
					ucs2_from_ucs1 ( newdata, s.text, s.count );
					s._kill();
					s.text = newdata;
					s.SetDataAndCsz(newdata,csz2);
					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;
				*ze = ucs4_char_to_upper(c);
			}
			return s;
		}
	default:
		{
			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;
				*ze = ucs4_char_to_upper(c);
			}
			return s;
		}
	}
}


/* ----	convert string to lowercase ------------------------------------
		charsize is not changed
*/
String String::ToLower ( ) const
{
	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;
				*ze = ucs4_char_to_lower(c^0x20);
			}
			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;
				*ze = ucs4_char_to_lower(c^0x20);
			}
			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 zz(count,req_charsize(n));
					ucs_from_ucs ( zz.Csz(),zz.text, csz1,text, count );
					return zz.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 zz(count,csz4);
					ucs4_from_ucs2 ( zz.Ucs4(), Ucs2(), count );
					return zz.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 ( UCS4Char quote ) const
{
	if (quote && !(quote&0xFFFFFF60)) quote='"';
	if (count==0) return quote ? String(2,quote) : emptyString;

	if(quote>0xffu)
	{
		CharSize rcsz = req_charsize(quote);
		if (rcsz>Csz())
		{
			String z(count,rcsz);
			ucs_from_ucs ( rcsz, z.text, Csz(), text, count );
			return z.ToEscaped(quote);
		}
	}

	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 (quote) *z++ = quote;

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

				if( (c&0x60) && c!=0x7f && c!='\\' && c!=quote )
				{
					*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++ = '\\';
				if(quote&&c==quote) { *z++ = c; continue; }
				ptr p = c ? strchr(cc,c) : NULL;
				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 (quote) *z++ = quote;

			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 (quote) *z++ = quote;

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

				if( (c&0xFF60) && c!=0x7f && c!='\\' && c!=quote )
				{
					*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++ = '\\';
				if(quote&&c==quote) { *z++ = c; continue; }
				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 (quote) *z++ = quote;

			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 (quote) *z++ = quote;

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

				if( (c&0xFFFFFF60) && c!=0x7f && c!='\\' && c!=quote )
				{
					*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++ = '\\';
				if(quote&&c==quote) { *z++ = c; continue; }
				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 (quote) *z++ = quote;

			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 '\"' (resp. any other passed quote char)
		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 ( UCS4Char quote ) const
{
	if (count==0) return emptyString;
	if (quote&&!(quote&0xFFFFFF60)) quote='"';
	CharSize csz = Csz();
	if (quote>0xffu&&req_charsize(quote)>csz) quote=no;
	String   s   = String(count,csz);

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

			if(quote&&count>=2&&*q==quote&&qe[-1]==quote) { 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(quote&&count>=2&&*q==quote&&qe[-1]==quote) { 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(quote&&count>=2&&*q==quote&&qe[-1]==quote) { 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;
		}
	}
}


/* ----	convert string from ucs-4 to any known encoding ---------------------------
*/
String String::ConvertedTo ( CharEncoding e ) const
{
	switch(e)
	{
	case ucs1:		// convert to UCS1. characters oorange are replaced by UCS1ReplacementChar (('?'))
		switch(Csz())
		{
		case csz1:	return *this;			// NOP
		case csz2:
			{
				String s(Len(),csz1);
				cUCS2Char*q=Ucs2();
				cUCS2Char*qe=q+Len();
				UCS1Char*z=s.Ucs1();
				while(q<qe) { UCS2Char c=*q++; *z++ = c>>8 ? UCS1ReplacementChar : c; }
				return s;
			}
		case csz4:
			{
				String s(Len(),csz1);
				cUCS4Char*q=Ucs4();
				cUCS4Char*qe=q+Len();
				UCS1Char*z=s.Ucs1();
				while(q<qe) { UCS4Char c=*q++; *z++ = c>>8 ? UCS1ReplacementChar : c; }
				return s;
			}
		}
	case ucs2:		// convert to UCS2. characters oorange are replaced by UCS2ReplacementChar (('?'))
		switch(Csz())
		{
		case csz1:							// nothing to do
		case csz2:	return *this;			// NOP
		case csz4:
			{
				String s(Len(),csz2);
				cUCS4Char*q=Ucs4();
				cUCS4Char*qe=q+Len();
				UCS2Char*z=s.Ucs2();
				while(q<qe) { UCS4Char c=*q++; *z++ = c>>16 ? UCS2ReplacementChar : c; }
				return s;
			}
		}
	case ucs4:		return *this;			// NOP
	case utf8:		{ str s = CString(); return String( (UCS1CharPtr)s, strlen(s) ); }
	case html:		return ToHtml();
	case quoted:	return ToQuoted();
	case escaped:	return ToEscaped();
	default:		NIMP(); return "";		// ***TODO***
	}
}


/* ----	convert string from any known encoding to ucs-4 ---------------------------
*/
String String::ConvertedFrom ( CharEncoding e ) const
{
	switch(e)
	{
	case ucs1:		// note: nothing to do
	case ucs2:		// note: nothing to do
	case ucs4:		return *this;			// NOP
	case utf8:		if(Csz()!=csz1) return ConvertedTo(ucs1).ConvertedFrom(utf8);
					return String( (UTF8CharPtr)Text(), Len() );
	case html:		return FromHtml();
	case quoted:	return FromQuoted();
	case escaped:	return FromEscaped();
	default:		NIMP(); return "";		// ***TODO***
	}
}



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





/* ===================================================================================
			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,csz1);
	z.Truncate( sprintf(z.Text(),"%.14g",n) );
	return z;
}

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

String NumString ( ulong n )
{
// max. 11 chars
	String z(12,csz1);
	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,csz1);
	while(digits>0)
	{
		z.Text()[--digits] = "0123456789ABCDEF"[n&0x0f];
		n >>= 4;
	}
	return z;
}
































