ObjFW  Artifact [2e27de79cd]

Artifact 2e27de79cda70b7ffc8e65aaa82f987969f9006a884e354f0873c8e2490c8b44:

  • File src/OFXMLElement.m — part of check-in [21bd46e17a] at 2011-05-14 20:00:48 on branch 0.5 — Fix namespace handling in OFXMLElement.

    Although it was originally decided not to backport this to 0.5, the
    decision was changed due to the fact that the initial assumption that
    it might break existing code is only partially true: It will only break
    code that already was broken, but it will fix all other code. (user: js, size: 16988) [annotate] [blame] [check-ins using]


/*
 * Copyright (c) 2008, 2009, 2010, 2011
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <string.h>
#include <assert.h>

#import "OFXMLElement.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFXMLAttribute.h"
#import "OFXMLParser.h"
#import "OFXMLElementBuilder.h"
#import "OFAutoreleasePool.h"

#import "OFInvalidArgumentException.h"
#import "OFMalformedXMLException.h"
#import "OFNotImplementedException.h"
#import "OFUnboundNamespaceException.h"

@interface OFXMLElement_OFXMLElementBuilderDelegate: OFObject
{
@public
	OFXMLElement *element;
}
@end

@implementation OFXMLElement_OFXMLElementBuilderDelegate
- (void)elementBuilder: (OFXMLElementBuilder*)builder
       didBuildElement: (OFXMLElement*)elem
{
	/*
	 * Make sure we don't take whitespaces before or after the root element
	 * into account.
	 */
	if ([elem name] != nil) {
		assert(element == nil);
		element = [elem retain];
	}
}

- (void)dealloc
{
	[element release];

	[super dealloc];
}
@end

@implementation OFXMLElement
+ elementWithName: (OFString*)name_
{
	return [[[self alloc] initWithName: name_] autorelease];
}

+ elementWithName: (OFString*)name
      stringValue: (OFString*)stringval
{
	return [[[self alloc] initWithName: name
			       stringValue: stringval] autorelease];
}

+ elementWithName: (OFString*)name
	namespace: (OFString*)ns
{
	return [[[self alloc] initWithName: name
				 namespace: ns] autorelease];
}

+ elementWithName: (OFString*)name
	namespace: (OFString*)ns
      stringValue: (OFString*)stringval
{
	return [[[self alloc] initWithName: name
				 namespace: ns
			       stringValue: stringval] autorelease];
}

+ elementWithCharacters: (OFString*)chars
{
	return [[[self alloc] initWithCharacters: chars] autorelease];
}

+ elementWithCDATA: (OFString*)cdata
{
	return [[[self alloc] initWithCDATA: cdata] autorelease];
}

+ elementWithComment: (OFString*)comment
{
	return [[[self alloc] initWithComment: comment] autorelease];
}

+ elementWithXMLString: (OFString*)str
{
	return [[[self alloc] initWithXMLString: str] autorelease];
}

- init
{
	Class c = isa;
	[self release];
	@throw [OFNotImplementedException newWithClass: c
					      selector: _cmd];
}

- initWithName: (OFString*)name_
{
	return [self initWithName: name_
			namespace: nil
		      stringValue: nil];
}

- initWithName: (OFString*)name_
   stringValue: (OFString*)stringval
{
	return [self initWithName: name_
			namespace: nil
		      stringValue: stringval];
}

- initWithName: (OFString*)name_
     namespace: (OFString*)ns_
{
	return [self initWithName: name_
			namespace: ns_
		      stringValue: nil];
}

