diff options
author | gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2009-09-03 00:01:14 +0000 |
---|---|---|
committer | gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3> | 2009-09-03 00:01:14 +0000 |
commit | 94c15346143f56b9b246e3c8dee64ec844acae88 (patch) | |
tree | 8921ed567e7a9bba0919b6ca0109fb6338fc870a /Foundation | |
parent | ac02ae31d09fcdfb4d92ea61062cf6528744c9c2 (diff) |
[Author: iwade]
Add NSData hex conversion routines.
Add a case-insensitive NSDictionary constructor.
I have a need for both pieces of functionality in an iPhone app I'm developing.
R=dmaclach,thomasvl
APPROVED=dmaclach
DELTA=521 (521 added, 0 deleted, 0 changed)
Diffstat (limited to 'Foundation')
-rw-r--r-- | Foundation/GTMNSData+Hex.h | 37 | ||||
-rw-r--r-- | Foundation/GTMNSData+Hex.m | 102 | ||||
-rw-r--r-- | Foundation/GTMNSData+HexTest.m | 58 | ||||
-rw-r--r-- | Foundation/GTMNSDictionary+CaseInsensitive.h | 42 | ||||
-rw-r--r-- | Foundation/GTMNSDictionary+CaseInsensitive.m | 116 | ||||
-rw-r--r-- | Foundation/GTMNSDictionary+CaseInsensitiveTest.m | 119 |
6 files changed, 474 insertions, 0 deletions
diff --git a/Foundation/GTMNSData+Hex.h b/Foundation/GTMNSData+Hex.h new file mode 100644 index 0000000..b1e668d --- /dev/null +++ b/Foundation/GTMNSData+Hex.h @@ -0,0 +1,37 @@ +// +// GTMNSData+Hex.h +// +// Copyright 2009 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 <Foundation/Foundation.h> + +/// Helpers for dealing w/ hex encoded strings. +@interface NSData (GTMHexAdditions) + +/// Return an autoreleased NSData w/ the result of decoding |hexString| to +/// binary data. +/// +/// Will return |nil| if |hexString| contains any non-hex characters (i.e. +/// 0-9, a-f, A-F) or if the length of |hexString| is not cleanly divisible by +/// two. +/// Leading 0x prefix is not supported and will result in a |nil| return value. ++ (NSData *)gtm_dataWithHexString:(NSString *)hexString; + +/// Return an autoreleased NSString w/ the result of encoding the NSData bytes +/// as hex. No leading 0x prefix is included. +- (NSString *)gtm_hexString; + +@end diff --git a/Foundation/GTMNSData+Hex.m b/Foundation/GTMNSData+Hex.m new file mode 100644 index 0000000..c7e740d --- /dev/null +++ b/Foundation/GTMNSData+Hex.m @@ -0,0 +1,102 @@ +// +// GTMNSData+Hex.m +// +// Copyright 2009 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 "GTMNSData+Hex.h" +#import "GTMDefines.h" + +@implementation NSData (GTMHexAdditions) + ++ (NSData *)gtm_dataWithHexString:(NSString *)hexString { + NSData *hexData = [hexString dataUsingEncoding:NSASCIIStringEncoding]; + const char *hexBuf = [hexData bytes]; + NSUInteger hexLen = [hexData length]; + + // This indicates an error converting to ASCII. + if (hexString && !hexData) { + return nil; + } + + if ((hexLen % 2) != 0) { + return nil; + } + + NSMutableData *binaryData = [NSMutableData dataWithLength:(hexLen / 2)]; + unsigned char *binaryPtr = [binaryData mutableBytes]; + unsigned char value = 0; + for (NSUInteger i = 0; i < hexLen; i++) { + char c = hexBuf[i]; + + if (!isxdigit(c)) { + return nil; + } + + if (isdigit(c)) { + value += c - '0'; + } else if (islower(c)) { + value += 10 + c - 'a'; + } else { + value += 10 + c - 'A'; + } + + if (i & 1) { + *binaryPtr++ = value; + value = 0; + } else { + value <<= 4; + } + } + + return [NSData dataWithData:binaryData]; +} + +- (NSString *)gtm_hexString { + static const char kHexTable[] = + "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + + const unsigned char *binaryPtr = [self bytes]; + NSUInteger binaryLen = [self length]; + + NSMutableData *hexData = [NSMutableData dataWithLength:(2 * binaryLen)]; + char *hexPtr = [hexData mutableBytes]; + + for (NSUInteger i = 0; i < binaryLen; i++) { + *hexPtr++ = kHexTable[(*binaryPtr)*2]; + *hexPtr++ = kHexTable[(*binaryPtr)*2 + 1]; + ++binaryPtr; + } + + return [[[NSString alloc] initWithData:hexData + encoding:NSASCIIStringEncoding] autorelease]; +} + +@end diff --git a/Foundation/GTMNSData+HexTest.m b/Foundation/GTMNSData+HexTest.m new file mode 100644 index 0000000..6bc1608 --- /dev/null +++ b/Foundation/GTMNSData+HexTest.m @@ -0,0 +1,58 @@ +// +// GTMNSData+HexTest.m +// +// Copyright 2009 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 "GTMSenTestCase.h" +#import "GTMNSData+Hex.h" + +@interface GTMNSData_HexTest : GTMTestCase +@end + +@implementation GTMNSData_HexTest + +- (void)testNSDataHexAdditions { + NSString *testString = @"1c2f0032f40123456789abcdef"; + char testBytes[] = { 0x1c, 0x2f, 0x00, 0x32, 0xf4, 0x01, 0x23, + 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef }; + NSData *testData = [NSData dataWithBytes:testBytes length:sizeof(testBytes)]; + + STAssertTrue([[testData gtm_hexString] isEqual:testString], + @"gtm_hexString doesn't encode as expected"); + + STAssertEqualStrings([[NSData data] gtm_hexString], @"", + @"gtm_hexString empty data should return empty string"); + + STAssertTrue([[NSData gtm_dataWithHexString:testString] isEqual:testData], + @"gtm_dataWithHexString: doesn't decode as expected"); + + STAssertNil([NSData gtm_dataWithHexString:@"1c2f003"], + @"gtm_dataWithHexString: parsed hex from an odd size string"); + + STAssertNil([NSData gtm_dataWithHexString:@"1c2f00ft"], + @"gtm_dataWithHexString: parsed hex from a non hex string"); + + STAssertNil([NSData gtm_dataWithHexString:@"abcdéf"], + @"gtm_dataWithHexString: parsed a non-ASCII character"); + + STAssertNotNil([NSData gtm_dataWithHexString:@""], + @"gtm_dataWithHexString: empty input resulted in nil output"); + + STAssertNotNil([NSData gtm_dataWithHexString:nil], + @"gtm_dataWithHexString: nil input resulted in nil output"); +} + +@end diff --git a/Foundation/GTMNSDictionary+CaseInsensitive.h b/Foundation/GTMNSDictionary+CaseInsensitive.h new file mode 100644 index 0000000..a31e81c --- /dev/null +++ b/Foundation/GTMNSDictionary+CaseInsensitive.h @@ -0,0 +1,42 @@ +// +// GTMNSDictionary+CaseInsensitive.h +// +// Copyright 2009 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 <Foundation/Foundation.h> + +/// Utility for building case-insensitive NSDictionary objects. +@interface NSDictionary (GTMNSDictionaryCaseInsensitiveAdditions) + +/// Initializes an NSDictionary with a case-insensitive comparison function +/// for NSString keys, while non-NSString keys are treated normally. +/// +/// The case for NSString keys is preserved, though duplicate keys (when +/// compared in a case-insensitive fashion) have one of their values dropped +/// arbitrarily. +/// +/// An example of use with HTTP headers in an NSHTTPURLResponse object: +/// +/// NSDictionary *headers = +/// [NSDictionary gtm_dictionaryWithDictionaryCaseInsensitive: +/// [response allHeaderFields]]; +/// NSString *contentType = [headers objectForKey:@"Content-Type"]; +- (id)gtm_initWithDictionaryCaseInsensitive:(NSDictionary *)dictionary; + +/// Returns a newly created and autoreleased NSDictionary object as above. ++ (id)gtm_dictionaryWithDictionaryCaseInsensitive:(NSDictionary *)dictionary; + +@end diff --git a/Foundation/GTMNSDictionary+CaseInsensitive.m b/Foundation/GTMNSDictionary+CaseInsensitive.m new file mode 100644 index 0000000..96494c2 --- /dev/null +++ b/Foundation/GTMNSDictionary+CaseInsensitive.m @@ -0,0 +1,116 @@ +// +// GTMNSDictionary+CaseInsensitive.m +// +// Copyright 2009 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 "GTMNSDictionary+CaseInsensitive.h" +#import "GTMDefines.h" +#import <CoreFoundation/CoreFoundation.h> + +@interface NSMutableDictionary (GTMNSMutableDictionaryCaseInsensitiveAdditions) + +// Returns a mutable equivalent to GTMNSDictionaryCaseInsensitiveAdditions. +- (id)gtm_initWithDictionaryCaseInsensitive:(NSDictionary *)dictionary; + +@end + +static Boolean CaseInsensitiveEqualCallback(const void *a, const void *b) { + id idA = (id)a; + id idB = (id)b; + Boolean ret = FALSE; + if ([idA isKindOfClass:[NSString class]] && + [idB isKindOfClass:[NSString class]]) { + ret = ([idA compare:idB options:NSCaseInsensitiveSearch|NSLiteralSearch] + == NSOrderedSame); + } else { + ret = [idA isEqual:idB]; + } + return ret; +} + +static CFHashCode CaseInsensitiveHashCallback(const void *value) { + id idValue = (id)value; + CFHashCode ret = 0; + if ([idValue isKindOfClass:[NSString class]]) { + ret = [[idValue lowercaseString] hash]; + } else { + ret = [idValue hash]; + } + return ret; +} + +@implementation NSDictionary (GTMNSDictionaryCaseInsensitiveAdditions) + +- (id)gtm_initWithDictionaryCaseInsensitive:(NSDictionary *)dictionary { + [self release]; + self = nil; + + CFIndex count = 0; + void *keys = NULL; + void *values = NULL; + + if (dictionary) { + count = CFDictionaryGetCount((CFDictionaryRef)dictionary); + + if (count) { + keys = malloc(count * sizeof(void *)); + values = malloc(count * sizeof(void *)); + if (!keys || !values) { + free(keys); + free(values); + return self; + } + + CFDictionaryGetKeysAndValues((CFDictionaryRef)dictionary, keys, values); + } + } + + CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks; + _GTMDevAssert(keyCallbacks.version == 0, + @"CFDictionaryKeyCallBacks structure updated"); + keyCallbacks.equal = CaseInsensitiveEqualCallback; + keyCallbacks.hash = CaseInsensitiveHashCallback; + + self = (id)CFDictionaryCreate(kCFAllocatorDefault, + keys, values, count, &keyCallbacks, + &kCFTypeDictionaryValueCallBacks); + + free(keys); + free(values); + + return self; +} + ++ (id)gtm_dictionaryWithDictionaryCaseInsensitive:(NSDictionary *)dictionary { + return [[[self alloc] + gtm_initWithDictionaryCaseInsensitive:dictionary] autorelease]; +} + +@end + +@implementation NSMutableDictionary (GTMNSMutableDictionaryCaseInsensitiveAdditions) + +- (id)gtm_initWithDictionaryCaseInsensitive:(NSDictionary *)dictionary { + if ((self = [super gtm_initWithDictionaryCaseInsensitive:dictionary])) { + id copy = (id)CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, + (CFDictionaryRef)self); + [self release]; + self = copy; + } + return self; +} + +@end diff --git a/Foundation/GTMNSDictionary+CaseInsensitiveTest.m b/Foundation/GTMNSDictionary+CaseInsensitiveTest.m new file mode 100644 index 0000000..67584ff --- /dev/null +++ b/Foundation/GTMNSDictionary+CaseInsensitiveTest.m @@ -0,0 +1,119 @@ +// +// GTMNSDictionary+CaseInsensitiveTest.m +// +// Copyright 2009 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 "GTMSenTestCase.h" +#import "GTMNSDictionary+CaseInsensitive.h" + +@interface GTMNSDictionary_CaseInsensitiveTest : GTMTestCase +@end + +@implementation GTMNSDictionary_CaseInsensitiveTest + +- (void)testNSDictionaryCaseInsensitiveAdditions { + NSURL *objKey = [NSURL URLWithString:@"http://WWW.Google.COM/"]; + NSURL *lcObjKey = [NSURL URLWithString:[[objKey absoluteString] + lowercaseString]]; + + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: + @"value", @"key", + @"value", @"KEY", + @"bar", @"FOO", + @"yes", objKey, + nil]; + + NSDictionary *ciDict = + [NSDictionary gtm_dictionaryWithDictionaryCaseInsensitive:dict]; + + STAssertNotNil(ciDict, @"gtm_dictionaryWithDictionaryCaseInsensitive failed"); + + STAssertTrue([ciDict count] == 3, + @"wrong count, multiple 'key' entries should be folded."); + + STAssertEqualStrings([ciDict objectForKey:@"foo"], @"bar", + @"case insensitive key lookup failed"); + + STAssertEqualStrings([ciDict objectForKey:@"kEy"], @"value", + @"case insensitive key lookup failed"); + + STAssertNotNil([ciDict objectForKey:objKey], + @"exact matches on non-NSString objects should still work."); + + STAssertNil([ciDict objectForKey:lcObjKey], + @"only NSString and subclasses are case-insensitive."); + + STAssertNotNil([NSDictionary gtm_dictionaryWithDictionaryCaseInsensitive: + [NSDictionary dictionary]], + @"empty dictionary should not return nil"); + + STAssertNotNil([NSDictionary gtm_dictionaryWithDictionaryCaseInsensitive: + nil], + @"nil dictionary should return empty dictionary"); + + STAssertNotNil([[[NSDictionary alloc] gtm_initWithDictionaryCaseInsensitive: + nil] autorelease], + @"nil dictionary should return empty dictionary"); +} + +- (void)testNSMutableDictionaryCaseInsensitiveAdditions { + NSURL *objKey = [NSURL URLWithString:@"http://WWW.Google.COM/"]; + NSURL *lcObjKey = [NSURL URLWithString:[[objKey absoluteString] + lowercaseString]]; + + NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys: + @"value", @"key", + @"value", @"KEY", + @"bar", @"FOO", + @"yes", objKey, + nil]; + + NSMutableDictionary *ciDict = + [NSMutableDictionary gtm_dictionaryWithDictionaryCaseInsensitive:dict]; + + STAssertNotNil(ciDict, @"gtm_dictionaryWithDictionaryCaseInsensitive failed"); + + STAssertTrue([ciDict count] == 3, + @"wrong count, multiple 'key' entries should be folded."); + + STAssertEqualStrings([ciDict objectForKey:@"foo"], @"bar", + @"case insensitive key lookup failed"); + + STAssertEqualStrings([ciDict objectForKey:@"kEy"], @"value", + @"case insensitive key lookup failed"); + + STAssertNotNil([ciDict objectForKey:objKey], + @"exact matches on non-NSString objects should still work."); + + STAssertNil([ciDict objectForKey:lcObjKey], + @"only NSString and subclasses are case-insensitive."); + + NSObject *obj = [[[NSObject alloc] init] autorelease]; + [ciDict setObject:obj forKey:@"kEy"]; + STAssertEquals([ciDict objectForKey:@"key"], obj, + @"mutable dictionary value not overwritten"); + + STAssertNotNil( + [NSMutableDictionary gtm_dictionaryWithDictionaryCaseInsensitive: + [NSDictionary dictionary]], + @"empty dictionary should not return nil"); + + STAssertNotNil( + [NSMutableDictionary gtm_dictionaryWithDictionaryCaseInsensitive:nil], + @"nil dictionary should return empty dictionary"); +} + +@end |