ObjFW  Check-in [12c5b7ee91]

Overview
Comment:OFINIFile: Add support for quoted keys / values

This is a much more logical way to handle leading and trailing
whitespaces and also seems to be used by a few other INI
implementations.

Additionally, this imports OFINICategory.h in OFINIFile.h so that
importing OFINIFile.h is enough - this should be less confusing - and
allows opening non-existant files, treating them like an empty file.

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 12c5b7ee912a9e4ba335e0c97b62a55f8b8286d41b92dae5ad25716c3be3abe3
User & Date: js on 2014-06-12 13:43:41
Other Links: manifest | tags
Context
2014-06-13
16:41
Clean up OFINIFileTests.m check-in: 34cf1fb8c2 user: js tags: trunk
2014-06-12
13:43
OFINIFile: Add support for quoted keys / values check-in: 12c5b7ee91 user: js tags: trunk
2014-05-31
17:57
OFStream: Add -[hasDataInReadBuffer]. check-in: acc999a75e user: js tags: trunk
Changes

Modified src/OFINICategory.m from [900a1099e7] to [8e99773efd].

35
36
37
38
39
40
41




























































42
43
44
45
46
47
48

@interface OFINICategory_Comment: OFObject
{
@public
	OFString *_comment;
}
@end





























































@implementation OFINICategory_Pair
- (void)dealloc
{
	[_key release];
	[_value release];








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

@interface OFINICategory_Comment: 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 prependString: @"\""];
	[mutableString appendString: @"\""];

	[mutableString makeImmutable];

	return mutableString;
}

static OFString*
unescapeString(OFString *string)
{
	OFMutableString *mutableString;

	if (![string hasPrefix: @"\""] || ![string hasSuffix: @"\""])
		return string;

	string = [string substringWithRange: of_range(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 OFINICategory_Pair
- (void)dealloc
{
	[_key release];
	[_value release];

108
109
110
111
112
113
114
115
116
117
118


119
120
121
122
123
124
125
		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 =







<
|
|
|
>
>







168
169
170
171
172
173
174

175
176
177
178
179
180
181
182
183
184
185
186
		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)];


		key = [key stringByDeletingEnclosingWhitespaces];
		value = [value stringByDeletingEnclosingWhitespaces];

		key = unescapeString(key);
		value = unescapeString(value);

		pair->_key = [key copy];
		pair->_value = [value copy];

		[_lines addObject: pair];
	} else {
		OFINICategory_Comment *comment =
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308

	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();








<
|
|
<
<
<







350
351
352
353
354
355
356

357
358



359
360
361
362
363
364
365

	objc_autoreleasePoolPop(pool);
}

- (void)setBool: (bool)bool_
	 forKey: (OFString*)key
{

	[self setString: (bool_ ? @"true" : @"false")
		 forKey: key];



}

- (void)setFloat: (float)float_
	  forKey: (OFString*)key
{
	void *pool = objc_autoreleasePoolPush();

369
370
371
372
373
374
375



376
377
378
379
380
381
382
383
384
	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







>
>
>
|
<







426
427
428
429
430
431
432
433
434
435
436

437
438
439
440
441
442
443
	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;
			OFString *key = escapeString(pair->_key);
			OFString *value = escapeString(pair->_value);

			[stream writeFormat: @"%@=%@\n", key, value];

		} else
			@throw [OFInvalidArgumentException exception];
	}

	return true;
}
@end

Modified src/OFINIFile.h from [48c881a928] to [d3236d0058].

11
12
13
14
15
16
17

18
19
20
21
22
23
24
25
26
27
28
 * 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 "OFObject.h"


@class OFMutableArray;
@class OFString;
@class OFINICategory;

/*!
 * @class OFINIFile OFINIFile.h ObjFW/OFINIFile.h
 *
 * @brief A class for reading, creating and modifying INI files.
 */
@interface OFINIFile: OFObject







>



<







11
12
13
14
15
16
17
18
19
20
21

22
23
24
25
26
27
28
 * 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 "OFObject.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.
 */
@interface OFINIFile: OFObject

Modified src/OFINIFile.m from [68683c719e] to [38c671870e].

18
19
20
21
22
23
24

25
26
27
28
29
30
31
#import "OFArray.h"
#import "OFString.h"
#import "OFFile.h"
#import "OFINICategory.h"
#import "OFINICategory+Private.h"

#import "OFInvalidFormatException.h"


#import "autorelease.h"
#import "macros.h"

@interface OFINIFile (OF_PRIVATE_CATEGORY)
- (void)OF_parseFile: (OFString*)path;
@end







>







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#import "OFArray.h"
#import "OFString.h"
#import "OFFile.h"
#import "OFINICategory.h"
#import "OFINICategory+Private.h"

#import "OFInvalidFormatException.h"
#import "OFOpenFileFailedException.h"

#import "autorelease.h"
#import "macros.h"

@interface OFINIFile (OF_PRIVATE_CATEGORY)
- (void)OF_parseFile: (OFString*)path;
@end
111
112
113
114
115
116
117
118
119
120
121











122
123
124
125
126
127
128

	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;







|
<


>
>
>
>
>
>
>
>
>
>
>







112
113
114
115
116
117
118
119

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

	return [category autorelease];
}

