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.
- data/.gitignore +8 -0
- data/Gemfile +4 -0
- data/README.md +54 -0
- data/Rakefile +24 -0
- data/app/app_delegate.rb +9 -0
- data/lib/wakizashi/base.rb +13 -0
- data/lib/wakizashi/version.rb +3 -0
- data/lib/wakizashi/xml_document.rb +148 -0
- data/lib/wakizashi/xml_element.rb +45 -0
- data/lib/wakizashi/xml_node.rb +5 -0
- data/lib/wakizashi.rb +15 -0
- data/spec/wakizashi_spec.rb +28 -0
- data/spec/xml_document_spec.rb +24 -0
- data/spec/xml_element_spec.rb +16 -0
- data/spec/xml_node_spec.rb +2 -0
- data/vendor/Podfile.lock +5 -0
- data/vendor/Pods/GDataXML-HTML/.gitignore +13 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/GDataXML-HTML-Info.plist +38 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/GDataXML-HTML-Prefix.pch +14 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/GDataXML_HTMLAppDelegate.h +28 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/GDataXML_HTMLAppDelegate.m +82 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/GDataXML_HTMLViewController.h +29 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/GDataXML_HTMLViewController.m +89 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/en.lproj/GDataXML_HTMLViewController.xib +351 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/en.lproj/InfoPlist.strings +2 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/en.lproj/MainWindow.xib +444 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/html.html +34 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/lib/GDataXMLNode.h +229 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/lib/GDataXMLNode.m +1910 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/main.m +24 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML/xml.xml +15 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML.xcodeproj/project.pbxproj +317 -0
- data/vendor/Pods/GDataXML-HTML/GDataXML-HTML.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- data/vendor/Pods/GDataXML-HTML/README.markdown +55 -0
- data/vendor/Pods/Headers/GDataXML-HTML/GDataXMLNode.h +229 -0
- data/vendor/Pods/Pods-prefix.pch +3 -0
- data/vendor/Pods/Pods-resources.sh +15 -0
- data/vendor/Pods/Pods.bridgesupport +231 -0
- data/vendor/Pods/Pods.xcconfig +4 -0
- data/vendor/Pods/build-iPhoneSimulator/libPods.a +0 -0
- data/wakizashi.gemspec +18 -0
- 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
|
+
|