// utf-8

/*	Copyright  (c)	Günter Woigk   1995-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.

	1995-06-29 kio	started work on pascal string lib
	2000-01-28 kio	started work on c string lib
	2002-01-19 kio	started unix port
	2002-01-26 kio	simplified Using(): now using vsnprintf()
	2002-01-26 kio	added xpool[] for unlimited string length support
	2003-05-27 kio	StrToDouble()
	2003-06-07 kio	fixed problem with Using(va_list); fixed bugs in StrToDouble()
*/

#define	LOG		3
#define	SAFE	3

#include	"config.h"
#include	<math.h>
#include	"cstrings.h"

#ifdef _USE_KIOS_THREADS
#include	"disp/disp.h"
#endif

#ifdef _USE_MACZOOP
#include	"MacZoop.h"										// DB020819 15:09 
#endif


#ifndef NAN
	const double NAN = 0.0/0.0;
#endif



/* ----	allocate char[] -----------------------------------------
		deallocate with delete[]
		includes OMEM check
		presets terminating 0
*/
str NewStr ( int n )
{
	str c = new char[n+1];
	OMEM(c);
	c[n] = 0;
	return c;
}



/* ----	allocate char[] -----------------------------------------
		deallocate with delete[]
		includes OMEM check
		returns NULL if source string was NULL			29.jan.2001
*/
str NewCopy ( cstr s )
{
	str c = NULL;
	if (s)
	{
		c = NewStr(strlen(s));
		strcpy ( c, s );
	}
	return c;
}


#ifdef _MPW
/* ----	vsnprintf emulation -----------
		kio 2002-09-15 quick hack. •••TODO•••
*/
inline int vsnprintf ( str dest, size_t size, cstr format, va_list va )
{
	int bytes_written = vsprintf(dest,format,va);
	TRAP(bytes_written>size);
	return bytes_written;
}
#endif



/* ----------------------------------------------------------
			POOL HANDLING
---------------------------------------------------------- */

#ifdef _USE_KIOS_THREADS

	#define	QSPOOLSIZE	5000		// size of pool for semi-durable
	#define	QSMAXLEN	255
	
	static char pool[QSPOOLSIZE];
	static idx = 0;
	
		
	/* ----	allocate and return a string from the pool ---------------------------------
			quick and dirty hack to allocate semi-durable strings
			use them quickly, for the buffer will be re-used after some more calls ...
			intended to prepare output of numbers etc.
			the string's last byte is preset with 0
	*/
	static char* QString ( int len )
	{	int len1=len+1;
		
		TRAP(len>QSMAXLEN);
		XTRAP(idx<0||idx>QSPOOLSIZE);
	
		Tid tid = MyTid();
		if (tid)
		{
			if (!tid->qspool)
			{
				tid->qspool = new char[QSPOOLSIZE];
				XXLog( "allocating qspool for \"%s\"%s", tid->name, tid->qspool ? "" : " failed!" );
				if (!tid->qspool) goto c;
				tid->qsidx = 0;
			}
			
			if (tid->qsidx+len1>QSPOOLSIZE) tid->qsidx=0;
			tid->qsidx += len1;
			tid->qspool[tid->qsidx-1] = 0;
			XXLogOut();
			return tid->qspool+tid->qsidx-len1;
		}
		
	c:	if (idx+len1>QSPOOLSIZE) idx=0;
		idx += len1;
		pool[idx-1] = 0;
		
		XXLogOut();
		return pool+idx-len1;
	}
	


