/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************************ The printf-like implementation in this file is based on the one found in the sqlite3 distribution is in the Public Domain. This copy was forked for use with the clob API in Feb 2008 by Stephan Beal (http://wanderinghorse.net/home/stephan/) and modified to send its output to arbitrary targets via a callback mechanism. Also refactored the %X specifier handlers a bit to make adding/removing specific handlers easier. All code in this file is released into the Public Domain. The printf implementation (fsl_appendfv()) is pretty easy to extend (e.g. adding or removing %-specifiers for fsl_appendfv()) if you're willing to poke around a bit and see how the specifiers are declared and dispatched. For an example, grep for 'etSTRING' and follow it through the process of declaration to implementation. See below for several FSLPRINTF_OMIT_xxx macros which can be set to remove certain features/extensions. **********************************************************************/ #include "fossil-scm/fossil-util.h" #include /* strlen() */ #include #include /* FIXME: determine this type at compile time via configuration options. OTOH, it compiles everywhere as-is so far. */ typedef long double LONGDOUBLE_TYPE; /* If FSLPRINTF_OMIT_FLOATING_POINT is defined to a true value, then floating point conversions are disabled. */ #ifndef FSLPRINTF_OMIT_FLOATING_POINT # define FSLPRINTF_OMIT_FLOATING_POINT 0 #endif /* If FSLPRINTF_OMIT_SIZE is defined to a true value, then the %n specifier is disabled. */ #ifndef FSLPRINTF_OMIT_SIZE # define FSLPRINTF_OMIT_SIZE 0 #endif /* If FSLPRINTF_OMIT_SQL is defined to a true value, then the %q, %Q, and %B specifiers are disabled. */ #ifndef FSLPRINTF_OMIT_SQL # define FSLPRINTF_OMIT_SQL 0 #endif /* If FSLPRINTF_OMIT_HTML is defined to a true value then the %h (HTML escape), %t (URL escape), and %T (URL unescape) specifiers are disabled. */ #ifndef FSLPRINTF_OMIT_HTML # define FSLPRINTF_OMIT_HTML 0 #endif /* Most C compilers handle variable-sized arrays, so we enable that by default. Some (e.g. tcc) do not, so we provide a way to disable it: set FSLPRINTF_HAVE_VARARRAY to 0 One approach would be to look at: defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) but some compilers support variable-sized arrays even when not explicitly running in c99 mode. */ #if !defined(FSLPRINTF_HAVE_VARARRAY) # if defined(__TINYC__) # define FSLPRINTF_HAVE_VARARRAY 0 # else # if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # define FSLPRINTF_HAVE_VARARRAY 1 /*use 1 in C99 mode */ # else # define FSLPRINTF_HAVE_VARARRAY 0 # endif # endif #endif /* Conversion types fall into various categories as defined by the following enumeration. */ enum PrintfCategory {etRADIX = 1, /* Integer types. %d, %x, %o, and so forth */ etFLOAT = 2, /* Floating point. %f */ etEXP = 3, /* Exponentional notation. %e and %E */ etGENERIC = 4, /* Floating or exponential, depending on exponent. %g */ etSIZE = 5, /* Return number of characters processed so far. %n */ etSTRING = 6, /* Strings. %s */ etDYNSTRING = 7, /* Dynamically allocated strings. %z */ etPERCENT = 8, /* Percent symbol. %% */ etCHARX = 9, /* Characters. %c */ /* The rest are extensions, not normally found in printf() */ etCHARLIT = 10, /* Literal characters. %' */ #if !FSLPRINTF_OMIT_SQL etSQLESCAPE = 11, /* Strings with '\'' doubled. %q */ etSQLESCAPE2 = 12, /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ etSQLESCAPE3 = 16, /* %w -> Strings with '\"' doubled */ etBLOBSQL = 13, /* %B -> Works like %Q, but requires a (fsl_buffer*) argument. */ #endif /* !FSLPRINTF_OMIT_SQL */ etPOINTER = 15, /* The %p conversion */ etORDINAL = 17, /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ #if ! FSLPRINTF_OMIT_HTML etHTML = 18, /* %h -> basic HTML escaping. */ etURLENCODE = 19, /* %t -> URL encoding. */ etURLDECODE = 20, /* %T -> URL decoding. */ #endif etPATH = 21, /* %/ -> replace '\\' with '/' in path-like strings. */ etBLOB = 22, /* Works like %s, but requires a (fsl_buffer*) argument. */ etFOSSILIZE = 23, /* %F => like %s, but fossilizes it. */ etSTRINGID = 24 /* String with length limit for a UUID prefix: %S */, etPLACEHOLDER = 100 }; /* An "etByte" is an 8-bit unsigned value. */ typedef unsigned char etByte; /* Each builtin conversion character (ex: the 'd' in "%d") is described by an instance of the following structure */ typedef struct et_info { /* Information about each format field */ char fmttype; /* The format field code letter */ etByte base; /* The base for radix conversion */ etByte flags; /* One or more of FLAG_ constants below */ etByte type; /* Conversion paradigm */ etByte charset; /* Offset into aDigits[] of the digits string */ etByte prefix; /* Offset into aPrefix[] of the prefix string */ } et_info; /* Allowed values for et_info.flags */ enum et_info_flags { FLAG_SIGNED = 1, /* True if the value to convert is signed */ FLAG_EXTENDED = 2, /* True if for internal/extended use only. */ FLAG_STRING = 4 /* Allow infinity precision */ }; /* Historically, the following table was searched linearly, so the most common conversions were kept at the front. Change 2008 Oct 31 by Stephan Beal: we reserve an array of ordered entries for all chars in the range [32..126]. Format character checks can now be done in constant time by addressing that array directly. This takes more static memory, but reduces the time and per-call overhead costs of fsl_appendfv(). */ static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; static const char aPrefix[] = "-x0\000X0"; static const et_info fmtinfo[] = { /** If FSLPRINTF_FMTINFO_FIXED is 1 then we use the original implementation: a linear list of entries. Search time is linear. If FSLPRINTF_FMTINFO_FIXED is 0 then we use a fixed-size array which we index directly using the format char as the key. The FSLPRINTF_FMTINFO_FIXED code is not well-maintained/well-tested. */ #define FSLPRINTF_FMTINFO_FIXED 0 #if FSLPRINTF_FMTINFO_FIXED { 'd', 10, FLAG_SIGNED, etRADIX, 0, 0 }, { 's', 0, FLAG_STRING, etSTRING, 0, 0 }, { 'g', 0, FLAG_SIGNED, etGENERIC, 30, 0 }, { 'z', 0, FLAG_STRING, etDYNSTRING, 0, 0 }, { 'c', 0, 0, etCHARX, 0, 0 }, { 'o', 8, 0, etRADIX, 0, 2 }, { 'u', 10, 0, etRADIX, 0, 0 }, { 'x', 16, 0, etRADIX, 16, 1 }, { 'X', 16, 0, etRADIX, 0, 4 }, { 'i', 10, FLAG_SIGNED, etRADIX, 0, 0 }, #if !FSLPRINTF_OMIT_FLOATING_POINT { 'f', 0, FLAG_SIGNED, etFLOAT, 0, 0 }, { 'e', 0, FLAG_SIGNED, etEXP, 30, 0 }, { 'E', 0, FLAG_SIGNED, etEXP, 14, 0 }, { 'G', 0, FLAG_SIGNED, etGENERIC, 14, 0 }, #endif /* !FSLPRINTF_OMIT_FLOATING_POINT */ { '%', 0, 0, etPERCENT, 0, 0 }, { 'p', 16, 0, etPOINTER, 0, 1 }, { 'r', 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL, 0, 0 }, #if ! FSLPRINTF_OMIT_SQL { 'q', 0, FLAG_STRING, etSQLESCAPE, 0, 0 }, { 'Q', 0, FLAG_STRING, etSQLESCAPE2, 0, 0 }, { 'B', 0, FLAG_EXTENDED, etBLOBSQL, 0, 0 }, { 'w', 0, FLAG_STRING, etSQLESCAPE3, 0, 0 }, #endif /* !FSLPRINTF_OMIT_SQL */ #if ! FSLPRINTF_OMIT_HTML { 'h', 0, FLAG_STRING, etHTML, 0, 0 }, { 't', 0, FLAG_STRING, etURLENCODE, 0, 0 }, { 'T', 0, FLAG_STRING, etURLDECODE, 0, 0 }, #endif /* !FSLPRINTF_OMIT_HTML */ #if !FSLPRINTF_OMIT_SIZE { 'n', 0, 0, etSIZE, 0, 0 }, #endif { '/', 0, 0, etPATH, 0, 0 }, { 'b', 0, FLAG_EXTENDED, etBLOB, 0, 0 }, { 'S', 0, FLAG_STRING, etSTRINGID, 0, 0 }, #else /* FSLPRINTF_FMTINFO_FIXED */ /* These entries MUST stay in ASCII order, sorted on their fmttype member! They MUST start with fmttype==32 and end at fmttype==126. */ {' '/*32*/, 0, 0, 0, 0, 0 }, {'!'/*33*/, 0, 0, 0, 0, 0 }, {'"'/*34*/, 0, 0, 0, 0, 0 }, {'#'/*35*/, 0, 0, 0, 0, 0 }, {'$'/*36*/, 0, 0, 0, 0, 0 }, {'%'/*37*/, 0, 0, etPERCENT, 0, 0 }, {'&'/*38*/, 0, 0, 0, 0, 0 }, {'\''/*39*/, 0, 0, 0, 0, 0 }, {'('/*40*/, 0, 0, 0, 0, 0 }, {')'/*41*/, 0, 0, 0, 0, 0 }, {'*'/*42*/, 0, 0, 0, 0, 0 }, {'+'/*43*/, 0, 0, 0, 0, 0 }, {','/*44*/, 0, 0, 0, 0, 0 }, {'-'/*45*/, 0, 0, 0, 0, 0 }, {'.'/*46*/, 0, 0, 0, 0, 0 }, {'/'/*47*/, 0, 0, etPATH, 0, 0 }, {'0'/*48*/, 0, 0, 0, 0, 0 }, {'1'/*49*/, 0, 0, 0, 0, 0 }, {'2'/*50*/, 0, 0, 0, 0, 0 }, {'3'/*51*/, 0, 0, 0, 0, 0 }, {'4'/*52*/, 0, 0, 0, 0, 0 }, {'5'/*53*/, 0, 0, 0, 0, 0 }, {'6'/*54*/, 0, 0, 0, 0, 0 }, {'7'/*55*/, 0, 0, 0, 0, 0 }, {'8'/*56*/, 0, 0, 0, 0, 0 }, {'9'/*57*/, 0, 0, 0, 0, 0 }, {':'/*58*/, 0, 0, 0, 0, 0 }, {';'/*59*/, 0, 0, 0, 0, 0 }, {'<'/*60*/, 0, 0, 0, 0, 0 }, {'='/*61*/, 0, 0, 0, 0, 0 }, {'>'/*62*/, 0, 0, 0, 0, 0 }, {'?'/*63*/, 0, 0, 0, 0, 0 }, {'@'/*64*/, 0, 0, 0, 0, 0 }, {'A'/*65*/, 0, 0, 0, 0, 0 }, #if FSLPRINTF_OMIT_SQL {'B'/*66*/, 0, 0, 0, 0, 0 }, #else {'B'/*66*/, 0, 2, etBLOBSQL, 0, 0 }, #endif {'C'/*67*/, 0, 0, 0, 0, 0 }, {'D'/*68*/, 0, 0, 0, 0, 0 }, {'E'/*69*/, 0, FLAG_SIGNED, etEXP, 14, 0 }, {'F'/*70*/, 0, 4, etFOSSILIZE, 0, 0 }, {'G'/*71*/, 0, FLAG_SIGNED, etGENERIC, 14, 0 }, {'H'/*72*/, 0, 0, 0, 0, 0 }, {'I'/*73*/, 0, 0, 0, 0, 0 }, {'J'/*74*/, 0, 0, 0, 0, 0 }, {'K'/*75*/, 0, 0, 0, 0, 0 }, {'L'/*76*/, 0, 0, 0, 0, 0 }, {'M'/*77*/, 0, 0, 0, 0, 0 }, {'N'/*78*/, 0, 0, 0, 0, 0 }, {'O'/*79*/, 0, 0, 0, 0, 0 }, {'P'/*80*/, 0, 0, 0, 0, 0 }, #if FSLPRINTF_OMIT_SQL {'Q'/*81*/, 0, 0, 0, 0, 0 }, #else {'Q'/*81*/, 0, FLAG_STRING, etSQLESCAPE2, 0, 0 }, #endif {'R'/*82*/, 0, 0, 0, 0, 0 }, {'S'/*83*/, 0, FLAG_STRING, etSTRINGID, 0, 0 }, {'T'/*84*/, 0, FLAG_STRING, etURLDECODE, 0, 0 }, {'U'/*85*/, 0, 0, 0, 0, 0 }, {'V'/*86*/, 0, 0, 0, 0, 0 }, {'W'/*87*/, 0, 0, 0, 0, 0 }, {'X'/*88*/, 16, 0, etRADIX, 0, 4 }, {'Y'/*89*/, 0, 0, 0, 0, 0 }, {'Z'/*90*/, 0, 0, 0, 0, 0 }, {'['/*91*/, 0, 0, 0, 0, 0 }, {'\\'/*92*/, 0, 0, 0, 0, 0 }, {']'/*93*/, 0, 0, 0, 0, 0 }, {'^'/*94*/, 0, 0, 0, 0, 0 }, {'_'/*95*/, 0, 0, 0, 0, 0 }, {'`'/*96*/, 0, 0, 0, 0, 0 }, {'a'/*97*/, 0, 0, 0, 0, 0 }, {'b'/*98*/, 0, 2, etBLOB, 0, 0 }, {'c'/*99*/, 0, 0, etCHARX, 0, 0 }, {'d'/*100*/, 10, FLAG_SIGNED, etRADIX, 0, 0 }, {'e'/*101*/, 0, FLAG_SIGNED, etEXP, 30, 0 }, {'f'/*102*/, 0, FLAG_SIGNED, etFLOAT, 0, 0}, {'g'/*103*/, 0, FLAG_SIGNED, etGENERIC, 30, 0 }, {'h'/*104*/, 0, FLAG_STRING, etHTML, 0, 0 }, {'i'/*105*/, 10, FLAG_SIGNED, etRADIX, 0, 0}, {'j'/*106*/, 0, 0, 0, 0, 0 }, {'k'/*107*/, 0, 0, 0, 0, 0 }, {'l'/*108*/, 0, 0, 0, 0, 0 }, {'m'/*109*/, 0, 0, 0, 0, 0 }, {'n'/*110*/, 0, 0, etSIZE, 0, 0 }, {'o'/*111*/, 8, 0, etRADIX, 0, 2 }, {'p'/*112*/, 16, 0, etPOINTER, 0, 1 }, #if FSLPRINTF_OMIT_SQL {'q'/*113*/, 0, 0, 0, 0, 0 }, #else {'q'/*113*/, 0, FLAG_STRING, etSQLESCAPE, 0, 0 }, #endif {'r'/*114*/, 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL, 0, 0}, {'s'/*115*/, 0, FLAG_STRING, etSTRING, 0, 0 }, {'t'/*116*/, 0, FLAG_STRING, etURLENCODE, 0, 0 }, {'u'/*117*/, 10, 0, etRADIX, 0, 0 }, {'v'/*118*/, 0, 0, 0, 0, 0 }, #if FSLPRINTF_OMIT_SQL {'w'/*119*/, 0, 0, 0, 0, 0 }, #else {'w'/*119*/, 0, FLAG_STRING, etSQLESCAPE3, 0, 0 }, #endif {'x'/*120*/, 16, 0, etRADIX, 16, 1 }, {'y'/*121*/, 0, 0, 0, 0, 0 }, {'z'/*122*/, 0, FLAG_STRING, etDYNSTRING, 0, 0}, {'{'/*123*/, 0, 0, 0, 0, 0 }, {'|'/*124*/, 0, 0, 0, 0, 0 }, {'}'/*125*/, 0, 0, 0, 0, 0 }, {'~'/*126*/, 0, 0, 0, 0, 0 }, #endif /* FSLPRINTF_FMTINFO_FIXED */ }; #define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0])) #if ! FSLPRINTF_OMIT_FLOATING_POINT /* "*val" is a double such that 0.1 <= *val < 10.0 Return the ascii code for the leading digit of *val, then multiply "*val" by 10.0 to renormalize. Example: input: *val = 3.14159 output: *val = 1.4159 function return = '3' The counter *cnt is incremented each time. After counter exceeds 16 (the number of significant digits in a 64-bit float) '0' is always returned. */ static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ int digit; LONGDOUBLE_TYPE d; if( (*cnt)++ >= 16 ) return '0'; digit = (int)*val; d = digit; digit += '0'; *val = (*val - d)*10.0; return digit; } #endif /* !FSLPRINTF_OMIT_FLOATING_POINT */ /* On machines with a small(?) stack size, you can redefine the FSLPRINTF_BUF_SIZE to be less than 350. But beware - for smaller values some %f conversions may go into an infinite loop. */ #ifndef FSLPRINTF_BUF_SIZE # define FSLPRINTF_BUF_SIZE 350 /* Size of the output buffer for numeric conversions */ #endif #if defined(FSL_INT_T_PFMT) /* int64_t is already defined. */ #else #if ! defined(__STDC__) && !defined(__TINYC__) #ifdef FSLPRINTF_INT64_TYPE typedef FSLPRINTF_INT64_TYPE int64_t; typedef unsigned FSLPRINTF_INT64_TYPE uint64_t; #elif defined(_MSC_VER) || defined(__BORLANDC__) typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #else typedef long long int int64_t; typedef unsigned long long int uint64_t; #endif #endif #endif /* Set up of int64 type */ #if 0 / Not yet used. */ enum PrintfArgTypes { TypeInt = 0, TypeIntP = 1, TypeFloat = 2, TypeFloatP = 3, TypeCString = 4 }; #endif #if 0 / Not yet used. */ typedef struct fsl_appendf_spec_handler_def { char letter; / e.g. %s */ int xtype; /* reference to the etXXXX values, or fmtinfo[*].type. */ int ntype; /* reference to PrintfArgTypes enum. */ } spec_handler; #endif /** fsl_appendf_spec_handler is an almost-generic interface for farming work out of fsl_appendfv()'s code into external functions. It doesn't actually save much (if any) overall code, but it makes the fsl_appendfv() code more manageable. REQUIREMENTS of implementations: - Expects an implementation-specific vargp pointer. fsl_appendfv() passes a pointer to the converted value of an entry from the format va_list. If it passes a type other than the expected one, undefined results. - If it calls pf then it must return the return value from that function. - If it calls pf it must do: pf( pfArg, D, N ), where D is the data to export and N is the number of bytes to export. It may call pf() an arbitrary number of times - If pf() successfully is called, the return value must be the accumulated totals of its return value(s), plus (possibly, but unlikely) an imnplementation-specific amount. - If it does not call pf() then it must return 0 (success) or a negative number (an error) or do all of the export processing itself and return the number of bytes exported. SIGNIFICANT LIMITATIONS: - Has no way of iterating over the format string, so handling precisions and such here can't work too well. (Nevermind: precision/justification is handled in fsl_appendfv().) */ typedef fsl_int_t (*fsl_appendf_spec_handler)( fsl_appendf_f pf, void * pfArg, unsigned int pfLen, void * vargp ); /** fsl_appendf_spec_handler for etSTRING types. It assumes that varg is a NUL-terminated (char [const] *) */ static fsl_int_t spech_string( fsl_appendf_f pf, void * pfArg, unsigned int pfLen, void * varg ) { char const * ch = (char const *) varg; return ch ? pf( pfArg, ch, pfLen ) : 0; } /** fsl_appendf_spec_handler for etDYNSTRING types. It assumes that varg is a non-const (char *). It behaves identically to spec_string() and then calls fsl_free() on that (char *). */ static fsl_int_t spech_dynstring( fsl_appendf_f pf, void * pfArg, unsigned int pfLen, void * varg ) { fsl_int_t ret = spech_string( pf, pfArg, pfLen, varg ); fsl_free( varg ); return ret; } #if !FSLPRINTF_OMIT_HTML static fsl_int_t spech_string_to_html( fsl_appendf_f pf, void * pfArg, unsigned int pfLen, void * varg ) { char const * ch = (char const *) varg; unsigned int i; fsl_int_t ret = 0; if( ! ch ) return 0; ret = 0; for( i = 0; (i= 32 && c <=47) || ( c>=58 && c<=64) || ( c>=91 && c<=96) || ( c>=123 && c<=126) || ( c<32 || c>=127) ); } /** The handler for the etURLENCODE specifier. It expects varg to be a string value, which it will preceed to encode using an URL encoding algothrim (certain characters are converted to %XX, where XX is their hex value) and passes the encoded string to pf(). It returns the total length of the output string. */ static fsl_int_t spech_urlencode( fsl_appendf_f pf, void * pfArg, unsigned int pfLen, void * varg ) { char const * str = (char const *) varg; fsl_int_t ret = 0; char ch = 0; char const * hex = "0123456789ABCDEF"; #define xbufsz 10 char xbuf[xbufsz]; int slen = 0; if( ! str ) return 0; memset( xbuf, 0, xbufsz ); ch = *str; #define xbufsz 10 slen = 0; for( ; ch; ch = *(++str) ) { if( ! httpurl_needs_escape( ch ) ) { ret += pf( pfArg, str, 1 ); continue; } else { xbuf[0] = '%'; xbuf[1] = hex[((ch>>4)&0xf)]; xbuf[2] = hex[(ch&0xf)]; xbuf[3] = 0; slen = 3; ret += pf( pfArg, xbuf, slen ); } } #undef xbufsz return ret; } /* hexchar_to_int(): For 'a'-'f', 'A'-'F' and '0'-'9', returns the appropriate decimal number. For any other character it returns -1. */ static int hexchar_to_int( int ch ) { if( (ch>='0' && ch<='9') ) return ch-'0'; else if( (ch>='a' && ch<='f') ) return ch-'a'+10; else if( (ch>='A' && ch<='F') ) return ch-'A'+10; else return -1; } /** The handler for the etURLDECODE specifier. It expects varg to be a ([const] char *), possibly encoded with URL encoding. It decodes the string using a URL decode algorithm and passes the decoded string to pf(). It returns the total length of the output string. If the input string contains malformed %XX codes then this function will return prematurely. */ static fsl_int_t spech_urldecode( fsl_appendf_f pf, void * pfArg, unsigned int pfLen, void * varg ) { char const * str = (char const *) varg; fsl_int_t ret = 0; char ch = 0; char ch2 = 0; char xbuf[4]; int decoded; if( ! str ) return 0; ch = *str; while( ch ) { if( ch == '%' ) { ch = *(++str); ch2 = *(++str); if( isxdigit((int)ch) && isxdigit((int)ch2) ) { decoded = (hexchar_to_int( ch ) * 16) + hexchar_to_int( ch2 ); xbuf[0] = (char)decoded; xbuf[1] = 0; ret += pf( pfArg, xbuf, 1 ); ch = *(++str); continue; } else { xbuf[0] = '%'; xbuf[1] = ch; xbuf[2] = ch2; xbuf[3] = 0; ret += pf( pfArg, xbuf, 3 ); ch = *(++str); continue; } } else if( ch == '+' ) { xbuf[0] = ' '; xbuf[1] = 0; ret += pf( pfArg, xbuf, 1 ); ch = *(++str); continue; } xbuf[0] = ch; xbuf[1] = 0; ret += pf( pfArg, xbuf, 1 ); ch = *(++str); } return ret; } #endif /* !FSLPRINTF_OMIT_HTML */ #if !FSLPRINTF_OMIT_SQL /** Quotes the (char *) varg as an SQL string 'should' be quoted. The exact type of the conversion is specified by xtype, which must be one of etSQLESCAPE, etSQLESCAPE2, or etSQLESCAPE3. Search this file for those constants to find the associated documentation. */ static fsl_int_t spech_sqlstring_main( int xtype, fsl_appendf_f pf, void * pfArg, unsigned int pfLen, void * varg ) { unsigned int i, n; int j, ch, isnull; int needQuote; char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ char const * escarg = (char const *) varg; char * bufpt = NULL; fsl_int_t ret; isnull = escarg==0; if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)"); for(i=n=0; (i='0' && c<='9' ){ width = width*10 + c - '0'; c = *++fmt; } } if( width > FSLPRINTF_BUF_SIZE-10 ){ width = FSLPRINTF_BUF_SIZE-10; } /* Get the precision */ if( c=='.' ){ precision = 0; c = *++fmt; if( c=='*' ){ precision = va_arg(ap,int); if( precision<0 ) precision = -precision; c = *++fmt; }else{ while( c>='0' && c<='9' ){ precision = precision*10 + c - '0'; c = *++fmt; } } }else{ precision = -1; } /* Get the conversion type modifier */ if( c=='l' ){ flag_long = 1; c = *++fmt; if( c=='l' ){ flag_longlong = 1; c = *++fmt; }else{ flag_longlong = 0; } }else{ flag_long = flag_longlong = 0; } /* Fetch the info entry for the field */ infop = 0; #if FSLPRINTF_FMTINFO_FIXED for(idx=0; idxflags & FLAG_EXTENDED)==0 ){ xtype = infop->type; }else{ FSLPRINTF_RETURN; } break; } } #else #define FMTNDX(N) (N - fmtinfo[0].fmttype) #define FMTINFO(N) (fmtinfo[ FMTNDX(N) ]) infop = ((c>=(fmtinfo[0].fmttype)) && (cfmttype,infop->type);*/ if( infop ) xtype = infop->type; #undef FMTINFO #undef FMTNDX #endif /* FSLPRINTF_FMTINFO_FIXED */ zExtra = 0; if( (!infop) || (!infop->type) ){ FSLPRINTF_RETURN; } /* Limit the precision to prevent overflowing buf[] during conversion */ if( precision>FSLPRINTF_BUF_SIZE-40 && (infop->flags & FLAG_STRING)==0 ){ precision = FSLPRINTF_BUF_SIZE-40; } /* At this point, variables are initialized as follows: flag_alternateform TRUE if a '#' is present. flag_altform2 TRUE if a '!' is present. flag_plussign TRUE if a '+' is present. flag_leftjustify TRUE if a '-' is present or if the field width was negative. flag_zeropad TRUE if the width began with 0. flag_long TRUE if the letter 'l' (ell) prefixed the conversion character. flag_longlong TRUE if the letter 'll' (ell ell) prefixed the conversion character. flag_blanksign TRUE if a ' ' is present. width The specified field width. This is always non-negative. Default is 0. precision The specified precision. The default is -1. xtype The class of the conversion. infop Pointer to the appropriate info struct. */ switch( xtype ){ case etPOINTER: flag_longlong = sizeof(char*)==sizeof(fsl_int64_t); flag_long = sizeof(char*)==sizeof(long int); /* Fall through into the next case */ case etORDINAL: case etRADIX: if( infop->flags & FLAG_SIGNED ){ fsl_int64_t v; if( flag_longlong ) v = va_arg(ap,fsl_int64_t); else if( flag_long ) v = va_arg(ap,long int); else v = va_arg(ap,int); if( v<0 ){ longvalue = -v; prefix = '-'; }else{ longvalue = v; if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; } }else{ if( flag_longlong ) longvalue = va_arg(ap,fsl_uint64_t); else if( flag_long ) longvalue = va_arg(ap,unsigned long int); else longvalue = va_arg(ap,unsigned int); prefix = 0; } if( longvalue==0 ) flag_alternateform = 0; if( flag_zeropad && precision=4 || (longvalue/10)%10==1 ){ x = 0; } buf[FSLPRINTF_BUF_SIZE-3] = zOrd[x*2]; buf[FSLPRINTF_BUF_SIZE-2] = zOrd[x*2+1]; bufpt -= 2; } { const char *cset; int base; cset = &aDigits[infop->charset]; base = infop->base; do{ /* Convert to ascii */ *(--bufpt) = cset[longvalue%base]; longvalue = longvalue/base; }while( longvalue>0 ); } length = &buf[FSLPRINTF_BUF_SIZE-1]-bufpt; for(idx=precision-length; idx>0; idx--){ *(--bufpt) = '0'; /* Zero pad */ } if( prefix ) *(--bufpt) = prefix; /* Add sign */ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ const char *pre; char x; pre = &aPrefix[infop->prefix]; if( *bufpt!=pre[0] ){ for(; (x=(*pre))!=0; pre++) *(--bufpt) = x; } } length = &buf[FSLPRINTF_BUF_SIZE-1]-bufpt; break; case etFLOAT: case etEXP: case etGENERIC: realvalue = va_arg(ap,double); #if ! FSLPRINTF_OMIT_FLOATING_POINT if( precision<0 ) precision = 6; /* Set default precision */ if( precision>FSLPRINTF_BUF_SIZE/2-10 ) precision = FSLPRINTF_BUF_SIZE/2-10; if( realvalue<0.0 ){ realvalue = -realvalue; prefix = '-'; }else{ if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; } if( xtype==etGENERIC && precision>0 ) precision--; #if 0 /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */ for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); #else /* It makes more sense to use 0.5 */ for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1){} #endif if( xtype==etFLOAT ) realvalue += rounder; /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ exp = 0; #if 1 if( (realvalue)!=(realvalue) ){ /* from sqlite3: #define sqlite3_isnan(X) ((X)!=(X)) */ /* This weird array thing is to avoid constness violations when assinging, e.g. "NaN" to bufpt. */ static char NaN[4] = {'N','a','N','\0'}; bufpt = NaN; length = 3; break; } #endif if( realvalue>0.0 ){ while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; } while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } if( exp>350 || exp<-350 ){ if( prefix=='-' ){ static char Inf[5] = {'-','I','n','f','\0'}; bufpt = Inf; }else if( prefix=='+' ){ static char Inf[5] = {'+','I','n','f','\0'}; bufpt = Inf; }else{ static char Inf[4] = {'I','n','f','\0'}; bufpt = Inf; } length = strlen(bufpt); break; } } bufpt = buf; /* If the field type is etGENERIC, then convert to either etEXP or etFLOAT, as appropriate. */ flag_exp = xtype==etEXP; if( xtype!=etFLOAT ){ realvalue += rounder; if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } } if( xtype==etGENERIC ){ flag_rtz = !flag_alternateform; if( exp<-4 || exp>precision ){ xtype = etEXP; }else{ precision = precision - exp; xtype = etFLOAT; } }else{ flag_rtz = 0; } if( xtype==etEXP ){ e2 = 0; }else{ e2 = exp; } nsd = 0; flag_dp = (precision>0) | flag_alternateform | flag_altform2; /* The sign in front of the number */ if( prefix ){ *(bufpt++) = prefix; } /* Digits prior to the decimal point */ if( e2<0 ){ *(bufpt++) = '0'; }else{ for(; e2>=0; e2--){ *(bufpt++) = et_getdigit(&realvalue,&nsd); } } /* The decimal point */ if( flag_dp ){ *(bufpt++) = '.'; } /* "0" digits after the decimal point but before the first significant digit of the number */ for(e2++; e2<0 && precision>0; precision--, e2++){ *(bufpt++) = '0'; } /* Significant digits after the decimal point */ while( (precision--)>0 ){ *(bufpt++) = et_getdigit(&realvalue,&nsd); } /* Remove trailing zeros and the "." if no digits follow the "." */ if( flag_rtz && flag_dp ){ while( bufpt[-1]=='0' ) *(--bufpt) = 0; /* assert( bufpt>buf ); */ if( bufpt[-1]=='.' ){ if( flag_altform2 ){ *(bufpt++) = '0'; }else{ *(--bufpt) = 0; } } } /* Add the "eNNN" suffix */ if( flag_exp || (xtype==etEXP && exp) ){ *(bufpt++) = aDigits[infop->charset]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; }else{ *(bufpt++) = '+'; } if( exp>=100 ){ *(bufpt++) = (exp/100)+'0'; /* 100's digit */ exp %= 100; } *(bufpt++) = exp/10+'0'; /* 10's digit */ *(bufpt++) = exp%10+'0'; /* 1's digit */ } *bufpt = 0; /* The converted number is in buf[] and zero terminated. Output it. Note that the number is in the usual order, not reversed as with integer conversions. */ length = bufpt-buf; bufpt = buf; /* Special case: Add leading zeros if the flag_zeropad flag is set and we are not left justified */ if( flag_zeropad && !flag_leftjustify && length < width){ int i; int nPad = width - length; for(i=width; i>=nPad; i--){ bufpt[i] = bufpt[i-nPad]; } i = prefix!=0; while( nPad-- ) bufpt[i++] = '0'; length = width; } #endif /* !FSLPRINTF_OMIT_FLOATING_POINT */ break; #if !FSLPRINTF_OMIT_SIZE case etSIZE: *(va_arg(ap,int*)) = outCount; length = width = 0; break; #endif case etPERCENT: buf[0] = '%'; bufpt = buf; length = 1; break; case etCHARLIT: case etCHARX: c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt); if( precision>=0 ){ for(idx=1; idx=0 && precision=0 && precision=0 && precision=0 && limit=0) && (length>limit)) length = limit; check = fsl_bytes_fossilize((unsigned char const *)bufpt, length, &fb); if(check){ fsl_buffer_reserve(&fb,0); FSLPRINTF_RETURN; } zExtra = bufpt = (char*)fb.mem /*transfer ownership*/; length = (int)fb.used; if( precision>=0 && precision0 ){ FSLPRINTF_SPACES(nspace); } } if( length>0 ){ pfrc = pfAppend( pfAppendArg, bufpt, length); FSLPRINTF_CHECKERR; } if( flag_leftjustify ){ int nspace; nspace = width-length; if( nspace>0 ){ FSLPRINTF_SPACES(nspace); } } if( zExtra ){ fsl_free(zExtra); zExtra = 0; } }/* End for loop over the format string */ FSLPRINTF_RETURN; } /* End of function */ #undef FSLPRINTF_CHARARRAY_STACK #undef FSLPRINTF_CHARARRAY #undef FSLPRINTF_CHARARRAY_FREE #undef FSLPRINTF_SPACES #undef FSLPRINTF_CHECKERR #undef FSLPRINTF_RETURN #undef FSLPRINTF_OMIT_FLOATING_POINT #undef FSLPRINTF_OMIT_SIZE #undef FSLPRINTF_OMIT_SQL #undef FSLPRINTF_BUF_SIZE #undef FSLPRINTF_OMIT_HTML fsl_int_t fsl_appendf(fsl_appendf_f pfAppend, void * pfAppendArg, const char *fmt, ... ){ fsl_int_t ret; va_list vargs; va_start( vargs, fmt ); ret = fsl_appendfv( pfAppend, pfAppendArg, fmt, vargs ); va_end(vargs); return ret; } /* fsl_appendf_f() impl which requires that state be-a writable (FILE*). */ fsl_int_t fsl_appendf_f_FILE( void * state, char const * s, fsl_int_t n ){ if( !state ) return -1; else return (1==fwrite( s, (size_t)n, 1, (FILE *)state )) ? n : -2; } fsl_int_t fsl_fprintfv( FILE * fp, char const * fmt, va_list args ){ return (fp && fmt) ? fsl_appendfv( fsl_appendf_f_FILE, fp, fmt, args ) : -1; } fsl_int_t fsl_fprintf( FILE * fp, char const * fmt, ... ){ if(!fp || !fmt) return -1; else { fsl_int_t ret; va_list vargs; va_start( vargs, fmt ); ret = fsl_appendfv( fsl_appendf_f_FILE, fp, fmt, vargs ); va_end(vargs); return ret; } } char * fsl_mprintfv( char const * fmt, va_list vargs ){ if( !fmt ) return 0; else if(!*fmt) return fsl_strndup("",0); else{ fsl_buffer buf = fsl_buffer_empty; int const rc = fsl_buffer_appendfv( &buf, fmt, vargs ); if(rc){ fsl_buffer_reserve(&buf, 0); assert(0==buf.mem); } return (char*)buf.mem /*transfer ownership*/; } } char * fsl_mprintf( char const * fmt, ... ){ char * ret; va_list vargs; va_start( vargs, fmt ); ret = fsl_mprintfv( fmt, vargs ); va_end( vargs ); return ret; } /** Internal state for fsl_snprintfv(). */ struct fsl_snp_state { /** Destination memory */ char * dest; /** Current output position in this->dest. */ fsl_size_t pos; /** Length of this->dest. */ fsl_size_t len; }; typedef struct fsl_snp_state fsl_snp_state; static fsl_int_t fsl_appendf_f_snprintf( void * arg, char const * data, fsl_int_t n ){ fsl_snp_state * st = (fsl_snp_state*) arg; assert(n>=0); if(n==0 || (st->pos >= st->len)) return 0; else if((n + st->pos) > st->len){ n = st->len - st->pos; } memcpy(st->dest + st->pos, data, (fsl_size_t)n); st->pos += n; assert(st->pos <= st->len); return n; } fsl_int_t fsl_snprintfv( char * dest, fsl_size_t n, char const * fmt, va_list args){ fsl_snp_state st = {NULL,0,0}; fsl_int_t rc; if(!dest || !fmt) return -1; else if(!n || !*fmt){ if(dest) *dest = 0; return 0; } st.len = n; st.dest = dest; rc = fsl_appendfv( fsl_appendf_f_snprintf, &st, fmt, args ); if(st.pos < st.len){ dest[st.pos] = 0; } assert( rc <= (fsl_int_t)n ); return rc; } fsl_int_t fsl_snprintf( char * dest, fsl_size_t n, char const * fmt, ... ){ fsl_int_t rc; va_list vargs; va_start( vargs, fmt ); rc = fsl_snprintfv( dest, n, fmt, vargs ); va_end( vargs ); return rc; }