aboutsummaryrefslogtreecommitdiff
path: root/Foundation/GTMURITemplate.m
diff options
context:
space:
mode:
Diffstat (limited to 'Foundation/GTMURITemplate.m')
-rw-r--r--Foundation/GTMURITemplate.m521
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