Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -27,10 +27,11 @@ OFFileManager.m \ OFGZIPStream.m \ OFHMAC.m \ OFINICategory.m \ OFINIFile.m \ + OFINISection.m \ OFIRI.m \ OFIRIHandler.m \ OFInflate64Stream.m \ OFInflateStream.m \ OFInvocation.m \ DELETED src/OFINICategory+Private.h Index: src/OFINICategory+Private.h ================================================================== --- src/OFINICategory+Private.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2008-2024 Jonathan Schleifer - * - * All rights reserved. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3.0 only, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3.0 for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * version 3.0 along with this program. If not, see - * . - */ - -#import "OFINICategory.h" -#import "OFString.h" - -OF_ASSUME_NONNULL_BEGIN - -@class OFStream; - -OF_DIRECT_MEMBERS -@interface OFINICategory () -- (instancetype)of_initWithName: (nullable OFString *)name - OF_METHOD_FAMILY(init); -- (void)of_parseLine: (OFString *)line; -- (bool)of_writeToStream: (OFStream *)stream - encoding: (OFStringEncoding)encoding - first: (bool)first; -@end - -OF_ASSUME_NONNULL_END Index: src/OFINICategory.h ================================================================== --- src/OFINICategory.h +++ src/OFINICategory.h @@ -15,219 +15,11 @@ * You should have received a copy of the GNU Lesser General Public License * version 3.0 along with this program. If not, see * . */ -#import "OFObject.h" - -OF_ASSUME_NONNULL_BEGIN - -@class OFArray OF_GENERIC(ObjectType); -@class OFMutableArray OF_GENERIC(ObjectType); -@class OFString; - -/** - * @class OFINICategory OFINICategory.h ObjFW/ObjFW.h - * - * @brief A class for representing a category of an INI file. - */ +#import "OFINISection.h" + OF_SUBCLASSING_RESTRICTED -@interface OFINICategory: OFObject -{ - OFString *_name; - OFMutableArray *_lines; -} - -/** - * @brief The name of the INI category - */ -@property (copy, nonatomic) OFString *name; - -- (instancetype)init OF_UNAVAILABLE; - -/** - * @brief Returns the string for the specified key, or `nil` if it does not - * exist. - * - * If the specified key is a multi-key (see @ref arrayValueForKey:), the value - * of the first key/value pair found is returned. - * - * @param key The key for which the string should be returned - * @return The string for the specified key, or `nil` if it does not exist - */ -- (nullable OFString *)stringValueForKey: (OFString *)key; - -/** - * @brief Returns the string for the specified key or the specified default - * value if it does not exist. - * - * If the specified key is a multi-key (see @ref arrayValueForKey:), the value - * of the first key/value pair found is returned. - * - * @param key The key for which the string should be returned - * @param defaultValue The value to return if the key does not exist - * @return The string for the specified key or the specified default value if - * it does not exist - */ -- (nullable OFString *)stringValueForKey: (OFString *)key - defaultValue: (nullable OFString *)defaultValue; - -/** - * @brief Returns the long long 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 arrayValueForKey:), the value - * of the first key/value pair found is returned. - * - * @param key The key for which the long long should be returned - * @param defaultValue The value to return if the key does not exist - * @return The long long for the specified key or the specified default value - * if it does not exist - * @throw OFInvalidFormatException The specified key is not in the correct - * format for a long long - */ -- (long long)longLongValueForKey: (OFString *)key - defaultValue: (long long)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 arrayValueForKey:), the value - * of the first key/value pair found is returned. - * - * @param key The key for which the bool should be returned - * @param defaultValue The value to return if the key does not exist - * @return The bool for the specified key or the specified default value if it - * does not exist - * @throw OFInvalidFormatException The specified key is not in the correct - * format for a bool - */ -- (bool)boolValueForKey: (OFString *)key 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 arrayValueForKey:), the value - * of the first key/value pair found is returned. - * - * @param key The key for which the float should be returned - * @param defaultValue The value to return if the key does not exist - * @return The float for the specified key or the specified default value if it - * does not exist - * @throw OFInvalidFormatException The specified key is not in the correct - * format for a float - */ -- (float)floatValueForKey: (OFString *)key 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 arrayValueForKey:), the value - * of the first key/value pair found is returned. - * - * @param key The key for which the double should be returned - * @param defaultValue The value to return if the key does not exist - * @return The double for the specified key or the specified default value if - * it does not exist - * @throw OFInvalidFormatException The specified key is not in the correct - * format for a double - */ -- (double)doubleValueForKey: (OFString *)key defaultValue: (double)defaultValue; - -/** - * @brief Returns an array of strings 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 OF_GENERIC(OFString *) *)arrayValueForKey: (OFString *)key; - -/** - * @brief Sets the value of the specified key to the specified string. - * - * If the specified key is a multi-key (see @ref arrayValueForKey:), the value - * of the first key/value pair found is changed. - * - * @param stringValue The string to which the key should be set - * @param key The key for which the new value should be set - */ -- (void)setStringValue: (OFString *)stringValue forKey: (OFString *)key; - -/** - * @brief Sets the value of the specified key to the specified long long. - * - * If the specified key is a multi-key (see @ref arrayValueForKey:), the value - * of the first key/value pair found is changed. - * - * @param longLongValue The long long to which the key should be set - * @param key The key for which the new value should be set - */ -- (void)setLongLongValue: (long long)longLongValue 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 arrayValueForKey:), the value - * of the first key/value pair found is changed. - * - * @param boolValue The bool to which the key should be set - * @param key The key for which the new value should be set - */ -- (void)setBoolValue: (bool)boolValue 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 arrayValueForKey:), the value - * of the first key/value pair found is changed. - * - * @param floatValue The float to which the key should be set - * @param key The key for which the new value should be set - */ -- (void)setFloatValue: (float)floatValue 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 arrayValueForKey:), the value - * of the first key/value pair found is changed. - * - * @param doubleValue The double to which the key should be set - * @param key The key for which the new value should be set - */ -- (void)setDoubleValue: (double)doubleValue 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 arrayValueForKey: for more information about multi-keys. - * - * @param arrayValue 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)setArrayValue: (OFArray OF_GENERIC(OFString *) *)arrayValue - forKey: (OFString *)key; - -/** - * @brief Removes the value for the specified key - * - * If the specified key is a multi-key (see @ref arrayValueForKey:), all - * key/value pairs matching the specified key are removed. - * - * @param key The key of the value to remove - */ -- (void)removeValueForKey: (OFString *)key; +OF_DEPRECATED(ObjFW, 1, 2, "Use OFINISection instead") +@interface OFINICategory: OFINISection @end - -OF_ASSUME_NONNULL_END Index: src/OFINICategory.m ================================================================== --- src/OFINICategory.m +++ src/OFINICategory.m @@ -18,499 +18,8 @@ */ #include "config.h" #import "OFINICategory.h" -#import "OFINICategory+Private.h" -#import "OFArray.h" -#import "OFString.h" -#import "OFStream.h" - -#import "OFInvalidArgumentException.h" -#import "OFInvalidFormatException.h" - -@interface OFINICategoryPair: OFObject -{ -@public - OFString *_key, *_value; -} -@end - -@interface OFINICategoryComment: OFObject -{ -@public - OFString *_comment; -} -@end - -static OFString * -escapeString(OFString *string) -{ - OFMutableString *mutableString; - - /* FIXME: Optimize */ - if (![string hasPrefix: @" "] && ![string hasPrefix: @"\t"] && - ![string hasPrefix: @"\f"] && ![string hasSuffix: @" "] && - ![string hasSuffix: @"\t"] && ![string hasSuffix: @"\f"] && - ![string containsString: @"\""]) - return string; - - mutableString = [[string mutableCopy] autorelease]; - - [mutableString replaceOccurrencesOfString: @"\\" withString: @"\\\\"]; - [mutableString replaceOccurrencesOfString: @"\f" withString: @"\\f"]; - [mutableString replaceOccurrencesOfString: @"\r" withString: @"\\r"]; - [mutableString replaceOccurrencesOfString: @"\n" withString: @"\\n"]; - [mutableString replaceOccurrencesOfString: @"\"" withString: @"\\\""]; - - [mutableString insertString: @"\"" atIndex: 0]; - [mutableString appendString: @"\""]; - - [mutableString makeImmutable]; - - return mutableString; -} - -static OFString * -unescapeString(OFString *string) -{ - OFMutableString *mutableString; - - if (![string hasPrefix: @"\""] || ![string hasSuffix: @"\""]) - return string; - - string = [string substringWithRange: OFMakeRange(1, string.length - 2)]; - mutableString = [[string mutableCopy] autorelease]; - - [mutableString replaceOccurrencesOfString: @"\\f" withString: @"\f"]; - [mutableString replaceOccurrencesOfString: @"\\r" withString: @"\r"]; - [mutableString replaceOccurrencesOfString: @"\\n" withString: @"\n"]; - [mutableString replaceOccurrencesOfString: @"\\\"" withString: @"\""]; - [mutableString replaceOccurrencesOfString: @"\\\\" withString: @"\\"]; - - [mutableString makeImmutable]; - - return mutableString; -} - -@implementation OFINICategoryPair -- (void)dealloc -{ - [_key release]; - [_value release]; - - [super dealloc]; -} - -- (OFString *)description -{ - return [OFString stringWithFormat: @"%@ = %@", _key, _value]; -} -@end - -@implementation OFINICategoryComment -- (void)dealloc -{ - [_comment release]; - - [super dealloc]; -} - -- (OFString *)description -{ - return [[_comment copy] autorelease]; -} -@end @implementation OFINICategory -@synthesize name = _name; - -- (instancetype)of_initWithName: (OFString *)name OF_DIRECT -{ - self = [super init]; - - @try { - _name = [name copy]; - _lines = [[OFMutableArray alloc] init]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (void)dealloc -{ - [_name release]; - [_lines release]; - - [super dealloc]; -} - -- (void)of_parseLine: (OFString *)line -{ - if (![line hasPrefix: @";"]) { - OFINICategoryPair *pair; - OFString *key, *value; - size_t pos; - - if (_name == nil) - @throw [OFInvalidFormatException exception]; - - pair = [[[OFINICategoryPair alloc] init] autorelease]; - - if ((pos = [line rangeOfString: @"="].location) == OFNotFound) - @throw [OFInvalidFormatException exception]; - - key = unescapeString([line substringToIndex: pos] - .stringByDeletingEnclosingWhitespaces); - value = unescapeString([line substringFromIndex: pos + 1] - .stringByDeletingEnclosingWhitespaces); - - pair->_key = [key copy]; - pair->_value = [value copy]; - - [_lines addObject: pair]; - } else { - OFINICategoryComment *comment = - [[[OFINICategoryComment alloc] init] autorelease]; - - comment->_comment = [line copy]; - - [_lines addObject: comment]; - } -} - -- (OFString *)stringValueForKey: (OFString *)key -{ - return [self stringValueForKey: key defaultValue: nil]; -} - -- (OFString *)stringValueForKey: (OFString *)key - defaultValue: (OFString *)defaultValue -{ - for (id line in _lines) { - OFINICategoryPair *pair; - - if (![line isKindOfClass: [OFINICategoryPair class]]) - continue; - - pair = line; - - if ([pair->_key isEqual: key]) - return [[pair->_value copy] autorelease]; - } - - return defaultValue; -} - -- (long long)longLongValueForKey: (OFString *)key - defaultValue: (long long)defaultValue -{ - void *pool = objc_autoreleasePoolPush(); - OFString *value = [self stringValueForKey: key defaultValue: nil]; - long long ret; - - if (value != nil) - ret = [value longLongValueWithBase: 0]; - else - ret = defaultValue; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (bool)boolValueForKey: (OFString *)key defaultValue: (bool)defaultValue -{ - void *pool = objc_autoreleasePoolPush(); - OFString *value = [self stringValueForKey: key defaultValue: nil]; - bool ret; - - if (value != nil) { - if ([value isEqual: @"true"]) - ret = true; - else if ([value isEqual: @"false"]) - ret = false; - else - @throw [OFInvalidFormatException exception]; - } else - ret = defaultValue; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (float)floatValueForKey: (OFString *)key defaultValue: (float)defaultValue -{ - void *pool = objc_autoreleasePoolPush(); - OFString *value = [self stringValueForKey: key defaultValue: nil]; - float ret; - - if (value != nil) - ret = value.floatValue; - else - ret = defaultValue; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (double)doubleValueForKey: (OFString *)key defaultValue: (double)defaultValue -{ - void *pool = objc_autoreleasePoolPush(); - OFString *value = [self stringValueForKey: key defaultValue: nil]; - double ret; - - if (value != nil) - ret = value.doubleValue; - else - ret = defaultValue; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (OFArray OF_GENERIC(OFString *) *)arrayValueForKey: (OFString *)key -{ - OFMutableArray *ret = [OFMutableArray array]; - void *pool = objc_autoreleasePoolPush(); - - for (id line in _lines) { - OFINICategoryPair *pair; - - if (![line isKindOfClass: [OFINICategoryPair class]]) - continue; - - pair = line; - - if ([pair->_key isEqual: key]) - [ret addObject: [[pair->_value copy] autorelease]]; - } - - objc_autoreleasePoolPop(pool); - - [ret makeImmutable]; - - return ret; -} - -- (void)setStringValue: (OFString *)string forKey: (OFString *)key -{ - void *pool = objc_autoreleasePoolPush(); - OFINICategoryPair *pair; - - for (id line in _lines) { - if (![line isKindOfClass: [OFINICategoryPair class]]) - continue; - - pair = line; - - if ([pair->_key isEqual: key]) { - OFString *old = pair->_value; - pair->_value = [string copy]; - [old release]; - - objc_autoreleasePoolPop(pool); - - return; - } - } - - pair = [[[OFINICategoryPair alloc] init] autorelease]; - pair->_key = nil; - pair->_value = nil; - - @try { - pair->_key = [key copy]; - pair->_value = [string copy]; - [_lines addObject: pair]; - } @catch (id e) { - [pair->_key release]; - [pair->_value release]; - - @throw e; - } - - objc_autoreleasePoolPop(pool); -} - -- (void)setLongLongValue: (long long)longLongValue forKey: (OFString *)key -{ - void *pool = objc_autoreleasePoolPush(); - - [self setStringValue: [OFString stringWithFormat: - @"%lld", longLongValue] - forKey: key]; - - objc_autoreleasePoolPop(pool); -} - -- (void)setBoolValue: (bool)boolValue forKey: (OFString *)key -{ - [self setStringValue: (boolValue ? @"true" : @"false") forKey: key]; -} - -- (void)setFloatValue: (float)floatValue forKey: (OFString *)key -{ - void *pool = objc_autoreleasePoolPush(); - - [self setStringValue: [OFString stringWithFormat: @"%g", floatValue] - forKey: key]; - - objc_autoreleasePoolPop(pool); -} - -- (void)setDoubleValue: (double)doubleValue forKey: (OFString *)key -{ - void *pool = objc_autoreleasePoolPush(); - - [self setStringValue: [OFString stringWithFormat: @"%g", doubleValue] - forKey: key]; - - objc_autoreleasePoolPop(pool); -} - -- (void)setArrayValue: (OFArray OF_GENERIC(OFString *) *)arrayValue - forKey: (OFString *)key -{ - void *pool; - OFMutableArray *pairs; - id const *lines; - size_t count; - bool replaced; - - if (arrayValue.count == 0) { - [self removeValueForKey: key]; - return; - } - - pool = objc_autoreleasePoolPush(); - - pairs = [OFMutableArray arrayWithCapacity: arrayValue.count]; - - for (OFString *string in arrayValue) { - OFINICategoryPair *pair; - - if (![string isKindOfClass: [OFString class]]) - @throw [OFInvalidArgumentException exception]; - - pair = [[[OFINICategoryPair alloc] init] autorelease]; - pair->_key = [key copy]; - pair->_value = [string copy]; - - [pairs addObject: pair]; - } - - lines = _lines.objects; - count = _lines.count; - replaced = false; - - for (size_t i = 0; i < count; i++) { - OFINICategoryPair *pair; - - if (![lines[i] isKindOfClass: [OFINICategoryPair 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 += arrayValue.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(); - id const *lines = _lines.objects; - size_t count = _lines.count; - - for (size_t i = 0; i < count; i++) { - OFINICategoryPair *pair; - - if (![lines[i] isKindOfClass: [OFINICategoryPair class]]) - continue; - - pair = lines[i]; - - if ([pair->_key isEqual: key]) { - [_lines removeObjectAtIndex: i]; - - lines = _lines.objects; - count = _lines.count; - - i--; /* Continue at same position */ - continue; - } - } - - objc_autoreleasePoolPop(pool); -} - -- (bool)of_writeToStream: (OFStream *)stream - encoding: (OFStringEncoding)encoding - first: (bool)first -{ - if (_lines.count == 0) - return false; - - if (_name != nil) { - if (first) - [stream writeFormat: @"[%@]\r\n", _name]; - else - [stream writeFormat: @"\r\n[%@]\r\n", _name]; - } - - for (id line in _lines) { - if ([line isKindOfClass: [OFINICategoryComment class]]) { - OFINICategoryComment *comment = line; - [stream writeFormat: @"%@\r\n", comment->_comment]; - } else if ([line isKindOfClass: [OFINICategoryPair class]]) { - OFINICategoryPair *pair = line; - OFString *key = escapeString(pair->_key); - OFString *value = escapeString(pair->_value); - OFString *tmp = [OFString - stringWithFormat: @"%@=%@\r\n", key, value]; - [stream writeString: tmp encoding: encoding]; - } else - @throw [OFInvalidArgumentException exception]; - } - - return true; -} - -- (OFString *)description -{ - return [OFString stringWithFormat: @"<%@ \"%@\": %@>", - self.class, _name, _lines]; -} @end Index: src/OFINIFile.h ================================================================== --- src/OFINIFile.h +++ src/OFINIFile.h @@ -17,11 +17,11 @@ * . */ #import "OFObject.h" #import "OFString.h" -#import "OFINICategory.h" +#import "OFINISection.h" OF_ASSUME_NONNULL_BEGIN @class OFIRI; @class OFMutableArray OF_GENERIC(ObjectType); @@ -32,18 +32,24 @@ * @brief A class for reading, creating and modifying INI files. */ OF_SUBCLASSING_RESTRICTED @interface OFINIFile: OFObject { - OFINICategory *_prologue; - OFMutableArray OF_GENERIC(OFINICategory *) *_categories; + OFINISection *_prologue; + OFMutableArray OF_GENERIC(OFINISection *) *_sections; } /** - * @brief All categories in the INI file. + * @brief All sections in the INI file. + */ +@property (readonly, nonatomic) OFArray OF_GENERIC(OFINISection *) *sections; + +/** + * @brief All sections in the INI file. */ -@property (readonly, nonatomic) OFArray OF_GENERIC(OFINICategory *) *categories; +@property (readonly, nonatomic) OFArray OF_GENERIC(OFINISection *) *categories + OF_DEPRECATED(ObjFW, 1, 2, "Use -[sections] instead"); /** * @brief Creates a new OFINIFile with the contents of the specified file. * * @param IRI The IRI to the file whose contents the OFINIFile should contain @@ -101,19 +107,29 @@ - (instancetype)initWithIRI: (OFIRI *)IRI encoding: (OFStringEncoding)encoding OF_DESIGNATED_INITIALIZER; /** - * @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 - * be returned - * - * @return An @ref OFINICategory for the category with the specified name - */ -- (OFINICategory *)categoryForName: (OFString *)name; + * @brief Returns an @ref OFINISection for the section with the specified name. + * + * @param name The name of the section for which an @ref OFINISection should be + * returned + * + * @return An @ref OFINISection for the section with the specified name + */ +- (OFINISection *)sectionForName: (OFString *)name; + +/** + * @brief Returns an @ref OFINISection for the section with the specified name. + * + * @param name The name of the section for which an @ref OFINISection should be + * returned + * + * @return An @ref OFINISection for the section with the specified name + */ +- (OFINISection *)categoryForName: (OFString *)name + OF_DEPRECATED(ObjFW, 1, 2, "Use -[sectionForName:] instead"); /** * @brief Writes the contents of the OFINIFile to a file. * * @param IRI The IRI of the file to write to Index: src/OFINIFile.m ================================================================== --- src/OFINIFile.m +++ src/OFINIFile.m @@ -21,12 +21,12 @@ #include #import "OFINIFile.h" #import "OFArray.h" -#import "OFINICategory+Private.h" -#import "OFINICategory.h" +#import "OFINISection.h" +#import "OFINISection+Private.h" #import "OFIRI.h" #import "OFIRIHandler.h" #import "OFStream.h" #import "OFString.h" @@ -50,11 +50,11 @@ return true; } @implementation OFINIFile -@synthesize categories = _categories; +@synthesize sections = _sections; + (instancetype)fileWithIRI: (OFIRI *)IRI { return [[[self alloc] initWithIRI: IRI] autorelease]; } @@ -77,12 +77,12 @@ - (instancetype)initWithIRI: (OFIRI *)IRI encoding: (OFStringEncoding)encoding { self = [super init]; @try { - _prologue = [[OFINICategory alloc] of_initWithName: nil]; - _categories = [[OFMutableArray alloc] init]; + _prologue = [[OFINISection alloc] of_initWithName: nil]; + _sections = [[OFMutableArray alloc] init]; [self of_parseIRI: IRI encoding: encoding]; } @catch (id e) { [self release]; @throw e; @@ -92,37 +92,47 @@ } - (void)dealloc { [_prologue release]; - [_categories release]; + [_sections release]; [super dealloc]; } -- (OFINICategory *)categoryForName: (OFString *)name +- (OFArray OF_GENERIC(OFINISection *) *)categories +{ + return self.sections; +} + +- (OFINISection *)sectionForName: (OFString *)name { void *pool = objc_autoreleasePoolPush(); - OFINICategory *category; - - for (category in _categories) - if ([category.name isEqual: name]) - return category; - - category = [[[OFINICategory alloc] of_initWithName: name] autorelease]; - [_categories addObject: category]; + OFINISection *section; + + for (section in _sections) + if ([section.name isEqual: name]) + return section; + + section = [[[OFINISection alloc] of_initWithName: name] autorelease]; + [_sections addObject: section]; objc_autoreleasePoolPop(pool); - return category; + return section; +} + +- (OFINISection *)categoryForName: (OFString *)name +{ + return [self sectionForName: name]; } - (void)of_parseIRI: (OFIRI *)IRI encoding: (OFStringEncoding)encoding { void *pool = objc_autoreleasePoolPush(); OFStream *file; - OFINICategory *category = _prologue; + OFINISection *section = _prologue; OFString *line; if (encoding == OFStringEncodingAutodetect) encoding = OFStringEncodingUTF8; @@ -139,22 +149,22 @@ while ((line = [file readLineWithEncoding: encoding]) != nil) { if (isWhitespaceLine(line)) continue; if ([line hasPrefix: @"["]) { - OFString *categoryName; + OFString *sectionName; if (![line hasSuffix: @"]"]) @throw [OFInvalidFormatException exception]; - categoryName = [line substringWithRange: + sectionName = [line substringWithRange: OFMakeRange(1, line.length - 2)]; - category = [[[OFINICategory alloc] - of_initWithName: categoryName] autorelease]; - [_categories addObject: category]; + section = [[[OFINISection alloc] + of_initWithName: sectionName] autorelease]; + [_sections addObject: section]; } else - [category of_parseLine: line]; + [section of_parseLine: line]; } objc_autoreleasePoolPop(pool); } @@ -170,20 +180,20 @@ bool first = true; if ([_prologue of_writeToStream: file encoding: encoding first: true]) first = false; - for (OFINICategory *category in _categories) - if ([category of_writeToStream: file - encoding: encoding - first: first]) + for (OFINISection *section in _sections) + if ([section of_writeToStream: file + encoding: encoding + first: first]) first = false; objc_autoreleasePoolPop(pool); } - (OFString *)description { return [OFString stringWithFormat: @"<%@: %@>", - self.class, _categories]; + self.class, _sections]; } @end Index: src/OFINIFileSettings.m ================================================================== --- src/OFINIFileSettings.m +++ src/OFINIFileSettings.m @@ -55,107 +55,104 @@ [_INIFile release]; [super dealloc]; } -- (void)of_getCategory: (OFString **)category - andKey: (OFString **)key - forPath: (OFString *)path OF_DIRECT +- (void)of_getSection: (OFString **)section + andKey: (OFString **)key + forPath: (OFString *)path OF_DIRECT { size_t pos = [path rangeOfString: @"." options: OFStringSearchBackwards].location; if (pos == OFNotFound) { - *category = @""; + *section = @""; *key = path; return; } - *category = [path substringToIndex: pos]; + *section = [path substringToIndex: pos]; *key = [path substringFromIndex: pos + 1]; } - (void)setString: (OFString *)string forPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; - [self of_getCategory: &category andKey: &key forPath: path]; - [[_INIFile categoryForName: category] setStringValue: string - forKey: key]; + [self of_getSection: §ion andKey: &key forPath: path]; + [[_INIFile sectionForName: section] setStringValue: string forKey: key]; objc_autoreleasePoolPop(pool); } - (void)setLongLong: (long long)longLong forPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; - [self of_getCategory: &category andKey: &key forPath: path]; - [[_INIFile categoryForName: category] setLongLongValue: longLong - forKey: key]; + [self of_getSection: §ion andKey: &key forPath: path]; + [[_INIFile sectionForName: section] setLongLongValue: longLong + forKey: key]; objc_autoreleasePoolPop(pool); } - (void)setBool: (bool)bool_ forPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; - [self of_getCategory: &category andKey: &key forPath: path]; - [[_INIFile categoryForName: category] setBoolValue: bool_ forKey: key]; + [self of_getSection: §ion andKey: &key forPath: path]; + [[_INIFile sectionForName: section] setBoolValue: bool_ forKey: key]; objc_autoreleasePoolPop(pool); } - (void)setFloat: (float)float_ forPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; - [self of_getCategory: &category andKey: &key forPath: path]; - [[_INIFile categoryForName: category] setFloatValue: float_ - forKey: key]; + [self of_getSection: §ion andKey: &key forPath: path]; + [[_INIFile sectionForName: section] setFloatValue: float_ forKey: key]; objc_autoreleasePoolPop(pool); } - (void)setDouble: (double)double_ forPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; - [self of_getCategory: &category andKey: &key forPath: path]; - [[_INIFile categoryForName: category] setDoubleValue: double_ - forKey: key]; + [self of_getSection: §ion andKey: &key forPath: path]; + [[_INIFile sectionForName: section] setDoubleValue: double_ + forKey: key]; objc_autoreleasePoolPop(pool); } - (void)setStringArray: (OFArray OF_GENERIC(OFString *) *)array forPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; - [self of_getCategory: &category andKey: &key forPath: path]; - [[_INIFile categoryForName: category] setArrayValue: array - forKey: key]; + [self of_getSection: §ion andKey: &key forPath: path]; + [[_INIFile sectionForName: section] setArrayValue: array forKey: key]; objc_autoreleasePoolPop(pool); } - (OFString *)stringForPath: (OFString *)path defaultValue: (OFString *)defaultValue { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key, *ret; + OFString *section, *key, *ret; - [self of_getCategory: &category andKey: &key forPath: path]; - ret = [[_INIFile categoryForName: category] + [self of_getSection: §ion andKey: &key forPath: path]; + ret = [[_INIFile sectionForName: section] stringValueForKey: key defaultValue: defaultValue]; [ret retain]; objc_autoreleasePoolPop(pool); @@ -164,15 +161,15 @@ - (long long)longLongForPath: (OFString *)path defaultValue: (long long)defaultValue { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; long long ret; - [self of_getCategory: &category andKey: &key forPath: path]; - ret = [[_INIFile categoryForName: category] + [self of_getSection: §ion andKey: &key forPath: path]; + ret = [[_INIFile sectionForName: section] longLongValueForKey: key defaultValue: defaultValue]; objc_autoreleasePoolPop(pool); @@ -180,15 +177,15 @@ } - (bool)boolForPath: (OFString *)path defaultValue: (bool)defaultValue { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; bool ret; - [self of_getCategory: &category andKey: &key forPath: path]; - ret = [[_INIFile categoryForName: category] + [self of_getSection: §ion andKey: &key forPath: path]; + ret = [[_INIFile sectionForName: section] boolValueForKey: key defaultValue: defaultValue]; objc_autoreleasePoolPop(pool); @@ -196,15 +193,15 @@ } - (float)floatForPath: (OFString *)path defaultValue: (float)defaultValue { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; float ret; - [self of_getCategory: &category andKey: &key forPath: path]; - ret = [[_INIFile categoryForName: category] + [self of_getSection: §ion andKey: &key forPath: path]; + ret = [[_INIFile sectionForName: section] floatValueForKey: key defaultValue: defaultValue]; objc_autoreleasePoolPop(pool); @@ -212,15 +209,15 @@ } - (double)doubleForPath: (OFString *)path defaultValue: (double)defaultValue { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; double ret; - [self of_getCategory: &category andKey: &key forPath: path]; - ret = [[_INIFile categoryForName: category] + [self of_getSection: §ion andKey: &key forPath: path]; + ret = [[_INIFile sectionForName: section] doubleValueForKey: key defaultValue: defaultValue]; objc_autoreleasePoolPop(pool); @@ -228,32 +225,32 @@ } - (OFArray OF_GENERIC(OFString *) *)stringArrayForPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; OFArray *ret; - [self of_getCategory: &category andKey: &key forPath: path]; - ret = [[_INIFile categoryForName: category] arrayValueForKey: key]; + [self of_getSection: §ion andKey: &key forPath: path]; + ret = [[_INIFile sectionForName: section] arrayValueForKey: key]; [ret retain]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } - (void)removeValueForPath: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFString *category, *key; + OFString *section, *key; - [self of_getCategory: &category andKey: &key forPath: path]; - [[_INIFile categoryForName: category] removeValueForKey: key]; + [self of_getSection: §ion andKey: &key forPath: path]; + [[_INIFile sectionForName: section] removeValueForKey: key]; objc_autoreleasePoolPop(pool); } - (void)save { [_INIFile writeToIRI: _fileIRI]; } @end ADDED src/OFINISection+Private.h Index: src/OFINISection+Private.h ================================================================== --- /dev/null +++ src/OFINISection+Private.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#import "OFINISection.h" +#import "OFString.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFStream; + +OF_DIRECT_MEMBERS +@interface OFINISection () +- (instancetype)of_initWithName: (nullable OFString *)name + OF_METHOD_FAMILY(init); +- (void)of_parseLine: (OFString *)line; +- (bool)of_writeToStream: (OFStream *)stream + encoding: (OFStringEncoding)encoding + first: (bool)first; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFINISection.h Index: src/OFINISection.h ================================================================== --- /dev/null +++ src/OFINISection.h @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#import "OFObject.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFArray OF_GENERIC(ObjectType); +@class OFMutableArray OF_GENERIC(ObjectType); +@class OFString; + +/** + * @class OFINISection OFINISection.h ObjFW/ObjFW.h + * + * @brief A class for representing a section of an INI file. + */ +@interface OFINISection: OFObject +{ + OFString *_name; + OFMutableArray *_lines; + OF_RESERVE_IVARS(OFINISection, 4) +} + +/** + * @brief The name of the INI section + */ +@property (copy, nonatomic) OFString *name; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Returns the string for the specified key, or `nil` if it does not + * exist. + * + * If the specified key is a multi-key (see @ref arrayValueForKey:), the value + * of the first key/value pair found is returned. + * + * @param key The key for which the string should be returned + * @return The string for the specified key, or `nil` if it does not exist + */ +- (nullable OFString *)stringValueForKey: (OFString *)key; + +/** + * @brief Returns the string for the specified key or the specified default + * value if it does not exist. + * + * If the specified key is a multi-key (see @ref arrayValueForKey:), the value + * of the first key/value pair found is returned. + * + * @param key The key for which the string should be returned + * @param defaultValue The value to return if the key does not exist + * @return The string for the specified key or the specified default value if + * it does not exist + */ +- (nullable OFString *)stringValueForKey: (OFString *)key + defaultValue: (nullable OFString *)defaultValue; + +/** + * @brief Returns the long long 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 arrayValueForKey:), the value + * of the first key/value pair found is returned. + * + * @param key The key for which the long long should be returned + * @param defaultValue The value to return if the key does not exist + * @return The long long for the specified key or the specified default value + * if it does not exist + * @throw OFInvalidFormatException The specified key is not in the correct + * format for a long long + */ +- (long long)longLongValueForKey: (OFString *)key + defaultValue: (long long)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 arrayValueForKey:), the value + * of the first key/value pair found is returned. + * + * @param key The key for which the bool should be returned + * @param defaultValue The value to return if the key does not exist + * @return The bool for the specified key or the specified default value if it + * does not exist + * @throw OFInvalidFormatException The specified key is not in the correct + * format for a bool + */ +- (bool)boolValueForKey: (OFString *)key 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 arrayValueForKey:), the value + * of the first key/value pair found is returned. + * + * @param key The key for which the float should be returned + * @param defaultValue The value to return if the key does not exist + * @return The float for the specified key or the specified default value if it + * does not exist + * @throw OFInvalidFormatException The specified key is not in the correct + * format for a float + */ +- (float)floatValueForKey: (OFString *)key 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 arrayValueForKey:), the value + * of the first key/value pair found is returned. + * + * @param key The key for which the double should be returned + * @param defaultValue The value to return if the key does not exist + * @return The double for the specified key or the specified default value if + * it does not exist + * @throw OFInvalidFormatException The specified key is not in the correct + * format for a double + */ +- (double)doubleValueForKey: (OFString *)key defaultValue: (double)defaultValue; + +/** + * @brief Returns an array of strings 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 section. 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 OF_GENERIC(OFString *) *)arrayValueForKey: (OFString *)key; + +/** + * @brief Sets the value of the specified key to the specified string. + * + * If the specified key is a multi-key (see @ref arrayValueForKey:), the value + * of the first key/value pair found is changed. + * + * @param stringValue The string to which the key should be set + * @param key The key for which the new value should be set + */ +- (void)setStringValue: (OFString *)stringValue forKey: (OFString *)key; + +/** + * @brief Sets the value of the specified key to the specified long long. + * + * If the specified key is a multi-key (see @ref arrayValueForKey:), the value + * of the first key/value pair found is changed. + * + * @param longLongValue The long long to which the key should be set + * @param key The key for which the new value should be set + */ +- (void)setLongLongValue: (long long)longLongValue 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 arrayValueForKey:), the value + * of the first key/value pair found is changed. + * + * @param boolValue The bool to which the key should be set + * @param key The key for which the new value should be set + */ +- (void)setBoolValue: (bool)boolValue 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 arrayValueForKey:), the value + * of the first key/value pair found is changed. + * + * @param floatValue The float to which the key should be set + * @param key The key for which the new value should be set + */ +- (void)setFloatValue: (float)floatValue 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 arrayValueForKey:), the value + * of the first key/value pair found is changed. + * + * @param doubleValue The double to which the key should be set + * @param key The key for which the new value should be set + */ +- (void)setDoubleValue: (double)doubleValue 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 arrayValueForKey: for more information about multi-keys. + * + * @param arrayValue 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)setArrayValue: (OFArray OF_GENERIC(OFString *) *)arrayValue + forKey: (OFString *)key; + +/** + * @brief Removes the value for the specified key + * + * If the specified key is a multi-key (see @ref arrayValueForKey:), all + * key/value pairs matching the specified key are removed. + * + * @param key The key of the value to remove + */ +- (void)removeValueForKey: (OFString *)key; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFINISection.m Index: src/OFINISection.m ================================================================== --- /dev/null +++ src/OFINISection.m @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2008-2024 Jonathan Schleifer + * + * All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3.0 only, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3.0 for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * version 3.0 along with this program. If not, see + * . + */ + +#include "config.h" + +#import "OFINISection.h" +#import "OFINISection+Private.h" +#import "OFArray.h" +#import "OFString.h" +#import "OFStream.h" + +#import "OFInvalidArgumentException.h" +#import "OFInvalidFormatException.h" + +@interface OFINISectionPair: OFObject +{ +@public + OFString *_key, *_value; +} +@end + +@interface OFINISectionComment: OFObject +{ +@public + OFString *_comment; +} +@end + +static OFString * +escapeString(OFString *string) +{ + OFMutableString *mutableString; + + /* FIXME: Optimize */ + if (![string hasPrefix: @" "] && ![string hasPrefix: @"\t"] && + ![string hasPrefix: @"\f"] && ![string hasSuffix: @" "] && + ![string hasSuffix: @"\t"] && ![string hasSuffix: @"\f"] && + ![string containsString: @"\""]) + return string; + + mutableString = [[string mutableCopy] autorelease]; + + [mutableString replaceOccurrencesOfString: @"\\" withString: @"\\\\"]; + [mutableString replaceOccurrencesOfString: @"\f" withString: @"\\f"]; + [mutableString replaceOccurrencesOfString: @"\r" withString: @"\\r"]; + [mutableString replaceOccurrencesOfString: @"\n" withString: @"\\n"]; + [mutableString replaceOccurrencesOfString: @"\"" withString: @"\\\""]; + + [mutableString insertString: @"\"" atIndex: 0]; + [mutableString appendString: @"\""]; + + [mutableString makeImmutable]; + + return mutableString; +} + +static OFString * +unescapeString(OFString *string) +{ + OFMutableString *mutableString; + + if (![string hasPrefix: @"\""] || ![string hasSuffix: @"\""]) + return string; + + string = [string substringWithRange: OFMakeRange(1, string.length - 2)]; + mutableString = [[string mutableCopy] autorelease]; + + [mutableString replaceOccurrencesOfString: @"\\f" withString: @"\f"]; + [mutableString replaceOccurrencesOfString: @"\\r" withString: @"\r"]; + [mutableString replaceOccurrencesOfString: @"\\n" withString: @"\n"]; + [mutableString replaceOccurrencesOfString: @"\\\"" withString: @"\""]; + [mutableString replaceOccurrencesOfString: @"\\\\" withString: @"\\"]; + + [mutableString makeImmutable]; + + return mutableString; +} + +@implementation OFINISectionPair +- (void)dealloc +{ + [_key release]; + [_value release]; + + [super dealloc]; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"%@ = %@", _key, _value]; +} +@end + +@implementation OFINISectionComment +- (void)dealloc +{ + [_comment release]; + + [super dealloc]; +} + +- (OFString *)description +{ + return [[_comment copy] autorelease]; +} +@end + +@implementation OFINISection +@synthesize name = _name; + +- (instancetype)of_initWithName: (OFString *)name OF_DIRECT +{ + self = [super init]; + + @try { + _name = [name copy]; + _lines = [[OFMutableArray alloc] init]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (void)dealloc +{ + [_name release]; + [_lines release]; + + [super dealloc]; +} + +- (void)of_parseLine: (OFString *)line +{ + if (![line hasPrefix: @";"]) { + OFINISectionPair *pair; + OFString *key, *value; + size_t pos; + + if (_name == nil) + @throw [OFInvalidFormatException exception]; + + pair = [[[OFINISectionPair alloc] init] autorelease]; + + if ((pos = [line rangeOfString: @"="].location) == OFNotFound) + @throw [OFInvalidFormatException exception]; + + key = unescapeString([line substringToIndex: pos] + .stringByDeletingEnclosingWhitespaces); + value = unescapeString([line substringFromIndex: pos + 1] + .stringByDeletingEnclosingWhitespaces); + + pair->_key = [key copy]; + pair->_value = [value copy]; + + [_lines addObject: pair]; + } else { + OFINISectionComment *comment = + [[[OFINISectionComment alloc] init] autorelease]; + + comment->_comment = [line copy]; + + [_lines addObject: comment]; + } +} + +- (OFString *)stringValueForKey: (OFString *)key +{ + return [self stringValueForKey: key defaultValue: nil]; +} + +- (OFString *)stringValueForKey: (OFString *)key + defaultValue: (OFString *)defaultValue +{ + for (id line in _lines) { + OFINISectionPair *pair; + + if (![line isKindOfClass: [OFINISectionPair class]]) + continue; + + pair = line; + + if ([pair->_key isEqual: key]) + return [[pair->_value copy] autorelease]; + } + + return defaultValue; +} + +- (long long)longLongValueForKey: (OFString *)key + defaultValue: (long long)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFString *value = [self stringValueForKey: key defaultValue: nil]; + long long ret; + + if (value != nil) + ret = [value longLongValueWithBase: 0]; + else + ret = defaultValue; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (bool)boolValueForKey: (OFString *)key defaultValue: (bool)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFString *value = [self stringValueForKey: key defaultValue: nil]; + bool ret; + + if (value != nil) { + if ([value isEqual: @"true"]) + ret = true; + else if ([value isEqual: @"false"]) + ret = false; + else + @throw [OFInvalidFormatException exception]; + } else + ret = defaultValue; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (float)floatValueForKey: (OFString *)key defaultValue: (float)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFString *value = [self stringValueForKey: key defaultValue: nil]; + float ret; + + if (value != nil) + ret = value.floatValue; + else + ret = defaultValue; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (double)doubleValueForKey: (OFString *)key defaultValue: (double)defaultValue +{ + void *pool = objc_autoreleasePoolPush(); + OFString *value = [self stringValueForKey: key defaultValue: nil]; + double ret; + + if (value != nil) + ret = value.doubleValue; + else + ret = defaultValue; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (OFArray OF_GENERIC(OFString *) *)arrayValueForKey: (OFString *)key +{ + OFMutableArray *ret = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + + for (id line in _lines) { + OFINISectionPair *pair; + + if (![line isKindOfClass: [OFINISectionPair class]]) + continue; + + pair = line; + + if ([pair->_key isEqual: key]) + [ret addObject: [[pair->_value copy] autorelease]]; + } + + objc_autoreleasePoolPop(pool); + + [ret makeImmutable]; + + return ret; +} + +- (void)setStringValue: (OFString *)string forKey: (OFString *)key +{ + void *pool = objc_autoreleasePoolPush(); + OFINISectionPair *pair; + + for (id line in _lines) { + if (![line isKindOfClass: [OFINISectionPair class]]) + continue; + + pair = line; + + if ([pair->_key isEqual: key]) { + OFString *old = pair->_value; + pair->_value = [string copy]; + [old release]; + + objc_autoreleasePoolPop(pool); + + return; + } + } + + pair = [[[OFINISectionPair alloc] init] autorelease]; + pair->_key = nil; + pair->_value = nil; + + @try { + pair->_key = [key copy]; + pair->_value = [string copy]; + [_lines addObject: pair]; + } @catch (id e) { + [pair->_key release]; + [pair->_value release]; + + @throw e; + } + + objc_autoreleasePoolPop(pool); +} + +- (void)setLongLongValue: (long long)longLongValue forKey: (OFString *)key +{ + void *pool = objc_autoreleasePoolPush(); + + [self setStringValue: [OFString stringWithFormat: + @"%lld", longLongValue] + forKey: key]; + + objc_autoreleasePoolPop(pool); +} + +- (void)setBoolValue: (bool)boolValue forKey: (OFString *)key +{ + [self setStringValue: (boolValue ? @"true" : @"false") forKey: key]; +} + +- (void)setFloatValue: (float)floatValue forKey: (OFString *)key +{ + void *pool = objc_autoreleasePoolPush(); + + [self setStringValue: [OFString stringWithFormat: @"%g", floatValue] + forKey: key]; + + objc_autoreleasePoolPop(pool); +} + +- (void)setDoubleValue: (double)doubleValue forKey: (OFString *)key +{ + void *pool = objc_autoreleasePoolPush(); + + [self setStringValue: [OFString stringWithFormat: @"%g", doubleValue] + forKey: key]; + + objc_autoreleasePoolPop(pool); +} + +- (void)setArrayValue: (OFArray OF_GENERIC(OFString *) *)arrayValue + forKey: (OFString *)key +{ + void *pool; + OFMutableArray *pairs; + id const *lines; + size_t count; + bool replaced; + + if (arrayValue.count == 0) { + [self removeValueForKey: key]; + return; + } + + pool = objc_autoreleasePoolPush(); + + pairs = [OFMutableArray arrayWithCapacity: arrayValue.count]; + + for (OFString *string in arrayValue) { + OFINISectionPair *pair; + + if (![string isKindOfClass: [OFString class]]) + @throw [OFInvalidArgumentException exception]; + + pair = [[[OFINISectionPair alloc] init] autorelease]; + pair->_key = [key copy]; + pair->_value = [string copy]; + + [pairs addObject: pair]; + } + + lines = _lines.objects; + count = _lines.count; + replaced = false; + + for (size_t i = 0; i < count; i++) { + OFINISectionPair *pair; + + if (![lines[i] isKindOfClass: [OFINISectionPair 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 += arrayValue.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(); + id const *lines = _lines.objects; + size_t count = _lines.count; + + for (size_t i = 0; i < count; i++) { + OFINISectionPair *pair; + + if (![lines[i] isKindOfClass: [OFINISectionPair class]]) + continue; + + pair = lines[i]; + + if ([pair->_key isEqual: key]) { + [_lines removeObjectAtIndex: i]; + + lines = _lines.objects; + count = _lines.count; + + i--; /* Continue at same position */ + continue; + } + } + + objc_autoreleasePoolPop(pool); +} + +- (bool)of_writeToStream: (OFStream *)stream + encoding: (OFStringEncoding)encoding + first: (bool)first +{ + if (_lines.count == 0) + return false; + + if (_name != nil) { + if (first) + [stream writeFormat: @"[%@]\r\n", _name]; + else + [stream writeFormat: @"\r\n[%@]\r\n", _name]; + } + + for (id line in _lines) { + if ([line isKindOfClass: [OFINISectionComment class]]) { + OFINISectionComment *comment = line; + [stream writeFormat: @"%@\r\n", comment->_comment]; + } else if ([line isKindOfClass: [OFINISectionPair class]]) { + OFINISectionPair *pair = line; + OFString *key = escapeString(pair->_key); + OFString *value = escapeString(pair->_value); + OFString *tmp = [OFString + stringWithFormat: @"%@=%@\r\n", key, value]; + [stream writeString: tmp encoding: encoding]; + } else + @throw [OFInvalidArgumentException exception]; + } + + return true; +} + +- (OFString *)description +{ + return [OFString stringWithFormat: @"<%@ \"%@\": %@>", + self.class, _name, _lines]; +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -75,10 +75,11 @@ #import "OFFileManager.h" #ifdef OF_HAVE_FILES # import "OFFile.h" #endif #import "OFINIFile.h" +#import "OFINICategory.h" #import "OFSettings.h" #ifdef OF_HAVE_SOCKETS # import "OFStreamSocket.h" # import "OFDatagramSocket.h" # import "OFSequencedPacketSocket.h" Index: tests/OFINIFileTests.m ================================================================== --- tests/OFINIFileTests.m +++ tests/OFINIFileTests.m @@ -47,62 +47,62 @@ [_file release]; [super dealloc]; } -- (void)testCategoryForName +- (void)testSectionForName { - OTAssertNotNil([_file categoryForName: @"tests"]); - OTAssertNotNil([_file categoryForName: @"foobar"]); - OTAssertNotNil([_file categoryForName: @"types"]); + OTAssertNotNil([_file sectionForName: @"tests"]); + OTAssertNotNil([_file sectionForName: @"foobar"]); + OTAssertNotNil([_file sectionForName: @"types"]); } - (void)testStringValueForKey { OTAssertEqualObjects( - [[_file categoryForName: @"tests"] stringValueForKey: @"foo"], + [[_file sectionForName: @"tests"] stringValueForKey: @"foo"], @"bar"); - OTAssertEqualObjects([[_file categoryForName: @"foobar"] + OTAssertEqualObjects([[_file sectionForName: @"foobar"] stringValueForKey: @"quxquxqux"], @"hello\"wörld"); } - (void)testLongLongValueForKeyDefaultValue { - OTAssertEqual([[_file categoryForName: @"types"] + OTAssertEqual([[_file sectionForName: @"types"] longLongValueForKey: @"integer" defaultValue: 2], 0x20); } - (void)testBoolValueForKeyDefaultValue { - OTAssertTrue([[_file categoryForName: @"types"] + OTAssertTrue([[_file sectionForName: @"types"] boolValueForKey: @"bool" defaultValue: false]); } - (void)testFloatValueForKeyDefaultValue { - OTAssertEqual([[_file categoryForName: @"types"] + OTAssertEqual([[_file sectionForName: @"types"] floatValueForKey: @"float" defaultValue: 1], 0.5f); } - (void)testDoubleValueForKeyDefaultValue { - OTAssertEqual([[_file categoryForName: @"types"] + OTAssertEqual([[_file sectionForName: @"types"] doubleValueForKey: @"double" defaultValue: 3], 0.25); } - (void)testArrayValueForKey { - OFINICategory *types = [_file categoryForName: @"types"]; + OFINISection *types = [_file sectionForName: @"types"]; OFArray *array = [OFArray arrayWithObjects: @"1", @"2", nil]; OTAssertEqualObjects([types arrayValueForKey: @"array1"], array); OTAssertEqualObjects([types arrayValueForKey: @"array2"], array); OTAssertEqualObjects([types arrayValueForKey: @"array3"], @@ -109,11 +109,11 @@ [OFArray array]); } - (void)testWriteToIRIEncoding { - OFString *expectedOutput = @"; Comment before categories\r\n" + OFString *expectedOutput = @"; Comment before sections\r\n" @"\r\n" @"[tests]\r\n" @"foo=baz\r\n" @"foobar=baz\r\n" @";comment\r\n" @@ -131,13 +131,13 @@ @"bool=false\r\n" @"float=0.25\r\n" @"array1=foo\r\n" @"array1=bar\r\n" @"double=0.75\r\n"; - OFINICategory *tests = [_file categoryForName: @"tests"]; - OFINICategory *foobar = [_file categoryForName: @"foobar"]; - OFINICategory *types = [_file categoryForName: @"types"]; + OFINISection *tests = [_file sectionForName: @"tests"]; + OFINISection *foobar = [_file sectionForName: @"foobar"]; + OFINISection *types = [_file sectionForName: @"types"]; OFArray *array = [OFArray arrayWithObjects: @"foo", @"bar", nil]; #if defined(OF_HAVE_FILES) && !defined(OF_NINTENDO_DS) OFIRI *writeIRI; #endif @@ -175,11 +175,11 @@ #else (void)expectedOutput; #endif } -- (void)testValuePairOutsideOfCategoryRejected +- (void)testPairOutsideOfSectionRejected { OFIRI *IRI = [OFIRI IRIWithString: @"embedded:testfile_broken.ini"]; OFRegisterEmbeddedFile(@"testfile_broken.ini", (const uint8_t *)"; comment\r\na=b", 14); Index: tests/testfile.ini ================================================================== --- tests/testfile.ini +++ tests/testfile.ini @@ -1,6 +1,6 @@ -; Comment before categories +; Comment before sections [tests] foo = bar foobar=baz ;comment