GDataXMLNode.m 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984
  1. /* Modifications for HTML parser support:
  2. * Copyright (c) 2011 Simon Grätzer simon@graetzer.org
  3. *
  4. * Copyright (c) 2008 Google Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. #define GDATAXMLNODE_DEFINE_GLOBALS 1
  19. #import "GDataXMLNode.h"
  20. @class NSArray, NSDictionary, NSError, NSString, NSURL;
  21. @class GDataXMLElement, GDataXMLDocument;
  22. static const int kGDataXMLParseOptions = (XML_PARSE_NOCDATA | XML_PARSE_NOBLANKS);
  23. static const int kGDataHTMLParseOptions = (HTML_PARSE_NOWARNING | HTML_PARSE_NOERROR);
  24. // dictionary key callbacks for string cache
  25. static const void *StringCacheKeyRetainCallBack(CFAllocatorRef allocator, const void *str);
  26. static void StringCacheKeyReleaseCallBack(CFAllocatorRef allocator, const void *str);
  27. static CFStringRef StringCacheKeyCopyDescriptionCallBack(const void *str);
  28. static Boolean StringCacheKeyEqualCallBack(const void *str1, const void *str2);
  29. static CFHashCode StringCacheKeyHashCallBack(const void *str);
  30. // isEqual: has the fatal flaw that it doesn't deal well with the received
  31. // being nil. We'll use this utility instead.
  32. // Static copy of AreEqualOrBothNil from GDataObject.m, so that using
  33. // GDataXMLNode does not require pulling in all of GData.
  34. static BOOL AreEqualOrBothNilPrivate(id obj1, id obj2) {
  35. if (obj1 == obj2) {
  36. return YES;
  37. }
  38. if (obj1 && obj2) {
  39. return [obj1 isEqual:obj2];
  40. }
  41. return NO;
  42. }
  43. // convert NSString* to xmlChar*
  44. //
  45. // the "Get" part implies that ownership remains with str
  46. static xmlChar* GDataGetXMLString(NSString *str) {
  47. xmlChar* result = (xmlChar *)[str UTF8String];
  48. return result;
  49. }
  50. // Make a fake qualified name we use as local name internally in libxml
  51. // data structures when there's no actual namespace node available to point to
  52. // from an element or attribute node
  53. //
  54. // Returns an autoreleased NSString*
  55. static NSString *GDataFakeQNameForURIAndName(NSString *theURI, NSString *name) {
  56. NSString *localName = [GDataXMLNode localNameForName:name];
  57. NSString *fakeQName = [NSString stringWithFormat:@"{%@}:%@",
  58. theURI, localName];
  59. return fakeQName;
  60. }
  61. // libxml2 offers xmlSplitQName2, but that searches forwards. Since we may
  62. // be searching for a whole URI shoved in as a prefix, like
  63. // {http://foo}:name
  64. // we'll search for the prefix in backwards from the end of the qualified name
  65. //
  66. // returns a copy of qname as the local name if there's no prefix
  67. static xmlChar *SplitQNameReverse(const xmlChar *qname, xmlChar **prefix) {
  68. // search backwards for a colon
  69. int qnameLen = xmlStrlen(qname);
  70. for (int idx = qnameLen - 1; idx >= 0; idx--) {
  71. if (qname[idx] == ':') {
  72. // found the prefix; copy the prefix, if requested
  73. if (prefix != NULL) {
  74. if (idx > 0) {
  75. *prefix = xmlStrsub(qname, 0, idx);
  76. } else {
  77. *prefix = NULL;
  78. }
  79. }
  80. if (idx < qnameLen - 1) {
  81. // return a copy of the local name
  82. xmlChar *localName = xmlStrsub(qname, idx + 1, qnameLen - idx - 1);
  83. return localName;
  84. } else {
  85. return NULL;
  86. }
  87. }
  88. }
  89. // no colon found, so the qualified name is the local name
  90. xmlChar *qnameCopy = xmlStrdup(qname);
  91. return qnameCopy;
  92. }
  93. @interface GDataXMLNode (PrivateMethods)
  94. // consuming a node implies it will later be freed when the instance is
  95. // dealloc'd; borrowing it implies that ownership and disposal remain the
  96. // job of the supplier of the node
  97. + (id)nodeConsumingXMLNode:(xmlNodePtr)theXMLNode;
  98. - (id)initConsumingXMLNode:(xmlNodePtr)theXMLNode;
  99. + (id)nodeBorrowingXMLNode:(xmlNodePtr)theXMLNode;
  100. - (id)initBorrowingXMLNode:(xmlNodePtr)theXMLNode;
  101. // getters of the underlying node
  102. - (xmlNodePtr)XMLNode;
  103. - (xmlNodePtr)XMLNodeCopy;
  104. // search for an underlying attribute
  105. - (GDataXMLNode *)attributeForXMLNode:(xmlAttrPtr)theXMLNode;
  106. // return an NSString for an xmlChar*, using our strings cache in the
  107. // document
  108. - (NSString *)stringFromXMLString:(const xmlChar *)chars;
  109. // setter/getter of the dealloc flag for the underlying node
  110. - (BOOL)shouldFreeXMLNode;
  111. - (void)setShouldFreeXMLNode:(BOOL)flag;
  112. @end
  113. @interface GDataXMLElement (PrivateMethods)
  114. + (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix
  115. graftingToTreeNode:(xmlNodePtr)graftPointNode;
  116. @end
  117. @implementation GDataXMLNode
  118. + (void)load {
  119. xmlInitParser();
  120. }
  121. // Note on convenience methods for making stand-alone element and
  122. // attribute nodes:
  123. //
  124. // Since we're making a node from scratch, we don't
  125. // have any namespace info. So the namespace prefix, if
  126. // any, will just be slammed into the node name.
  127. // We'll rely on the -addChild method below to remove
  128. // the namespace prefix and replace it with a proper ns
  129. // pointer.
  130. + (GDataXMLElement *)elementWithName:(NSString *)name {
  131. xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace
  132. GDataGetXMLString(name));
  133. if (theNewNode) {
  134. // succeeded
  135. return [self nodeConsumingXMLNode:theNewNode];
  136. }
  137. return nil;
  138. }
  139. + (GDataXMLElement *)elementWithName:(NSString *)name stringValue:(NSString *)value {
  140. xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace
  141. GDataGetXMLString(name));
  142. if (theNewNode) {
  143. xmlNodePtr textNode = xmlNewText(GDataGetXMLString(value));
  144. if (textNode) {
  145. xmlNodePtr temp = xmlAddChild(theNewNode, textNode);
  146. if (temp) {
  147. // succeeded
  148. return [self nodeConsumingXMLNode:theNewNode];
  149. }
  150. }
  151. // failed; free the node and any children
  152. xmlFreeNode(theNewNode);
  153. }
  154. return nil;
  155. }
  156. + (GDataXMLElement *)elementWithName:(NSString *)name URI:(NSString *)theURI {
  157. // since we don't know a prefix yet, shove in the whole URI; we'll look for
  158. // a proper namespace ptr later when addChild calls fixUpNamespacesForNode
  159. NSString *fakeQName = GDataFakeQNameForURIAndName(theURI, name);
  160. xmlNodePtr theNewNode = xmlNewNode(NULL, // namespace
  161. GDataGetXMLString(fakeQName));
  162. if (theNewNode) {
  163. return [self nodeConsumingXMLNode:theNewNode];
  164. }
  165. return nil;
  166. }
  167. + (id)attributeWithName:(NSString *)name stringValue:(NSString *)value {
  168. xmlChar *xmlName = GDataGetXMLString(name);
  169. xmlChar *xmlValue = GDataGetXMLString(value);
  170. xmlAttrPtr theNewAttr = xmlNewProp(NULL, // parent node for the attr
  171. xmlName, xmlValue);
  172. if (theNewAttr) {
  173. return [self nodeConsumingXMLNode:(xmlNodePtr) theNewAttr];
  174. }
  175. return nil;
  176. }
  177. + (id)attributeWithName:(NSString *)name URI:(NSString *)attributeURI stringValue:(NSString *)value {
  178. // since we don't know a prefix yet, shove in the whole URI; we'll look for
  179. // a proper namespace ptr later when addChild calls fixUpNamespacesForNode
  180. NSString *fakeQName = GDataFakeQNameForURIAndName(attributeURI, name);
  181. xmlChar *xmlName = GDataGetXMLString(fakeQName);
  182. xmlChar *xmlValue = GDataGetXMLString(value);
  183. xmlAttrPtr theNewAttr = xmlNewProp(NULL, // parent node for the attr
  184. xmlName, xmlValue);
  185. if (theNewAttr) {
  186. return [self nodeConsumingXMLNode:(xmlNodePtr) theNewAttr];
  187. }
  188. return nil;
  189. }
  190. + (id)textWithStringValue:(NSString *)value {
  191. xmlNodePtr theNewText = xmlNewText(GDataGetXMLString(value));
  192. if (theNewText) {
  193. return [self nodeConsumingXMLNode:theNewText];
  194. }
  195. return nil;
  196. }
  197. + (id)namespaceWithName:(NSString *)name stringValue:(NSString *)value {
  198. xmlChar *href = GDataGetXMLString(value);
  199. xmlChar *prefix;
  200. if ([name length] > 0) {
  201. prefix = GDataGetXMLString(name);
  202. } else {
  203. // default namespace is represented by a nil prefix
  204. prefix = nil;
  205. }
  206. xmlNsPtr theNewNs = xmlNewNs(NULL, // parent node
  207. href, prefix);
  208. if (theNewNs) {
  209. return [self nodeConsumingXMLNode:(xmlNodePtr) theNewNs];
  210. }
  211. return nil;
  212. }
  213. + (id)nodeConsumingXMLNode:(xmlNodePtr)theXMLNode {
  214. Class theClass;
  215. if (theXMLNode->type == XML_ELEMENT_NODE) {
  216. theClass = [GDataXMLElement class];
  217. } else {
  218. theClass = [GDataXMLNode class];
  219. }
  220. return [[theClass alloc] initConsumingXMLNode:theXMLNode];
  221. }
  222. - (id)initConsumingXMLNode:(xmlNodePtr)theXMLNode {
  223. self = [super init];
  224. if (self) {
  225. xmlNode_ = theXMLNode;
  226. shouldFreeXMLNode_ = YES;
  227. }
  228. return self;
  229. }
  230. + (id)nodeBorrowingXMLNode:(xmlNodePtr)theXMLNode {
  231. Class theClass;
  232. if (theXMLNode->type == XML_ELEMENT_NODE) {
  233. theClass = [GDataXMLElement class];
  234. } else {
  235. theClass = [GDataXMLNode class];
  236. }
  237. return [[theClass alloc] initBorrowingXMLNode:theXMLNode];
  238. }
  239. - (id)initBorrowingXMLNode:(xmlNodePtr)theXMLNode {
  240. self = [super init];
  241. if (self) {
  242. xmlNode_ = theXMLNode;
  243. shouldFreeXMLNode_ = NO;
  244. }
  245. return self;
  246. }
  247. - (void)releaseCachedValues {
  248. cachedName_ = nil;
  249. cachedChildren_ = nil;
  250. cachedAttributes_ = nil;
  251. }
  252. // convert xmlChar* to NSString*
  253. //
  254. // returns an autoreleased NSString*, from the current node's document strings
  255. // cache if possible
  256. - (NSString *)stringFromXMLString:(const xmlChar *)chars {
  257. #if DEBUG
  258. NSCAssert(chars != NULL, @"GDataXMLNode sees an unexpected empty string");
  259. #endif
  260. if (chars == NULL) return nil;
  261. CFMutableDictionaryRef cacheDict = NULL;
  262. NSString *result = nil;
  263. if (xmlNode_ != NULL
  264. && (xmlNode_->type == XML_ELEMENT_NODE
  265. || xmlNode_->type == XML_ATTRIBUTE_NODE
  266. || xmlNode_->type == XML_TEXT_NODE)) {
  267. // there is no xmlDocPtr in XML_NAMESPACE_DECL nodes,
  268. // so we can't cache the text of those
  269. // look for a strings cache in the document
  270. //
  271. // the cache is in the document's user-defined _private field
  272. if (xmlNode_->doc != NULL) {
  273. cacheDict = xmlNode_->doc->_private;
  274. if (cacheDict) {
  275. // this document has a strings cache
  276. result = (__bridge NSString *) CFDictionaryGetValue(cacheDict, chars);
  277. if (result) {
  278. // we found the xmlChar string in the cache; return the previously
  279. // allocated NSString, rather than allocate a new one
  280. return result;
  281. }
  282. }
  283. }
  284. }
  285. // allocate a new NSString for this xmlChar*
  286. result = [NSString stringWithUTF8String:(const char *) chars];
  287. if (cacheDict) {
  288. // save the string in the document's string cache
  289. CFDictionarySetValue(cacheDict, chars, (__bridge const void *)(result));
  290. }
  291. return result;
  292. }
  293. - (void)dealloc {
  294. if (xmlNode_ && shouldFreeXMLNode_) {
  295. xmlFreeNode(xmlNode_);
  296. xmlNode_ = NULL;
  297. }
  298. [self releaseCachedValues];
  299. }
  300. #pragma mark -
  301. - (void)setStringValue:(NSString *)str {
  302. if (xmlNode_ != NULL && str != nil) {
  303. if (xmlNode_->type == XML_NAMESPACE_DECL) {
  304. // for a namespace node, the value is the namespace URI
  305. xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;
  306. if (nsNode->href != NULL) xmlFree((char *)nsNode->href);
  307. nsNode->href = xmlStrdup(GDataGetXMLString(str));
  308. } else {
  309. // attribute or element node
  310. // do we need to call xmlEncodeSpecialChars?
  311. xmlNodeSetContent(xmlNode_, GDataGetXMLString(str));
  312. }
  313. }
  314. }
  315. - (NSString *)stringValue {
  316. NSString *str = nil;
  317. if (xmlNode_ != NULL) {
  318. if (xmlNode_->type == XML_NAMESPACE_DECL) {
  319. // for a namespace node, the value is the namespace URI
  320. xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;
  321. str = [self stringFromXMLString:(nsNode->href)];
  322. } else {
  323. // attribute or element node
  324. xmlChar* chars = xmlNodeGetContent(xmlNode_);
  325. if (chars) {
  326. str = [self stringFromXMLString:chars];
  327. xmlFree(chars);
  328. }
  329. }
  330. }
  331. return str;
  332. }
  333. - (NSString *)XMLString {
  334. NSString *str = nil;
  335. if (xmlNode_ != NULL) {
  336. xmlBufferPtr buff = xmlBufferCreate();
  337. if (buff) {
  338. xmlDocPtr doc = NULL;
  339. int level = 0;
  340. int format = 0;
  341. int result = xmlNodeDump(buff, doc, xmlNode_, level, format);
  342. if (result > -1) {
  343. str = [[NSString alloc] initWithBytes:(xmlBufferContent(buff))
  344. length:(xmlBufferLength(buff))
  345. encoding:NSUTF8StringEncoding];
  346. }
  347. xmlBufferFree(buff);
  348. }
  349. }
  350. // remove leading and trailing whitespace
  351. NSCharacterSet *ws = [NSCharacterSet whitespaceAndNewlineCharacterSet];
  352. NSString *trimmed = [str stringByTrimmingCharactersInSet:ws];
  353. return trimmed;
  354. }
  355. - (NSString *)localName {
  356. NSString *str = nil;
  357. if (xmlNode_ != NULL) {
  358. str = [self stringFromXMLString:(xmlNode_->name)];
  359. // if this is part of a detached subtree, str may have a prefix in it
  360. str = [[self class] localNameForName:str];
  361. }
  362. return str;
  363. }
  364. - (NSString *)prefix {
  365. NSString *str = nil;
  366. if (xmlNode_ != NULL) {
  367. // the default namespace's prefix is an empty string, though libxml
  368. // represents it as NULL for ns->prefix
  369. str = @"";
  370. if (xmlNode_->ns != NULL && xmlNode_->ns->prefix != NULL) {
  371. str = [self stringFromXMLString:(xmlNode_->ns->prefix)];
  372. }
  373. }
  374. return str;
  375. }
  376. - (NSString *)URI {
  377. NSString *str = nil;
  378. if (xmlNode_ != NULL) {
  379. if (xmlNode_->ns != NULL && xmlNode_->ns->href != NULL) {
  380. str = [self stringFromXMLString:(xmlNode_->ns->href)];
  381. }
  382. }
  383. return str;
  384. }
  385. - (NSString *)qualifiedName {
  386. // internal utility
  387. NSString *str = nil;
  388. if (xmlNode_ != NULL) {
  389. if (xmlNode_->type == XML_NAMESPACE_DECL) {
  390. // name of a namespace node
  391. xmlNsPtr nsNode = (xmlNsPtr)xmlNode_;
  392. // null is the default namespace; one is the loneliest number
  393. if (nsNode->prefix == NULL) {
  394. str = @"";
  395. }
  396. else {
  397. str = [self stringFromXMLString:(nsNode->prefix)];
  398. }
  399. } else if (xmlNode_->ns != NULL && xmlNode_->ns->prefix != NULL) {
  400. // name of a non-namespace node
  401. // has a prefix
  402. char *qname;
  403. if (asprintf(&qname, "%s:%s", (const char *)xmlNode_->ns->prefix,
  404. xmlNode_->name) != -1) {
  405. str = [self stringFromXMLString:(const xmlChar *)qname];
  406. free(qname);
  407. }
  408. } else {
  409. // lacks a prefix
  410. str = [self stringFromXMLString:(xmlNode_->name)];
  411. }
  412. }
  413. return str;
  414. }
  415. - (NSString *)name {
  416. if (cachedName_ != nil) {
  417. return cachedName_;
  418. }
  419. NSString *str = [self qualifiedName];
  420. cachedName_ = str;
  421. return str;
  422. }
  423. + (NSString *)localNameForName:(NSString *)name {
  424. if (name != nil) {
  425. NSRange range = [name rangeOfString:@":"];
  426. if (range.location != NSNotFound) {
  427. // found a colon
  428. if (range.location + 1 < [name length]) {
  429. NSString *localName = [name substringFromIndex:(range.location + 1)];
  430. return localName;
  431. }
  432. }
  433. }
  434. return name;
  435. }
  436. + (NSString *)prefixForName:(NSString *)name {
  437. if (name != nil) {
  438. NSRange range = [name rangeOfString:@":"];
  439. if (range.location != NSNotFound) {
  440. NSString *prefix = [name substringToIndex:(range.location)];
  441. return prefix;
  442. }
  443. }
  444. return nil;
  445. }
  446. - (NSUInteger)childCount {
  447. if (cachedChildren_ != nil) {
  448. return [cachedChildren_ count];
  449. }
  450. if (xmlNode_ != NULL) {
  451. unsigned int count = 0;
  452. xmlNodePtr currChild = xmlNode_->children;
  453. while (currChild != NULL) {
  454. ++count;
  455. currChild = currChild->next;
  456. }
  457. return count;
  458. }
  459. return 0;
  460. }
  461. - (NSArray *)children {
  462. if (cachedChildren_ != nil) {
  463. return cachedChildren_;
  464. }
  465. NSMutableArray *array = nil;
  466. if (xmlNode_ != NULL) {
  467. xmlNodePtr currChild = xmlNode_->children;
  468. while (currChild != NULL) {
  469. GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:currChild];
  470. if (array == nil) {
  471. array = [NSMutableArray arrayWithObject:node];
  472. } else {
  473. [array addObject:node];
  474. }
  475. currChild = currChild->next;
  476. }
  477. cachedChildren_ = array;
  478. }
  479. return array;
  480. }
  481. - (GDataXMLNode *)childAtIndex:(unsigned)index {
  482. NSArray *children = [self children];
  483. if ([children count] > index) {
  484. return [children objectAtIndex:index];
  485. }
  486. return nil;
  487. }
  488. - (GDataXMLNodeKind)kind {
  489. if (xmlNode_ != NULL) {
  490. xmlElementType nodeType = xmlNode_->type;
  491. switch (nodeType) {
  492. case XML_ELEMENT_NODE: return GDataXMLElementKind;
  493. case XML_ATTRIBUTE_NODE: return GDataXMLAttributeKind;
  494. case XML_TEXT_NODE: return GDataXMLTextKind;
  495. case XML_CDATA_SECTION_NODE: return GDataXMLTextKind;
  496. case XML_ENTITY_REF_NODE: return GDataXMLEntityDeclarationKind;
  497. case XML_ENTITY_NODE: return GDataXMLEntityDeclarationKind;
  498. case XML_PI_NODE: return GDataXMLProcessingInstructionKind;
  499. case XML_COMMENT_NODE: return GDataXMLCommentKind;
  500. case XML_DOCUMENT_NODE: return GDataXMLDocumentKind;
  501. case XML_DOCUMENT_TYPE_NODE: return GDataXMLDocumentKind;
  502. case XML_DOCUMENT_FRAG_NODE: return GDataXMLDocumentKind;
  503. case XML_NOTATION_NODE: return GDataXMLNotationDeclarationKind;
  504. case XML_HTML_DOCUMENT_NODE: return GDataXMLDocumentKind;
  505. case XML_DTD_NODE: return GDataXMLDTDKind;
  506. case XML_ELEMENT_DECL: return GDataXMLElementDeclarationKind;
  507. case XML_ATTRIBUTE_DECL: return GDataXMLAttributeDeclarationKind;
  508. case XML_ENTITY_DECL: return GDataXMLEntityDeclarationKind;
  509. case XML_NAMESPACE_DECL: return GDataXMLNamespaceKind;
  510. case XML_XINCLUDE_START: return GDataXMLProcessingInstructionKind;
  511. case XML_XINCLUDE_END: return GDataXMLProcessingInstructionKind;
  512. case XML_DOCB_DOCUMENT_NODE: return GDataXMLDocumentKind;
  513. }
  514. }
  515. return GDataXMLInvalidKind;
  516. }
  517. - (GDataXMLNode *)firstNodeForXPath:(NSString *)xpath error:(NSError **)error
  518. {
  519. NSArray *nodes = [self nodesForXPath:xpath error:error];
  520. if (!nodes.count) {
  521. return nil;
  522. }
  523. return [nodes objectAtIndex:0];
  524. }
  525. - (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error {
  526. // call through with no explicit namespace dictionary; that will register the
  527. // root node's namespaces
  528. return [self nodesForXPath:xpath namespaces:nil error:error];
  529. }
  530. - (GDataXMLNode *)firstNodeForXPath:(NSString *)xpath namespaces:(NSDictionary *)namespaces error:(NSError **)error
  531. {
  532. NSArray *nodes = [self nodesForXPath:xpath namespaces:namespaces error:error];
  533. if (!nodes.count) {
  534. return nil;
  535. }
  536. return [nodes objectAtIndex:0];
  537. }
  538. - (NSArray *)nodesForXPath:(NSString *)xpath
  539. namespaces:(NSDictionary *)namespaces
  540. error:(NSError **)error {
  541. NSMutableArray *array = nil;
  542. NSInteger errorCode = -1;
  543. NSDictionary *errorInfo = nil;
  544. // xmlXPathNewContext requires a doc for its context, but if our elements
  545. // are created from GDataXMLElement's initWithXMLString there may not be
  546. // a document. (We may later decide that we want to stuff the doc used
  547. // there into a GDataXMLDocument and retain it, but we don't do that now.)
  548. //
  549. // We'll temporarily make a document to use for the xpath context.
  550. xmlDocPtr tempDoc = NULL;
  551. xmlNodePtr topParent = NULL;
  552. if (xmlNode_->doc == NULL) {
  553. tempDoc = xmlNewDoc(NULL);
  554. if (tempDoc) {
  555. // find the topmost node of the current tree to make the root of
  556. // our temporary document
  557. topParent = xmlNode_;
  558. while (topParent->parent != NULL) {
  559. topParent = topParent->parent;
  560. }
  561. xmlDocSetRootElement(tempDoc, topParent);
  562. }
  563. }
  564. if (xmlNode_ != NULL && xmlNode_->doc != NULL) {
  565. xmlXPathContextPtr xpathCtx = xmlXPathNewContext(xmlNode_->doc);
  566. if (xpathCtx) {
  567. // anchor at our current node
  568. xpathCtx->node = xmlNode_;
  569. // if a namespace dictionary was provided, register its contents
  570. if (namespaces) {
  571. // the dictionary keys are prefixes; the values are URIs
  572. for (NSString *prefix in namespaces) {
  573. NSString *uri = [namespaces objectForKey:prefix];
  574. xmlChar *prefixChars = (xmlChar *) [prefix UTF8String];
  575. xmlChar *uriChars = (xmlChar *) [uri UTF8String];
  576. int result = xmlXPathRegisterNs(xpathCtx, prefixChars, uriChars);
  577. if (result != 0) {
  578. #if DEBUG
  579. NSCAssert1(result == 0, @"GDataXMLNode XPath namespace %@ issue",
  580. prefix);
  581. #endif
  582. }
  583. }
  584. } else {
  585. // no namespace dictionary was provided
  586. //
  587. // register the namespaces of this node, if it's an element, or of
  588. // this node's root element, if it's a document
  589. xmlNodePtr nsNodePtr = xmlNode_;
  590. if (xmlNode_->type == XML_DOCUMENT_NODE) {
  591. nsNodePtr = xmlDocGetRootElement((xmlDocPtr) xmlNode_);
  592. }
  593. // step through the namespaces, if any, and register each with the
  594. // xpath context
  595. if (nsNodePtr != NULL) {
  596. for (xmlNsPtr nsPtr = nsNodePtr->ns; nsPtr != NULL; nsPtr = nsPtr->next) {
  597. // default namespace is nil in the tree, but there's no way to
  598. // register a default namespace, so we'll register a fake one,
  599. // _def_ns
  600. const xmlChar* prefix = nsPtr->prefix;
  601. if (prefix == NULL) {
  602. prefix = (xmlChar*) kGDataXMLXPathDefaultNamespacePrefix;
  603. }
  604. int result = xmlXPathRegisterNs(xpathCtx, prefix, nsPtr->href);
  605. if (result != 0) {
  606. #if DEBUG
  607. NSCAssert1(result == 0, @"GDataXMLNode XPath namespace %s issue",
  608. prefix);
  609. #endif
  610. }
  611. }
  612. }
  613. }
  614. // now evaluate the path
  615. xmlXPathObjectPtr xpathObj;
  616. xpathObj = xmlXPathEval(GDataGetXMLString(xpath), xpathCtx);
  617. if (xpathObj) {
  618. // we have some result from the search
  619. array = [NSMutableArray array];
  620. xmlNodeSetPtr nodeSet = xpathObj->nodesetval;
  621. if (nodeSet) {
  622. // add each node in the result set to our array
  623. for (int index = 0; index < nodeSet->nodeNr; index++) {
  624. xmlNodePtr currNode = nodeSet->nodeTab[index];
  625. GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:currNode];
  626. if (node) {
  627. [array addObject:node];
  628. }
  629. }
  630. }
  631. xmlXPathFreeObject(xpathObj);
  632. } else {
  633. // provide an error for failed evaluation
  634. const char *msg = xpathCtx->lastError.str1;
  635. errorCode = xpathCtx->lastError.code;
  636. if (msg) {
  637. NSString *errStr = [NSString stringWithUTF8String:msg];
  638. errorInfo = [NSDictionary dictionaryWithObject:errStr
  639. forKey:@"error"];
  640. }
  641. }
  642. xmlXPathFreeContext(xpathCtx);
  643. }
  644. } else {
  645. // not a valid node for using XPath
  646. errorInfo = [NSDictionary dictionaryWithObject:@"invalid node"
  647. forKey:@"error"];
  648. }
  649. if (array == nil && error != nil) {
  650. *error = [NSError errorWithDomain:@"com.google.GDataXML"
  651. code:errorCode
  652. userInfo:errorInfo];
  653. }
  654. if (tempDoc != NULL) {
  655. xmlUnlinkNode(topParent);
  656. xmlSetTreeDoc(topParent, NULL);
  657. xmlFreeDoc(tempDoc);
  658. }
  659. return array;
  660. }
  661. - (NSString *)description {
  662. int nodeType = (xmlNode_ ? (int)xmlNode_->type : -1);
  663. return [NSString stringWithFormat:@"%@ %p: {type:%d name:%@ xml:\"%@\"}",
  664. [self class], self, nodeType, [self name], [self XMLString]];
  665. }
  666. - (id)copyWithZone:(NSZone *)zone {
  667. xmlNodePtr nodeCopy = [self XMLNodeCopy];
  668. if (nodeCopy != NULL) {
  669. return [[[self class] alloc] initConsumingXMLNode:nodeCopy];
  670. }
  671. return nil;
  672. }
  673. - (BOOL)isEqual:(GDataXMLNode *)other {
  674. if (self == other) return YES;
  675. if (![other isKindOfClass:[GDataXMLNode class]]) return NO;
  676. return [self XMLNode] == [other XMLNode]
  677. || ([self kind] == [other kind]
  678. && AreEqualOrBothNilPrivate([self name], [other name])
  679. && [[self children] count] == [[other children] count]);
  680. }
  681. - (NSUInteger)hash {
  682. return (NSUInteger) (__bridge void *) [GDataXMLNode class];
  683. }
  684. - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
  685. return [super methodSignatureForSelector:selector];
  686. }
  687. #pragma mark -
  688. - (xmlNodePtr)XMLNodeCopy {
  689. if (xmlNode_ != NULL) {
  690. // Note: libxml will create a new copy of namespace nodes (xmlNs records)
  691. // and attach them to this copy in order to keep namespaces within this
  692. // node subtree copy value.
  693. xmlNodePtr nodeCopy = xmlCopyNode(xmlNode_, 1); // 1 = recursive
  694. return nodeCopy;
  695. }
  696. return NULL;
  697. }
  698. - (xmlNodePtr)XMLNode {
  699. return xmlNode_;
  700. }
  701. - (BOOL)shouldFreeXMLNode {
  702. return shouldFreeXMLNode_;
  703. }
  704. - (void)setShouldFreeXMLNode:(BOOL)flag {
  705. shouldFreeXMLNode_ = flag;
  706. }
  707. @end
  708. @implementation GDataXMLElement
  709. - (id)initWithXMLString:(NSString *)str error:(NSError **)error {
  710. self = [super init];
  711. if (self) {
  712. const char *utf8Str = [str UTF8String];
  713. // NOTE: We are assuming a string length that fits into an int
  714. xmlDocPtr doc = xmlReadMemory(utf8Str, (int)strlen(utf8Str), NULL, // URL
  715. NULL, // encoding
  716. kGDataXMLParseOptions);
  717. if (doc == NULL) {
  718. if (error) {
  719. // TODO(grobbins) use xmlSetGenericErrorFunc to capture error
  720. }
  721. } else {
  722. // copy the root node from the doc
  723. xmlNodePtr root = xmlDocGetRootElement(doc);
  724. if (root) {
  725. xmlNode_ = xmlCopyNode(root, 1); // 1: recursive
  726. shouldFreeXMLNode_ = YES;
  727. }
  728. xmlFreeDoc(doc);
  729. }
  730. if (xmlNode_ == NULL) {
  731. // failure
  732. if (error) {
  733. *error = [NSError errorWithDomain:@"com.google.GDataXML"
  734. code:-1
  735. userInfo:nil];
  736. }
  737. return nil;
  738. }
  739. }
  740. return self;
  741. }
  742. - (id)initWithHTMLString:(NSString *)str error:(NSError **)error {
  743. self = [super init];
  744. if (self) {
  745. const char *utf8Str = [str UTF8String];
  746. // NOTE: We are assuming a string length that fits into an int
  747. xmlDocPtr doc = htmlReadMemory(utf8Str, (int)strlen(utf8Str), NULL, // URL
  748. NULL, // encoding
  749. kGDataHTMLParseOptions);
  750. if (doc == NULL) {
  751. if (error) {
  752. // TODO(grobbins) use xmlSetGenericErrorFunc to capture error
  753. }
  754. } else {
  755. // copy the root node from the doc
  756. xmlNodePtr root = xmlDocGetRootElement(doc);
  757. if (root) {
  758. xmlNode_ = xmlCopyNode(root, 1); // 1: recursive
  759. shouldFreeXMLNode_ = YES;
  760. }
  761. xmlFreeDoc(doc);
  762. }
  763. if (xmlNode_ == NULL) {
  764. // failure
  765. if (error) {
  766. *error = [NSError errorWithDomain:@"com.google.GDataXML"
  767. code:-1
  768. userInfo:nil];
  769. }
  770. return nil;
  771. }
  772. }
  773. return self;
  774. }
  775. - (NSArray *)namespaces {
  776. NSMutableArray *array = nil;
  777. if (xmlNode_ != NULL && xmlNode_->nsDef != NULL) {
  778. xmlNsPtr currNS = xmlNode_->nsDef;
  779. while (currNS != NULL) {
  780. // add this prefix/URI to the list, unless it's the implicit xml prefix
  781. if (!xmlStrEqual(currNS->prefix, (const xmlChar *) "xml")) {
  782. GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:(xmlNodePtr) currNS];
  783. if (array == nil) {
  784. array = [NSMutableArray arrayWithObject:node];
  785. } else {
  786. [array addObject:node];
  787. }
  788. }
  789. currNS = currNS->next;
  790. }
  791. }
  792. return array;
  793. }
  794. - (void)setNamespaces:(NSArray *)namespaces {
  795. if (xmlNode_ != NULL) {
  796. [self releaseCachedValues];
  797. // remove previous namespaces
  798. if (xmlNode_->nsDef) {
  799. xmlFreeNsList(xmlNode_->nsDef);
  800. xmlNode_->nsDef = NULL;
  801. }
  802. // add a namespace for each object in the array
  803. NSEnumerator *enumerator = [namespaces objectEnumerator];
  804. GDataXMLNode *namespaceNode;
  805. while ((namespaceNode = [enumerator nextObject]) != nil) {
  806. xmlNsPtr ns = (xmlNsPtr) [namespaceNode XMLNode];
  807. if (ns) {
  808. (void)xmlNewNs(xmlNode_, ns->href, ns->prefix);
  809. }
  810. }
  811. // we may need to fix this node's own name; the graft point is where
  812. // the namespace search starts, so that points to this node too
  813. [[self class] fixUpNamespacesForNode:xmlNode_
  814. graftingToTreeNode:xmlNode_];
  815. }
  816. }
  817. - (void)addNamespace:(GDataXMLNode *)aNamespace {
  818. if (xmlNode_ != NULL) {
  819. [self releaseCachedValues];
  820. xmlNsPtr ns = (xmlNsPtr) [aNamespace XMLNode];
  821. if (ns) {
  822. (void)xmlNewNs(xmlNode_, ns->href, ns->prefix);
  823. // we may need to fix this node's own name; the graft point is where
  824. // the namespace search starts, so that points to this node too
  825. [[self class] fixUpNamespacesForNode:xmlNode_
  826. graftingToTreeNode:xmlNode_];
  827. }
  828. }
  829. }
  830. - (void)addChild:(GDataXMLNode *)child {
  831. if ([child kind] == GDataXMLAttributeKind) {
  832. [self addAttribute:child];
  833. return;
  834. }
  835. if (xmlNode_ != NULL) {
  836. [self releaseCachedValues];
  837. xmlNodePtr childNodeCopy = [child XMLNodeCopy];
  838. if (childNodeCopy) {
  839. xmlNodePtr resultNode = xmlAddChild(xmlNode_, childNodeCopy);
  840. if (resultNode == NULL) {
  841. // failed to add
  842. xmlFreeNode(childNodeCopy);
  843. } else {
  844. // added this child subtree successfully; see if it has
  845. // previously-unresolved namespace prefixes that can now be fixed up
  846. [[self class] fixUpNamespacesForNode:childNodeCopy
  847. graftingToTreeNode:xmlNode_];
  848. }
  849. }
  850. }
  851. }
  852. - (void)removeChild:(GDataXMLNode *)child {
  853. // this is safe for attributes too
  854. if (xmlNode_ != NULL) {
  855. [self releaseCachedValues];
  856. xmlNodePtr node = [child XMLNode];
  857. xmlUnlinkNode(node);
  858. // if the child node was borrowing its xmlNodePtr, then we need to
  859. // explicitly free it, since there is probably no owning object that will
  860. // free it on dealloc
  861. if (![child shouldFreeXMLNode]) {
  862. xmlFreeNode(node);
  863. }
  864. }
  865. }
  866. - (NSArray *)elementsForName:(NSString *)name {
  867. NSString *desiredName = name;
  868. if (xmlNode_ != NULL) {
  869. NSString *prefix = [[self class] prefixForName:desiredName];
  870. if (prefix) {
  871. xmlChar* desiredPrefix = GDataGetXMLString(prefix);
  872. xmlNsPtr foundNS = xmlSearchNs(xmlNode_->doc, xmlNode_, desiredPrefix);
  873. if (foundNS) {
  874. // we found a namespace; fall back on elementsForLocalName:URI:
  875. // to get the elements
  876. NSString *desiredURI = [self stringFromXMLString:(foundNS->href)];
  877. NSString *localName = [[self class] localNameForName:desiredName];
  878. NSArray *nsArray = [self elementsForLocalName:localName URI:desiredURI];
  879. return nsArray;
  880. }
  881. }
  882. // no namespace found for the node's prefix; try an exact match
  883. // for the name argument, including any prefix
  884. NSMutableArray *array = nil;
  885. // walk our list of cached child nodes
  886. NSArray *children = [self children];
  887. for (GDataXMLNode *child in children) {
  888. xmlNodePtr currNode = [child XMLNode];
  889. // find all children which are elements with the desired name
  890. if (currNode->type == XML_ELEMENT_NODE) {
  891. NSString *qName = [child name];
  892. if ([qName isEqual:name]) {
  893. if (array == nil) {
  894. array = [NSMutableArray arrayWithObject:child];
  895. } else {
  896. [array addObject:child];
  897. }
  898. }
  899. }
  900. }
  901. return array;
  902. }
  903. return nil;
  904. }
  905. - (NSArray *)elementsForLocalName:(NSString *)localName URI:(NSString *)URI {
  906. NSMutableArray *array = nil;
  907. if (xmlNode_ != NULL && xmlNode_->children != NULL) {
  908. xmlChar* desiredNSHref = GDataGetXMLString(URI);
  909. xmlChar* requestedLocalName = GDataGetXMLString(localName);
  910. xmlChar* expectedLocalName = requestedLocalName;
  911. // resolve the URI at the parent level, since usually children won't
  912. // have their own namespace definitions, and we don't want to try to
  913. // resolve it once for every child
  914. xmlNsPtr foundParentNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, desiredNSHref);
  915. if (foundParentNS == NULL) {
  916. NSString *fakeQName = GDataFakeQNameForURIAndName(URI, localName);
  917. expectedLocalName = GDataGetXMLString(fakeQName);
  918. }
  919. NSArray *children = [self children];
  920. for (GDataXMLNode *child in children) {
  921. xmlNodePtr currChildPtr = [child XMLNode];
  922. // find all children which are elements with the desired name and
  923. // namespace, or with the prefixed name and a null namespace
  924. if (currChildPtr->type == XML_ELEMENT_NODE) {
  925. // normally, we can assume the resolution done for the parent will apply
  926. // to the child, as most children do not define their own namespaces
  927. xmlNsPtr childLocalNS = foundParentNS;
  928. xmlChar* childDesiredLocalName = expectedLocalName;
  929. if (currChildPtr->nsDef != NULL) {
  930. // this child has its own namespace definitons; do a fresh resolve
  931. // of the namespace starting from the child, and see if it differs
  932. // from the resolve done starting from the parent. If the resolve
  933. // finds a different namespace, then override the desired local
  934. // name just for this child.
  935. childLocalNS = xmlSearchNsByHref(xmlNode_->doc, currChildPtr, desiredNSHref);
  936. if (childLocalNS != foundParentNS) {
  937. // this child does indeed have a different namespace resolution
  938. // result than was found for its parent
  939. if (childLocalNS == NULL) {
  940. // no namespace found
  941. NSString *fakeQName = GDataFakeQNameForURIAndName(URI, localName);
  942. childDesiredLocalName = GDataGetXMLString(fakeQName);
  943. } else {
  944. // a namespace was found; use the original local name requested,
  945. // not a faked one expected from resolving the parent
  946. childDesiredLocalName = requestedLocalName;
  947. }
  948. }
  949. }
  950. // check if this child's namespace and local name are what we're
  951. // seeking
  952. if (currChildPtr->ns == childLocalNS
  953. && currChildPtr->name != NULL
  954. && xmlStrEqual(currChildPtr->name, childDesiredLocalName)) {
  955. if (array == nil) {
  956. array = [NSMutableArray arrayWithObject:child];
  957. } else {
  958. [array addObject:child];
  959. }
  960. }
  961. }
  962. }
  963. // we return nil, not an empty array, according to docs
  964. }
  965. return array;
  966. }
  967. - (NSArray *)attributes {
  968. if (cachedAttributes_ != nil) {
  969. return cachedAttributes_;
  970. }
  971. NSMutableArray *array = nil;
  972. if (xmlNode_ != NULL && xmlNode_->properties != NULL) {
  973. xmlAttrPtr prop = xmlNode_->properties;
  974. while (prop != NULL) {
  975. GDataXMLNode *node = [GDataXMLNode nodeBorrowingXMLNode:(xmlNodePtr) prop];
  976. if (array == nil) {
  977. array = [NSMutableArray arrayWithObject:node];
  978. } else {
  979. [array addObject:node];
  980. }
  981. prop = prop->next;
  982. }
  983. cachedAttributes_ = array;
  984. }
  985. return array;
  986. }
  987. - (void)addAttribute:(GDataXMLNode *)attribute {
  988. if (xmlNode_ != NULL) {
  989. [self releaseCachedValues];
  990. xmlAttrPtr attrPtr = (xmlAttrPtr) [attribute XMLNode];
  991. if (attrPtr) {
  992. // ignore this if an attribute with the name is already present,
  993. // similar to NSXMLNode's addAttribute
  994. xmlAttrPtr oldAttr;
  995. if (attrPtr->ns == NULL) {
  996. oldAttr = xmlHasProp(xmlNode_, attrPtr->name);
  997. } else {
  998. oldAttr = xmlHasNsProp(xmlNode_, attrPtr->name, attrPtr->ns->href);
  999. }
  1000. if (oldAttr == NULL) {
  1001. xmlNsPtr newPropNS = NULL;
  1002. // if this attribute has a namespace, search for a matching namespace
  1003. // on the node we're adding to
  1004. if (attrPtr->ns != NULL) {
  1005. newPropNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, attrPtr->ns->href);
  1006. if (newPropNS == NULL) {
  1007. // make a new namespace on the parent node, and use that for the
  1008. // new attribute
  1009. newPropNS = xmlNewNs(xmlNode_, attrPtr->ns->href, attrPtr->ns->prefix);
  1010. }
  1011. }
  1012. // copy the attribute onto this node
  1013. xmlChar *value = xmlNodeGetContent((xmlNodePtr) attrPtr);
  1014. xmlAttrPtr newProp = xmlNewNsProp(xmlNode_, newPropNS, attrPtr->name, value);
  1015. if (newProp != NULL) {
  1016. // we made the property, so clean up the property's namespace
  1017. [[self class] fixUpNamespacesForNode:(xmlNodePtr)newProp
  1018. graftingToTreeNode:xmlNode_];
  1019. }
  1020. if (value != NULL) {
  1021. xmlFree(value);
  1022. }
  1023. }
  1024. }
  1025. }
  1026. }
  1027. - (GDataXMLNode *)attributeForXMLNode:(xmlAttrPtr)theXMLNode {
  1028. // search the cached attributes list for the GDataXMLNode with
  1029. // the underlying xmlAttrPtr
  1030. NSArray *attributes = [self attributes];
  1031. for (GDataXMLNode *attr in attributes) {
  1032. if (theXMLNode == (xmlAttrPtr) [attr XMLNode]) {
  1033. return attr;
  1034. }
  1035. }
  1036. return nil;
  1037. }
  1038. - (GDataXMLNode *)attributeForName:(NSString *)name {
  1039. if (xmlNode_ != NULL) {
  1040. xmlAttrPtr attrPtr = xmlHasProp(xmlNode_, GDataGetXMLString(name));
  1041. if (attrPtr == NULL) {
  1042. // can we guarantee that xmlAttrPtrs always have the ns ptr and never
  1043. // a namespace as part of the actual attribute name?
  1044. // split the name and its prefix, if any
  1045. xmlNsPtr ns = NULL;
  1046. NSString *prefix = [[self class] prefixForName:name];
  1047. if (prefix) {
  1048. // find the namespace for this prefix, and search on its URI to find
  1049. // the xmlNsPtr
  1050. name = [[self class] localNameForName:name];
  1051. ns = xmlSearchNs(xmlNode_->doc, xmlNode_, GDataGetXMLString(prefix));
  1052. }
  1053. const xmlChar* nsURI = ((ns != NULL) ? ns->href : NULL);
  1054. attrPtr = xmlHasNsProp(xmlNode_, GDataGetXMLString(name), nsURI);
  1055. }
  1056. if (attrPtr) {
  1057. GDataXMLNode *attr = [self attributeForXMLNode:attrPtr];
  1058. return attr;
  1059. }
  1060. }
  1061. return nil;
  1062. }
  1063. - (GDataXMLNode *)attributeForLocalName:(NSString *)localName
  1064. URI:(NSString *)attributeURI {
  1065. if (xmlNode_ != NULL) {
  1066. const xmlChar* name = GDataGetXMLString(localName);
  1067. const xmlChar* nsURI = GDataGetXMLString(attributeURI);
  1068. xmlAttrPtr attrPtr = xmlHasNsProp(xmlNode_, name, nsURI);
  1069. if (attrPtr == NULL) {
  1070. // if the attribute is in a tree lacking the proper namespace,
  1071. // the local name may include the full URI as a prefix
  1072. NSString *fakeQName = GDataFakeQNameForURIAndName(attributeURI, localName);
  1073. const xmlChar* xmlFakeQName = GDataGetXMLString(fakeQName);
  1074. attrPtr = xmlHasProp(xmlNode_, xmlFakeQName);
  1075. }
  1076. if (attrPtr) {
  1077. GDataXMLNode *attr = [self attributeForXMLNode:attrPtr];
  1078. return attr;
  1079. }
  1080. }
  1081. return nil;
  1082. }
  1083. - (NSString *)resolvePrefixForNamespaceURI:(NSString *)namespaceURI {
  1084. if (xmlNode_ != NULL) {
  1085. xmlChar* desiredNSHref = GDataGetXMLString(namespaceURI);
  1086. xmlNsPtr foundNS = xmlSearchNsByHref(xmlNode_->doc, xmlNode_, desiredNSHref);
  1087. if (foundNS) {
  1088. // we found the namespace
  1089. if (foundNS->prefix != NULL) {
  1090. NSString *prefix = [self stringFromXMLString:(foundNS->prefix)];
  1091. return prefix;
  1092. } else {
  1093. // empty prefix is default namespace
  1094. return @"";
  1095. }
  1096. }
  1097. }
  1098. return nil;
  1099. }
  1100. #pragma mark Namespace fixup routines
  1101. + (void)deleteNamespacePtr:(xmlNsPtr)namespaceToDelete
  1102. fromXMLNode:(xmlNodePtr)node {
  1103. // utilty routine to remove a namespace pointer from an element's
  1104. // namespace definition list. This is just removing the nsPtr
  1105. // from the singly-linked list, the node's namespace definitions.
  1106. xmlNsPtr currNS = node->nsDef;
  1107. xmlNsPtr prevNS = NULL;
  1108. while (currNS != NULL) {
  1109. xmlNsPtr nextNS = currNS->next;
  1110. if (namespaceToDelete == currNS) {
  1111. // found it; delete it from the head of the node's ns definition list
  1112. // or from the next field of the previous namespace
  1113. if (prevNS != NULL) prevNS->next = nextNS;
  1114. else node->nsDef = nextNS;
  1115. xmlFreeNs(currNS);
  1116. return;
  1117. }
  1118. prevNS = currNS;
  1119. currNS = nextNS;
  1120. }
  1121. }
  1122. + (void)fixQualifiedNamesForNode:(xmlNodePtr)nodeToFix
  1123. graftingToTreeNode:(xmlNodePtr)graftPointNode {
  1124. // Replace prefix-in-name with proper namespace pointers
  1125. //
  1126. // This is an inner routine for fixUpNamespacesForNode:
  1127. //
  1128. // see if this node's name lacks a namespace and is qualified, and if so,
  1129. // see if we can resolve the prefix against the parent
  1130. //
  1131. // The prefix may either be normal, "gd:foo", or a URI
  1132. // "{http://blah.com/}:foo"
  1133. if (nodeToFix->ns == NULL) {
  1134. xmlNsPtr foundNS = NULL;
  1135. xmlChar* prefix = NULL;
  1136. xmlChar* localName = SplitQNameReverse(nodeToFix->name, &prefix);
  1137. if (localName != NULL) {
  1138. if (prefix != NULL) {
  1139. // if the prefix is wrapped by { and } then it's a URI
  1140. int prefixLen = xmlStrlen(prefix);
  1141. if (prefixLen > 2
  1142. && prefix[0] == '{'
  1143. && prefix[prefixLen - 1] == '}') {
  1144. // search for the namespace by URI
  1145. xmlChar* uri = xmlStrsub(prefix, 1, prefixLen - 2);
  1146. if (uri != NULL) {
  1147. foundNS = xmlSearchNsByHref(graftPointNode->doc, graftPointNode, uri);
  1148. xmlFree(uri);
  1149. }
  1150. }
  1151. }
  1152. if (foundNS == NULL) {
  1153. // search for the namespace by prefix, even if the prefix is nil
  1154. // (nil prefix means to search for the default namespace)
  1155. foundNS = xmlSearchNs(graftPointNode->doc, graftPointNode, prefix);
  1156. }
  1157. if (foundNS != NULL) {
  1158. // we found a namespace, so fix the ns pointer and the local name
  1159. xmlSetNs(nodeToFix, foundNS);
  1160. xmlNodeSetName(nodeToFix, localName);
  1161. }
  1162. if (prefix != NULL) {
  1163. xmlFree(prefix);
  1164. prefix = NULL;
  1165. }
  1166. xmlFree(localName);
  1167. }
  1168. }
  1169. }
  1170. + (void)fixDuplicateNamespacesForNode:(xmlNodePtr)nodeToFix
  1171. graftingToTreeNode:(xmlNodePtr)graftPointNode
  1172. namespaceSubstitutionMap:(NSMutableDictionary *)nsMap {
  1173. // Duplicate namespace removal
  1174. //
  1175. // This is an inner routine for fixUpNamespacesForNode:
  1176. //
  1177. // If any of this node's namespaces are already defined at the graft point
  1178. // level, add that namespace to the map of namespace substitutions
  1179. // so it will be replaced in the children below the nodeToFix, and
  1180. // delete the namespace record
  1181. if (nodeToFix->type == XML_ELEMENT_NODE) {
  1182. // step through the namespaces defined on this node
  1183. xmlNsPtr definedNS = nodeToFix->nsDef;
  1184. while (definedNS != NULL) {
  1185. // see if this namespace is already defined higher in the tree,
  1186. // with both the same URI and the same prefix; if so, add a mapping for
  1187. // it
  1188. xmlNsPtr foundNS = xmlSearchNsByHref(graftPointNode->doc, graftPointNode,
  1189. definedNS->href);
  1190. if (foundNS != NULL
  1191. && foundNS != definedNS
  1192. && xmlStrEqual(definedNS->prefix, foundNS->prefix)) {
  1193. // store a mapping from this defined nsPtr to the one found higher
  1194. // in the tree
  1195. [nsMap setObject:[NSValue valueWithPointer:foundNS]
  1196. forKey:[NSValue valueWithPointer:definedNS]];
  1197. // remove this namespace from the ns definition list of this node;
  1198. // all child elements and attributes referencing this namespace
  1199. // now have a dangling pointer and must be updated (that is done later
  1200. // in this method)
  1201. //
  1202. // before we delete this namespace, move our pointer to the
  1203. // next one
  1204. xmlNsPtr nsToDelete = definedNS;
  1205. definedNS = definedNS->next;
  1206. [self deleteNamespacePtr:nsToDelete fromXMLNode:nodeToFix];
  1207. } else {
  1208. // this namespace wasn't a duplicate; move to the next
  1209. definedNS = definedNS->next;
  1210. }
  1211. }
  1212. }
  1213. // if this node's namespace is one we deleted, update it to point
  1214. // to someplace better
  1215. if (nodeToFix->ns != NULL) {
  1216. NSValue *currNS = [NSValue valueWithPointer:nodeToFix->ns];
  1217. NSValue *replacementNS = [nsMap objectForKey:currNS];
  1218. if (replacementNS != nil) {
  1219. xmlNsPtr replaceNSPtr = (xmlNsPtr)[replacementNS pointerValue];
  1220. xmlSetNs(nodeToFix, replaceNSPtr);
  1221. }
  1222. }
  1223. }
  1224. + (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix
  1225. graftingToTreeNode:(xmlNodePtr)graftPointNode
  1226. namespaceSubstitutionMap:(NSMutableDictionary *)nsMap {
  1227. // This is the inner routine for fixUpNamespacesForNode:graftingToTreeNode:
  1228. //
  1229. // This routine fixes two issues:
  1230. //
  1231. // Because we can create nodes with qualified names before adding
  1232. // them to the tree that declares the namespace for the prefix,
  1233. // we need to set the node namespaces after adding them to the tree.
  1234. //
  1235. // Because libxml adds namespaces to nodes when it copies them,
  1236. // we want to remove redundant namespaces after adding them to
  1237. // a tree.
  1238. //
  1239. // If only the Mac's libxml had xmlDOMWrapReconcileNamespaces, it could do
  1240. // namespace cleanup for us
  1241. // We only care about fixing names of elements and attributes
  1242. if (nodeToFix->type != XML_ELEMENT_NODE
  1243. && nodeToFix->type != XML_ATTRIBUTE_NODE) return;
  1244. // Do the fixes
  1245. [self fixQualifiedNamesForNode:nodeToFix
  1246. graftingToTreeNode:graftPointNode];
  1247. [self fixDuplicateNamespacesForNode:nodeToFix
  1248. graftingToTreeNode:graftPointNode
  1249. namespaceSubstitutionMap:nsMap];
  1250. if (nodeToFix->type == XML_ELEMENT_NODE) {
  1251. // when fixing element nodes, recurse for each child element and
  1252. // for each attribute
  1253. xmlNodePtr currChild = nodeToFix->children;
  1254. while (currChild != NULL) {
  1255. [self fixUpNamespacesForNode:currChild
  1256. graftingToTreeNode:graftPointNode
  1257. namespaceSubstitutionMap:nsMap];
  1258. currChild = currChild->next;
  1259. }
  1260. xmlAttrPtr currProp = nodeToFix->properties;
  1261. while (currProp != NULL) {
  1262. [self fixUpNamespacesForNode:(xmlNodePtr)currProp
  1263. graftingToTreeNode:graftPointNode
  1264. namespaceSubstitutionMap:nsMap];
  1265. currProp = currProp->next;
  1266. }
  1267. }
  1268. }
  1269. + (void)fixUpNamespacesForNode:(xmlNodePtr)nodeToFix
  1270. graftingToTreeNode:(xmlNodePtr)graftPointNode {
  1271. // allocate the namespace map that will be passed
  1272. // down on recursive calls
  1273. NSMutableDictionary *nsMap = [NSMutableDictionary dictionary];
  1274. [self fixUpNamespacesForNode:nodeToFix
  1275. graftingToTreeNode:graftPointNode
  1276. namespaceSubstitutionMap:nsMap];
  1277. }
  1278. @end
  1279. @interface GDataXMLDocument (PrivateMethods)
  1280. - (void)addStringsCacheToDoc;
  1281. const char *IANAEncodingCStringFromNSStringEncoding(NSStringEncoding encoding);
  1282. @end
  1283. @implementation GDataXMLDocument
  1284. const char *IANAEncodingCStringFromNSStringEncoding(NSStringEncoding encoding)
  1285. {
  1286. CFStringEncoding cfEncoding = CFStringConvertNSStringEncodingToEncoding(encoding);
  1287. CFStringRef ianaCharacterSetName = CFStringConvertEncodingToIANACharSetName(cfEncoding);
  1288. // To avoid brainfuck with encoding of the encoding string, let's just use NSString convenience method
  1289. return [(__bridge NSString*)ianaCharacterSetName UTF8String];
  1290. // const char *cIanaCharacterSetName = NULL;
  1291. //
  1292. // cIanaCharacterSetName = CFStringGetCStringPtr(ianaCharacterSetName, kCFStringEncodingMacRoman);
  1293. //
  1294. // if (cIanaCharacterSetName == NULL) {
  1295. // CFStringGetCString(ianaCharacterSetName, cIanaCharacterSetName, CFStringGetLength(ianaCharacterSetName), kCFStringEncodingMacRoman);
  1296. // }
  1297. //
  1298. // return cIanaCharacterSetName;
  1299. }
  1300. - (id)initWithXMLString:(NSString *)str error:(NSError **)error
  1301. {
  1302. return [self initWithXMLString:str encoding:NSUTF8StringEncoding error:error];
  1303. }
  1304. - (id)initWithData:(NSData *)data error:(NSError **)error
  1305. {
  1306. return [self initWithData:data encoding:NSUTF8StringEncoding error:error];
  1307. }
  1308. - (id)initWithHTMLString:(NSString *)str error:(NSError **)error
  1309. {
  1310. return [self initWithHTMLString:str encoding:NSUTF8StringEncoding error:error];
  1311. }
  1312. - (id)initWithHTMLData:(NSData *)data error:(NSError **)error
  1313. {
  1314. return [self initWithHTMLData:data encoding:NSUTF8StringEncoding error:error];
  1315. }
  1316. - (id)initWithXMLString:(NSString *)str encoding:(NSStringEncoding)encoding error:(NSError **)error {
  1317. NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
  1318. GDataXMLDocument *doc = [self initWithData:data encoding:encoding error:error];
  1319. return doc;
  1320. }
  1321. - (id)initWithHTMLString:(NSString *)str encoding:(NSStringEncoding)encoding error:(NSError **)error {
  1322. NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
  1323. GDataXMLDocument *doc = [self initWithHTMLData:data encoding:encoding error:error];
  1324. return doc;
  1325. }
  1326. - (id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding error:(NSError **)error {
  1327. self = [super init];
  1328. if (!self) {
  1329. return nil;
  1330. }
  1331. _encoding = encoding;
  1332. const char *baseURL = NULL;
  1333. const char *xmlEncoding = IANAEncodingCStringFromNSStringEncoding(encoding);
  1334. // NOTE: We are assuming [data length] fits into an int.
  1335. xmlDoc_ = xmlReadMemory((const char*)[data bytes], (int)[data length], baseURL, xmlEncoding,
  1336. kGDataXMLParseOptions); // TODO(grobbins) map option values
  1337. if (xmlDoc_ == NULL) {
  1338. if (error) {
  1339. *error = [NSError errorWithDomain:@"com.google.GDataXML"
  1340. code:-1
  1341. userInfo:nil];
  1342. // TODO(grobbins) use xmlSetGenericErrorFunc to capture error
  1343. }
  1344. return nil;
  1345. } else {
  1346. if (error) *error = NULL;
  1347. [self addStringsCacheToDoc];
  1348. }
  1349. return self;
  1350. }
  1351. - (id)initWithHTMLData:(NSData *)data encoding:(NSStringEncoding)encoding error:(NSError **)error {
  1352. self = [super init];
  1353. if (!self) {
  1354. return nil;
  1355. }
  1356. const char *baseURL = NULL;
  1357. _encoding = encoding;
  1358. const char *xmlEncoding = IANAEncodingCStringFromNSStringEncoding(encoding);
  1359. xmlDoc_ = htmlReadMemory((const char*)[data bytes], (int)[data length], baseURL, xmlEncoding, kGDataHTMLParseOptions);
  1360. if (xmlDoc_ == NULL) {
  1361. if (error) {
  1362. *error = [NSError errorWithDomain:@"com.google.GDataXML"
  1363. code:-1
  1364. userInfo:nil];
  1365. // TODO(grobbins) use xmlSetGenericErrorFunc to capture error
  1366. }
  1367. return nil;
  1368. } else {
  1369. if (error) *error = NULL;
  1370. [self addStringsCacheToDoc];
  1371. }
  1372. return self;
  1373. }
  1374. - (id)initWithRootElement:(GDataXMLElement *)element {
  1375. self = [super init];
  1376. if (self) {
  1377. xmlDoc_ = xmlNewDoc(NULL);
  1378. (void) xmlDocSetRootElement(xmlDoc_, [element XMLNodeCopy]);
  1379. [self addStringsCacheToDoc];
  1380. }
  1381. return self;
  1382. }
  1383. - (void)addStringsCacheToDoc {
  1384. // utility routine for init methods
  1385. #if DEBUG
  1386. NSCAssert(xmlDoc_ != NULL && xmlDoc_->_private == NULL,
  1387. @"GDataXMLDocument cache creation problem");
  1388. #endif
  1389. // add a strings cache as private data for the document
  1390. //
  1391. // we'll use plain C pointers (xmlChar*) as the keys, and NSStrings
  1392. // as the values
  1393. CFIndex capacity = 0; // no limit
  1394. CFDictionaryKeyCallBacks keyCallBacks = {
  1395. 0, // version
  1396. StringCacheKeyRetainCallBack,
  1397. StringCacheKeyReleaseCallBack,
  1398. StringCacheKeyCopyDescriptionCallBack,
  1399. StringCacheKeyEqualCallBack,
  1400. StringCacheKeyHashCallBack
  1401. };
  1402. CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
  1403. kCFAllocatorDefault, capacity,
  1404. &keyCallBacks, &kCFTypeDictionaryValueCallBacks);
  1405. // we'll use the user-defined _private field for our cache
  1406. xmlDoc_->_private = dict;
  1407. }
  1408. - (NSString *)description {
  1409. return [NSString stringWithFormat:@"%@ %p", [self class], self];
  1410. }
  1411. - (void)dealloc {
  1412. if (xmlDoc_ != NULL) {
  1413. // release the strings cache
  1414. //
  1415. // since it's a CF object, were anyone to use this in a GC environment,
  1416. // this would need to be released in a finalize method, too
  1417. if (xmlDoc_->_private != NULL) {
  1418. CFRelease(xmlDoc_->_private);
  1419. }
  1420. xmlFreeDoc(xmlDoc_);
  1421. }
  1422. }
  1423. #pragma mark -
  1424. - (GDataXMLElement *)rootElement {
  1425. GDataXMLElement *element = nil;
  1426. if (xmlDoc_ != NULL) {
  1427. xmlNodePtr rootNode = xmlDocGetRootElement(xmlDoc_);
  1428. if (rootNode) {
  1429. element = [GDataXMLElement nodeBorrowingXMLNode:rootNode];
  1430. }
  1431. }
  1432. return element;
  1433. }
  1434. - (NSData *)XMLData {
  1435. if (xmlDoc_ != NULL) {
  1436. xmlChar *buffer = NULL;
  1437. int bufferSize = 0;
  1438. xmlDocDumpMemory(xmlDoc_, &buffer, &bufferSize);
  1439. if (buffer) {
  1440. NSData *data = [NSData dataWithBytes:buffer
  1441. length:bufferSize];
  1442. xmlFree(buffer);
  1443. return data;
  1444. }
  1445. }
  1446. return nil;
  1447. }
  1448. - (void)setVersion:(NSString *)version {
  1449. if (xmlDoc_ != NULL) {
  1450. if (xmlDoc_->version != NULL) {
  1451. // version is a const char* so we must cast
  1452. xmlFree((char *) xmlDoc_->version);
  1453. xmlDoc_->version = NULL;
  1454. }
  1455. if (version != nil) {
  1456. xmlDoc_->version = xmlStrdup(GDataGetXMLString(version));
  1457. }
  1458. }
  1459. }
  1460. - (void)setCharacterEncoding:(NSString *)encoding {
  1461. if (xmlDoc_ != NULL) {
  1462. if (xmlDoc_->encoding != NULL) {
  1463. // version is a const char* so we must cast
  1464. xmlFree((char *) xmlDoc_->encoding);
  1465. xmlDoc_->encoding = NULL;
  1466. }
  1467. if (encoding != nil) {
  1468. xmlDoc_->encoding = xmlStrdup(GDataGetXMLString(encoding));
  1469. }
  1470. }
  1471. }
  1472. - (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error {
  1473. return [self nodesForXPath:xpath namespaces:nil error:error];
  1474. }
  1475. - (GDataXMLNode *)firstNodeForXPath:(NSString *)xpath error:(NSError **)error
  1476. {
  1477. NSArray *nodes = [self nodesForXPath:xpath error:error];
  1478. if (!nodes.count) {
  1479. return nil;
  1480. }
  1481. return [nodes objectAtIndex:0];
  1482. }
  1483. - (NSArray *)nodesForXPath:(NSString *)xpath
  1484. namespaces:(NSDictionary *)namespaces
  1485. error:(NSError **)error {
  1486. if (xmlDoc_ != NULL) {
  1487. xmlNodePtr rootElement = xmlDocGetRootElement(xmlDoc_);
  1488. if (rootElement != NULL) {
  1489. GDataXMLNode *rootNode = [GDataXMLElement nodeBorrowingXMLNode:rootElement];
  1490. NSArray *array = [rootNode nodesForXPath:xpath
  1491. namespaces:namespaces
  1492. error:error];
  1493. return array;
  1494. }
  1495. }
  1496. return nil;
  1497. }
  1498. - (GDataXMLNode *)firstNodeForXPath:(NSString *)xpath namespaces:(NSDictionary *)namespaces error:(NSError *__autoreleasing *)error
  1499. {
  1500. NSArray *nodes = [self nodesForXPath:xpath namespaces:namespaces error:error];
  1501. if (!nodes.count) {
  1502. return nil;
  1503. }
  1504. return [nodes objectAtIndex:0];
  1505. }
  1506. @end
  1507. //
  1508. // Dictionary key callbacks for our C-string to NSString cache dictionary
  1509. //
  1510. static const void *StringCacheKeyRetainCallBack(CFAllocatorRef allocator, const void *str) {
  1511. // copy the key
  1512. xmlChar* key = xmlStrdup(str);
  1513. return key;
  1514. }
  1515. static void StringCacheKeyReleaseCallBack(CFAllocatorRef allocator, const void *str) {
  1516. // free the key
  1517. char *chars = (char *)str;
  1518. xmlFree((char *) chars);
  1519. }
  1520. static CFStringRef StringCacheKeyCopyDescriptionCallBack(const void *str) {
  1521. // make a CFString from the key
  1522. CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorDefault,
  1523. (const char *)str,
  1524. kCFStringEncodingUTF8);
  1525. return cfStr;
  1526. }
  1527. static Boolean StringCacheKeyEqualCallBack(const void *str1, const void *str2) {
  1528. // compare the key strings
  1529. if (str1 == str2) return true;
  1530. int result = xmlStrcmp(str1, str2);
  1531. return (result == 0);
  1532. }
  1533. static CFHashCode StringCacheKeyHashCallBack(const void *str) {
  1534. // dhb hash, per http://www.cse.yorku.ca/~oz/hash.html
  1535. CFHashCode hash = 5381;
  1536. int c;
  1537. const char *chars = (const char *)str;
  1538. while ((c = *chars++) != 0) {
  1539. hash = ((hash << 5) + hash) + c;
  1540. }
  1541. return hash;
  1542. }