ObjFW  Check-in [44de69ef31]

Overview
Comment:OFINIFile: Add support for different encodings
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 44de69ef31988e5fae6537fa5fdc4c6273c5f593cebd45425c34feb73dbe02f9
User & Date: js on 2014-06-14 09:44:45
Other Links: manifest | tags
Context
2014-06-16
15:06
Make return type of -[OFArray objects] const check-in: 68d32a92c1 user: js tags: trunk
2014-06-14
09:44
OFINIFile: Add support for different encodings check-in: 44de69ef31 user: js tags: trunk
2014-06-13
16:41
Clean up OFINIFileTests.m check-in: 34cf1fb8c2 user: js tags: trunk
Changes

Modified src/OFINICategory+Private.h from [141e6d31f9] to [963e590a59].

11
12
13
14
15
16
17



18
19
20
21
22
23
24

25
26
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30







+
+
+







+


 * 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 "OFINICategory.h"
#import "OFString.h"

#import "macros.h"

@class OFStream;

@interface OFINICategory (OF_PRIVATE_CATEGORY)
- (instancetype)OF_init;
- (void)OF_parseLine: (OFString*)line;
- (bool)OF_writeToStream: (OFStream*)stream
		encoding: (of_string_encoding_t)encoding
		   first: (bool)first;
@end

Modified src/OFINICategory.m from [8e99773efd] to [077a8d208b].

406
407
408
409
410
411
412

413
414
415
416
417
418
419
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420







+







		i++;
	}

	objc_autoreleasePoolPop(pool);
}

- (bool)OF_writeToStream: (OFStream*)stream
		encoding: (of_string_encoding_t)encoding
		   first: (bool)first
{
	OFEnumerator *enumerator;
	id line;

	if ([_lines count] == 0)
		return false;
428
429
430
431
432
433
434


435
436


437
438
439
440
441
442
443
429
430
431
432
433
434
435
436
437
438

439
440
441
442
443
444
445
446
447







+
+

-
+
+







		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);
			OFString *line = [OFString
			    stringWithFormat: @"%@=%@\n", key, value];

			[stream writeFormat: @"%@=%@\n", key, value];
			[stream writeString: line
				   encoding: encoding];
		} else
			@throw [OFInvalidArgumentException exception];
	}

	return true;
}
@end

Modified src/OFINIFile.h from [d3236d0058] to [ad0922c06d].

11
12
13
14
15
16
17

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
11
12
13
14
15
16
17
18
19
20
21

22
23
24
25
26
27
28
29
30
31
32
33
34
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







+



-




















+
+
+
+
+
+
+
+
+
+
+
+










+
+
+
+
+
+
+
+
+
+
+
+

















+
+
+
+
+
+
+
+
+
+

 * 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 "OFString.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
{
	OFMutableArray *_categories;
}

/*!
 * @brief Creates a new OFINIFile with the contents of the specified file.
 *
 * @param path The path to the file whose contents the OFINIFile should contain
 *
 * @return A new, autoreleased OFINIFile with the contents of the specified file
 */
+ (instancetype)fileWithPath: (OFString*)path;

/*!
 * @brief Creates a new OFINIFile with the contents of the specified file in
 *	  the specified encoding.
 *
 * @param path The path to the file whose contents the OFINIFile should contain
 * @param encoding The encoding of the specified file
 *
 * @return A new, autoreleased OFINIFile with the contents of the specified file
 */
+ (instancetype)fileWithPath: (OFString*)path
		    encoding: (of_string_encoding_t)encoding;

/*!
 * @brief Initializes an already allocated OFINIFile with the contents of the
 *	  specified file.
 *
 * @param path The path to the file whose contents the OFINIFile should contain
 *
 * @return An initialized OFINIFile with the contents of the specified file
 */
- initWithPath: (OFString*)path;

/*!
 * @brief Initializes an already allocated OFINIFile with the contents of the
 *	  specified file in the specified encoding.
 *
 * @param path The path to the file whose contents the OFINIFile should contain
 * @param encoding The encoding of the specified file
 *
 * @return An initialized OFINIFile with the contents of the specified file
 */
- initWithPath: (OFString*)path
      encoding: (of_string_encoding_t)encoding;

/*!
 * @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 Writes the contents of the OFINIFile to a file.
 *
 * @param path The path of the file to write to
 */
- (void)writeToFile: (OFString*)path;

/*!
 * @brief Writes the contents of the OFINIFile to a file in the specified
 *	  encoding.
 *
 * @param path The path of the file to write to
 * @param encoding The encoding to use
 */
- (void)writeToFile: (OFString*)path
	   encoding: (of_string_encoding_t)encoding;
@end

Modified src/OFINIFile.m from [38c671870e] to [7cf0535b70].

24
25
26
27
28
29
30
31


32
33
34
35
36
37
38
24
25
26
27
28
29
30

31
32
33
34
35
36
37
38
39







-
+
+







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

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

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