#else	/* don't _USE_KIOS_THREADS */
	// ••• TO DO: separate pool per thread! •••
	// ••• TO DO: manual flushing and/or temporary pool! •••
	
	#define QSMAXLEN		100							// max. size to go into the pool
	#define	QSDURABILITY	32							// guaranteed durability (number of QString() calls)
	#define	QSPOOLSIZE		(QSMAXLEN*QSDURABILITY)		// size of pool for semi-durable
	
	static char	 pool[QSPOOLSIZE];
	static char* poolptr = pool;
	const  char* poolend = pool+QSPOOLSIZE;
	
	static str	 xpool[QSDURABILITY];
	static str*	 xptr = xpool;
	const  str*  xend = xpool+QSDURABILITY;
	static int	 xcnt = 0;
	
	
	void FlushQSPool ( )
	{
		xcnt=0; for(xptr=xpool;xptr<xend;xptr++) if (*xptr) { delete[] *xptr; *xptr=NULL; } 
	}


	/* ----	allocate and return a string from the pool ---------------------------------
			quick and dirty hack to allocate semi-durable strings
			use them quickly, for the buffer will be re-used after some more calls ...
			intended to prepare output of numbers etc.
			the string's last byte is preset with 0
	*/
	str QString ( int len )
	{
		if (xcnt)
		{	
			xcnt--;
			if (++xptr>=xend) xptr=xpool;
			if (*xptr) { delete[] *xptr; *xptr=NULL; } 
		}
		
		if ((uint)len<QSMAXLEN)
		{
			XXXTRAP(poolptr<pool||poolptr>poolend);

			len++;
			if (poolptr+len>poolend) poolptr=pool;
			poolptr += len;
			poolptr[-1] = 0;
			return poolptr-len;
		}
		else
		{
			XTRAP(len<0);
			xcnt = QSDURABILITY;
			return *xptr = NewStr(len);
		}
	}
#endif	/* _USE_KIOS_THREADS */
	


/* ----	Create duplicate of string ---------------------------------
		Put a string in the pool
		28.dec.2000: replaces PoolString() because of same functionality
*/
str DupStr ( cstr s )
{
	if (!s||!*s) return "";			// 28.dec.2000
	int n = strlen(s);
#ifdef _USE_KIOS_THREADS			// still uses old version without xpool[]
	if (n>QSMAXLEN) n=QSMAXLEN;		// 28.dec.2000
#endif
	str c = QString(n);
	memcpy ( c,s,n );
	return c;
}



/* ----------------------------------------------------------
			METHODS
---------------------------------------------------------- */


/* ---- compare strings -------------------------------------
		NULL pointers are assumed ""
*/
bool SameStr ( cstr s, cstr t )
{
	if (s&&t)										// kio 28.dec.2000
	{
		while (*s) if (*s++!=*t++) return false;
		return *t==0;								// kio 1.apr.2000
	}
	else
	{
		return (!s||!*s)&&(!t||!*t);				// korr: kio 2002-01-20
	}
}



/* ----	search sub string ------------------------------------ kio 11.aug.2001 ----
*/
char* FindStr ( cstr target, cstr search )
{
	if (!search||!*search) return (char*)target;
	
	int t_len = StrLen(target);
	int s_len = StrLen(search);
	
	for ( int i=0; i<=t_len-s_len; i++ )
	{
		const char* s = search;
		const char* t = target+i;
		do { if (*s==0) return (char*)target+i; } while (*s++==*t++);
	}
	return NULL;	// not found
}



/* ----	search sub string ------------------------------------ kio 22.aug.2001 ----
*/
char* RFindStr ( cstr target, cstr search )
{
	int t_len = StrLen(target);
	int s_len = StrLen(search);	
	
	if (!search||!*search) return (char*)target+t_len;
	const char* se = search+s_len;

	for ( int i=t_len-s_len; i>=0; i-- )
	{
		const char* s = search;
		const char* t = target+i;
		do { if (s==se) return (char*)target+i; } while (*s++==*t++);
	}
	return NULL;	// not found
}



/* ----	Create and clear a string ---------------------------
*/
str SpaceStr ( int n, char c )
{
	if (n<=0) return "";			// kio 26.nov.2000
	str s = QString(n);
	memset ( s, c, n );
	return s;
}



/* ----	Convert pascal-style string to c-style string ------------------------------
*/
str CStr ( const uchar *p )
{	
	XTRAP(p==0);

	str s = QString(*p);
	memcpy(s,p+1,*p);
	return s;
}



