Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

ZXVCardResultParser.m 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. * Copyright 2012 ZXing authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "ZXAddressBookParsedResult.h"
  17. #import "ZXResult.h"
  18. #import "ZXVCardResultParser.h"
  19. static NSRegularExpression *ZX_BEGIN_VCARD = nil;
  20. static NSRegularExpression *ZX_VCARD_LIKE_DATE = nil;
  21. static NSRegularExpression *ZX_CR_LF_SPACE_TAB = nil;
  22. static NSRegularExpression *ZX_NEWLINE_ESCAPE = nil;
  23. static NSRegularExpression *ZX_VCARD_ESCAPES = nil;
  24. static NSString *ZX_EQUALS = @"=";
  25. static NSString *ZX_SEMICOLON = @";";
  26. static NSRegularExpression *ZX_UNESCAPED_SEMICOLONS = nil;
  27. static NSCharacterSet *ZX_COMMA = nil;
  28. static NSCharacterSet *ZX_SEMICOLON_OR_COMMA = nil;
  29. @implementation ZXVCardResultParser
  30. + (void)initialize {
  31. if ([self class] != [ZXVCardResultParser class]) return;
  32. ZX_BEGIN_VCARD = [[NSRegularExpression alloc] initWithPattern:@"BEGIN:VCARD" options:NSRegularExpressionCaseInsensitive error:nil];
  33. ZX_VCARD_LIKE_DATE = [[NSRegularExpression alloc] initWithPattern:@"\\d{4}-?\\d{2}-?\\d{2}" options:0 error:nil];
  34. ZX_CR_LF_SPACE_TAB = [[NSRegularExpression alloc] initWithPattern:@"\r\n[ \t]" options:0 error:nil];
  35. ZX_NEWLINE_ESCAPE = [[NSRegularExpression alloc] initWithPattern:@"\\\\[nN]" options:0 error:nil];
  36. ZX_VCARD_ESCAPES = [[NSRegularExpression alloc] initWithPattern:@"\\\\([,;\\\\])" options:0 error:nil];
  37. ZX_UNESCAPED_SEMICOLONS = [[NSRegularExpression alloc] initWithPattern:@"(?<!\\\\);+" options:0 error:nil];
  38. ZX_COMMA = [NSCharacterSet characterSetWithCharactersInString:@","];
  39. ZX_SEMICOLON_OR_COMMA = [NSCharacterSet characterSetWithCharactersInString:@";,"];
  40. }
  41. - (ZXParsedResult *)parse:(ZXResult *)result {
  42. // Although we should insist on the raw text ending with "END:VCARD", there's no reason
  43. // to throw out everything else we parsed just because this was omitted. In fact, Eclair
  44. // is doing just that, and we can't parse its contacts without this leniency.
  45. NSString *rawText = [ZXResultParser massagedText:result];
  46. if ([ZX_BEGIN_VCARD numberOfMatchesInString:rawText options:0 range:NSMakeRange(0, rawText.length)] == 0) {
  47. return nil;
  48. }
  49. NSMutableArray *names = [[self class] matchVCardPrefixedField:@"FN" rawText:rawText trim:YES parseFieldDivider:NO];
  50. if (names == nil) {
  51. // If no display names found, look for regular name fields and format them
  52. names = [[self class] matchVCardPrefixedField:@"N" rawText:rawText trim:YES parseFieldDivider:NO];
  53. [self formatNames:names];
  54. }
  55. NSArray *nicknameString = [[self class] matchSingleVCardPrefixedField:@"NICKNAME" rawText:rawText trim:YES parseFieldDivider:NO];
  56. NSArray *nicknames = nicknameString == nil ? nil : [nicknameString[0] componentsSeparatedByCharactersInSet:ZX_COMMA];
  57. NSArray *phoneNumbers = [[self class] matchVCardPrefixedField:@"TEL" rawText:rawText trim:YES parseFieldDivider:NO];
  58. NSArray *emails = [[self class] matchVCardPrefixedField:@"EMAIL" rawText:rawText trim:YES parseFieldDivider:NO];
  59. NSArray *note = [[self class] matchSingleVCardPrefixedField:@"NOTE" rawText:rawText trim:NO parseFieldDivider:NO];
  60. NSMutableArray *addresses = [[self class] matchVCardPrefixedField:@"ADR" rawText:rawText trim:YES parseFieldDivider:YES];
  61. NSArray *org = [[self class] matchSingleVCardPrefixedField:@"ORG" rawText:rawText trim:YES parseFieldDivider:YES];
  62. NSArray *birthday = [[self class] matchSingleVCardPrefixedField:@"BDAY" rawText:rawText trim:YES parseFieldDivider:NO];
  63. if (birthday != nil && ![self isLikeVCardDate:birthday[0]]) {
  64. birthday = nil;
  65. }
  66. NSArray *title = [[self class] matchSingleVCardPrefixedField:@"TITLE" rawText:rawText trim:YES parseFieldDivider:NO];
  67. NSArray *urls = [[self class] matchVCardPrefixedField:@"URL" rawText:rawText trim:YES parseFieldDivider:NO];
  68. NSArray *instantMessenger = [[self class] matchSingleVCardPrefixedField:@"IMPP" rawText:rawText trim:YES parseFieldDivider:NO];
  69. NSArray *geoString = [[self class] matchSingleVCardPrefixedField:@"GEO" rawText:rawText trim:YES parseFieldDivider:NO];
  70. NSArray *geo = geoString == nil ? nil : [geoString[0] componentsSeparatedByCharactersInSet:ZX_SEMICOLON_OR_COMMA];
  71. if (geo != nil && geo.count != 2) {
  72. geo = nil;
  73. }
  74. return [ZXAddressBookParsedResult addressBookParsedResultWithNames:[self toPrimaryValues:names]
  75. nicknames:nicknames
  76. pronunciation:nil
  77. phoneNumbers:[self toPrimaryValues:phoneNumbers]
  78. phoneTypes:[self toTypes:phoneNumbers]
  79. emails:[self toPrimaryValues:emails]
  80. emailTypes:[self toTypes:emails]
  81. instantMessenger:[self toPrimaryValue:instantMessenger]
  82. note:[self toPrimaryValue:note]
  83. addresses:[self toPrimaryValues:addresses]
  84. addressTypes:[self toTypes:addresses]
  85. org:[self toPrimaryValue:org]
  86. birthday:[self toPrimaryValue:birthday]
  87. title:[self toPrimaryValue:title]
  88. urls:[self toPrimaryValues:urls]
  89. geo:geo];
  90. }
  91. + (NSMutableArray *)matchVCardPrefixedField:(NSString *)prefix rawText:(NSString *)rawText trim:(BOOL)trim parseFieldDivider:(BOOL)parseFieldDivider {
  92. NSMutableArray *matches = nil;
  93. NSUInteger i = 0;
  94. NSUInteger max = [rawText length];
  95. while (i < max) {
  96. // At start or after newling, match prefix, followed by optional metadata
  97. // (led by ;) ultimately ending in colon
  98. NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"(?:^|\n)%@(?:;([^:]*))?:", prefix]
  99. options:NSRegularExpressionCaseInsensitive error:nil];
  100. if (i > 0) {
  101. i--; // Find from i-1 not i since looking at the preceding character
  102. }
  103. NSArray *regexMatches = [regex matchesInString:rawText options:0 range:NSMakeRange(i, rawText.length - i)];
  104. if (regexMatches.count == 0) {
  105. break;
  106. }
  107. NSRange matchRange = [regexMatches[0] range];
  108. i = matchRange.location + matchRange.length;
  109. NSString *metadataString = nil;
  110. if ([regexMatches[0] rangeAtIndex:1].location != NSNotFound) {
  111. metadataString = [rawText substringWithRange:[regexMatches[0] rangeAtIndex:1]];
  112. }
  113. NSMutableArray *metadata = nil;
  114. BOOL quotedPrintable = NO;
  115. NSString *quotedPrintableCharset = nil;
  116. if (metadataString != nil) {
  117. for (NSString *metadatum in [metadataString componentsSeparatedByString:ZX_SEMICOLON]) {
  118. if (metadata == nil) {
  119. metadata = [NSMutableArray array];
  120. }
  121. [metadata addObject:metadatum];
  122. NSUInteger equals = [metadatum rangeOfString:ZX_EQUALS].location;
  123. if (equals != NSNotFound) {
  124. NSString *key = [metadatum substringToIndex:equals];
  125. NSString *value = [metadatum substringFromIndex:equals + 1];
  126. if ([@"ENCODING" caseInsensitiveCompare:key] == NSOrderedSame &&
  127. [@"QUOTED-PRINTABLE" caseInsensitiveCompare:value] == NSOrderedSame) {
  128. quotedPrintable = YES;
  129. } else if ([@"CHARSET" caseInsensitiveCompare:key] == NSOrderedSame) {
  130. quotedPrintableCharset = value;
  131. }
  132. }
  133. }
  134. }
  135. NSUInteger matchStart = i; // Found the start of a match here
  136. while ((NSUInteger)(i = [rawText rangeOfString:@"\n" options:NSLiteralSearch range:NSMakeRange(i, [rawText length] - i)].location) != NSNotFound) { // Really, end in \r\n
  137. if (i < [rawText length] - 1 && // But if followed by tab or space,
  138. ([rawText characterAtIndex:i + 1] == ' ' || // this is only a continuation
  139. [rawText characterAtIndex:i + 1] == '\t')) {
  140. i += 2; // Skip \n and continutation whitespace
  141. } else if (quotedPrintable && // If preceded by = in quoted printable
  142. ((i >= 1 && [rawText characterAtIndex:i - 1] == '=') || // this is a continuation
  143. (i >= 2 && [rawText characterAtIndex:i - 2] == '='))) {
  144. i++; // Skip \n
  145. } else {
  146. break;
  147. }
  148. }
  149. if (i == NSNotFound) {
  150. // No terminating end character? uh, done. Set i such that loop terminates and break
  151. i = max;
  152. } else if (i > matchStart) {
  153. // found a match
  154. if (matches == nil) {
  155. matches = [NSMutableArray arrayWithCapacity:1];
  156. }
  157. if (i >= 1 && [rawText characterAtIndex:i-1] == '\r') {
  158. i--; // Back up over \r, which really should be there
  159. }
  160. NSString *element = [rawText substringWithRange:NSMakeRange(matchStart, i - matchStart)];
  161. if (trim) {
  162. element = [element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  163. }
  164. if (quotedPrintable) {
  165. element = [self decodeQuotedPrintable:element charset:quotedPrintableCharset];
  166. if (parseFieldDivider) {
  167. element = [[ZX_UNESCAPED_SEMICOLONS stringByReplacingMatchesInString:element options:0 range:NSMakeRange(0, element.length) withTemplate:@"\n"]
  168. stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  169. }
  170. } else {
  171. if (parseFieldDivider) {
  172. element = [[ZX_UNESCAPED_SEMICOLONS stringByReplacingMatchesInString:element options:0 range:NSMakeRange(0, element.length) withTemplate:@"\n"]
  173. stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  174. }
  175. element = [ZX_CR_LF_SPACE_TAB stringByReplacingMatchesInString:element options:0 range:NSMakeRange(0, element.length) withTemplate:@""];
  176. element = [ZX_NEWLINE_ESCAPE stringByReplacingMatchesInString:element options:0 range:NSMakeRange(0, element.length) withTemplate:@"\n"];
  177. element = [ZX_VCARD_ESCAPES stringByReplacingMatchesInString:element options:0 range:NSMakeRange(0, element.length) withTemplate:@"$1"];
  178. }
  179. if (metadata == nil) {
  180. NSMutableArray *match = [NSMutableArray arrayWithObject:element];
  181. [match addObject:element];
  182. [matches addObject:match];
  183. } else {
  184. [metadata insertObject:element atIndex:0];
  185. [matches addObject:metadata];
  186. }
  187. i++;
  188. } else {
  189. i++;
  190. }
  191. }
  192. return matches;
  193. }
  194. + (NSString *)decodeQuotedPrintable:(NSString *)value charset:(NSString *)charset {
  195. NSUInteger length = [value length];
  196. NSMutableString *result = [NSMutableString stringWithCapacity:length];
  197. NSMutableData *fragmentBuffer = [NSMutableData data];
  198. for (int i = 0; i < length; i++) {
  199. unichar c = [value characterAtIndex:i];
  200. switch (c) {
  201. case '\r':
  202. case '\n':
  203. break;
  204. case '=':
  205. if (i < length - 2) {
  206. unichar nextChar = [value characterAtIndex:i + 1];
  207. if (nextChar != '\r' && nextChar != '\n') {
  208. unichar nextNextChar = [value characterAtIndex:i + 2];
  209. int firstDigit = [self parseHexDigit:nextChar];
  210. int secondDigit = [self parseHexDigit:nextNextChar];
  211. if (firstDigit >= 0 && secondDigit >= 0) {
  212. int encodedByte = (firstDigit << 4) + secondDigit;
  213. [fragmentBuffer appendBytes:&encodedByte length:1];
  214. } // else ignore it, assume it was incorrectly encoded
  215. i += 2;
  216. }
  217. }
  218. break;
  219. default:
  220. [self maybeAppendFragment:fragmentBuffer charset:charset result:result];
  221. [result appendFormat:@"%C", c];
  222. }
  223. }
  224. [self maybeAppendFragment:fragmentBuffer charset:charset result:result];
  225. return result;
  226. }
  227. + (void)maybeAppendFragment:(NSMutableData *)fragmentBuffer charset:(NSString *)charset result:(NSMutableString *)result {
  228. if ([fragmentBuffer length] > 0) {
  229. NSString *fragment;
  230. if (charset == nil || CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset) == kCFStringEncodingInvalidId) {
  231. fragment = [[NSString alloc] initWithData:fragmentBuffer encoding:NSUTF8StringEncoding];
  232. } else {
  233. fragment = [[NSString alloc] initWithData:fragmentBuffer encoding:CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset))];
  234. if (!fragment) {
  235. fragment = [[NSString alloc] initWithData:fragmentBuffer encoding:NSUTF8StringEncoding];
  236. }
  237. }
  238. [fragmentBuffer setLength:0];
  239. [result appendString:fragment];
  240. }
  241. }
  242. + (NSArray *)matchSingleVCardPrefixedField:(NSString *)prefix rawText:(NSString *)rawText trim:(BOOL)trim parseFieldDivider:(BOOL)parseFieldDivider {
  243. NSArray *values = [self matchVCardPrefixedField:prefix rawText:rawText trim:trim parseFieldDivider:parseFieldDivider];
  244. return values == nil ? nil : values[0];
  245. }
  246. - (NSString *)toPrimaryValue:(NSArray *)list {
  247. return list == nil || list.count == 0 ? nil : list[0];
  248. }
  249. - (NSArray *)toPrimaryValues:(NSArray *)lists {
  250. if (lists == nil || lists.count == 0) {
  251. return nil;
  252. }
  253. NSMutableArray *result = [NSMutableArray arrayWithCapacity:lists.count];
  254. for (NSArray *list in lists) {
  255. NSString *value = list[0];
  256. if (value != nil && value.length > 0) {
  257. [result addObject:value];
  258. }
  259. }
  260. return result;
  261. }
  262. - (NSArray *)toTypes:(NSArray *)lists {
  263. if (lists == nil || lists.count == 0) {
  264. return nil;
  265. }
  266. NSMutableArray *result = [NSMutableArray arrayWithCapacity:lists.count];
  267. for (NSArray *list in lists) {
  268. NSString *type = nil;
  269. for (int i = 1; i < list.count; i++) {
  270. NSString *metadatum = list[i];
  271. NSUInteger equals = [metadatum rangeOfString:@"=" options:NSCaseInsensitiveSearch].location;
  272. if (equals == NSNotFound) {
  273. // take the whole thing as a usable label
  274. type = metadatum;
  275. break;
  276. }
  277. if ([@"TYPE" isEqualToString:[[metadatum substringToIndex:equals] uppercaseString]]) {
  278. type = [metadatum substringFromIndex:equals + 1];
  279. break;
  280. }
  281. }
  282. if (type) {
  283. [result addObject:type];
  284. } else {
  285. [result addObject:[NSNull null]];
  286. }
  287. }
  288. return result;
  289. }
  290. - (BOOL)isLikeVCardDate:(NSString *)value {
  291. return value == nil || [ZX_VCARD_LIKE_DATE numberOfMatchesInString:value options:0 range:NSMakeRange(0, value.length)] > 0;
  292. }
  293. /**
  294. * Formats name fields of the form "Public;John;Q.;Reverend;III" into a form like
  295. * "Reverend John Q. Public III".
  296. *
  297. * @param names name values to format, in place
  298. */
  299. - (void)formatNames:(NSMutableArray *)names {
  300. if (names != nil) {
  301. for (NSMutableArray *list in names) {
  302. NSString *name = list[0];
  303. NSArray *allComponents = [name componentsSeparatedByString:@";"];
  304. NSMutableArray *components = [NSMutableArray array];
  305. for (NSString *component in allComponents) {
  306. if ([component length] > 0) {
  307. [components addObject:component];
  308. }
  309. }
  310. NSMutableString *newName = [NSMutableString stringWithCapacity:100];
  311. [self maybeAppendComponent:components i:3 newName:newName];
  312. [self maybeAppendComponent:components i:1 newName:newName];
  313. [self maybeAppendComponent:components i:2 newName:newName];
  314. [self maybeAppendComponent:components i:0 newName:newName];
  315. [self maybeAppendComponent:components i:4 newName:newName];
  316. list[0] = [newName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  317. }
  318. }
  319. }
  320. - (void)maybeAppendComponent:(NSArray *)components i:(int)i newName:(NSMutableString *)newName {
  321. if ([components count] > i && components[i] && [(NSString *)components[i] length] > 0) {
  322. if ([newName length] > 0) {
  323. [newName appendString:@" "];
  324. }
  325. [newName appendString:components[i]];
  326. }
  327. }
  328. @end