-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathVCTitleCase.m
146 lines (126 loc) · 6.34 KB
/
VCTitleCase.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//
// VCTitleCase.m
// Title Case extension for NSString
//
// Based on titlecase.pl by:
// John Gruber
// http://daringfireball.net/
// 10 May 2008
//
// Cocoa Foundation version by:
// Marshall Elfstrand
// http://vengefulcow.com/
// 24 May 2008
//
// License: http://www.opensource.org/licenses/mit-license.php
//
#import "VCTitleCase.h"
NSString * VCTitleCaseString(NSString *input) {
static NSArray *shortWords;
static NSMutableCharacterSet *wordStartCharacterSet;
static NSMutableCharacterSet *wordMiddleCharacterSet;
static NSMutableCharacterSet *wordEndCharacterSet;
static NSMutableCharacterSet *wordIgnoreCharacterSet;
// Initialize the list of "short" words that remain lowercase.
if (!shortWords) {
shortWords = [[NSArray alloc] initWithObjects:
@"a", @"an", @"and", @"as", @"at", @"but", @"by", @"en", @"for",
@"if", @"in", @"of", @"on", @"or", @"the", @"to", @"v", @"via",
@"vs", nil];
}
// Initialize the set of characters allowed at the start of words.
if (!wordStartCharacterSet) {
wordStartCharacterSet = [[NSCharacterSet uppercaseLetterCharacterSet] mutableCopy];
[wordStartCharacterSet formUnionWithCharacterSet:[NSCharacterSet lowercaseLetterCharacterSet]];
}
// Initialize the set of characters allowed in the middle of words.
if (!wordMiddleCharacterSet) {
wordMiddleCharacterSet = [[NSCharacterSet uppercaseLetterCharacterSet] mutableCopy];
[wordMiddleCharacterSet formUnionWithCharacterSet:[NSCharacterSet lowercaseLetterCharacterSet]];
[wordMiddleCharacterSet addCharactersInString:@".&'’"];
}
// Initialize the set of characters allowed at the end of words.
if (!wordEndCharacterSet) wordEndCharacterSet = wordStartCharacterSet;
// Initialize the set of characters that cause a word to be ignored
// when they appear in the middle.
if (!wordIgnoreCharacterSet) {
wordIgnoreCharacterSet = [[NSCharacterSet uppercaseLetterCharacterSet] mutableCopy];
[wordIgnoreCharacterSet addCharactersInString:@"."];
}
// Create a mutable copy of the string that we can modify in-place.
NSMutableString *newString = [input mutableCopy];
// Create a scanner that we can use to locate words in the string.
NSScanner *scanner = [NSScanner scannerWithString:input];
[scanner setCaseSensitive:YES];
// Begin scanning for words.
NSRange currentRange = {}; // Range of word located by scanner
NSString *word; // Extracted word
NSString *lowercaseWord; // Lowercase version of extracted word
NSRange ignoreTriggerRange; // Range of character causing word to be ignored
BOOL isFirstWord = YES; // To determine whether to capitalize small word
while (![scanner isAtEnd]) {
// Locate the beginning of the next word.
[scanner scanUpToCharactersFromSet:wordStartCharacterSet
intoString:NULL];
if ([scanner scanLocation] >= [input length]) continue; // No more words
currentRange = NSMakeRange([scanner scanLocation], 1);
// Check to see if we stopped on whitespace and advance to the
// actual beginning of the word.
if (![wordStartCharacterSet characterIsMember:[input characterAtIndex:[scanner scanLocation]]]) {
[scanner setScanLocation:[scanner scanLocation] + 1];
currentRange = NSMakeRange([scanner scanLocation], 1);
}
// Advance to the next character in the word.
[scanner scanString:[input substringWithRange:currentRange]
intoString:NULL];
// See if the next character is a valid word character, and if so,
// scan through the end of the word.
if (input.length > scanner.scanLocation && [wordMiddleCharacterSet characterIsMember:[input characterAtIndex:[scanner scanLocation]]]) {
[scanner scanCharactersFromSet:wordMiddleCharacterSet
intoString:NULL];
currentRange.length = [scanner scanLocation] - currentRange.location;
}
// Back off the word until it ends with a valid character.
unichar lastCharacter = [input characterAtIndex:(NSMaxRange(currentRange) - 1)];
while (![wordEndCharacterSet characterIsMember:lastCharacter]) {
[scanner setScanLocation:[scanner scanLocation] - 1];
currentRange.length -= 1;
lastCharacter = [input characterAtIndex:(NSMaxRange(currentRange) - 1)];
}
// We have now located a word.
word = [input substringWithRange:currentRange];
lowercaseWord = [word lowercaseString];
// Check to see if the word needs to be capitalized.
// Words that have dots in the middle or that already contain
// capitalized letters in the middle (e.g. "iTunes") are ignored.
ignoreTriggerRange = [input
rangeOfCharacterFromSet:wordIgnoreCharacterSet
options:NSLiteralSearch
range:NSMakeRange(currentRange.location + 1, currentRange.length - 1)
];
if (ignoreTriggerRange.location == NSNotFound) {
if ([word rangeOfString:@"&"].location != NSNotFound) {
// Uppercase words that contain ampersands.
[newString replaceCharactersInRange:currentRange
withString:[word uppercaseString]];
} else {
if ((!isFirstWord) && [shortWords containsObject:lowercaseWord]) {
// Lowercase small words.
[newString replaceCharactersInRange:currentRange
withString:lowercaseWord];
} else {
// Capitalize word.
[newString replaceCharactersInRange:currentRange
withString:[word capitalizedString]];
}
}
}
isFirstWord = NO;
}
// Make sure the last word is capitalized, even if it is a small word.
if (lowercaseWord && [shortWords containsObject:lowercaseWord]) {
[newString replaceCharactersInRange:currentRange
withString:[lowercaseWord capitalizedString]];
}
return [newString copy];
}