// ----	Convert c type string to pascal type string --------------------------------
//		string length is limited to 255 (or QSMAXLEN)
uchar* PStr ( cstr c )
{	
	XTRAP(c==0);

	int n = StrLen(c); 
#ifdef _USE_KIOS_THREAD						// kio 2002-09-15
	if (n>MIN(QSMAXLEN,255)) n = MIN(QSMAXLEN,255);
#else
	if (n>255) n = 255;						// no limit for cstring lib, limit for p-strings only
#endif
	uchar* p = (uchar*)QString(n);
	*p=n;
	const uchar* e = p+*p;
	while (p<=e) *++p=*c++;
	return p-n-1;
}



/* ----	create blanked-out copy of string ------------------------------------------
		new: 2002-01-27 kio
*/
str WhiteStr ( cstr q, char c )
{
	str s = DupStr(q);
	for( uchar*p=(uchar*)s; *p; p++ ) if(*p>' ') *p=c;
	return s;
} 



/* ----	create string from char ----------------------------------------------------
*/
str CharStr ( char c )
{
	str s = QString(1);
	*s = c;
	return s;
}



// ----	Replace any occurance of a character by another ----------------------------
str ReplaceStr ( cstr s, char old, char nju )
{
	str t = DupStr(s);
	for ( char* p=t; *p; p++ ) if (*p==old) *p=nju;
	return t;
}



// ----	Convert a string to all lower case -----------------------------------------
str LowerStr ( cstr s )
{	
	str t = DupStr(s);
	ToLower(t);
	return t;
}



// ----	Convert a string to all upper case -----------------------------------------
str UpperStr ( cstr s )
{
	str t = DupStr(s);
	ToUpper(t);
	return t;
}



/* ----	Repeate string n times ------------------------------------ 22.aug.2001 ----
*/
char* MulStr ( cstr q, int n )
{
	if (q==NULL || *q==0 || n<=0) return "";

	int len = strlen(q);
	str s   = QString(len*n);
	char* z = s;
	
//	while (n--) z = (char*)memcpy(z,q,len);			should work acc. to man page but doesn't
	while (n--) { memcpy(z,q,len); z+=len; }

	return s;
}



/* ----	Concatenate 2 strings ------------------------------------------------------
*/
char* CatStr ( cstr s1, cstr s2 )
{
	str s = QString(StrLen(s1)+StrLen(s2));
	s[0]=0;
	if (s1) strcpy ( s, s1 );
	if (s2) strcat ( s, s2 );
	return s;
}



/* ----	Concatenate up to 6 strings -------------------------------------------------
*/
char* CatStr ( cstr s1, cstr s2, cstr s3, cstr s4, cstr s5, cstr s6 )
{
	str s = QString( StrLen(s1)+StrLen(s2)+StrLen(s3)+StrLen(s4)+StrLen(s5)+StrLen(s6) );
	s[0]=0;
	if (s1) strcpy ( s, s1 );
	if (s2) strcat ( s, s2 );
	if (s3) strcat ( s, s3 );
	if (s4) strcat ( s, s4 );
	if (s5) strcat ( s, s5 );
	if (s6) strcat ( s, s6 );
	return s;
}



/* ----	Convert number to decimal string ----------
		the string is sized to fit (max. 12 char)
*/
char* NumStr ( long n )
{
	char* s = QString(12);
	uint len = sprintf ( s, "%li", n );
	XXTRAP(len>12);
	return s;
}



/* ----	Convert floating point number to decimal string ---------- kio 15.aug.2001 ----
		the string is sized to fit
*/
char* NumStr ( double d )
{
	char* s = QString(19);
	sprintf ( s, "%.14g", d );
	return s;
}



/* ----	Convert number to hexadecimal string ----------
*/
char* HexStr ( long n, int digits )
{
	const char hex[] = "0123456789ABCDEF";
	char* c = QString(digits);

	while(digits)
	{
		c[--digits] = hex[n&0x0f];
		n >>= 4;
	}
	return c;
}



