wakizashi 0.1.0

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