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

		if ([key hasSuffix: @" "]) {
			key = [key stringByDeletingEnclosingWhitespaces];
			value = [value stringByDeletingEnclosingWhitespaces];
		}
		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
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
{
	if (bool_)
		[self setString: @"true"
			 forKey: key];
	[self setString: (bool_ ? @"true" : @"false")
		 forKey: key];
	else
		[self setString: @"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
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",
			[stream writeFormat: @"%@=%@\n", key, value];
					     pair->_key, pair->_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
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 OFINICategory;

/*!
 * @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
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
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 = [OFFile fileWithPath: path
	OFFile *file;
				       mode: @"r"];
	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
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 "OFINICategory.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
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
	    @"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 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
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"]))
	    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
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
qux=" asd"
"quxqux " = asd
quxquxqux="hello\"world"
qux2="\f"

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