diff --git a/plist-cil.test/ValuePreprocessorTests.cs b/plist-cil.test/ValuePreprocessorTests.cs index af30ce4..349631b 100644 --- a/plist-cil.test/ValuePreprocessorTests.cs +++ b/plist-cil.test/ValuePreprocessorTests.cs @@ -36,18 +36,18 @@ namespace plistcil.test [Fact] public static void TestPassiveDefaultPreprocessorsRegistered() { - Assert.Equal(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL), true); - Assert.Equal(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.BOOL), false); - Assert.Equal(ValuePreprocessor.Preprocess("true", ValuePreprocessor.Types.BOOL), "true"); - Assert.Equal(ValuePreprocessor.Preprocess("42",ValuePreprocessor.Types.INTEGER), "42"); - Assert.Equal(ValuePreprocessor.Preprocess("3.14159",ValuePreprocessor.Types.FLOATING_POINT), "3.14159"); - Assert.Equal(ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Types.UNDEFINED_NUMBER), "2.71828"); - Assert.Equal(ValuePreprocessor.Preprocess("TestString",ValuePreprocessor.Types.STRING), "TestString"); - Assert.Equal(ValuePreprocessor.Preprocess("TestData",ValuePreprocessor.Types.DATA), "TestData"); + Assert.Equal(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL), true); + Assert.Equal(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL), false); + Assert.Equal(ValuePreprocessor.Preprocess("true", ValuePreprocessor.Type.BOOL), "true"); + Assert.Equal(ValuePreprocessor.Preprocess("42",ValuePreprocessor.Type.INTEGER), "42"); + Assert.Equal(ValuePreprocessor.Preprocess("3.14159",ValuePreprocessor.Type.FLOATING_POINT), "3.14159"); + Assert.Equal(ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Type.UNDEFINED_NUMBER), "2.71828"); + Assert.Equal(ValuePreprocessor.Preprocess("TestString",ValuePreprocessor.Type.STRING), "TestString"); + Assert.Equal(ValuePreprocessor.Preprocess("TestData",ValuePreprocessor.Type.DATA), "TestData"); byte[] value = { 0x1, 0x2, 0x4, 0x8 }; - Assert.Equal(ValuePreprocessor.Preprocess(value,ValuePreprocessor.Types.DATA), value); - Assert.Equal(ValuePreprocessor.Preprocess("01.02.1903",ValuePreprocessor.Types.DATE), "01.02.1903"); - Assert.Equal(ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Types.DATE), 23.0); + Assert.Equal(ValuePreprocessor.Preprocess(value,ValuePreprocessor.Type.DATA), value); + Assert.Equal(ValuePreprocessor.Preprocess("01.02.1903",ValuePreprocessor.Type.DATE), "01.02.1903"); + Assert.Equal(ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Type.DATE), 23.0); } [Fact] @@ -57,12 +57,12 @@ namespace plistcil.test string testString = "TestString"; string expected = "gnirtStseT"; - ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Types.STRING); - string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Types.STRING); + ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Type.STRING); + string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Type.STRING); Assert.Equal(actual, expected); - ValuePreprocessor.Unregister(ValuePreprocessor.Types.STRING); + ValuePreprocessor.Unregister(ValuePreprocessor.Type.STRING); } [Fact] @@ -71,13 +71,13 @@ namespace plistcil.test Func examplePreprocessor = value => new string(value.Reverse().ToArray()); string testString = "TestString"; - ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Types.STRING); - string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Types.DATA); + ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Type.STRING); + string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Type.DATA); // assert unchanged, since the selected preprocessor != registered preprocessor Assert.Equal(actual, testString); - ValuePreprocessor.Unregister(ValuePreprocessor.Types.STRING); + ValuePreprocessor.Unregister(ValuePreprocessor.Type.STRING); } [Fact] @@ -86,7 +86,7 @@ namespace plistcil.test byte[] testArray = { 0x1, 0x2, 0x4, 0x8 }; // there's no registered preprocessor for byte array arguments for STRING - Assert.Throws(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Types.STRING)); + Assert.Throws(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING)); } } } diff --git a/plist-cil/ASCIIPropertyListParser.cs b/plist-cil/ASCIIPropertyListParser.cs index 7849c5a..9303561 100644 --- a/plist-cil/ASCIIPropertyListParser.cs +++ b/plist-cil/ASCIIPropertyListParser.cs @@ -409,15 +409,15 @@ namespace Claunia.PropertyList quotedString[4] == DATE_DATE_FIELD_DELIMITER) try { - return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Types.DATE)); + return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.DATE)); } catch(Exception) { //not a date? --> return string - return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Types.STRING)); + return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING)); } - return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Types.STRING)); + return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING)); } default: { @@ -429,7 +429,7 @@ namespace Claunia.PropertyList //non-numerical -> string or boolean string parsedString = ParseString(); - return new NSString(ValuePreprocessor.Preprocess(parsedString, ValuePreprocessor.Types.STRING)); + return new NSString(ValuePreprocessor.Preprocess(parsedString, ValuePreprocessor.Type.STRING)); } } } @@ -529,9 +529,9 @@ namespace Claunia.PropertyList Expect(DATA_GSBOOL_TRUE_TOKEN, DATA_GSBOOL_FALSE_TOKEN); if(Accept(DATA_GSBOOL_TRUE_TOKEN)) - obj = new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL)); + obj = new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); else - obj = new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.BOOL)); + obj = new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); //Skip the parsed boolean token Skip(); @@ -541,14 +541,14 @@ namespace Claunia.PropertyList //Date Skip(); string dateString = ReadInputUntil(DATA_END_TOKEN); - obj = new NSDate(ValuePreprocessor.Preprocess(dateString, ValuePreprocessor.Types.DATE)); + obj = new NSDate(ValuePreprocessor.Preprocess(dateString, ValuePreprocessor.Type.DATE)); } else if(Accept(DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN)) { //Number Skip(); string numberString = ReadInputUntil(DATA_END_TOKEN); - obj = new NSNumber(ValuePreprocessor.Preprocess(numberString, ValuePreprocessor.Types.UNDEFINED_NUMBER)); + obj = new NSNumber(ValuePreprocessor.Preprocess(numberString, ValuePreprocessor.Type.UNDEFINED_NUMBER)); } //parse data end token @@ -569,7 +569,7 @@ namespace Claunia.PropertyList bytes[i] = (byte)byteValue; } - obj = new NSData(ValuePreprocessor.Preprocess(bytes, ValuePreprocessor.Types.DATA)); + obj = new NSData(ValuePreprocessor.Preprocess(bytes, ValuePreprocessor.Type.DATA)); //skip end token Skip(); @@ -586,18 +586,18 @@ namespace Claunia.PropertyList if(numericalString.Length <= 4 || numericalString[4] != DATE_DATE_FIELD_DELIMITER) - return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Types.STRING)); + return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.STRING)); try { - return new NSDate(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Types.DATE)); + return new NSDate(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.DATE)); } catch(Exception) { //An exception occurs if the string is not a date but just a string } - return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Types.STRING)); + return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.STRING)); } /// diff --git a/plist-cil/BinaryPropertyListParser.cs b/plist-cil/BinaryPropertyListParser.cs index 3dbeda4..947459c 100644 --- a/plist-cil/BinaryPropertyListParser.cs +++ b/plist-cil/BinaryPropertyListParser.cs @@ -204,12 +204,12 @@ namespace Claunia.PropertyList case 0x8: { //false - return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.BOOL)); + return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); } case 0x9: { //true - return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL)); + return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); } case 0xC: { @@ -267,7 +267,7 @@ namespace Claunia.PropertyList //Data ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int dataoffset); - return new NSData(ValuePreprocessor.Preprocess(CopyOfRange(bytes, offset + dataoffset, offset + dataoffset + length), ValuePreprocessor.Types.DATA)); + return new NSData(ValuePreprocessor.Preprocess(CopyOfRange(bytes, offset + dataoffset, offset + dataoffset + length), ValuePreprocessor.Type.DATA)); } case 0x5: { diff --git a/plist-cil/NSDate.cs b/plist-cil/NSDate.cs index 68d706f..2ff9778 100644 --- a/plist-cil/NSDate.cs +++ b/plist-cil/NSDate.cs @@ -52,7 +52,7 @@ namespace Claunia.PropertyList public NSDate(ReadOnlySpan bytes) => //dates are 8 byte big-endian double, seconds since the epoch - Date = EPOCH.AddSeconds(ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Types.DATE)); + Date = EPOCH.AddSeconds(ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Type.DATE)); /// /// Parses a date from its textual representation. That representation has the following pattern: diff --git a/plist-cil/NSNumber.cs b/plist-cil/NSNumber.cs index 7729fbb..018f875 100644 --- a/plist-cil/NSNumber.cs +++ b/plist-cil/NSNumber.cs @@ -69,13 +69,13 @@ namespace Claunia.PropertyList switch(type) { case INTEGER: - doubleValue = longValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseLong(bytes), ValuePreprocessor.Types.INTEGER); + doubleValue = longValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseLong(bytes), ValuePreprocessor.Type.INTEGER); break; case REAL: - doubleValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Types.FLOATING_POINT); - longValue = ValuePreprocessor.Preprocess((long)Math.Round(doubleValue), ValuePreprocessor.Types.INTEGER); + doubleValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Type.FLOATING_POINT); + longValue = ValuePreprocessor.Preprocess((long)Math.Round(doubleValue), ValuePreprocessor.Type.INTEGER); break; diff --git a/plist-cil/NSString.cs b/plist-cil/NSString.cs index 6196bd1..4426973 100644 --- a/plist-cil/NSString.cs +++ b/plist-cil/NSString.cs @@ -48,9 +48,9 @@ namespace Claunia.PropertyList public NSString(ReadOnlySpan bytes, Encoding encoding) { #if NATIVE_SPAN - Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes), ValuePreprocessor.Types.STRING); + Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes), ValuePreprocessor.Type.STRING); #else - Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes.ToArray()), ValuePreprocessor.Types.STRING); + Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes.ToArray()), ValuePreprocessor.Type.STRING); #endif } diff --git a/plist-cil/PropertyListParser.cs b/plist-cil/PropertyListParser.cs index 5c102e1..eea8073 100644 --- a/plist-cil/PropertyListParser.cs +++ b/plist-cil/PropertyListParser.cs @@ -150,7 +150,7 @@ namespace Claunia.PropertyList /// Register preprocessing functions for for plist values. /// A function that preprocesses the passed string and returns the adjusted value. /// The type of value preprocessor to use. - public static void RegisterValuePreprocessor(Func preprocessor, ValuePreprocessor.Types type) => + public static void RegisterValuePreprocessor(Func preprocessor, ValuePreprocessor.Type type) => ValuePreprocessor.Register(preprocessor, type); /// Reads all bytes from an Stream and stores them in an array, up to a maximum count. diff --git a/plist-cil/ValuePreprocessor.cs b/plist-cil/ValuePreprocessor.cs index f9f9ab6..df5295b 100644 --- a/plist-cil/ValuePreprocessor.cs +++ b/plist-cil/ValuePreprocessor.cs @@ -9,41 +9,65 @@ namespace Claunia.PropertyList /// public static class ValuePreprocessor { - public enum Types + /// + /// Indicates the semantic type of content the preprocessor will work on--independent + /// from the underlying data type (which will be string in most cases anyway). + /// + public enum Type { BOOL, INTEGER, FLOATING_POINT, UNDEFINED_NUMBER, STRING, DATA, DATE }; - - private record struct TypeIdentifier(Types ValueType, Type ProcessingType); - private static T NullPreprocessor(T value) => value; + /// + /// A Null-Implementation of a preprocessor for registered, but passive, use cases. + /// + private static T NullPreprocessor(T value) => value; + private record struct TypeIdentifier(Type ValueType, System.Type ProcessingType); + + /// + /// Default preprocessors for all the standard cases. + /// private static readonly Dictionary _preprocessors = new() { - { new TypeIdentifier(Types.BOOL, typeof(bool)), NullPreprocessor }, - { new TypeIdentifier(Types.BOOL, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Types.INTEGER, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Types.FLOATING_POINT, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Types.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Types.STRING, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Types.DATA, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Types.DATA, typeof(byte[])), NullPreprocessor }, - { new TypeIdentifier(Types.DATE, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Types.DATE, typeof(double)), NullPreprocessor }, + { new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor }, + { new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.DATA, typeof(byte[])), NullPreprocessor }, + { new TypeIdentifier(Type.DATE, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.DATE, typeof(double)), NullPreprocessor }, }; - public static void Register(Func preprocessor, Types type) => + /// + /// Register a custom preprocessor. + /// + public static void Register(Func preprocessor, Type type) => _preprocessors[new(type, typeof(T))] = preprocessor; - public static void Unregister(Types type) => + /// + /// Unregister a specific preprocessor--replaces it with a null implementation + /// to prevent argument errors. + /// + public static void Unregister(Type type) => _preprocessors[new(type, typeof(T))] = NullPreprocessor; - public static T Preprocess(T value, Types type) => + /// + /// Preprocess the supplied data using the appropriate registered implementation. + /// + /// If no appropriate preprocessor--not even a default null implementation--was registered. + public static T Preprocess(T value, Type type) => TryGetPreprocessor(type, out Func preprocess) ? preprocess(value) : throw new ArgumentException($"Failed to find a preprocessor for value '{value}'."); - private static bool TryGetPreprocessor(Types type, out Func preprocess) + /// + /// Gets the appropriate registered implementation--or null--and casts it back to the required type. + /// + private static bool TryGetPreprocessor(Type type, out Func preprocess) { if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor)) { diff --git a/plist-cil/XmlPropertyListParser.cs b/plist-cil/XmlPropertyListParser.cs index 203d0b3..b4c02a2 100644 --- a/plist-cil/XmlPropertyListParser.cs +++ b/plist-cil/XmlPropertyListParser.cs @@ -174,13 +174,13 @@ namespace Claunia.PropertyList return array; } - case "true": return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL)); - case "false": return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.BOOL)); - case "integer": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Types.INTEGER)); - case "real": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Types.FLOATING_POINT)); - case "string": return new NSString(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Types.STRING)); - case "data": return new NSData(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Types.DATA)); - default: return n.Name.Equals("date") ? new NSDate(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Types.DATE)) : null; + case "true": return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); + case "false": return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); + case "integer": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.INTEGER), NSNumber.INTEGER); + case "real": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.FLOATING_POINT), NSNumber.REAL); + case "string": return new NSString(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.STRING)); + case "data": return new NSData(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.DATA)); + default: return n.Name.Equals("date") ? new NSDate(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.DATE)) : null; } }