static bool
isWhitespaceLine(OFString *line)
{
	const char *cString = [line UTF8String];
	size_t i, length = [line UTF8StringLength];
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
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







+
+
+
+
+
+
+








+
+
+
+
+
+
+





-
+
+







}

@implementation OFINIFile
+ (instancetype)fileWithPath: (OFString*)path
{
	return [[[self alloc] initWithPath: path] autorelease];
}

+ (instancetype)fileWithPath: (OFString*)path
		    encoding: (of_string_encoding_t)encoding
{
	return [[[self alloc] initWithPath: path
				  encoding: encoding] autorelease];
}

- init
{
	OF_INVALID_INIT_METHOD
}

- initWithPath: (OFString*)path
{
	return [self initWithPath: path
			 encoding: OF_STRING_ENCODING_UTF_8];
}

- initWithPath: (OFString*)path
      encoding: (of_string_encoding_t)encoding
{
	self = [super init];

	@try {
		_categories = [[OFMutableArray alloc] init];

		[self OF_parseFile: path];
		[self OF_parseFile: path
			  encoding: encoding];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}
110
111
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
140
141
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150

151
152
153
154
155
156
157
158







+

















-
+








	objc_autoreleasePoolPop(pool);

	return [category autorelease];
}

- (void)OF_parseFile: (OFString*)path
	    encoding: (of_string_encoding_t)encoding
{
	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) {
	while ((line = [file readLineWithEncoding: encoding]) != nil) {
		if (isWhitespaceLine(line))
			continue;

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

			if (![line hasSuffix: @"]"])
157
158
159
160
161
162
163







164
165
166
167
168
169
170
171
172

173
174
175
176
177
178
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203







+
+
+
+
+
+
+









+






	}

	objc_autoreleasePoolPop(pool);
}

- (void)writeToFile: (OFString*)path
{
	[self writeToFile: path
		 encoding: OF_STRING_ENCODING_UTF_8];
}

- (void)writeToFile: (OFString*)path
	   encoding: (of_string_encoding_t)encoding
{
	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
				      encoding: encoding
					 first: first])
			first = false;

	objc_autoreleasePoolPop(pool);
}
@end

Modified tests/OFINIFileTests.m from [b708218fa8] to [4b57243305].

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







-
+











-
-
+
+
+











-
+







	    @"foobar=baz" NL
	    @";comment" NL
	    @"new=new" NL
	    NL
	    @"[foobar]" NL
	    @";foobarcomment" NL
	    @"qux=\" asd\"" NL
	    @"quxquxqux=\"hello\\\"world\"" NL
	    @"quxquxqux=\"hello\\\"wörld\"" NL
	    @"qux2=\"a\\f\"" NL
	    @"qux3=a\fb" NL
	    NL
	    @"[types]" NL
	    @"integer=16" NL
	    @"bool=false" NL
	    @"float=0.25" NL
	    @"double=0.75" NL;
	OFINIFile *file;
	OFINICategory *tests, *foobar, *types;

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

	tests = [file categoryForName: @"tests"];
	foobar = [file categoryForName: @"foobar"];
	types = [file categoryForName: @"types"];
	TEST(@"-[categoryForName:]",
	    tests != nil && foobar != nil && types != nil)

	module = @"OFINICategory";

	TEST(@"-[stringForKey:]",
	    [[tests stringForKey: @"foo"] isEqual: @"bar"] &&
	    [[foobar stringForKey: @"quxquxqux"] isEqual: @"hello\"world"])
	    [[foobar stringForKey: @"quxquxqux"] isEqual: @"hello\"wörld"])

	TEST(@"-[setString:forKey:]",
	    R([tests setString: @"baz"
			forKey: @"foo"]) &&
	    R([tests setString: @"new"
			forKey: @"new"]) &&
	    R([foobar setString: @"a\fb"
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







+
-
-
+
+
+
+
+









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

	module = @"OFINIFile";

	/* FIXME: Find a way to write files on Nintendo DS */
#ifndef OF_NINTENDO_DS
	TEST(@"-[writeToFile:encoding:]",
	TEST(@"-[writeToFile:]", R([file writeToFile: @"tmpfile.ini"]) &&
	    [[OFString stringWithContentsOfFile: @"tmpfile.ini"]
	    R([file writeToFile: @"tmpfile.ini"
		       encoding: OF_STRING_ENCODING_CODEPAGE_437]) &&
	    [[OFString
		stringWithContentsOfFile: @"tmpfile.ini"
				encoding: OF_STRING_ENCODING_CODEPAGE_437]
	    isEqual: output])
	[OFFile removeItemAtPath: @"tmpfile.ini"];
#else
	(void)output;
#endif

	[pool drain];
}
@end

Modified tests/testfile.ini from [2addf8918f] to [a2b6991eb2].

1
2
3
4
5
6
7
8
9
10

11
12
13
14
15
16
17
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"
quxquxqux="hello\"wrld"
qux2="a\f"

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