]> err.no Git - sope/blob - sope-core/NGExtensions/FdExt.subproj/NSString+misc.m
minor improvement to quoting
[sope] / sope-core / NGExtensions / FdExt.subproj / NSString+misc.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 "NSString+misc.h"
23 #include "common.h"
24
25 @interface NSStringVariableBindingException : NSException
26 @end
27
28 @implementation NSStringVariableBindingException
29 @end
30
31 @implementation NSObject(StringBindings)
32
33 - (NSString *)valueForStringBinding:(NSString *)_key {
34   if (_key == nil) return nil;
35   return [[self valueForKey:_key] stringValue];
36 }
37
38 @end /* NSObject(StringBindings) */
39
40 @implementation NSDictionary(StringBindings)
41
42 - (NSString *)valueForStringBinding:(NSString *)_key {
43   if (_key == nil) return nil;
44   return [[self objectForKey:_key] stringValue];
45 }
46
47 @end /* NSDictionary(StringBindings) */
48
49 @implementation NSString(misc)
50
51 - (NSSet *)bindingVariables
52 {
53   unsigned        len, pos = 0;
54   const char      *buf     = NULL;
55   NSMutableSet    *result  = nil;
56
57   result = [NSMutableSet set];
58   len    = [self cStringLength];  
59   buf    = [self cString];
60   
61   while (pos < len) {
62     unsigned startPos;
63     
64     if (pos + 1 == len) { /* last entry */
65       if (buf[pos] == '$') { /* found $ without end-char */
66         [[[NSStringVariableBindingException alloc]
67            initWithFormat:@"did not find end of variable for string %@", self]
68            raise];
69       }
70       break;
71     }
72     if (buf[pos] != '$') {
73       pos++;
74       continue;
75     }
76     
77     if (buf[pos + 1] == '$') { /* found $$ --> ignore*/
78       pos += 2;
79       continue;
80     }
81
82     /* process binding */
83     
84     startPos = pos;
85     
86     pos += 2; /* buf[pos + 1] != '$' */
87     while (pos < len) {
88       if (buf[pos] != '$')
89         pos++;
90       else
91         break;
92     }
93     if (pos == len) { /* end of string was reached */
94       [[[NSStringVariableBindingException alloc]
95                                           initWithFormat:@"didn`t find end of "
96                                             @"variable for string %@", self]
97                                           raise];
98     }
99     else {
100       NSString *key   = nil;
101
102       key = [[NSString alloc]
103                        initWithCStringNoCopy:(char*)buf + startPos + 1
104                        length:pos - startPos - 1
105                        freeWhenDone:NO];
106       [result addObject:key];
107       [key release];
108     }
109     pos++;
110   }
111   return [[result copy] autorelease];
112 }
113
114 - (NSString *)stringByReplacingVariablesWithBindings:(id)_bindings
115   stringForUnknownBindings:(NSString *)_unknown
116 {
117   unsigned        len, pos = 0;
118   const char      *buf     = NULL;
119   NSMutableString *str     = nil;
120   
121   str = [self mutableCopy];
122   len = [str cStringLength];  
123   buf = [str cString];
124   
125   while (pos < len) {
126     if (pos + 1 == len) { /* last entry */
127       if (buf[pos] == '$') { /* found $ without end-char */
128         [[[NSStringVariableBindingException alloc]
129            initWithFormat:@"did not find end of variable for string %@", self]
130           raise];
131       }
132       break;
133     }
134     if (buf[pos] == '$') {
135       if (buf[pos + 1] == '$') { /* found $$ --> $ */
136         [str deleteCharactersInRange:NSMakeRange(pos, 1)];
137         buf = [str cString];
138         len = [str cStringLength];
139       }
140       else {
141         unsigned startPos = pos;
142
143         pos += 2; /* buf[pos + 1] != '$' */
144         while (pos < len) {
145           if (buf[pos] != '$')
146             pos++;
147           else
148             break;
149         }
150         if (pos == len) { /* end of string was reached */
151           [[[NSStringVariableBindingException alloc]
152              initWithFormat:@"did not find end of variable for string %@", 
153              self] raise];
154         }
155         else {
156           NSString *key;
157           NSString *value;
158
159           key = [[NSString alloc]
160                            initWithCStringNoCopy:(char*)buf + startPos + 1
161                            length:pos - startPos - 1
162                            freeWhenDone:NO];
163           
164           if ((value = [_bindings valueForStringBinding:key]) == nil) {
165             if (_unknown == nil) {
166               [[[NSStringVariableBindingException alloc]
167                           initWithFormat:@"did not find binding for "
168                                          @"name %@ in binding-dictionary %@",
169                                          [key autorelease], _bindings] raise];
170             }
171             else
172               value = _unknown;
173           }
174           [key release]; key = nil;
175           [str replaceCharactersInRange:
176                  NSMakeRange(startPos, pos - startPos + 1)
177                withString:value];
178           buf = [str cString];
179           len = [str cStringLength];
180           pos = startPos - 1 + [value length];
181         }
182       }
183     }
184     pos++;
185   }
186   {
187     id tmp = str;
188     str = [str copy];
189     [tmp release]; tmp = nil;
190   }
191   return [str autorelease];
192 }
193
194 - (NSString *)stringByReplacingVariablesWithBindings:(id)_bindings {
195   return [self stringByReplacingVariablesWithBindings:_bindings
196                stringForUnknownBindings:nil];
197 }
198
199 @end /* NSString(misc) */
200
201
202 @implementation NSString(FilePathVersioningMethods)
203
204 /*
205   "/path/file.txt;1"
206 */
207 - (NSString *)pathVersion {
208   NSRange r;
209
210   r = [self rangeOfString:@";"];
211   if (r.length > 0) {
212     return ([self length] > r.location)
213       ? [self substringFromIndex:(r.location + r.length)]
214       : @"";
215   }
216   return nil;
217 }
218
219 - (NSString *)stringByDeletingPathVersion {
220   NSRange r;
221
222   r = [self rangeOfString:@";"];
223   return (r.length > 0)
224     ? [self substringToIndex:r.location]
225     : self;
226 }
227
228 - (NSString *)stringByAppendingPathVersion:(NSString *)_version {
229   return [[self stringByAppendingString:@";"] 
230                 stringByAppendingString:_version];
231 }
232
233 @end /* NSString(FilePathMethodsVersioning) */
234
235 @implementation NSString(NGScanning)
236
237 - (NSRange)rangeOfString:(NSString *)_s 
238   skipQuotes:(NSString *)_quotes
239   escapedByChar:(unichar)_escape
240 {
241   // TODO: speed ...
242   // TODO: check correctness with invalid input !
243   static NSRange notFound = { 0, 0 };
244   NSCharacterSet *quotes;
245   unsigned i, len, slen;
246   unichar sc;
247   
248   if ((slen = [_s length]) == 0)
249     return notFound;
250   if ((len = [self length]) < slen) /* to short */
251     return notFound;
252   
253   if ([_quotes length] == 0)
254     _quotes = @"'\"";
255   quotes = [NSCharacterSet characterSetWithCharactersInString:_quotes];
256   
257   sc = [_s characterAtIndex:0];
258   
259   for (i = 0; i < len; i++) {
260     unichar c;
261     
262     c = [self characterAtIndex:i];
263     
264     if (c == sc) {
265       /* start search section */
266       if (slen == 1)
267         return NSMakeRange(i, 1);
268       
269       if ([[self substringFromIndex:i] hasPrefix:_s])
270         return NSMakeRange(i, slen);
271     }
272     else if ([quotes characterIsMember:c]) {
273       /* skip quotes */
274       for (i++; i < len && ![quotes characterIsMember:c]; i++) {
275         if (c == _escape) {
276           i++; /* skip next char (eg \') */
277           continue;
278         }
279       }
280     }
281   }
282   
283   return notFound;
284 }
285
286 - (NSRange)rangeOfString:(NSString *)_s skipQuotes:(NSString *)_quotes {
287   return [self rangeOfString:_s skipQuotes:_quotes escapedByChar:'\\'];
288 }
289
290 @end /* NSString(NGScanning) */
291
292
293 @implementation NSString(MailQuoting)
294
295 - (NSString *)stringByApplyingMailQuoting {
296   NSString *s;
297   unsigned i, len, nl;
298   unichar  *sourceBuf, *targetBuf;
299   
300   if ((len = [self length]) == 0)
301     return @"";
302   
303   sourceBuf = malloc((len + 4) * sizeof(unichar));
304   [self getCharacters:sourceBuf];
305   
306   for (nl = 0, i = 0; i < len; i++) {
307     if (sourceBuf[i] == '\n') 
308       nl++;
309   }
310   
311   if (nl == 0) {
312     if (sourceBuf) free(sourceBuf);
313     return [@"> " stringByAppendingString:self];
314   }
315   
316   targetBuf = malloc((len + 8 + (nl * 3)) * sizeof(unichar));
317   targetBuf[0] = '>';
318   targetBuf[1] = ' ';
319   nl = 2;
320   
321   for (i = 0; i < len; i++) {
322     targetBuf[nl] = sourceBuf[i];
323     nl++;
324     
325     if (sourceBuf[i] == '\n' && (i + 1 != len)) {
326       targetBuf[nl] = '>'; nl++;
327       targetBuf[nl] = ' '; nl++;
328     }
329   }
330   
331   s = [[NSString alloc] initWithCharacters:targetBuf length:nl];
332   if (targetBuf) free(targetBuf);
333   if (sourceBuf) free(sourceBuf);
334   return [s autorelease];
335 }
336
337 @end /* NSString(MailQuoting) */
338
339 // linking
340
341 void __link_NSString_misc(void) {
342   __link_NSString_misc();
343 }