1/*
2  Copyright (C) 2000-2008 SKYRIX Software AG
3  Copyright (C) 2006-2008 Helge Hess
4
5  This file is part of SOPE.
6
7  SOPE is free software; you can redistribute it and/or modify it under
8  the terms of the GNU Lesser General Public License as published by the
9  Free Software Foundation; either version 2, or (at your option) any
10  later version.
11
12  SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
13  WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
15  License for more details.
16
17  You should have received a copy of the GNU Lesser General Public
18  License along with SOPE; see the file COPYING.  If not, write to the
19  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
20  02111-1307, USA.
21*/
22
23#include "NSString+misc.h"
24#include "NSException+misc.h"
25#include "common.h"
26
27@implementation NSObject(StringBindings)
28
29- (NSString *)valueForStringBinding:(NSString *)_key {
30  if (_key == nil) return nil;
31  return [[self valueForKeyPath:_key] stringValue];
32}
33
34@end /* NSObject(StringBindings) */
35
36@implementation NSString(misc)
37
38- (NSSet *)bindingVariables
39{
40  unsigned        len, pos = 0;
41  unichar         *wbuf    = NULL;
42  NSMutableSet    *result  = nil;
43
44  result = [NSMutableSet setWithCapacity:16];
45  len    = [self length];
46  wbuf   = malloc(sizeof(unichar) * (len + 4));
47  [self getCharacters:wbuf];
48
49  while (pos < len) {
50    unsigned startPos;
51
52    if (pos + 1 == len) { /* last entry */
53      if (wbuf[pos] == '$') { /* found $ without end-char */
54	[[NSException exceptionWithName: @"NSStringVariableBindingException"
55		reason:[NSString stringWithFormat:
56			@"did not find end of variable for string %@", self]
57		userInfo:nil] raise];
58      }
59      break;
60    }
61    if (wbuf[pos] != '$') {
62      pos++;
63      continue;
64    }
65
66    if (wbuf[pos + 1] == '$') { /* found $$ --> ignore*/
67      pos += 2;
68      continue;
69    }
70
71    /* process binding */
72
73    startPos = pos;
74
75    pos += 2; /* wbuf[pos + 1] != '$' */
76    while (pos < len) {
77      if (wbuf[pos] != '$')
78	pos++;
79      else
80	break;
81    }
82    if (pos == len) { /* end of string was reached */
83	[[NSException exceptionWithName: @"NSStringVariableBindingException"
84		reason:[NSString stringWithFormat:
85			@"did not find end of variable for string %@", self]
86		userInfo:nil] raise];
87    }
88    else {
89      NSString *key = nil;
90
91      key = [[NSString alloc]
92                       initWithCharacters:(unichar *)wbuf + startPos + 1
93	               length:(pos - startPos - 1)];
94      [result addObject:key];
95      [key release];
96    }
97    pos++;
98  }
99  if (wbuf != NULL) { free(wbuf); wbuf = NULL; }
100
101  return [[result copy] autorelease];
102}
103
104- (NSString *)stringByReplacingVariablesWithBindings:(id)_bindings
105  stringForUnknownBindings:(NSString *)_unknown
106{
107  unsigned        len, pos = 0;
108  unichar         *wbuf    = NULL;
109  NSMutableString *str     = nil;
110
111  str = [self mutableCopy];
112  len = [str length];
113  wbuf   = malloc(sizeof(unichar) * (len + 4));
114  [self getCharacters:wbuf];
115
116  while (pos < len) {
117    if (pos + 1 == len) { /* last entry */
118      if (wbuf[pos] == '$') { /* found $ without end-char */
119	[[NSException exceptionWithName: @"NSStringVariableBindingException"
120		reason:[NSString stringWithFormat:
121			@"did not find end of variable for string %@", self]
122		userInfo:nil] raise];
123      }
124      break;
125    }
126    if (wbuf[pos] == '$') {
127      if (wbuf[pos + 1] == '$') { /* found $$ --> $ */
128        [str deleteCharactersInRange:NSMakeRange(pos, 1)];
129
130	if (wbuf != NULL) { free(wbuf); wbuf = NULL; }
131        len  = [str length];
132	wbuf = malloc(sizeof(unichar) * (len + 4));
133	[str getCharacters:wbuf];
134      }
135      else {
136        unsigned startPos = pos;
137
138        pos += 2; /* wbuf[pos + 1] != '$' */
139        while (pos < len) {
140          if (wbuf[pos] != '$')
141            pos++;
142          else
143            break;
144        }
145        if (pos == len) { /* end of string was reached */
146	[[NSException exceptionWithName: @"NSStringVariableBindingException"
147		reason:[NSString stringWithFormat:
148			@"did not find end of variable for string %@", self]
149		userInfo:nil] raise];
150	}
151        else {
152          NSString *key;
153          NSString *value;
154
155          key = [[NSString alloc]
156		  initWithCharacters:(wbuf + startPos + 1)
157		  length:(pos - startPos - 1)];
158
159          if ((value = [_bindings valueForStringBinding:key]) == nil) {
160            if (_unknown == nil) {
161		[[NSException exceptionWithName:
162			@"NSStringVariableBindingException"
163			reason:[NSString stringWithFormat:
164				@"did not find binding for "
165				@"name %@ in binding-dictionary %@",
166				[key autorelease], _bindings]
167			userInfo:nil] raise];
168            }
169            else
170              value = _unknown;
171          }
172          [key release]; key = nil;
173
174          [str replaceCharactersInRange:
175		 NSMakeRange(startPos, pos - startPos + 1)
176               withString:value];
177
178	  if (wbuf != NULL) { free(wbuf); wbuf = NULL; }
179	  len  = [str length];
180	  wbuf = malloc(sizeof(unichar) * (len + 4));
181	  [str getCharacters:wbuf];
182
183          pos = startPos - 1 + [value length];
184        }
185      }
186    }
187    pos++;
188  }
189  if (wbuf != NULL) { free(wbuf); wbuf = NULL; }
190  {
191    id tmp = str;
192    str = [str copy];
193    [tmp release]; tmp = nil;
194  }
195  return [str autorelease];
196}
197
198- (NSString *)stringByReplacingVariablesWithBindings:(id)_bindings {
199  return [self stringByReplacingVariablesWithBindings:_bindings
200               stringForUnknownBindings:nil];
201}
202
203@end /* NSString(misc) */
204
205
206@implementation NSString(FilePathVersioningMethods)
207
208/*
209  "/path/file.txt;1"
210*/
211- (NSString *)pathVersion {
212  NSRange r;
213
214  r = [self rangeOfString:@";"];
215  if (r.length > 0) {
216    return ([self length] > r.location)
217      ? [self substringFromIndex:(r.location + r.length)]
218      : (NSString *)@"";
219  }
220  return nil;
221}
222
223- (NSString *)stringByDeletingPathVersion {
224  NSRange r;
225
226  r = [self rangeOfString:@";"];
227  return (r.length > 0)
228    ? [self substringToIndex:r.location]
229    : self;
230}
231
232- (NSString *)stringByAppendingPathVersion:(NSString *)_version {
233  return [[self stringByAppendingString:@";"]
234	        stringByAppendingString:_version];
235}
236
237@end /* NSString(FilePathMethodsVersioning) */
238
239@implementation NSString(NGScanning)
240
241- (NSRange)rangeOfString:(NSString *)_s
242  skipQuotes:(NSString *)_quotes
243  escapedByChar:(unichar)_escape
244{
245  // TODO: speed ...
246  // TODO: check correctness with invalid input !
247  static NSRange notFound = { 0, 0 };
248  NSCharacterSet *quotes;
249  unsigned i, len, slen;
250  unichar sc;
251
252  if ((slen = [_s length]) == 0)
253    return notFound;
254  if ((len = [self length]) < slen) /* to short */
255    return notFound;
256
257  if ([_quotes length] == 0)
258    _quotes = @"'\"";
259  quotes = [NSCharacterSet characterSetWithCharactersInString:_quotes];
260
261  sc = [_s characterAtIndex:0];
262
263  for (i = 0; i < len; i++) {
264    unichar c;
265
266    c = [self characterAtIndex:i];
267
268    if (c == sc) {
269      /* start search section */
270      if (slen == 1)
271        return NSMakeRange(i, 1);
272
273      if ([[self substringFromIndex:i] hasPrefix:_s])
274        return NSMakeRange(i, slen);
275    }
276    else if ([quotes characterIsMember:c]) {
277      /* skip quotes */
278      i++;
279      c = [self characterAtIndex:i];
280      for (; i < len && ![quotes characterIsMember:c]; i++) {
281	c = [self characterAtIndex:i];
282        if (c == _escape) {
283          i++; /* skip next char (eg \') */
284          continue;
285        }
286      }
287    }
288  }
289
290  return notFound;
291}
292
293- (NSRange)rangeOfString:(NSString *)_s skipQuotes:(NSString *)_quotes {
294  return [self rangeOfString:_s skipQuotes:_quotes escapedByChar:'\\'];
295}
296
297@end /* NSString(NGScanning) */
298
299
300@implementation NSString(MailQuoting)
301
302- (NSString *)stringByApplyingMailQuoting {
303  NSString *s;
304  unsigned i, len, nl;
305  unichar  *sourceBuf, *targetBuf;
306
307  if ((len = [self length]) == 0)
308    return @"";
309
310  sourceBuf = malloc((len + 4) * sizeof(unichar));
311  [self getCharacters:sourceBuf];
312
313  for (nl = 0, i = 0; i < len; i++) {
314    if (sourceBuf[i] == '\n')
315      nl++;
316  }
317
318  if (nl == 0) {
319    if (sourceBuf) free(sourceBuf);
320    return [@"> " stringByAppendingString:self];
321  }
322
323  targetBuf = malloc((len + 8 + (nl * 3)) * sizeof(unichar));
324  targetBuf[0] = '>';
325  targetBuf[1] = ' ';
326  nl = 2;
327
328  for (i = 0; i < len; i++) {
329    targetBuf[nl] = sourceBuf[i];
330    nl++;
331
332    if (sourceBuf[i] == '\n' && (i + 1 != len)) {
333      targetBuf[nl] = '>'; nl++;
334      targetBuf[nl] = ' '; nl++;
335    }
336  }
337
338  s = [[NSString alloc] initWithCharacters:targetBuf length:nl];
339  if (targetBuf) free(targetBuf);
340  if (sourceBuf) free(sourceBuf);
341  return [s autorelease];
342}
343
344@end /* NSString(MailQuoting) */
345
346@implementation NSString(HeaderCapitalization)
347
348- (NSString *) asCapitalizedHeader
349{
350  NSString *result;
351  NSUInteger count, max;
352  unichar *chars;
353  BOOL capitalize = YES;
354
355  max = [self length];
356  if (max == 3 && [[self lowercaseString] isEqualToString: @"dav"])
357    result = @"DAV";
358  else
359    {
360      chars = malloc (max * sizeof (unichar));
361      [self getCharacters: chars];
362      for (count = 0; count < max; count++)
363        {
364          if (capitalize)
365            {
366              if (chars[count] >= 97 && chars[count] <= 122)
367                chars[count] -= 32;
368              capitalize = NO;
369            }
370          else if (chars[count] == '-')
371            capitalize = YES;
372        }
373      result = [NSString stringWithCharacters: chars length: max];
374      free (chars);
375    }
376
377  return result;
378}
379
380@end
381
382// linking
383
384void __link_NSString_misc(void) {
385  __link_NSString_misc();
386}
387