- initWithName: (OFString*)name_
     namespace: (OFString*)ns_
   stringValue: (OFString*)stringval
{
	self = [super init];

	@try {
		name = [name_ copy];
		ns = [ns_ copy];

		if (stringval != nil) {
			OFAutoreleasePool *pool;

			pool = [[OFAutoreleasePool alloc] init];
			[self addChild:
			    [OFXMLElement elementWithCharacters: stringval]];
			[pool release];
		}

		namespaces = [[OFMutableDictionary alloc]
		    initWithKeysAndObjects:
		    @"http://www.w3.org/XML/1998/namespace", @"xml",
		    @"http://www.w3.org/2000/xmlns/", @"xmlns", nil];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- initWithCharacters: (OFString*)chars
{
	self = [super init];

	@try {
		characters = [chars copy];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- initWithCDATA: (OFString*)cdata_
{
	self = [super init];

	@try {
		cdata = [cdata_ copy];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- initWithComment: (OFString*)comment_
{
	self = [super init];

	@try {
		comment = [comment_ copy];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- initWithXMLString: (OFString*)str
{
	OFAutoreleasePool *pool;
	OFXMLParser *parser;
	OFXMLElementBuilder *builder;
	OFXMLElement_OFXMLElementBuilderDelegate *delegate;
	Class c;

	c = isa;
	[self release];

	pool = [[OFAutoreleasePool alloc] init];

	parser = [OFXMLParser parser];
	builder = [OFXMLElementBuilder elementBuilder];
	delegate = [[[OFXMLElement_OFXMLElementBuilderDelegate alloc] init]
	    autorelease];

	[parser setDelegate: builder];
	[builder setDelegate: delegate];

	[parser parseString: str];

	if (![parser finishedParsing])
		@throw [OFMalformedXMLException newWithClass: c
						      parser: parser];

	self = [delegate->element retain];

	@try {
		[pool release];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (OFString*)name
{
	return [[name copy] autorelease];
}

- (OFString*)namespace
{
	return [[ns copy] autorelease];
}

- (OFArray*)attributes
{
	return [[attributes copy] autorelease];
}

- (void)setChildren: (OFArray*)children_
{
	OFMutableArray *new = [children_ mutableCopy];

	@try {
		[children release];
	} @catch (id e) {
		[new release];
		@throw e;
	}

	children = new;
}

- (OFArray*)children
{
	return [[children copy] autorelease];
}

- (void)setStringValue: (OFString*)value
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];

	[self setChildren: [OFArray arrayWithObject:
	    [OFXMLElement elementWithCharacters: value]]];

	[pool release];
}

- (OFString*)stringValue
{
	OFAutoreleasePool *pool;
	OFMutableString *ret;
	OFXMLElement **children_c;
	size_t i, count = [children count];

	if (count == 0)
		return @"";

	ret = [OFMutableString string];
	children_c = [children cArray];
	pool = [[OFAutoreleasePool alloc] init];

	for (i = 0; i < count; i++) {
		if (children_c[i]->characters != nil)
			[ret appendString: children_c[i]->characters];
		else if (children_c[i]->cdata != nil)
			[ret appendString: children_c[i]->cdata];
		else if (children_c[i]->comment == nil) {
			[ret appendString: [children_c[i] stringValue]];
			[pool releaseObjects];
		}
	}

	[pool release];

	/*
	 * Class swizzle the string to be immutable. We declared the return type
	 * to be OFString*, so it can't be modified anyway. But not swizzling it
	 * would create a real copy each time -[copy] is called.
	 */
	ret->isa = [OFString class];
	return ret;
}

- (OFString*)_XMLStringWithParent: (OFXMLElement*)parent
{
	OFAutoreleasePool *pool, *pool2;
	char *str_c;
	size_t len, i, j, attrs_count;
	OFString *prefix, *parent_prefix;
	OFXMLAttribute **attrs_carray;
	OFString *ret, *tmp;
	OFMutableDictionary *all_namespaces;
	OFString *def_ns;

	if (characters != nil)
		return [characters stringByXMLEscaping];

	if (cdata != nil)
		return [OFString stringWithFormat: @"<![CDATA[%@]]>", cdata];

	if (comment != nil) {
		OFMutableString *str;

		str = [OFMutableString stringWithString: @"<!--"];
		[str appendString: comment];
		[str appendString: @"-->"];

		/*
		 * Class swizzle the string to be immutable. We declared the
		 * return type to be OFString*, so it can't be modified anyway.
		 * But not swizzling it would create a real copy each time
		 * -[copy] is called.
		 */
		str->isa = [OFString class];
		return str;
	}

	pool = [[OFAutoreleasePool alloc] init];

	if (parent != nil && parent->namespaces != nil) {
		OFEnumerator *key_enum = [namespaces keyEnumerator];
		OFEnumerator *obj_enum = [namespaces objectEnumerator];
		id key, obj;

		all_namespaces = [[parent->namespaces mutableCopy] autorelease];

		while ((key = [key_enum nextObject]) != nil &&
		    (obj = [obj_enum nextObject]) != nil)
			[all_namespaces setObject: obj
					   forKey: key];
	} else
		all_namespaces = namespaces;

	prefix = [all_namespaces objectForKey:
	    (ns != nil ? ns : (OFString*)@"")];
	parent_prefix = [all_namespaces objectForKey:
	    (parent != nil && parent->ns != nil ? parent->ns : (OFString*)@"")];

	if (parent != nil && parent->ns != nil && parent_prefix == nil)
		def_ns = parent->ns;
	else if (parent != nil && parent->defaultNamespace != nil)
		def_ns = parent->defaultNamespace;
	else
		def_ns = defaultNamespace;

	i = 0;
	len = [name cStringLength] + 3;
	str_c = [self allocMemoryWithSize: len];

	/* Start of tag */
	str_c[i++] = '<';

	if (prefix != nil && ![ns isEqual: def_ns]) {
		len += [prefix cStringLength] + 1;
		@try {
			str_c = [self resizeMemory: str_c
					    toSize: len];
		} @catch (id e) {
			[self freeMemory: str_c];
			@throw e;
		}

		memcpy(str_c + i, [prefix cString],
		    [prefix cStringLength]);
		i += [prefix cStringLength];
		str_c[i++] = ':';
	}

	memcpy(str_c + i, [name cString], [name cStringLength]);
	i += [name cStringLength];

	/* xmlns if necessary */
	if (prefix == nil && ((ns != nil && ![ns isEqual: def_ns]) ||
	    (ns == nil && def_ns != nil))) {
		len += [ns cStringLength] + 9;

		@try {
			str_c = [self resizeMemory: str_c
					    toSize: len];
		} @catch (id e) {
			[self freeMemory: str_c];
			@throw e;
		}

		memcpy(str_c + i, " xmlns='", 8);
		i += 8;
		memcpy(str_c + i, [ns cString], [ns cStringLength]);
		i += [ns cStringLength];
		str_c[i++] = '\'';
	}

	/* Attributes */
	attrs_carray = [attributes cArray];
	attrs_count = [attributes count];

	pool2 = [[OFAutoreleasePool alloc] init];
	for (j = 0; j < attrs_count; j++) {
		OFString *attr_name = [attrs_carray[j] name];
		OFString *attr_prefix = nil;
		tmp = [[attrs_carray[j] stringValue] stringByXMLEscaping];

		if ([attrs_carray[j] namespace] != nil &&
		    (attr_prefix = [all_namespaces objectForKey:
		    [attrs_carray[j] namespace]]) == nil)
			@throw [OFUnboundNamespaceException
			    newWithClass: isa
			       namespace: [attrs_carray[j] namespace]];

		len += [attr_name cStringLength] +
		    (attr_prefix != nil ? [attr_prefix cStringLength] + 1 : 0) +
		    [tmp cStringLength] + 4;

		@try {
			str_c = [self resizeMemory: str_c
					    toSize: len];
		} @catch (id e) {
			[self freeMemory: str_c];
			@throw e;
		}

		str_c[i++] = ' ';
		if (attr_prefix != nil) {
			memcpy(str_c + i, [attr_prefix cString],
			    [attr_prefix cStringLength]);
			i += [attr_prefix cStringLength];
			str_c[i++] = ':';
		}
		memcpy(str_c + i, [attr_name cString],
				[attr_name cStringLength]);
		i += [attr_name cStringLength];
		str_c[i++] = '=';
		str_c[i++] = '\'';
		memcpy(str_c + i, [tmp cString], [tmp cStringLength]);
		i += [tmp cStringLength];
		str_c[i++] = '\'';

		[pool2 releaseObjects];
	}

	/* Childen */
	if (children != nil) {
		OFXMLElement **children_carray = [children cArray];
		size_t children_count = [children count];
		IMP append;

		tmp = [OFMutableString string];
		append = [tmp methodForSelector:
		    @selector(appendCStringWithoutUTF8Checking:)];

		for (j = 0; j < children_count; j++)
			append(tmp, @selector(
			    appendCStringWithoutUTF8Checking:),
			    [[children_carray[j]
			    _XMLStringWithParent: self] cString]);

		len += [tmp cStringLength] + [name cStringLength] + 2;
		@try {
			str_c = [self resizeMemory: str_c
					    toSize: len];
		} @catch (id e) {
			[self freeMemory: str_c];
			@throw e;
		}

		str_c[i++] = '>';
		memcpy(str_c + i, [tmp cString], [tmp cStringLength]);
		i += [tmp cStringLength];
		str_c[i++] = '<';
		str_c[i++] = '/';
		if (prefix != nil) {
			len += [prefix cStringLength] + 1;
			@try {
				str_c = [self resizeMemory: str_c
						    toSize: len];
			} @catch (id e) {
				[self freeMemory: str_c];
				@throw e;
			}

			memcpy(str_c + i, [prefix cString],
			    [prefix cStringLength]);
			i += [prefix cStringLength];
			str_c[i++] = ':';
		}
		memcpy(str_c + i, [name cString], [name cStringLength]);
		i += [name cStringLength];
	} else
		str_c[i++] = '/';

	str_c[i++] = '>';
	assert(i == len);

	[pool release];

	@try {
		ret = [OFString stringWithCString: str_c
					   length: len];
	} @finally {
		[self freeMemory: str_c];
	}
	return ret;
}

- (OFString*)XMLString
{
	return [self _XMLStringWithParent: nil];
}

- (OFString*)description
{
	return [self XMLString];
}

- (void)addAttribute: (OFXMLAttribute*)attr
{
	if (name == nil)
		@throw [OFInvalidArgumentException newWithClass: isa
						       selector: _cmd];

	if (attributes == nil)
		attributes = [[OFMutableArray alloc] init];

	if ([self attributeForName: attr->name
			 namespace: attr->ns] == nil)
		[attributes addObject: attr];
}

- (void)addAttributeWithName: (OFString*)name_
		 stringValue: (OFString*)value
{
	[self addAttributeWithName: name_
			 namespace: nil
		       stringValue: value];
}

- (void)addAttributeWithName: (OFString*)name_
		   namespace: (OFString*)ns_
		 stringValue: (OFString*)value
{
	OFAutoreleasePool *pool;

	if (name == nil)
		@throw [OFInvalidArgumentException newWithClass: isa
						       selector: _cmd];

	pool = [[OFAutoreleasePool alloc] init];
	[self addAttribute: [OFXMLAttribute attributeWithName: name_
						    namespace: ns_
						  stringValue: value]];
	[pool release];
}

- (OFXMLAttribute*)attributeForName: (OFString*)attrname
{
	return [self attributeForName: attrname
			    namespace: nil];
}

- (OFXMLAttribute*)attributeForName: (OFString*)attrname
			  namespace: (OFString*)attrns
{
	OFXMLAttribute **attrs_c = [attributes cArray];
	size_t i, attrs_count = [attributes count];

	if (attrns != nil) {
		for (i = 0; i < attrs_count; i++)
			if ([attrs_c[i]->ns isEqual: attrns] &&
			    [attrs_c[i]->name isEqual: attrname])
				return attrs_c[i];
	} else {
		for (i = 0; i < attrs_count; i++)
			if (attrs_c[i]->ns == nil &&
			    [attrs_c[i]->name isEqual: attrname])
				return attrs_c[i];
	}

	return nil;
}

- (void)removeAttributeForName: (OFString*)attrname
{
	[self removeAttributeForName: attrname
			   namespace: nil];
}

- (void)removeAttributeForName: (OFString*)attrname
		     namespace: (OFString*)attrns
{
	OFXMLAttribute **attrs_c = [attributes cArray];
	size_t i, attrs_count = [attributes count];

	if (attrns != nil) {
		for (i = 0; i < attrs_count; i++) {
			if ([attrs_c[i]->ns isEqual: attrns] &&
			    [attrs_c[i]->name isEqual: attrname]) {
				[attributes removeObjectAtIndex: i];

				return;
			}
		}
	} else {
		for (i = 0; i < attrs_count; i++) {
			if (attrs_c[i]->ns == nil &&
			    [attrs_c[i]->name isEqual: attrname]) {
				[attributes removeObjectAtIndex: i];

				return;
			}
		}
	}
}

- (void)setPrefix: (OFString*)prefix
     forNamespace: (OFString*)ns_
{
	if (name == nil || prefix == nil || [prefix isEqual: @""])
		@throw [OFInvalidArgumentException newWithClass: isa
						       selector: _cmd];
	if (ns_ == nil)
		ns_ = @"";

	[namespaces setObject: prefix
		       forKey: ns_];
}

- (void)bindPrefix: (OFString*)prefix
      forNamespace: (OFString*)ns_
{
	[self setPrefix: prefix
	   forNamespace: ns_];
	[self addAttributeWithName: prefix
			 namespace: @"http://www.w3.org/2000/xmlns/"
		       stringValue: ns_];
}

- (OFString*)defaultNamespace
{
	if (name == nil)
		@throw [OFInvalidArgumentException newWithClass: isa
						       selector: _cmd];

	return [[defaultNamespace retain] autorelease];
}

- (void)setDefaultNamespace: (OFString*)ns_
{
	if (name == nil)
		@throw [OFInvalidArgumentException newWithClass: isa
						       selector: _cmd];

	OFString *old = defaultNamespace;
	defaultNamespace = [ns_ copy];
	[old release];
}

- (void)addChild: (OFXMLElement*)child
{
	if (name == nil)
		@throw [OFInvalidArgumentException newWithClass: isa
						       selector: _cmd];

	if (children == nil)
		children = [[OFMutableArray alloc] init];

	[children addObject: child];
}

- (OFXMLElement*)elementForName: (OFString*)elemname
{
	return [self elementForName: elemname
			  namespace: nil];
}

- (OFArray*)elementsForName: (OFString*)elemname
{
	return [self elementsForName: elemname
			   namespace: nil];
}

- (OFXMLElement*)elementForName: (OFString*)elemname
		      namespace: (OFString*)elemns
{
	return [[self elementsForName: elemname
			    namespace: elemns] firstObject];
}

- (OFArray*)elementsForName: (OFString*)elemname
		  namespace: (OFString*)elemns
{
	OFMutableArray *ret = [OFMutableArray array];
	OFXMLElement **children_c = [children cArray];
	size_t i, children_count = [children count];

	if (elemns != nil) {
		for (i = 0; i < children_count; i++)
			if ([children_c[i]->ns isEqual: elemns] &&
			    [children_c[i]->name isEqual: elemname])
				[ret addObject: children_c[i]];
	} else {
		for (i = 0; i < children_count; i++)
			if (children_c[i]->ns == nil &&
			    [children_c[i]->name isEqual: elemname])
				[ret addObject: children_c[i]];
	}

	return ret;
}

- (void)dealloc
{
	[name release];
	[ns release];
	[defaultNamespace release];
	[attributes release];
	[namespaces release];
	[children release];
	[characters release];
	[cdata release];
	[comment release];

	[super dealloc];
}
@end