diff options
Diffstat (limited to 'Foundation/GTMURITemplate.m')
-rw-r--r-- | Foundation/GTMURITemplate.m | 521 |
1 files changed, 0 insertions, 521 deletions
diff --git a/Foundation/GTMURITemplate.m b/Foundation/GTMURITemplate.m deleted file mode 100644 index 5938b72..0000000 --- a/Foundation/GTMURITemplate.m +++ /dev/null @@ -1,521 +0,0 @@ -/* Copyright (c) 2010 Google Inc. - * - * 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 "GTMURITemplate.h" - -#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 - -// Key constants for handling variables. -static NSString *const kVariable = @"variable"; // NSString -static NSString *const kExplode = @"explode"; // NSString -static NSString *const kPartial = @"partial"; // NSString -static NSString *const kPartialValue = @"partialValue"; // NSNumber - -// Help for passing the Expansion info in one shot. -struct ExpansionInfo { - // Constant for the whole expansion. - unichar expressionOperator; - NSString *joiner; - BOOL allowReservedInEscape; - - // Update for each variable. - NSString *explode; -}; - -// Helper just to shorten the lines when needed. -static NSString *UnescapeString(NSString *str) { - return [str stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; -} - -static NSString *EscapeString(NSString *str, BOOL allowReserved) { - static CFStringRef kReservedChars = CFSTR(":/?#[]@!$&'()*+,;="); - CFStringRef allowedChars = allowReserved ? kReservedChars : NULL; - - // NSURL's stringByAddingPercentEscapesUsingEncoding: does not escape - // some characters that should be escaped in URL parameters, like / and ?; - // we'll use CFURL to force the encoding of those - // - // Reference: http://www.ietf.org/rfc/rfc3986.txt - static CFStringRef kCharsToForceEscape = CFSTR("!*'();:@&=+$,/?%#[]"); - static CFStringRef kCharsToForceEscapeSansReserved = CFSTR("%"); - CFStringRef forceEscapedChars = - allowReserved ? kCharsToForceEscapeSansReserved : kCharsToForceEscape; - - NSString *resultStr = str; - CFStringRef escapedStr = - CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, - (CFStringRef)str, - allowedChars, - forceEscapedChars, - kCFStringEncodingUTF8); - if (escapedStr) { - resultStr = [(NSString *)escapedStr autorelease]; - } - return resultStr; -} - -@interface GTMURITemplate () -+ (BOOL)parseExpression:(NSString *)expression - expressionOperator:(unichar*)outExpressionOperator - variables:(NSMutableArray **)outVariables - defaultValues:(NSMutableDictionary **)outDefaultValues; - -+ (NSString *)expandVariables:(NSArray *)variables - expressionOperator:(unichar)expressionOperator - values:(id)valueProvider - defaultValues:(NSMutableDictionary *)defaultValues; - -+ (NSString *)expandString:(NSString *)valueStr - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo; -+ (NSString *)expandArray:(NSArray *)valueArray - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo; -+ (NSString *)expandDictionary:(NSDictionary *)valueDict - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo; -@end - -@implementation GTMURITemplate - -#pragma mark Internal Helpers - -+ (BOOL)parseExpression:(NSString *)expression - expressionOperator:(unichar*)outExpressionOperator - variables:(NSMutableArray **)outVariables - defaultValues:(NSMutableDictionary **)outDefaultValues { - - // Please see the spec for full details, but here are the basics: - // - // URI-Template = *( literals / expression ) - // expression = "{" [ operator ] variable-list "}" - // variable-list = varspec *( "," varspec ) - // varspec = varname [ modifier ] [ "=" default ] - // varname = varchar *( varchar / "." ) - // modifier = explode / partial - // explode = ( "*" / "+" ) - // partial = ( substring / remainder ) offset - // - // Examples: - // http://www.example.com/foo{?query,number} - // http://maps.com/mapper{?address*} - // http://directions.org/directions{?from+,to+} - // http://search.org/query{?terms+=none} - // - - // http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-2.2 - // Operator and op-reserve characters - static NSCharacterSet *operatorSet = nil; - // http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-2.4.1 - // Explode characters - static NSCharacterSet *explodeSet = nil; - // http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-2.4.2 - // Partial (prefix/subset) characters - static NSCharacterSet *partialSet = nil; - - @synchronized(self) { - if (operatorSet == nil) { - operatorSet = [[NSCharacterSet characterSetWithCharactersInString:@"+./;?|!@"] retain]; - } - if (explodeSet == nil) { - explodeSet = [[NSCharacterSet characterSetWithCharactersInString:@"*+"] retain]; - } - if (partialSet == nil) { - partialSet = [[NSCharacterSet characterSetWithCharactersInString:@":^"] retain]; - } - } - - // http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-3.3 - // Empty expression inlines the expression. - if ([expression length] == 0) return NO; - - // Pull off any operator. - *outExpressionOperator = 0; - unichar firstChar = [expression characterAtIndex:0]; - if ([operatorSet characterIsMember:firstChar]) { - *outExpressionOperator = firstChar; - expression = [expression substringFromIndex:1]; - } - - if ([expression length] == 0) return NO; - - // Need to find at least one varspec for the expresssion to be considered - // valid. - BOOL gotAVarspec = NO; - - // Split the variable list. - NSArray *varspecs = [expression componentsSeparatedByString:@","]; - - // Extract the defaults, explodes and modifiers from the varspecs. - *outVariables = [NSMutableArray arrayWithCapacity:[varspecs count]]; - for (NSString *varspec in varspecs) { - NSString *defaultValue = nil; - - if ([varspec length] == 0) continue; - - NSMutableDictionary *varInfo = - [NSMutableDictionary dictionaryWithCapacity:4]; - - // Check for a default (foo=bar). - NSRange range = [varspec rangeOfString:@"="]; - if (range.location != NSNotFound) { - defaultValue = - UnescapeString([varspec substringFromIndex:range.location + 1]); - varspec = [varspec substringToIndex:range.location]; - - if ([varspec length] == 0) continue; - } - - // Check for explode (foo*). - NSUInteger lenLessOne = [varspec length] - 1; - if ([explodeSet characterIsMember:[varspec characterAtIndex:lenLessOne]]) { - [varInfo setObject:[varspec substringFromIndex:lenLessOne] forKey:kExplode]; - varspec = [varspec substringToIndex:lenLessOne]; - if ([varspec length] == 0) continue; - } else { - // Check for partial (prefix/suffix) (foo:12). - range = [varspec rangeOfCharacterFromSet:partialSet]; - if (range.location != NSNotFound) { - NSString *partialMode = [varspec substringWithRange:range]; - NSString *valueStr = [varspec substringFromIndex:range.location + 1]; - // If there wasn't a value for the partial, ignore it. - if ([valueStr length] > 0) { - [varInfo setObject:partialMode forKey:kPartial]; - // TODO: Should validate valueStr is just a number... - [varInfo setObject:[NSNumber numberWithInteger:[valueStr integerValue]] - forKey:kPartialValue]; - } - varspec = [varspec substringToIndex:range.location]; - if ([varspec length] == 0) continue; - } - } - - // Spec allows percent escaping in names, so undo that. - varspec = UnescapeString(varspec); - - // Save off the cleaned up variable name. - [varInfo setObject:varspec forKey:kVariable]; - [*outVariables addObject:varInfo]; - gotAVarspec = YES; - - // Now that the variable has been cleaned up, store its default. - if (defaultValue) { - if (*outDefaultValues == nil) { - *outDefaultValues = [NSMutableDictionary dictionary]; - } - [*outDefaultValues setObject:defaultValue forKey:varspec]; - } - } - // All done. - return gotAVarspec; -} - -+ (NSString *)expandVariables:(NSArray *)variables - expressionOperator:(unichar)expressionOperator - values:(id)valueProvider - defaultValues:(NSMutableDictionary *)defaultValues { - NSString *prefix = nil; - struct ExpansionInfo expansionInfo; - expansionInfo.expressionOperator = expressionOperator; - expansionInfo.joiner = nil; - expansionInfo.allowReservedInEscape = NO; - switch (expressionOperator) { - case 0: - expansionInfo.joiner = @","; - prefix = @""; - break; - case '+': - expansionInfo.joiner = @","; - prefix = @""; - // The reserved character are safe from escaping. - expansionInfo.allowReservedInEscape = YES; - break; - case '.': - expansionInfo.joiner = @"."; - prefix = @"."; - break; - case '/': - expansionInfo.joiner = @"/"; - prefix = @"/"; - break; - case ';': - expansionInfo.joiner = @";"; - prefix = @";"; - break; - case '?': - expansionInfo.joiner = @"&"; - prefix = @"?"; - break; - default: - [NSException raise:@"GTMURITemplateUnsupported" - format:@"Unknown expression operator '%C'", expressionOperator]; - break; - } - - NSMutableArray *results = [NSMutableArray arrayWithCapacity:[variables count]]; - - for (NSDictionary *varInfo in variables) { - NSString *variable = [varInfo objectForKey:kVariable]; - - expansionInfo.explode = [varInfo objectForKey:kExplode]; - // Look up the variable value. - id rawValue = [valueProvider objectForKey:variable]; - - // If the value is an empty array or dictionary, the default is still used. - if (([rawValue isKindOfClass:[NSArray class]] - || [rawValue isKindOfClass:[NSDictionary class]]) - && [rawValue count] == 0) { - rawValue = nil; - } - - // Got nothing? Check defaults. - if (rawValue == nil) { - rawValue = [defaultValues objectForKey:variable]; - } - - // If we didn't get any value, on to the next thing. - if (!rawValue) { - continue; - } - - // Time do to the work... - NSString *result = nil; - if ([rawValue isKindOfClass:[NSString class]]) { - result = [self expandString:rawValue - variableName:variable - expansionInfo:&expansionInfo]; - } else if ([rawValue isKindOfClass:[NSNumber class]]) { - // Turn the number into a string and send it on its way. - result = [self expandString:[rawValue stringValue] - variableName:variable - expansionInfo:&expansionInfo]; - } else if ([rawValue isKindOfClass:[NSArray class]]) { - result = [self expandArray:rawValue - variableName:variable - expansionInfo:&expansionInfo]; - } else if ([rawValue isKindOfClass:[NSDictionary class]]) { - result = [self expandDictionary:rawValue - variableName:variable - expansionInfo:&expansionInfo]; - } else { - [NSException raise:@"GTMURITemplateUnsupported" - format:@"Variable returned unsupported type (%@)", - NSStringFromClass([rawValue class])]; - } - - // Did it generate anything? - if (!result) - continue; - - // Apply partial. - // Defaults should get partial applied? - // ( http://tools.ietf.org/html/draft-gregorio-uritemplate-04#section-2.5 ) - NSString *partial = [varInfo objectForKey:kPartial]; - if ([partial length] > 0) { - [NSException raise:@"GTMURITemplateUnsupported" - format:@"Unsupported partial on expansion %@", partial]; - } - - // Add the result - [results addObject:result]; - } - - // Join and add any needed prefix. - NSString *joinedResults = - [results componentsJoinedByString:expansionInfo.joiner]; - if (([prefix length] > 0) && ([joinedResults length] > 0)) { - return [prefix stringByAppendingString:joinedResults]; - } - return joinedResults; -} - -+ (NSString *)expandString:(NSString *)valueStr - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo { - NSString *escapedValue = - EscapeString(valueStr, expansionInfo->allowReservedInEscape); - switch (expansionInfo->expressionOperator) { - case ';': - case '?': - if ([valueStr length] > 0) { - return [NSString stringWithFormat:@"%@=%@", variableName, escapedValue]; - } - return variableName; - default: - return escapedValue; - } -} - -+ (NSString *)expandArray:(NSArray *)valueArray - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo { - NSMutableArray *results = [NSMutableArray arrayWithCapacity:[valueArray count]]; - // When joining variable with value, use "var.val" except for 'path' and - // 'form' style expression, use 'var=val' then. - char variableValueJoiner = '.'; - char expressionOperator = expansionInfo->expressionOperator; - if ((expressionOperator == ';') || (expressionOperator == '?')) { - variableValueJoiner = '='; - } - // Loop over the values. - for (NSString *value in valueArray) { - // Escape it. - value = EscapeString(value, expansionInfo->allowReservedInEscape); - // Should variable names be used? - if ([expansionInfo->explode isEqual:@"+"]) { - value = [NSString stringWithFormat:@"%@%c%@", - variableName, variableValueJoiner, value]; - } - [results addObject:value]; - } - if ([results count] > 0) { - // Use the default joiner unless there was no explode request, then a list - // always gets comma seperated. - NSString *joiner = expansionInfo->joiner; - if (expansionInfo->explode == nil) { - joiner = @","; - } - // Join the values. - NSString *joined = [results componentsJoinedByString:joiner]; - // 'form' style without an explode gets the variable name set to the - // joined list of values. - if ((expressionOperator == '?') && (expansionInfo->explode == nil)) { - return [NSString stringWithFormat:@"%@=%@", variableName, joined]; - } - return joined; - } - return nil; -} - -+ (NSString *)expandDictionary:(NSDictionary *)valueDict - variableName:(NSString *)variableName - expansionInfo:(struct ExpansionInfo *)expansionInfo { - NSMutableArray *results = [NSMutableArray arrayWithCapacity:[valueDict count]]; - // When joining variable with value: - // - Default to the joiner... - // - No explode, always comma... - // - For 'path' and 'form' style expression, use 'var=val'. - NSString *keyValueJoiner = expansionInfo->joiner; - char expressionOperator = expansionInfo->expressionOperator; - if (!expansionInfo->explode) { - keyValueJoiner = @","; - } else if ((expressionOperator == ';') || (expressionOperator == '?')) { - keyValueJoiner = @"="; - } - // Loop over the sorted keys. - NSArray *sortedKeys = - [[valueDict allKeys] sortedArrayUsingSelector:@selector(compare:)]; - for (NSString *key in sortedKeys) { - NSString *value = [valueDict objectForKey:key]; - // Escape them. - key = EscapeString(key, expansionInfo->allowReservedInEscape); - value = EscapeString(value, expansionInfo->allowReservedInEscape); - // Should variable names be used? - if ([expansionInfo->explode isEqual:@"+"]) { - key = [NSString stringWithFormat:@"%@.%@", variableName, key]; - } - if ((expressionOperator == '?' || expressionOperator == ';') - && ([value length] == 0)) { - [results addObject:key]; - } else { - NSString *pair = [NSString stringWithFormat:@"%@%@%@", - key, keyValueJoiner, value]; - [results addObject:pair]; - } - } - if ([results count]) { - // Use the default joiner unless there was no explode request, then a list - // always gets comma seperated. - NSString *joiner = expansionInfo->joiner; - if (!expansionInfo->explode) { - joiner = @","; - } - // Join the values. - NSString *joined = [results componentsJoinedByString:joiner]; - // 'form' style without an explode gets the variable name set to the - // joined list of values. - if ((expressionOperator == '?') && (expansionInfo->explode == nil)) { - return [NSString stringWithFormat:@"%@=%@", variableName, joined]; - } - return joined; - } - return nil; -} - -#pragma mark Public API - -+ (NSString *)expandTemplate:(NSString *)uriTemplate values:(id)valueProvider { - NSMutableString *result = - [NSMutableString stringWithCapacity:[uriTemplate length]]; - - NSScanner *scanner = [NSScanner scannerWithString:uriTemplate]; - [scanner setCharactersToBeSkipped:nil]; - - // Defaults have to live through the full evaluation, so if any are encoured - // they are reused throughout the expansion calls. - NSMutableDictionary *defaultValues = nil; - - // Pull out the expressions for processing. - while (![scanner isAtEnd]) { - NSString *skipped = nil; - // Find the next '{'. - if ([scanner scanUpToString:@"{" intoString:&skipped]) { - // Add anything before it to the result. - [result appendString:skipped]; - } - // Advance over the '{'. - [scanner scanString:@"{" intoString:nil]; - // Collect the expression. - NSString *expression = nil; - if ([scanner scanUpToString:@"}" intoString:&expression]) { - // Collect the trailing '}' on the expression. - BOOL hasTrailingBrace = [scanner scanString:@"}" intoString:nil]; - - // Parse the expression. - NSMutableArray *variables = nil; - unichar expressionOperator = 0; - if ([self parseExpression:expression - expressionOperator:&expressionOperator - variables:&variables - defaultValues:&defaultValues]) { - // Do the expansion. - NSString *substitution = [self expandVariables:variables - expressionOperator:expressionOperator - values:valueProvider - defaultValues:defaultValues]; - if (substitution) { - [result appendString:substitution]; - } - } else { - // Failed to parse, add the raw expression to the output. - if (hasTrailingBrace) { - [result appendFormat:@"{%@}", expression]; - } else { - [result appendFormat:@"{%@", expression]; - } - } - } else if (![scanner isAtEnd]) { - // Empty expression ('{}'). Copy over the opening brace and the trailing - // one will be copied by the next cycle of the loop. - [result appendString:@"{"]; - } - } - - return result; -} - -@end - -#endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 |