]> err.no Git - sope/blob - sope-core/NGExtensions/FdExt.subproj/NSString+Formatting.m
Drop apache 1 build-dependency
[sope] / sope-core / NGExtensions / FdExt.subproj / NSString+Formatting.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "common.h"
23 #include "NSString+Formatting.h"
24 #include "NGMemoryAllocation.h"
25
26 #if 0
27
28 #if !ISERIES
29 #  ifndef USE_VA_LIST_PTR
30 #    define USE_VA_LIST_PTR 1
31 #  endif
32 #else
33 #  define USE_VA_LIST_PTR 0
34 #endif
35
36
37 // format
38 //   %|[justification]|[fieldwidth]|[precision]|formatChar
39 //   %[-]{digit}*[.{digit}*]conv
40
41 static const char *formatChars =
42   "diouxX" // integers
43   "feEgG"  // double
44   "c"      // int arg -> uchar
45   "s"      // string
46   "@"      // object
47   "$"      // string object
48 ;
49 static const char *formatAttrs = "#0- +,";
50 static const char *formatConv  = "hlLqZ";
51 static Class StrClass = Nil;
52
53 typedef enum {
54   DEFAULT_TYPE = 0,
55   SHORT_TYPE,
56   LONG_TYPE,
57   VERY_LONG_TYPE
58 } NGFormatType;
59
60 // implementation
61
62 static inline NSString *
63 wideString(const char *str, int width, char fillChar, BOOL isRightAligned)
64 {
65   unsigned char *tmp;
66   register int cnt;
67   int len;
68   
69   if (StrClass == Nil) StrClass = [NSString class];
70   
71   if (width <= 0)
72     return str ? [StrClass stringWithCString:str] : nil;
73
74   if ((len = strlen(str)) >= width)
75     return str ? [StrClass stringWithCString:str] : nil;
76
77   tmp = malloc(width + 2);
78   if (isRightAligned) { // right aligned
79     for (cnt = 0; cnt < (width - len); cnt++)
80       tmp[cnt] = fillChar;
81     strncpy(tmp + (width - len), str, len);
82     tmp[width] = '\0';
83   }
84   else { // left aligned
85     for (cnt = len; cnt < width; cnt++)
86       tmp[cnt] = fillChar;
87     strncpy(tmp, str, len);
88     tmp[cnt] = '\0';
89   }
90   
91   return [[[StrClass alloc]
92                      initWithCStringNoCopy:tmp length:strlen(tmp)
93                      freeWhenDone:YES] autorelease];
94 }
95
96 static inline NSString *xsSimpleFormatObject(char *fmt, id _value, NSString *_ds) {
97   /*
98     formats an object value in an simple format
99     (this is only the '%{mods}{type}' part of a real
100     format string !)
101     The method to get the value of the object is
102     determined by the {type} of the Format,
103     eg: i becomes intValue, @ becomes description ..
104
105     the decimal point in float-strings can be replaced by the _ds
106     String. To do this, the result of the Format is scanned for
107     '.' and _ds is expanded for this.
108   */
109   int  fmtLen  = strlen(fmt);
110   char fmtChar = fmt[fmtLen - 1];
111   NGFormatType typeMode = DEFAULT_TYPE;
112   int  width      = -1;
113   int  prec       = -1;
114   BOOL alignRight = YES;
115   char *pos = fmt + 1;
116   char *tmp;
117   
118   if (StrClass == Nil) StrClass = [NSString class];
119   
120   while (index(formatAttrs, *pos)) {
121     if (*pos == '-')
122       alignRight = NO;
123     pos++;
124   }
125   
126   /* width */
127   tmp = pos;
128   while (isdigit((int)*pos)) pos++;
129   if (tmp != pos) {
130     char old = *pos;
131     *pos = '\0';
132     width = atoi(tmp);
133     *pos = old;
134   }
135
136   /* prec */
137   if (*pos == '.') {
138     pos++;
139     tmp = pos;
140     while (isdigit((int)*pos)) pos++;
141     if (tmp != pos) {
142       char old = *pos;
143       *pos = '\0';
144       prec = atoi(tmp);
145       *pos = old;
146     }
147   }
148
149   /* conversion */
150   if (index(formatConv, *pos)) {
151     switch (*pos) {
152       case 'h':
153         typeMode = SHORT_TYPE;
154         pos++;
155         break;
156       case 'l':
157         typeMode = LONG_TYPE;
158         pos++;
159         if (*pos == 'l') { // long-long
160           typeMode = VERY_LONG_TYPE;
161           pos++;
162         }
163         break;
164       case 'L':
165       case 'q':
166         typeMode = VERY_LONG_TYPE;
167         pos++;
168         break;
169     }
170   }
171
172   if (index("diouxX", fmtChar)) {
173     char buf[128];
174     int  len = -1;
175
176     switch (typeMode) {
177       case SHORT_TYPE: {
178         unsigned short int i = [(NSNumber *)_value unsignedShortValue];
179         len = sprintf(buf, fmt, i);
180         break;
181       }
182       case LONG_TYPE: {
183         unsigned long int i = [(NSNumber *)_value unsignedLongValue];
184         len = sprintf(buf, fmt, i);
185         break;
186       }
187       case VERY_LONG_TYPE: {
188         long long i = [(NSNumber *)_value unsignedLongLongValue];
189         len = sprintf(buf, fmt, i);
190         break;
191       }
192       default: {
193         unsigned int i = [(NSNumber *)_value unsignedIntValue];
194         len = sprintf(buf, fmt, i);
195         break;
196       }
197     }
198
199     return (len >= 0) ? [StrClass stringWithCString:buf length:len] : nil;
200   }
201   
202   if (fmtChar == 'c') {
203     char buf[64];
204     int  i = [(NSNumber *)_value unsignedCharValue];
205     int  len;
206
207     len = sprintf(buf, fmt, (unsigned char)i);
208
209     return (len > 0) ? [StrClass stringWithCString:buf length:len] : nil;
210   }
211   if (fmtChar == 'C') {
212     // TODO: implement correctly
213     /* 16bit unichar value */
214     char buf[64];
215     int  i, len;
216     
217     /* TODO: evil hack */
218     fmt[fmtLen - 1] = 'c';
219     i = [(NSNumber *)_value unsignedCharValue];
220     len = sprintf(buf, fmt, (unsigned char)i);
221     
222     return (len > 0) ? [StrClass stringWithCString:buf length:len] : nil;
223   }
224   
225   if (index("feEgG", fmtChar)) {
226     unsigned char buf[256];
227     unsigned len;
228
229     if (typeMode == VERY_LONG_TYPE) {
230       long double i = [(NSNumber *)_value doubleValue];
231       len = sprintf(buf, fmt, i);
232     }
233     else {
234       double i = [(NSNumber *)_value doubleValue];
235       len = sprintf(buf, fmt, i);
236     }
237
238     if (len >= 0) {
239       NSMutableString *result;
240       unsigned int cnt;
241       char         *ptr = buf;
242       unsigned int bufCount = 0;
243       
244       if (_ds == nil)
245         return [StrClass stringWithCString:buf length:len];
246
247       result = 
248         [NSMutableString stringWithCapacity:len + [_ds cStringLength]];
249
250       for (cnt = 0; cnt < len; cnt++) {
251         if (buf[cnt] == '.') {
252           if (bufCount > 0) {
253             NSString *s;
254             
255             // TODO: cache selector
256             s = [[StrClass alloc] initWithCString:ptr length:bufCount];
257             if (s) [result appendString:s];
258             [s release];
259           }
260
261           if (_ds) [result appendString:_ds];
262           
263           ptr = &(buf[cnt + 1]);
264           bufCount = 0;
265         }
266         else {
267           bufCount++;
268         }
269         
270         if (bufCount > 0) {
271           NSString *s;
272           
273           // TODO: cache selector
274           s = [[StrClass alloc] initWithCString:ptr length:bufCount];
275           if (s) [result appendString:s];
276           [s release];
277         }
278         return result;
279       }
280     }
281     else 
282       return nil;
283   }
284   else {
285     switch (fmtChar) {
286       case 's': case 'S': {
287         /* TODO: implement 'S', current mech is evil hack */
288         unsigned len;
289         char *buffer = NULL;
290         id result;
291
292         if (_value == nil)
293           return wideString("<null>", width, ' ', alignRight);
294         
295         len    = [(NSString *)_value cStringLength];
296         buffer = malloc(len + 10);
297         [_value getCString:buffer];
298         buffer[len] = '\0';
299         
300         result = wideString(buffer, width, ' ', alignRight);
301         free(buffer);
302         return result;
303       }
304       
305       case '@': {
306         id       obj   = _value;
307         NSString *dstr = obj ? [obj description] : @"<nil>";
308         char     *buffer = NULL;
309         unsigned len;
310
311         if (dstr == nil)
312           return wideString("<null>", width, ' ', alignRight);
313         
314         len    = [dstr cStringLength];
315         buffer = malloc(len + 10);
316         [dstr getCString:buffer];
317         buffer[len] = '\0';
318         
319         dstr = wideString(buffer, width, ' ', alignRight);
320         free(buffer);
321         return dstr;
322       }
323       
324       case '$': {
325         id       obj   = _value;
326         NSString *dstr;
327         char     *cstr;
328
329         dstr = obj ? [obj stringValue] : @"<nil>";
330         cstr = (char *)[dstr cString];
331         if (cstr == NULL) cstr = "<null>";
332         
333         return wideString(cstr, width, ' ', alignRight);
334       }
335         
336       default:
337         fprintf(stderr, "WARNING(%s): unknown printf format used: '%s'\n", 
338                 __PRETTY_FUNCTION__, fmt);
339         break;
340     }
341   }
342   return nil;
343 }
344
345 #if USE_VA_LIST_PTR
346 static inline NSString *handleFormat(char *fmt, int fmtLen, va_list *_ap) {
347 #else
348 static inline NSString *handleFormat(char *fmt, int fmtLen, va_list _ap) {
349 #endif
350   char fmtChar;
351   char typeMode   = DEFAULT_TYPE;
352   int  width      = -1;
353   int  prec       = -1;
354   BOOL alignRight = YES;
355   char *pos = fmt + 1;
356   char *tmp;
357   if (StrClass == Nil) StrClass = [NSString class];
358
359   if (fmtLen == 0)
360     return @"";
361
362   fmtChar = fmt[fmtLen - 1];
363   
364   while (index(formatAttrs, *pos)) {
365     if (*pos == '-')
366       alignRight = NO;
367     pos++;
368   }
369   
370   /* width */
371   tmp = pos;
372   while (isdigit((int)*pos)) pos++;
373   if (tmp != pos) {
374     char old = *pos;
375     *pos = '\0';
376     width = atoi(tmp);
377     *pos = old;
378   }
379
380   /* prec */
381   if (*pos == '-') {
382     pos++;
383     tmp = pos;
384     while (isdigit((int)*pos)) pos++;
385     if (tmp != pos) {
386       char old = *pos;
387       *pos = '\0';
388       prec = atoi(tmp);
389       *pos = old;
390     }
391   }
392
393   /* conversion */
394   if (index(formatConv, *pos)) {
395     switch (*pos) {
396       case 'h':
397         typeMode = SHORT_TYPE;
398         pos++;
399         break;
400       case 'l':
401         typeMode = LONG_TYPE;
402         pos++;
403         if (*pos == 'l') { // long-long
404           typeMode = VERY_LONG_TYPE;
405           pos++;
406         }
407         break;
408       case 'L':
409       case 'q':
410         typeMode = VERY_LONG_TYPE;
411         pos++;
412         break;
413     }
414   }
415
416 #if HEAVY_DEBUG && 0
417   printf("  width=%i prec=%i\n", width, prec); fflush(stdout);
418 #endif
419     
420   if (index("diouxX", fmtChar)) {
421     char buf[128];
422     int  len = -1;
423
424     switch (typeMode) {
425       case SHORT_TYPE: {
426 #if USE_VA_LIST_PTR
427         unsigned short int i = va_arg(*_ap, int);
428 #else
429         unsigned short int i = va_arg(_ap, int);
430 #endif
431         len = sprintf(buf, fmt, i);
432         break;
433       }
434       case LONG_TYPE: {
435 #if USE_VA_LIST_PTR        
436         unsigned long int i = va_arg(*_ap, unsigned long int);
437 #else
438         unsigned long int i = va_arg(_ap, unsigned long int);
439 #endif
440         len = sprintf(buf, fmt, i);
441         break;
442       }
443       case VERY_LONG_TYPE: {
444 #if USE_VA_LIST_PTR        
445         long long i = va_arg(*_ap, long long);
446 #else
447         long long i = va_arg(_ap, long long);
448 #endif
449         len = sprintf(buf, fmt, i);
450         break;
451       }
452       default: {
453 #if USE_VA_LIST_PTR        
454         unsigned int i = va_arg(*_ap, unsigned int);
455 #else
456         unsigned int i = va_arg(_ap, unsigned int);
457 #endif
458         len = sprintf(buf, fmt, i);
459         break;
460       }
461     }
462
463     return (len >= 0) ? [StrClass stringWithCString:buf length:len] : nil;
464   }
465   
466   if (fmtChar == 'c') {
467     char buf[64];
468 #if USE_VA_LIST_PTR        
469     int  i = va_arg(*_ap, int);
470 #else
471     int  i = va_arg(_ap, int);
472 #endif
473     int  len;
474
475     if (i == 0)
476       return @"<'\\0'-char>";
477
478     len = sprintf(buf, fmt, (unsigned char)i);
479
480 #if HEAVY_DEBUG && 0
481     xsprintf("got format %s char %i made %s len %i\n",
482              fmt, i, buf, len);
483 #endif
484     
485     if (len == 0) return nil;
486     return [StrClass stringWithCString:buf length:len];
487   }
488   if (fmtChar == 'C') {
489     /* 16bit unichar */
490     char buf[64];
491 #if USE_VA_LIST_PTR        
492     int  i = va_arg(*_ap, int);
493 #else
494     int  i = va_arg(_ap, int);
495 #endif
496     int  len;
497
498     if (i == 0)
499       return @"<'\\0'-unichar>";
500     
501     /* TODO: implement properly, evil hack */
502     fmt[fmtLen - 1] = 'c';
503     len = sprintf(buf, fmt, (unsigned char)i);
504
505 #if HEAVY_DEBUG && 0
506     xsprintf("got format %s unichar %i made %s len %i\n",
507              fmt, i, buf, len);
508 #endif
509     
510     if (len == 0) return nil;
511     return [StrClass stringWithCString:buf length:len];
512   }
513   
514   if (index("feEgG", fmtChar)) {
515     char buf[256];
516     int  len;
517
518     if (typeMode == VERY_LONG_TYPE) {
519 #if USE_VA_LIST_PTR        
520       long double i = va_arg(*_ap, long double);
521 #else
522       long double i = va_arg(_ap, long double);
523 #endif
524       len = sprintf(buf, fmt, i);
525     }
526     else {
527 #if USE_VA_LIST_PTR        
528       double i = va_arg(*_ap, double);
529 #else
530       double i = va_arg(_ap, double);
531 #endif
532       len = sprintf(buf, fmt, i);
533     }
534
535     return (len >= 0) ? [StrClass stringWithCString:buf length:len] : nil;
536   }
537   
538   {
539     id result = nil;
540     
541     switch (fmtChar) {
542       case 's':
543       case 'S': /* unicode char array */
544       case '@':
545       case '$': {
546         char *cstr = NULL;
547         BOOL owned = NO;
548
549         if (fmtChar == 's') {
550 #if USE_VA_LIST_PTR        
551           cstr = va_arg(*_ap, char *);
552 #else
553           cstr = va_arg(_ap, char *);
554 #endif
555         }
556         else {
557 #if USE_VA_LIST_PTR        
558           id obj = va_arg(*_ap, id);
559 #else
560           id obj = va_arg(_ap, id);
561 #endif
562           if (obj == nil)
563             cstr = "<nil>";
564           else {
565             NSString *d;
566             
567             if ((d = (fmtChar == '@') ?[obj description]:[obj stringValue])) {
568               unsigned len = [d cStringLength];
569
570               cstr = NGMalloc(len + 1);
571               [d getCString:cstr];
572               cstr[len] = '\0';
573               owned = YES;
574             }
575           }
576         }
577
578         if (cstr == NULL) cstr = "<null>";
579
580         result = wideString(cstr, width, ' ', alignRight);
581         if (owned) NGFree(cstr);
582         break;
583       }
584
585       default:
586         fprintf(stderr, "WARNING(%s): unknown printf format used: '%s'\n", 
587                 __PRETTY_FUNCTION__, fmt);
588         break;
589     }
590     return result;
591   }
592   return nil;
593 }
594
595 static inline NSString *_stringWithCFormat(const char *_format, va_list _ap) {
596   const char *firstPercent;
597   NSMutableString *result;
598   
599   if (StrClass == Nil) StrClass = [NSString class];
600   firstPercent = index(_format, '%');
601 #if 0
602   fprintf(stderr, "OWN: format='%s'\n", _format);
603   fflush(stderr);
604 #endif
605   
606   // first check whether there are any '%' in the format ..
607   if (firstPercent == NULL) {
608     // no formatting contained in _format
609     return [StrClass stringWithCString:_format];
610   }
611   
612   result = [NSMutableString stringWithCapacity:256];
613
614   if ((firstPercent - _format) > 0) {
615     NSString *s;
616     s = [[StrClass alloc] initWithCString:_format 
617                           length:(firstPercent - _format)];
618     if (s) [result appendString:s];
619     [s release];
620     _format = firstPercent;
621   }
622
623   while (*_format != '\0') { // until end of format string
624     if (*_format == '%') { // found formatting character
625       _format++; // skip '%'
626
627       if (*_format == '%') { // was a quoted '%'
628         [result appendString:@"%"];
629         _format++;
630       }
631       else { // check format
632         char extFmt[16];
633         char *pos  = extFmt;
634
635         extFmt[0] = '%';
636         pos++;
637       
638         while ((*_format != '\0') &&
639                 (index(formatChars, *_format) == NULL)) {
640           *pos = *_format;
641           _format++;
642           pos++;
643         }
644         *pos = *_format;
645         _format++;
646         pos++;
647         *pos = '\0';
648
649         // printf("handling ext format '%s'\n", extFmt); fflush(stdout);
650         /* hack for iSeries port, ix86 seems to copy va_list iSeries
651             don`t like pointers to va_list
652         */
653         {
654           NSString *s;
655 #if USE_VA_LIST_PTR
656           s = handleFormat(extFmt, strlen(extFmt), &_ap);
657 #else
658           s = handleFormat(extFmt, strlen(extFmt), _ap);
659 #endif
660           if (s) [result appendString:s];
661         }
662       }
663     }
664     else { // normal char
665       const char *start = _format; // remember start
666       NSString *s;
667       _format++; // skip found char
668
669       // further increase format until '\0' or '%'
670       while ((*_format != '\0') && (*_format != '%'))
671         _format++;
672       
673       s = [[StrClass alloc] initWithCString:start
674                             length:(_format - start)];
675       if (s) [result appendString:s];
676       [s release];
677     }
678   }
679   return result;
680 }
681
682 @implementation NSString(XSFormatting)
683
684 + (id)stringWithCFormat:(const char *)_format arguments:(va_list)_ap {
685   return [self stringWithString:_stringWithCFormat(_format, _ap)];
686 }
687
688 + (id)stringWithFormat:(NSString *)_format arguments:(va_list)_ap {
689   unsigned len;
690   char     *cfmt;
691   id s;
692
693   len = [_format cStringLength] + 1;
694   cfmt = malloc(len + 1);
695   [_format getCString:cfmt]; cfmt[len] = '\0';
696   s = [self stringWithString:_stringWithCFormat(cfmt, _ap)];
697   free(cfmt);
698   return s;
699 }
700
701 + (id)stringWithCFormat:(const char *)_format, ... {
702   id      result = nil;
703   va_list ap;
704   
705   va_start(ap, _format);
706   result = [self stringWithString:_stringWithCFormat(_format, ap)];
707   va_end(ap);
708   return result;
709 }
710
711 + (id)stringWithFormat:(NSString *)_format, ... {
712   id       result = nil;
713   unsigned len;
714   char     *cfmt;
715   va_list  ap;
716   
717   len = [_format cStringLength];
718   cfmt = malloc(len + 1);
719   [_format getCString:cfmt]; cfmt[len] = '\0';
720   va_start(ap, _format);
721   result = [self stringWithString:_stringWithCFormat(cfmt, ap)];
722   va_end(ap);
723   free(cfmt);
724   return result;
725 }
726
727 - (id)initWithFormat:(NSString *)_format arguments:(va_list)_ap {
728   unsigned len;
729   char *cfmt;
730
731   len = [_format cStringLength];
732   cfmt = malloc(len + 1);
733   [_format getCString:cfmt]; cfmt[len] = '\0';
734   self = [self initWithString:_stringWithCFormat(cfmt, _ap)];
735   free(cfmt);
736   return self;
737 }
738
739 @end /* NSString(XSFormatting) */
740
741 @implementation NSMutableString(XSFormatting)
742
743 - (void)appendFormat:(NSString *)_format arguments:(va_list)_ap {
744   unsigned len;
745   NSString *s;
746   char     *cfmt;
747   
748   len = [_format cStringLength];
749   cfmt = malloc(len + 4);
750   
751   [_format getCString:cfmt]; cfmt[len] = '\0';
752   s = _stringWithCFormat(cfmt, _ap);
753   if (cfmt) free(cfmt);
754   if (s) [self appendString:s];
755 }
756 - (void)appendFormat:(NSString *)_format, ... {
757   unsigned len;
758   char     *cfmt;
759   NSString *s;
760   va_list  ap;
761   
762   len = [_format cStringLength];
763   cfmt = malloc(len + 4);
764   [_format getCString:cfmt]; cfmt[len] = '\0';
765   va_start(ap, _format);
766   s = _stringWithCFormat(cfmt, ap);
767   va_end(ap);
768   if (cfmt) free(cfmt);
769   if (s) [self appendString:s];
770 }
771
772 @end /* NSMutableString(XSFormatting) */
773
774 #endif
775
776 // var args wrappers
777
778 int xs_sprintf(char *str, const char *format, ...) {
779   va_list ap;
780   int result;
781   va_start(ap, format);
782   result = xs_vsprintf(str, format, ap);
783   va_end(ap);
784   return result;
785 }
786 int xs_snprintf(char *str, size_t size, const char *format, ...) {
787   va_list ap;
788   int result;
789   va_start(ap, format);
790   result = xs_vsnprintf(str, size, format, ap);
791   va_end(ap);
792   return result;
793 }
794
795 /* static linking */
796
797 void __link_NSString_Formatting(void) {
798   __link_NSString_Formatting();
799 }