]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/libopensrf/osrf_json_object.c
Performance tweak to jsonIterator.
[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         osrfHashIteratorFree(itr->hashItr);
401         free(itr);
402 }
403
404 jsonObject* jsonIteratorNext(jsonIterator* itr) {
405         if(!(itr && itr->obj)) return NULL;
406         if( itr->obj->type == JSON_HASH ) {
407                 if(!itr->hashItr)
408                         return NULL;
409
410                 jsonObject* item = osrfHashIteratorNext(itr->hashItr);
411                 if( item )
412                         itr->key = osrfHashIteratorKey(itr->hashItr);
413                 else
414                         itr->key = NULL;
415                 return item;
416         } else {
417                 return jsonObjectGetIndex( itr->obj, itr->index++ );
418         }
419 }
420
421 int jsonIteratorHasNext(const jsonIterator* itr) {
422         if(!(itr && itr->obj)) return 0;
423         if( itr->obj->type == JSON_HASH )
424                 return osrfHashIteratorHasNext( itr->hashItr );
425         return (itr->index < itr->obj->size) ? 1 : 0;
426 }
427
428 jsonObject* jsonObjectGetIndex( const jsonObject* obj, unsigned long index ) {
429         if(!obj) return NULL;
430         return (obj->type == JSON_ARRAY) ? 
431         (OSRF_LIST_GET_INDEX(obj->value.l, index)) : NULL;
432 }
433
434
435
436 unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index) {
437         if( dest && dest->type == JSON_ARRAY ) {
438                 osrfListRemove(dest->value.l, index);
439                 return dest->value.l->size;
440         }
441         return -1;
442 }
443
444
445 jsonObject* jsonObjectExtractIndex(jsonObject* dest, unsigned long index) {
446         if( dest && dest->type == JSON_ARRAY ) {
447                 return osrfListExtract(dest->value.l, index);
448         } else
449                 return NULL;
450 }
451
452
453 unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key) {
454         if( dest && key && dest->type == JSON_HASH ) {
455                 osrfHashRemove(dest->value.h, key);
456                 return 1;
457         }
458         return -1;
459 }
460
461 /**
462  Allocate a buffer and format a specified numeric value into it.
463  Caller is responsible for freeing the buffer.
464 **/
465 char* doubleToString( double num ) {
466         
467         char buf[ 64 ];
468         size_t len = snprintf(buf, sizeof( buf ), "%.30g", num) + 1;
469         if( len < sizeof( buf ) )
470                 return strdup( buf );
471         else
472         {
473                 // Need a bigger buffer (should never be necessary)
474                 
475                 char* bigger_buff = safe_malloc( len + 1 );
476                 (void) snprintf(bigger_buff, len + 1, "%.30g", num);
477                 return bigger_buff;
478         }
479 }
480
481 char* jsonObjectGetString(const jsonObject* obj) {
482         if(obj)
483         {
484                 if( obj->type == JSON_STRING )
485                         return obj->value.s;
486                 else if( obj->type == JSON_NUMBER )
487                         return obj->value.s ? obj->value.s : "0";
488                 else
489                         return NULL;
490         }
491         else
492                 return NULL;
493 }
494
495 double jsonObjectGetNumber( const jsonObject* obj ) {
496         return (obj && obj->type == JSON_NUMBER && obj->value.s)
497                         ? strtod( obj->value.s, NULL ) : 0;
498 }
499
500 void jsonObjectSetString(jsonObject* dest, const char* string) {
501         if(!(dest && string)) return;
502         JSON_INIT_CLEAR(dest, JSON_STRING);
503         dest->value.s = strdup(string);
504 }
505
506 /**
507  Turn a jsonObject into a JSON_NUMBER (if it isn't already one) and store
508  a specified numeric string in it.  If the string is not numeric,
509  store the equivalent of zero, and return an error status.
510 **/
511 int jsonObjectSetNumberString(jsonObject* dest, const char* string) {
512         if(!(dest && string)) return -1;
513         JSON_INIT_CLEAR(dest, JSON_NUMBER);
514
515         if( jsonIsNumeric( string ) ) {
516                 dest->value.s = strdup(string);
517                 return 0;
518         }
519         else {
520                 dest->value.s = NULL;  // equivalent to zero
521                 return -1;
522         }
523 }
524
525 void jsonObjectSetNumber(jsonObject* dest, double num) {
526         if(!dest) return;
527         JSON_INIT_CLEAR(dest, JSON_NUMBER);
528         dest->value.s = doubleToString( num );
529 }
530
531 void jsonObjectSetClass(jsonObject* dest, const char* classname ) {
532         if(!(dest && classname)) return;
533         free(dest->classname);
534         dest->classname = strdup(classname);
535 }
536 const char* jsonObjectGetClass(const jsonObject* dest) {
537     if(!dest) return NULL;
538     return dest->classname;
539 }
540
541 jsonObject* jsonObjectClone( const jsonObject* o ) {
542     if(!o) return jsonNewObject(NULL);
543
544     int i;
545     jsonObject* arr; 
546     jsonObject* hash; 
547     jsonIterator* itr;
548     jsonObject* tmp;
549     jsonObject* result = NULL;
550
551     switch(o->type) {
552         case JSON_NULL:
553             result = jsonNewObject(NULL);
554             break;
555         case JSON_STRING:
556             result = jsonNewObject(jsonObjectGetString(o));
557             break;
558         case JSON_NUMBER:
559                         result = jsonNewObject( o->value.s );
560                         result->type = JSON_NUMBER;
561             break;
562         case JSON_BOOL:
563             result = jsonNewBoolObject(jsonBoolIsTrue((jsonObject*) o));
564             break;
565         case JSON_ARRAY:
566             arr = jsonNewObject(NULL);
567             arr->type = JSON_ARRAY;
568             for(i=0; i < o->size; i++) 
569                 jsonObjectPush(arr, jsonObjectClone(jsonObjectGetIndex(o, i)));
570             result = arr;
571             break;
572         case JSON_HASH:
573             hash = jsonNewObject(NULL);
574             hash->type = JSON_HASH;
575             itr = jsonNewIterator(o);
576             while( (tmp = jsonIteratorNext(itr)) )
577                 jsonObjectSetKey(hash, itr->key, jsonObjectClone(tmp));
578             jsonIteratorFree(itr);
579             result = hash;
580             break;
581     }
582
583     jsonObjectSetClass(result, jsonObjectGetClass(o));
584     return result;
585 }
586
587 int jsonBoolIsTrue( const jsonObject* boolObj ) {
588     if( boolObj && boolObj->type == JSON_BOOL && boolObj->value.b )
589         return 1;
590     return 0;
591 }
592
593
594 char* jsonObjectToSimpleString( const jsonObject* o ) {
595         if(!o) return NULL;
596
597         char* value = NULL;
598
599         switch( o->type ) {
600
601                 case JSON_NUMBER:
602                         value = strdup( o->value.s ? o->value.s : "0" );
603                         break;
604
605                 case JSON_STRING:
606                         value = strdup(o->value.s);
607         }
608
609         return value;
610 }
611
612 /**
613  Return 1 if the string is numeric, otherwise return 0.
614  This validation follows the rules defined by the grammar at:
615  http://www.json.org/
616  **/
617 int jsonIsNumeric( const char* s ) {
618
619         if( !s || !*s ) return 0;
620
621         const char* p = s;
622
623         // skip leading minus sign, if present (leading plus sign not allowed)
624
625         if( '-' == *p )
626                 ++p;
627
628         // There must be at least one digit to the left of the decimal
629
630         if( isdigit( (unsigned char) *p ) ) {
631                 if( '0' == *p++ ) {
632
633                         // If the first digit is zero, it must be the
634                         // only digit to the left of the decimal
635
636                         if( isdigit( (unsigned char) *p ) )
637                                 return 0;
638                 }
639                 else {
640
641                         // Skip over the following digits
642
643                         while( isdigit( (unsigned char) *p ) ) ++p;
644                 }
645         }
646         else
647                 return 0;
648
649         if( !*p )
650                 return 1;             // integer
651
652         if( '.' == *p ) {
653
654                 ++p;
655
656                 // If there is a decimal point, there must be
657                 // at least one digit to the right of it
658
659                 if( isdigit( (unsigned char) *p ) )
660                         ++p;
661                 else
662                         return 0;
663
664                 // skip over contiguous digits
665
666                 while( isdigit( (unsigned char) *p ) ) ++p;
667         }
668
669         if( ! *p )
670                 return 1;  // decimal fraction, no exponent
671         else if( *p != 'e' && *p != 'E' )
672                 return 0;  // extra junk, no exponent
673         else
674                 ++p;
675
676         // If we get this far, we have the beginnings of an exponent.
677         // Skip over optional sign of exponent.
678
679         if( '-' == *p || '+' == *p )
680                 ++p;
681
682         // There must be at least one digit in the exponent
683         
684         if( isdigit( (unsigned char) *p ) )
685                 ++p;
686         else
687                 return 0;
688
689         // skip over contiguous digits
690
691         while( isdigit( (unsigned char) *p ) ) ++p;
692
693         if( *p )
694                 return 0;  // extra junk
695         else
696                 return 1;  // number with exponent
697 }
698
699 /**
700  Allocate and reformat a numeric string into one that is valid
701  by JSON rules.  If the string is not numeric, return NULL.
702  Caller is responsible for freeing the buffer.
703  **/
704 char* jsonScrubNumber( const char* s ) {
705         if( !s || !*s ) return NULL;
706
707         growing_buffer* buf = buffer_init( 64 );
708
709         // Skip leading white space, if present
710
711         while( isspace( (unsigned char) *s ) ) ++s;
712
713         // Skip leading plus sign, if present, but keep a minus
714
715         if( '-' == *s )
716         {
717                 buffer_add_char( buf, '-' );
718                 ++s;
719         }
720         else if( '+' == *s )
721                 ++s;
722
723         if( '\0' == *s ) {
724                 // No digits found
725
726                 buffer_free( buf );
727                 return NULL;
728         }
729         // Skip any leading zeros
730
731         while( '0' == *s ) ++s;
732
733         // Capture digits to the left of the decimal,
734         // and note whether there are any.
735
736         int left_digit = 0;  // boolean
737
738         if( isdigit( (unsigned char) *s ) ) {
739                 buffer_add_char( buf, *s++ );
740                 left_digit = 1;
741         }
742         
743         while( isdigit( (unsigned char) *s  ) )
744                 buffer_add_char( buf, *s++ );
745
746         // Now we expect to see a decimal point,
747         // an exponent, or end-of-string.
748
749         switch( *s )
750         {
751                 case '\0' :
752                         break;
753                 case '.' :
754                 {
755                         // Add a single leading zero, if we need to
756
757                         if( ! left_digit )
758                                 buffer_add_char( buf, '0' );
759                         buffer_add_char( buf, '.' );
760                         ++s;
761
762                         if( ! left_digit && ! isdigit( (unsigned char) *s ) )
763                         {
764                                 // No digits on either side of decimal
765
766                                 buffer_free( buf );
767                                 return NULL;
768                         }
769
770                         // Collect digits to right of decimal
771
772                         while( isdigit( (unsigned char) *s ) )
773                                 buffer_add_char( buf, *s++ );
774
775                         break;
776                 }
777                 case 'e' :
778                 case 'E' :
779
780                         // Exponent; we'll deal with it later, but
781                         // meanwhile make sure we have something
782                         // to its left
783
784                         if( ! left_digit )
785                                 buffer_add_char( buf, '1' );
786                         break;
787                 default :
788
789                         // Unexpected character; bail out
790
791                         buffer_free( buf );
792                         return NULL;
793         }
794
795         if( '\0' == *s )    // Are we done yet?
796                 return buffer_release( buf );
797
798         if( 'e' != *s && 'E' != *s ) {
799
800                 // Unexpected character: bail out
801
802                 buffer_free( buf );
803                 return NULL;
804         }
805
806         // We have an exponent.  Load the e or E,
807         // and the sign if there is one.
808
809         buffer_add_char( buf, *s++ );
810
811         if( '+' == *s || '-' == *s )
812                 buffer_add_char( buf, *s++ );
813
814         // Collect digits of the exponent
815
816         while( isdigit( (unsigned char) *s ) )
817                 buffer_add_char( buf, *s++ );
818
819         // There better not be anything left
820
821         if( *s ) {
822                 buffer_free( buf );
823                 return NULL;
824         }
825
826         return buffer_release( buf );
827 }