CXMLNode.m 10 KB
Newer Older
Pierre's avatar
Pierre committed
1 2 3 4 5
//
//  CXMLNode.m
//  TouchCode
//
//  Created by Jonathan Wight on 03/07/08.
6
//  Copyright 2011 toxicsoftware.com. All rights reserved.
Pierre's avatar
Pierre committed
7
//
8 9
//  Redistribution and use in source and binary forms, with or without modification, are
//  permitted provided that the following conditions are met:
Pierre's avatar
Pierre committed
10
//
11 12
//     1. Redistributions of source code must retain the above copyright notice, this list of
//        conditions and the following disclaimer.
Pierre's avatar
Pierre committed
13
//
14 15 16
//     2. Redistributions in binary form must reproduce the above copyright notice, this list
//        of conditions and the following disclaimer in the documentation and/or other materials
//        provided with the distribution.
Pierre's avatar
Pierre committed
17
//
18 19 20 21 22 23 24 25 26 27 28 29 30
//  THIS SOFTWARE IS PROVIDED BY TOXICSOFTWARE.COM ``AS IS'' AND ANY EXPRESS OR IMPLIED
//  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL TOXICSOFTWARE.COM OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//  The views and conclusions contained in the software and documentation are those of the
//  authors and should not be interpreted as representing official policies, either expressed
//  or implied, of toxicsoftware.com.
Pierre's avatar
Pierre committed
31 32 33 34 35 36

#import "CXMLNode.h"

#import "CXMLNode_PrivateExtensions.h"
#import "CXMLDocument.h"
#import "CXMLElement.h"
37
#import "CXMLNode_CreationExtensions.h"
Pierre's avatar
Pierre committed
38 39 40 41 42 43 44 45 46 47 48 49 50

#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/xmlIO.h>

static int MyXmlOutputWriteCallback(void * context, const char * buffer, int len);
static int MyXmlOutputCloseCallback(void * context);


@implementation CXMLNode

- (void)dealloc
{
51
[self invalidate];
Pierre's avatar
Pierre committed
52 53 54 55 56
//
}

- (id)copyWithZone:(NSZone *)zone;
{
57
#pragma unused (zone)
Pierre's avatar
Pierre committed
58 59
xmlNodePtr theNewNode = xmlCopyNode(_node, 1);
CXMLNode *theNode = [[[self class] alloc] initWithLibXMLNode:theNewNode freeOnDealloc:YES];
60
theNewNode->_private = (__bridge void *)(theNode);
Pierre's avatar
Pierre committed
61 62 63 64 65 66 67 68
return(theNode);
}

#pragma mark -

- (CXMLNodeKind)kind
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
69
return((CXMLNodeKind)_node->type); // TODO this isn't 100% accurate!
Pierre's avatar
Pierre committed
70 71 72 73
}

- (NSString *)name
{
74 75 76 77 78
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
	// TODO use xmlCheckUTF8 to check name
	if (_node->name == NULL)
		return(NULL);

79
	NSString *localName = @((const char *)_node->name);
80 81 82 83

	if (_node->ns == NULL || _node->ns->prefix == NULL)
		return localName;

84
	return [NSString stringWithFormat:@"%@:%@",	@((const char *)_node->ns->prefix), localName];
Pierre's avatar
Pierre committed
85 86 87 88
}

- (NSString *)stringValue
{
89 90 91
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

	if (_node->type == XML_TEXT_NODE || _node->type == XML_CDATA_SECTION_NODE)
92
		return @((const char *)_node->content);
93 94

	if (_node->type == XML_ATTRIBUTE_NODE)
95
		return @((const char *)_node->children->content);
96

97
	NSMutableString *theStringValue = [[NSMutableString alloc] init];
Pierre's avatar
Pierre committed
98

99
	for (CXMLNode *child in [self children])
Pierre's avatar
Pierre committed
100
	{
101
		[theStringValue appendString:[child stringValue]];
Pierre's avatar
Pierre committed
102 103
	}

104
	return theStringValue;
Pierre's avatar
Pierre committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
}

- (NSUInteger)index
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

xmlNodePtr theCurrentNode = _node->prev;
NSUInteger N;
for (N = 0; theCurrentNode != NULL; ++N, theCurrentNode = theCurrentNode->prev)
	;
return(N);
}

