XMLDictionary.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. //
  2. // XMLDictionary.m
  3. //
  4. // Version 1.4.1
  5. //
  6. // Created by Nick Lockwood on 15/11/2010.
  7. // Copyright 2010 Charcoal Design. All rights reserved.
  8. //
  9. // Get the latest version of XMLDictionary from here:
  10. //
  11. // https://github.com/nicklockwood/XMLDictionary
  12. //
  13. // This software is provided 'as-is', without any express or implied
  14. // warranty. In no event will the authors be held liable for any damages
  15. // arising from the use of this software.
  16. //
  17. // Permission is granted to anyone to use this software for any purpose,
  18. // including commercial applications, and to alter it and redistribute it
  19. // freely, subject to the following restrictions:
  20. //
  21. // 1. The origin of this software must not be misrepresented; you must not
  22. // claim that you wrote the original software. If you use this software
  23. // in a product, an acknowledgment in the product documentation would be
  24. // appreciated but is not required.
  25. //
  26. // 2. Altered source versions must be plainly marked as such, and must not be
  27. // misrepresented as being the original software.
  28. //
  29. // 3. This notice may not be removed or altered from any source distribution.
  30. //
  31. #import "XMLDictionary.h"
  32. #pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
  33. #pragma GCC diagnostic ignored "-Wpartial-availability"
  34. #pragma GCC diagnostic ignored "-Wdirect-ivar-access"
  35. #pragma GCC diagnostic ignored "-Wformat-non-iso"
  36. #pragma GCC diagnostic ignored "-Wgnu"
  37. #import <Availability.h>
  38. #if !__has_feature(objc_arc)
  39. #error This class requires automatic reference counting
  40. #endif
  41. @interface XMLDictionaryParser () <NSXMLParserDelegate>
  42. @property (nonatomic, strong) NSMutableDictionary<NSString *, id> *root;
  43. @property (nonatomic, strong) NSMutableArray *stack;
  44. @property (nonatomic, strong) NSMutableString *text;
  45. @end
  46. @implementation XMLDictionaryParser
  47. + (XMLDictionaryParser *)sharedInstance
  48. {
  49. static dispatch_once_t once;
  50. static XMLDictionaryParser *sharedInstance;
  51. dispatch_once(&once, ^{
  52. sharedInstance = [[XMLDictionaryParser alloc] init];
  53. });
  54. return sharedInstance;
  55. }
  56. - (instancetype)init
  57. {
  58. if ((self = [super init]))
  59. {
  60. _collapseTextNodes = YES;
  61. _stripEmptyNodes = YES;
  62. _trimWhiteSpace = YES;
  63. _alwaysUseArrays = NO;
  64. _preserveComments = NO;
  65. _wrapRootNode = NO;
  66. }
  67. return self;
  68. }
  69. - (id)copyWithZone:(NSZone *)zone
  70. {
  71. XMLDictionaryParser *copy = [[[self class] allocWithZone:zone] init];
  72. copy.collapseTextNodes = _collapseTextNodes;
  73. copy.stripEmptyNodes = _stripEmptyNodes;
  74. copy.trimWhiteSpace = _trimWhiteSpace;
  75. copy.alwaysUseArrays = _alwaysUseArrays;
  76. copy.preserveComments = _preserveComments;
  77. copy.attributesMode = _attributesMode;
  78. copy.nodeNameMode = _nodeNameMode;
  79. copy.wrapRootNode = _wrapRootNode;
  80. return copy;
  81. }
  82. - (NSDictionary<NSString *, id> *)dictionaryWithParser:(NSXMLParser *)parser
  83. {
  84. parser.delegate = self;
  85. [parser parse];
  86. id result = _root;
  87. _root = nil;
  88. _stack = nil;
  89. _text = nil;
  90. return result;
  91. }
  92. - (NSDictionary<NSString *, id> *)dictionaryWithData:(NSData *)data
  93. {
  94. NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
  95. return [self dictionaryWithParser:parser];
  96. }
  97. - (NSDictionary<NSString *, id> *)dictionaryWithString:(NSString *)string
  98. {
  99. NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
  100. return [self dictionaryWithData:data];
  101. }
  102. - (NSDictionary<NSString *, id> *)dictionaryWithFile:(NSString *)path
  103. {
  104. NSData *data = [NSData dataWithContentsOfFile:path];
  105. return [self dictionaryWithData:data];
  106. }
  107. + (NSString *)XMLStringForNode:(id)node withNodeName:(NSString *)nodeName
  108. {
  109. if ([node isKindOfClass:[NSArray class]])
  110. {
  111. NSMutableArray<NSString *> *nodes = [NSMutableArray arrayWithCapacity:[node count]];
  112. for (id individualNode in node)
  113. {
  114. [nodes addObject:[self XMLStringForNode:individualNode withNodeName:nodeName]];
  115. }
  116. return [nodes componentsJoinedByString:@"\n"];
  117. }
  118. else if ([node isKindOfClass:[NSDictionary class]])
  119. {
  120. NSDictionary<NSString *, NSString *> *attributes = [(NSDictionary *)node attributes];
  121. NSMutableString *attributeString = [NSMutableString string];
  122. [attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, __unused BOOL *stop) {
  123. [attributeString appendFormat:@" %@=\"%@\"", key.description.XMLEncodedString, value.description.XMLEncodedString];
  124. }];
  125. NSString *innerXML = [node innerXML];
  126. if (innerXML.length)
  127. {
  128. return [NSString stringWithFormat:@"<%1$@%2$@>%3$@</%1$@>", nodeName, attributeString, innerXML];
  129. }
  130. else
  131. {
  132. return [NSString stringWithFormat:@"<%@%@/>", nodeName, attributeString];
  133. }
  134. }
  135. else
  136. {
  137. return [NSString stringWithFormat:@"<%1$@>%2$@</%1$@>", nodeName, [node description].XMLEncodedString];
  138. }
  139. }
  140. - (void)endText
  141. {
  142. if (_trimWhiteSpace)
  143. {
  144. _text = [[_text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
  145. }
  146. if (_text.length)
  147. {
  148. NSMutableDictionary *top = _stack.lastObject;
  149. id existing = top[XMLDictionaryTextKey];
  150. if ([existing isKindOfClass:[NSArray class]])
  151. {
  152. [existing addObject:_text];
  153. }
  154. else if (existing)
  155. {
  156. top[XMLDictionaryTextKey] = [@[existing, _text] mutableCopy];
  157. }
  158. else
  159. {
  160. top[XMLDictionaryTextKey] = _text;
  161. }
  162. }
  163. _text = nil;
  164. }
  165. - (void)addText:(NSString *)text
  166. {
  167. if (!_text)
  168. {
  169. _text = [NSMutableString stringWithString:text];
  170. }
  171. else
  172. {
  173. [_text appendString:text];
  174. }
  175. }
  176. - (void)parser:(__unused NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName attributes:(NSDictionary *)attributeDict
  177. {
  178. [self endText];
  179. NSMutableDictionary<NSString *, id> *node = [NSMutableDictionary dictionary];
  180. switch (_nodeNameMode)
  181. {
  182. case XMLDictionaryNodeNameModeRootOnly:
  183. {
  184. if (!_root)
  185. {
  186. node[XMLDictionaryNodeNameKey] = elementName;
  187. }
  188. break;
  189. }
  190. case XMLDictionaryNodeNameModeAlways:
  191. {
  192. node[XMLDictionaryNodeNameKey] = elementName;
  193. break;
  194. }
  195. case XMLDictionaryNodeNameModeNever:
  196. {
  197. break;
  198. }
  199. }
  200. if (attributeDict.count)
  201. {
  202. switch (_attributesMode)
  203. {
  204. case XMLDictionaryAttributesModePrefixed:
  205. {
  206. for (NSString *key in attributeDict)
  207. {
  208. node[[XMLDictionaryAttributePrefix stringByAppendingString:key]] = attributeDict[key];
  209. }
  210. break;
  211. }
  212. case XMLDictionaryAttributesModeDictionary:
  213. {
  214. node[XMLDictionaryAttributesKey] = attributeDict;
  215. break;
  216. }
  217. case XMLDictionaryAttributesModeUnprefixed:
  218. {
  219. [node addEntriesFromDictionary:attributeDict];
  220. break;
  221. }
  222. case XMLDictionaryAttributesModeDiscard:
  223. {
  224. break;
  225. }
  226. }
  227. }
  228. if (!_root)
  229. {
  230. _root = node;
  231. _stack = [NSMutableArray arrayWithObject:node];
  232. if (_wrapRootNode)
  233. {
  234. _root = [NSMutableDictionary dictionaryWithObject:_root forKey:elementName];
  235. [_stack insertObject:_root atIndex:0];
  236. }
  237. }
  238. else
  239. {
  240. NSMutableDictionary<NSString *, id> *top = _stack.lastObject;
  241. id existing = top[elementName];
  242. if ([existing isKindOfClass:[NSArray class]])
  243. {
  244. [(NSMutableArray *)existing addObject:node];
  245. }
  246. else if (existing)
  247. {
  248. top[elementName] = [@[existing, node] mutableCopy];
  249. }
  250. else if (_alwaysUseArrays)
  251. {
  252. top[elementName] = [NSMutableArray arrayWithObject:node];
  253. }
  254. else
  255. {
  256. top[elementName] = node;
  257. }
  258. [_stack addObject:node];
  259. }
  260. }
  261. - (NSString *)nameForNode:(NSDictionary<NSString *, id> *)node inDictionary:(NSDictionary<NSString *, id> *)dict
  262. {
  263. if (node.nodeName)
  264. {
  265. return node.nodeName;
  266. }
  267. else
  268. {
  269. for (NSString *name in dict)
  270. {
  271. id object = dict[name];
  272. if (object == node)
  273. {
  274. return name;
  275. }
  276. else if ([object isKindOfClass:[NSArray class]] && [(NSArray *)object containsObject:node])
  277. {
  278. return name;
  279. }
  280. }
  281. }
  282. return nil;
  283. }
  284. - (void)parser:(__unused NSXMLParser *)parser didEndElement:(__unused NSString *)elementName namespaceURI:(__unused NSString *)namespaceURI qualifiedName:(__unused NSString *)qName
  285. {
  286. [self endText];
  287. NSMutableDictionary<NSString *, id> *top = _stack.lastObject;
  288. [_stack removeLastObject];
  289. if (!top.attributes && !top.childNodes && !top.comments)
  290. {
  291. NSMutableDictionary<NSString *, id> *newTop = _stack.lastObject;
  292. NSString *nodeName = [self nameForNode:top inDictionary:newTop];
  293. if (nodeName)
  294. {
  295. id parentNode = newTop[nodeName];
  296. NSString *innerText = top.innerText;
  297. if (innerText && _collapseTextNodes)
  298. {
  299. if ([parentNode isKindOfClass:[NSArray class]])
  300. {
  301. parentNode[[parentNode count] - 1] = innerText;
  302. }
  303. else
  304. {
  305. newTop[nodeName] = innerText;
  306. }
  307. }
  308. else if (!innerText)
  309. {
  310. if (_stripEmptyNodes)
  311. {
  312. if ([parentNode isKindOfClass:[NSArray class]])
  313. {
  314. [(NSMutableArray *)parentNode removeLastObject];
  315. }
  316. else
  317. {
  318. [newTop removeObjectForKey:nodeName];
  319. }
  320. }
  321. else if (!_collapseTextNodes)
  322. {
  323. top[XMLDictionaryTextKey] = @"";
  324. }
  325. }
  326. }
  327. }
  328. }
  329. - (void)parser:(__unused NSXMLParser *)parser foundCharacters:(NSString *)string
  330. {
  331. [self addText:string];
  332. }
  333. - (void)parser:(__unused NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock
  334. {
  335. [self addText:[[NSString alloc] initWithData:CDATABlock encoding:NSUTF8StringEncoding]];
  336. }
  337. - (void)parser:(__unused NSXMLParser *)parser foundComment:(NSString *)comment
  338. {
  339. if (_preserveComments)
  340. {
  341. NSMutableDictionary<NSString *, id> *top = _stack.lastObject;
  342. NSMutableArray<NSString *> *comments = top[XMLDictionaryCommentsKey];
  343. if (!comments)
  344. {
  345. comments = [@[comment] mutableCopy];
  346. top[XMLDictionaryCommentsKey] = comments;
  347. }
  348. else
  349. {
  350. [comments addObject:comment];
  351. }
  352. }
  353. }
  354. @end
  355. @implementation NSDictionary(XMLDictionary)
  356. + (NSDictionary<NSString *, id> *)dictionaryWithXMLParser:(NSXMLParser *)parser
  357. {
  358. return [[[XMLDictionaryParser sharedInstance] copy] dictionaryWithParser:parser];
  359. }
  360. + (NSDictionary<NSString *, id> *)dictionaryWithXMLData:(NSData *)data
  361. {
  362. return [[[XMLDictionaryParser sharedInstance] copy] dictionaryWithData:data];
  363. }
  364. + (NSDictionary<NSString *, id> *)dictionaryWithXMLString:(NSString *)string
  365. {
  366. return [[[XMLDictionaryParser sharedInstance] copy] dictionaryWithString:string];
  367. }
  368. + (NSDictionary<NSString *, id> *)dictionaryWithXMLFile:(NSString *)path
  369. {
  370. return [[[XMLDictionaryParser sharedInstance] copy] dictionaryWithFile:path];
  371. }
  372. - (nullable NSDictionary<NSString *, NSString *> *)attributes
  373. {
  374. NSDictionary<NSString *, NSString *> *attributes = self[XMLDictionaryAttributesKey];
  375. if (attributes)
  376. {
  377. return attributes.count? attributes: nil;
  378. }
  379. else
  380. {
  381. NSMutableDictionary<NSString *, id> *filteredDict = [NSMutableDictionary dictionaryWithDictionary:self];
  382. [filteredDict removeObjectsForKeys:@[XMLDictionaryCommentsKey, XMLDictionaryTextKey, XMLDictionaryNodeNameKey]];
  383. for (NSString *key in filteredDict.allKeys)
  384. {
  385. [filteredDict removeObjectForKey:key];
  386. if ([key hasPrefix:XMLDictionaryAttributePrefix])
  387. {
  388. filteredDict[[key substringFromIndex:XMLDictionaryAttributePrefix.length]] = self[key];
  389. }
  390. }
  391. return filteredDict.count? filteredDict: nil;
  392. }
  393. return nil;
  394. }
  395. - (nullable NSDictionary *)childNodes
  396. {
  397. NSMutableDictionary *filteredDict = [self mutableCopy];
  398. [filteredDict removeObjectsForKeys:@[XMLDictionaryAttributesKey, XMLDictionaryCommentsKey, XMLDictionaryTextKey, XMLDictionaryNodeNameKey]];
  399. for (NSString *key in filteredDict.allKeys)
  400. {
  401. if ([key hasPrefix:XMLDictionaryAttributePrefix])
  402. {
  403. [filteredDict removeObjectForKey:key];
  404. }
  405. }
  406. return filteredDict.count? filteredDict: nil;
  407. }
  408. - (nullable NSArray *)comments
  409. {
  410. return self[XMLDictionaryCommentsKey];
  411. }
  412. - (nullable NSString *)nodeName
  413. {
  414. return self[XMLDictionaryNodeNameKey];
  415. }
  416. - (id)innerText
  417. {
  418. id text = self[XMLDictionaryTextKey];
  419. if ([text isKindOfClass:[NSArray class]])
  420. {
  421. return [text componentsJoinedByString:@"\n"];
  422. }
  423. else
  424. {
  425. return text;
  426. }
  427. }
  428. - (NSString *)innerXML
  429. {
  430. NSMutableArray *nodes = [NSMutableArray array];
  431. for (NSString *comment in [self comments])
  432. {
  433. [nodes addObject:[NSString stringWithFormat:@"<!--%@-->", [comment XMLEncodedString]]];
  434. }
  435. NSDictionary *childNodes = [self childNodes];
  436. for (NSString *key in childNodes)
  437. {
  438. [nodes addObject:[XMLDictionaryParser XMLStringForNode:childNodes[key] withNodeName:key]];
  439. }
  440. NSString *text = [self innerText];
  441. if (text)
  442. {
  443. [nodes addObject:[text XMLEncodedString]];
  444. }
  445. return [nodes componentsJoinedByString:@"\n"];
  446. }
  447. - (NSString *)XMLString
  448. {
  449. if (self.count == 1 && ![self nodeName])
  450. {
  451. //ignore outermost dictionary
  452. return [self innerXML];
  453. }
  454. else
  455. {
  456. return [XMLDictionaryParser XMLStringForNode:self withNodeName:[self nodeName] ?: @"root"];
  457. }
  458. }
  459. - (nullable NSArray *)arrayValueForKeyPath:(NSString *)keyPath
  460. {
  461. id value = [self valueForKeyPath:keyPath];
  462. if (value && ![value isKindOfClass:[NSArray class]])
  463. {
  464. return @[value];
  465. }
  466. return value;
  467. }
  468. - (nullable NSString *)stringValueForKeyPath:(NSString *)keyPath
  469. {
  470. id value = [self valueForKeyPath:keyPath];
  471. if ([value isKindOfClass:[NSArray class]])
  472. {
  473. value = ((NSArray *)value).firstObject;
  474. }
  475. if ([value isKindOfClass:[NSDictionary class]])
  476. {
  477. return [(NSDictionary *)value innerText];
  478. }
  479. return value;
  480. }
  481. - (nullable NSDictionary<NSString *, id> *)dictionaryValueForKeyPath:(NSString *)keyPath
  482. {
  483. id value = [self valueForKeyPath:keyPath];
  484. if ([value isKindOfClass:[NSArray class]])
  485. {
  486. value = [value count]? value[0]: nil;
  487. }
  488. if ([value isKindOfClass:[NSString class]])
  489. {
  490. return @{XMLDictionaryTextKey: value};
  491. }
  492. return value;
  493. }
  494. @end
  495. @implementation NSString (XMLDictionary)
  496. - (NSString *)XMLEncodedString
  497. {
  498. return [[[[[self stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"]
  499. stringByReplacingOccurrencesOfString:@"<" withString:@"&lt;"]
  500. stringByReplacingOccurrencesOfString:@">" withString:@"&gt;"]
  501. stringByReplacingOccurrencesOfString:@"\"" withString:@"&quot;"]
  502. stringByReplacingOccurrencesOfString:@"\'" withString:@"&apos;"];
  503. }
  504. @end