/* ----	create string [a ... [e ----------------------  1.apr.00 kio
*/
char* SubStr ( cstr a, cstr e )
{
	if (e<=a) return "";
	char* c = QString(e-a);
	memcpy ( c, a, e-a );
	return c;
}



/* ----	create left substring ------------------------
		splits n bytes from the left
		if source string is shorter, the result is shorter too
*/
char* LeftStr ( cstr s, int n )
{
//	long m = StrLen(s); if (m<n) n=m;
	const char*a=s; 		// kio 2.aug.2001
	const char*e=s+n;		// ""
	while (a<e&&*a) a++;	// ""
	n = a-s;				// ""

	if (n<=0) return "";
	char* c = QString(n);
	memcpy ( c, s, n );
	return c;
}



/* ----	create right substring ------------------------
		splits n bytes from the right
		if source string is shorter, the result is shorter too
*/
char* RightStr ( cstr s, int n )
{
	long m = StrLen(s); if (m<n) n=m;
	if (n<=0) return "";
	char* c = QString(n);
	memcpy ( c, s+m-n, n );	
	return c;
}



/* ----	create mid substring ------------------------
		skip a bytes from the left then split n bytes
		if source string is shorter, the result is shorter too
*/
char* MidStr ( cstr s, int a, int n )
{	
	if (a<0) { n+=a; a=0; }			// korr. 6.aug.2001 kio
	n = Min(n,StrLen(s)-a);
	if (n<=0) return "";
//	return LeftStr(s+a,n);
	return SubStr(s+a,s+a+n);		// kio 2002-03-29
}



/* ----	create mid-to-end substring -------------------
		kio 2002-03-29
*/
char* MidStr ( cstr s, int a )
{	
	if(!s) return "";
	if (a<0) a=0; 
	return SubStr(s+a,strchr(s,0));
}



/* ----	create formatted string ----------------------------------
		2002-01-26 kio now using vsnprintf()
*/
char* Using ( cstr format, ... )
{
	OSErr err = Error();			// vsnprintf sets spurious errors => fix

	char* s = QString(QSMAXLEN-1);	// allocate in pool, not xpool

	va_list va;
	va_start(va,format);
	uint n = vsnprintf( s, QSMAXLEN, format, va );
	va_end(va);

	if (n>=QSMAXLEN)				// didn't fit into QSMAXLEN ?
	{
		if (int(n)<0) return ""; 	// error  or  pre glibc 2.1 and output truncated

		s = QString(n);
		va_start(va,format);
		uint m = vsnprintf( s, n+1, format, va );
		va_end(va);
		if (m>=n) return "";		// error
	}

	Error() = err;					// all ok => restore errno
	return s;
}



/* ----	VaUsing() -----------------------
		2003-06-26 kio: hopefully fixed "va_list z = va;" problem
		note: caller must call va_start() and va_end() on va_list va
*/
str VaUsing ( cstr format, va_list va )
{
	str s = QString(QSMAXLEN-1);		// allocate in pool, not xpool

#if defined(_FREEBSD)||defined(_DEBIAN)||defined(_SOLARIS)
	va_list z = va;
#else
	va_list z;
	va_copy(z,va);
#endif
	int n = vsnprintf( s, QSMAXLEN, format, z );
	va_end(z);

	if (n>=QSMAXLEN)					// didn't fit into QSMAXLEN ?
	{
		#ifdef _USE_KIOS_THREADS
			// old version has no xpool[]
			// just log an error and return truncated result
			LogLine("VaUsing(): error: result too long. (truncated!)");
		#else
			s = QString(n);
			vsnprintf( s, n+1, format, va );
		#endif
	}

	return s;
}



/* ----	create beautified text for number ---------------------------
		from project vipsi
*/
str SpeakingNumber ( double n )
{
#if defined(_MACINTOSH)
	if (n>=0x7ff80000) return DupStr("2++ GB");
#endif

	if (n>=1000000000) return Using( "%7.3lf GB", n/1000000000 );
	if (n>=1000000   ) return Using( "%7.3lf MB", n/1000000 );
	if (n>=1000      ) return Using( "%7.3lf kB", n/1000 );
	                   return Using( "%4.0lf bytes", n );
}



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

