Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -76,10 +76,11 @@ ${OFSTDIOSTREAM_WIN32CONSOLE_M} \ OFStream.m \ OFString.m \ OFString+CryptoHashing.m \ OFString+JSONValue.m \ + OFString+PropertyListValue.m \ OFString+Serialization.m \ OFString+URLEncoding.m \ OFString+XMLEscaping.m \ OFString+XMLUnescaping.m \ OFSystemInfo.m \ ADDED src/OFString+PropertyListValue.h Index: src/OFString+PropertyListValue.h ================================================================== --- /dev/null +++ src/OFString+PropertyListValue.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * 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 "OFString.h" + +OF_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif +extern int _OFString_PropertyListValue_reference; +#ifdef __cplusplus +} +#endif + +@interface OFString (PropertyListValue) +/*! + * @brief The string interpreted as a property list and parsed as an object. + * + * @note This only supports XML property lists! + */ +@property (readonly, nonatomic) id propertyListValue; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFString+PropertyListValue.m Index: src/OFString+PropertyListValue.m ================================================================== --- /dev/null +++ src/OFString+PropertyListValue.m @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * 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. + */ + +#include "config.h" + +#import "OFString+PropertyListValue.h" +#import "OFArray.h" +#import "OFData.h" +#import "OFDate.h" +#import "OFDictionary.h" +#import "OFNumber.h" +#import "OFXMLElement.h" + +#import "OFInvalidFormatException.h" +#import "OFUnsupportedVersionException.h" + +int _OFString_PropertyListValue_reference; + +static id parseElement(OFXMLElement *element); + +static OFArray * +parseArrayElement(OFXMLElement *element) +{ + OFMutableArray *ret = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + + for (OFXMLElement *child in [element elements]) + [ret addObject: parseElement(child)]; + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +static OFDictionary * +parseDictElement(OFXMLElement *element) +{ + OFMutableDictionary *ret = [OFMutableDictionary dictionary]; + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFXMLElement *) *children = [element elements]; + OFEnumerator OF_GENERIC(OFXMLElement *) *enumerator; + OFXMLElement *key, *object; + + if ([children count] % 2 != 0) + @throw [OFInvalidFormatException exception]; + + enumerator = [children objectEnumerator]; + while ((key = [enumerator nextObject]) && + (object = [enumerator nextObject])) { + if ([key namespace] != nil || [[key attributes] count] != 0 || + ![[key name] isEqual: @"key"]) + @throw [OFInvalidFormatException exception]; + + [ret setObject: parseElement(object) + forKey: [key stringValue]]; + } + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +static OFString * +parseStringElement(OFXMLElement *element) +{ + return [element stringValue]; +} + +static OFData * +parseDataElement(OFXMLElement *element) +{ + return [OFData dataWithBase64EncodedString: [element stringValue]]; +} + +static OFDate * +parseDateElement(OFXMLElement *element) +{ + return [OFDate dateWithDateString: [element stringValue] + format: @"%Y-%m-%dT%H:%M:%SZ"]; +} + +static OFNumber * +parseTrueElement(OFXMLElement *element) +{ + if ([[element children] count] != 0) + @throw [OFInvalidFormatException exception]; + + return [OFNumber numberWithBool: true]; +} + +static OFNumber * +parseFalseElement(OFXMLElement *element) +{ + if ([[element children] count] != 0) + @throw [OFInvalidFormatException exception]; + + return [OFNumber numberWithBool: false]; +} + +static OFNumber * +parseRealElement(OFXMLElement *element) +{ + return [OFNumber numberWithDouble: [[element stringValue] doubleValue]]; +} + +static OFNumber * +parseIntegerElement(OFXMLElement *element) +{ + return [OFNumber numberWithIntMax: + [[element stringValue] decimalValue]]; +} + +static id +parseElement(OFXMLElement *element) +{ + OFString *elementName; + + if ([element namespace] != nil || + [[element attributes] count] != 0) + @throw [OFInvalidFormatException exception]; + + elementName = [element name]; + + if ([elementName isEqual: @"array"]) + return parseArrayElement(element); + else if ([elementName isEqual: @"dict"]) + return parseDictElement(element); + else if ([elementName isEqual: @"string"]) + return parseStringElement(element); + else if ([elementName isEqual: @"data"]) + return parseDataElement(element); + else if ([elementName isEqual: @"date"]) + return parseDateElement(element); + else if ([elementName isEqual: @"true"]) + return parseTrueElement(element); + else if ([elementName isEqual: @"false"]) + return parseFalseElement(element); + else if ([elementName isEqual: @"real"]) + return parseRealElement(element); + else if ([elementName isEqual: @"integer"]) + return parseIntegerElement(element); + else + @throw [OFInvalidFormatException exception]; +} + +@implementation OFString (PropertyListValue) +- (id)propertyListValue +{ + void *pool = objc_autoreleasePoolPush(); + OFXMLElement *rootElement = [OFXMLElement elementWithXMLString: self]; + OFXMLAttribute *versionAttribute; + OFArray OF_GENERIC(OFXMLElement *) *elements; + id ret; + + if (![[rootElement name] isEqual: @"plist"] || + [rootElement namespace] != nil) + @throw [OFInvalidFormatException exception]; + + versionAttribute = [rootElement attributeForName: @"version"]; + + if (versionAttribute == nil) + @throw [OFInvalidFormatException exception]; + + if (![[versionAttribute stringValue] isEqual: @"1.0"]) + @throw [OFUnsupportedVersionException + exceptionWithVersion: [versionAttribute stringValue]]; + + elements = [rootElement elements]; + + if ([elements count] != 1) + @throw [OFInvalidFormatException exception]; + + ret = parseElement([elements firstObject]); + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} +@end Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -1220,10 +1220,11 @@ #import "OFString+CryptoHashing.h" #import "OFString+JSONValue.h" #ifdef OF_HAVE_FILES # import "OFString+PathAdditions.h" #endif +#import "OFString+PropertyListValue.h" #import "OFString+Serialization.h" #import "OFString+URLEncoding.h" #import "OFString+XMLEscaping.h" #import "OFString+XMLUnescaping.h" Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -121,10 +121,11 @@ _OFString_CryptoHashing_reference = 1; _OFString_JSONValue_reference = 1; #ifdef OF_HAVE_FILES _OFString_PathAdditions_reference = 1; #endif + _OFString_PropertyListValue_reference = 1; _OFString_Serialization_reference = 1; _OFString_URLEncoding_reference = 1; _OFString_XMLEscaping_reference = 1; _OFString_XMLUnescaping_reference = 1; } Index: src/OFXMLParser.h ================================================================== --- src/OFXMLParser.h +++ src/OFXMLParser.h @@ -127,11 +127,11 @@ * @brief An event-based XML parser. * * OFXMLParser is an event-based XML parser which calls the delegate's callbacks * as soon as it finds something, thus suitable for streams as well. */ -@interface OFXMLParser: OFObject +@interface OFXMLParser: OFObject { id _Nullable _delegate; enum of_xml_parser_state { OF_XMLPARSER_IN_BYTE_ORDER_MARK, OF_XMLPARSER_OUTSIDE_TAG, Index: src/OFXMLParser.m ================================================================== --- src/OFXMLParser.m +++ src/OFXMLParser.m @@ -39,11 +39,11 @@ typedef void (*state_function_t)(id, SEL); static SEL selectors[OF_XMLPARSER_NUM_STATES]; static state_function_t lookupTable[OF_XMLPARSER_NUM_STATES]; -@interface OFXMLParser () +@interface OFXMLParser () - (void)of_inByteOrderMarkState; - (void)of_outsideTagState; - (void)of_tagOpenedState; - (void)of_inProcessingInstructionsState; - (void)of_inTagNameState; Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -22,10 +22,11 @@ OFJSONTests.m \ OFListTests.m \ OFMethodSignatureTests.m \ OFNumberTests.m \ OFObjectTests.m \ + OFPropertyListTests.m \ OFSetTests.m \ OFStreamTests.m \ OFStringTests.m \ OFURLTests.m \ OFValueTests.m \ ADDED tests/OFPropertyListTests.m Index: tests/OFPropertyListTests.m ================================================================== --- /dev/null +++ tests/OFPropertyListTests.m @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * 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. + */ + +#include "config.h" + +#import "OFString.h" +#import "OFArray.h" +#import "OFData.h" +#import "OFDate.h" +#import "OFDictionary.h" +#import "OFNumber.h" +#import "OFAutoreleasePool.h" + +#import "OFInvalidFormatException.h" +#import "OFUnsupportedVersionException.h" + +#import "TestsAppDelegate.h" + +#define PLIST(x) \ + @"" \ + @"" \ + @"\n" \ + x @"\n" \ + @"" + +static OFString *module = @"OFPropertyList"; +static OFString *PLIST1 = PLIST(@"Hello"); +static OFString *PLIST2 = PLIST( + @"" + @" Hello" + @" V29ybGQh" + @" 2018-03-14T12:34:56Z" + @" " + @" " + @" 12.25" + @" -10" + @""); +static OFString *PLIST3 = PLIST( + @"" + @" array" + @" " + @" Hello" + @" V29ybGQh" + @" 2018-03-14T12:34:56Z" + @" " + @" " + @" 12.25" + @" -10" + @" " + @" foo" + @" bar" + @""); + +@implementation TestsAppDelegate (OFPLISTParser) +- (void)propertyListTests +{ + OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init]; + OFArray *array = [OFArray arrayWithObjects: + @"Hello", + [OFData dataWithItems: "World!" + count: 6], + [OFDate dateWithTimeIntervalSince1970: 1521030896], + [OFNumber numberWithBool: true], + [OFNumber numberWithBool: false], + [OFNumber numberWithFloat: 12.25], + [OFNumber numberWithInt: -10], + nil]; + + TEST(@"-[propertyListValue:] #1", + [[PLIST1 propertyListValue] isEqual: @"Hello"]) + + TEST(@"-[propertyListValue:] #2", + [[PLIST2 propertyListValue] isEqual: array]) + + TEST(@"-[propertyListValue:] #3", + [[PLIST3 propertyListValue] isEqual: + [OFDictionary dictionaryWithKeysAndObjects: + @"array", array, + @"foo", @"bar", + nil]]) + + EXPECT_EXCEPTION(@"-[propertyListValue] detecting unsupported version", + OFUnsupportedVersionException, + [[PLIST(@"") stringByReplacingOccurrencesOfString: @"1.0" + withString: @"1.1"] + propertyListValue]) + + EXPECT_EXCEPTION( + @"-[propertyListValue] detecting invalid format #1", + OFInvalidFormatException, + [PLIST(@"") propertyListValue]) + + EXPECT_EXCEPTION( + @"-[propertyListValue] detecting invalid format #2", + OFInvalidFormatException, + [PLIST(@"") propertyListValue]) + + EXPECT_EXCEPTION( + @"-[propertyListValue] detecting invalid format #3", + OFInvalidFormatException, + [PLIST(@"") propertyListValue]) + + EXPECT_EXCEPTION( + @"-[propertyListValue] detecting invalid format #4", + OFInvalidFormatException, + [PLIST(@"") propertyListValue]) + + EXPECT_EXCEPTION( + @"-[propertyListValue] detecting invalid format #5", + OFInvalidFormatException, + [PLIST(@"") propertyListValue]) + + [pool drain]; +} +@end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -146,10 +146,14 @@ @end @interface TestsAppDelegate (OFObjectTests) - (void)objectTests; @end + +@interface TestsAppDelegate (OFPropertyListTests) +- (void)propertyListTests; +@end @interface TestsAppDelegate (OFPluginTests) - (void)pluginTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -435,10 +435,11 @@ [self XMLElementBuilderTests]; #ifdef OF_HAVE_FILES [self serializationTests]; #endif [self JSONTests]; + [self propertyListTests]; #if defined(OF_HAVE_PLUGINS) [self pluginTests]; #endif #if defined(OF_IOS)