Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -24,10 +24,11 @@ OFStream.m \ OFString.m \ OFTCPSocket.m \ OFThread.m \ OFURLEncoding.m \ + OFXMLElement.m \ OFXMLFactory.m \ INCLUDESTMP := ${SRCS:.c=.h} INCLUDES := ${INCLUDESTMP:.m=.h} \ OFMacros.h \ Index: src/OFDictionary.m ================================================================== --- src/OFDictionary.m +++ src/OFDictionary.m @@ -15,11 +15,11 @@ #import "OFDictionary.h" #import "OFIterator.h" #import "OFExceptions.h" -/* Reference for static linking */ +/* References for static linking */ void _references_to_categories_of_OFDictionary() { _OFIterator_reference = 1; } Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -152,5 +152,6 @@ @end #import "OFConstString.h" #import "OFMutableString.h" #import "OFURLEncoding.h" +#import "OFXMLElement.h" Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -23,19 +23,21 @@ #endif #import "OFString.h" #import "OFAutoreleasePool.h" #import "OFURLEncoding.h" +#import "OFXMLElement.h" #import "OFExceptions.h" #import "OFMacros.h" #import "asprintf.h" -/* Reference for static linking */ +/* References for static linking */ void _references_to_categories_of_OFString() { _OFURLEncoding_reference = 1; + _OFXMLElement_reference = 1; }; int of_string_check_utf8(const char *str, size_t len) { ADDED src/OFXMLElement.h Index: src/OFXMLElement.h ================================================================== --- src/OFXMLElement.h +++ src/OFXMLElement.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2008 - 2009 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of libobjfw. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE included in + * the packaging of this file. + */ + +#import "OFObject.h" +#import "OFString.h" +#import "OFDictionary.h" +#import "OFArray.h" + +extern int _OFXMLElement_reference; + +@interface OFXMLElement: OFObject +{ + OFString *name; + OFDictionary *attrs; + OFString *stringval; + OFArray *children; +} + ++ elementWithName: (OFString*)name; ++ elementWithName: (OFString*)name + andStringValue: (OFString*)stringval; +- initWithName: (OFString*)name; +- initWithName: (OFString*)name + andStringValue: (OFString*)stringval; +- (OFString*)string; +- addAttributeWithName: (OFString*)name + andValue: (OFString*)value; +- addChild: (OFXMLElement*)child; +@end + +@interface OFString (OFXMLEscaping) +- stringByXMLEscaping; +@end ADDED src/OFXMLElement.m Index: src/OFXMLElement.m ================================================================== --- src/OFXMLElement.m +++ src/OFXMLElement.m @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2008 - 2009 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of libobjfw. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE included in + * the packaging of this file. + */ + +#include "config.h" + +#include +#include +#include + +#import "OFXMLElement.h" +#import "OFAutoreleasePool.h" +#import "OFExceptions.h" + +int _OFXMLElement_reference; + +@implementation OFXMLElement ++ elementWithName: (OFString*)name_ +{ + return [[[self alloc] initWithName: name_] autorelease]; +} + ++ elementWithName: (OFString*)name_ + andStringValue: (OFString*)stringval_ +{ + return [[[self alloc] initWithName: name_ + andStringValue: stringval_] autorelease]; +} + +- initWithName: (OFString*)name_ +{ + self = [super init]; + + name = [name_ retain]; + + return self; +} + +- initWithName: (OFString*)name_ + andStringValue: (OFString*)stringval_ +{ + self = [super init]; + + name = [name_ retain]; + stringval = [stringval_ retain]; + + return self; +} + +- (OFString*)string +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + char *str_c; + size_t len, i, j; + OFString *ret, *tmp; + OFIterator *iter; + + len = [name length] + 4; + str_c = [self allocMemoryWithSize: len]; + + /* Start of tag */ + *str_c = '<'; + memcpy(str_c + 1, [name cString], [name length]); + i = [name length] + 1; + + /* Attributes */ + iter = [attrs iterator]; + for (;;) { + OFString *key, *val; + + key = [iter nextObject]; + val = [iter nextObject]; + + if (key == nil || val == nil) + break; + + tmp = [val stringByXMLEscaping]; + + len += [key length] + [tmp length] + 4; + @try { + str_c = [self resizeMemory: str_c + toSize: len]; + } @catch (OFException *e) { + [self freeMemory: str_c]; + @throw e; + } + + str_c[i++] = ' '; + memcpy(str_c + i, [key cString], [key length]); + i += [key length]; + str_c[i++] = '='; + str_c[i++] = '\''; + memcpy(str_c + i, [tmp cString], [tmp length]); + i += [tmp length]; + str_c[i++] = '\''; + + [pool releaseObjects]; + } + + /* Childen */ + if (stringval != nil || children != nil) { + if (stringval != nil) + tmp = [stringval stringByXMLEscaping]; + else if (children != nil) { + OFXMLElement **data = [children data]; + size_t count = [children count]; + IMP append; + + tmp = [OFMutableString string]; + append = [tmp methodFor: @selector( + appendCStringWithoutUTF8Checking:)]; + + for (j = 0; j < count; j++) + append(tmp, @selector( + appendCStringWithoutUTF8Checking:), + [[data[j] string] cString]); + } + + len += [tmp length] + [name length] + 2; + @try { + str_c = [self resizeMemory: str_c + toSize: len]; + } @catch (OFException *e) { + [self freeMemory: str_c]; + @throw e; + } + + str_c[i++] = '>'; + memcpy(str_c + i, [tmp cString], [tmp length]); + i += [tmp length]; + str_c[i++] = '<'; + str_c[i++] = '/'; + memcpy(str_c + i, [name cString], [name length]); + i += [name length]; + } else + str_c[i++] = '/'; + + str_c[i++] = '>'; + str_c[i++] = '\0'; + assert(i == len); + + [pool release]; + + @try { + ret = [OFString stringWithCString: str_c]; + } @finally { + [self freeMemory: str_c]; + } + + return ret; +} + +- addAttributeWithName: (OFString*)name_ + andValue: (OFString*)value_ +{ + if (attrs == nil) + attrs = [[OFMutableDictionary alloc] init]; + + [attrs setObject: value_ + forKey: name_]; + + return self; +} + +- addChild: (OFXMLElement*)child +{ + if (stringval != nil) + @throw [OFInvalidArgumentException newWithClass: isa + andSelector: _cmd]; + + if (children == nil) + children = [[OFMutableArray alloc] init]; + + [children addObject: child]; + + return self; +} + +- (void)dealloc +{ + [name release]; + [attrs release]; + [stringval release]; + [children release]; + + [super dealloc]; +} +@end + +@implementation OFString (OFXMLEscaping) +- stringByXMLEscaping +{ + char *str_c, *append, *tmp; + size_t len, append_len; + size_t i, j; + OFString *ret; + + j = 0; + len = length + 1; + + /* + * We can't use allocMemoryWithSize: here as it might be a @"" literal + */ + if ((str_c = malloc(len + 1)) == NULL) + @throw [OFOutOfMemoryException newWithClass: isa + andSize: len]; + + for (i = 0; i < length; i++) { + switch (string[i]) { + case '<': + append = "<"; + append_len = 4; + break; + case '>': + append = ">"; + append_len = 4; + break; + case '"': + append = """; + append_len = 6; + break; + case '\'': + append = "'"; + append_len = 6; + break; + case '&': + append = "&"; + append_len = 5; + break; + default: + append = NULL; + append_len = 0; + } + + if (append != NULL) { + if ((tmp = realloc(str_c, len + append_len)) == NULL) { + free(str_c); + @throw [OFOutOfMemoryException + newWithClass: isa + andSize: len + append_len]; + } + str_c = tmp; + len += append_len - 1; + + memcpy(str_c + j, append, append_len); + j += append_len; + } else + str_c[j++] = string[i]; + } + + str_c[j++] = '\0'; + assert(j == len); + + @try { + ret = [OFString stringWithCString: str_c]; + } @finally { + free(str_c); + } + + return ret; +} +@end Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -9,9 +9,10 @@ ${OFPLUGIN} \ OFString \ OFTCPSocket \ OFThread \ OFList \ + OFXMLElement \ OFXMLFactory \ ${OBJC_SYNC} include ../buildsys.mk Index: tests/OFString/OFString.m ================================================================== --- tests/OFString/OFString.m +++ tests/OFString/OFString.m @@ -23,11 +23,11 @@ #define ZD "%zd" #else #define ZD "%u" #endif -#define NUM_TESTS 33 +#define NUM_TESTS 34 #define SUCCESS \ printf("\r\033[1;%dmTests successful: " ZD "/%d\033[0m", \ (i == NUM_TESTS - 1 ? 32 : 33), i + 1, NUM_TESTS); \ fflush(stdout); #define FAIL \ @@ -99,34 +99,38 @@ CHECK(!strcmp([s1 cString], "test: 123")) [s1 appendWithFormat: @"%02X", 15]; CHECK(!strcmp([s1 cString], "test: 1230F")) + /* Split tests */ a = [@"fooXXbarXXXXbazXXXX" splitWithDelimiter: @"XX"]; CHECK([[a objectAtIndex: j++] isEqual: @"foo"]) CHECK([[a objectAtIndex: j++] isEqual: @"bar"]) CHECK([[a objectAtIndex: j++] isEqual: @""]) CHECK([[a objectAtIndex: j++] isEqual: @"baz"]) CHECK([[a objectAtIndex: j++] isEqual: @""]) CHECK([[a objectAtIndex: j++] isEqual: @""]) + /* URL encoding tests */ CHECK([[@"foo\"ba'_$" stringByURLEncoding] isEqual: @"foo%22ba%27_%24"]) CHECK([[@"foo%20bar%22%24" stringByURLDecoding] isEqual: @"foo bar\"$"]) CHECK_EXCEPT([@"foo%bar" stringByURLDecoding], OFInvalidEncodingException) CHECK_EXCEPT([@"foo%FFbar" stringByURLDecoding], OFInvalidEncodingException) + /* Replace tests */ s1 = [@"asd fo asd fofo asd" mutableCopy]; [s1 replaceOccurrencesOfString: @"fo" withString: @"foo"]; CHECK([s1 isEqual: @"asd foo asd foofoo asd"]) s1 = [@"XX" mutableCopy]; [s1 replaceOccurrencesOfString: @"X" withString: @"XX"]; CHECK([s1 isEqual: @"XXXX"]) + /* Whitespace removing tests */ s1 = [@" \t\t \tasd \t \t\t" mutableCopy]; s2 = [s1 mutableCopy]; s3 = [s1 mutableCopy]; CHECK([[s1 removeLeadingWhitespaces] isEqual: @"asd \t \t\t"]) CHECK([[s2 removeTrailingWhitespaces] isEqual: @" \t\t \tasd"]) @@ -137,9 +141,13 @@ s3 = [s1 mutableCopy]; CHECK([[s1 removeLeadingWhitespaces] isEqual: @""]) CHECK([[s2 removeTrailingWhitespaces] isEqual: @""]) CHECK([[s3 removeLeadingAndTrailingWhitespaces] isEqual: @""]) + /* XML escaping tests */ + s1 = [@" &world'\"!&" stringByXMLEscaping]; + CHECK([s1 isEqual: @"<hello> &world'"!&"]) + puts(""); return 0; } ADDED tests/OFXMLElement/Makefile Index: tests/OFXMLElement/Makefile ================================================================== --- tests/OFXMLElement/Makefile +++ tests/OFXMLElement/Makefile @@ -0,0 +1,25 @@ +PROG_NOINST = ofxmlelement${PROG_SUFFIX} +SRCS = OFXMLElement.m + +include ../../buildsys.mk +include ../../extra.mk + +CPPFLAGS += -I../../src -I../.. +LIBS := -L../../src -lobjfw ${LIBS} + +.PHONY: run + +all: run +run: ${PROG_NOINST} + rm -f libobjfw.so.0 libobjfw.so.0.1 libobjfw.dll libobjfw.dylib + ln -s ../../src/libobjfw.so libobjfw.so.0 + ln -s ../../src/libobjfw.so libobjfw.so.0.1 + if test -f ../../src/libobjfw.dll; then \ + ln ../../src/libobjfw.dll libobjfw.dll; \ + fi + ln -s ../../src/libobjfw.dylib libobjfw.dylib + LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH+:}$$LD_LIBRARY_PATH \ + DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ + ${TEST_LAUNCHER} ./${PROG_NOINST}; EXIT=$$?; \ + rm -f libobjfw.so.0 libobjfw.so.0.1 libobjfw.dll libobjfw.dylib; \ + exit $$EXIT ADDED tests/OFXMLElement/OFXMLElement.m Index: tests/OFXMLElement/OFXMLElement.m ================================================================== --- tests/OFXMLElement/OFXMLElement.m +++ tests/OFXMLElement/OFXMLElement.m @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2008 - 2009 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of libobjfw. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE included in + * the packaging of this file. + */ + +#include "config.h" + +#include + +#import "OFXMLElement.h" + +#ifndef _WIN32 +#define ZD "%zd" +#else +#define ZD "%u" +#endif + +#define NUM_TESTS 5 +#define SUCCESS \ + printf("\r\033[1;%dmTests successful: " ZD "/%d\033[0m", \ + (i == NUM_TESTS - 1 ? 32 : 33), i + 1, NUM_TESTS); \ + fflush(stdout); +#define FAIL \ + printf("\r\033[K\033[1;31mTest " ZD "/%d failed!\033[m\n", \ + i + 1, NUM_TESTS); \ + return 1; +#define CHECK(cond) \ + if (cond) { \ + SUCCESS \ + } else { \ + FAIL \ + } \ + i++; +#define CHECK_EXCEPT(code, exception) \ + @try { \ + code; \ + FAIL \ + } @catch (exception *e) { \ + SUCCESS \ + } \ + i++; + +int +main() +{ + size_t i = 0; + + OFXMLElement *elem; + + elem = [OFXMLElement elementWithName: @"foo"]; + CHECK([[elem string] isEqual: @""]); + + [elem addAttributeWithName: @"foo" + andValue: @"b&ar"]; + CHECK([[elem string] isEqual: @""]) + + [elem addChild: [OFXMLElement elementWithName: @"bar"]]; + CHECK([[elem string] isEqual: @""]) + + elem = [OFXMLElement elementWithName: @"foo" + andStringValue: @"b&ar"]; + CHECK([[elem string] isEqual: @"b&ar"]) + + [elem addAttributeWithName: @"foo" + andValue: @"b&ar"]; + CHECK([[elem string] isEqual: @"b&ar"]) + + puts(""); + + return 0; +}