static const char* StrComp(cstr a, cstr b)
{	char c,d;
	while((c=*a)&&(d=*b)&&c==d) {a++;b++; }
	return a;
}

char NextChar ( char const *& p )
{	char c;
	c = *p++; if (c!='\\') return c;	// plain char
	c = *p++;
	if( (c-'0')&~7 )					// no octal digit
	{
		cptr q = strchr(ec,c);
	 	if (c==0) p--;					// dont advance text pointer if "\" at end of text
		if (q) return cc[q-ec]; 		// control code
		return c;						// unkn. escape char / char escaped for unknown reason
	}
	else								// octal digit
	{
		uchar n = c-'0';
		if (((c=*p++-'0')&~7)==0) { n=n*8+c; if (((c=*p++-'0')&~7)==0) return n*8+c; }
		p--; return n;
	}
}


str ConvertedTo ( cstr s0, strconvtype ctype )
{	str  s;
	char c;
	int  i;
	char *q,*z;
	uchar uc;

	if (ctype==str_quoted)
	{
		s = ConvertedTo(s0,str_printable);
		return CatStr("\"",s,"\"");
	}
	
	if (!s0||!*s0) return "";
	z = s = (str)s0;
	
	if (ctype==str_html)
	{
		for ( i=0; (c=s[i]); i++ )
		{
			switch(c)
			{
			default: 	continue;
			case '<':	z = "&lt;";   break;
			case '>':	z = "&gt;";   break;
			case '&':	z = "&amp;";  break;
			case '"':	z = "&quot;"; break;		// 2002-07-30 kio: added
			}
			s = CatStr( LeftStr(s,i), z, s+i+1 ); i+=3;
		}
	}

// str_printable:
	else
	{
		while ( (uc=*z) )
		{
			if( uc>=' ' && uc!=0x7F && uc!='\\' && uc!='\"' ) { z++; continue; }
	//		if( uc>=' ' && uc<0x7F && uc!='\\' && uc!='\"' ) { z++; continue; }
	//		if( uc>=0x80&&UTF8CharValid(z)) { z=UTF8StrNextChar(z); continue; }

			if ((q=strchr(cc,uc)))
			{
				*z = ec[q-cc];
				q=s; s=CatStr(SubStr(s,z),"\\",z); z+=2+(s-q); continue;
			}
			char oc[]="\\000";
			oc[1]='0'+(uc>>6); uc&=(1<<6)-1;
			oc[2]='0'+(uc>>3); uc&=(1<<3)-1;
			oc[3]='0'+(uc>>0);
			q=s; s=CatStr(SubStr(s,z),oc,z+1); z+=4+(s-q); continue;
		}
	}

	return s!=s0 ? s : DupStr(s0);
}

str ConvertedFrom ( cstr s0, strconvtype ctype )
{	char c;
	char *q,*z;
	str  s;
	int  n;
	
	if(!s0||!*s0) return "";

	s = DupStr(s0);

	if (ctype==str_html)
	{
		q = z = strchr(s,'&');

		if(q) for(;;)
		{
			while((c=*q++)!='&') { *z++=c; if(!c) return s; }
			if (*StrComp("amp;", q)==0) { *z++ = '&'; q+=4; continue; }
			if (*StrComp("quot;",q)==0) { *z++ = '"'; q+=4; continue; }		// 2002-07-30 kio: added
			if (*StrComp("gt;",  q)==0) { *z++ = '>'; q+=3; continue; }
			if (*StrComp("lt;",  q)==0) { *z++ = '<'; q+=3; continue; }
			*z++='&';	// unquoted '&'
		}
	}
	
	else 
	{
		if (ctype==str_quoted)
		{
			n=strlen(s);
			c=s[0];
			if( n>=2 && (c=='"'||c=='\'') && s[n-1]==c )
			{	//  backslashes zählen:  ist "abc\\\\\\\\" gequoted ?
				for (q=s+n-2;*q=='\\';q--) {}
				if( (n-(q-s))&1 ) { s++; q[1]=0; }
			}
		}

	// str_printable:	
		{
			q = z = strchr(s,'\\');

			if(q) for(;;)
			{
				while((c=*q++)!='\\') { *z++=c; if(!c) return s; } 

				c=*q++; 
				if( (c-'0')&~7 )	// no octal digit
				{
					const char* p;
					if ((p=strchr(ec,c))) *z++ = cc[p-ec]; 	// control code
					else { *z++ = '\\'; q--; }				// unkn. escape char
				}
				else				// octal digit
				{
					n=c-'0';
					if (((c=*q++-'0')&~7)==0) { n=n*8+c; if (((c=*q++-'0')&~7)==0) n=n*8+c; else q--; } else q--;
					*z++ = n;
				}
			}
		}
	}

	return s;
}



