Adds utility methods to NSString, for dealing with whitespace, string splitting, and other things.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

NSString+SA_NSStringExtensions.m 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. //
  2. // NSString+SA_NSStringExtensions.m
  3. //
  4. // Copyright 2015-2021 Said Achmiz.
  5. // See LICENSE and README.md for more info.
  6. #import "NSString+SA_NSStringExtensions.h"
  7. #import "NSIndexSet+SA_NSIndexSetExtensions.h"
  8. #import "NSArray+SA_NSArrayExtensions.h"
  9. #import <CommonCrypto/CommonDigest.h>
  10. static BOOL _SA_NSStringExtensions_RaiseRegularExpressionCreateException = YES;
  11. /***********************************************************/
  12. #pragma mark - SA_NSStringExtensions category implementation
  13. /***********************************************************/
  14. @implementation NSString (SA_NSStringExtensions)
  15. /******************************/
  16. #pragma mark - Class properties
  17. /******************************/
  18. +(void) setSA_NSStringExtensions_RaiseRegularExpressionCreateException:(BOOL)SA_NSStringExtensions_RaiseRegularExpressionCreateException {
  19. _SA_NSStringExtensions_RaiseRegularExpressionCreateException = SA_NSStringExtensions_RaiseRegularExpressionCreateException;
  20. }
  21. +(BOOL) SA_NSStringExtensions_RaiseRegularExpressionCreateException {
  22. return _SA_NSStringExtensions_RaiseRegularExpressionCreateException;
  23. }
  24. /*************************************/
  25. #pragma mark - Working with characters
  26. /*************************************/
  27. -(BOOL) containsCharactersInSet:(NSCharacterSet *)characters {
  28. NSRange rangeOfCharacters = [self rangeOfCharacterFromSet:characters];
  29. return rangeOfCharacters.location != NSNotFound;
  30. }
  31. -(BOOL) containsCharactersInString:(NSString *)characters {
  32. return [self containsCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:characters]];
  33. }
  34. -(NSString *) stringByRemovingCharactersInSet:(NSCharacterSet *)characters {
  35. NSMutableString *workingCopy = [self mutableCopy];
  36. [workingCopy removeCharactersInSet:characters];
  37. // NSRange rangeOfCharacters = [workingCopy rangeOfCharacterFromSet:characters];
  38. // while (rangeOfCharacters.location != NSNotFound) {
  39. // [workingCopy replaceCharactersInRange:rangeOfCharacters withString:@""];
  40. // rangeOfCharacters = [workingCopy rangeOfCharacterFromSet:characters];
  41. // }
  42. return [workingCopy copy];
  43. }
  44. -(NSString *) stringByRemovingCharactersInString:(NSString *)characters {
  45. return [self stringByRemovingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:characters]];
  46. }
  47. /**********************/
  48. #pragma mark - Trimming
  49. /**********************/
  50. -(NSString *) stringByTrimmingToMaxLengthInBytes:(NSUInteger)maxLengthInBytes
  51. usingEncoding:(NSStringEncoding)encoding
  52. withStringEnumerationOptions:(NSStringEnumerationOptions)enumerationOptions
  53. andStringTrimmingOptions:(SA_NSStringTrimmingOptions)trimmingOptions {
  54. NSMutableString *workingCopy = [self mutableCopy];
  55. [workingCopy trimToMaxLengthInBytes:maxLengthInBytes
  56. usingEncoding:encoding
  57. withStringEnumerationOptions:enumerationOptions
  58. andStringTrimmingOptions:trimmingOptions];
  59. return [workingCopy copy];
  60. // NSString *trimmedString = self;
  61. //
  62. // // Trim whitespace.
  63. // if (trimmingOptions & SA_NSStringTrimming_TrimWhitespace)
  64. // trimmedString = [trimmedString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  65. //
  66. // // Collapse whitespace.
  67. // if (trimmingOptions & SA_NSStringTrimming_CollapseWhitespace)
  68. // trimmedString = [trimmedString stringByReplacingAllOccurrencesOfPattern:@"\\s+"
  69. // withTemplate:@" "];
  70. //
  71. // // Length of the ellipsis suffix, in bytes.
  72. // NSString *ellipsis = @" …";
  73. // NSUInteger ellipsisLengthInBytes = [ellipsis lengthOfBytesUsingEncoding:encoding];
  74. //
  75. // // Trim (leaving space for ellipsis, if necessary).
  76. // __block NSUInteger cutoffLength = 0;
  77. // [trimmedString enumerateSubstringsInRange:trimmedString.fullRange
  78. // options:(enumerationOptions|NSStringEnumerationSubstringNotRequired)
  79. // usingBlock:^(NSString * _Nullable substring,
  80. // NSRange substringRange,
  81. // NSRange enclosingRange,
  82. // BOOL * _Nonnull stop) {
  83. // NSUInteger endOfEnclosingRange = NSMaxRange(enclosingRange);
  84. // NSUInteger endOfEnclosingRangeInBytes = [[trimmedString substringToIndex:endOfEnclosingRange]
  85. // lengthOfBytesUsingEncoding:encoding];
  86. //
  87. // // If we need to append ellipsis when trimming...
  88. // if (trimmingOptions & SA_NSStringTrimming_AppendEllipsis) {
  89. // if ( trimmedString.fullRange.length == endOfEnclosingRange
  90. // && endOfEnclosingRangeInBytes <= maxLengthInBytes) {
  91. // // Either the ellipsis is not needed, because the string is not cut off...
  92. // cutoffLength = endOfEnclosingRange;
  93. // } else if (endOfEnclosingRangeInBytes <= (maxLengthInBytes - ellipsisLengthInBytes)) {
  94. // // Or there will still be room for the ellipsis after adding this piece...
  95. // cutoffLength = endOfEnclosingRange;
  96. // } else {
  97. // // Or we don’t add this piece.
  98. // *stop = YES;
  99. // }
  100. // } else {
  101. // if (endOfEnclosingRangeInBytes <= maxLengthInBytes) {
  102. // cutoffLength = endOfEnclosingRange;
  103. // } else {
  104. // *stop = YES;
  105. // }
  106. // }
  107. // }];
  108. // NSUInteger lengthBeforeTrimming = trimmedString.length;
  109. // trimmedString = [trimmedString substringToIndex:cutoffLength];
  110. //
  111. // // Append ellipsis.
  112. // if ( trimmingOptions & SA_NSStringTrimming_AppendEllipsis
  113. // && cutoffLength < lengthBeforeTrimming
  114. // && maxLengthInBytes >= ellipsisLengthInBytes
  115. // && ( cutoffLength > 0
  116. // || !(trimmingOptions & SA_NSStringTrimming_ElideEllipsisWhenEmpty))
  117. // ) {
  118. // trimmedString = [trimmedString stringByAppendingString:ellipsis];
  119. // }
  120. //
  121. // return trimmedString;
  122. }
  123. +(instancetype) trimmedStringFromComponents:(NSArray <NSDictionary *> *)components
  124. maxLength:(NSUInteger)maxLengthInBytes
  125. encoding:(NSStringEncoding)encoding
  126. cleanWhitespace:(BOOL)cleanWhitespace {
  127. SA_NSStringTrimmingOptions trimmingOptions = (cleanWhitespace
  128. ? ( SA_NSStringTrimming_CollapseWhitespace
  129. |SA_NSStringTrimming_TrimWhitespace
  130. |SA_NSStringTrimming_AppendEllipsis)
  131. : SA_NSStringTrimming_AppendEllipsis);
  132. NSMutableArray <NSDictionary *> *mutableComponents = [components mutableCopy];
  133. // Get the formatted version of the component
  134. // (inserting the value into the format string).
  135. NSString *(^formatComponent)(NSDictionary *) = ^NSString *(NSDictionary *component) {
  136. return (component[@"appendFormat"] != nil
  137. ? [NSString stringWithFormat:component[@"appendFormat"], component[@"value"]]
  138. : component[@"value"]);
  139. };
  140. // Get the full formatted result.
  141. NSString *(^formatResult)(NSArray <NSDictionary *> *) = ^NSString *(NSArray <NSDictionary *> *componentsArray) {
  142. return [componentsArray reduce:^NSString *(NSString *resultSoFar,
  143. NSDictionary *component) {
  144. return [resultSoFar stringByAppendingString:formatComponent(component)];
  145. } initial:@""];
  146. };
  147. // Clean and trim (if need be) each component.
  148. [mutableComponents enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull component,
  149. NSUInteger idx,
  150. BOOL * _Nonnull stop) {
  151. NSMutableDictionary *adjustedComponent = [component mutableCopy];
  152. // Clean whitespace.
  153. if (cleanWhitespace) {
  154. adjustedComponent[@"value"] = [adjustedComponent[@"value"] stringByReplacingAllOccurrencesOfPatterns:@[ @"^\\s*(.*?)\\s*$", // Trim whitespace.
  155. @"(\\s*\\n\\s*)+", // Replace newlines with ‘ / ’.
  156. @"\\s+" // Collapse whitespace.
  157. ]
  158. withTemplates:@[ @"$1",
  159. @" / ",
  160. @" "
  161. ]];
  162. }
  163. // If component length is individually limited, trim it.
  164. if ( adjustedComponent[@"limit"] != nil
  165. && adjustedComponent[@"trimBy"] != nil) {
  166. adjustedComponent[@"value"] = [adjustedComponent[@"value"] stringByTrimmingToMaxLengthInBytes:[adjustedComponent[@"limit"] unsignedIntegerValue]
  167. usingEncoding:encoding
  168. withStringEnumerationOptions:[adjustedComponent[@"trimBy"] unsignedIntegerValue]
  169. andStringTrimmingOptions:trimmingOptions];
  170. }
  171. [mutableComponents replaceObjectAtIndex:idx
  172. withObject:adjustedComponent];
  173. }];
  174. // Maybe there’s no length limit? If so, don’t trim; just format and return.
  175. if (maxLengthInBytes == 0)
  176. return formatResult(mutableComponents);
  177. // Get the total (formatted) length of all the components.
  178. NSUInteger (^getTotalLength)(NSArray <NSDictionary *> *) = ^NSUInteger(NSArray <NSDictionary *> *componentsArray) {
  179. return ((NSNumber *)[componentsArray reduce:^NSNumber *(NSNumber *lengthSoFar,
  180. NSDictionary *component) {
  181. return @(lengthSoFar.unsignedIntegerValue + [formatComponent(component) lengthOfBytesUsingEncoding:encoding]);
  182. } initial:@(0)]).unsignedIntegerValue;
  183. };
  184. // The “lowest” priority is actually the highest numeric priority value.
  185. NSUInteger (^getIndexOfLowestPriorityComponent)(NSArray <NSDictionary *> *) = ^NSUInteger(NSArray <NSDictionary *> *componentsArray) {
  186. // By default, return the index of the last component.
  187. __block NSUInteger lowestPriorityComponentIndex = (componentsArray.count - 1);
  188. [componentsArray enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull component,
  189. NSUInteger idx,
  190. BOOL * _Nonnull stop) {
  191. if ([component[@"priority"] unsignedIntegerValue] > [componentsArray[lowestPriorityComponentIndex][@"priority"] unsignedIntegerValue])
  192. lowestPriorityComponentIndex = idx;
  193. }];
  194. return lowestPriorityComponentIndex;
  195. };
  196. // Keep trimming until we’re below the max length.
  197. NSInteger excessLength = (NSInteger)(getTotalLength(mutableComponents) - maxLengthInBytes);
  198. while (excessLength > 0) {
  199. NSUInteger lowestPriorityComponentIndex = getIndexOfLowestPriorityComponent(mutableComponents);
  200. NSDictionary *lowestPriorityComponent = mutableComponents[lowestPriorityComponentIndex];
  201. NSUInteger lowestPriorityComponentValueLengthInBytes = [lowestPriorityComponent[@"value"] lengthOfBytesUsingEncoding:encoding];
  202. if ( lowestPriorityComponent[@"trimBy"] == nil
  203. || lowestPriorityComponentValueLengthInBytes <= (NSUInteger)excessLength) {
  204. [mutableComponents removeObjectAtIndex:lowestPriorityComponentIndex];
  205. } else {
  206. NSMutableDictionary *adjustedComponent = [lowestPriorityComponent mutableCopy];
  207. adjustedComponent[@"value"] = [lowestPriorityComponent[@"value"] stringByTrimmingToMaxLengthInBytes:(lowestPriorityComponentValueLengthInBytes - (NSUInteger)excessLength)
  208. usingEncoding:encoding
  209. withStringEnumerationOptions:[lowestPriorityComponent[@"trimBy"] unsignedIntegerValue]
  210. andStringTrimmingOptions:trimmingOptions];
  211. // Check to make sure we haven’t trimmed all the way to nothing!
  212. // (Actually this can’t happen because the SA_NSStringTrimming_ElideEllipsisWhenEmpty
  213. // flag is not set on the trim call above...)
  214. if ([adjustedComponent[@"value"] lengthOfBytesUsingEncoding:encoding] == 0) {
  215. // ... if we have, just remove the component.
  216. [mutableComponents removeObjectAtIndex:lowestPriorityComponentIndex];
  217. } else {
  218. // ... otherwise, update it.
  219. [mutableComponents replaceObjectAtIndex:lowestPriorityComponentIndex
  220. withObject:adjustedComponent];
  221. }
  222. }
  223. excessLength = (NSInteger)(getTotalLength(mutableComponents) - maxLengthInBytes);
  224. }
  225. // Trimming is done; return.
  226. return formatResult(mutableComponents);
  227. }
  228. /****************************************/
  229. #pragma mark - Partitioning by whitespace
  230. /****************************************/
  231. -(NSRange) firstWhitespaceAfterRange:(NSRange)aRange {
  232. NSRange restOfString = NSMakeRange(NSMaxRange(aRange), self.length - NSMaxRange(aRange));
  233. NSRange firstWhitespace = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]
  234. options:(NSStringCompareOptions) 0
  235. range:restOfString];
  236. return firstWhitespace;
  237. }
  238. -(NSRange) firstNonWhitespaceAfterRange:(NSRange)aRange {
  239. NSRange restOfString = NSMakeRange(NSMaxRange(aRange), self.length - NSMaxRange(aRange));
  240. NSRange firstNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]
  241. options:(NSStringCompareOptions) 0
  242. range:restOfString];
  243. return firstNonWhitespace;
  244. }
  245. -(NSRange) lastWhitespaceBeforeRange:(NSRange)aRange {
  246. NSRange stringUntilRange = NSMakeRange(0, aRange.location);
  247. NSRange lastWhitespace = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]
  248. options:NSBackwardsSearch
  249. range:stringUntilRange];
  250. return lastWhitespace;
  251. }
  252. -(NSRange) lastNonWhitespaceBeforeRange:(NSRange)aRange {
  253. NSRange stringUntilRange = NSMakeRange(0, aRange.location);
  254. NSRange lastNonWhitespace = [self rangeOfCharacterFromSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]
  255. options:NSBackwardsSearch
  256. range:stringUntilRange];
  257. return lastNonWhitespace;
  258. }
  259. /********************/
  260. #pragma mark - Ranges
  261. /********************/
  262. -(NSRange) rangeAfterRange:(NSRange)aRange {
  263. return NSMakeRange(NSMaxRange(aRange), self.length - NSMaxRange(aRange));
  264. }
  265. -(NSRange) rangeFromEndOfRange:(NSRange)aRange {
  266. return NSMakeRange(NSMaxRange(aRange) - 1, self.length - NSMaxRange(aRange) + 1);
  267. }
  268. -(NSRange) rangeToEndFrom:(NSRange)aRange {
  269. return NSMakeRange(aRange.location, self.length - aRange.location);
  270. }
  271. -(NSRange) startRange {
  272. return NSMakeRange(0, 0);
  273. }
  274. -(NSRange) fullRange {
  275. return NSMakeRange(0, self.length);
  276. }
  277. -(NSRange) endRange {
  278. return NSMakeRange(self.length, 0);
  279. }
  280. /***********************/
  281. #pragma mark - Splitting
  282. /***********************/
  283. -(NSArray <NSString *> *) componentsSplitByWhitespace {
  284. return [self componentsSplitByWhitespaceWithMaxSplits:NSUIntegerMax
  285. dropEmptyString:YES];
  286. }
  287. -(NSArray <NSString *> *) componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits {
  288. return [self componentsSplitByWhitespaceWithMaxSplits:maxSplits
  289. dropEmptyString:YES];
  290. }
  291. -(NSArray <NSString *> *) componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits
  292. dropEmptyString:(BOOL)dropEmptyString {
  293. // No need to do anything fancy in this case.
  294. if (maxSplits == 0)
  295. return @[ self ];
  296. static NSRegularExpression *regex;
  297. static dispatch_once_t onceToken;
  298. dispatch_once(&onceToken, ^{
  299. regex = [@"^\\S*|(?<=\\s)$|\\S+" regularExpression];
  300. });
  301. NSMutableArray <NSString *> *components = [NSMutableArray array];
  302. [regex enumerateMatchesInString:self
  303. options:(NSMatchingOptions) 0
  304. range:self.fullRange
  305. usingBlock:^(NSTextCheckingResult * _Nullable result,
  306. NSMatchingFlags flags,
  307. BOOL * _Nonnull stop) {
  308. if ( dropEmptyString
  309. && result.range.length == 0) {
  310. // Nothing.
  311. } else if (components.count < maxSplits) {
  312. [components addObject:[self substringWithRange:result.range]];
  313. } else {
  314. [components addObject:[self substringWithRange:[self rangeToEndFrom:result.range]]];
  315. *stop = YES;
  316. }
  317. }];
  318. return components;
  319. }
  320. //-(NSArray <NSString *> *) componentsSplitByWhitespaceWithMaxSplits:(NSUInteger)maxSplits {
  321. // if (maxSplits == 0) {
  322. // return @[ self ];
  323. // }
  324. //
  325. // NSMutableArray <NSString *> *components = [NSMutableArray array];
  326. //
  327. // __block NSUInteger tokenStart;
  328. // __block NSUInteger tokenEnd;
  329. // __block BOOL currentlyInToken = NO;
  330. // __block NSRange tokenRange = NSMakeRange(NSNotFound, 0);
  331. //
  332. // __block NSUInteger splits = 0;
  333. //
  334. // [self enumerateSubstringsInRange:self.fullRange
  335. // options:NSStringEnumerationByComposedCharacterSequences
  336. // usingBlock:^(NSString *character,
  337. // NSRange characterRange,
  338. // NSRange enclosingRange,
  339. // BOOL *stop) {
  340. // if ( currentlyInToken == NO
  341. // && [character containsCharactersInSet:[[NSCharacterSet whitespaceCharacterSet] invertedSet]]
  342. // ) {
  343. // currentlyInToken = YES;
  344. // tokenStart = characterRange.location;
  345. // } else if ( currentlyInToken == YES
  346. // && [character containsCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
  347. // ) {
  348. // currentlyInToken = NO;
  349. // tokenEnd = characterRange.location;
  350. //
  351. // tokenRange = NSMakeRange(tokenStart,
  352. // tokenEnd - tokenStart);
  353. // [components addObject:[self substringWithRange:tokenRange]];
  354. // splits++;
  355. // if (splits == maxSplits) {
  356. // *stop = YES;
  357. // NSRange lastTokenRange = [self rangeToEndFrom:[self firstNonWhitespaceAfterRange:tokenRange]];
  358. // if (lastTokenRange.location != NSNotFound) {
  359. // [components addObject:[self substringWithRange:lastTokenRange]];
  360. // }
  361. // }
  362. // }
  363. // }];
  364. //
  365. // // If we were in a token when we got to the end, add that last token.
  366. // if ( splits < maxSplits
  367. // && currentlyInToken == YES) {
  368. // tokenEnd = self.length;
  369. //
  370. // tokenRange = NSMakeRange(tokenStart,
  371. // tokenEnd - tokenStart);
  372. // [components addObject:[self substringWithRange:tokenRange]];
  373. // }
  374. //
  375. // return components;
  376. //}
  377. -(NSArray <NSString *> *) componentsSeparatedByString:(NSString *)separator
  378. maxSplits:(NSUInteger)maxSplits {
  379. NSArray <NSString *> *components = [self componentsSeparatedByString:separator];
  380. if (maxSplits >= (components.count - 1))
  381. return components;
  382. return [[components subarrayWithRange:NSMakeRange(0, maxSplits)]
  383. arrayByAddingObject:[[components
  384. subarrayWithRange:NSMakeRange(maxSplits,
  385. components.count - maxSplits)]
  386. componentsJoinedByString:separator]];
  387. }
  388. -(NSArray <NSString *> *) componentsSeparatedByString:(NSString *)separator
  389. dropEmptyString:(BOOL)dropEmptyString {
  390. NSMutableArray* components = [[self componentsSeparatedByString:separator] mutableCopy];
  391. if (dropEmptyString == YES)
  392. [components removeObject:@""];
  393. return [components copy];
  394. }
  395. /***************************/
  396. #pragma mark - Byte encoding
  397. /***************************/
  398. -(NSUInteger) UTF8length {
  399. return [self lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
  400. }
  401. -(NSData *) dataAsUTF8 {
  402. return [self dataUsingEncoding:NSUTF8StringEncoding];
  403. }
  404. +(instancetype) stringWithData:(NSData *)data
  405. encoding:(NSStringEncoding)encoding {
  406. return [[self alloc] initWithData:data
  407. encoding:encoding];
  408. }
  409. +(instancetype) stringWithUTF8Data:(NSData *)data {
  410. return [self stringWithData:data
  411. encoding:NSUTF8StringEncoding];
  412. }
  413. /*********************/
  414. #pragma mark - Hashing
  415. /*********************/
  416. -(NSString *) MD5Hash {
  417. const char *cStr = [self UTF8String];
  418. unsigned char result[CC_MD5_DIGEST_LENGTH];
  419. CC_MD5(cStr, (CC_LONG) strlen(cStr), result);
  420. return [NSString stringWithFormat:
  421. @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
  422. result[0], result[1], result[2], result[3],
  423. result[4], result[5], result[6], result[7],
  424. result[8], result[9], result[10], result[11],
  425. result[12], result[13], result[14], result[15]
  426. ];
  427. }
  428. /***********************/
  429. #pragma mark - Sentences
  430. /***********************/
  431. -(NSString *) firstSentence {
  432. __block NSString *firstSentence;
  433. [self enumerateSubstringsInRange:self.fullRange
  434. options:NSStringEnumerationBySentences
  435. usingBlock:^(NSString * _Nullable substring,
  436. NSRange substringRange,
  437. NSRange enclosingRange,
  438. BOOL * _Nonnull stop) {
  439. firstSentence = substring;
  440. *stop = YES;
  441. }];
  442. return firstSentence;
  443. }
  444. /*********************/
  445. #pragma mark - Padding
  446. /*********************/
  447. -(NSString *) stringLeftPaddedTo:(int)width {
  448. return [NSString stringWithFormat:@"%*s", width, [self stringByAppendingString:@"\0"].dataAsUTF8.bytes];
  449. }
  450. /****************************************************/
  451. #pragma mark - Regular expression convenience methods
  452. /****************************************************/
  453. /*********************************************/
  454. /* Construct regular expressions from strings.
  455. *********************************************/
  456. -(NSRegularExpression *) regularExpression {
  457. return [self regularExpressionWithOptions:(NSRegularExpressionOptions) 0];
  458. }
  459. -(NSRegularExpression *) regularExpressionWithOptions:(NSRegularExpressionOptions)options {
  460. NSError *error;
  461. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:self
  462. options:options
  463. error:&error];
  464. if (error) {
  465. if (NSString.SA_NSStringExtensions_RaiseRegularExpressionCreateException == YES)
  466. [NSException raise:@"SA_NSStringExtensions_RegularExpressionCreateException"
  467. format:@"%@", error.localizedDescription];
  468. return nil;
  469. }
  470. return regex;
  471. }
  472. /**********************************************/
  473. /* Get matches for a regular expression object.
  474. **********************************************/
  475. -(NSArray <NSString *> *) matchesForRegex:(NSRegularExpression *)regex {
  476. return [self matchesForRegex:regex
  477. all:NO];
  478. }
  479. -(NSArray <NSArray <NSString *> *> *) allMatchesForRegex:(NSRegularExpression *)regex {
  480. return [self matchesForRegex:regex
  481. all:YES];
  482. }
  483. /* Helper method (private).
  484. */
  485. -(NSArray *) matchesForRegex:(NSRegularExpression *)regex
  486. all:(BOOL)all {
  487. NSMutableArray *matches = [NSMutableArray array];
  488. [regex enumerateMatchesInString:self
  489. options:(NSMatchingOptions) 0
  490. range:self.fullRange
  491. usingBlock:^(NSTextCheckingResult * _Nullable result,
  492. NSMatchingFlags flags,
  493. BOOL *stop) {
  494. if (all)
  495. [matches addObject:[NSMutableArray array]];
  496. [NSIndexSet from:0
  497. for:result.numberOfRanges
  498. do:^(NSUInteger idx) {
  499. NSString *resultString = ([result rangeAtIndex:idx].location == NSNotFound
  500. ? @""
  501. : [self substringWithRange:[result rangeAtIndex:idx]]);
  502. if (all) {
  503. [((NSMutableArray *) matches.lastObject) addObject:resultString];
  504. } else {
  505. [matches addObject:resultString];
  506. }
  507. }];
  508. if (!all)
  509. *stop = YES;
  510. }];
  511. return matches;
  512. }
  513. /*************************************************************************/
  514. /* Get matches for a string representing a regular expression (a pattern).
  515. *************************************************************************/
  516. -(NSArray <NSString *> *) matchesForRegexPattern:(NSString *)pattern {
  517. return [self matchesForRegex:[pattern regularExpression]];
  518. }
  519. -(NSArray <NSString *> *) matchesForRegexPattern:(NSString *)pattern
  520. options:(NSRegularExpressionOptions)options {
  521. return [self matchesForRegex:[pattern regularExpressionWithOptions:options]];
  522. }
  523. -(NSArray <NSArray <NSString *> *> *) allMatchesForRegexPattern:(NSString *)pattern {
  524. return [self allMatchesForRegex:[pattern regularExpression]];
  525. }
  526. -(NSArray <NSArray <NSString *> *> *) allMatchesForRegexPattern:(NSString *)pattern
  527. options:(NSRegularExpressionOptions)options {
  528. return [self allMatchesForRegex:[pattern regularExpressionWithOptions:options]];
  529. }
  530. /*******************************************************************************/
  531. /* Use a pattern (a string representing a regular expression) to do replacement.
  532. *******************************************************************************/
  533. -(NSString *) stringByReplacingFirstOccurrenceOfPattern:(NSString *)pattern
  534. withTemplate:(NSString *)template {
  535. return [self stringByReplacingFirstOccurrenceOfPattern:pattern
  536. withTemplate:template
  537. regularExpressionOptions:(NSRegularExpressionOptions) 0
  538. matchingOptions:(NSMatchingOptions) 0];
  539. }
  540. -(NSString *) stringByReplacingFirstOccurrenceOfPattern:(NSString *)pattern
  541. withTemplate:(NSString *)template
  542. regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
  543. matchingOptions:(NSMatchingOptions)matchingOptions {
  544. NSRegularExpression *regex = [pattern regularExpressionWithOptions:regexpOptions];
  545. NSTextCheckingResult *match = [regex firstMatchInString:self
  546. options:matchingOptions
  547. range:self.fullRange];
  548. if ( match
  549. && match.range.location != NSNotFound) {
  550. return [self stringByReplacingCharactersInRange:match.range
  551. withString:[regex replacementStringForResult:match
  552. inString:self
  553. offset:0
  554. template:template]];
  555. } else {
  556. return self;
  557. }
  558. }
  559. -(NSString *) stringByReplacingAllOccurrencesOfPattern:(NSString *)pattern
  560. withTemplate:(NSString *)template {
  561. return [self stringByReplacingAllOccurrencesOfPattern:pattern
  562. withTemplate:template
  563. regularExpressionOptions:(NSRegularExpressionOptions) 0
  564. matchingOptions:(NSMatchingOptions) 0];
  565. }
  566. -(NSString *) stringByReplacingAllOccurrencesOfPattern:(NSString *)pattern
  567. withTemplate:(NSString *)template
  568. regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
  569. matchingOptions:(NSMatchingOptions)matchingOptions {
  570. return [[pattern regularExpressionWithOptions:regexpOptions] stringByReplacingMatchesInString:self
  571. options:matchingOptions
  572. range:self.fullRange
  573. withTemplate:template];
  574. }
  575. -(NSString *) stringByReplacingAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
  576. withTemplates:(NSArray <NSString *> *)replacements {
  577. return [self stringByReplacingAllOccurrencesOfPatterns:patterns
  578. withTemplates:replacements
  579. regularExpressionOptions:(NSRegularExpressionOptions) 0
  580. matchingOptions:(NSMatchingOptions) 0];
  581. }
  582. -(NSString *) stringByReplacingAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
  583. withTemplates:(NSArray <NSString *> *)replacements
  584. regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
  585. matchingOptions:(NSMatchingOptions)matchingOptions {
  586. NSMutableString *workingCopy = [self mutableCopy];
  587. [workingCopy replaceAllOccurrencesOfPatterns:patterns
  588. withTemplates:replacements
  589. regularExpressionOptions:regexpOptions
  590. matchingOptions:matchingOptions];
  591. return [workingCopy copy];
  592. }
  593. @end
  594. /*****************************************************************************/
  595. #pragma mark - SA_NSStringExtensions category implementation (NSMutableString)
  596. /*****************************************************************************/
  597. @implementation NSMutableString (SA_NSStringExtensions)
  598. /*************************************/
  599. #pragma mark - Working with characters
  600. /*************************************/
  601. -(void) removeCharactersInSet:(NSCharacterSet *)characters {
  602. NSRange rangeOfCharacters = [self rangeOfCharacterFromSet:characters];
  603. while (rangeOfCharacters.location != NSNotFound) {
  604. [self replaceCharactersInRange:rangeOfCharacters withString:@""];
  605. rangeOfCharacters = [self rangeOfCharacterFromSet:characters];
  606. }
  607. }
  608. -(void) removeCharactersInString:(NSString *)characters {
  609. [self removeCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:characters]];
  610. }
  611. /**********************/
  612. #pragma mark - Trimming
  613. /**********************/
  614. -(void) trimToMaxLengthInBytes:(NSUInteger)maxLengthInBytes
  615. usingEncoding:(NSStringEncoding)encoding
  616. withStringEnumerationOptions:(NSStringEnumerationOptions)enumerationOptions
  617. andStringTrimmingOptions:(SA_NSStringTrimmingOptions)trimmingOptions {
  618. // Trim whitespace.
  619. if (trimmingOptions & SA_NSStringTrimming_TrimWhitespace)
  620. [self replaceAllOccurrencesOfPattern:@"^\\s*(.*?)\\s*$"
  621. withTemplate:@"$1"];
  622. // Collapse whitespace.
  623. if (trimmingOptions & SA_NSStringTrimming_CollapseWhitespace)
  624. [self replaceAllOccurrencesOfPattern:@"\\s+"
  625. withTemplate:@" "];
  626. // Length of the ellipsis suffix, in bytes.
  627. NSString *ellipsis = @" …";
  628. NSUInteger ellipsisLengthInBytes = [ellipsis lengthOfBytesUsingEncoding:encoding];
  629. // Trim (leaving space for ellipsis, if necessary).
  630. __block NSUInteger cutoffLength = 0;
  631. [self enumerateSubstringsInRange:self.fullRange
  632. options:(enumerationOptions|NSStringEnumerationSubstringNotRequired)
  633. usingBlock:^(NSString * _Nullable substring,
  634. NSRange substringRange,
  635. NSRange enclosingRange,
  636. BOOL * _Nonnull stop) {
  637. NSUInteger endOfEnclosingRange = NSMaxRange(enclosingRange);
  638. NSUInteger endOfEnclosingRangeInBytes = [[self substringToIndex:endOfEnclosingRange] lengthOfBytesUsingEncoding:encoding];
  639. // If we need to append ellipsis when trimming...
  640. if (trimmingOptions & SA_NSStringTrimming_AppendEllipsis) {
  641. if ( self.fullRange.length == endOfEnclosingRange
  642. && endOfEnclosingRangeInBytes <= maxLengthInBytes) {
  643. // Either the ellipsis is not needed, because the string is not cut off...
  644. cutoffLength = endOfEnclosingRange;
  645. } else if (endOfEnclosingRangeInBytes <= (maxLengthInBytes - ellipsisLengthInBytes)) {
  646. // Or there will still be room for the ellipsis after adding this piece...
  647. cutoffLength = endOfEnclosingRange;
  648. } else {
  649. // Or we don’t add this piece.
  650. *stop = YES;
  651. }
  652. } else {
  653. if (endOfEnclosingRangeInBytes <= maxLengthInBytes) {
  654. cutoffLength = endOfEnclosingRange;
  655. } else {
  656. *stop = YES;
  657. }
  658. }
  659. }];
  660. NSUInteger lengthBeforeTrimming = self.length;
  661. [self deleteCharactersInRange:NSMakeRange(cutoffLength, self.length - cutoffLength)];
  662. // Trim whitespace again.
  663. if (trimmingOptions & SA_NSStringTrimming_TrimWhitespace)
  664. [self replaceAllOccurrencesOfPattern:@"^\\s*(.*?)\\s*$"
  665. withTemplate:@"$1"];
  666. // Append ellipsis.
  667. if ( trimmingOptions & SA_NSStringTrimming_AppendEllipsis
  668. && cutoffLength < lengthBeforeTrimming
  669. && maxLengthInBytes >= ellipsisLengthInBytes
  670. && ( cutoffLength > 0
  671. || !(trimmingOptions & SA_NSStringTrimming_ElideEllipsisWhenEmpty))
  672. ) {
  673. [self appendString:ellipsis];
  674. }
  675. }
  676. /*********************/
  677. #pragma mark - Padding
  678. /*********************/
  679. -(void) leftPadTo:(int)width {
  680. [self setString:[NSString stringWithFormat:@"%*s", width, [self stringByAppendingString:@"\0"].dataAsUTF8.bytes]];
  681. }
  682. /****************************************************/
  683. #pragma mark - Regular expression convenience methods
  684. /****************************************************/
  685. -(void) replaceFirstOccurrenceOfPattern:(NSString *)pattern
  686. withTemplate:(NSString *)template {
  687. [self replaceFirstOccurrenceOfPattern:pattern
  688. withTemplate:template
  689. regularExpressionOptions:(NSRegularExpressionOptions) 0
  690. matchingOptions:(NSMatchingOptions) 0];
  691. }
  692. -(void) replaceFirstOccurrenceOfPattern:(NSString *)pattern
  693. withTemplate:(NSString *)template
  694. regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
  695. matchingOptions:(NSMatchingOptions)matchingOptions {
  696. NSRegularExpression *regex = [pattern regularExpressionWithOptions:regexpOptions];
  697. NSTextCheckingResult *match = [regex firstMatchInString:self
  698. options:matchingOptions
  699. range:self.fullRange];
  700. if ( match
  701. && match.range.location != NSNotFound) {
  702. NSString *replacementString = [regex replacementStringForResult:match
  703. inString:self
  704. offset:0
  705. template:template];
  706. [self replaceCharactersInRange:match.range
  707. withString:replacementString];
  708. }
  709. }
  710. -(void) replaceAllOccurrencesOfPattern:(NSString *)pattern
  711. withTemplate:(NSString *)template {
  712. [self replaceAllOccurrencesOfPattern:pattern
  713. withTemplate:template
  714. regularExpressionOptions:(NSRegularExpressionOptions) 0
  715. matchingOptions:(NSMatchingOptions) 0];
  716. }
  717. -(void) replaceAllOccurrencesOfPattern:(NSString *)pattern
  718. withTemplate:(NSString *)template
  719. regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
  720. matchingOptions:(NSMatchingOptions)matchingOptions {
  721. [[pattern regularExpressionWithOptions:regexpOptions] replaceMatchesInString:self
  722. options:matchingOptions
  723. range:self.fullRange
  724. withTemplate:template];
  725. }
  726. -(void) replaceAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
  727. withTemplates:(NSArray <NSString *> *)replacements {
  728. [self replaceAllOccurrencesOfPatterns:patterns
  729. withTemplates:replacements
  730. regularExpressionOptions:(NSRegularExpressionOptions) 0
  731. matchingOptions:(NSMatchingOptions) 0];
  732. }
  733. -(void) replaceAllOccurrencesOfPatterns:(NSArray <NSString *> *)patterns
  734. withTemplates:(NSArray <NSString *> *)replacements
  735. regularExpressionOptions:(NSRegularExpressionOptions)regexpOptions
  736. matchingOptions:(NSMatchingOptions)matchingOptions {
  737. [patterns enumerateObjectsUsingBlock:^(NSString * _Nonnull pattern,
  738. NSUInteger idx,
  739. BOOL * _Nonnull stop) {
  740. NSString *replacement = (replacements.count > idx
  741. ? replacements[idx]
  742. : @"");
  743. [self replaceAllOccurrencesOfPattern:pattern
  744. withTemplate:replacement
  745. regularExpressionOptions:regexpOptions
  746. matchingOptions:matchingOptions];
  747. }];
  748. }
  749. @end