1// Copyright © 2019 650 Industries. All rights reserved. 2 3#import <EXUpdates/EXUpdatesParameterParser.h> 4 5NS_ASSUME_NONNULL_BEGIN 6 7@implementation EXUpdatesParameterParser { 8 // String to be parsed. 9 NSString *_parameterString; 10 11 // Current position in the string. 12 int _currentPosition; 13 14 // Start of a token. 15 int _i1; 16 17 // End of a token. 18 int _i2; 19} 20 21/** 22 * A helper method to process the parsed token. This method removes 23 * leading and trailing blanks as well as enclosing quotation marks, 24 * when necessary. 25 */ 26- (nullable NSString *)getTokenWithQuoted:(BOOL)quoted { 27 // Trim leading white spaces 28 while ((_i1 < _i2) && [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[_parameterString characterAtIndex:_i1]]) { 29 _i1++; 30 } 31 32 // Trim trailing white spaces 33 while ((_i2 > _i1) && [[NSCharacterSet whitespaceCharacterSet] characterIsMember:[_parameterString characterAtIndex:(_i2 - 1)]]) { 34 _i2--; 35 } 36 37 // Strip away quotation marks if necessary 38 if (quoted 39 && ((_i2 - _i1) >= 2) 40 && ([_parameterString characterAtIndex:_i1] == '"') 41 && ([_parameterString characterAtIndex:(_i2 - 1)] == '"')) { 42 _i1++; 43 _i2--; 44 } 45 46 NSString *result = nil; 47 if (_i2 > _i1) { 48 result = [_parameterString substringWithRange:NSMakeRange(_i1, _i2 - _i1)]; 49 } 50 return result; 51} 52 53/** 54 * Parses out a token until any of the given terminators 55 * is encountered. 56 */ 57- (NSString *)parseTokenWithTerminators:(NSCharacterSet *)terminators { 58 unichar ch; 59 _i1 = _currentPosition; 60 _i2 = _currentPosition; 61 while ([self hasChar]) { 62 ch = [_parameterString characterAtIndex:_currentPosition]; 63 if ([terminators characterIsMember:ch]) { 64 break; 65 } 66 _i2++; 67 _currentPosition++; 68 } 69 return [self getTokenWithQuoted:false]; 70} 71 72/** 73 * Parses out a token until any of the given terminators 74 * is encountered outside the quotation marks. 75 */ 76- (NSString *)parseQuotedTokenWithTerminators:(NSCharacterSet *)terminators { 77 unichar ch; 78 _i1 = _currentPosition; 79 _i2 = _currentPosition; 80 BOOL quoted = false; 81 BOOL charEscaped = false; 82 83 while ([self hasChar]) { 84 ch = [_parameterString characterAtIndex:_currentPosition]; 85 if (!quoted && [terminators characterIsMember:ch]) { 86 break; 87 } 88 if (!charEscaped && ch == '"') { 89 quoted = !quoted; 90 } 91 charEscaped = (!charEscaped && ch == '\\'); 92 _i2++; 93 _currentPosition++; 94 } 95 96 return [self getTokenWithQuoted:true]; 97} 98 99/** 100 * Are there any characters left to parse? 101 */ 102- (BOOL)hasChar { 103 return _currentPosition < _parameterString.length; 104} 105 106/** 107 * Extracts a map of name/value pairs from the given string. Names are expected to be unique. 108 */ 109- (NSDictionary *)parseParameterString:(NSString *)parameterString withDelimiter:(unichar)delimiter { 110 _parameterString = parameterString; 111 112 NSMutableDictionary *params = [NSMutableDictionary new]; 113 114 NSString *paramName; 115 NSString *paramValue; 116 117 NSCharacterSet *charSetDelimiter = [NSCharacterSet characterSetWithCharactersInString:[NSString stringWithFormat:@"%C", delimiter]]; 118 NSCharacterSet *charSetDelimiterAndEquals = [NSCharacterSet characterSetWithCharactersInString:[NSString stringWithFormat:@"%C=", delimiter]]; 119 120 while ([self hasChar]) { 121 paramName = [self parseTokenWithTerminators:charSetDelimiterAndEquals]; 122 paramValue = nil; 123 124 if ([self hasChar] && [_parameterString characterAtIndex:_currentPosition] == '=') { 125 _currentPosition++; // skip '=' 126 paramValue = [self parseQuotedTokenWithTerminators:charSetDelimiter]; 127 } 128 129 if ([self hasChar] && ([_parameterString characterAtIndex:_currentPosition] == delimiter)) { 130 _currentPosition++; // skip separator 131 } 132 133 if (paramName != nil && paramName.length > 0) { 134 [params setValue:(paramValue ?: [NSNull null]) forKey:paramName]; 135 } 136 } 137 138 return params; 139} 140 141@end 142 143NS_ASSUME_NONNULL_END 144