// // XMLDictionary.m // // Version 1.4 // // Created by Nick Lockwood on 15/11/2010. // Copyright 2010 Charcoal Design. All rights reserved. // // Get the latest version of XMLDictionary from here: // // https://github.com/nicklockwood/XMLDictionary // // 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 "AWSXMLDictionary.h" #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis" #pragma GCC diagnostic ignored "-Wdirect-ivar-access" #pragma GCC diagnostic ignored "-Wformat-non-iso" #pragma GCC diagnostic ignored "-Wgnu" #import #if !__has_feature(objc_arc) #error This class requires automatic reference counting #endif @interface AWSXMLDictionaryParser () @property (nonatomic, strong) NSMutableDictionary *root; @property (nonatomic, strong) NSMutableArray *stack; @property (nonatomic, strong) NSMutableString *text; @end @implementation AWSXMLDictionaryParser + (AWSXMLDictionaryParser *)sharedInstance { static dispatch_once_t once; static AWSXMLDictionaryParser *sharedInstance; dispatch_once(&once, ^{ sharedInstance = [[AWSXMLDictionaryParser alloc] init]; }); return sharedInstance; } - (id)init { if ((self = [super init])) { _collapseTextNodes = YES; _stripEmptyNodes = YES; _trimWhiteSpace = YES; _alwaysUseArrays = NO; _preserveComments = NO; _wrapRootNode = NO; } return self; } - (id)copyWithZone:(NSZone *)zone { AWSXMLDictionaryParser *copy = [[[self class] allocWithZone:zone] init]; copy.collapseTextNodes = _collapseTextNodes; copy.stripEmptyNodes = _stripEmptyNodes; copy.trimWhiteSpace = _trimWhiteSpace; copy.alwaysUseArrays = _alwaysUseArrays; copy.preserveComments = _preserveComments; copy.attributesMode = _attributesMode; copy.nodeNameMode = _nodeNameMode; copy.wrapRootNode = _wrapRootNode; return copy; } - (NSDictionary *)dictionaryWithParser:(NSXMLParser *)parser { [parser setDelegate:self]; [parser parse]; id result = _root; _root = nil; _stack = nil; _text = nil; return result; } - (NSDictionary *)dictionaryWithData:(NSData *)data { NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; return [self dictionaryWithParser:parser]; } - (NSDictionary *)dictionaryWithString:(NSString *)string { NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; return [self dictionaryWithData:data]; } - (NSDictionary *)dictionaryWithFile:(NSString *)path { NSData *data = [NSData dataWithContentsOfFile:path]; return [self dictionaryWithData:data]; } + (NSString *)XMLStringForNode:(id)node withNodeName:(NSString *)nodeName { if ([node isKindOfClass:[NSArray class]]) { NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[node count]]; for (id individualNode in node) { [nodes addObject:[self XMLStringForNode:individualNode withNodeName:nodeName]]; } return [nodes componentsJoinedByString:@"\n"]; } else if ([node isKindOfClass:[NSDictionary class]]) { NSDictionary *attributes = [(NSDictionary *)node awsxml_attributes]; NSMutableString *attributeString = [NSMutableString string]; for (NSString *key in [attributes allKeys]) { [attributeString appendFormat:@" %@=\"%@\"", [[key description] awsxml_XMLEncodedString], [[attributes[key] description] awsxml_XMLEncodedString]]; } NSString *innerXML = [node awsxml_innerXML]; if ([innerXML length]) { return [NSString stringWithFormat:@"<%1$@%2$@>%3$@", nodeName, attributeString, innerXML]; } else { return [NSString stringWithFormat:@"<%@%@/>", nodeName, attributeString]; } } else { return [NSString stringWithFormat:@"<%1$@>%2$@", nodeName, [[node description] awsxml_XMLEncodedString]]; } } - (void)endText { if (_trimWhiteSpace) { _text = [[_text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy]; } if ([_text length]) { NSMutableDictionary *top = [_stack lastObject]; id existing = top[AWSXMLDictionaryTextKey]; if ([existing isKindOfClass:[NSArray class]]) { [existing addObject:_text]; } else if (existing) { top[AWSXMLDictionaryTextKey] = [@[existing, _text] mutableCopy]; } else { top[AWSXMLDictionaryTextKey] = _text; } } _text = nil; } - (void)addText:(NSString *)text { if (!_text) { _text = [NSMutableString stringWithString:text]; } else { [_text appendString:text]; } } - (void)parser:(__unused NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName attributes:(NSDictionary *)attributeDict { [self endText]; NSMutableDictionary *node = [NSMutableDictionary dictionary]; switch (_nodeNameMode) { case AWSXMLDictionaryNodeNameModeRootOnly: { if (!_root) { node[AWSXMLDictionaryNodeNameKey] = elementName; } break; } case AWSXMLDictionaryNodeNameModeAlways: { node[AWSXMLDictionaryNodeNameKey] = elementName; break; } case AWSXMLDictionaryNodeNameModeNever: { break; } } if ([attributeDict count]) { switch (_attributesMode) { case AWSXMLDictionaryAttributesModePrefixed: { for (NSString *key in [attributeDict allKeys]) { node[[AWSXMLDictionaryAttributePrefix stringByAppendingString:key]] = attributeDict[key]; } break; } case AWSXMLDictionaryAttributesModeDictionary: { node[AWSXMLDictionaryAttributesKey] = attributeDict; break; } case AWSXMLDictionaryAttributesModeUnprefixed: { [node addEntriesFromDictionary:attributeDict]; break; } case AWSXMLDictionaryAttributesModeDiscard: { break; } } } if (!_root) { _root = node; _stack = [NSMutableArray arrayWithObject:node]; if (_wrapRootNode) { _root = [NSMutableDictionary dictionaryWithObject:_root forKey:elementName]; [_stack insertObject:_root atIndex:0]; } } else { NSMutableDictionary *top = [_stack lastObject]; id existing = top[elementName]; if ([existing isKindOfClass:[NSArray class]]) { [existing addObject:node]; } else if (existing) { top[elementName] = [@[existing, node] mutableCopy]; } else if (_alwaysUseArrays) { top[elementName] = [NSMutableArray arrayWithObject:node]; } else { top[elementName] = node; } [_stack addObject:node]; } } - (NSString *)nameForNode:(NSDictionary *)node inDictionary:(NSDictionary *)dict { if (node.awsxml_nodeName) { return node.awsxml_nodeName; } else { for (NSString *name in dict) { id object = dict[name]; if (object == node) { return name; } else if ([object isKindOfClass:[NSArray class]] && [object containsObject:node]) { return name; } } } return nil; } - (void)parser:(__unused NSXMLParser *)parser didEndElement:(__unused NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName { [self endText]; NSMutableDictionary *top = [_stack lastObject]; [_stack removeLastObject]; if (!top.awsxml_attributes && !top.awsxml_childNodes && !top.awsxml_comments) { NSMutableDictionary *newTop = [_stack lastObject]; NSString *nodeName = [self nameForNode:top inDictionary:newTop]; if (nodeName) { id parentNode = newTop[nodeName]; if (top.awsxml_innerText && _collapseTextNodes) { if ([parentNode isKindOfClass:[NSArray class]]) { parentNode[[parentNode count] - 1] = top.awsxml_innerText; } else { newTop[nodeName] = top.awsxml_innerText; } } else if (!top.awsxml_innerText && _stripEmptyNodes) { if ([parentNode isKindOfClass:[NSArray class]]) { [parentNode removeLastObject]; } else { [newTop removeObjectForKey:nodeName]; } } else if (!top.awsxml_innerText && !_collapseTextNodes && !_stripEmptyNodes) { top[AWSXMLDictionaryTextKey] = @""; } } } } - (void)parser:(__unused NSXMLParser *)parser foundCharacters:(NSString *)string { [self addText:string]; } - (void)parser:(__unused NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock { [self addText:[[NSString alloc] initWithData:CDATABlock encoding:NSUTF8StringEncoding]]; } - (void)parser:(__unused NSXMLParser *)parser foundComment:(NSString *)comment { if (_preserveComments) { NSMutableDictionary *top = [_stack lastObject]; NSMutableArray *comments = top[AWSXMLDictionaryCommentsKey]; if (!comments) { comments = [@[comment] mutableCopy]; top[AWSXMLDictionaryCommentsKey] = comments; } else { [comments addObject:comment]; } } } @end @implementation NSDictionary(AWSXMLDictionary) + (NSDictionary *)awsxml_dictionaryWithXMLParser:(NSXMLParser *)parser { return [[[AWSXMLDictionaryParser sharedInstance] copy] dictionaryWithParser:parser]; } + (NSDictionary *)awsxml_dictionaryWithXMLData:(NSData *)data { return [[[AWSXMLDictionaryParser sharedInstance] copy] dictionaryWithData:data]; } + (NSDictionary *)awsxml_dictionaryWithXMLString:(NSString *)string { return [[[AWSXMLDictionaryParser sharedInstance] copy] dictionaryWithString:string]; } + (NSDictionary *)awsxml_dictionaryWithXMLFile:(NSString *)path { return [[[AWSXMLDictionaryParser sharedInstance] copy] dictionaryWithFile:path]; } - (NSDictionary *)awsxml_attributes { NSDictionary *attributes = self[AWSXMLDictionaryAttributesKey]; if (attributes) { return [attributes count]? attributes: nil; } else { NSMutableDictionary *filteredDict = [NSMutableDictionary dictionaryWithDictionary:self]; [filteredDict removeObjectsForKeys:@[AWSXMLDictionaryCommentsKey, AWSXMLDictionaryTextKey, AWSXMLDictionaryNodeNameKey]]; for (NSString *key in [filteredDict allKeys]) { [filteredDict removeObjectForKey:key]; if ([key hasPrefix:AWSXMLDictionaryAttributePrefix]) { filteredDict[[key substringFromIndex:[AWSXMLDictionaryAttributePrefix length]]] = self[key]; } } return [filteredDict count]? filteredDict: nil; } return nil; } - (NSDictionary *)awsxml_childNodes { NSMutableDictionary *filteredDict = [self mutableCopy]; [filteredDict removeObjectsForKeys:@[AWSXMLDictionaryAttributesKey, AWSXMLDictionaryCommentsKey, AWSXMLDictionaryTextKey, AWSXMLDictionaryNodeNameKey]]; for (NSString *key in [filteredDict allKeys]) { if ([key hasPrefix:AWSXMLDictionaryAttributePrefix]) { [filteredDict removeObjectForKey:key]; } } return [filteredDict count]? filteredDict: nil; } - (NSArray *)awsxml_comments { return self[AWSXMLDictionaryCommentsKey]; } - (NSString *)awsxml_nodeName { return self[AWSXMLDictionaryNodeNameKey]; } - (id)awsxml_innerText { id text = self[AWSXMLDictionaryTextKey]; if ([text isKindOfClass:[NSArray class]]) { return [text componentsJoinedByString:@"\n"]; } else { return text; } } - (NSString *)awsxml_innerXML { NSMutableArray *nodes = [NSMutableArray array]; for (NSString *comment in [self awsxml_comments]) { [nodes addObject:[NSString stringWithFormat:@"", [comment awsxml_XMLEncodedString]]]; } NSDictionary *childNodes = [self awsxml_childNodes]; for (NSString *key in childNodes) { [nodes addObject:[AWSXMLDictionaryParser XMLStringForNode:childNodes[key] withNodeName:key]]; } NSString *text = [self awsxml_innerText]; if (text) { [nodes addObject:[text awsxml_XMLEncodedString]]; } return [nodes componentsJoinedByString:@"\n"]; } - (NSString *)awsxml_XMLString { if ([self count] == 1 && ![self awsxml_nodeName]) { //ignore outermost dictionary return [self awsxml_innerXML]; } else { return [AWSXMLDictionaryParser XMLStringForNode:self withNodeName:[self awsxml_nodeName] ?: @"root"]; } } - (NSArray *)awsxml_arrayValueForKeyPath:(NSString *)keyPath { id value = [self valueForKeyPath:keyPath]; if (value && ![value isKindOfClass:[NSArray class]]) { return @[value]; } return value; } - (NSString *)awsxml_stringValueForKeyPath:(NSString *)keyPath { id value = [self valueForKeyPath:keyPath]; if ([value isKindOfClass:[NSArray class]]) { value = [value count]? value[0]: nil; } if ([value isKindOfClass:[NSDictionary class]]) { return [(NSDictionary *)value awsxml_innerText]; } return value; } - (NSDictionary *)awsxml_dictionaryValueForKeyPath:(NSString *)keyPath { id value = [self valueForKeyPath:keyPath]; if ([value isKindOfClass:[NSArray class]]) { value = [value count]? value[0]: nil; } if ([value isKindOfClass:[NSString class]]) { return @{AWSXMLDictionaryTextKey: value}; } return value; } @end @implementation NSString (AWSXMLDictionary) - (NSString *)awsxml_XMLEncodedString { return [[[[[self stringByReplacingOccurrencesOfString:@"&" withString:@"&"] stringByReplacingOccurrencesOfString:@"<" withString:@"<"] stringByReplacingOccurrencesOfString:@">" withString:@">"] stringByReplacingOccurrencesOfString:@"\"" withString:@"""] stringByReplacingOccurrencesOfString:@"\'" withString:@"'"]; } @end