- (NSUInteger)level
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

xmlNodePtr theCurrentNode = _node->parent;
NSUInteger N;
for (N = 0; theCurrentNode != NULL; ++N, theCurrentNode = theCurrentNode->parent)
	;
return(N);
}

- (CXMLDocument *)rootDocument
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

133
return(__bridge CXMLDocument *)((_node->doc->_private));
Pierre's avatar
Pierre committed
134 135 136 137 138 139 140 141 142
}

- (CXMLNode *)parent
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

if (_node->parent == NULL)
	return(NULL);
else
143
	return (__bridge CXMLNode *)((_node->parent->_private));
Pierre's avatar
Pierre committed
144 145 146 147
}

- (NSUInteger)childCount
{
148
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
Pierre's avatar
Pierre committed
149

150 151 152 153 154 155 156 157
	if (_node->type == CXMLAttributeKind)
		return 0; // NSXMLNodes of type NSXMLAttributeKind can't have children

	xmlNodePtr theCurrentNode = _node->children;
	NSUInteger N;
	for (N = 0; theCurrentNode != NULL; ++N, theCurrentNode = theCurrentNode->next)
		;
	return(N);
Pierre's avatar
Pierre committed
158 159 160 161
}

- (NSArray *)children
{
162
	NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
Pierre's avatar
Pierre committed
163

164 165 166
	NSMutableArray *theChildren = [NSMutableArray array];

	if (_node->type != CXMLAttributeKind) // NSXML Attribs don't have children.
Pierre's avatar
Pierre committed
167
	{
168 169 170 171 172 173 174
		xmlNodePtr theCurrentNode = _node->children;
		while (theCurrentNode != NULL)
		{
			CXMLNode *theNode = [CXMLNode nodeWithLibXMLNode:theCurrentNode freeOnDealloc:NO];
			[theChildren addObject:theNode];
			theCurrentNode = theCurrentNode->next;
		}
Pierre's avatar
Pierre committed
175
	}
176
	return(theChildren);
Pierre's avatar
Pierre committed
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
}

- (CXMLNode *)childAtIndex:(NSUInteger)index
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

xmlNodePtr theCurrentNode = _node->children;
NSUInteger N;
for (N = 0; theCurrentNode != NULL && N != index; ++N, theCurrentNode = theCurrentNode->next)
	;
if (theCurrentNode)
	return([CXMLNode nodeWithLibXMLNode:theCurrentNode freeOnDealloc:NO]);
return(NULL);
}

- (CXMLNode *)previousSibling
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

if (_node->prev == NULL)
	return(NULL);
else
	return([CXMLNode nodeWithLibXMLNode:_node->prev freeOnDealloc:NO]);
}

- (CXMLNode *)nextSibling
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

if (_node->next == NULL)
	return(NULL);
else
	return([CXMLNode nodeWithLibXMLNode:_node->next freeOnDealloc:NO]);
}

//- (CXMLNode *)previousNode;
//- (CXMLNode *)nextNode;
//- (NSString *)XPath;

- (NSString *)localName
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");
// TODO use xmlCheckUTF8 to check name
if (_node->name == NULL)
	return(NULL);
else
223
	return(@((const char *)_node->name));
Pierre's avatar
Pierre committed
224 225 226 227
}

- (NSString *)prefix
{
228
if (_node->ns && _node->ns->prefix)
229
	return(@((const char *)_node->ns->prefix));
Pierre's avatar
Pierre committed
230
else
231
	return(@"");
Pierre's avatar
Pierre committed
232 233 234 235 236
}

- (NSString *)URI
{
if (_node->ns)
237
	return(@((const char *)_node->ns->href));
Pierre's avatar
Pierre committed
238 239 240 241
else
	return(NULL);
}

242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
+ (NSString *)localNameForName:(NSString *)name
{
	NSRange split = [name rangeOfString:@":"];

	if (split.length > 0)
		return [name substringFromIndex:split.location + 1];

	return name;
}

+ (NSString *)prefixForName:(NSString *)name
{
	NSRange split = [name rangeOfString:@":"];

	if (split.length > 0)
		return [name substringToIndex:split.location];

	return @"";
}

