├── README.md ├── XMLReader.h └── XMLReader.m /README.md: -------------------------------------------------------------------------------- 1 | # XMLReader 2 | 3 | This project comes from a component developed by Troy Brant and published on his website : http://troybrant.net/blog/2010/09/simple-xml-to-nsdictionary-converter/ 4 | 5 | I'm open sourcing some of the updates I've made on it. 6 | 7 | 8 | ## Usage 9 | 10 | NSData *data = ...; // some data that can be received from remote service 11 | NSError *error = nil; 12 | NSDictionary *dict = [XMLReader dictionaryForXMLData:data 13 | options:XMLReaderOptionsProcessNamespaces 14 | error:&error]; 15 | 16 | 17 | ## Requirements 18 | 19 | Xcode 4.4 and above because project use the "auto-synthesized property" feature. 20 | 21 | 22 | ## FAQ 23 | 24 | #### Sometimes I get an `NSDictionary` while I must get an `NSArray`, why ? 25 | 26 | In the algorithm of the `XMLReader`, when the parser found a new tag it automatically creates an `NSDictionary`, if it found another occurrence of the same tag at the same level in the XML tree it creates another dictionary and put both dictionaries inside an `NSArray`. 27 | 28 | The consequence is: if you have a list that contains only one item, you will get an `NSDictionary` as result and not an `NSArray`. 29 | The only workaround is to check the class of the object contained for in the dictionary using `isKindOfClass:`. See sample code below : 30 | 31 | NSData *data = ...; 32 | NSError *error = nil; 33 | NSDictionary *dict = [XMLReader dictionaryForXMLData:data error:&error]; 34 | 35 | NSArray *list = [dict objectForKey:@"list"]; 36 | if (![list isKindOfClass:[NSArray class]]) 37 | { 38 | // if 'list' isn't an array, we create a new array containing our object 39 | list = [NSArray arrayWithObject:list]; 40 | } 41 | 42 | // we can loop through items safely now 43 | for (NSDictionary *item in list) 44 | { 45 | // ... 46 | } 47 | 48 | 49 | #### I don't have enable ARC on my project, how can I use your library ? 50 | 51 | You have 2 options: 52 | 53 | * Use the branch "[no-objc-arc](https://github.com/amarcadet/XMLReader/tree/no-objc-arc)" that use manual reference counting. 54 | * **Better choice:** add the "-fobjc-arc" compiler flag on `XMLReader.m` file in your build phases. 55 | 56 | #### I have trust issues, I don't want ARC, I prefer MRC, what can I do ? 57 | 58 | Well, nobody is perfect but, still, you can use the branch "[no-objc-arc](https://github.com/amarcadet/XMLReader/tree/no-objc-arc)". 59 | 60 | 61 | ## Contributions 62 | 63 | Thanks to the original author of this component Troy Brant and to [Divan "snip3r8" Visagie](https://github.com/snip3r8) for providing ARC support. 64 | 65 | 66 | ## License 67 | 68 | Copyright (C) 2012 Antoine Marcadet 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 71 | 72 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 73 | 74 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 75 | 76 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/amarcadet/XMLReader/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 77 | 78 | -------------------------------------------------------------------------------- /XMLReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // XMLReader.h 3 | // 4 | // Created by Troy Brant on 9/18/10. 5 | // Updated by Antoine Marcadet on 9/23/11. 6 | // Updated by Divan Visagie on 2012-08-26 7 | // 8 | 9 | #import 10 | 11 | enum { 12 | XMLReaderOptionsProcessNamespaces = 1 << 0, // Specifies whether the receiver reports the namespace and the qualified name of an element. 13 | XMLReaderOptionsReportNamespacePrefixes = 1 << 1, // Specifies whether the receiver reports the scope of namespace declarations. 14 | XMLReaderOptionsResolveExternalEntities = 1 << 2, // Specifies whether the receiver reports declarations of external entities. 15 | }; 16 | typedef NSUInteger XMLReaderOptions; 17 | 18 | @interface XMLReader : NSObject 19 | 20 | + (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)errorPointer; 21 | + (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)errorPointer; 22 | + (NSDictionary *)dictionaryForXMLData:(NSData *)data options:(XMLReaderOptions)options error:(NSError **)errorPointer; 23 | + (NSDictionary *)dictionaryForXMLString:(NSString *)string options:(XMLReaderOptions)options error:(NSError **)errorPointer; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /XMLReader.m: -------------------------------------------------------------------------------- 1 | // 2 | // XMLReader.m 3 | // 4 | // Created by Troy Brant on 9/18/10. 5 | // Updated by Antoine Marcadet on 9/23/11. 6 | // Updated by Divan Visagie on 2012-08-26 7 | // 8 | 9 | #import "XMLReader.h" 10 | 11 | #if !defined(__has_feature) || !__has_feature(objc_arc) 12 | #error "XMLReader requires ARC support." 13 | #endif 14 | 15 | NSString *const kXMLReaderTextNodeKey = @"text"; 16 | NSString *const kXMLReaderAttributePrefix = @"@"; 17 | 18 | @interface XMLReader () 19 | 20 | @property (nonatomic, strong) NSMutableArray *dictionaryStack; 21 | @property (nonatomic, strong) NSMutableString *textInProgress; 22 | @property (nonatomic, strong) NSError *errorPointer; 23 | 24 | @end 25 | 26 | 27 | @implementation XMLReader 28 | 29 | #pragma mark - Public methods 30 | 31 | + (NSDictionary *)dictionaryForXMLData:(NSData *)data error:(NSError **)error 32 | { 33 | XMLReader *reader = [[XMLReader alloc] initWithError:error]; 34 | NSDictionary *rootDictionary = [reader objectWithData:data options:0]; 35 | return rootDictionary; 36 | } 37 | 38 | + (NSDictionary *)dictionaryForXMLString:(NSString *)string error:(NSError **)error 39 | { 40 | NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; 41 | return [XMLReader dictionaryForXMLData:data error:error]; 42 | } 43 | 44 | + (NSDictionary *)dictionaryForXMLData:(NSData *)data options:(XMLReaderOptions)options error:(NSError **)error 45 | { 46 | XMLReader *reader = [[XMLReader alloc] initWithError:error]; 47 | NSDictionary *rootDictionary = [reader objectWithData:data options:options]; 48 | return rootDictionary; 49 | } 50 | 51 | + (NSDictionary *)dictionaryForXMLString:(NSString *)string options:(XMLReaderOptions)options error:(NSError **)error 52 | { 53 | NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; 54 | return [XMLReader dictionaryForXMLData:data options:options error:error]; 55 | } 56 | 57 | 58 | #pragma mark - Parsing 59 | 60 | - (id)initWithError:(NSError **)error 61 | { 62 | self = [super init]; 63 | if (self) 64 | { 65 | self.errorPointer = *error; 66 | } 67 | return self; 68 | } 69 | 70 | - (NSDictionary *)objectWithData:(NSData *)data options:(XMLReaderOptions)options 71 | { 72 | // Clear out any old data 73 | self.dictionaryStack = [[NSMutableArray alloc] init]; 74 | self.textInProgress = [[NSMutableString alloc] init]; 75 | 76 | // Initialize the stack with a fresh dictionary 77 | [self.dictionaryStack addObject:[NSMutableDictionary dictionary]]; 78 | 79 | // Parse the XML 80 | NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data]; 81 | 82 | [parser setShouldProcessNamespaces:(options & XMLReaderOptionsProcessNamespaces)]; 83 | [parser setShouldReportNamespacePrefixes:(options & XMLReaderOptionsReportNamespacePrefixes)]; 84 | [parser setShouldResolveExternalEntities:(options & XMLReaderOptionsResolveExternalEntities)]; 85 | 86 | parser.delegate = self; 87 | BOOL success = [parser parse]; 88 | 89 | // Return the stack's root dictionary on success 90 | if (success) 91 | { 92 | NSDictionary *resultDict = [self.dictionaryStack objectAtIndex:0]; 93 | return resultDict; 94 | } 95 | 96 | return nil; 97 | } 98 | 99 | 100 | #pragma mark - NSXMLParserDelegate methods 101 | 102 | - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict 103 | { 104 | // Get the dictionary for the current level in the stack 105 | NSMutableDictionary *parentDict = [self.dictionaryStack lastObject]; 106 | 107 | // Create the child dictionary for the new element, and initilaize it with the attributes 108 | NSMutableDictionary *childDict = [NSMutableDictionary dictionary]; 109 | [childDict addEntriesFromDictionary:attributeDict]; 110 | 111 | // If there's already an item for this key, it means we need to create an array 112 | id existingValue = [parentDict objectForKey:elementName]; 113 | if (existingValue) 114 | { 115 | NSMutableArray *array = nil; 116 | if ([existingValue isKindOfClass:[NSMutableArray class]]) 117 | { 118 | // The array exists, so use it 119 | array = (NSMutableArray *) existingValue; 120 | } 121 | else 122 | { 123 | // Create an array if it doesn't exist 124 | array = [NSMutableArray array]; 125 | [array addObject:existingValue]; 126 | 127 | // Replace the child dictionary with an array of children dictionaries 128 | [parentDict setObject:array forKey:elementName]; 129 | } 130 | 131 | // Add the new child dictionary to the array 132 | [array addObject:childDict]; 133 | } 134 | else 135 | { 136 | // No existing value, so update the dictionary 137 | [parentDict setObject:childDict forKey:elementName]; 138 | } 139 | 140 | // Update the stack 141 | [self.dictionaryStack addObject:childDict]; 142 | } 143 | 144 | - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 145 | { 146 | // Update the parent dict with text info 147 | NSMutableDictionary *dictInProgress = [self.dictionaryStack lastObject]; 148 | 149 | // Set the text property 150 | if ([self.textInProgress length] > 0) 151 | { 152 | // trim after concatenating 153 | NSString *trimmedString = [self.textInProgress stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 154 | [dictInProgress setObject:[trimmedString mutableCopy] forKey:kXMLReaderTextNodeKey]; 155 | 156 | // Reset the text 157 | self.textInProgress = [[NSMutableString alloc] init]; 158 | } 159 | 160 | // Pop the current dict 161 | [self.dictionaryStack removeLastObject]; 162 | } 163 | 164 | - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string 165 | { 166 | // Build the text value 167 | [self.textInProgress appendString:string]; 168 | } 169 | 170 | - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError 171 | { 172 | // Set the error pointer to the parser's error object 173 | self.errorPointer = parseError; 174 | } 175 | 176 | @end 177 | --------------------------------------------------------------------------------