- (void)OF_parseFile: (OFString*)path
{
	void *pool = objc_autoreleasePoolPush();
	OFFile *file;

	OFINICategory *category = nil;
	OFString *line;

	@try {
		file = [OFFile fileWithPath: path
				       mode: @"r"];
	} @catch (OFOpenFileFailedException *e) {
		/* Handle missing file like an empty file */
		if ([e errNo] == ENOENT)
			return;

		@throw e;
	}

	while ((line = [file readLine]) != nil) {
		if (isWhitespaceLine(line))
			continue;

		if ([line hasPrefix: @"["]) {
			OFString *categoryName;

Modified src/ObjFW.h from [9b2458d655] to [8316d27014].

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

#import "OFStream.h"
#import "OFStdIOStream.h"
#import "OFDeflateStream.h"
#ifdef OF_HAVE_FILES
# import "OFFile.h"
# import "OFINIFile.h"
# import "OFINICategory.h"
# import "OFZIPArchive.h"
# import "OFZIPArchiveEntry.h"
#endif
#ifdef OF_HAVE_SOCKETS
# import "OFStreamSocket.h"
# import "OFTCPSocket.h"
# import "OFUDPSocket.h"







<







46
47
48
49
50
51
52

53
54
55
56
57
58
59

#import "OFStream.h"
#import "OFStdIOStream.h"
#import "OFDeflateStream.h"
#ifdef OF_HAVE_FILES
# import "OFFile.h"
# import "OFINIFile.h"

# import "OFZIPArchive.h"
# import "OFZIPArchiveEntry.h"
#endif
#ifdef OF_HAVE_SOCKETS
# import "OFStreamSocket.h"
# import "OFTCPSocket.h"
# import "OFUDPSocket.h"

Modified tests/OFINIFileTests.m from [7ea6705870] to [556f5814fc].

40
41
42
43
44
45
46
47


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66




67
68
69
70
71
72
73
	    @"foo=baz" NL
	    @"foobar=baz" NL
	    @";comment" NL
	    @"new=new" NL
	    NL
	    @"[foobar]" NL
	    @";foobarcomment" NL
	    @"qux= asd" NL


	    NL
	    @"[types]" NL
	    @"integer=16" NL
	    @"bool=false" NL
	    @"float=0.25" NL
	    @"double=0.75" NL;
	OFINIFile *file;
	OFINICategory *category;

	TEST(@"+[fileWithPath:]",
	    (file = [OFINIFile fileWithPath: @"testfile.ini"]))

	TEST(@"-[categoryForName:]",
	    (category = [file categoryForName: @"tests"]))

	module = @"OFINICategory";

	TEST(@"-[stringForKey:]",
	    [[category stringForKey: @"foo"] isEqual: @"bar"])





	TEST(@"-[setString:forKey:]",
	    R([category setString: @"baz"
			   forKey: @"foo"]) &&
	    R([category setString: @"new"
			   forKey: @"new"]))








|
>
>


















|
>
>
>
>







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
	    @"foo=baz" NL
	    @"foobar=baz" NL
	    @";comment" NL
	    @"new=new" NL
	    NL
	    @"[foobar]" NL
	    @";foobarcomment" NL
	    @"qux=\" asd\"" NL
	    @"quxquxqux=\"hello\\\"world\"" NL
	    @"qux2=\"\\f\"" NL
	    NL
	    @"[types]" NL
	    @"integer=16" NL
	    @"bool=false" NL
	    @"float=0.25" NL
	    @"double=0.75" NL;
	OFINIFile *file;
	OFINICategory *category;

	TEST(@"+[fileWithPath:]",
	    (file = [OFINIFile fileWithPath: @"testfile.ini"]))

	TEST(@"-[categoryForName:]",
	    (category = [file categoryForName: @"tests"]))

	module = @"OFINICategory";

	TEST(@"-[stringForKey:]",
	    [[category stringForKey: @"foo"] isEqual: @"bar"] &&
	    (category = [file categoryForName: @"foobar"]) &&
	    [[category stringForKey: @"quxquxqux"] isEqual: @"hello\"world"])

	category = [file categoryForName: @"tests"];

	TEST(@"-[setString:forKey:]",
	    R([category setString: @"baz"
			   forKey: @"foo"]) &&
	    R([category setString: @"new"
			   forKey: @"new"]))

100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

	TEST(@"-[setDouble:forKey:]", R([category setDouble: 0.75
						     forKey: @"double"]))

	category = [file categoryForName: @"foobar"];

	TEST(@"-[removeValueForKey:]",
	    R([category removeValueForKey: @"quxqux"]))

	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"]







|







106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

	TEST(@"-[setDouble:forKey:]", R([category setDouble: 0.75
						     forKey: @"double"]))

	category = [file categoryForName: @"foobar"];

	TEST(@"-[removeValueForKey:]",
	    R([category removeValueForKey: @"quxqux "]))

	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"]

Modified tests/testfile.ini from [89e5125eda] to [cf73f59c64].

1
2
3
4
5
6
7
8
9


10
11
12
13
14
15
[tests]
foo = bar
foobar=baz
;comment

[foobar]
;foobarcomment
qux= asd
quxqux =asd



[types]
integer = 0x20
bool = true
float = 0.5
double = 0.25







|
|
>
>






1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[tests]
foo = bar
foobar=baz
;comment

[foobar]
;foobarcomment
qux=" asd"
"quxqux " = asd
quxquxqux="hello\"world"
qux2="\f"

[types]
integer = 0x20
bool = true
float = 0.5
double = 0.25