+ (CXMLNode *)predefinedNamespaceForPrefix:(NSString *)name
{
	if ([name isEqualToString:@"xml"])
		return [CXMLNode namespaceWithName:@"xml" stringValue:@"http://www.w3.org/XML/1998/namespace"];

	if ([name isEqualToString:@"xs"])
		return [CXMLNode namespaceWithName:@"xs" stringValue:@"http://www.w3.org/2001/XMLSchema"];

	if ([name isEqualToString:@"xsi"])
		return [CXMLNode namespaceWithName:@"xsi" stringValue:@"http://www.w3.org/2001/XMLSchema-instance"];

	if ([name isEqualToString:@"xmlns"]) // Not in Cocoa, but should be as it's reserved by W3C
		return [CXMLNode namespaceWithName:@"xmlns" stringValue:@"http://www.w3.org/2000/xmlns/"];

	return nil;
}
Pierre's avatar
Pierre committed
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292

- (NSString *)description
{
NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

return([NSString stringWithFormat:@"<%@ %p [%p] %@ %@>", NSStringFromClass([self class]), self, self->_node, [self name], [self XMLStringWithOptions:0]]);
}

- (NSString *)XMLString
{
return([self XMLStringWithOptions:0]);
}

- (NSString *)XMLStringWithOptions:(NSUInteger)options
{
293 294
#pragma unused (options)

295
NSMutableData *theData = [[NSMutableData alloc] init];
Pierre's avatar
Pierre committed
296

297
xmlOutputBufferPtr theOutputBuffer = xmlOutputBufferCreateIO(MyXmlOutputWriteCallback, MyXmlOutputCloseCallback, (__bridge void *)(theData), NULL);
Pierre's avatar
Pierre committed
298 299 300 301 302

xmlNodeDumpOutput(theOutputBuffer, _node->doc, _node, 0, 0, "utf-8");

xmlOutputBufferFlush(theOutputBuffer);

303
NSString *theString = [[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding];
Pierre's avatar
Pierre committed
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330

xmlOutputBufferClose(theOutputBuffer);

return(theString);
}
//- (NSString *)canonicalXMLStringPreservingComments:(BOOL)comments;

- (NSArray *)nodesForXPath:(NSString *)xpath error:(NSError **)error
{
#pragma unused (error)

NSAssert(_node != NULL, @"CXMLNode does not have attached libxml2 _node.");

NSArray *theResult = NULL;

xmlXPathContextPtr theXPathContext = xmlXPathNewContext(_node->doc);
theXPathContext->node = _node;

// TODO considering putting xmlChar <-> UTF8 into a NSString category
xmlXPathObjectPtr theXPathObject = xmlXPathEvalExpression((const xmlChar *)[xpath UTF8String], theXPathContext);
if (theXPathObject == NULL)
	{
	if (error)
		*error = [NSError errorWithDomain:@"TODO_DOMAIN" code:-1 userInfo:NULL];
	return(NULL);
	}
if (xmlXPathNodeSetIsEmpty(theXPathObject->nodesetval))
331
	theResult = @[]; // TODO better to return NULL?
Pierre's avatar
Pierre committed
332 333 334 335 336 337 338 339 340
else
	{
	NSMutableArray *theArray = [NSMutableArray array];
	int N;
	for (N = 0; N < theXPathObject->nodesetval->nodeNr; N++)
		{
		xmlNodePtr theNode = theXPathObject->nodesetval->nodeTab[N];
		[theArray addObject:[CXMLNode nodeWithLibXMLNode:theNode freeOnDealloc:NO]];
		}
Pierre's avatar
Pierre committed
341

Pierre's avatar
Pierre committed
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
	theResult = theArray;
	}

xmlXPathFreeObject(theXPathObject);
xmlXPathFreeContext(theXPathContext);
return(theResult);
}

//- (NSArray *)objectsForXQuery:(NSString *)xquery constants:(NSDictionary *)constants error:(NSError **)error;
//- (NSArray *)objectsForXQuery:(NSString *)xquery error:(NSError **)error;


@end

static int MyXmlOutputWriteCallback(void * context, const char * buffer, int len)
{
358
NSMutableData *theData = (__bridge NSMutableData *)(context);
Pierre's avatar
Pierre committed
359 360 361 362 363 364 365 366
[theData appendBytes:buffer length:len];
return(len);
}

static int MyXmlOutputCloseCallback(void * context)
{
return(0);
}