/* ----	convert ascii string to numeric value --------------------------------
		=>	±123.456e±78
			±'zb.ascii'
			±$abcd.ef
			±%101010.101010

		Works according to 'strtod'.

		Wenn endptr nicht NULL ist, wird ein Zeiger auf das letzte konvertierte
		Zeichen in endptr gespeichert.

		Wenn  keine Konvertierung stattgefunden hat, wird 0 zurückgeliefert und
		der Wert von nptr an der Stelle endptr gespeichert.

		Wenn der korrekte Wert einen Überlauf verursacht, wird HUGE_VAL
		zurückgeliefert (in Abhängigkeit des Vorzeichens des Wertes) und
		ERANGE wird in errno gespeichert. Verursacht der korrekte Werte einen
		negativen Überlauf, wird 0 zurückgegeben und ERANGE wird in errno
		gespeichert.
*/
double StrToDouble ( cptr q0, cptr* endptr )
{
	cptr   q   = q0;
	char   c   = *q; while(c>0&&c<=' ')   { c = *++q; }
	bool   neg = c=='-'; if (neg||c=='+') { c = *++q; }
	cptr   e   = q0;
	cptr   a;
	double f   = 0.0;
	int	   n;

	if (_is_dec_digit(c))		// 123.456e78
	{
		f = strtod(q,(ptr*)&e);
		if(e==q) e=q0;
		if (Error()==HUGE_VAL) ForceError(numbertoobig);
		//XXLog("strtod(%s) = %.20f",SubStr(q0,e),f);
	}
	else if (c=='$')			// $abc.def
	{
		a=q+1;
		while ( (n=_digit_value(*++q))<16 ) { f = ldexp(f,4) + n; }
		if (*q=='.')
		{
			a=q+1;
			while ( (n=_digit_value(*++q))<16 ) { f = ldexp(f,4) + n; }
			f = ldexp(f,4*(a-q));
		}
		if(q>a) e = q;
	}
	else if (c=='%')			// %101010.101010
	{
		a=q+1;
		while ( (n=_digit_val(*++q))<2 ) { f = ldexp(f,1) + n; }
		if (*q=='.')
		{
			a=q+1;
			while ( (n=_digit_val(*++q))<2 ) { f = ldexp(f,1) + n; }
			f = ldexp(f,a-q);
		}
		if(q>a) e = q;
	}
	else if (c=='\'') 			// 'abcd'
	{
		while((c=*++q))
		{
			if(c=='\'') { e=++q; break; }
			if(c=='\\') { c = NextChar(q); q--; }
			f = ldexp(f,8) + (uchar)c;
		}
	}

	if(e==q0) { f=NAN; SetError(notanumber); }
	if (endptr)	*endptr = e;
	return neg ? -f : f;
}


str ParseStr ( cptr& text )
{	char c;
	cptr q = text;
	
	if (*q!='"') return NULL; else q++;
	
	while ((c=*q++))
	{
		if (c=='"') break;
		if (c!='\\') continue;
		q--; NextChar(q);
	}
	
	if (c) { str s = Unescaped(SubStr(text+1,q-1)); text = q; return s; }
	else return NULL;
}






