Index: src/OFINICategory.h ================================================================== --- src/OFINICategory.h +++ src/OFINICategory.h @@ -15,10 +15,11 @@ */ #import "OFObject.h" @class OFString; +@class OFArray; @class OFMutableArray; /*! * @class OFINICategory OFINICategory.h ObjFW/OFINICategory.h * @@ -49,19 +50,25 @@ - (OFString*)name; /*! * @brief Returns the string value for the specified key, or nil if it does not * exist. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is returned. * * @param key The key for which the string value should be returned * @return The string value for the specified key, or nil if it does not exist */ - (OFString*)stringForKey: (OFString*)key; /*! * @brief Returns the string value for the specified key or the specified * default value if it does not exist. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is returned. * * @param key The key for which the string value should be returned * @param defaultValue The value to return if the key does not exist * @return The string value for the specified key or the specified default * value if it does not exist @@ -70,10 +77,13 @@ defaultValue: (OFString*)defaultValue; /*! * @brief Returns the integer value for the specified key or the specified * default value if it does not exist. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is returned. * * @param key The key for which the integer value should be returned * @param defaultValue The value to return if the key does not exist * @return The integer value for the specified key or the specified default * value if it does not exist @@ -82,10 +92,13 @@ defaultValue: (intmax_t)defaultValue; /*! * @brief Returns the bool value for the specified key or the specified default * value if it does not exist. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is returned. * * @param key The key for which the bool value should be returned * @param defaultValue The value to return if the key does not exist * @return The bool value for the specified key or the specified default value * if it does not exist @@ -94,10 +107,13 @@ defaultValue: (bool)defaultValue; /*! * @brief Returns the float value for the specified key or the specified * default value if it does not exist. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is returned. * * @param key The key for which the float value should be returned * @param defaultValue The value to return if the key does not exist * @return The float value for the specified key or the specified default value * if it does not exist @@ -106,66 +122,115 @@ defaultValue: (float)defaultValue; /*! * @brief Returns the double value for the specified key or the specified * default value if it does not exist. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is returned. * * @param key The key for which the double value should be returned * @param defaultValue The value to return if the key does not exist * @return The double value for the specified key or the specified default * value if it does not exist */ - (double)doubleForKey: (OFString*)key defaultValue: (double)defaultValue; +/*! + * @brief Returns an array of string values for the specified multi-key, or an + * empty array if the key does not exist. + * + * A multi-key is a key which exists several times in the same category. Each + * occurrence of the key/value pair adds the respective value to the array. + * + * @param key The multi-key for which the array should be returned + * @return The array for the specified key, or an empty array if it does not + * exist + */ +- (OFArray*)arrayForKey: (OFString*)key; + /*! * @brief Sets the value of the specified key to the specified string. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is changed. * * @param string The string to which the value of the key should be set * @param key The key for which the new value should be set */ - (void)setString: (OFString*)string forKey: (OFString*)key; /*! * @brief Sets the value of the specified key to the specified integer. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is changed. * * @param integer The integer to which the value of the key should be set * @param key The key for which the new value should be set */ - (void)setInteger: (intmax_t)integer forKey: (OFString*)key; /*! * @brief Sets the value of the specified key to the specified bool. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is changed. * * @param bool_ The bool to which the value of the key should be set * @param key The key for which the new value should be set */ - (void)setBool: (bool)bool_ forKey: (OFString*)key; /*! * @brief Sets the value of the specified key to the specified float. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is changed. * * @param float_ The float to which the value of the key should be set * @param key The key for which the new value should be set */ - (void)setFloat: (float)float_ forKey: (OFString*)key; /*! * @brief Sets the value of the specified key to the specified double. + * + * If the specified key is a multi-key (see @ref arrayForKey:), the value of + * the first key/value pair found is changed. * * @param double_ The double to which the value of the key should be set * @param key The key for which the new value should be set */ - (void)setDouble: (double)double_ forKey: (OFString*)key; +/*! + * @brief Sets the specified multi-key to the specified array of strings. + * + * It replaces the first occurrence of the multi-key with several key/value + * pairs and removes all following occurrences. If the multi-key does not exist + * yet, it is appended to the section. + * + * See also @ref arrayForKey: for more information about multi-keys. + * + * @param array The array of strings to which the multi-key should be set + * @param key The multi-key for which the new values should be set + */ +- (void)setArray: (OFArray*)array + forKey: (OFString*)key; + /*! * @brief Removes the value for the specified key + * + * If the specified key is a multi-key (see @ref arrayForKey:), all key/value + * pairs matching the specified key are removed. * * @param key The key of the value to remove */ - (void)removeValueForKey: (OFString*)key; @end Index: src/OFINICategory.m ================================================================== --- src/OFINICategory.m +++ src/OFINICategory.m @@ -304,10 +304,36 @@ objc_autoreleasePoolPop(pool); return ret; } + +- (OFArray*)arrayForKey: (OFString*)key +{ + OFMutableArray *ret = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + OFEnumerator *enumerator = [_lines objectEnumerator]; + id line; + + while ((line = [enumerator nextObject]) != nil) { + OFINICategory_Pair *pair; + + if (![line isKindOfClass: [OFINICategory_Pair class]]) + continue; + + pair = line; + + if ([pair->_key isEqual: key]) + [ret addObject: [[pair->_value copy] autorelease]]; + } + + objc_autoreleasePoolPop(pool); + + [ret makeImmutable]; + + return ret; +} - (void)setString: (OFString*)string forKey: (OFString*)key { void *pool = objc_autoreleasePoolPush(); @@ -377,35 +403,103 @@ [self setString: [OFString stringWithFormat: @"%g", double_] forKey: key]; objc_autoreleasePoolPop(pool); } + +- (void)setArray: (OFArray*)array + forKey: (OFString*)key +{ + void *pool; + OFEnumerator *enumerator; + OFMutableArray *pairs; + id object; + id const *lines; + size_t i, count; + bool replaced; + + if ([array count] == 0) { + [self removeValueForKey: key]; + return; + } + + pool = objc_autoreleasePoolPush(); + + enumerator = [array objectEnumerator]; + pairs = [OFMutableArray arrayWithCapacity: [array count]]; + + while ((object = [enumerator nextObject]) != nil) { + OFINICategory_Pair *pair; + + pair = [[[OFINICategory_Pair alloc] init] autorelease]; + pair->_key = [key copy]; + pair->_value = [object copy]; + + [pairs addObject: pair]; + } + + lines = [_lines objects]; + count = [_lines count]; + replaced = false; + + for (i = 0; i < count; i++) { + OFINICategory_Pair *pair; + + if (![lines[i] isKindOfClass: [OFINICategory_Pair class]]) + continue; + + pair = lines[i]; + + if ([pair->_key isEqual: key]) { + [_lines removeObjectAtIndex: i]; + + if (!replaced) { + [_lines insertObjectsFromArray: pairs + atIndex: i]; + + replaced = true; + /* Continue after inserted pairs */ + i += [array count] - 1; + } else + i--; /* Continue at same position */ + + lines = [_lines objects]; + count = [_lines count]; + + continue; + } + } + + if (!replaced) + [_lines addObjectsFromArray: pairs]; + + objc_autoreleasePoolPop(pool); +} - (void)removeValueForKey: (OFString*)key { void *pool = objc_autoreleasePoolPush(); - OFEnumerator *enumerator = [_lines objectEnumerator]; - size_t i; - id line; + id const *lines = [_lines objects]; + size_t i, count = [_lines count]; - i = 0; - while ((line = [enumerator nextObject]) != nil) { + for (i = 0; i < count; i++) { OFINICategory_Pair *pair; - if (![line isKindOfClass: [OFINICategory_Pair class]]) { - i++; + if (![lines[i] isKindOfClass: [OFINICategory_Pair class]]) continue; - } - pair = line; + pair = lines[i]; if ([pair->_key isEqual: key]) { [_lines removeObjectAtIndex: i]; - break; - } + + lines = [_lines objects]; + count = [_lines count]; - i++; + i--; /* Continue at same position */ + continue; + } } objc_autoreleasePoolPop(pool); } Index: tests/OFINIFileTests.m ================================================================== --- tests/OFINIFileTests.m +++ tests/OFINIFileTests.m @@ -17,10 +17,11 @@ #include "config.h" #import "OFINIFile.h" #import "OFINICategory.h" #import "OFString.h" +#import "OFArray.h" #import "OFFile.h" #import "OFAutoreleasePool.h" #import "TestsAppDelegate.h" @@ -51,13 +52,16 @@ NL @"[types]" NL @"integer=16" NL @"bool=false" NL @"float=0.25" NL + @"array1=foo" NL + @"array1=bar" NL @"double=0.75" NL; OFINIFile *file; OFINICategory *tests, *foobar, *types; + OFArray *array; TEST(@"+[fileWithPath:encoding:]", (file = [OFINIFile fileWithPath: @"testfile.ini" encoding: OF_STRING_ENCODING_CODEPAGE_437])) @@ -106,13 +110,24 @@ [types doubleForKey: @"double" defaultValue: 3] == 0.25) TEST(@"-[setDouble:forKey:]", R([types setDouble: 0.75 forKey: @"double"])) + + array = [OFArray arrayWithObjects: @"1", @"2", nil]; + TEST(@"-[arrayForKey:]", + [[types arrayForKey: @"array1"] isEqual: array] && + [[types arrayForKey: @"array2"] isEqual: array] && + [[types arrayForKey: @"array3"] isEqual: [OFArray array]]) + + array = [OFArray arrayWithObjects: @"foo", @"bar", nil]; + TEST(@"-[setArray:forKey:]", R([types setArray: array + forKey: @"array1"])) TEST(@"-[removeValueForKey:]", - R([foobar removeValueForKey: @"quxqux "])) + R([foobar removeValueForKey: @"quxqux "]) && + R([types removeValueForKey: @"array2"])) module = @"OFINIFile"; /* FIXME: Find a way to write files on Nintendo DS */ #ifndef OF_NINTENDO_DS Index: tests/testfile.ini ================================================================== --- tests/testfile.ini +++ tests/testfile.ini @@ -12,6 +12,10 @@ [types] integer = 0x20 bool = true float = 0.5 +array1 = 1 +array2 = 1 double = 0.25 +array1 = 2 +array2 = 2