diff --git a/YAML-Framework.podspec b/YAML-Framework.podspec new file mode 100644 index 0000000..6226bde --- /dev/null +++ b/YAML-Framework.podspec @@ -0,0 +1,39 @@ +Pod::Spec.new do |s| + s.name = "YAML-Framework" + s.version = "0.0.2" + s.summary = "Proper YAML support for Objective-C. Based on recommended LibYAML." + s.description = "YAML.framework provides support for YAML (de)serialisation similarly to standard NSPropertyListSerialization. Based on C LibYAML library by Kirill Simonov." + s.homepage = "https://github.com/mirek/YAML.framework" + s.license = { + :type => 'MIT', + :text => <<-LICENSE + Copyright (c) 2010 Mirek Rusin (YAML.framework) + 2006 Kirill Simonov (LibYAML) + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + LICENSE + } + + s.authors = { "Mirek Rusin" => "mirek@me.com", "Kirill Simonov" => "kiril" } + s.source = { :git => "https://github.com/mirek/YAML.framework.git", :tag => 'v0.0.2' } + s.source_files = 'YAMLSerialization.{h,m}', 'yaml-0.1.4/config.h' + s.public_header_files = 'YAMLSerialization.h' + s.requires_arc = false + s.dependency 'LibYAML', '~> 0.1.4' +end diff --git a/YAMLSerialization.h b/YAMLSerialization.h index 1612264..2783c84 100644 --- a/YAMLSerialization.h +++ b/YAMLSerialization.h @@ -1,10 +1,10 @@ // // YAMLSerialization.h // YAML Serialization support by Mirek Rusin based on C library LibYAML by Kirill Simonov -// Released under MIT License +// Released under MIT License // // Copyright 2010 Mirek Rusin -// Copyright 2010 Stanislav Yudin +// Copyright 2010 Stanislav Yudin // #import diff --git a/YAMLSerialization.m b/YAMLSerialization.m index 25927c6..5ef5684 100644 --- a/YAMLSerialization.m +++ b/YAMLSerialization.m @@ -14,176 +14,216 @@ // 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]] + if (error) \ + *error = [NSError errorWithDomain: YAMLErrorDomain \ + code: errorCode \ + userInfo: [NSDictionary dictionaryWithObjectsAndKeys: \ + description, NSLocalizedDescriptionKey, \ + recovery, NSLocalizedRecoverySuggestionErrorKey, \ + nil]] @implementation YAMLSerialization +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]; + }); + return [[numberFormatter numberFromString:str] retain]; +} + +static NSDate *ParseDate(NSString *str) { + static dispatch_once_t dateFormatterOnceToken; + static NSDateFormatter *dateFormatter = nil; + dispatch_once(&dateFormatterOnceToken, ^{ + dateFormatter = [[NSDateFormatter alloc] init]; + // set dateformatter defaults (otherwise it picks up current settings) + dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; + dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; + dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; + dateFormatter.dateFormat = @"yyyy-MM-dd"; + }); + return [[dateFormatter dateFromString:str] retain]; +} + +static NSNull *ParseNull(NSString *str) { + if (str.length == 0 || [str isEqualToString:@"~"] || [str.lowercaseString isEqualToString:@"null"]) + return [NSNull null]; + return nil; +} + #pragma mark Reading YAML static int __YAMLSerializationParserInputReadHandler (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; - } + 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 __YAMLSerializationObjectWithYAMLDocument (yaml_document_t *document, YAMLReadOptions opt, NSError **error) { - id root = nil; - id *objects = nil; + id root = nil; + id *objects = nil; - // Mutability options + // Mutability options Class arrayClass = [NSMutableArray class]; // TODO: FIXME: Class dictionaryClass = [NSMutableDictionary class]; // TODO: FIXME: - Class stringClass = [NSString class]; - if (opt & kYAMLReadOptionMutableContainers) { - arrayClass = [NSMutableArray class]; - dictionaryClass = [NSMutableDictionary class]; - if (opt & kYAMLReadOptionMutableContainersAndLeaves) { - stringClass = [NSMutableString class]; - } + Class stringClass = [NSString class]; + if (opt & kYAMLReadOptionMutableContainers) { + arrayClass = [NSMutableArray class]; + dictionaryClass = [NSMutableDictionary class]; + if (opt & kYAMLReadOptionMutableContainersAndLeaves) { + stringClass = [NSMutableString class]; } - - if (opt & kYAMLReadOptionStringScalars) { - // Supported - } else { - YAML_SET_ERROR(kYAMLErrorInvalidOptions, @"Currently only kYAMLReadOptionStringScalars is supported", @"Serialize with kYAMLReadOptionStringScalars option"); - return nil; - } - + } + yaml_node_t *node = NULL; yaml_node_item_t *item = NULL; yaml_node_pair_t *pair = NULL; - int i = 0; + int i = 0; objects = (id *) calloc(document->nodes.top - document->nodes.start, sizeof(id)); if (objects == NULL) { - 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: - objects[i] = [[stringClass alloc] initWithUTF8String: (const char *)node->data.scalar.value]; - if (!root) root = objects[i]; - break; - - case YAML_SEQUENCE_NODE: + 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)) { + id parsedValue = ParseNull(value) ?: ParseBoolean(value) ?: ParseNumber(value) ?: ParseDate(value) ?: nil; + if (parsedValue) { + [value release]; + value = parsedValue; + } + } + objects[i] = value; + if (!root) root = objects[i]; + break; + } + case YAML_SEQUENCE_NODE: objects[i] = [[arrayClass alloc] initWithCapacity: node->data.sequence.items.top - node->data.sequence.items.start]; - if (!root) root = objects[i]; - break; - - case YAML_MAPPING_NODE: + if (!root) root = objects[i]; + break; + + case YAML_MAPPING_NODE: objects[i] = [[dictionaryClass alloc] initWithCapacity: node->data.mapping.pairs.top - node->data.mapping.pairs.start]; - if (!root) root = objects[i]; - break; + 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; + } + + // 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 + } + + // Retain the root object if (root != nil) { - [root retain]; + [root retain]; } - - // Release all objects. The root object and all referenced (in containers) objects - // will have retain count > 0 + + // 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]; + [objects[i] release]; } if (objects != NULL) { - free(objects); + free(objects); } - - return root; + + return root; } + (NSMutableArray *) objectsWithYAMLStream: (NSInputStream *) stream - options: (YAMLReadOptions) opt - error: (NSError **) error + 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]; - + NSMutableArray *documents = [NSMutableArray array]; + id documentObject = nil; + + yaml_parser_t parser; + yaml_document_t document; + BOOL done = NO; + + // Open input stream + [stream open]; + memset(&parser, 0, sizeof(yaml_parser_t)); - 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; - } - + 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, __YAMLSerializationParserInputReadHandler, (void *)stream); + + while (!done) { - 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) { + 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 = __YAMLSerializationObjectWithYAMLDocument(&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); + if (error && *error) { + yaml_document_delete(&document); + } else { + [documents addObject: documentObject]; + [documentObject release]; + } } - - yaml_parser_delete(&parser); - - return documents; + + // TODO: Check if aliases to previous documents are allowed by the specs + yaml_document_delete(&document); + } + + yaml_parser_delete(&parser); + + return documents; } + (id) objectWithYAMLStream: (NSInputStream *) stream options: (YAMLReadOptions) opt error: (NSError **) error { @@ -191,8 +231,8 @@ + (id) objectWithYAMLStream: (NSInputStream *) stream options: (YAMLReadOptions) } + (NSMutableArray *) objectsWithYAMLData: (NSData *) data - options: (YAMLReadOptions) opt - error: (NSError **) error; + options: (YAMLReadOptions) opt + error: (NSError **) error; { NSMutableArray *result = nil; if (data != nil) { @@ -237,7 +277,7 @@ + (id) objectWithYAMLString: (NSString *) string options: (YAMLReadOptions) opt int valueIndex = __YAMLSerializationAddObject(document, [value objectForKey: key]); yaml_document_append_mapping_pair(document, result, keyIndex, valueIndex); } - } + } else if ([value isKindOfClass: [NSArray class]]) { result = yaml_document_add_sequence(document, NULL, YAML_BLOCK_SEQUENCE_STYLE); for (id element in value) { @@ -245,7 +285,7 @@ + (id) objectWithYAMLString: (NSString *) string options: (YAMLReadOptions) opt yaml_document_append_sequence_item(document, result, elementIndex); } } - else { + else { NSString *string = nil; if ([value isKindOfClass: [NSString class]]) { string = value; @@ -253,7 +293,7 @@ + (id) objectWithYAMLString: (NSString *) string options: (YAMLReadOptions) opt string = [value stringValue]; } result = yaml_document_add_scalar(document, NULL, (yaml_char_t *)[string UTF8String], (int) [string length], YAML_PLAIN_SCALAR_STYLE); - } + } return (int) result; } @@ -276,41 +316,39 @@ + (BOOL) __YAMLSerializationAddRootObjectAndEmit: (id) object emitter: (yaml_emi + (BOOL) writeObject: (id) object toYAMLStream: (NSOutputStream *) stream - options: (YAMLWriteOptions) opt - error: (NSError **) error + options: (YAMLWriteOptions) opt + error: (NSError **) error { BOOL result = YES; - yaml_emitter_t emitter; + yaml_emitter_t emitter; memset(&emitter, 0, sizeof(yaml_emitter_t)); - if (!yaml_emitter_initialize(&emitter)) { - YAML_SET_ERROR(kYAMLErrorCodeEmitterError, @"Error in yaml_emitter_initialize(&emitter)", @"Internal error, please let us know about this error"); + 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 NO; - } - - yaml_emitter_set_encoding(&emitter, YAML_UTF8_ENCODING); + } + + yaml_emitter_set_encoding(&emitter, YAML_UTF8_ENCODING); yaml_emitter_set_output(&emitter, __YAMLSerializationEmitterOutputWriteHandler, (void *)stream); - + // Open output stream. - [stream open]; - - if (kYAMLWriteOptionMultipleDocuments & opt) { - + [stream open]; + + if (kYAMLWriteOptionMultipleDocuments & opt) { + // YAML is an array of documents. for (id child in object) { - // TODO: Check result code. [self __YAMLSerializationAddRootObjectAndEmit: child emitter: &emitter]; - } - } - else { - + } + } + else { // YAML is a single document. [self __YAMLSerializationAddRootObjectAndEmit: object emitter: &emitter]; - } - - [stream close]; - yaml_emitter_delete(&emitter); + } + + [stream close]; + yaml_emitter_delete(&emitter); return result; } @@ -323,7 +361,7 @@ + (NSData *) createYAMLDataWithObject: (id) object options: (YAMLWriteOptions) o [stream release]; return result; } - + + (NSData *) YAMLDataWithObject: (id) object options: (YAMLWriteOptions) opt error: (NSError **) error { return [[self createYAMLDataWithObject: object options: opt error: error] autorelease]; } @@ -331,19 +369,19 @@ + (NSData *) YAMLDataWithObject: (id) object options: (YAMLWriteOptions) opt err + (NSString *) createYAMLStringWithObject: (id) object options: (YAMLWriteOptions) opt error: (NSError **) error { return [[NSString alloc] initWithData: [self YAMLDataWithObject: object options: opt error: error] encoding: NSUTF8StringEncoding]; - + } - + + (NSString *) YAMLStringWithObject: (id) object options: (YAMLWriteOptions) opt error: (NSError **) error { return [[self createYAMLStringWithObject: object options: opt error: error] autorelease]; } - + #pragma mark Deprecated + (NSMutableArray *) YAMLWithStream: (NSInputStream *) stream options: (YAMLReadOptions) opt error: (NSError **) error { return [self objectsWithYAMLStream: stream options: opt error: error]; } - + + (NSMutableArray *) YAMLWithData: (NSData *) data options: (YAMLReadOptions) opt error: (NSError **) error { return [self objectsWithYAMLData: data options: opt error: error]; } diff --git a/test/test.m b/test/test.m index 08b29db..bee3fae 100644 --- a/test/test.m +++ b/test/test.m @@ -1,8 +1,8 @@ // -// test.m -// YAML Serialization support by Mirek Rusin based on C library LibYAML by Kirill Simonov +// test.m +// YAML Serialization support by Mirek Rusin based on C library LibYAML by Kirill Simonov // -// Copyright 2010 Mirek Rusin, Released under MIT License +// Copyright 2010 Mirek Rusin, Released under MIT License // #import @@ -12,41 +12,41 @@ test (int argc, char *argv[]) { int result = 0; - NSLog(@"reading test file... "); - NSData *data = [NSData dataWithContentsOfFile: @"yaml/basic.yaml"]; - NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath: @"yaml/basic.yaml"] autorelease]; - NSLog(@"done."); + NSLog(@"reading test file... "); + NSData *data = [NSData dataWithContentsOfFile: @"yaml/basic.yaml"]; + NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath: @"yaml/basic.yaml"] autorelease]; + NSLog(@"done."); - NSTimeInterval before = [[NSDate date] timeIntervalSince1970]; - NSMutableArray *yaml = [YAMLSerialization objectsWithYAMLData: data options: kYAMLReadOptionStringScalars error: nil]; - NSLog(@"YAMLWithData took %f", ([[NSDate date] timeIntervalSince1970] - before)); - NSLog(@"%@", yaml); + NSTimeInterval before = [[NSDate date] timeIntervalSince1970]; + NSMutableArray *yaml = [YAMLSerialization objectsWithYAMLData: data options: kYAMLReadOptionStringScalars error: nil]; + NSLog(@"YAMLWithData took %f", ([[NSDate date] timeIntervalSince1970] - before)); + NSLog(@"%@", yaml); NSError *err = nil; - NSTimeInterval before2 = [[NSDate date] timeIntervalSince1970]; - NSMutableArray *yaml2 = [YAMLSerialization objectsWithYAMLStream: stream options: kYAMLReadOptionStringScalars error: &err]; - NSLog(@"YAMLWithStream took %f", ([[NSDate date] timeIntervalSince1970] - before2)); - NSLog(@"%@", yaml2); + NSTimeInterval before2 = [[NSDate date] timeIntervalSince1970]; + NSMutableArray *yaml2 = [YAMLSerialization objectsWithYAMLStream: stream options: kYAMLReadOptionStringScalars error: &err]; + NSLog(@"YAMLWithStream took %f", ([[NSDate date] timeIntervalSince1970] - before2)); + NSLog(@"%@", yaml2); err = nil; - NSTimeInterval before3 = [[NSDate date] timeIntervalSince1970]; - NSOutputStream *outStream = [NSOutputStream outputStreamToMemory]; - [YAMLSerialization writeObject: yaml toYAMLStream: outStream options: kYAMLWriteOptionMultipleDocuments error: &err]; - if (err) { - NSLog(@"Error: %@", err); - return -1; - } - NSLog(@"writeYAML took %f", (float) ([[NSDate date] timeIntervalSince1970] - before3)); - NSLog(@"out stream %@", outStream); - - NSTimeInterval before4 = [[NSDate date] timeIntervalSince1970]; - NSData *outData = [YAMLSerialization YAMLDataWithObject: yaml2 options: kYAMLWriteOptionMultipleDocuments error: &err]; - if (!outData) { - NSLog(@"Data is nil!"); - return -1; - } - NSLog(@"dataFromYAML took %f", ([[NSDate date] timeIntervalSince1970] - before4)); - NSLog(@"out data %@", outData); + NSTimeInterval before3 = [[NSDate date] timeIntervalSince1970]; + NSOutputStream *outStream = [NSOutputStream outputStreamToMemory]; + [YAMLSerialization writeObject: yaml toYAMLStream: outStream options: kYAMLWriteOptionMultipleDocuments error: &err]; + if (err) { + NSLog(@"Error: %@", err); + return -1; + } + NSLog(@"writeYAML took %f", (float) ([[NSDate date] timeIntervalSince1970] - before3)); + NSLog(@"out stream %@", outStream); + + NSTimeInterval before4 = [[NSDate date] timeIntervalSince1970]; + NSData *outData = [YAMLSerialization YAMLDataWithObject: yaml2 options: kYAMLWriteOptionMultipleDocuments error: &err]; + if (!outData) { + NSLog(@"Data is nil!"); + return -1; + } + NSLog(@"dataFromYAML took %f", ([[NSDate date] timeIntervalSince1970] - before4)); + NSLog(@"out data %@", outData); return result; } @@ -57,5 +57,5 @@ @autoreleasepool { result = test(argc, argv); } - return result; + return result; } \ No newline at end of file