/* ----	skip white spaces in string -------------------------------
		indexes starting with 1
		idx points to the next word or to the end of the string
void QSSkipSpace ( uchar *s, short&idx )
{	int i;
	for (i=idx; i<=*s && s[i]<=' '; i++){};
	idx=i;
}
*/

/* ----	find white space in string --------------------------
		indexes starting with 1
		idx points behind the actual word
void QSSkipWord ( uchar *s, short&idx )
{	int i;
	for (i=idx; i<=*s && s[i]>' '; i++){};
	idx=i;
}
*/

/* ----	extract next word from string -------------------------------------
		indexes starting with 1
		kio 22.5.98
uchar* QSNextWord ( uchar*s, short&idx )
{
	QSSkipSpace(s,idx);
	short a=idx;
	QSSkipWord(s,idx);
	return QSMidStr(s,a,idx-1);
}
*/

/* ----	find next occurance of a special character in string ---------------
		indexes starting with 1
		idx points to the found char or behind string
void QSFindChar ( uchar *s, uchar c, short&idx )
{	int i;

	for (i=idx; i<=*s && s[i]!=c; i++){};
	idx=i;
}
*/


/* ----- write line to file ---------------------------------------
OSErr QSWriteStr ( uchar *s, short fileID )
{	long tfu;

	tfu = *s;
	return FSWrite ( fileID, &tfu, s+1 );
}
*/

/* ----	read line from file --------------------------------------
		returns line in supplied string which must be QSMAXLEN bytes long!
		returns nil if eof or error
		forced line break after QSMAXLEN characters
OSErr QSReadStr ( uchar *s, short fileID )
{	OSErr 	err;
	long	tfu;
	uchar	*p,c1,c2;

// read as many bytes as possible
	tfu = QSMAXLEN-1;	
	err = FSRead ( fileID, &tfu, s+1 ); if (tfu==0) { *s=0; return err; };

// search cr or lf and set length byte
	s[tfu+1]=13;							// stopper
	for ( p=s+1; *p!=10&&*p!=13; p++){};	// search line break
	*s = p-(s+1);							// line length

// if 2 bytes were read beyond line end, simple cr/lf/cr+lf/lf+cr checking is possible
	if (*s<=tfu-2)
	{	if (*p+*(p+1)==23) p++;			// cr+lf / lf+cr  =>  skip both
		return SetFPos ( fileID, 3, p-s-tfu );
	}

// no cr/lf found  =>  test, whether next char from file should be appended to string
	if (*s==tfu)
	{	tfu=1;
		err = FSRead ( fileID, &tfu, p ); if (tfu==0) return err==eofErr?noErr:err;
		if (*p!=10&&*p!=13) { *s++; p++; }
		tfu=*s;
	}

// no cr/lf found  =>  test next char from file
	if (*s==tfu)
	{	tfu=1;
		err = FSRead ( fileID, &tfu, &c1 ); if (tfu==0) return err==eofErr?noErr:err;
		if (c1!=10&&c1!=13) return SetFPos ( fileID, 3, -1 );	// line too long  =>  split
	}
	else
	{	c1=*p;
	}
	
// now test next char whether it makes a cr+lf or lf+cr pair
	tfu=1;
	err = FSRead ( fileID, &tfu, &c2 ); if (tfu==0) return err==eofErr?noErr:err;
	if (c1+c2!=23) return SetFPos ( fileID, 3, -1 );	// cr or lf only  =>  skip only one
	return noErr;		// cr+lf or lf+cr  =>  skip both
}
*/

/* reformat date & time
uchar* QSDateTimeStr ( uchar* date, uchar* time )
{	
	date = QSReplaceStr(date,' ','.'); if (date[5]=='.') date=QSCatStr(QSLeftStr(date,4),QSRightStr(date,6));
	time = QSLeftStr(time,5);
	return QSCatStr(QSCatStr(date,"\p "),time);
}
*/



