/* * Copyright 2012 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "ZXAddressBookAUResultParser.h" #import "ZXAddressBookDoCoMoResultParser.h" #import "ZXAddressBookParsedResult.h" #import "ZXBizcardResultParser.h" #import "ZXBookmarkDoCoMoResultParser.h" #import "ZXCalendarParsedResult.h" #import "ZXEmailAddressParsedResult.h" #import "ZXEmailAddressResultParser.h" #import "ZXEmailDoCoMoResultParser.h" #import "ZXExpandedProductParsedResult.h" #import "ZXExpandedProductResultParser.h" #import "ZXGeoParsedResult.h" #import "ZXGeoResultParser.h" #import "ZXISBNParsedResult.h" #import "ZXISBNResultParser.h" #import "ZXParsedResult.h" #import "ZXProductParsedResult.h" #import "ZXProductResultParser.h" #import "ZXResult.h" #import "ZXResultParser.h" #import "ZXSMSMMSResultParser.h" #import "ZXSMSParsedResult.h" #import "ZXSMSTOMMSTOResultParser.h" #import "ZXSMTPResultParser.h" #import "ZXTelParsedResult.h" #import "ZXTelResultParser.h" #import "ZXTextParsedResult.h" #import "ZXURIParsedResult.h" #import "ZXURIResultParser.h" #import "ZXURLTOResultParser.h" #import "ZXVCardResultParser.h" #import "ZXVEventResultParser.h" #import "ZXVINResultParser.h" #import "ZXWifiParsedResult.h" #import "ZXWifiResultParser.h" static NSArray *ZX_PARSERS = nil; static NSRegularExpression *ZX_DIGITS = nil; static NSString *ZX_AMPERSAND = @"&"; static NSString *ZX_EQUALS = @"="; static unichar ZX_BYTE_ORDER_MARK = L'\ufeff'; @implementation ZXResultParser + (void)initialize { if ([self class] != [ZXResultParser class]) return; ZX_PARSERS = @[[[ZXBookmarkDoCoMoResultParser alloc] init], [[ZXAddressBookDoCoMoResultParser alloc] init], [[ZXEmailDoCoMoResultParser alloc] init], [[ZXAddressBookAUResultParser alloc] init], [[ZXVCardResultParser alloc] init], [[ZXBizcardResultParser alloc] init], [[ZXVEventResultParser alloc] init], [[ZXEmailAddressResultParser alloc] init], [[ZXSMTPResultParser alloc] init], [[ZXTelResultParser alloc] init], [[ZXSMSMMSResultParser alloc] init], [[ZXSMSTOMMSTOResultParser alloc] init], [[ZXGeoResultParser alloc] init], [[ZXWifiResultParser alloc] init], [[ZXURLTOResultParser alloc] init], [[ZXURIResultParser alloc] init], [[ZXISBNResultParser alloc] init], [[ZXProductResultParser alloc] init], [[ZXExpandedProductResultParser alloc] init], [[ZXVINResultParser alloc] init]]; ZX_DIGITS = [[NSRegularExpression alloc] initWithPattern:@"^\\d+$" options:0 error:nil]; } - (ZXParsedResult *)parse:(ZXResult *)result { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] userInfo:nil]; } + (NSString *)massagedText:(ZXResult *)result { NSString *text = result.text; if (text.length > 0 && [text characterAtIndex:0] == ZX_BYTE_ORDER_MARK) { text = [text substringFromIndex:1]; } return text; } + (ZXParsedResult *)parseResult:(ZXResult *)theResult { for (ZXResultParser *parser in ZX_PARSERS) { ZXParsedResult *result = [parser parse:theResult]; if (result != nil) { return result; } } return [ZXTextParsedResult textParsedResultWithText:[theResult text] language:nil]; } - (void)maybeAppend:(NSString *)value result:(NSMutableString *)result { if (value != nil) { [result appendFormat:@"\n%@", value]; } } - (void)maybeAppendArray:(NSArray *)value result:(NSMutableString *)result { if (value != nil) { for (NSString *s in value) { [result appendFormat:@"\n%@", s]; } } } - (NSArray *)maybeWrap:(NSString *)value { return value == nil ? nil : @[value]; } + (NSString *)unescapeBackslash:(NSString *)escaped { NSUInteger backslash = [escaped rangeOfString:@"\\"].location; if (backslash == NSNotFound) { return escaped; } NSUInteger max = [escaped length]; NSMutableString *unescaped = [NSMutableString stringWithCapacity:max - 1]; [unescaped appendString:[escaped substringToIndex:backslash]]; BOOL nextIsEscaped = NO; for (int i = (int)backslash; i < max; i++) { unichar c = [escaped characterAtIndex:i]; if (nextIsEscaped || c != '\\') { [unescaped appendFormat:@"%C", c]; nextIsEscaped = NO; } else { nextIsEscaped = YES; } } return unescaped; } + (int)parseHexDigit:(unichar)c { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'f') { return 10 + (c - 'a'); } if (c >= 'A' && c <= 'F') { return 10 + (c - 'A'); } return -1; } + (BOOL)isStringOfDigits:(NSString *)value length:(unsigned int)length { return value != nil && length > 0 && length == value.length && [ZX_DIGITS numberOfMatchesInString:value options:0 range:NSMakeRange(0, value.length)] > 0; } - (NSString *)urlDecode:(NSString *)escaped { if (escaped == nil) { return nil; } int first = [self findFirstEscape:escaped]; if (first == -1) { return escaped; } NSUInteger max = [escaped length]; NSMutableString *unescaped = [NSMutableString stringWithCapacity:max - 2]; [unescaped appendString:[escaped substringToIndex:first]]; for (int i = first; i < max; i++) { unichar c = [escaped characterAtIndex:i]; switch (c) { case '+': [unescaped appendString:@" "]; break; case '%': if (i >= max - 2) { [unescaped appendString:@"%"]; } else { int firstDigitValue = [[self class] parseHexDigit:[escaped characterAtIndex:++i]]; int secondDigitValue = [[self class] parseHexDigit:[escaped characterAtIndex:++i]]; if (firstDigitValue < 0 || secondDigitValue < 0) { [unescaped appendFormat:@"%%%C%C", [escaped characterAtIndex:i - 1], [escaped characterAtIndex:i]]; } [unescaped appendFormat:@"%C", (unichar)((firstDigitValue << 4) + secondDigitValue)]; } break; default: [unescaped appendFormat:@"%C", c]; break; } } return unescaped; } - (int)findFirstEscape:(NSString *)escaped { NSUInteger max = [escaped length]; for (int i = 0; i < max; i++) { unichar c = [escaped characterAtIndex:i]; if (c == '+' || c == '%') { return i; } } return -1; } + (BOOL)isSubstringOfDigits:(NSString *)value offset:(int)offset length:(int)length { if (value == nil || length <= 0) { return NO; } int max = offset + length; return value.length >= max && [ZX_DIGITS numberOfMatchesInString:value options:0 range:NSMakeRange(offset, max - offset)] > 0; } - (NSMutableDictionary *)parseNameValuePairs:(NSString *)uri { NSUInteger paramStart = [uri rangeOfString:@"?"].location; if (paramStart == NSNotFound) { return nil; } NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:3]; for (NSString *keyValue in [[uri substringFromIndex:paramStart + 1] componentsSeparatedByString:ZX_AMPERSAND]) { [self appendKeyValue:keyValue result:result]; } return result; } - (void)appendKeyValue:(NSString *)keyValue result:(NSMutableDictionary *)result { NSRange equalsRange = [keyValue rangeOfString:ZX_EQUALS]; if (equalsRange.location != NSNotFound) { NSString *key = [keyValue substringToIndex:equalsRange.location]; NSString *value = [keyValue substringFromIndex:equalsRange.location + 1]; value = [self urlDecode:value]; result[key] = value; } } + (NSString *)urlDecode:(NSString *)encoded { NSString *result = [encoded stringByReplacingOccurrencesOfString:@"+" withString:@" "]; result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; return result; } + (NSArray *)matchPrefixedField:(NSString *)prefix rawText:(NSString *)rawText endChar:(unichar)endChar trim:(BOOL)trim { NSMutableArray *matches = nil; NSUInteger i = 0; NSUInteger max = [rawText length]; while (i < max) { i = [rawText rangeOfString:prefix options:NSLiteralSearch range:NSMakeRange(i, [rawText length] - i - 1)].location; if (i == NSNotFound) { break; } i += [prefix length]; // Skip past this prefix we found to start NSUInteger start = i; // Found the start of a match here BOOL more = YES; while (more) { i = [rawText rangeOfString:[NSString stringWithFormat:@"%C", endChar] options:NSLiteralSearch range:NSMakeRange(i, [rawText length] - i)].location; if (i == NSNotFound) { // No terminating end character? uh, done. Set i such that loop terminates and break i = [rawText length]; more = NO; } else if ([self countPrecedingBackslashes:rawText pos:i] % 2 != 0) { // semicolon was escaped (odd count of preceding backslashes) so continue i++; } else { // found a match if (matches == nil) { matches = [NSMutableArray arrayWithCapacity:3]; // lazy init } NSString *element = [self unescapeBackslash:[rawText substringWithRange:NSMakeRange(start, i - start)]]; if (trim) { element = [element stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } if (element.length > 0) { [matches addObject:element]; } i++; more = NO; } } } if (matches == nil || [matches count] == 0) { return nil; } return matches; } + (int)countPrecedingBackslashes:(NSString *)s pos:(NSInteger)pos { int count = 0; for (NSInteger i = pos - 1; i >= 0; i--) { if ([s characterAtIndex:i] == '\\') { count++; } else { break; } } return count; } + (NSString *)matchSinglePrefixedField:(NSString *)prefix rawText:(NSString *)rawText endChar:(unichar)endChar trim:(BOOL)trim { NSArray *matches = [self matchPrefixedField:prefix rawText:rawText endChar:endChar trim:trim]; return matches == nil ? nil : matches[0]; } @end