/* * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 * Jonathan Schleifer * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #import "OFINIFile.h" #import "OFArray.h" #import "OFString.h" #import "OFFile.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "autorelease.h" #import "macros.h" @interface OFINIFile (OF_PRIVATE_CATEGORY) - (void)OF_parseFile: (OFString*)path; @end @interface OFINICategory (OF_PRIVATE_CATEGORY) - (instancetype)OF_init; - (void)OF_parseLine: (OFString*)line; - (bool)OF_writeToStream: (OFStream*)stream first: (bool)first; @end @interface OFINICategory_Pair: OFObject { @public OFString *_key, *_value; } @end @interface OFINICategory_Comment: OFObject { @public OFString *_comment; } @end static bool isWhitespaceLine(OFString *line) { const char *cString = [line UTF8String]; size_t i, length = [line UTF8StringLength]; for (i = 0; i < length; i++) { switch (cString[i]) { case ' ': case '\t': case '\n': case '\r': continue; default: return false; } } return true; } @implementation OFINICategory - (instancetype)OF_init { self = [super init]; @try { _lines = [[OFMutableArray alloc] init]; } @catch (id e) { [self release]; @throw e; } return self; } - init { OF_INVALID_INIT_METHOD } - (void)dealloc { [_name release]; [_lines release]; [super dealloc]; } - (void)setName: (OFString*)name { OF_SETTER(_name, name, true, true) } - (OFString*)name { OF_GETTER(_name, true) } - (void)OF_parseLine: (OFString*)line { if (![line hasPrefix: @";"]) { OFINICategory_Pair *pair = [[[OFINICategory_Pair alloc] init] autorelease]; OFString *key, *value; size_t pos; if ((pos = [line rangeOfString: @"="].location) == OF_NOT_FOUND) @throw [OFInvalidFormatException exception]; key = [line substringWithRange: of_range(0, pos)]; value = [line substringWithRange: of_range(pos + 1, [line length] - pos - 1)]; if ([key hasSuffix: @" "]) { key = [key stringByDeletingEnclosingWhitespaces]; value = [value stringByDeletingEnclosingWhitespaces]; } pair->_key = [key copy]; pair->_value = [value copy]; [_lines addObject: pair]; } else { OFINICategory_Comment *comment = [[[OFINICategory_Comment alloc] init] autorelease]; comment->_comment = [line copy]; [_lines addObject: comment]; } } - (OFString*)stringForKey: (OFString*)key { return [self stringForKey: key defaultValue: nil]; } - (OFString*)stringForKey: (OFString*)key defaultValue: (OFString*)defaultValue { 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]) { OFString *value = [pair->_value copy]; objc_autoreleasePoolPop(pool); return [value autorelease]; } } objc_autoreleasePoolPop(pool); return defaultValue; } - (intmax_t)integerForKey: (OFString*)key defaultValue: (intmax_t)defaultValue { void *pool = objc_autoreleasePoolPush(); OFString *value = [self stringForKey: key defaultValue: nil]; intmax_t ret; if (value != nil) { if ([value hasPrefix: @"0x"] || [value hasPrefix: @"$"]) ret = [value hexadecimalValue]; else ret = [value decimalValue]; } else ret = defaultValue; objc_autoreleasePoolPop(pool); return ret; } - (bool)boolForKey: (OFString*)key defaultValue: (bool)defaultValue { void *pool = objc_autoreleasePoolPush(); OFString *value = [self stringForKey: 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)floatForKey: (OFString*)key defaultValue: (float)defaultValue { void *pool = objc_autoreleasePoolPush(); OFString *value = [self stringForKey: key defaultValue: nil]; float ret; if (value != nil) ret = [value floatValue]; else ret = defaultValue; objc_autoreleasePoolPop(pool); return ret; } - (double)doubleForKey: (OFString*)key defaultValue: (double)defaultValue { void *pool = objc_autoreleasePoolPush(); OFString *value = [self stringForKey: key defaultValue: nil]; double ret; if (value != nil) ret = [value doubleValue]; else ret = defaultValue; objc_autoreleasePoolPop(pool); return ret; } - (void)setString: (OFString*)string forKey: (OFString*)key { void *pool = objc_autoreleasePoolPush(); OFEnumerator *enumerator = [_lines objectEnumerator]; OFINICategory_Pair *pair; id line; while ((line = [enumerator nextObject]) != nil) { if (![line isKindOfClass: [OFINICategory_Pair class]]) continue; pair = line; if ([pair->_key isEqual: key]) { OFString *old = pair->_value; pair->_value = [string copy]; [old release]; objc_autoreleasePoolPop(pool); return; } } pair = [[[OFINICategory_Pair alloc] init] autorelease]; pair->_key = [key copy]; pair->_value = [string copy]; [_lines addObject: pair]; objc_autoreleasePoolPop(pool); } - (void)setInteger: (intmax_t)integer forKey: (OFString*)key { void *pool = objc_autoreleasePoolPush(); [self setString: [OFString stringWithFormat: @"%jd", integer] forKey: key]; objc_autoreleasePoolPop(pool); } - (void)setBool: (bool)bool_ forKey: (OFString*)key { if (bool_) [self setString: @"true" forKey: key]; else [self setString: @"false" forKey: key]; } - (void)setFloat: (float)float_ forKey: (OFString*)key { void *pool = objc_autoreleasePoolPush(); [self setString: [OFString stringWithFormat: @"%g", float_] forKey: key]; objc_autoreleasePoolPop(pool); } - (void)setDouble: (double)double_ forKey: (OFString*)key { void *pool = objc_autoreleasePoolPush(); [self setString: [OFString stringWithFormat: @"%lg", double_] forKey: key]; objc_autoreleasePoolPop(pool); } - (void)removeValueForKey: (OFString*)key { void *pool = objc_autoreleasePoolPush(); OFEnumerator *enumerator = [_lines objectEnumerator]; size_t i; id line; i = 0; while ((line = [enumerator nextObject]) != nil) { OFINICategory_Pair *pair; if (![line isKindOfClass: [OFINICategory_Pair class]]) { i++; continue; } pair = line; if ([pair->_key isEqual: key]) { [_lines removeObjectAtIndex: i]; break; } i++; } objc_autoreleasePoolPop(pool); } - (bool)OF_writeToStream: (OFStream*)stream first: (bool)first { OFEnumerator *enumerator; id line; if ([_lines count] == 0) return false; if (first) [stream writeFormat: @"[%@]\n", _name]; else [stream writeFormat: @"\n[%@]\n", _name]; enumerator = [_lines objectEnumerator]; while ((line = [enumerator nextObject]) != nil) { if ([line isKindOfClass: [OFINICategory_Comment class]]) { OFINICategory_Comment *comment = line; [stream writeLine: comment->_comment]; } else if ([line isKindOfClass: [OFINICategory_Pair class]]) { OFINICategory_Pair *pair = line; [stream writeFormat: @"%@=%@\n", pair->_key, pair->_value]; } else @throw [OFInvalidArgumentException exception]; } return true; } @end @implementation OFINICategory_Pair - (void)dealloc { [_key release]; [_value release]; [super dealloc]; } @end @implementation OFINICategory_Comment - (void)dealloc { [_comment release]; [super dealloc]; } @end @implementation OFINIFile + (instancetype)fileWithPath: (OFString*)path { return [[[self alloc] initWithPath: path] autorelease]; } - init { OF_INVALID_INIT_METHOD } - initWithPath: (OFString*)path { self = [super init]; @try { _categories = [[OFMutableArray alloc] init]; [self OF_parseFile: path]; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_categories release]; [super dealloc]; } - (OFINICategory*)categoryForName: (OFString*)name { void *pool = objc_autoreleasePoolPush(); OFEnumerator *enumerator = [_categories objectEnumerator]; OFINICategory *category; while ((category = [enumerator nextObject]) != nil) { if ([[category name] isEqual: name]) { OFINICategory *ret = [category retain]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } } category = [[[OFINICategory alloc] OF_init] autorelease]; [category setName: name]; [_categories addObject: category]; [category retain]; objc_autoreleasePoolPop(pool); return [category autorelease]; } - (void)OF_parseFile: (OFString*)path { void *pool = objc_autoreleasePoolPush(); OFFile *file = [OFFile fileWithPath: path mode: @"r"]; OFINICategory *category = nil; OFString *line; while ((line = [file readLine]) != nil) { if (isWhitespaceLine(line)) continue; if ([line hasPrefix: @"["]) { OFString *categoryName; if (![line hasSuffix: @"]"]) @throw [OFInvalidFormatException exception]; categoryName = [line substringWithRange: of_range(1, [line length] - 2)]; category = [[[OFINICategory alloc] OF_init] autorelease]; [category setName: categoryName]; [_categories addObject: category]; } else { if (category == nil) @throw [OFInvalidFormatException exception]; [category OF_parseLine: line]; } } objc_autoreleasePoolPop(pool); } - (void)writeToFile: (OFString*)path { 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 first: first]) first = false; objc_autoreleasePoolPop(pool); } @end