/* ---- Uhrzeit ------------------------------------------------------

	format = "0h0m0s0"
		wobei Nullen jeweils die Mindestzahl von Stellen angeben und
		h, m und s Stunden-, Minuten-, Sekunden- und Ticks-Felder trennen.
		
		F√ºr h wird :, f√ºr m wird : und f√ºr s wird , ausgegeben.
		
	

uchar* ClockStr ( unsigned int t, const uchar* f )
{
}
*/

/* ---- Zeitdauer ------------------------------[kio 5.1.98]---------

	format = "\p0h0m0s0"
		wobei Nullen jeweils die Mindestzahl von Stellen angeben und
		h, m und s Stunden-, Minuten-, Sekunden- und Ticks-Felder trennen.

		F√ºr m kann auch " und f√ºr s kann auch ' angegeben werden.
		Das jeweils benutzte Trennzeichen wird im Ausgabestring verwendet.
		
		Alle Zeichen, die nicht h, m, s " oder ' sind, werden wie Nullen behandelt
		und an der entsprechenden Stelle im String eingesetzt, wenn der Platz
		dort sonst leer w√§re.
		
		"0"00'00"	 ergibt z.B. "13"30'00"
		"--h00m00s0" ergibt z.B. "-4h13m04s17"
		"000h0"	 	 ergibt z.B. "001h1746"
		
		Wird NULL als Formatstring angegeben, wird ein dem Wert angepasstes Format
		variabler L√§nge benutzt.

uchar* QSDurationStr ( unsigned int d, const uchar* f )
{	int 	n;
	uchar	*p;
	uchar	*s1,*s2;

// default format	
	if ( !f ) 
	{	if ( d>=60*60*60 ) 	f = "\p0h00m00s"; else
		if ( d>=60*60 )		f = "\p0m00s"; else 
							f = "\p0s00";
	}

// runden
	switch ( f[*f] )
	{
	case 's':
	case '\"':	d += 60/2;		break;
	case 'm':
	case '\'':	d += 60*60/2; 	break;
	case 'h':	d += 60*60*60/2;break;
	}
	
// textsammler
	s1 = "\p";	

// stunden 
	p = (uchar*)memchr(f+1,'h',*f);
	if (p)
	{	n=p-(f+1);
		s2 = QSNumStr ( d/(60*60*60) ); 
		if ( *s2<n ) s2 = QSCatStr ( QSLeftStr(f,n-*s2), s2 );
		s1 = QSCatStr ( s2, "\ph" );
		d %= (60*60*60);
		f = QSRightStr ( f, n+2 );
	}

// minuten
	p = (uchar*)memchr(f+1,'m',*f); if (!p) p = (uchar*)memchr(f+1,'\'',*f);
	if (p)
	{	n=p-(f+1);
		s2 = QSNumStr ( d/(60*60) ); 
		if ( *s2<n ) s2 = QSCatStr ( QSLeftStr(f,n-*s2), s2 );
		s2 = QSCatStr ( s2, QSCharStr(*p) );
		s1 = QSCatStr ( s1,s2 );
		d %= (60*60);
		f = QSRightStr ( f, n+2 );
	}

// sekunden
	p = (uchar*)memchr(f+1,'s',*f); if (!p) p = (uchar*)memchr(f+1,'\"',*f);
	if (p)
	{	n=p-(f+1);
		s2 = QSNumStr ( d/(60) ); 
		if ( *s2<n ) s2 = QSCatStr ( QSLeftStr(f,n-*s2), s2 );
		s2 = QSCatStr ( s2, QSCharStr(*p) );
		s1 = QSCatStr ( s1,s2 );
		d %= (60);
		f = QSRightStr ( f, n+2 );
	}

// ticks
	n=*f;
	if (n)
	{	s2 = QSNumStr ( d );
		if ( *s2<n ) s2 = QSCatStr ( QSLeftStr(f,n-*s2), s2 );
		s1 = QSCatStr ( s1,s2 );
	}	

	return s1;
}
*/





