Index: src/OFINICategory+Private.h ================================================================== --- src/OFINICategory+Private.h +++ src/OFINICategory+Private.h @@ -13,14 +13,18 @@ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #import "OFINICategory.h" +#import "OFString.h" + +#import "macros.h" @class OFStream; @interface OFINICategory (OF_PRIVATE_CATEGORY) - (instancetype)OF_init; - (void)OF_parseLine: (OFString*)line; - (bool)OF_writeToStream: (OFStream*)stream + encoding: (of_string_encoding_t)encoding first: (bool)first; @end Index: src/OFINICategory.m ================================================================== --- src/OFINICategory.m +++ src/OFINICategory.m @@ -408,10 +408,11 @@ objc_autoreleasePoolPop(pool); } - (bool)OF_writeToStream: (OFStream*)stream + encoding: (of_string_encoding_t)encoding first: (bool)first { OFEnumerator *enumerator; id line; @@ -430,14 +431,17 @@ [stream writeLine: comment->_comment]; } else if ([line isKindOfClass: [OFINICategory_Pair class]]) { OFINICategory_Pair *pair = line; OFString *key = escapeString(pair->_key); OFString *value = escapeString(pair->_value); + OFString *line = [OFString + stringWithFormat: @"%@=%@\n", key, value]; - [stream writeFormat: @"%@=%@\n", key, value]; + [stream writeString: line + encoding: encoding]; } else @throw [OFInvalidArgumentException exception]; } return true; } @end Index: src/OFINIFile.h ================================================================== --- src/OFINIFile.h +++ src/OFINIFile.h @@ -13,14 +13,14 @@ * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #import "OFObject.h" +#import "OFString.h" #import "OFINICategory.h" @class OFMutableArray; -@class OFString; /*! * @class OFINIFile OFINIFile.h ObjFW/OFINIFile.h * * @brief A class for reading, creating and modifying INI files. @@ -37,10 +37,22 @@ * * @return A new, autoreleased OFINIFile with the contents of the specified file */ + (instancetype)fileWithPath: (OFString*)path; +/*! + * @brief Creates a new OFINIFile with the contents of the specified file in + * the specified encoding. + * + * @param path The path to the file whose contents the OFINIFile should contain + * @param encoding The encoding of the specified file + * + * @return A new, autoreleased OFINIFile with the contents of the specified file + */ ++ (instancetype)fileWithPath: (OFString*)path + encoding: (of_string_encoding_t)encoding; + /*! * @brief Initializes an already allocated OFINIFile with the contents of the * specified file. * * @param path The path to the file whose contents the OFINIFile should contain @@ -47,10 +59,22 @@ * * @return An initialized OFINIFile with the contents of the specified file */ - initWithPath: (OFString*)path; +/*! + * @brief Initializes an already allocated OFINIFile with the contents of the + * specified file in the specified encoding. + * + * @param path The path to the file whose contents the OFINIFile should contain + * @param encoding The encoding of the specified file + * + * @return An initialized OFINIFile with the contents of the specified file + */ +- initWithPath: (OFString*)path + encoding: (of_string_encoding_t)encoding; + /*! * @brief Returns an @ref OFINICategory for the category with the specified * name. * * @param name The name of the category for which an @ref OFINICategory should @@ -64,6 +88,16 @@ * @brief Writes the contents of the OFINIFile to a file. * * @param path The path of the file to write to */ - (void)writeToFile: (OFString*)path; + +/*! + * @brief Writes the contents of the OFINIFile to a file in the specified + * encoding. + * + * @param path The path of the file to write to + * @param encoding The encoding to use + */ +- (void)writeToFile: (OFString*)path + encoding: (of_string_encoding_t)encoding; @end Index: src/OFINIFile.m ================================================================== --- src/OFINIFile.m +++ src/OFINIFile.m @@ -26,11 +26,12 @@ #import "autorelease.h" #import "macros.h" @interface OFINIFile (OF_PRIVATE_CATEGORY) -- (void)OF_parseFile: (OFString*)path; +- (void)OF_parseFile: (OFString*)path + encoding: (of_string_encoding_t)encoding; @end static bool isWhitespaceLine(OFString *line) { @@ -55,24 +56,39 @@ @implementation OFINIFile + (instancetype)fileWithPath: (OFString*)path { return [[[self alloc] initWithPath: path] autorelease]; } + ++ (instancetype)fileWithPath: (OFString*)path + encoding: (of_string_encoding_t)encoding +{ + return [[[self alloc] initWithPath: path + encoding: encoding] autorelease]; +} - init { OF_INVALID_INIT_METHOD } - initWithPath: (OFString*)path { + return [self initWithPath: path + encoding: OF_STRING_ENCODING_UTF_8]; +} + +- initWithPath: (OFString*)path + encoding: (of_string_encoding_t)encoding +{ self = [super init]; @try { _categories = [[OFMutableArray alloc] init]; - [self OF_parseFile: path]; + [self OF_parseFile: path + encoding: encoding]; } @catch (id e) { [self release]; @throw e; } @@ -112,10 +128,11 @@ return [category autorelease]; } - (void)OF_parseFile: (OFString*)path + encoding: (of_string_encoding_t)encoding { void *pool = objc_autoreleasePoolPush(); OFFile *file; OFINICategory *category = nil; OFString *line; @@ -129,11 +146,11 @@ return; @throw e; } - while ((line = [file readLine]) != nil) { + while ((line = [file readLineWithEncoding: encoding]) != nil) { if (isWhitespaceLine(line)) continue; if ([line hasPrefix: @"["]) { OFString *categoryName; @@ -159,20 +176,28 @@ objc_autoreleasePoolPop(pool); } - (void)writeToFile: (OFString*)path { + [self writeToFile: path + encoding: OF_STRING_ENCODING_UTF_8]; +} + +- (void)writeToFile: (OFString*)path + encoding: (of_string_encoding_t)encoding +{ void *pool = objc_autoreleasePoolPush(); OFFile *file = [OFFile fileWithPath: path mode: @"w"]; OFEnumerator *enumerator = [_categories objectEnumerator]; OFINICategory *category; bool first = true; while ((category = [enumerator nextObject]) != nil) if ([category OF_writeToStream: file + encoding: encoding first: first]) first = false; objc_autoreleasePoolPop(pool); } @end Index: tests/OFINIFileTests.m ================================================================== --- tests/OFINIFileTests.m +++ tests/OFINIFileTests.m @@ -43,11 +43,11 @@ @"new=new" NL NL @"[foobar]" NL @";foobarcomment" NL @"qux=\" asd\"" NL - @"quxquxqux=\"hello\\\"world\"" NL + @"quxquxqux=\"hello\\\"wörld\"" NL @"qux2=\"a\\f\"" NL @"qux3=a\fb" NL NL @"[types]" NL @"integer=16" NL @@ -55,12 +55,13 @@ @"float=0.25" NL @"double=0.75" NL; OFINIFile *file; OFINICategory *tests, *foobar, *types; - TEST(@"+[fileWithPath:]", - (file = [OFINIFile fileWithPath: @"testfile.ini"])) + TEST(@"+[fileWithPath:encoding:]", + (file = [OFINIFile fileWithPath: @"testfile.ini" + encoding: OF_STRING_ENCODING_CODEPAGE_437])) tests = [file categoryForName: @"tests"]; foobar = [file categoryForName: @"foobar"]; types = [file categoryForName: @"types"]; TEST(@"-[categoryForName:]", @@ -68,11 +69,11 @@ module = @"OFINICategory"; TEST(@"-[stringForKey:]", [[tests stringForKey: @"foo"] isEqual: @"bar"] && - [[foobar stringForKey: @"quxquxqux"] isEqual: @"hello\"world"]) + [[foobar stringForKey: @"quxquxqux"] isEqual: @"hello\"wörld"]) TEST(@"-[setString:forKey:]", R([tests setString: @"baz" forKey: @"foo"]) && R([tests setString: @"new" @@ -113,16 +114,20 @@ module = @"OFINIFile"; /* FIXME: Find a way to write files on Nintendo DS */ #ifndef OF_NINTENDO_DS - TEST(@"-[writeToFile:]", R([file writeToFile: @"tmpfile.ini"]) && - [[OFString stringWithContentsOfFile: @"tmpfile.ini"] + TEST(@"-[writeToFile:encoding:]", + R([file writeToFile: @"tmpfile.ini" + encoding: OF_STRING_ENCODING_CODEPAGE_437]) && + [[OFString + stringWithContentsOfFile: @"tmpfile.ini" + encoding: OF_STRING_ENCODING_CODEPAGE_437] isEqual: output]) [OFFile removeItemAtPath: @"tmpfile.ini"]; #else (void)output; #endif [pool drain]; } @end Index: tests/testfile.ini ================================================================== --- tests/testfile.ini +++ tests/testfile.ini @@ -5,13 +5,13 @@ [foobar] ;foobarcomment qux=" asd" "quxqux " = asd -quxquxqux="hello\"world" +quxquxqux="hello\"w”rld" qux2="a\f" [types] integer = 0x20 bool = true float = 0.5 double = 0.25