Bug Summary

File:NSString_BDSKExtensions.m
Location:line 1173, column 16
Description:Memory Leak
Code is compiled without garbage collection.

Annotated Source Code

1// NSString_BDSKExtensions.m
2
3// Created by Michael McCracken on Sun Jul 21 2002.
4/*
5 This software is Copyright (c) 2002-2008
6 Michael O. McCracken. All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions
10 are met:
11
12 - Redistributions of source code must retain the above copyright
13 notice, this list of conditions and the following disclaimer.
14
15 - Redistributions in binary form must reproduce the above copyright
16 notice, this list of conditions and the following disclaimer in
17 the documentation and/or other materials provided with the
18 distribution.
19
20 - Neither the name of Michael O. McCracken nor the names of any
21 contributors may be used to endorse or promote products derived
22 from this software without specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36
37#import "NSString_BDSKExtensions.h"
38#import <OmniFoundation/NSString-OFExtensions.h>
39#import <Cocoa/Cocoa.h>
40#import <AGRegex/AGRegex.h>
41#import <OmniFoundation/OFCharacterSet.h>
42#import <OmniFoundation/OFStringScanner.h>
43#import "BDSKStringConstants.h"
44#import "CFString_BDSKExtensions.h"
45#import "OFCharacterSet_BDSKExtensions.h"
46#import "NSURL_BDSKExtensions.h"
47#import "NSScanner_BDSKExtensions.h"
48#import "html2tex.h"
49#import "NSDictionary_BDSKExtensions.h"
50#import "NSWorkspace_BDSKExtensions.h"
51#import "BDSKStringEncodingManager.h"
52#import "BDSKTypeManager.h"
53#import "NSFileManager_BDSKExtendedAttributes.h"
54
55static NSString *yesString = nil( ( void * ) 0 );
56static NSString *noString = nil( ( void * ) 0 );
57static NSString *mixedString = nil( ( void * ) 0 );
58
59@implementation NSString (BDSKExtensions)
60
61+ (void)didLoad
62{
63 yesString = [NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "Yes" )
value : @ "" table : ( ( void * ) 0 ) ]
(@"Yes", @"") copy];
64 noString = [NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "No" ) value
: @ "" table : ( ( void * ) 0 ) ]
(@"No", @"") copy];
65 mixedString = [NSLocalizedString[ [ NSBundle mainBundle ] localizedStringForKey : ( @ "-" ) value
: @ "" table : ( ( void * ) 0 ) ]
(@"-", @"indeterminate or mixed value indicator") copy];
66
67}
68
69+ (NSString *)hexStringForCharacter:(unichar)ch{
70 NSMutableString *string = [NSMutableString stringWithCapacity:4];
71 [string appendFormat:@"%X", ch];
72 while([string length] < 4)
73 [string insertString:@"0" atIndex:0];
74 [string insertString:@"0x" atIndex:0];
75 return string;
76}
77
78static int MAX_RATING = 5;
79+ (NSString *)ratingStringWithInteger:(int)rating;
80{
81 NSParameterAssertdo { if ( ! ( ( rating <= MAX_RATING ) ) ) { [ [ NSAssertionHandler
currentHandler ] handleFailureInMethod : _cmd object : self file
: [ NSString stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/NSString_BDSKExtensions.m"
] lineNumber : 81 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "rating <= MAX_RATING" ) , ( 0 ) , ( 0 ) , ( 0 ) , (
0 ) ] ; } } while ( 0 )
(rating <= MAX_RATING);
82 static CFMutableDictionaryRef ratings = NULL( ( void * ) 0 );
83 if(ratings == NULL( ( void * ) 0 )){
84 ratings = CFDictionaryCreateMutable(CFAllocatorGetDefault(), MAX_RATING + 1, &OFIntegerDictionaryKeyCallbacks, &OFNSObjectDictionaryValueCallbacks);
85 int i = 0;
86 NSMutableString *ratingString = [NSMutableString string];
87 do {
88 CFDictionaryAddValue(ratings, (const void *)i, (const void *)[[ratingString copy] autorelease]);
89 [ratingString appendCharacter:(0x278A + i)];
90 } while(i++ < MAX_RATING);
91 OBPOSTCONDITIONdo { if ( ! ( ( int ) [ ( id ) ratings count ] == MAX_RATING +
1 ) ) OBAssertFailed ( "POSTCONDITION" , "(int)[(id)ratings count] == MAX_RATING + 1"
, "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/NSString_BDSKExtensions.m"
, 91 ) ; } while ( ( BOOL ) 0 )
((int)[(id)ratings count] == MAX_RATING + 1);
92 }
93 return (NSString *)CFDictionaryGetValue(ratings, (const void *)rating);
94}
95
96+ (NSString *)stringWithBool:(BOOL)boolValue {
97 return boolValue ? yesString : noString;
98}
99
100+ (NSString *)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)encoding guessEncoding:(BOOL)try;
101{
102 return [[self alloc] initWithContentsOfFile:path encoding:encoding guessEncoding:try];
103}
104
105+ (NSString *)stringWithFileSystemRepresentation:(const char *)cstring;
106{
107 NSParameterAssertdo { if ( ! ( ( cstring != ( ( void * ) 0 ) ) ) ) { [ [ NSAssertionHandler
currentHandler ] handleFailureInMethod : _cmd object : self file
: [ NSString stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/NSString_BDSKExtensions.m"
] lineNumber : 107 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "cstring != NULL" ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ;
} } while ( 0 )
(cstring != NULL);
108 return [(id)CFStringCreateWithFileSystemRepresentation(CFAllocatorGetDefault(), cstring) autorelease];
109}
110
111+ (NSString *)stringWithTriStateValue:(NSCellStateValue)triStateValue {
112 switch (triStateValue) {
113 case NSOffState:
114 return noString;
115 break;
116 case NSOnState:
117 return yesString;
118 break;
119 case NSMixedState:
120 default:
121 return mixedString;
122 break;
123 }
124}
125
126+ (NSString *)unicodeNameOfCharacter:(unichar)ch;
127{
128 CFMutableStringRef charString = CFStringCreateMutable(CFAllocatorGetDefault(), 0);
129 CFStringAppendCharacters(charString, &ch, 1);
130
131 // ignore failures for now
132 CFStringTransform(charString, NULL( ( void * ) 0 ), kCFStringTransformToUnicodeName, FALSE0);
133
134 return [(id)charString autorelease];
135}
136
137+ (NSString *)IANACharSetNameForEncoding:(NSStringEncoding)enc;
138{
139 CFStringEncoding cfEnc = CFStringConvertNSStringEncodingToEncoding(enc);
140 NSString *encName = nil( ( void * ) 0 );
141 if (kCFStringEncodingInvalidId( 0xffffffffU ) != cfEnc)
142 encName = (NSString *)CFStringConvertEncodingToIANACharSetName(cfEnc);
143 return encName;
144}
145
146+ (NSStringEncoding)encodingForIANACharSetName:(NSString *)name
147{
148 NSStringEncoding nsEnc = 0;
149 CFStringEncoding cfEnc = kCFStringEncodingInvalidId( 0xffffffffU );
150
151 if (name)
152 cfEnc = CFStringConvertIANACharSetNameToEncoding((CFStringRef)name);
153
154 if (kCFStringEncodingInvalidId( 0xffffffffU ) != cfEnc)
155 nsEnc = CFStringConvertEncodingToNSStringEncoding(cfEnc);
156
157 return nsEnc;
158}
159
160static inline BOOL dataHasUnicodeByteOrderMark(NSData *data)
161{
162 unsigned len = [data length];
163 size_t size = sizeof(UniChar);
164 BOOL rv = NO( BOOL ) 0;
165 if(len >= size){
166 const UniChar bigEndianBOM = 0xfeff;
167 const UniChar littleEndianBOM = 0xfffe;
168
169 UniChar possibleBOM = 0;
170 [data getBytes:&possibleBOM length:size];
171 rv = (possibleBOM == bigEndianBOM || possibleBOM == littleEndianBOM);
172 }
173 return rv;
174}
175
176- (NSString *)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)encoding guessEncoding:(BOOL)try;
177{
178 if(self = [self init]){
179 NSData *data = [[NSData alloc] initWithContentsOfFile:path options:NSMappedRead error:NULL( ( void * ) 0 )];
180
181 NSString *string = nil( ( void * ) 0 );
182 // zero encoding is never valid
183 if(encoding > 0)
184 string = [[NSString alloc] initWithData:data encoding:encoding];
185 // read com.apple.TextEncoding on Leopard, or when reading a Tiger file saved on Leopard
186 if(nil( ( void * ) 0 ) == string) {
187 encoding = [[NSFileManager defaultManager] appleStringEncodingAtPath:path error:NULL( ( void * ) 0 )];
188 if (encoding > 0)
189 string = [[NSString alloc] initWithData:data encoding:encoding];
190 }
191 // if the string is nil at this point, the passed in encoding was not valid/correct and com.apple.TextEncoding wasn't present
192 if(nil( ( void * ) 0 ) == string && try && dataHasUnicodeByteOrderMark(data) && encoding != NSUnicodeStringEncoding)
193 string = [[NSString alloc] initWithData:data encoding:NSUnicodeStringEncoding];
194 if(nil( ( void * ) 0 ) == string && try && encoding != NSUTF8StringEncoding)
195 string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
196 if(nil( ( void * ) 0 ) == string && try && encoding != [NSString defaultCStringEncoding])
197 string = [[NSString alloc] initWithData:data encoding:[NSString defaultCStringEncoding]];
198 if(nil( ( void * ) 0 ) == string && try && encoding != [BDSKStringEncodingManager defaultEncoding])
199 string = [[NSString alloc] initWithData:data encoding:[BDSKStringEncodingManager defaultEncoding]];
200 if(nil( ( void * ) 0 ) == string && try && encoding != NSISOLatin1StringEncoding)
201 string = [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding];
202
203 [data release];
204 [self release];
205 self = string;
206 }
207 return self;
208}
209
210
211#pragma mark TeX cleaning
212
213- (NSString *)stringByConvertingDoubleHyphenToEndash{
214 NSString *string = self;
215 NSString *doubleHyphen = @"--";
216 NSRange range = [self rangeOfString:doubleHyphen];
217 if (range.location != NSNotFound) {
218 NSMutableString *mutString = [[self mutableCopy] autorelease];
219 do {
220 [mutString replaceCharactersInRange:range withString:[NSString endashString]];
221 range = [mutString rangeOfString:doubleHyphen];
222 } while (range.location != NSNotFound);
223 string = mutString;
224 }
225 return string;
226}
227
228- (NSString *)stringByRemovingCurlyBraces{
229 return [self stringByRemovingCharactersInOFCharacterSet:[OFCharacterSet curlyBraceCharacterSet]];
230}
231
232- (NSString *)stringByRemovingTeX{
233 NSRange searchRange = NSMakeRange(0, [self length]);
234 NSRange foundRange = [self rangeOfTeXCommandInRange:searchRange];
235
236 if (foundRange.length == 0 && [self rangeOfCharacterFromSet:[NSCharacterSet curlyBraceCharacterSet]].length == 0)
237 return self;
238
239 NSMutableString *mutableString = [[self mutableCopy] autorelease];
240 while(foundRange.length){
241 [mutableString replaceCharactersInRange:foundRange withString:@""];
242 searchRange.length = NSMaxRange(searchRange) - NSMaxRange(foundRange);
243 searchRange.location = foundRange.location;
244 foundRange = [mutableString rangeOfTeXCommandInRange:searchRange];
245 }
246 [mutableString deleteCharactersInCharacterSet:[NSCharacterSet curlyBraceCharacterSet]];
247 return mutableString;
248}
249
250#pragma mark TeX parsing
251
252- (NSString *)entryType;
253{
254 // we could save a little memory by using a case-insensitive dictionary, but this is faster (and these strings are small)
255 static NSMutableDictionary *entryDictionary = nil( ( void * ) 0 );
256 if (nil( ( void * ) 0 ) == entryDictionary)
257 entryDictionary = [[NSMutableDictionary alloc] initWithCapacity:100];
258
259 NSString *entryType = [entryDictionary objectForKey:self];
260 if (nil( ( void * ) 0 ) == entryType) {
261 entryType = [self lowercaseString];
262 [entryDictionary setObject:entryType forKey:self];
263 }
264 return entryType;
265}
266
267- (NSString *)fieldName;
268{
269 // we could save a little memory by using a case-insensitive dictionary, but this is faster (and these strings are small)
270 static NSMutableDictionary *fieldDictionary = nil( ( void * ) 0 );
271 if (nil( ( void * ) 0 ) == fieldDictionary)
272 fieldDictionary = [[NSMutableDictionary alloc] initWithCapacity:100];
273
274 NSString *fieldName = [fieldDictionary objectForKey:self];
275 if (nil( ( void * ) 0 ) == fieldName) {
276 fieldName = [self capitalizedString];
277 [fieldDictionary setObject:fieldName forKey:self];
278 }
279 return fieldName;
280}
281
282- (NSString *)localizedFieldName;
283{
284 // this is used for display, for now we don't do anything
285 return self;
286}
287
288- (unsigned)indexOfRightBraceMatchingLeftBraceAtIndex:(unsigned int)startLoc
289{
290 return [self indexOfRightBraceMatchingLeftBraceInRange:NSMakeRange(startLoc, [self length] - startLoc)];
291}
292
293- (unsigned)indexOfRightBraceMatchingLeftBraceInRange:(NSRange)range
294{
295
296 CFStringInlineBuffer inlineBuffer;
297 CFIndex length = CFStringGetLength((CFStringRef)self);
298 CFIndex startLoc = range.location;
299 CFIndex endLoc = NSMaxRange(range);
300 CFIndex cnt;
301 BOOL matchFound = NO( BOOL ) 0;
302
303 CFStringInitInlineBuffer((CFStringRef)self, &inlineBuffer, CFRangeMake(0, length));
304 UniChar ch;
305 int nesting = 0;
306
307 if(CFStringGetCharacterFromInlineBuffer(&inlineBuffer, startLoc) != '{')
308 [NSException raise:NSInternalInconsistencyException format:@"character at index %i is not a brace", startLoc];
309
310 // we don't consider escaped braces yet
311 for(cnt = startLoc; cnt < endLoc; cnt++){
312 ch = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, cnt);
313 if(ch == '\\')
314 cnt++;
315 else if(ch == '{')
316 nesting++;
317 else if(ch == '}')
318 nesting--;
319 if(nesting == 0){
320 //NSLog(@"match found at index %i", cnt);
321 matchFound = YES( BOOL ) 1;
322 break;
323 }
324 }
325
326 return (matchFound == YES( BOOL ) 1) ? cnt : NSNotFound;
327}
328
329- (BOOL)isStringTeXQuotingBalancedWithBraces:(BOOL)braces connected:(BOOL)connected{
330 return [self isStringTeXQuotingBalancedWithBraces:braces connected:connected range:NSMakeRange(0,[self length])];
331}
332
333- (BOOL)isStringTeXQuotingBalancedWithBraces:(BOOL)braces connected:(BOOL)connected range:(NSRange)range{
334 int nesting = 0;
335 NSCharacterSet *delimCharSet;
336 unichar rightDelim;
337
338 if (braces) {
339 delimCharSet = [NSCharacterSet curlyBraceCharacterSet];
340 rightDelim = '}';
341 } else {
342 delimCharSet = [NSCharacterSet characterSetWithCharactersInString:@"\""];
343 rightDelim = '"';
344 }
345
346 NSRange delimRange = [self rangeOfCharacterFromSet:delimCharSet options:NSLiteralSearch range:range];
347 int delimLoc = delimRange.location;
348
349 while (delimLoc != NSNotFound) {
350 if (delimLoc == 0 || braces || [self characterAtIndex:delimLoc - 1] != '\\') {
351 // we found an unescaped delimiter
352 if (connected && nesting == 0) // connected quotes cannot have a nesting of 0 in the middle
353 return NO( BOOL ) 0;
354 if ([self characterAtIndex:delimLoc] == rightDelim) {
355 --nesting;
356 } else {
357 ++nesting;
358 }
359 if (nesting < 0) // we should never get a negative nesting
360 return NO( BOOL ) 0;
361 }
362 // set the range to the part of the range after the last found brace
363 range = NSMakeRange(delimLoc + 1, range.length - delimLoc + range.location - 1);
364 // search for the next brace
365 delimRange = [self rangeOfCharacterFromSet:delimCharSet options:NSLiteralSearch range:range];
366 delimLoc = delimRange.location;
367 }
368
369 return (nesting == 0);
370}
371
372// transforms a bibtex string to have temp cite keys, using the method in openWithPhoneyKeys.
373- (NSString *)stringWithPhoneyCiteKeys:(NSString *)tmpKey{
374 // ^(@[[:alpha:]]+{),?$ will grab either "@type{,eol" or "@type{eol", which is what we get
375 // from Bookends and EndNote, respectively.
376 AGRegex *theRegex = [AGRegex regexWithPattern:@"^([ \\t]*@[[:alpha:]]+[ \\t]*{)[ \\t]*,?$" options:AGRegexCaseInsensitive];
377
378 // should assert that the noKeysString matches theRegex
379 //NSAssert([theRegex findInString:self] != nil, @"stringWithPhoneyCiteKeys called on non-matching string");
380
381 // replace with "@type{FixMe,eol" (add the comma in, since we remove it if present)
382 NSCharacterSet *newlineCharacterSet = [NSCharacterSet newlineCharacterSet];
383
384 // do not use NSCharacterSets with OFStringScanners!
385 OFCharacterSet *newlineOFCharset = [[[OFCharacterSet alloc] initWithCharacterSet:newlineCharacterSet] autorelease];
386
387 OFStringScanner *scanner = [[[OFStringScanner alloc] initWithString:self] autorelease];
388 NSMutableString *mutableFileString = [NSMutableString stringWithCapacity:[self length]];
389 NSString *tmp = nil( ( void * ) 0 );
390 int scanLocation = 0;
391 NSString *replaceRegex = [NSString stringWithFormat:@"$1%@,", tmpKey];
392
393 // we scan up to an (newline@) sequence, then to a newline; we then replace only in that line using theRegex, which is much more efficient than using AGRegex to find/replace in the entire string
394 do {
395 // append the previous part to the mutable string
396 tmp = [scanner readFullTokenWithDelimiterCharacter:'@'];
397 if(tmp) [mutableFileString appendString:tmp];
398
399 scanLocation = scannerScanLocation(scanner);
400 if(scanLocation == 0 || [newlineCharacterSet characterIsMember:[self characterAtIndex:scanLocation - 1]]){
401
402 tmp = [scanner readFullTokenWithDelimiterOFCharacterSet:newlineOFCharset];
403
404 // if we read something between the @ and newline, see if we can do the regex find/replace
405 if(tmp){
406 // this should be a noop if the pattern isn't matched
407 tmp = [theRegex replaceWithString:replaceRegex inString:tmp];
408 [mutableFileString appendString:tmp]; // guaranteed non-nil result from AGRegex
409 }
410 } else
411 scannerReadCharacter(scanner);
412
413 } while(scannerHasData(scanner));
414
415 NSString *toReturn = [NSString stringWithString:mutableFileString];
416
417 return toReturn;
418}
419
420- (NSRange)rangeOfTeXCommandInRange:(NSRange)searchRange;
421{
422 static CFCharacterSetRef nonLetterCharacterSet = NULL( ( void * ) 0 );
423
424 if (NULL( ( void * ) 0 ) == nonLetterCharacterSet) {
425 CFMutableCharacterSetRef letterCFCharacterSet = CFCharacterSetCreateMutableCopy(CFAllocatorGetDefault(), CFCharacterSetGetPredefined(kCFCharacterSetLetter));
426 CFCharacterSetInvert(letterCFCharacterSet);
427 nonLetterCharacterSet = CFCharacterSetCreateCopy(CFAllocatorGetDefault(), letterCFCharacterSet);
428 CFRelease(letterCFCharacterSet);
429 }
430
431 CFRange bsSearchRange = *(CFRange*)&searchRange;
432 CFRange cmdStartRange, cmdEndRange;
433 CFIndex endLoc = NSMaxRange(searchRange);
434
435 while(bsSearchRange.length > 4 && BDStringFindCharacter((CFStringRef)self, '\\', bsSearchRange, &cmdStartRange) &&
436 CFStringFindCharacterFromSet((CFStringRef)self, nonLetterCharacterSet, CFRangeMake(cmdStartRange.location + 1, endLoc - cmdStartRange.location - 1), 0, &cmdEndRange)){
437 // if the char right behind the backslash is a non-letter char, it's a one-letter command
438 if(cmdEndRange.location == cmdStartRange.location + 1)
439 cmdEndRange.location++;
440 // see if we found a left brace, we ignore commands like \LaTeX{} which we want to keep
441 if('{' == CFStringGetCharacterAtIndex((CFStringRef)self, cmdEndRange.location) &&
442 '}' != CFStringGetCharacterAtIndex((CFStringRef)self, cmdEndRange.location + 1))
443 return NSMakeRange(cmdStartRange.location, cmdEndRange.location - cmdStartRange.location);
444
445 bsSearchRange = CFRangeMake(cmdEndRange.location, endLoc - cmdEndRange.location);
446 }
447
448 return NSMakeRange(NSNotFound, 0);
449}
450
451- (NSString *)stringByBackslashEscapingTeXSpecials;
452{
453 static NSCharacterSet *charSet = nil( ( void * ) 0 );
454 // We could really go crazy with this, but the main need is to escape characters that commonly appear in titles and journal names when importing from z39.50 and other non-RIS/non-BibTeX search group sources. Those sources aren't processed by the HTML->TeX path that's used for RIS, since they generally don't have embedded HTML.
455 if (nil( ( void * ) 0 ) == charSet)
456 charSet = [[NSCharacterSet characterSetWithCharactersInString:@"%&"] copy];
457 return [self stringByBackslashEscapingCharactersInSet:charSet];
458}
459
460- (NSString *)stringByBackslashEscapingCharactersInSet:(NSCharacterSet *)charSet;
461{
462 NSRange r = [self rangeOfCharacterFromSet:charSet options:NSLiteralSearch];
463 if (r.location == NSNotFound)
464 return self;
465
466 NSMutableString *toReturn = [self mutableCopy];
467 while (r.length) {
468 unsigned start;
469 if (r.location == 0 || [toReturn characterAtIndex:(r.location - 1)] != '\\') {
470 // insert the backslash, then advance the search range by two characters
471 [toReturn replaceCharactersInRange:NSMakeRange(r.location, 0) withString:@"\\"];
472 start = r.location + 2;
473 r = [toReturn rangeOfCharacterFromSet:charSet options:NSLiteralSearch range:NSMakeRange(start, [toReturn length] - start)];
474 } else {
475 // this one was already escaped, so advance a character and repeat the search, unless that puts us over the end
476 if (r.location < [toReturn length]) {
477 start = r.location + 1;
478 r = [toReturn rangeOfCharacterFromSet:charSet options:NSLiteralSearch range:NSMakeRange(start, [toReturn length] - start)];
479 } else {
480 r = NSMakeRange(NSNotFound, 0);
481 }
482 }
483 }
484 return [toReturn autorelease];
485}
486
487- (NSString *)stringByConvertingHTMLToTeX;
488{
489 static NSCharacterSet *asciiSet = nil( ( void * ) 0 );
490 if(asciiSet == nil( ( void * ) 0 ))
491 asciiSet = [[NSCharacterSet characterSetWithRange:NSMakeRange(0, 127)] retain];
492
493 // set these up here, so we don't autorelease them every time we parse an entry
494 // Some entries from Compendex have spaces in the tags, which is why we match 0-1 spaces between each character.
495 static AGRegex *findSubscriptLeadingTag = nil( ( void * ) 0 );
496 if(findSubscriptLeadingTag == nil( ( void * ) 0 ))
497 findSubscriptLeadingTag = [[AGRegex alloc] initWithPattern:@"< ?s ?u ?b ?>"];
498 static AGRegex *findSubscriptOrSuperscriptTrailingTag = nil( ( void * ) 0 );
499 if(findSubscriptOrSuperscriptTrailingTag == nil( ( void * ) 0 ))
500 findSubscriptOrSuperscriptTrailingTag = [[AGRegex alloc] initWithPattern:@"< ?/ ?s ?u ?[bp] ?>"];
501 static AGRegex *findSuperscriptLeadingTag = nil( ( void * ) 0 );
502 if(findSuperscriptLeadingTag == nil( ( void * ) 0 ))
503 findSuperscriptLeadingTag = [[AGRegex alloc] initWithPattern:@"< ?s ?u ?p ?>"];
504
505 // This one might require some explanation. An entry with TI of "Flapping flight as a bifurcation in Re<sub>&omega;</sub>"
506 // was run through the html conversion to give "...Re<sub>$\omega$</sub>", then the find sub/super regex replaced the sub tags to give
507 // "...Re$_$omega$$", which LaTeX barfed on. So, we now search for <sub></sub> tags with matching dollar signs inside, and remove the inner
508 // dollar signs, since we'll use the dollar signs from our subsequent regex search and replace; however, we have to
509 // reject the case where there is a <sub><\sub> by matching [^<]+ (at least one character which is not <), or else it goes to the next </sub> tag
510 // and deletes dollar signs that it shouldn't touch. Yuck.
511 static AGRegex *findNestedDollar = nil( ( void * ) 0 );
512 if(findNestedDollar == nil( ( void * ) 0 ))
513 findNestedDollar = [[AGRegex alloc] initWithPattern:@"(< ?s ?u ?[bp] ?>[^<]+)(\\$)(.*)(\\$)(.*< ?/ ?s ?u ?[bp] ?>)"];
514
515 // Run the value string through the HTML2LaTeX conversion, to clean up &theta; and friends.
516 // NB: do this before the regex find/replace on <sub> and <sup> tags, or else your LaTeX math
517 // stuff will get munged. Unfortunately, the C code for HTML2LaTeX will destroy accented characters, so we only send it ASCII, and just keep
518 // the accented characters to let BDSKConverter deal with them later.
519
520 NSScanner *scanner = [[NSScanner alloc] initWithString:self];
521 NSString *asciiAndHTMLChars, *nonAsciiAndHTMLChars;
522 NSMutableString *fullString = [[NSMutableString alloc] initWithCapacity:[self length]];
523
524 while(![scanner isAtEnd]){
525 if([scanner scanCharactersFromSet:asciiSet intoString:&asciiAndHTMLChars])
526 [fullString appendString:[NSString TeXStringWithHTMLString:asciiAndHTMLChars ]];
527 if([scanner scanUpToCharactersFromSet:asciiSet intoString:&nonAsciiAndHTMLChars])
528 [fullString appendString:nonAsciiAndHTMLChars];
529 }
530 [scanner release];
531
532 NSString *newValue = [[fullString copy] autorelease];
533 [fullString release];
534
535 // see if we have nested math modes and try to fix them; see note earlier on findNestedDollar
536 if([findNestedDollar findInString:newValue] != nil( ( void * ) 0 )){
537 NSLog(@"WARNING: found nested math mode; trying to repair...");
538 newValue = [findNestedDollar replaceWithString:@"$1$3$5"
539 inString:newValue];
540 }
541
542 // Do a regex find and replace to put LaTeX subscripts and superscripts in place of the HTML
543 // that Compendex (and possibly others) give us.
544 newValue = [findSubscriptLeadingTag replaceWithString:@"\\$_{" inString:newValue];
545 newValue = [findSuperscriptLeadingTag replaceWithString:@"\\$^{" inString:newValue];
546 newValue = [findSubscriptOrSuperscriptTrailingTag replaceWithString:@"}\\$" inString:newValue];
547
548 return newValue;
549}
550
551+ (NSString *)TeXStringWithHTMLString:(NSString *)htmlString;
552{
553 const char *str = [htmlString UTF8String];
554 int ln = strlen(str);
555 FILE *freport = stdout__stdoutp;
556 char *html_fn = NULL( ( void * ) 0 );
557 BOOL in_math = NO( BOOL ) 0;
558 BOOL in_verb = NO( BOOL ) 0;
559 BOOL in_alltt = NO( BOOL ) 0;
560
561/* ARM: this code was taken directly from HTML2LaTeX. I modified it to return
562 an NSString object, since working with FILE* streams led to really nasty problems
563 with NSPipe needing asynchronous reads to avoid blocking.
564 The NSMutableString appendFormat method was used to replace all of the calls to
565 fputc, fprintf, and fputs.
566
567 Frans Faase, the author of HTML2LaTeX, gave permission to include this in BibDesk under the BSD license.
568 The following copyright notice was taken verbatim from the HTML2LaTeX code:
569
570HTML2LaTeX -- Converting HTML files to LaTeX
571Copyright (C) 1995-2003 Frans Faase
572
573This program is free software; you can redistribute it and/or modify
574it under the terms of the GNU General Public License as published by
575the Free Software Foundation; either version 2 of the License, or
576(at your option) any later version.
577
578This program is distributed in the hope that it will be useful,
579but WITHOUT ANY WARRANTY; without even the implied warranty of
580MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
581GNU General Public License for more details.
582
583You should have received a copy of the GNU General Public License
584along with this program; if not, write to the Free Software
585Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
586
587GNU General Public License:
588http://home.planet.nl/~faase009/GNU.txt
589*/
590
591
592 NSMutableString *mString = [NSMutableString stringWithCapacity:ln];
593
594 BOOL option_warn = YES( BOOL ) 1;
595
596 for(; *str; str++)
597 { BOOL special = NO( BOOL ) 0;
598 int v = 0;
599 char ch = '\0';
600 char html_ch[10];
601 html_ch[0] = '\0';
602
603 if (*str == '&')
604 { int i = 0;
605 BOOL correct = NO( BOOL ) 0;
606
607 if (isalpha(str[1]))
608 { for (i = 0; i < 9; i++)
609 if (isalpha(str[i+1]))
610 html_ch[i] = str[i+1];
611 else
612 break;
613 html_ch[i] = '\0';
614 for (v = 0; v < NR_CH_TABLE170; v++)
615 if ( ch_table[v].html_ch != NULL( ( void * ) 0 )
616 && !strcmp(html_ch, ch_table[v].html_ch))
617 { special = YES( BOOL ) 1;
618 correct = YES( BOOL ) 1;
619 ch = ch_table[v].ch;
620 break;
621 }
622 }
623 else if (str[1] == '#')
624 { int code = 0;
625 html_ch[0] = '#';
626 for (i = 1; i < 9; i++)
627 if (isdigit(str[i+1]))
628 { html_ch[i] = str[i+1];
629 code = code * 10 + str[i+1] - '0';
630 }
631 else
632 break;
633 if ((code >= ' ' && code < 127) || code == 8)
634 { correct = YES( BOOL ) 1;
635 ch = code;
636 }
637 else if (code >= 160 && code <= 255)
638 {
639 correct = YES( BOOL ) 1;
640 special = YES( BOOL ) 1;
641 v = code - 160;
642 ch = ch_table[v].ch;
643 }
644 }
645 html_ch[i] = '\0';
646
647 if (correct)
648 { str += i;
649 if (str[1] == ';')
650 str++;
651 }
652 else
653 { if (freport != NULL( ( void * ) 0 ) && option_warn)
654 if (html_ch[0] == '\0')
655 fprintf(freport,
656 "%s (%d) : Replace `&' by `&amp;'.\n",
657 html_fn, ln);
658 else
659 fprintf(freport,
660 "%s (%d) : Unknown sequence `&%s;'.\n",
661 html_fn, ln, html_ch);
662 ch = *str;
663 }
664 }
665 else if (((unsigned char)*str >= ' ' && (unsigned char)*str <= HIGHASCII126) || *str == '\t')
666 ch = *str;
667 else if (option_warn && freport != NULL( ( void * ) 0 ))
668 fprintf(freport,
669 "%s (%d) : Unknown character %d (decimal)\n",
670 html_fn, ln, (unsigned char)*str);
671 if (mString)
672 { if (in_verb)
673 {
674 [mString appendFormat:@"%c", ch != '\0' ? ch : ' '];
675 if ( special && freport != NULL( ( void * ) 0 ) && option_warn
676 && v < NR_CH_M159)
677 { fprintf(freport, "%s (%d) : ", html_fn, ln);
678 if (html_ch[0] == '\0')
679 fprintf(freport, "character %d (decimal)",
680 (unsigned char) *str);
681 else
682 fprintf(freport, "sequence `&%s;'", html_ch);
683 fprintf(freport, " rendered as `%c' in verbatim\n",
684 ch != '\0' ? ch : ' ');
685 }
686 }
687 else if (in_alltt)
688 { if (special)
689 { char *o = ch_table[v].tex_ch;
690 if (o != NULL( ( void * ) 0 ))
691 if (*o == '$')
692 [mString appendFormat:@"\\(%s\\)", o + 1];
693 else
694 [mString appendFormat:@"%s", o];
695 }
696 else if (ch == '{' || ch == '}')
697 [mString appendFormat:@"\\%c", ch];
698 else if (ch == '\\')
699 [mString appendFormat:@"\\%c", ch];
700 else if (ch != '\0')
701 [mString appendFormat:@"%c", ch];
702 }
703 else if (special)
704 { char *o = ch_table[v].tex_ch;
705 if (o == NULL( ( void * ) 0 ))
706 { if (freport != NULL( ( void * ) 0 ) && option_warn)
707 { fprintf(freport,
708 "%s (%d) : no LaTeX representation for ",
709 html_fn, ln);
710 if (html_ch[0] == '\0')
711 fprintf(freport, "character %d (decimal)\n",
712 (unsigned char) *str);
713 else
714 fprintf(freport, "sequence `&%s;'\n", html_ch);
715 }
716 }
717 else if (*o == '$')
718 if (in_math)
719 [mString appendFormat:@"%s", o+1];
720 else
721 [mString appendFormat:@"{%s$}", o];
722 else
723 [mString appendFormat:@"%s", o];
724 }
725 else if (in_math)
726 { if (ch == '#' || ch == '%')
727 [mString appendFormat:@"\\%c", ch];
728 else
729 [mString appendFormat:@"%c", ch];
730 }
731 else
732 { switch(ch)
733 { case '\0' : break;
734 case '\t': [mString appendString:@" "]; break;
735 case '_': case '{': case '}':
736 case '#': case '$': case '%':
737 [mString appendFormat:@"{\\%c}", ch]; break;
738 case '@' : [mString appendFormat:@"{\\char64}"]; break;
739 case '[' :
740 case ']' : [mString appendFormat:@"{$%c$}", ch]; break;
741 case '~' : [mString appendString:@"\\~{}"]; break;
742 case '^' : [mString appendString:@"\\^{}"]; break;
743 case '|' : [mString appendString:@"{$|$}"]; break;
744 case '\\': [mString appendString:@"{$\\backslash$}"]; break;
745 case '&' : [mString appendString:@"\\&"]; break;
746 default: [mString appendFormat:@"%c", ch]; break;
747 }
748 }
749 }
750 }
751 return mString;
752}
753
754- (NSArray *)sourceLinesBySplittingString;
755{
756 // ARM: This code came from Art Isbell to cocoa-dev on Tue Jul 10 22:13:11 2001. Comments are his.
757 // We were using componentsSeparatedByString:@"\r", but this is not robust. Files from ScienceDirect
758 // have \n as newlines, so this code handles those cases as well as PubMed.
759 unsigned stringLength = [self length];
760 unsigned startIndex;
761 unsigned lineEndIndex = 0;
762 unsigned contentsEndIndex;
763 NSRange range;
764 NSMutableArray *sourceLines = [NSMutableArray array];
765
766 // There is more than one way to terminate this loop. Beware of an
767 // invalid termination test which might exist in this untested example :-)
768 while (lineEndIndex < stringLength)
769 {
770 // Include only a single character in range. Not sure whether
771 // this will work with empty lines, but if not, try a length of 0.
772 range = NSMakeRange(lineEndIndex, 1);
773 [self getLineStart:&startIndex
774 end:&lineEndIndex
775 contentsEnd:&contentsEndIndex
776 forRange:range];
777
778 // If you want to exclude line terminators...
779 [sourceLines addObject:[self substringWithRange:NSMakeRange(startIndex, contentsEndIndex - startIndex)]];
780 }
781 return sourceLines;
782}
783
784- (NSString *)stringByEscapingGroupPlistEntities{
785 NSMutableString *escapedValue = [self mutableCopy];
786 // escape braces as they can give problems with btparse
787 [escapedValue replaceAllOccurrencesOfString:@"%" withString:@"%25"]; // this should come first
788 [escapedValue replaceAllOccurrencesOfString:@"{" withString:@"%7B"];
789 [escapedValue replaceAllOccurrencesOfString:@"}" withString:@"%7D"];
790 [escapedValue replaceAllOccurrencesOfString:@"<" withString:@"%3C"];
791 [escapedValue replaceAllOccurrencesOfString:@">" withString:@"%3E"];
792 return [escapedValue autorelease];
793}
794
795- (NSString *)stringByUnescapingGroupPlistEntities{
796 NSMutableString *escapedValue = [self mutableCopy];
797 // escape braces as they can give problems with btparse, and angles as they can give problems with the plist xml
798 [escapedValue replaceAllOccurrencesOfString:@"%7B" withString:@"{"];
799 [escapedValue replaceAllOccurrencesOfString:@"%7D" withString:@"}"];
800 [escapedValue replaceAllOccurrencesOfString:@"%3C" withString:@"<"];
801 [escapedValue replaceAllOccurrencesOfString:@"%3E" withString:@">"];
802 [escapedValue replaceAllOccurrencesOfString:@"%25" withString:@"%"]; // this should come last
803 return [escapedValue autorelease];
804}
805
806- (NSString *)lossyASCIIString{
807 return [[[NSString alloc] initWithData:[self dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES( BOOL ) 1] encoding:NSASCIIStringEncoding] autorelease];
808}
809
810#pragma mark Comparisons
811
812- (NSComparisonResult)localizedCaseInsensitiveNumericCompare:(NSString *)aStr{
813 return [self compare:aStr
814 options:NSCaseInsensitiveSearch | NSNumericSearch
815 range:NSMakeRange(0, [self length])
816 locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]];
817}
818
819// -[NSString compare: options:NSNumericSearch] is buggy for string literals (tested on 10.4.3), but CFStringCompare() works and returns the same comparison constants
820- (NSComparisonResult)numericCompare:(NSString *)otherString{
821 return CFStringCompare((CFStringRef)self, (CFStringRef)otherString, kCFCompareNumerically);
822}
823
824- (NSString *)stringByRemovingTeXAndStopWords;
825{
826 CFMutableStringRef modifiedSelf = CFStringCreateMutableCopy(CFAllocatorGetDefault(), CFStringGetLength((CFStringRef)self), (CFStringRef)self);
827 BDDeleteArticlesForSorting(modifiedSelf);
828 BDDeleteTeXForSorting(modifiedSelf);
829 return [(id)modifiedSelf autorelease];
830}
831
832- (NSComparisonResult)localizedCaseInsensitiveNonTeXNonArticleCompare:(NSString *)otherString;
833{
834
835 // Check before passing to CFStringCompare, as a nil argument causes a crash. The caller has to handle nil comparisons.
836 NSParameterAssertdo { if ( ! ( ( otherString != ( ( void * ) 0 ) ) ) ) { [ [ NSAssertionHandler
currentHandler ] handleFailureInMethod : _cmd object : self file
: [ NSString stringWithUTF8String : "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/NSString_BDSKExtensions.m"
] lineNumber : 836 description : ( @ "Invalid parameter not satisfying: %s"
) , ( "otherString != nil" ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 )
] ; } } while ( 0 )
(otherString != nil);
837
838 CFAllocatorRef allocator = CFAllocatorGetDefault();
839 CFMutableStringRef modifiedSelf = CFStringCreateMutableCopy(allocator, CFStringGetLength((CFStringRef)self), (CFStringRef)self);
840 CFMutableStringRef modifiedOther = CFStringCreateMutableCopy(allocator, CFStringGetLength((CFStringRef)otherString), (CFStringRef)otherString);
841
842 BDDeleteArticlesForSorting(modifiedSelf);
843 BDDeleteArticlesForSorting(modifiedOther);
844 BDDeleteTeXForSorting(modifiedSelf);
845 BDDeleteTeXForSorting(modifiedOther);
846
847 // the mutating functions above should only create an empty string, not a nil string
848 OBASSERTdo { if ( ! ( modifiedSelf != ( ( void * ) 0 ) ) ) OBAssertFailed
( "ASSERT" , "modifiedSelf != nil" , "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/NSString_BDSKExtensions.m"
, 848 ) ; } while ( ( BOOL ) 0 )
(modifiedSelf != nil);
849 OBASSERTdo { if ( ! ( modifiedOther != ( ( void * ) 0 ) ) ) OBAssertFailed
( "ASSERT" , "modifiedOther != nil" , "/Volumes/Local/Users/amaxwell/build/bibdesk-clean/NSString_BDSKExtensions.m"
, 849 ) ; } while ( ( BOOL ) 0 )
(modifiedOther != nil);
850
851 // CFComparisonResult returns same values as NSComparisonResult
852 CFComparisonResult result = CFStringCompare(modifiedSelf, modifiedOther, kCFCompareCaseInsensitive | kCFCompareLocalized);
853 CFRelease(modifiedSelf);
854 CFRelease(modifiedOther);
855
856 return result;
857}
858
859- (NSComparisonResult)sortCompare:(NSString *)other{
860 BOOL otherIsEmpty = [NSString isEmptyString:other];
861 if ([self isEqualToString:@""]) {
862 return (otherIsEmpty)? NSOrderedSame : NSOrderedDescending;
863 } else if (otherIsEmpty) {
864 return NSOrderedAscending;
865 }
866 return [self localizedCaseInsensitiveNumericCompare:other];
867}
868
869- (NSComparisonResult)extensionCompare:(NSString *)other{
870 NSString *myExtension = [self pathExtension];
871 NSString *otherExtension = [other pathExtension];
872 BOOL otherIsEmpty = [NSString isEmptyString:otherExtension];
873 if ([myExtension isEqualToString:@""])
874 return otherIsEmpty ? NSOrderedSame : NSOrderedDescending;
875 if (otherIsEmpty)
876 return NSOrderedAscending;
877 return [myExtension localizedCaseInsensitiveCompare:otherExtension];
878}
879
880- (NSComparisonResult)triStateCompare:(NSString *)other{
881 // we order increasingly as 0, -1, 1
882 int myValue = [self triStateValue];
883 int otherValue = [other triStateValue];
884 if (myValue == otherValue)
885 return NSOrderedSame;
886 else if (myValue == 0 || otherValue == 1)
887 return NSOrderedAscending;
888 else
889 return NSOrderedDescending;
890}
891
892static NSURL *CreateFileURLFromPathOrURLString(NSString *aPath, NSString *basePath)
893{
894 // default return values
895 NSURL *fileURL = nil( ( void * ) 0 );
896
897 if ([aPath hasPrefix:@"file://"]) {
898 fileURL = [[NSURL alloc] initWithString:aPath];
899 } else if ([aPath length]) {
900 unichar ch = [aPath characterAtIndex:0];
901 if ('/' != ch && '~' != ch)
902 aPath = [basePath stringByAppendingPathComponent:aPath];
903 if (aPath)
904 fileURL = [[NSURL alloc] initFileURLWithPath:[aPath stringByStandardizingPath]];
905 }
906 return fileURL;
907}
908
909static NSString *UTIForPathOrURLString(NSString *aPath, NSString *basePath)
910{
911 NSString *theUTI = nil( ( void * ) 0 );
912 NSURL *fileURL = nil( ( void * ) 0 );
913 // !!! We return nil when a file doesn't exist if it's a properly resolvable path/URL, but we have no way of checking existence with a relative path. Returning nil is preferable, since then nonexistent files will be sorted to the top or bottom and they're easy to find.
914 if (fileURL = CreateFileURLFromPathOrURLString(aPath, basePath)) {
915 // UTI will be nil for a file that doesn't exist, yet had an absolute/resolvable path
916 if (fileURL) {
917 theUTI = [[NSWorkspace sharedWorkspace] UTIForURL:fileURL error:NULL( ( void * ) 0 )];
918 [fileURL release];
919 }
920
921 } else {
922
923 // fall back to extension; this is probably a relative path, so we'll assume it exists
924 NSString *extension = [aPath pathExtension];
925 if ([extension isEqualToString:@""] == NO( BOOL ) 0)
926 theUTI = [[NSWorkspace sharedWorkspace] UTIForPathExtension:extension];
927 }
928 return theUTI;
929}
930
931- (NSComparisonResult)UTICompare:(NSString *)other{
932 return [self UTICompare:other basePath:nil( ( void * ) 0 )];
933}
934
935- (NSComparisonResult)UTICompare:(NSString *)other basePath:(NSString *)basePath{
936 NSString *otherUTI = UTIForPathOrURLString(other, basePath);
937 NSString *selfUTI = UTIForPathOrURLString(self, basePath);
938 if (nil( ( void * ) 0 ) == selfUTI)
939 return (nil( ( void * ) 0 ) == otherUTI ? NSOrderedSame : NSOrderedDescending);
940 if (nil( ( void * ) 0 ) == otherUTI)
941 return NSOrderedAscending;
942 return [selfUTI caseInsensitiveCompare:otherUTI];
943}
944
945#pragma mark -
946
947- (BOOL)booleanValue{
948 // Omni's boolValue method uses YES, Y, yes, y and 1 with isEqualToString
949 if([self compare:[NSString stringWithBool:YES( BOOL ) 1] options:NSCaseInsensitiveSearch] == NSOrderedSame ||
950 [self compare:@"y" options:NSCaseInsensitiveSearch] == NSOrderedSame ||
951 [self compare:@"yes" options:NSCaseInsensitiveSearch] == NSOrderedSame ||
952 [self isEqualToString:@"1"])
953 return YES( BOOL ) 1;
954 else
955 return NO( BOOL ) 0;
956}
957
958- (NSCellStateValue)triStateValue{
959 if([self booleanValue] == YES( BOOL ) 1){
960 return NSOnState;
961 }else if([self isEqualToString:@""] ||
962 [self compare:[NSString stringWithBool:NO( BOOL ) 0] options:NSCaseInsensitiveSearch] == NSOrderedSame ||
963 [self compare:@"n" options:NSCaseInsensitiveSearch] == NSOrderedSame ||
964 [self compare:@"no" options:NSCaseInsensitiveSearch] == NSOrderedSame ||
965 [self isEqualToString:@"0"]){
966 return NSOffState;
967 }else{
968 return NSMixedState;
969 }
970}
971
972- (NSString *)acronymValueIgnoringWordLength:(unsigned int)ignoreLength{
973 NSMutableString *result = [NSMutableString string];
974 NSArray *allComponents = [self componentsSeparatedByString:@" "]; // single whitespace
975 NSEnumerator *e = [allComponents objectEnumerator];
976 NSString *component = nil( ( void * ) 0 );
977 unsigned int currentIgnoreLength;
978
979 while(component = [e nextObject]){
980 currentIgnoreLength = ignoreLength;
981 if(![component isEqualToString:@""]) // stringByTrimmingCharactersInSet will choke on an empty string
982 component = [component stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
983 if([component length] > 1 && [component characterAtIndex:[component length] - 1] == '.')
984 currentIgnoreLength = 0;
985 if(![component isEqualToString:@""])
986 component = [component stringByTrimmingCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]];
987 if([component length] > currentIgnoreLength){
988 [result appendString:[[component substringToIndex:1] uppercaseString]];
989 }
990 }
991 return result;
992}
993
994#pragma mark -
995
996- (BOOL)containsString:(NSString *)searchString options:(unsigned int)mask range:(NSRange)aRange{
997 return !searchString || [searchString length] == 0 || [self rangeOfString:searchString options:mask range:aRange].length > 0;
998}
999
1000- (BOOL)containsWord:(NSString *)aWord{
1001
1002 NSRange subRange = [self rangeOfString:aWord];
1003
1004 if(subRange.location == NSNotFound)
1005 return NO( BOOL ) 0;
1006
1007 CFIndex wordLength = [aWord length];
1008 CFIndex myLength = [self length];
1009
1010 // trivial case; we contain the word, and have the same length
1011 if(myLength == wordLength)
1012 return YES( BOOL ) 1;
1013
1014 CFIndex beforeIndex, afterIndex;
1015
1016 beforeIndex = subRange.location - 1;
1017 afterIndex = NSMaxRange(subRange);
1018
1019 UniChar beforeChar = '\0', afterChar = '\0';
1020
1021 if(beforeIndex >= 0)
1022 beforeChar = [self characterAtIndex:beforeIndex];
1023
1024 if(afterIndex < myLength)
1025 afterChar = [self characterAtIndex:afterIndex];
1026
1027 static NSCharacterSet *wordTestSet = nil( ( void * ) 0 );
1028 if(wordTestSet == nil( ( void * ) 0 )){
1029 NSMutableCharacterSet *set = [[NSCharacterSet punctuationCharacterSet] mutableCopy];
1030 [set formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
1031 wordTestSet = [set copy];
1032 [set release];
1033 }
1034
1035 // if a character appears before the start of the substring match, see if it is punctuation or whitespace
1036 if(beforeChar && [wordTestSet characterIsMember:beforeChar] == NO( BOOL ) 0)
1037 return NO( BOOL ) 0;
1038
1039 // now check after the substring match
1040 if(afterChar && [wordTestSet characterIsMember:afterChar] == NO( BOOL ) 0)
1041 return NO( BOOL ) 0;
1042
1043 return YES( BOOL ) 1;
1044}
1045
1046- (BOOL)hasCaseInsensitivePrefix:(NSString *)prefix;
1047{
1048 unsigned int length = [prefix length];
1049 if(prefix == nil( ( void * ) 0 ) || length > [self length])
1050 return NO( BOOL ) 0;
1051
1052 return (CFStringCompareWithOptions((CFStringRef)self,(CFStringRef)prefix, CFRangeMake(0, length), kCFCompareCaseInsensitive) == kCFCompareEqualTo ? YES( BOOL ) 1 : NO( BOOL ) 0);
1053}
1054
1055#pragma mark -
1056
1057- (NSArray *)componentsSeparatedByCharactersInSet:(NSCharacterSet *)charSet trimWhitespace:(BOOL)trim;
1058{
1059 return [(id)BDStringCreateComponentsSeparatedByCharacterSetTrimWhitespace(CFAllocatorGetDefault(), (CFStringRef)self, (CFCharacterSetRef)charSet, trim) autorelease];
1060}
1061
1062- (NSArray *)componentsSeparatedByStringCaseInsensitive:(NSString *)separator;
1063{
1064 return [(id)BDStringCreateArrayBySeparatingStringsWithOptions(CFAllocatorGetDefault(), (CFStringRef)self, (CFStringRef)separator, kCFCompareCaseInsensitive) autorelease];
1065}
1066
1067- (NSArray *)componentsSeparatedByFieldSeparators;
1068{
1069 NSCharacterSet *acSet = [[BDSKTypeManager sharedManager] separatorCharacterSetForField:BDSKKeywordsString];
1070 if([self containsCharacterInSet:acSet])
1071 return [self componentsSeparatedByCharactersInSet:acSet trimWhitespace:YES( BOOL ) 1];
1072 else
1073 return [self componentsSeparatedByStringCaseInsensitive:@" and "];
1074}
1075
1076- (NSArray *)componentsSeparatedByAnd;
1077{
1078 return [self componentsSeparatedByStringCaseInsensitive:@" and "];
1079}
1080
1081- (NSArray *)componentsSeparatedByComma;
1082{
1083 return [self componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@","] trimWhitespace:YES( BOOL ) 1];
1084}
1085
1086- (NSString *)fastStringByCollapsingWhitespaceAndRemovingSurroundingWhitespace;
1087{
1088 return [(id)BDStringCreateByCollapsingAndTrimmingWhitespace(CFAllocatorGetDefault(), (CFStringRef)self) autorelease];
1089}
1090
1091- (NSString *)fastStringByCollapsingWhitespaceAndNewlinesAndRemovingSurroundingWhitespaceAndNewlines;
1092{
1093 return [(id)BDStringCreateByCollapsingAndTrimmingWhitespaceAndNewlines(CFAllocatorGetDefault(), (CFStringRef)self) autorelease];
1094}
1095
1096- (NSString *)stringByNormalizingSpacesAndLineBreaks;
1097{
1098 return [(id)BDStringCreateByNormalizingWhitespaceAndNewlines(CFAllocatorGetDefault(), (CFStringRef)self) autorelease];
1099}
1100
1101- (NSString *)stringByAppendingEllipsis{
1102 return [self stringByAppendingString:[NSString horizontalEllipsisString]];
1103}
1104
1105- (NSString *)stringBySurroundingWithSpacesIfNotEmpty
1106{
1107 return [self isEqualToString:@""] ? self : [NSString stringWithFormat:@" %@ ", self];
1108}
1109
1110- (NSString *)stringByAppendingSpaceIfNotEmpty
1111{
1112 return [self isEqualToString:@""] ? self : [self stringByAppendingString:@" "];
1113}
1114
1115- (NSString *)stringByAppendingDoubleSpaceIfNotEmpty
1116{
1117 return [self isEqualToString:@""] ? self : [self stringByAppendingString:@" "];
1118}
1119
1120- (NSString *)stringByPrependingSpaceIfNotEmpty
1121{
1122 return [self isEqualToString:@""] ? self : [NSString stringWithFormat:@" %@", self];
1123}
1124
1125- (NSString *)stringByAppendingCommaIfNotEmpty
1126{
1127 return [self isEqualToString:@""] ? self : [self stringByAppendingString:@","];
1128}
1129
1130- (NSString *)stringByAppendingFullStopIfNotEmpty
1131{
1132 return [self isEqualToString:@""] ? self : [self stringByAppendingString:@"."];
1133}
1134
1135- (NSString *)stringByAppendingCommaAndSpaceIfNotEmpty
1136{
1137 return [self isEqualToString:@""] ? self : [self stringByAppendingString:@", "];
1138}
1139
1140- (NSString *)stringByAppendingFullStopAndSpaceIfNotEmpty
1141{
1142 return [self isEqualToString:@""] ? self : [self stringByAppendingString:@". "];
1143}
1144
1145- (NSString *)stringByPrependingCommaAndSpaceIfNotEmpty
1146{
1147 return [self isEqualToString:@""] ? self : [NSString stringWithFormat:@", %@", self];
1148}
1149
1150- (NSString *)stringByPrependingFullStopAndSpaceIfNotEmpty
1151{
1152 return [self isEqualToString:@""] ? self : [NSString stringWithFormat:@". %@", self];
1153}
1154
1155- (NSString *)quotedStringIfNotEmpty
1156{
1157 return [self isEqualToString:@""] ? self : [NSString stringWithFormat:@"\"%@\"", self];
1158}
1159
1160- (NSString *)parenthesizedStringIfNotEmpty
1161{
1162 return [self isEqualToString:@""] ? self : [NSString stringWithFormat:@"(%@)", self];
1163}
1164
1165- (NSString *)titlecaseString;
1166{
1167 CFAllocatorRef alloc = CFGetAllocator((CFStringRef)self);
[1] Function call returns an object with a +1 retain count (owning reference).
1168 CFMutableStringRef mutableString = CFStringCreateMutableCopy(alloc, 0, (CFStringRef)self);
1169 CFLocaleRef locale = CFLocaleCopyCurrent();
1170 CFStringCapitalize(mutableString, locale);
1171 CFRelease(locale);
1172
[2] Object allocated on line 1168 and stored into 'mutableString' is no longer referenced after this point and has a retain count of +1 (object leaked).
1173 CFArrayRef comp = BDStringCreateComponentsSeparatedByCharacterSetTrimWhitespace(alloc, mutableString, CFCharacterSetGetPredefined(kCFCharacterSetWhitespace), TRUE1);
1174 NSMutableArray *words = nil( ( void * ) 0 );
1175
1176 if (comp) {
1177 words = (NSMutableArray *)CFArrayCreateMutableCopy(alloc, CFArrayGetCount(comp), comp);
1178 CFRelease(comp);
1179 }
1180
1181 const NSString *uppercaseWords[] = {
1182 @"A",
1183 @"An",
1184 @"The",
1185 @"Of",
1186 @"And",
1187 };
1188
1189 const NSString *lowercaseWords[] = {
1190 @"a",
1191 @"an",
1192 @"the",
1193 @"of",
1194 @"and",
1195 };
1196
1197 unsigned i, j, iMax = sizeof(uppercaseWords) / sizeof(NSString *);
1198
1199 for (i = 0; i < iMax; i++) {
1200
1201 const NSString *ucWord = uppercaseWords[i];
1202 const NSString *lcWord = lowercaseWords[i];
1203
1204 // omit the first word, since it should always be capitalized
1205 while (NSNotFound != (j = [words indexOfObject:ucWord]) && j > 0)
1206 [words replaceObjectAtIndex:j withObject:lcWord];
1207 }
1208
1209 NSString *toReturn = nil( ( void * ) 0 );
1210 if (words) {
1211 toReturn = (NSString *)CFStringCreateByCombiningStrings(alloc, (CFArrayRef)words, CFSTR( ( CFStringRef ) __builtin___CFStringMakeConstantString ( ""
" " "" ) )
(" "));
1212 [words release];
1213 }
1214 return [toReturn autorelease];
1215}
1216
1217- (NSString *)stringByTrimmingFromLastPunctuation{
1218 NSRange range = [self rangeOfCharacterFromSet:[NSCharacterSet punctuationCharacterSet] options:NSBackwardsSearch];
1219
1220 if(range.location != NSNotFound && (range.location += 1) < [self length])
1221 return [self substringWithRange:NSMakeRange(range.location, [self length] - range.location)];
1222 else
1223 return self;
1224}
1225
1226- (NSString *)stringByTrimmingPrefixCharactersFromSet:(NSCharacterSet *)characterSet;
1227{
1228 NSString *string = nil( ( void * ) 0 );
1229 NSScanner *scanner = [[NSScanner alloc] initWithString:self];
1230 [scanner setCharactersToBeSkipped:nil( ( void * ) 0 )];
1231 [scanner scanCharactersFromSet:characterSet intoString:nil( ( void * ) 0 )];
1232 NSRange range = NSMakeRange(0, [scanner scanLocation]);
1233 [scanner release];
1234
1235 if(range.length){
1236 NSMutableString *mutableCopy = [self mutableCopy];
1237 [mutableCopy deleteCharactersInRange:range];
1238 string = [mutableCopy autorelease];
1239 }
1240 return string ? string : self;
1241}
1242
1243#pragma mark HTML/XML
1244
1245- (NSString *)stringByConvertingHTMLLineBreaks{
1246 NSMutableString *rv = [self mutableCopy];
1247 [rv replaceOccurrencesOfString:@"\n"
1248 withString:@"<br>"
1249 options:NSCaseInsensitiveSearch
1250 range:NSMakeRange(0,[self length])];
1251 return [rv autorelease];
1252}
1253
1254- (NSString *)stringByEscapingBasicXMLEntitiesUsingUTF8;
1255{
1256 return [OFXMLCreateStringWithEntityReferencesInCFEncoding(self, OFXMLBasicEntityMask( ( 0x01 ) | ( ( 0x00 ) << ( 8 ) ) | ( ( 0x00 ) <<
( 16 ) ) )
, nil( ( void * ) 0 ), kCFStringEncodingUTF8) autorelease];
1257}
1258
1259#define APPEND_PREVIOUS() \
1260 string = [[NSString alloc] initWithCharacters:begin length:(ptr - begin)]; \
1261 [result appendString:string]; \
1262 [string release]; \
1263 begin = ptr + 1;
1264
1265// Stolen and modified from the OmniFoundation -htmlString.
1266- (NSString *)xmlString;
1267{
1268 unichar *ptr, *begin, *end;
1269 NSMutableString *result;
1270 NSString *string;
1271 int length;
1272
1273 length = [self length];
1274 ptr = NSZoneMalloc([self zone], length * sizeof(unichar));
1275 void *originalPtr = ptr;
1276 end = ptr + length;
1277 [self getCharacters:ptr];
1278 result = [NSMutableString stringWithCapacity:length];
1279
1280 begin = ptr;
1281 while (ptr < end) {
1282 if (*ptr > 127) {
1283 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1284 [result appendFormat:@"&#%d;", (int)*ptr];
1285 } else if (*ptr == '&') {
1286 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1287 [result appendString:@"&amp;"];
1288 } else if (*ptr == '\"') {
1289 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1290 [result appendString:@"&quot;"];
1291 } else if (*ptr == '<') {
1292 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1293 [result appendString:@"&lt;"];
1294 } else if (*ptr == '>') {
1295 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1296 [result appendString:@"&gt;"];
1297 } else if (*ptr == '\n') {
1298 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1299 if (ptr + 1 != end && *(ptr + 1) == '\n') {
1300 [result appendString:@"&lt;p&gt;"];
1301 ptr++;
1302 } else
1303 [result appendString:@"&lt;br&gt;"];
1304 }
1305 ptr++;
1306 }
1307 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1308 NSZoneFree([self zone], originalPtr);
1309 return result;
1310}
1311
1312- (NSString *)csvString;
1313{
1314 unichar *ptr, *begin, *end;
1315 NSMutableString *result;
1316 NSString *string;
1317 int length;
1318 BOOL isQuoted, needsSpace;
1319
1320 length = [self length];
1321 ptr = NSZoneMalloc([self zone], length * sizeof(unichar));
1322 void *originalPtr = ptr;
1323 end = ptr + length;
1324 [self getCharacters:ptr];
1325 result = [NSMutableString stringWithCapacity:length];
1326 isQuoted = length > 0 && (*ptr == ' ' || *(end-1) == ' ');
1327 needsSpace = NO( BOOL ) 0;
1328
1329 if(isQuoted == NO( BOOL ) 0 && [self containsCharacterInSet:[NSCharacterSet characterSetWithCharactersInString:@"\n\r\t\","]] == NO( BOOL ) 0) {
1330 NSZoneFree([self zone], originalPtr);
1331 return self;
1332 }
1333
1334 begin = ptr;
1335 while (ptr < end) {
1336 switch (*ptr) {
1337 case '\n':
1338 case '\r':
1339 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1340 if (needsSpace)
1341 [result appendString:@" "];
1342 case ' ':
1343 case '\t':
1344 needsSpace = NO( BOOL ) 0;
1345 break;
1346 case '"':
1347 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1348 [result appendString:@"\"\""];
1349 case ',':
1350 isQuoted = YES( BOOL ) 1;
1351 default:
1352 needsSpace = YES( BOOL ) 1;
1353 break;
1354 }
1355 ptr++;
1356 }
1357 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1358 if (isQuoted) {
1359 [result insertString:@"\"" atIndex:0];
1360 [result appendString:@"\""];
1361 }
1362 NSZoneFree([self zone], originalPtr);
1363 return result;
1364}
1365
1366- (NSString *)tsvString;
1367{
1368 if([self containsCharacterInSet:[NSCharacterSet characterSetWithCharactersInString:@"\t\n\r"]] == NO( BOOL ) 0)
1369 return self;
1370
1371 unichar *ptr, *begin, *end;
1372 NSMutableString *result;
1373 NSString *string;
1374 int length;
1375 BOOL needsSpace;
1376
1377 length = [self length];
1378 ptr = NSZoneMalloc([self zone], length * sizeof(unichar));
1379 void *originalPtr = ptr;
1380 end = ptr + length;
1381 [self getCharacters:ptr];
1382 result = [NSMutableString stringWithCapacity:length];
1383 needsSpace = NO( BOOL ) 0;
1384
1385 begin = ptr;
1386 while (ptr < end) {
1387 switch (*ptr) {
1388 case '\t':
1389 needsSpace = YES( BOOL ) 1;
1390 case '\n':
1391 case '\r':
1392 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1393 if (needsSpace)
1394 [result appendString:@" "];
1395 case ' ':
1396 needsSpace = NO( BOOL ) 0;
1397 break;
1398 default:
1399 needsSpace = YES( BOOL ) 1;
1400 break;
1401 }
1402 ptr++;
1403 }
1404 APPEND_PREVIOUSstring = [ [ NSString alloc ] initWithCharacters : begin length
: ( ptr - begin ) ] ; [ result appendString : string ] ; [ string
release ] ; begin = ptr + 1 ;
();
1405 NSZoneFree([self zone], originalPtr);
1406 return result;
1407}
1408
1409#pragma mark -
1410#pragma mark Script arguments
1411
1412// parses a space separated list of shell script argments
1413// allows quoting parts of an argument and escaped characters outside quotes, according to shell rules
1414- (NSArray *)shellScriptArgumentsArray {
1415 static NSCharacterSet *specialChars = nil( ( void * ) 0 );
1416 static NSCharacterSet *quoteChars = nil( ( void * ) 0 );
1417
1418 if (specialChars == nil( ( void * ) 0 )) {
1419 NSMutableCharacterSet *tmpSet = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
1420 [tmpSet addCharactersInString:@"\\\"'`"];
1421 specialChars = [tmpSet copy];
1422 [tmpSet release];
1423 quoteChars = [[NSCharacterSet characterSetWithCharactersInString:@"\"'`"] retain];
1424 }
1425
1426 NSScanner *scanner = [NSScanner scannerWithString:self];
1427 NSString *s = nil( ( void * ) 0 );
1428 unichar ch = 0;
1429 NSMutableString *currArg = [scanner isAtEnd] ? nil( ( void * ) 0 ) : [NSMutableString string];
1430 NSMutableArray *arguments = [NSMutableArray array];
1431
1432 [scanner setCharactersToBeSkipped:nil( ( void * ) 0 )];
1433 [scanner scanCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:NULL( ( void * ) 0 )];
1434
1435 while ([scanner isAtEnd] == NO( BOOL ) 0) {
1436 if ([scanner scanUpToCharactersFromSet:specialChars intoString:&s])
1437 [currArg appendString:s];
1438 if ([scanner scanCharacter:&ch] == NO( BOOL ) 0)
1439 break;
1440 if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:ch]) {
1441 // argument separator, add the last one we found and ignore more whitespaces
1442 [scanner scanCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:NULL( ( void * ) 0 )];
1443 [arguments addObject:currArg];
1444 currArg = [scanner isAtEnd] ? nil( ( void * ) 0 ) : [NSMutableString string];
1445 } else if (ch == '\\') {
1446 // escaped character
1447 if ([scanner scanCharacter:&ch] == NO( BOOL ) 0)
1448 [NSException raise:NSInternalInconsistencyException format:@"Missing character"];
1449 if ([currArg length] == 0 && [[NSCharacterSet newlineCharacterSet] characterIsMember:ch])
1450 // ignore escaped newlines between arguments, as they should be considered whitespace
1451 [scanner scanCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:NULL( ( void * ) 0 )];
1452 else // real escaped character, just add the character, so we can ignore it if it is a special character
1453 [currArg appendFormat:@"%C", ch];
1454 } else if ([quoteChars characterIsMember:ch]) {
1455 // quoted part of an argument, scan up to the matching quote
1456 if ([scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithRange:NSMakeRange(ch, 1)] intoString:&s])
1457 [currArg appendString:s];
1458 if ([scanner scanCharacter:NULL( ( void * ) 0 )] == NO( BOOL ) 0)
1459 [NSException raise:NSInternalInconsistencyException format:@"Unmatched %C", ch];
1460 }
1461 }
1462 if (currArg)
1463 [arguments addObject:currArg];
1464 return arguments;
1465}
1466
1467// parses a comma separated list of AppleScript type arguments
1468- (NSArray *)appleScriptArgumentsArray {
1469 NSMutableArray *arguments = [NSMutableArray array];
1470 NSScanner *scanner = [NSScanner scannerWithString:self];
1471 unichar ch = 0;
1472 id object;
1473
1474 [scanner setCharactersToBeSkipped:nil( ( void * ) 0 )];
1475
1476 while ([scanner isAtEnd] == NO( BOOL ) 0) {
1477 if ([scanner scanAppleScriptValueUpToCharactersInSet:[NSCharacterSet commaCharacterSet] intoObject:&object])
1478 [arguments addObject:object];
1479 if ([scanner scanCharacter:&ch] == NO( BOOL ) 0)
1480 break;
1481 if (ch != ',')
1482 [NSException raise:NSInternalInconsistencyException format:@"Missing ,"];
1483 }
1484 return arguments;
1485}
1486
1487#pragma mark Empty lines
1488
1489// whitespace at the beginning of the string up to and including a newline
1490- (NSRange)rangeOfLeadingEmptyLine {
1491 return [self rangeOfLeadingEmptyLineRequiringNewline:YES( BOOL ) 1];
1492}
1493
1494- (NSRange)rangeOfLeadingEmptyLineRequiringNewline:(BOOL)requireNL {
1495 return [self rangeOfLeadingEmptyLineRequiringNewline:requireNL range:NSMakeRange(0, [self length])];
1496}
1497
1498- (NSRange)rangeOfLeadingEmptyLineInRange:(NSRange)range {
1499 return [self rangeOfLeadingEmptyLineRequiringNewline:YES( BOOL ) 1 range:range];
1500}
1501
1502- (NSRange)rangeOfLeadingEmptyLineRequiringNewline:(BOOL)requireNL range:(NSRange)range {
1503 NSRange firstCharRange = [self rangeOfCharacterFromSet:[NSCharacterSet nonWhitespaceCharacterSet] options:0 range:range];
1504 NSRange wsRange = NSMakeRange(NSNotFound, 0);
1505 unsigned int start = range.location;
1506 if (firstCharRange.location == NSNotFound) {
1507 if (requireNL == NO( BOOL ) 0)
1508 wsRange = range;
1509 } else {
1510 unichar firstChar = [self characterAtIndex:firstCharRange.location];
1511 unsigned int rangeEnd = NSMaxRange(firstCharRange);
1512 if([[NSCharacterSet newlineCharacterSet] characterIsMember:firstChar]) {
1513 if (firstChar == '\r' && rangeEnd < NSMaxRange(range) && [self characterAtIndex:rangeEnd] == '\n')
1514 wsRange = NSMakeRange(start, rangeEnd + 1 - start);
1515 else
1516 wsRange = NSMakeRange(start, rangeEnd - start);
1517 }
1518 }
1519 return wsRange;
1520}
1521
1522// whitespace at the end of the string after a newline
1523- (NSRange)rangeOfTrailingEmptyLine {
1524 return [self rangeOfTrailingEmptyLineRequiringNewline:YES( BOOL ) 1];
1525}
1526
1527- (NSRange)rangeOfTrailingEmptyLineRequiringNewline:(BOOL)requireNL {
1528 return [self rangeOfTrailingEmptyLineRequiringNewline:requireNL range:NSMakeRange(0, [self length])];
1529}
1530
1531- (NSRange)rangeOfTrailingEmptyLineInRange:(NSRange)range {
1532 return [self rangeOfTrailingEmptyLineRequiringNewline:YES( BOOL ) 1 range:range];
1533}
1534
1535- (NSRange)rangeOfTrailingEmptyLineRequiringNewline:(BOOL)requireNL range:(NSRange)range {
1536 NSRange lastCharRange = [self rangeOfCharacterFromSet:[NSCharacterSet nonWhitespaceCharacterSet] options:NSBackwardsSearch range:range];
1537 NSRange wsRange = NSMakeRange(NSNotFound, 0);
1538 unsigned int end = NSMaxRange(range);
1539 if (lastCharRange.location == NSNotFound) {
1540 if (requireNL == NO( BOOL ) 0)
1541 wsRange = range;
1542 } else {
1543 unichar lastChar = [self characterAtIndex:lastCharRange.location];
1544 unsigned int rangeEnd = NSMaxRange(lastCharRange);
1545 if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar])
1546 wsRange = NSMakeRange(rangeEnd, end - rangeEnd);
1547 }
1548 return wsRange;
1549}
1550
1551#pragma mark Some convenience keys for templates
1552
1553- (NSURL *)url {
1554 NSURL *url = nil( ( void * ) 0 );
1555 if ([self rangeOfString:@"://"].location != NSNotFound)
1556 url = [NSURL URLWithStringByNormalizingPercentEscapes:self];
1557 else
1558 url = [NSURL fileURLWithPath:[self stringByExpandingTildeInPath]];
1559 return url;
1560}
1561
1562- (NSAttributedString *)linkedText {
1563 return [[[NSAttributedString alloc] initWithString:self attributeName:NSLinkAttributeName attributeValue:[self url]] autorelease];
1564}
1565
1566- (NSAttributedString *)icon {
1567 return [[self url] icon];
1568}
1569
1570- (NSAttributedString *)smallIcon {
1571 return [[self url] smallIcon];
1572}
1573
1574- (NSAttributedString *)linkedIcon {
1575 return [[self url] linkedIcon];
1576}
1577
1578- (NSAttributedString *)linkedSmallIcon {
1579 return [[self url] linkedSmallIcon];
1580}
1581
1582- (NSString *)textSkimNotes {
1583 return [[self url] textSkimNotes];
1584}
1585
1586- (NSAttributedString *)richTextSkimNotes {
1587 return [[self url] richTextSkimNotes];
1588}
1589
1590- (NSString *)titleCapitalizedString {
1591 NSScanner *scanner = [[NSScanner alloc] initWithString:self];
1592 NSString *s = nil( ( void * ) 0 );
1593 NSMutableString *returnString = [NSMutableString stringWithCapacity:[self length]];
1594 int nesting = 0;
1595 unichar ch;
1596 unsigned location;
1597 NSRange range;
1598 BOOL foundFirstLetter = NO( BOOL ) 0;
1599
1600 [scanner setCharactersToBeSkipped:nil( ( void * ) 0 )];
1601
1602 while([scanner isAtEnd] == NO( BOOL ) 0){
1603 if([scanner scanUpToCharactersFromSet:[NSCharacterSet curlyBraceCharacterSet] intoString:&s])
1604 [returnString appendString:nesting == 0 ? [s lowercaseString] : s];
1605 if (foundFirstLetter == NO( BOOL ) 0) {
1606 range = [returnString rangeOfCharacterFromSet:[NSCharacterSet letterCharacterSet]];
1607 if (range.location != NSNotFound) {
1608 foundFirstLetter = YES( BOOL ) 1;
1609 if (nesting == 0)
1610 [returnString replaceCharactersInRange:range withString:[[returnString substringWithRange:range] uppercaseString]];
1611 }
1612 }
1613 if([scanner scanCharacter:&ch] == NO( BOOL ) 0)
1614 break;
1615 [returnString appendFormat:@"%C", ch];
1616 location = [scanner scanLocation];
1617 if(location > 0 && [self characterAtIndex:location - 1] == '\\')
1618 continue;
1619 if(ch == '{')
1620 nesting++;
1621 else
1622 nesting--;
1623 }
1624
1625 [scanner release];
1626
1627 return returnString;
1628}
1629
1630- (NSString *)firstLetter{
1631 return [self length] ? [self substringToIndex:1] : nil( ( void * ) 0 );
1632}
1633
1634- (NSString *)stringByAddingPercentEscapesIncludingReserved{
1635 return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, NULL( ( void * ) 0 ), CFSTR( ( CFStringRef ) __builtin___CFStringMakeConstantString ( ""
";/?:@&=+$," "" ) )
(";/?:@&=+$,"), kCFStringEncodingUTF8) autorelease];
1636}
1637
1638- (NSString *)stringByAddingPercentEscapes{
1639 return [self stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
1640}
1641
1642- (NSString *)stringByReplacingPercentEscapes{
1643 return [self stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
1644}
1645
1646@end
1647
1648
1649@implementation NSMutableString (BDSKExtensions)
1650
1651- (BOOL)isMutableString;
1652{
1653 int isMutable = YES( BOOL ) 1;
1654 @try{
1655 [self appendCharacter:'X'];
1656 }
1657 @catch(NSException *localException){
1658 if([[localException name] isEqual:NSInvalidArgumentException])
1659 isMutable = NO( BOOL ) 0;
1660 else
1661 @throw;
1662 }
1663 @catch(id localException){
1664 @throw;
1665 }
1666
1667 [self deleteCharactersInRange:NSMakeRange([self length] - 1, 1)];
1668 return isMutable;
1669}
1670
1671- (void)deleteCharactersInCharacterSet:(NSCharacterSet *)characterSet;
1672{
1673 BDDeleteCharactersInCharacterSet((CFMutableStringRef)self, (CFCharacterSetRef)characterSet);
1674}
1675
1676@end