1. Create a new osrfListExtract function, which removes an item
[OpenSRF.git] / src / libopensrf / osrf_json_object.c
1 /*
2 Copyright (C) 2006  Georgia Public Library Service 
3 Bill Erickson <billserickson@gmail.com>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 */
15
16 #include <stdlib.h>
17 #include <ctype.h>
18 #include <errno.h>
19 #include <limits.h>
20 #include <opensrf/log.h>
21 #include <opensrf/osrf_json.h>
22 #include <opensrf/osrf_json_utils.h>
23 #include <opensrf/osrf_utf8.h>
24
25 /* cleans up an object if it is morphing another object, also
26  * verifies that the appropriate storage container exists where appropriate */
27 #define JSON_INIT_CLEAR(_obj_, newtype)         \
28         if( _obj_->type == JSON_HASH && newtype != JSON_HASH ) {                        \
29                 osrfHashFree(_obj_->value.h);                   \
30                 _obj_->value.h = NULL;                                  \
31 } else if( _obj_->type == JSON_ARRAY && newtype != JSON_ARRAY ) {       \
32                 osrfListFree(_obj_->value.l);                   \
33                 _obj_->value.l = NULL;                                  \
34 } else if( _obj_->type == JSON_STRING || _obj_->type == JSON_NUMBER ) { \
35                 free(_obj_->value.s);                                           \
36                 _obj_->value.s = NULL;                                  \
37 } \
38         _obj_->type = newtype;\
39         if( newtype == JSON_HASH && _obj_->value.h == NULL ) {  \
40                 _obj_->value.h = osrfNewHash();         \
41                 osrfHashSetCallback( _obj_->value.h, _jsonFreeHashItem ); \
42 } else if( newtype == JSON_ARRAY && _obj_->value.l == NULL ) {  \
43                 _obj_->value.l = osrfNewList();         \
44                 _obj_->value.l->freeItem = _jsonFreeListItem;\
45 }
46
47 static int unusedObjCapture = 0;
48 static int unusedObjRelease = 0;
49 static int mallocObjCreate = 0;
50 static int currentListLen = 0;
51
52 union unusedObjUnion{
53
54         union unusedObjUnion* next;
55         jsonObject obj;
56 };
57 typedef union unusedObjUnion unusedObj;
58
59 // We maintain a free list of jsonObjects that are available
60 // for use, in order to reduce the churning through
61 // malloc() and free().
62
63 static unusedObj* freeObjList = NULL;
64
65 static void add_json_to_buffer( const jsonObject* obj,
66         growing_buffer * buf, int do_classname, int second_pass );
67
68 /**
69  * Return all unused jsonObjects to the heap
70  * @return Nothing
71  */
72 void jsonObjectFreeUnused( void ) {
73
74         unusedObj* temp;
75         while( freeObjList ) {
76                 temp = freeObjList->next;
77                 free( freeObjList );
78                 freeObjList = temp;
79         }
80 }
81
82 jsonObject* jsonNewObject(const char* data) {
83
84         jsonObject* o;
85
86         if( freeObjList ) {
87                 o = (jsonObject*) freeObjList;
88                 freeObjList = freeObjList->next;
89         unusedObjRelease++;
90         currentListLen--;
91         } else {
92                 OSRF_MALLOC( o, sizeof(jsonObject) );
93         mallocObjCreate++;
94     }
95
96         o->size = 0;
97         o->classname = NULL;
98         o->parent = NULL;
99
100         if(data) {
101                 o->type = JSON_STRING;
102                 o->value.s = strdup(data);
103         } else {
104                 o->type = JSON_NULL;
105                 o->value.s = NULL;
106         }
107
108         return o;
109 }
110
111 jsonObject* jsonNewObjectFmt(const char* data, ...) {
112
113         jsonObject* o;
114
115         if( freeObjList ) {
116                 o = (jsonObject*) freeObjList;
117                 freeObjList = freeObjList->next;
118         unusedObjRelease++;
119         currentListLen--;
120         } else {
121                 OSRF_MALLOC( o, sizeof(jsonObject) );
122         mallocObjCreate++;
123     }
124
125         o->size = 0;
126         o->classname = NULL;
127         o->parent = NULL;
128
129         if(data) {
130                 VA_LIST_TO_STRING(data);
131                 o->type = JSON_STRING;
132                 o->value.s = strdup(VA_BUF);
133         }
134         else {
135                 o->type = JSON_NULL;
136                 o->value.s = NULL;
137         }
138         
139         return o;
140 }
141
142 jsonObject* jsonNewNumberObject( double num ) {
143         jsonObject* o = jsonNewObject(NULL);
144         o->type = JSON_NUMBER;
145         o->value.s = doubleToString( num );
146         return o;
147 }
148
149 /**
150  * Creates a new number object from a numeric string
151  */
152 jsonObject* jsonNewNumberStringObject( const char* numstr ) {
153         if( !numstr )
154                 numstr = "0";
155         else if( !jsonIsNumeric( numstr ) )
156                 return NULL;
157
158         jsonObject* o = jsonNewObject(NULL);
159         o->type = JSON_NUMBER;
160         o->value.s = strdup( numstr );
161         return o;
162 }
163
164 jsonObject* jsonNewBoolObject(int val) {
165     jsonObject* o = jsonNewObject(NULL);
166     o->type = JSON_BOOL;
167     jsonSetBool(o, val);
168     return o;
169 }
170
171 jsonObject* jsonNewObjectType(int type) {
172         jsonObject* o = jsonNewObject(NULL);
173         o->type = type;
174         return o;
175 }
176
177 void jsonObjectFree( jsonObject* o ) {
178
179         if(!o || o->parent) return;
180         free(o->classname);
181
182         switch(o->type) {
183                 case JSON_HASH          : osrfHashFree(o->value.h); break;
184                 case JSON_ARRAY : osrfListFree(o->value.l); break;
185                 case JSON_STRING        : free(o->value.s); break;
186                 case JSON_NUMBER        : free(o->value.s); break;
187         }
188
189         // Stick the old jsonObject onto a free list
190         // for potential reuse
191         
192         unusedObj* unused = (unusedObj*) o;
193         unused->next = freeObjList;
194         freeObjList = unused;
195
196     unusedObjCapture++;
197     currentListLen++;
198     if (unusedObjCapture > 1 && !(unusedObjCapture % 1000))
199         osrfLogDebug( OSRF_LOG_MARK, "Objects malloc()'d: %d, "
200                         "Reusable objects captured: %d, Objects reused: %d, "
201                         "Current List Length: %d",
202                         mallocObjCreate, unusedObjCapture, unusedObjRelease, currentListLen );
203 }
204
205 static void _jsonFreeHashItem(char* key, void* item){
206         if(!item) return;
207         jsonObject* o = (jsonObject*) item;
208         o->parent = NULL; /* detach the item */
209         jsonObjectFree(o);
210 }
211 static void _jsonFreeListItem(void* item){
212         if(!item) return;
213         jsonObject* o = (jsonObject*) item;
214         o->parent = NULL; /* detach the item */
215         jsonObjectFree(o);
216 }
217
218 void jsonSetBool(jsonObject* bl, int val) {
219     if(!bl) return;
220     JSON_INIT_CLEAR(bl, JSON_BOOL);
221     bl->value.b = val;
222 }
223
224 unsigned long jsonObjectPush(jsonObject* o, jsonObject* newo) {
225     if(!o) return -1;
226     if(!newo) newo = jsonNewObject(NULL);
227         JSON_INIT_CLEAR(o, JSON_ARRAY);
228         newo->parent = o;
229         osrfListPush( o->value.l, newo );
230         o->size = o->value.l->size;
231         return o->size;
232 }
233
234 unsigned long jsonObjectSetIndex(jsonObject* dest, unsigned long index, jsonObject* newObj) {
235     if(!dest) return -1;
236     if(!newObj) newObj = jsonNewObject(NULL);
237         JSON_INIT_CLEAR(dest, JSON_ARRAY);
238         newObj->parent = dest;
239         osrfListSet( dest->value.l, newObj, index );
240         dest->size = dest->value.l->size;
241         return dest->value.l->size;
242 }
243
244 unsigned long jsonObjectSetKey( jsonObject* o, const char* key, jsonObject* newo) {
245     if(!o) return -1;
246     if(!newo) newo = jsonNewObject(NULL);
247         JSON_INIT_CLEAR(o, JSON_HASH);
248         newo->parent = o;
249         osrfHashSet( o->value.h, newo, key );
250         o->size = osrfHashGetCount(o->value.h);
251         return o->size;
252 }
253
254 jsonObject* jsonObjectGetKey( jsonObject* obj, const char* key ) {
255         if(!(obj && obj->type == JSON_HASH && obj->value.h && key)) return NULL;
256         return osrfHashGet( obj->value.h, key);
257 }
258
259 const jsonObject* jsonObjectGetKeyConst( const jsonObject* obj, const char* key ) {
260         if(!(obj && obj->type == JSON_HASH && obj->value.h && key)) return NULL;
261         return osrfHashGet( obj->value.h, key);
262 }
263
264 /**
265  * Recursively traverse a jsonObject, formatting it into a JSON string.
266  *
267  * The last two parameters are booleans.
268  *
269  * If do_classname is true, examine each node for a classname, and if you
270  * find one, pretend that the node is under an extra layer of JSON_HASH, with
271  * JSON_CLASS_KEY and JSON_DATA_KEY as keys.
272  *
273  * second_pass should always be false except for some recursive calls.  It
274  * is used when expanding classnames, to distinguish between the first and
275  * second passes through a given node.
276  *
277  * @return Nothing
278  */
279 static void add_json_to_buffer( const jsonObject* obj,
280         growing_buffer * buf, int do_classname, int second_pass ) {
281
282     if(NULL == obj) {
283         OSRF_BUFFER_ADD(buf, "null");
284         return;
285     }
286
287         if( obj->classname && do_classname )
288         {
289                 if( second_pass )
290                         second_pass = 0;
291                 else
292                 {
293                         // Pretend we see an extra layer of JSON_HASH
294                         
295                         OSRF_BUFFER_ADD( buf, "{\"" );
296                         OSRF_BUFFER_ADD( buf, JSON_CLASS_KEY );
297                         OSRF_BUFFER_ADD( buf, "\":\"" );
298                         OSRF_BUFFER_ADD( buf, obj->classname );
299                         OSRF_BUFFER_ADD( buf, "\",\"" );
300                         OSRF_BUFFER_ADD( buf, JSON_DATA_KEY );
301                         OSRF_BUFFER_ADD( buf, "\":" );
302                         add_json_to_buffer( obj, buf, 1, 1 );
303                         buffer_add_char( buf, '}' );
304                         return;
305                 }
306         }
307
308         switch(obj->type) {
309
310                 case JSON_BOOL :
311                         if(obj->value.b) OSRF_BUFFER_ADD(buf, "true"); 
312                         else OSRF_BUFFER_ADD(buf, "false"); 
313                         break;
314
315         case JSON_NUMBER: {
316             if(obj->value.s) OSRF_BUFFER_ADD( buf, obj->value.s );
317             else OSRF_BUFFER_ADD_CHAR( buf, '0' );
318             break;
319         }
320
321                 case JSON_NULL:
322                         OSRF_BUFFER_ADD(buf, "null");
323                         break;
324
325                 case JSON_STRING:
326                         OSRF_BUFFER_ADD_CHAR(buf, '"');
327                         buffer_append_utf8(buf, obj->value.s);
328                         OSRF_BUFFER_ADD_CHAR(buf, '"');
329                         break;
330                         
331                 case JSON_ARRAY: {
332                         OSRF_BUFFER_ADD_CHAR(buf, '[');
333                         if( obj->value.l ) {
334                                 int i;
335                                 for( i = 0; i != obj->value.l->size; i++ ) {
336                                         if(i > 0) OSRF_BUFFER_ADD(buf, ",");
337                                         add_json_to_buffer(
338                                                 OSRF_LIST_GET_INDEX(obj->value.l, i), buf, do_classname, second_pass );
339                                 }
340                         }
341                         OSRF_BUFFER_ADD_CHAR(buf, ']');
342                         break;
343                 }
344
345                 case JSON_HASH: {
346         
347                         OSRF_BUFFER_ADD_CHAR(buf, '{');
348                         osrfHashIterator* itr = osrfNewHashIterator(obj->value.h);
349                         jsonObject* item;
350                         int i = 0;
351
352                         while( (item = osrfHashIteratorNext(itr)) ) {
353                                 if(i++ > 0) OSRF_BUFFER_ADD_CHAR(buf, ',');
354                                 OSRF_BUFFER_ADD_CHAR(buf, '"');
355                                 buffer_append_utf8(buf, osrfHashIteratorKey(itr));
356                                 OSRF_BUFFER_ADD(buf, "\":");
357                                 add_json_to_buffer( item, buf, do_classname, second_pass );
358                         }
359
360                         osrfHashIteratorFree(itr);
361                         OSRF_BUFFER_ADD_CHAR(buf, '}');
362                         break;
363                 }
364         }
365 }
366
367 char* jsonObjectToJSONRaw( const jsonObject* obj ) {
368         if(!obj) return NULL;
369         growing_buffer* buf = buffer_init(32);
370         add_json_to_buffer( obj, buf, 0, 0 );
371         return buffer_release( buf );
372 }
373
374 char* jsonObjectToJSON( const jsonObject* obj ) {
375         if(!obj) return NULL;
376         growing_buffer* buf = buffer_init(32);
377         add_json_to_buffer( obj, buf, 1, 0 );
378         return buffer_release( buf );
379 }
380
381 jsonIterator* jsonNewIterator(const jsonObject* obj) {
382         if(!obj) return NULL;
383         jsonIterator* itr;
384         OSRF_MALLOC(itr, sizeof(jsonIterator));
385
386         itr->obj    = (jsonObject*) obj;
387         itr->index  = 0;
388         itr->key    = NULL;
389
390         if( obj->type == JSON_HASH )
391                 itr->hashItr = osrfNewHashIterator(obj->value.h);
392         else
393                 itr->hashItr = NULL;
394         
395         return itr;
396 }
397
398 void jsonIteratorFree(jsonIterator* itr) {
399         if(!itr) return;
400         free(itr->key);
401         osrfHashIteratorFree(itr->hashItr);
402         free(itr);
403 }
404
405 jsonObject* jsonIteratorNext(jsonIterator* itr) {
406         if(!(itr && itr->obj)) return NULL;
407         if( itr->obj->type == JSON_HASH ) {
408                 if(!itr->hashItr) return NULL;
409                 jsonObject* item = osrfHashIteratorNext(itr->hashItr);
410         if(!item) return NULL;
411                 free(itr->key);
412                 itr->key = strdup( osrfHashIteratorKey(itr->hashItr) );
413                 return item;
414         } else {
415                 return jsonObjectGetIndex( itr->obj, itr->index++ );
416         }
417 }
418
419 int jsonIteratorHasNext(const jsonIterator* itr) {
420         if(!(itr && itr->obj)) return 0;
421         if( itr->obj->type == JSON_HASH )
422                 return osrfHashIteratorHasNext( itr->hashItr );
423         return (itr->index < itr->obj->size) ? 1 : 0;
424 }
425
426 jsonObject* jsonObjectGetIndex( const jsonObject* obj, unsigned long index ) {
427         if(!obj) return NULL;
428         return (obj->type == JSON_ARRAY) ? 
429         (OSRF_LIST_GET_INDEX(obj->value.l, index)) : NULL;
430 }
431
432
433
434 unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index) {
435         if( dest && dest->type == JSON_ARRAY ) {
436                 osrfListRemove(dest->value.l, index);
437                 return dest->value.l->size;
438         }
439         return -1;
440 }
441
442
443 jsonObject* jsonObjectExtractIndex(jsonObject* dest, unsigned long index) {
444         if( dest && dest->type == JSON_ARRAY ) {
445                 return osrfListExtract(dest->value.l, index);
446         } else
447                 return NULL;
448 }
449
450
451 unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key) {
452         if( dest && key && dest->type == JSON_HASH ) {
453                 osrfHashRemove(dest->value.h, key);
454                 return 1;
455         }
456         return -1;
457 }
458
459 /**
460  Allocate a buffer and format a specified numeric value into it.
461  Caller is responsible for freeing the buffer.
462 **/
463 char* doubleToString( double num ) {
464         
465         char buf[ 64 ];
466         size_t len = snprintf(buf, sizeof( buf ), "%.30g", num) + 1;
467         if( len < sizeof( buf ) )
468                 return strdup( buf );
469         else
470         {
471                 // Need a bigger buffer (should never be necessary)
472                 
473                 char* bigger_buff = safe_malloc( len + 1 );
474                 (void) snprintf(bigger_buff, len + 1, "%.30g", num);
475                 return bigger_buff;
476         }
477 }
478
479 char* jsonObjectGetString(const jsonObject* obj) {
480         if(obj)
481         {
482                 if( obj->type == JSON_STRING )
483                         return obj->value.s;
484                 else if( obj->type == JSON_NUMBER )
485                         return obj->value.s ? obj->value.s : "0";
486                 else
487                         return NULL;
488         }
489         else
490                 return NULL;
491 }
492
493 double jsonObjectGetNumber( const jsonObject* obj ) {
494         return (obj && obj->type == JSON_NUMBER && obj->value.s)
495                         ? strtod( obj->value.s, NULL ) : 0;
496 }
497
498 void jsonObjectSetString(jsonObject* dest, const char* string) {
499         if(!(dest && string)) return;
500         JSON_INIT_CLEAR(dest, JSON_STRING);
501         dest->value.s = strdup(string);
502 }
503
504 /**
505  Turn a jsonObject into a JSON_NUMBER (if it isn't already one) and store
506  a specified numeric string in it.  If the string is not numeric,
507  store the equivalent of zero, and return an error status.
508 **/
509 int jsonObjectSetNumberString(jsonObject* dest, const char* string) {
510         if(!(dest && string)) return -1;
511         JSON_INIT_CLEAR(dest, JSON_NUMBER);
512
513         if( jsonIsNumeric( string ) ) {
514                 dest->value.s = strdup(string);
515                 return 0;
516         }
517         else {
518                 dest->value.s = NULL;  // equivalent to zero
519                 return -1;
520         }
521 }
522
523 void jsonObjectSetNumber(jsonObject* dest, double num) {
524         if(!dest) return;
525         JSON_INIT_CLEAR(dest, JSON_NUMBER);
526         dest->value.s = doubleToString( num );
527 }
528
529 void jsonObjectSetClass(jsonObject* dest, const char* classname ) {
530         if(!(dest && classname)) return;
531         free(dest->classname);
532         dest->classname = strdup(classname);
533 }
534 const char* jsonObjectGetClass(const jsonObject* dest) {
535     if(!dest) return NULL;
536     return dest->classname;
537 }
538
539 jsonObject* jsonObjectClone( const jsonObject* o ) {
540     if(!o) return jsonNewObject(NULL);
541
542     int i;
543     jsonObject* arr; 
544     jsonObject* hash; 
545     jsonIterator* itr;
546     jsonObject* tmp;
547     jsonObject* result = NULL;
548
549     switch(o->type) {
550         case JSON_NULL:
551             result = jsonNewObject(NULL);
552             break;
553         case JSON_STRING:
554             result = jsonNewObject(jsonObjectGetString(o));
555             break;
556         case JSON_NUMBER:
557                         result = jsonNewObject( o->value.s );
558                         result->type = JSON_NUMBER;
559             break;
560         case JSON_BOOL:
561             result = jsonNewBoolObject(jsonBoolIsTrue((jsonObject*) o));
562             break;
563         case JSON_ARRAY:
564             arr = jsonNewObject(NULL);
565             arr->type = JSON_ARRAY;
566             for(i=0; i < o->size; i++) 
567                 jsonObjectPush(arr, jsonObjectClone(jsonObjectGetIndex(o, i)));
568             result = arr;
569             break;
570         case JSON_HASH:
571             hash = jsonNewObject(NULL);
572             hash->type = JSON_HASH;
573             itr = jsonNewIterator(o);
574             while( (tmp = jsonIteratorNext(itr)) )
575                 jsonObjectSetKey(hash, itr->key, jsonObjectClone(tmp));
576             jsonIteratorFree(itr);
577             result = hash;
578             break;
579     }
580
581     jsonObjectSetClass(result, jsonObjectGetClass(o));
582     return result;
583 }
584
585 int jsonBoolIsTrue( const jsonObject* boolObj ) {
586     if( boolObj && boolObj->type == JSON_BOOL && boolObj->value.b )
587         return 1;
588     return 0;
589 }
590
591
592 char* jsonObjectToSimpleString( const jsonObject* o ) {
593         if(!o) return NULL;
594
595         char* value = NULL;
596
597         switch( o->type ) {
598
599                 case JSON_NUMBER:
600                         value = strdup( o->value.s ? o->value.s : "0" );
601                         break;
602
603                 case JSON_STRING:
604                         value = strdup(o->value.s);
605         }
606
607         return value;
608 }
609
610 /**
611  Return 1 if the string is numeric, otherwise return 0.
612  This validation follows the rules defined by the grammar at:
613  http://www.json.org/
614  **/
615 int jsonIsNumeric( const char* s ) {
616
617         if( !s || !*s ) return 0;
618
619         const char* p = s;
620
621         // skip leading minus sign, if present (leading plus sign not allowed)
622
623         if( '-' == *p )
624                 ++p;
625
626         // There must be at least one digit to the left of the decimal
627
628         if( isdigit( (unsigned char) *p ) ) {
629                 if( '0' == *p++ ) {
630
631                         // If the first digit is zero, it must be the
632                         // only digit to the lerft of the decimal
633
634                         if( isdigit( (unsigned char) *p ) )
635                                 return 0;
636                 }
637                 else {
638
639                         // Skip oer the following digits
640
641                         while( isdigit( (unsigned char) *p ) ) ++p;
642                 }
643         }
644         else
645                 return 0;
646
647         if( !*p )
648                 return 1;             // integer
649
650         if( '.' == *p ) {
651
652                 ++p;
653
654                 // If there is a decimal point, there must be
655                 // at least one digit to the right of it
656
657                 if( isdigit( (unsigned char) *p ) )
658                         ++p;
659                 else
660                         return 0;
661
662                 // skip over contiguous digits
663
664                 while( isdigit( (unsigned char) *p ) ) ++p;
665         }
666
667         if( ! *p )
668                 return 1;  // decimal fraction, no exponent
669         else if( *p != 'e' && *p != 'E' )
670                 return 0;  // extra junk, no exponent
671         else
672                 ++p;
673
674         // If we get this far, we have the beginnings of an exponent.
675         // Skip over optional sign of exponent.
676
677         if( '-' == *p || '+' == *p )
678                 ++p;
679
680         // There must be at least one digit in the exponent
681         
682         if( isdigit( (unsigned char) *p ) )
683                 ++p;
684         else
685                 return 0;
686
687         // skip over contiguous digits
688
689         while( isdigit( (unsigned char) *p ) ) ++p;
690
691         if( *p )
692                 return 0;  // extra junk
693         else
694                 return 1;  // number with exponent
695 }
696
697 /**
698  Allocate and reformat a numeric string into one that is valid
699  by JSON rules.  If the string is not numeric, return NULL.
700  Caller is responsible for freeing the buffer.
701  **/
702 char* jsonScrubNumber( const char* s ) {
703         if( !s || !*s ) return NULL;
704
705         growing_buffer* buf = buffer_init( 64 );
706
707         // Skip leading white space, if present
708
709         while( isspace( (unsigned char) *s ) ) ++s;
710
711         // Skip leading plus sign, if present, but keep a minus
712
713         if( '-' == *s )
714         {
715                 buffer_add_char( buf, '-' );
716                 ++s;
717         }
718         else if( '+' == *s )
719                 ++s;
720
721         if( '\0' == *s ) {
722                 // No digits found
723
724                 buffer_free( buf );
725                 return NULL;
726         }
727         // Skip any leading zeros
728
729         while( '0' == *s ) ++s;
730
731         // Capture digits to the left of the decimal,
732         // and note whether there are any.
733
734         int left_digit = 0;  // boolean
735
736         if( isdigit( (unsigned char) *s ) ) {
737                 buffer_add_char( buf, *s++ );
738                 left_digit = 1;
739         }
740         
741         while( isdigit( (unsigned char) *s  ) )
742                 buffer_add_char( buf, *s++ );
743
744         // Now we expect to see a decimal point,
745         // an exponent, or end-of-string.
746
747         switch( *s )
748         {
749                 case '\0' :
750                         break;
751                 case '.' :
752                 {
753                         // Add a single leading zero, if we need to
754
755                         if( ! left_digit )
756                                 buffer_add_char( buf, '0' );
757                         buffer_add_char( buf, '.' );
758                         ++s;
759
760                         if( ! left_digit && ! isdigit( (unsigned char) *s ) )
761                         {
762                                 // No digits on either side of decimal
763
764                                 buffer_free( buf );
765                                 return NULL;
766                         }
767
768                         // Collect digits to right of decimal
769
770                         while( isdigit( (unsigned char) *s ) )
771                                 buffer_add_char( buf, *s++ );
772
773                         break;
774                 }
775                 case 'e' :
776                 case 'E' :
777
778                         // Exponent; we'll deal with it later, but
779                         // meanwhile make sure we have something
780                         // to its left
781
782                         if( ! left_digit )
783                                 buffer_add_char( buf, '1' );
784                         break;
785                 default :
786
787                         // Unexpected character; bail out
788
789                         buffer_free( buf );
790                         return NULL;
791         }
792
793         if( '\0' == *s )    // Are we done yet?
794                 return buffer_release( buf );
795
796         if( 'e' != *s && 'E' != *s ) {
797
798                 // Unexpected character: bail out
799
800                 buffer_free( buf );
801                 return NULL;
802         }
803
804         // We have an exponent.  Load the e or E,
805         // and the sign if there is one.
806
807         buffer_add_char( buf, *s++ );
808
809         if( '+' == *s || '-' == *s )
810                 buffer_add_char( buf, *s++ );
811
812         // Collect digits of the exponent
813
814         while( isdigit( (unsigned char) *s ) )
815                 buffer_add_char( buf, *s++ );
816
817         // There better not be anything left
818
819         if( *s ) {
820                 buffer_free( buf );
821                 return NULL;
822         }
823
824         return buffer_release( buf );
825 }