forked from mirek/YAML.framework
-
Notifications
You must be signed in to change notification settings - Fork 0
/
YAMLSerialization.m
416 lines (348 loc) · 13.2 KB
/
YAMLSerialization.m
1
2
3
4
5
6
7
8
9
10
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
104
105
106
107
108
109
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
//
// YAMLSerialization.m
// YAML Serialization support by Mirek Rusin based on C library LibYAML by Kirill Simonov
// Released under MIT License
//
// Copyright 2010 Mirek Rusin
// Copyright 2010 Stanislav Yudin
//
#import "YAMLSerialization.h"
NSString *const YAMLErrorDomain = @"com.github.mirek.yaml";
// Assumes NSError **error is in the current scope
#define YAML_SET_ERROR(errorCode, description, recovery) \
if (error) \
*error = [NSError errorWithDomain: YAMLErrorDomain \
code: errorCode \
userInfo: [NSDictionary dictionaryWithObjectsAndKeys: \
description, NSLocalizedDescriptionKey, \
recovery, NSLocalizedRecoverySuggestionErrorKey, \
nil]]
static NSNumber *ParseBoolean(NSString *str) {
for (NSString *s in @[@"y", @"yes", @"true", @"on"])
if ([s isEqualToString:str.lowercaseString])
return @YES;
for (NSString *s in @[@"n", @"no", @"false", @"off"])
if ([s isEqualToString:str.lowercaseString])
return @NO;
return nil;
}
static NSNumber *ParseNumber(NSString *str) {
static dispatch_once_t numberFormatterOnceToken;
static NSNumberFormatter *numberFormatter = nil;
dispatch_once(&numberFormatterOnceToken, ^{
numberFormatter = [[NSNumberFormatter alloc] init];
});
NSNumber *number = nil;
if ([numberFormatter getObjectValue:&number forString:str errorDescription:nil])
return [number autorelease];
return nil;
}
static NSDate *ParseDate(NSString *str) {
static dispatch_once_t dateFormatterOnceToken;
static NSDateFormatter *dateFormatter = nil;
dispatch_once(&dateFormatterOnceToken, ^{
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy-MM-dd";
});
NSDate *date;
if ([dateFormatter getObjectValue:&date forString:str errorDescription:nil])
return [date autorelease];
return nil;
}
static NSNull *ParseNull(NSString *str) {
if (str.length == 0 || [str isEqualToString:@"~"] || [str.lowercaseString isEqualToString:@"null"])
return [NSNull null];
return nil;
}
#pragma mark Write support
#pragma mark -
static int YAMLSerializationDataHandler(void *data, unsigned char *buffer, size_t size) {
NSMutableString *string = (NSMutableString*)data;
NSString *buf = [[NSString alloc] initWithBytes:buffer length:size encoding:NSUTF8StringEncoding];
[string appendString:buf];
[buf release];
return YES;
}
static int YAMLSerializationWriteHandler(void *data, unsigned char *buffer, size_t size) {
if ([(NSOutputStream *)data write:buffer maxLength:size] <= 0) {
return NO;
} else {
return YES;
}
}
static int YAMLSerializationProcessValue(yaml_document_t *document, id value) {
NSInteger nodeId = 0;
if ([value isKindOfClass:[NSDictionary class]] ) {
nodeId = yaml_document_add_mapping(document, NULL, YAML_BLOCK_MAPPING_STYLE);
for(NSString *key in [value allKeys]) {
int keyId = YAMLSerializationProcessValue(document, key);
id keyValue = [value objectForKey:key];
int valueId = YAMLSerializationProcessValue(document, keyValue);
yaml_document_append_mapping_pair(document, (int) nodeId, keyId, valueId);
}
}
else if ([value isKindOfClass:[NSArray class]] ) {
nodeId = yaml_document_add_sequence(document, NULL, YAML_BLOCK_SEQUENCE_STYLE);
for(id childValue in value) {
int childId = YAMLSerializationProcessValue(document, childValue);
yaml_document_append_sequence_item(document, (int) nodeId, childId);
}
}
else {
if ( ![value isKindOfClass:[NSString class]] ) {
value = [value stringValue];
}
nodeId = yaml_document_add_scalar(document, NULL, (yaml_char_t*)[value UTF8String], (int) [value length], YAML_PLAIN_SCALAR_STYLE);
}
return (int) nodeId;
}
// De-serialize single, parsed document. Creates the document
static yaml_document_t* YAMLSerializationToDocument(id yaml, YAMLWriteOptions opt, NSError **error) {
yaml_document_t *document = (yaml_document_t*)malloc( sizeof(yaml_document_t));
if (!document) {
YAML_SET_ERROR(kYAMLErrorCodeOutOfMemory, @"Couldn't allocate memory", @"Please try to free memory and retry");
return NULL;
}
if (!yaml_document_initialize(document, NULL, NULL, NULL, 0, 0)) {
YAML_SET_ERROR(kYAMLErrorInvalidYamlObject, @"Failed to initialize yaml document", @"Underlying data structure failed to initalize");
free(document);
return NULL;
}
//add root element
int rootId = 0;
if ([yaml isKindOfClass:[NSDictionary class]]) {
rootId = yaml_document_add_mapping(document, NULL, YAML_ANY_MAPPING_STYLE);
for(NSString *key in [yaml allKeys]) {
int keyId = YAMLSerializationProcessValue(document, key);
id value = [yaml objectForKey:key];
int valueId = YAMLSerializationProcessValue(document, value);
yaml_document_append_mapping_pair(document, rootId, keyId, valueId);
}
}
else if ([yaml isKindOfClass:[NSArray class]]) {
rootId = yaml_document_add_sequence(document, NULL, YAML_ANY_SEQUENCE_STYLE);
for(id value in yaml) {
int valueId = YAMLSerializationProcessValue(document, value);
yaml_document_append_sequence_item(document, rootId, valueId);
}
}
else {
//unsupported root element
YAML_SET_ERROR(kYAMLErrorInvalidYamlObject, @"Yaml object must be either NSDictionary of NSArray. Scalar roots are pointless.", @"Send dictionary or array as yaml document root");
free(document);
return NULL;
}
return document;
}
#pragma mark Read support
#pragma mark -
static int YAMLSerializationReadHandler(void *data, unsigned char *buffer, size_t size, size_t *size_read) {
NSInteger outcome = [(NSInputStream *)data read: (uint8_t *)buffer maxLength: size];
if (outcome < 0) {
*size_read = 0;
return NO;
} else {
*size_read = outcome;
return YES;
}
}
// Serialize single, parsed document. Does not destroy the document.
static id YAMLSerializationWithDocument(yaml_document_t *document, YAMLReadOptions opt, NSError **error) {
id root = nil;
id *objects = nil;
// Mutability options
Class arrayClass = [NSArray class];
Class dictionaryClass = [NSDictionary class];
Class stringClass = [NSString class];
if (opt & kYAMLReadOptionMutableContainers) {
arrayClass = [NSMutableArray class];
dictionaryClass = [NSMutableDictionary class];
if (opt & kYAMLReadOptionMutableContainersAndLeaves) {
stringClass = [NSMutableString class];
}
}
yaml_node_t *node;
yaml_node_item_t *item;
yaml_node_pair_t *pair;
int i = 0;
objects = (id *)malloc(sizeof(id) * (document->nodes.top - document->nodes.start));
if (!objects) {
YAML_SET_ERROR(kYAMLErrorCodeOutOfMemory, @"Couldn't allocate memory", @"Please try to free memory and retry");
return nil;
}
// Create all objects, don't fill containers yet...
for (node = document->nodes.start, i = 0; node < document->nodes.top; node++, i++) {
switch (node->type) {
case YAML_SCALAR_NODE: {
id value = [[stringClass alloc] initWithUTF8String: (const char *)node->data.scalar.value];
if (!(opt & kYAMLReadOptionStringScalars)) {
value = ParseNull(value) ?: ParseBoolean(value) ?: ParseNumber(value) ?: ParseDate(value) ?: value;
}
objects[i] = value;
if (!root) root = objects[i];
break;
}
case YAML_SEQUENCE_NODE:
objects[i] = [[NSMutableArray alloc] initWithCapacity: node->data.sequence.items.top - node->data.sequence.items.start];
if (!root) root = objects[i];
break;
case YAML_MAPPING_NODE:
objects[i] = [[NSMutableDictionary alloc] initWithCapacity: node->data.mapping.pairs.top - node->data.mapping.pairs.start];
if (!root) root = objects[i];
break;
default: break;
}
}
// Fill in containers
for (node = document->nodes.start, i = 0; node < document->nodes.top; node++, i++) {
switch (node->type) {
case YAML_SEQUENCE_NODE:
for (item = node->data.sequence.items.start; item < node->data.sequence.items.top; item++)
[objects[i] addObject: objects[*item - 1]];
break;
case YAML_MAPPING_NODE:
for (pair = node->data.mapping.pairs.start; pair < node->data.mapping.pairs.top; pair++)
[objects[i] setObject: objects[pair->value - 1]
forKey: objects[pair->key - 1]];
break;
default: break;
}
}
// Retain the root object
if (root)
[root retain];
// Release all objects. The root object and all referenced (in containers) objects
// will have retain count > 0
for (node = document->nodes.start, i = 0; node < document->nodes.top; node++, i++)
[objects[i] release];
if (objects)
free(objects);
return root;
}
#pragma mark YAMLSerialization Implementation
@implementation YAMLSerialization
+ (NSMutableArray *) YAMLWithStream: (NSInputStream *) stream
options: (YAMLReadOptions) opt
error: (NSError **) error
{
NSMutableArray *documents = [NSMutableArray array];
id documentObject = nil;
yaml_parser_t parser;
yaml_document_t document;
BOOL done = NO;
// Open input stream
[stream open];
if (!yaml_parser_initialize(&parser)) {
YAML_SET_ERROR(kYAMLErrorCodeParserInitializationFailed, @"Error in yaml_parser_initialize(&parser)", @"Internal error, please let us know about this error");
return nil;
}
yaml_parser_set_input(&parser, YAMLSerializationReadHandler, (void *)stream);
while (!done) {
if (!yaml_parser_load(&parser, &document)) {
YAML_SET_ERROR(kYAMLErrorCodeParseError, @"Parse error", @"Make sure YAML file is well formed");
return nil;
}
done = !yaml_document_get_root_node(&document);
if (!done) {
documentObject = YAMLSerializationWithDocument(&document, opt, error);
if (error && *error) {
yaml_document_delete(&document);
} else {
[documents addObject: documentObject];
[documentObject release];
}
}
// TODO: Check if aliases to previous documents are allowed by the specs
yaml_document_delete(&document);
}
yaml_parser_delete(&parser);
return documents;
}
+ (NSMutableArray *) YAMLWithData: (NSData *) data
options: (YAMLReadOptions) opt
error: (NSError **) error;
{
if (data) {
NSInputStream *inputStream = [NSInputStream inputStreamWithData:data];
NSMutableArray *documents = [self YAMLWithStream: inputStream options: opt error: error];
return documents;
}
else {
return nil;
}
}
+ (void) writeYAML: (id) yaml
toStream: (NSOutputStream *) stream
options: (YAMLWriteOptions) opt
error: (NSError **) error
{
yaml_emitter_t emitter;
if (!yaml_emitter_initialize(&emitter)) {
YAML_SET_ERROR(kYAMLErrorCodeEmitterError, @"Error in yaml_emitter_initialize(&emitter)", @"Internal error, please let us know about this error");
return;
}
yaml_emitter_set_encoding(&emitter, YAML_UTF8_ENCODING);
yaml_emitter_set_output(&emitter, YAMLSerializationWriteHandler, (void *)stream);
// Open output stream
[stream open];
if (kYAMLWriteOptionMultipleDocuments & opt) {
//yaml is an array of documents
for(id documentObject in yaml) {
yaml_document_t *document = YAMLSerializationToDocument(documentObject, opt, error);
if (!document) {
YAML_SET_ERROR(kYAMLErrorInvalidYamlObject, @"Failed to de-serialize document at index %d", @"Document root can be either NSArray or NSDictionary");
yaml_emitter_delete(&emitter);
[stream close];
return;
}
yaml_emitter_dump(&emitter, document);
}
}
else {
//yaml is a single document
yaml_document_t *document = YAMLSerializationToDocument(yaml, opt, error);
if (!document) {
yaml_emitter_delete(&emitter);
[stream close];
return;
}
yaml_emitter_dump(&emitter, document);
}
[stream close];
yaml_emitter_delete(&emitter);
}
+ (NSData *) dataFromYAML:(id)yaml options:(YAMLWriteOptions) opt error:(NSError **) error
{
NSMutableString *data = [NSMutableString string];
yaml_emitter_t emitter;
if (!yaml_emitter_initialize(&emitter)) {
YAML_SET_ERROR(kYAMLErrorCodeEmitterError, @"Error in yaml_emitter_initialize(&emitter)", @"Internal error, please let us know about this error");
return nil;
}
yaml_emitter_set_encoding(&emitter, YAML_UTF8_ENCODING);
yaml_emitter_set_output(&emitter, YAMLSerializationDataHandler, (void *)data);
if (kYAMLWriteOptionMultipleDocuments & opt) {
//yaml is an array of documents
for(id documentObject in yaml) {
yaml_document_t *document = YAMLSerializationToDocument(documentObject, opt, error);
if (!document) {
YAML_SET_ERROR(kYAMLErrorInvalidYamlObject, @"Failed to de-serialize document at index %d", @"Document root can be either NSArray or NSDictionary");
yaml_emitter_delete(&emitter);
return nil;
}
yaml_emitter_dump(&emitter, document);
}
}
else {
//yaml is a single document
yaml_document_t *document = YAMLSerializationToDocument(yaml, opt, error);
if (!document) {
yaml_emitter_delete(&emitter);
return nil;
}
yaml_emitter_dump(&emitter, document);
}
yaml_emitter_delete(&emitter);
return [data dataUsingEncoding:NSUnicodeStringEncoding];
}
@end