123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749 |
- //
- // OrderedDictionary.m
- //
- // Version 1.4
- //
- // Created by Nick Lockwood on 21/09/2010.
- // Copyright 2010 Charcoal Design
- //
- // Distributed under the permissive zlib license
- // Get the latest version from here:
- //
- // https://github.com/nicklockwood/OrderedDictionary
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- //
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- //
- // 3. This notice may not be removed or altered from any source distribution.
- //
- #import "OrderedDictionary.h"
- #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
- #pragma GCC diagnostic ignored "-Wnullable-to-nonnull-conversion"
- #pragma GCC diagnostic ignored "-Wdirect-ivar-access"
- #pragma GCC diagnostic ignored "-Wfloat-equal"
- #pragma GCC diagnostic ignored "-Wgnu"
- #import <Availability.h>
- #if !__has_feature(objc_arc)
- #error This class requires automatic reference counting
- #endif
- @implementation NSThread (XMLPlist)
- - (NSDateFormatter *)XMLPlistDateFormatter
- {
- static NSString *const key = @"XMLPlistDateFormatter";
- NSDateFormatter *formatter = self.threadDictionary[key];
- if (!formatter)
- {
- formatter = [[NSDateFormatter alloc] init];
- formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
- formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
- formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'Z'";
- self.threadDictionary[key] = formatter;
- }
- return formatter;
- }
- @end
- @interface NSObject (XMLPlistWriting)
- - (NSString *)XMLPlistStringWithIndent:(NSString *)indent;
- @end
- @interface OrderedDictionaryXMLPlistParser : NSObject<NSXMLParserDelegate>
- @property (nonatomic, readonly) OrderedDictionary *root;
- - (instancetype)initWithData:(NSData *)data root:(OrderedDictionary *)root;
- @end
- @implementation OrderedDictionary
- {
- @protected
- NSArray *_values;
- NSOrderedSet *_keys;
- }
- + (instancetype)dictionaryWithContentsOfFile:(NSString *)path
- {
- NSData *data = [NSData dataWithContentsOfFile:path];
- __autoreleasing id dictionary = [[self alloc] initWithPlistData:data];
- return dictionary;
- }
- + (instancetype)dictionaryWithContentsOfURL:(NSURL *)url
- {
- NSData *data = [NSData dataWithContentsOfURL:url];
- __autoreleasing id dictionary = [[self alloc] initWithPlistData:data];
- return dictionary;
- }
- - (instancetype)initWithContentsOfFile:(NSString *)path
- {
- NSData *data = [NSData dataWithContentsOfFile:path];
- return [self initWithPlistData:data];
- return nil;
- }
- - (instancetype)initWithContentsOfURL:(NSURL *)url
- {
- NSData *data = [NSData dataWithContentsOfURL:url];
- return [self initWithPlistData:data];
- return nil;
- }
- - (instancetype)initWithObjects:(NSArray *)objects forKeys:(NSArray *)keys
- {
- if ((self = [super init]))
- {
- _values = [objects copy];
- _keys = [NSOrderedSet orderedSetWithArray:keys];
-
- NSParameterAssert([_keys count] == [_values count]);
- }
- return self;
- }
- - (instancetype)initWithPlistData:(NSData *)data
- {
- if (data)
- {
- const void *bytes = data.bytes;
- char header[7];
- memcpy(header, &bytes, 6);
- header[6] = '\0';
-
- NSAssert(strcmp(header, "bplist") != 0, @"OrderedDictionary does not support loading binary plist files. Use an XML plist file instead. Xcode automatically converts XML plist files to binary files in built apps - see documentation for tips on how to disable this.");
- return [[OrderedDictionaryXMLPlistParser alloc] initWithData:data root:[self init]].root;
- }
- return nil;
- }
- - (instancetype)initWithObjects:(const __unsafe_unretained id [])objects forKeys:(const __unsafe_unretained id <NSCopying> [])keys count:(NSUInteger)count
- {
- if ((self = [super init]))
- {
- _values = [[NSArray alloc] initWithObjects:objects count:count];
- _keys = [[NSOrderedSet alloc] initWithObjects:keys count:count];
-
- NSParameterAssert([_values count] == count);
- NSParameterAssert([_keys count] == count);
- }
- return self;
- }
- - (Class)classForCoder
- {
- return [self class];
- }
- - (instancetype)init
- {
- if ((self = [super init]) && [self class] == [OrderedDictionary class])
- {
- static OrderedDictionary *singleton;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- singleton = self;
- self->_values = @[];
- self->_keys = [[NSOrderedSet alloc] init];
- });
- return singleton;
- }
- return self;
- }
- - (instancetype)initWithCoder:(NSCoder *)decoder
- {
- if ((self = [super init]))
- {
- _values = [decoder decodeObjectOfClass:[NSArray class] forKey:@"values"];
- _keys = [decoder decodeObjectOfClass:[NSOrderedSet class] forKey:@"keys"];
- }
- return self;
- }
- - (void)encodeWithCoder:(NSCoder *)coder
- {
- [coder encodeObject:_values forKey:@"values"];
- [coder encodeObject:_keys forKey:@"keys"];
- }
- + (BOOL)supportsSecureCoding
- {
- return YES;
- }
- - (instancetype)copyWithZone:(__unused NSZone *)zone
- {
- return self;
- }
- - (instancetype)mutableCopyWithZone:(NSZone *)zone
- {
- return [[MutableOrderedDictionary allocWithZone:zone] initWithDictionary:self];
- }
- - (NSArray *)allKeys
- {
- return _keys.array;
- }
- - (NSArray *)allValues
- {
- return [_values copy];
- }
- - (NSUInteger)count
- {
- return _keys.count;
- }
- - (NSUInteger)indexOfKey:(id)key
- {
- return [_keys indexOfObject:key];
- }
- - (id)objectForKey:(id)key
- {
- NSUInteger index = [_keys indexOfObject:key];
- if (index != NSNotFound)
- {
- return _values[index];
- }
- return nil;
-
- }
- - (NSEnumerator *)keyEnumerator
- {
- return [_keys objectEnumerator];
- }
- - (NSEnumerator *)reverseKeyEnumerator
- {
- return [_keys reverseObjectEnumerator];
- }
- - (NSEnumerator *)objectEnumerator
- {
- return [_values objectEnumerator];
- }
- - (NSEnumerator *)reverseObjectEnumerator
- {
- return [_values reverseObjectEnumerator];
- }
- - (void)enumerateKeysAndObjectsWithIndexUsingBlock:(void (^)(id key, id obj, NSUInteger idx, BOOL *stop))block
- {
- [_keys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) {
- block(key, self->_values[idx], idx, stop);
- }];
- }
- - (id)keyAtIndex:(NSUInteger)index
- {
- return _keys[index];
- }
- - (id)objectAtIndex:(NSUInteger)index
- {
- return _values[index];
- }
- - (id)objectAtIndexedSubscript:(NSUInteger)index
- {
- return _values[index];
- }
- - (NSString *)descriptionWithLocale:(nullable id)locale indent:(NSUInteger)level
- {
- NSMutableString *indent = [NSMutableString string];
- for (int i = 0; i < level; i++)
- {
- [indent appendString:@" "];
- }
- NSMutableString *string = [NSMutableString string];
- [string appendString:indent];
- [string appendString:@"{\n"];
- [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, __unused BOOL *stop) {
- NSString *description;
- if ([obj respondsToSelector:@selector(descriptionWithLocale:indent:)]) {
- description = [obj descriptionWithLocale:locale indent:level + 1];
- } else if ([obj respondsToSelector:@selector(descriptionWithLocale:)]) {
- description = [obj descriptionWithLocale:locale];
- } else {
- description = [obj description];
- }
- [string appendString:indent];
- [string appendFormat:@" %@ = %@;\n", key, description];
- }];
- [string appendString:indent];
- [string appendString:@"}"];
- return string;
- }
- - (NSString *)XMLPlistString
- {
- return [NSString stringWithFormat:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
- "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
- "<plist version=\"1.0\">\n%@\n</plist>\n",
- [self XMLPlistStringWithIndent:@""]];
- }
- - (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile
- {
- return [[self XMLPlistString] writeToFile:path atomically:useAuxiliaryFile encoding:NSUTF8StringEncoding error:NULL];
- }
- - (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically
- {
- return [[self XMLPlistString] writeToURL:url atomically:atomically encoding:NSUTF8StringEncoding error:NULL];
- }
- @end
- @implementation MutableOrderedDictionary
- #define _mutableValues ((NSMutableArray *)_values)
- #define _mutableKeys ((NSMutableOrderedSet *)_keys)
- + (instancetype)dictionaryWithCapacity:(NSUInteger)count
- {
- return [(MutableOrderedDictionary *)[self alloc] initWithCapacity:count];
- }
- - (instancetype)initWithObjects:(const __unsafe_unretained id [])objects forKeys:(const __unsafe_unretained id <NSCopying> [])keys count:(NSUInteger)count
- {
- if ((self = [super init]))
- {
- _values = [[NSMutableArray alloc] initWithObjects:objects count:count];
- _keys = [[NSMutableOrderedSet alloc] initWithObjects:keys count:count];
- }
- return self;
- }
- - (instancetype)initWithCapacity:(NSUInteger)capacity
- {
- if ((self = [super init]))
- {
- _values = [[NSMutableArray alloc] initWithCapacity:capacity];
- _keys = [[NSMutableOrderedSet alloc] initWithCapacity:capacity];
- }
- return self;
- }
- - (instancetype)init
- {
- return [self initWithCapacity:0];
- }
- - (instancetype)initWithCoder:(NSCoder *)decoder
- {
- if ((self = [super init]))
- {
- _values = [decoder decodeObjectOfClass:[NSMutableArray class] forKey:@"values"];
- _keys = [decoder decodeObjectOfClass:[NSMutableOrderedSet class] forKey:@"keys"];
- }
- return self;
- }
- - (id)copyWithZone:(NSZone *)zone
- {
- return [[OrderedDictionary allocWithZone:zone] initWithDictionary:self];
- }
- - (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary
- {
- [otherDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, __unused BOOL *stop) {
- [self setObject:obj forKey:key];
- }];
- }
- - (void)insertObject:(id)object forKey:(id)key atIndex:(NSUInteger)index
- {
- [self removeObjectForKey:key];
- [_mutableKeys insertObject:key atIndex:index];
- [_mutableValues insertObject:object atIndex:index];
- }
- - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)object
- {
- _mutableValues[index] = object;
- }
- - (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
- {
- _mutableValues[index] = object;
- }
- - (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2
- {
- [_mutableKeys exchangeObjectAtIndex:idx1 withObjectAtIndex:idx2];
- [_mutableValues exchangeObjectAtIndex:idx1 withObjectAtIndex:idx2];
- }
- - (void)removeAllObjects
- {
- [_mutableKeys removeAllObjects];
- [_mutableValues removeAllObjects];
- }
- - (void)removeObjectAtIndex:(NSUInteger)index
- {
- [_mutableKeys removeObjectAtIndex:index];
- [_mutableValues removeObjectAtIndex:index];
- }
- - (void)removeObjectsAtIndexes:(NSIndexSet *)indexes
- {
- [_mutableKeys removeObjectsAtIndexes:indexes];
- [_mutableValues removeObjectsAtIndexes:indexes];
- }
- - (void)removeObjectForKey:(id)key
- {
- NSUInteger index = [self->_keys indexOfObject:key];
- if (index != NSNotFound)
- {
- [self removeObjectAtIndex:index];
- }
- }
- - (void)removeObjectsForKeys:(NSArray *)keyArray
- {
- for (id key in [keyArray copy])
- {
- [self removeObjectForKey:key];
- }
- }
- - (void)setDictionary:(NSDictionary *)otherDictionary
- {
- [_mutableKeys removeAllObjects];
- [_mutableKeys addObjectsFromArray:otherDictionary.allKeys];
- [_mutableValues setArray:otherDictionary.allValues];
- }
- - (void)setObject:(id)object forKey:(id)key
- {
- NSUInteger index = [_keys indexOfObject:key];
- if (index != NSNotFound)
- {
- _mutableValues[index] = object;
- }
- else
- {
- [_mutableKeys addObject:key];
- [_mutableValues addObject:object];
- }
- }
- - (void)setValue:(id)value forKey:(NSString *)key
- {
- if (value)
- {
- [self setObject:value forKey:key];
- }
- else
- {
- [self removeObjectForKey:key];
- }
- }
- - (void)setObject:(id)object forKeyedSubscript:(id)key
- {
- [self setObject:object forKey:key];
- }
- @end
- @implementation NSObject (XMLPlistWriting)
- - (NSString *)XMLPlistStringWithIndent:(__unused NSString *)indent
- {
- NSLog(@"%@ is not a supported property list type.", self.classForCoder);
- [self doesNotRecognizeSelector:_cmd];
- return nil;
- }
- @end
- @implementation NSString (XMLPlistWriting)
- - (NSString *)XMLEscapedString
- {
- return [[[self stringByReplacingOccurrencesOfString:@"&" withString:@"&"]
- stringByReplacingOccurrencesOfString:@"<" withString:@"<"]
- stringByReplacingOccurrencesOfString:@">" withString:@">"];
- }
- - (NSString *)XMLPlistStringWithIndent:(__unused NSString *)indent
- {
- return [NSString stringWithFormat:@"<string>%@</string>", [self XMLEscapedString]];
- }
- @end
- @implementation NSNumber (XMLPlistWriting)
- - (NSString *)XMLPlistStringWithIndent:(__unused NSString *)indent
- {
- if ((__bridge CFBooleanRef)self == kCFBooleanTrue)
- {
- return @"<true/>";
- }
- else if ((__bridge CFBooleanRef)self == kCFBooleanFalse)
- {
- return @"<false/>";
- }
- else if (self.doubleValue != (double)self.integerValue)
- {
- return [NSString stringWithFormat:@"<real>%@</real>", self];
- }
- else
- {
- return [NSString stringWithFormat:@"<integer>%@</integer>", self];
- }
- }
- @end
- @implementation NSDate (XMLPlistWriting)
- - (NSString *)XMLPlistStringWithIndent:(__unused NSString *)indent
- {
- NSDateFormatter *formatter = [[NSThread currentThread] XMLPlistDateFormatter];
- return [NSString stringWithFormat:@"<date>%@</date>", [formatter stringFromDate:self]];
- }
- @end
- @implementation NSData (XMLPlistWriting)
- - (NSString *)XMLPlistStringWithIndent:(NSString *)indent
- {
- NSString *base64 = [self base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
- return [NSString stringWithFormat:@"<data>\n%@%@\n%@</data>", indent, base64, indent];
- }
- @end
- @implementation NSArray (XMLPlistWriting)
- - (NSString *)XMLPlistStringWithIndent:(NSString *)indent
- {
- NSMutableString *xml = [NSMutableString string];
- [xml appendString:@"<array>\n"];
- NSString *subindent = [indent stringByAppendingString:@"\t"];
- for (id value in self)
- {
- [xml appendString:subindent];
- [xml appendString:[value XMLPlistStringWithIndent:subindent]];
- [xml appendString:@"\n"];
- }
- [xml appendString:indent];
- [xml appendString:@"</array>"];
- return xml;
- }
- @end
- @implementation NSDictionary (XMLPlistWriting)
- - (NSString *)XMLPlistStringWithIndent:(NSString *)indent
- {
- NSMutableString *xml = [NSMutableString string];
- [xml appendString:@"<dict>\n"];
- NSString *subindent = [indent stringByAppendingString:@"\t"];
- [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, __unused BOOL *stop) {
- [xml appendString:subindent];
- [xml appendFormat:@"<key>%@</key>\n", [[key description] XMLEscapedString]];
- [xml appendString:subindent];
- [xml appendString:[value XMLPlistStringWithIndent:subindent]];
- [xml appendString:@"\n"];
- }];
- [xml appendString:indent];
- [xml appendString:@"</dict>"];
- return xml;
- }
- @end
- @implementation OrderedDictionaryXMLPlistParser
- {
- NSDateFormatter *_formatter;
- NSMutableArray *_valueStack;
- NSMutableArray *_keyStack;
- NSString *_text;
- BOOL _failed;
- }
- - (instancetype)initWithData:(NSData *)data root:(OrderedDictionary *)root
- {
- if ((self = [super init]))
- {
- _root = [root isKindOfClass:[MutableOrderedDictionary class]] ? root : nil;
- _keyStack = [NSMutableArray array];
- NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
- parser.delegate = self;
- [parser parse];
- }
- return self;
- }
- - (void)failWithError:(NSString *)error
- {
- NSLog(@"OrderedDictionary XML parsing error: %@", error);
- _failed = YES;
- _root = nil;
- }
- - (void)parser:(NSXMLParser *)parser didStartElement:(nonnull NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName attributes:(__unused NSDictionary<NSString *, NSString *> *)attributeDict
- {
- if ([elementName isEqualToString:@"dict"])
- {
- if (_valueStack == nil)
- {
- _valueStack = [NSMutableArray arrayWithObject:_root ?: [MutableOrderedDictionary dictionary]];
- }
- else
- {
- [_valueStack addObject:[MutableOrderedDictionary dictionary]];
- }
- }
- else if (![elementName isEqualToString:@"plist"] && _valueStack == nil)
- {
- [self failWithError:@"Root element was not a dictionary."];
- [parser abortParsing];
- return;
- }
- else if ([elementName isEqualToString:@"array"])
- {
- [_valueStack addObject:[NSMutableArray array]];
- }
- }
- - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName
- {
- id value = [_text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
- _text = nil;
-
- if ([elementName isEqualToString:@"key"])
- {
- [_keyStack addObject:value ?: @""];
- return;
- }
-
- if ([elementName isEqualToString:@"string"])
- {
- // Do nothing
- }
- else if ([elementName isEqualToString:@"real"])
- {
- value = @([value doubleValue]);
- }
- else if ([elementName isEqualToString:@"integer"])
- {
- value = @([value integerValue]);
- }
- else if ([elementName isEqualToString:@"date"])
- {
- if (!_formatter)
- {
- _formatter = [[NSThread currentThread] XMLPlistDateFormatter];
- }
- NSString *dateString = value;
- if (!(value = [_formatter dateFromString:dateString]))
- {
- [self failWithError:[NSString stringWithFormat:@"Unabled to parse date string: %@", dateString]];
- [parser abortParsing];
- return;
- }
- }
- else if ([elementName isEqualToString:@"data"])
- {
- NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
- if (!(value = [[NSData alloc] initWithBase64EncodedData:data options:NSDataBase64DecodingIgnoreUnknownCharacters]))
- {
- [self failWithError:@"Unabled to parse data."];
- [parser abortParsing];
- return;
- }
- }
- else if ([elementName isEqualToString:@"true"])
- {
- value = @YES;
- }
- else if ([elementName isEqualToString:@"false"])
- {
- value = @NO;
- }
- else if ([elementName isEqualToString:@"dict"] || [elementName isEqualToString:@"array"])
- {
- value = [_valueStack.lastObject copy];
- [_valueStack removeLastObject];
- }
- else if ([elementName isEqualToString:@"plist"])
- {
- return;
- }
-
- id top = _valueStack.lastObject;
- if ([top isKindOfClass:[MutableOrderedDictionary class]])
- {
- NSString *key = _keyStack.lastObject;
- ((MutableOrderedDictionary *)top)[key] = value;
- [_keyStack removeLastObject];
- }
- else if ([top isKindOfClass:[NSArray class]])
- {
- [(NSMutableArray *)top addObject:value];
- }
- else if (_root == nil && !_failed)
- {
- _root = [value copy];
- }
- }
- - (void)parser:(__unused NSXMLParser *)parser foundCharacters:(NSString *)string
- {
- _text = [_text ?: @"" stringByAppendingString:string];
- }
- @end
|