Index: ObjFW.xcodeproj/project.pbxproj ================================================================== --- ObjFW.xcodeproj/project.pbxproj +++ ObjFW.xcodeproj/project.pbxproj @@ -122,10 +122,12 @@ 4B0D249411DFAA3D00ED6FFC /* OFXMLElementBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFXMLElementBuilder.h; path = src/OFXMLElementBuilder.h; sourceTree = ""; }; 4B0D249511DFAA3D00ED6FFC /* OFXMLElementBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFXMLElementBuilder.m; path = src/OFXMLElementBuilder.m; sourceTree = ""; }; 4B175C1D116D130B003C99CB /* OFApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFApplication.h; path = src/OFApplication.h; sourceTree = ""; }; 4B175C1E116D130B003C99CB /* OFApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFApplication.m; path = src/OFApplication.m; sourceTree = ""; }; 4B4986DF1101F64500A2CFDA /* objc_properties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = objc_properties.m; path = src/objc_properties.m; sourceTree = ""; }; + 4B4A61F212DF5EA20048F3F2 /* OFURL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFURL.h; path = src/OFURL.h; sourceTree = SOURCE_ROOT; }; + 4B4A61F312DF5EA20048F3F2 /* OFURL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFURL.m; path = src/OFURL.m; sourceTree = SOURCE_ROOT; }; 4B6799561099E7C50041064A /* asprintf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = asprintf.h; path = src/asprintf.h; sourceTree = ""; }; 4B6799581099E7C50041064A /* objc_sync.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = objc_sync.m; path = src/objc_sync.m; sourceTree = ""; }; 4B67995A1099E7C50041064A /* OFArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFArray.h; path = src/OFArray.h; sourceTree = ""; }; 4B67995B1099E7C50041064A /* OFArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFArray.m; path = src/OFArray.m; sourceTree = ""; }; 4B67995C1099E7C50041064A /* OFAutoreleasePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFAutoreleasePool.h; path = src/OFAutoreleasePool.h; sourceTree = ""; }; @@ -207,10 +209,11 @@ 4BE5F0D812DF4225005C7A0C /* OFConstantString.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFConstantString.m; path = src/OFConstantString.m; sourceTree = SOURCE_ROOT; }; 4BE5F0D912DF4225005C7A0C /* OFDate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFDate.h; path = src/OFDate.h; sourceTree = SOURCE_ROOT; }; 4BE5F0DA12DF4225005C7A0C /* OFDate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFDate.m; path = src/OFDate.m; sourceTree = SOURCE_ROOT; }; 4BE5F0E412DF4259005C7A0C /* OFBlockTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFBlockTests.m; path = tests/OFBlockTests.m; sourceTree = SOURCE_ROOT; }; 4BE5F0E512DF4259005C7A0C /* OFDateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFDateTests.m; path = tests/OFDateTests.m; sourceTree = SOURCE_ROOT; }; + 4BF0749512DFAFCA00A4ADD1 /* OFURLTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFURLTests.m; path = tests/OFURLTests.m; sourceTree = SOURCE_ROOT; }; 4BF1BCBF11C9663F0025511F /* objfw-defs.h.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "objfw-defs.h.in"; path = "src/objfw-defs.h.in"; sourceTree = ""; }; 4BF1BCC011C9663F0025511F /* OFHash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFHash.h; path = src/OFHash.h; sourceTree = ""; }; 4BF1BCC111C9663F0025511F /* OFHash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFHash.m; path = src/OFHash.m; sourceTree = ""; }; 4BF1BCC211C9663F0025511F /* OFMD5Hash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OFMD5Hash.h; path = src/OFMD5Hash.h; sourceTree = ""; }; 4BF1BCC311C9663F0025511F /* OFMD5Hash.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OFMD5Hash.m; path = src/OFMD5Hash.m; sourceTree = ""; }; @@ -305,10 +308,12 @@ 4BF1BCCD11C9663F0025511F /* OFString+XMLUnescaping.m */, 4B6799811099E7C50041064A /* OFTCPSocket.h */, 4B6799821099E7C50041064A /* OFTCPSocket.m */, 4B6799831099E7C50041064A /* OFThread.h */, 4B6799841099E7C50041064A /* OFThread.m */, + 4B4A61F212DF5EA20048F3F2 /* OFURL.h */, + 4B4A61F312DF5EA20048F3F2 /* OFURL.m */, 4BF1BCCE11C9663F0025511F /* OFXMLAttribute.h */, 4BF1BCCF11C9663F0025511F /* OFXMLAttribute.m */, 4B6799871099E7C50041064A /* OFXMLElement.h */, 4B6799881099E7C50041064A /* OFXMLElement.m */, 4B0D249411DFAA3D00ED6FFC /* OFXMLElementBuilder.h */, @@ -351,10 +356,11 @@ 4B6EF6771235358D0076B512 /* OFSHA1HashTests.m */, 4B6EF6781235358D0076B512 /* OFStreamTests.m */, 4B6EF6791235358D0076B512 /* OFStringTests.m */, 4B6EF67A1235358D0076B512 /* OFTCPSocketTests.m */, 4B6EF67B1235358D0076B512 /* OFThreadTests.m */, + 4BF0749512DFAFCA00A4ADD1 /* OFURLTests.m */, 4B6EF67C1235358D0076B512 /* OFXMLElementBuilderTests.m */, 4B6EF67D1235358D0076B512 /* OFXMLElementTests.m */, 4B6EF67E1235358D0076B512 /* OFXMLParserTests.m */, 4B6EF67F1235358D0076B512 /* PropertiesTests.m */, 4B6EF6801235358D0076B512 /* TestsAppDelegate.h */, Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -35,10 +35,11 @@ OFString+URLEncoding.m \ OFString+XMLEscaping.m \ OFString+XMLUnescaping.m \ OFTCPSocket.m \ ${OFTHREAD_M} \ + OFURL.m \ OFXMLAttribute.m \ OFXMLElement.m \ OFXMLElementBuilder.m \ OFXMLParser.m \ unicode.m Index: src/OFExceptions.h ================================================================== --- src/OFExceptions.h +++ src/OFExceptions.h @@ -17,10 +17,11 @@ #include #import "OFObject.h" @class OFString; +@class OFURL; /** * \brief An exception indicating an object could not be allocated. * * This exception is preallocated, as if there's no memory, no exception can @@ -1227,21 +1228,21 @@ */ + newWithClass: (Class)class_ prefix: (OFString*)prefix; /** - * Initializes an already allocated unbound namespace failed exception + * Initializes an already allocated unbound namespace exception * * \param class_ The class of the object which caused the exception * \param ns The namespace which is unbound * \return An initialized unbound namespace exception */ - initWithClass: (Class)class_ namespace: (OFString*)ns; /** - * Initializes an already allocated unbound namespace failed exception + * Initializes an already allocated unbound namespace exception * * \param class_ The class of the object which caused the exception * \param prefix The prefix which is unbound * \return An initialized unbound namespace exception */ @@ -1256,5 +1257,42 @@ /** * \return The unbound prefix */ - (OFString*)prefix; @end + +/** + * \brief An exception indicating that the protocol specified by the URL is not + * supported. + */ +@interface OFUnsupportedProtocolException: OFException +{ + OFURL *URL; +} + +#ifdef OF_HAVE_PROPERTIES +@property (readonly, nonatomic) OFURL *URL; +#endif + +/** + * \param class_ The class of the object which caused the exception + * \param url The URL whose protocol is unsupported + * \return A new unsupported protocol exception + */ ++ newWithClass: (Class)class_ + URL: (OFURL*)url; + +/** + * Initializes an already allocated unsupported protocol exception + * + * \param class_ The class of the object which caused the exception + * \param url The URL whose protocol is unsupported + * \return An initialized unsupported protocol exception + */ +- initWithClass: (Class)class_ + URL: (OFURL*)url; + +/** + * \return The URL whose protocol is unsupported + */ +- (OFURL*)URL; +@end Index: src/OFExceptions.m ================================================================== --- src/OFExceptions.m +++ src/OFExceptions.m @@ -1845,5 +1845,61 @@ - (OFString*)prefix { return prefix; } @end + +@implementation OFUnsupportedProtocolException ++ newWithClass: (Class)class_ + URL: (OFURL*)url +{ + return [[self alloc] initWithClass: class_ + URL: url]; +} + +- initWithClass: (Class)class_ +{ + Class c = isa; + [self release]; + @throw [OFNotImplementedException newWithClass: c + selector: _cmd]; +} + +- initWithClass: (Class)class_ + URL: (OFURL*)url +{ + self = [super initWithClass: class_]; + + @try { + URL = [url copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [URL release]; + + [super dealloc]; +} + +- (OFString*)description +{ + if (description != nil) + return description; + + description = [[OFString alloc] initWithFormat: + @"The protocol of URL %s is not supported by class %s", + [[URL description] cString], class_getName(inClass)]; + + return description; +} + +- (OFURL*)URL +{ + return URL; +} +@end ADDED src/OFURL.h Index: src/OFURL.h ================================================================== --- src/OFURL.h +++ src/OFURL.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011 + * Jonathan Schleifer + * + * 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. + */ + +#import "OFObject.h" + +@class OFString; + +/** + * \brief A class for parsing URLs and accessing parts of it. + */ +@interface OFURL: OFObject +{ + OFString *scheme; + OFString *host; + uint16_t port; + OFString *user; + OFString *password; + OFString *path; + OFString *parameters; + OFString *query; + OFString *fragment; +} + +#ifdef OF_HAVE_PROPERTIES +@property (readonly, copy) OFString *scheme; +@property (readonly, copy) OFString *host; +@property (readonly, assign) uint16_t port; +@property (readonly, copy) OFString *user; +@property (readonly, copy) OFString *password; +@property (readonly, copy) OFString *path; +@property (readonly, copy) OFString *parameters; +@property (readonly, copy) OFString *query; +@property (readonly, copy) OFString *fragment; +#endif + +/** + * \param str A string describing an URL + * \return A new, autoreleased OFURL + */ ++ URLWithString: (OFString*)str; + +/** + * Initializes an already allocated OFURL. + * + * \param str A string describing an URL + * \return An initialized OFURL + */ +- initWithString: (OFString*)str; + +/** + * \return The scheme part of the URL + */ +- (OFString*)scheme; + +/** + * \return The host part of the URL + */ +- (OFString*)host; + +/** + * \return The port part of the URL + */ +- (uint16_t)port; + +/** + * \return The user part of the URL + */ +- (OFString*)user; + +/** + * \return The password part of the URL + */ +- (OFString*)password; + +/** + * \return The path part of the URL + */ +- (OFString*)path; + +/** + * \return The parameters part of the URL + */ +- (OFString*)parameters; + +/** + * \return The query part of the URL + */ +- (OFString*)query; + +/** + * \return The fragment part of the URL + */ +- (OFString*)fragment; +@end ADDED src/OFURL.m Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011 + * Jonathan Schleifer + * + * 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 +#include +#include + +#import "OFURL.h" +#import "OFString.h" +#import "OFAutoreleasePool.h" +#import "OFExceptions.h" + +@implementation OFURL ++ URLWithString: (OFString*)str +{ + return [[[self alloc] initWithString: str] autorelease]; +} + +- initWithString: (OFString*)str +{ + char *str_c, *str_c2 = NULL; + + self = [super init]; + + @try { + char *tmp, *tmp2; + + if ((str_c2 = strdup([str cString])) == NULL) + @throw [OFOutOfMemoryException + newWithClass: isa + requestedSize: [str cStringLength]]; + + str_c = str_c2; + + if (!strncmp(str_c, "http://", 7)) { + scheme = @"http"; + str_c += 7; + } else if (!strncmp(str_c, "https://", 8)) { + scheme = @"https"; + str_c += 8; + } else + @throw [OFInvalidFormatException newWithClass: isa]; + + if ((tmp = strchr(str_c, '/')) != NULL) { + *tmp = '\0'; + tmp++; + } + + if ((tmp2 = strchr(str_c, '@')) != NULL) { + char *tmp3; + + *tmp2 = '\0'; + tmp2++; + + if ((tmp3 = strchr(str_c, ':')) != NULL) { + *tmp3 = '\0'; + tmp3++; + + user = [[OFString alloc] + initWithCString: str_c]; + password = [[OFString alloc] + initWithCString: tmp3]; + } else + user = [[OFString alloc] + initWithCString: str_c]; + + str_c = tmp2; + } + + if ((tmp2 = strchr(str_c, ':')) != NULL) { + OFAutoreleasePool *pool; + OFString *port_str; + + *tmp2 = '\0'; + tmp2++; + + host = [[OFString alloc] initWithCString: str_c]; + + pool = [[OFAutoreleasePool alloc] init]; + port_str = [[OFString alloc] initWithCString: tmp2]; + + if ([port_str decimalValue] > 65535) + @throw [OFInvalidFormatException + newWithClass: isa]; + + port = [port_str decimalValue]; + + [pool release]; + } else { + host = [[OFString alloc] initWithCString: str_c]; + + if ([scheme isEqual: @"http"]) + port = 80; + else if ([scheme isEqual: @"https"]) + port = 443; + else + assert(0); + } + + if ((str_c = tmp) != NULL) { + if ((tmp = strchr(str_c, '#')) != NULL) { + *tmp = '\0'; + + fragment = [[OFString alloc] + initWithCString: tmp + 1]; + } + + if ((tmp = strchr(str_c, '?')) != NULL) { + *tmp = '\0'; + + query = [[OFString alloc] + initWithCString: tmp + 1]; + } + + if ((tmp = strchr(str_c, ';')) != NULL) { + *tmp = '\0'; + + parameters = [[OFString alloc] + initWithCString: tmp + 1]; + } + + path = [[OFString alloc] initWithCString: str_c]; + } + } @catch (id e) { + [self release]; + @throw e; + } @finally { + if (str_c2 != NULL) + free(str_c2); + } + + return self; +} + +- (void)dealloc +{ + [scheme release]; + [host release]; + [user release]; + [password release]; + [path release]; + [parameters release]; + [query release]; + [fragment release]; + + [super dealloc]; +} + +- (BOOL)isEqual: (id)obj +{ + OFURL *url; + + if (![obj isKindOfClass: [OFURL class]]) + return NO; + + url = obj; + + if (![url->scheme isEqual: scheme]) + return NO; + if (![url->host isEqual: host]) + return NO; + if (url->port != port) + return NO; + if (![url->user isEqual: user]) + return NO; + if (![url->password isEqual: password]) + return NO; + if (![url->path isEqual: path]) + return NO; + if (![url->parameters isEqual: parameters]) + return NO; + if (![url->query isEqual: query]) + return NO; + if (![url->fragment isEqual: fragment]) + return NO; + + return YES; +} + +- copy +{ + OFURL *new = [[OFURL alloc] init]; + + new->scheme = [scheme copy]; + new->host = [host copy]; + new->port = port; + new->user = [user copy]; + new->password = [password copy]; + new->path = [path copy]; + new->parameters = [parameters copy]; + new->query = [query copy]; + new->fragment = [fragment copy]; + + return new; +} + +- (OFString*)scheme +{ + return [[scheme copy] autorelease]; +} + +- (OFString*)host +{ + return [[host copy] autorelease]; +} + +- (uint16_t)port +{ + return port; +} + +- (OFString*)user +{ + return [[user copy] autorelease]; +} + +- (OFString*)password +{ + return [[password copy] autorelease]; +} + +- (OFString*)path +{ + return [[path copy] autorelease]; +} + +- (OFString*)parameters +{ + return [[parameters copy] autorelease]; +} + +- (OFString*)query +{ + return [[query copy] autorelease]; +} + +- (OFString*)fragment +{ + return [[fragment copy] autorelease]; +} + +- (OFString*)description +{ + OFMutableString *desc = [OFMutableString + stringWithFormat: @"%s://", [scheme cString]]; + BOOL needPort = YES; + + if (user != nil && password != nil) + [desc appendFormat: @"%s:%s@", [user cString], + [password cString]]; + else if (user != nil) + [desc appendFormat: @"%s@", [user cString]]; + + [desc appendString: host]; + + if (([scheme isEqual: @"http"] && port == 80) || + ([scheme isEqual: @"https"] && port == 443)) + needPort = NO; + + if (needPort) + [desc appendFormat: @":%d", port]; + + if (path != nil) + [desc appendFormat: @"/%s", [path cString]]; + + if (parameters != nil) + [desc appendFormat: @";%s", [parameters cString]]; + + if (query != nil) + [desc appendFormat: @"?%s", [query cString]]; + + if (fragment != nil) + [desc appendFormat: @"#%s", [fragment cString]]; + + return desc; +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -31,10 +31,11 @@ #import "OFDictionary.h" #import "OFEnumerator.h" #import "OFNumber.h" #import "OFDate.h" +#import "OFURL.h" #import "OFStream.h" #import "OFStreamObserver.h" #import "OFFile.h" Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -17,10 +17,11 @@ OFSHA1HashTests.m \ OFStreamTests.m \ OFStringTests.m \ OFTCPSocketTests.m \ ${OFTHREADTESTS_M} \ + OFURLTests.m \ OFXMLElementTests.m \ OFXMLElementBuilderTests.m \ OFXMLParserTests.m \ ${PROPERTIESTESTS_M} \ TestsAppDelegate.m ADDED tests/OFURLTests.m Index: tests/OFURLTests.m ================================================================== --- tests/OFURLTests.m +++ tests/OFURLTests.m @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011 + * Jonathan Schleifer + * + * 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" + +#import "OFURL.h" +#import "OFString.h" +#import "OFAutoreleasePool.h" +#import "OFExceptions.h" + +#import "TestsAppDelegate.h" + +static OFString *module = @"OFURL"; +static OFString *url_str = @"http://u:p@h:1234/f;p?q#f"; + +@implementation TestsAppDelegate (OFURLTests) +- (void)URLTests +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFURL *u1, *u2, *u3, *u4; + + TEST(@"+[URLWithString:]", + R(u1 = [OFURL URLWithString: url_str]) && + R(u2 = [OFURL URLWithString: @"http://foo:80"]) && + R(u3 = [OFURL URLWithString: @"http://bar/"])) + + TEST(@"-[description]", + [[u1 description] isEqual: url_str] && + [[u2 description] isEqual: @"http://foo"] && + [[u3 description] isEqual: @"http://bar/"]) + + TEST(@"-[scheme]", [[u1 scheme] isEqual: @"http"]) + TEST(@"-[user]", [[u1 user] isEqual: @"u"]) + TEST(@"-[password]", [[u1 password] isEqual: @"p"]) + TEST(@"-[host]", [[u1 host] isEqual: @"h"]) + TEST(@"-[port]", [u1 port] == 1234) + TEST(@"-[path]", [[u1 path] isEqual: @"f"]) + TEST(@"-[parameters]", [[u1 parameters] isEqual: @"p"]) + TEST(@"-[query]", [[u1 query] isEqual: @"q"]) + TEST(@"-[fragment]", [[u1 fragment] isEqual: @"f"]) + + TEST(@"-[copy]", R(u4 = [[u1 copy] autorelease])) + + TEST(@"-[isEqual:]", [u1 isEqual: u4] && ![u2 isEqual: u3]) + + EXPECT_EXCEPTION(@"Detection of invalid format", + OFInvalidFormatException, [OFURL URLWithString: @"http"]) + + EXPECT_EXCEPTION(@"Detection of invalid scheme", + OFInvalidFormatException, [OFURL URLWithString: @"foo://"]) + + [pool drain]; +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -138,10 +138,14 @@ @end @interface TestsAppDelegate (OFThreadTests) - (void)threadTests; @end + +@interface TestsAppDelegate (OFURLTests) +- (void)URLTests; +@end @interface TestsAppDelegate (OFXMLElementTests) - (void)XMLElementTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -100,10 +100,11 @@ [self streamTests]; [self TCPSocketTests]; #ifdef OF_THREADS [self threadTests]; #endif + [self URLTests]; [self XMLElementTests]; [self XMLParserTests]; [self XMLElementBuilderTests]; #ifdef OF_PLUGINS [self pluginTests];