From e4a29b7e2a4df7e88f42c40603803f3ed3033666 Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Tue, 21 Jan 2025 19:54:52 +0100 Subject: [PATCH 1/9] Basic value preprocessor implementation --- plist-cil/ValuePreprocessor.cs | 56 ++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 plist-cil/ValuePreprocessor.cs diff --git a/plist-cil/ValuePreprocessor.cs b/plist-cil/ValuePreprocessor.cs new file mode 100644 index 0000000..498d48e --- /dev/null +++ b/plist-cil/ValuePreprocessor.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; + +namespace Claunia.PropertyList +{ + /// + /// Allows you to override the default value class initialization for the values found + /// in the parsed plists by registering your own preprocessing implementations. + /// + public static class ValuePreprocessor + { + public enum Types + { + BOOL, INTEGER, FLOATING_POINT, UNDEFINED_NUMBER, + STRING, DATA, DATE + }; + + private record struct TypeIdentifier(Types ValueType, Type ProcessingType); + private static T NullPreprocessor(T value) => value; + + private static readonly Dictionary _preprocessors = new() + { + { new(Types.BOOL, typeof(bool)), NullPreprocessor }, + { new(Types.BOOL, typeof(string)), NullPreprocessor }, + { new(Types.INTEGER, typeof(string)), NullPreprocessor }, + { new(Types.FLOATING_POINT, typeof(string)), NullPreprocessor }, + { new(Types.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor }, + { new(Types.STRING, typeof(string)), NullPreprocessor }, + { new(Types.DATA, typeof(string)), NullPreprocessor }, + { new(Types.DATA, typeof(byte[])), NullPreprocessor }, + { new(Types.DATE, typeof(string)), NullPreprocessor }, + { new(Types.DATE, typeof(double)), NullPreprocessor }, + }; + + public static void Register(Func preprocessor, Types type) => + _preprocessors[new(type, typeof(T))] = preprocessor; + + public static T Preprocess(T value, Types 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) + { + if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor)) + { + preprocess = (Func) preprocessor; + return true; + } + + preprocess = default; + return false; + } + } + +} \ No newline at end of file From 9e53befd4622b3e5befc69ccf959f1d4c9b290ca Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Tue, 21 Jan 2025 19:58:45 +0100 Subject: [PATCH 2/9] Add call to preprocessors in the plist parsers --- plist-cil/ASCIIPropertyListParser.cs | 24 ++++++++++++------------ plist-cil/BinaryPropertyListParser.cs | 6 +++--- plist-cil/NSDate.cs | 2 +- plist-cil/NSNumber.cs | 6 +++--- plist-cil/NSString.cs | 4 ++-- plist-cil/PropertyListParser.cs | 8 +++++++- plist-cil/XmlPropertyListParser.cs | 15 ++++++++------- 7 files changed, 36 insertions(+), 29 deletions(-) diff --git a/plist-cil/ASCIIPropertyListParser.cs b/plist-cil/ASCIIPropertyListParser.cs index 42ecb43..7849c5a 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(quotedString); + return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Types.DATE)); } catch(Exception) { //not a date? --> return string - return new NSString(quotedString); + return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Types.STRING)); } - return new NSString(quotedString); + return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Types.STRING)); } default: { @@ -429,7 +429,7 @@ namespace Claunia.PropertyList //non-numerical -> string or boolean string parsedString = ParseString(); - return new NSString(parsedString); + return new NSString(ValuePreprocessor.Preprocess(parsedString, ValuePreprocessor.Types.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(true); + obj = new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL)); else - obj = new NSNumber(false); + obj = new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.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(dateString); + obj = new NSDate(ValuePreprocessor.Preprocess(dateString, ValuePreprocessor.Types.DATE)); } else if(Accept(DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN)) { //Number Skip(); string numberString = ReadInputUntil(DATA_END_TOKEN); - obj = new NSNumber(numberString); + obj = new NSNumber(ValuePreprocessor.Preprocess(numberString, ValuePreprocessor.Types.UNDEFINED_NUMBER)); } //parse data end token @@ -569,7 +569,7 @@ namespace Claunia.PropertyList bytes[i] = (byte)byteValue; } - obj = new NSData(bytes); + obj = new NSData(ValuePreprocessor.Preprocess(bytes, ValuePreprocessor.Types.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(numericalString); + return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Types.STRING)); try { - return new NSDate(numericalString); + return new NSDate(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Types.DATE)); } catch(Exception) { //An exception occurs if the string is not a date but just a string } - return new NSString(numericalString); + return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Types.STRING)); } /// diff --git a/plist-cil/BinaryPropertyListParser.cs b/plist-cil/BinaryPropertyListParser.cs index 95a0e41..c94fbe9 100644 --- a/plist-cil/BinaryPropertyListParser.cs +++ b/plist-cil/BinaryPropertyListParser.cs @@ -204,12 +204,12 @@ namespace Claunia.PropertyList case 0x8: { //false - return new NSNumber(false); + return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.BOOL)); } case 0x9: { //true - return new NSNumber(true); + return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL)); } case 0xC: { @@ -267,7 +267,7 @@ namespace Claunia.PropertyList //Data ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int dataoffset); - return new NSData(CopyOfRange(bytes, offset + dataoffset, offset + dataoffset + length)); + return new NSData(ValuePreprocessor.Preprocess(CopyOfRange(bytes, offset + dataoffset, offset + dataoffset + length), ValuePreprocessor.Types.DATA)); } case 0x5: { diff --git a/plist-cil/NSDate.cs b/plist-cil/NSDate.cs index d424925..68d706f 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(BinaryPropertyListParser.ParseDouble(bytes)); + Date = EPOCH.AddSeconds(ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Types.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 380530a..7729fbb 100644 --- a/plist-cil/NSNumber.cs +++ b/plist-cil/NSNumber.cs @@ -69,13 +69,13 @@ namespace Claunia.PropertyList switch(type) { case INTEGER: - doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes); + doubleValue = longValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseLong(bytes), ValuePreprocessor.Types.INTEGER); break; case REAL: - doubleValue = BinaryPropertyListParser.ParseDouble(bytes); - longValue = (long)Math.Round(doubleValue); + doubleValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Types.FLOATING_POINT); + longValue = ValuePreprocessor.Preprocess((long)Math.Round(doubleValue), ValuePreprocessor.Types.INTEGER); break; diff --git a/plist-cil/NSString.cs b/plist-cil/NSString.cs index cfad91a..6196bd1 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 = encoding.GetString(bytes); + Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes), ValuePreprocessor.Types.STRING); #else - Content = encoding.GetString(bytes.ToArray()); + Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes.ToArray()), ValuePreprocessor.Types.STRING); #endif } diff --git a/plist-cil/PropertyListParser.cs b/plist-cil/PropertyListParser.cs index d5105fc..5c102e1 100644 --- a/plist-cil/PropertyListParser.cs +++ b/plist-cil/PropertyListParser.cs @@ -1,4 +1,4 @@ -// plist-cil - An open source library to parse and generate property lists for .NET +// plist-cil - An open source library to parse and generate property lists for .NET // Copyright (C) 2015 Natalia Portillo // // This code is based on: @@ -147,6 +147,12 @@ namespace Claunia.PropertyList return type; } + /// 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) => + ValuePreprocessor.Register(preprocessor, type); + /// Reads all bytes from an Stream and stores them in an array, up to a maximum count. /// The Stream pointing to the data that should be stored in the array. internal static byte[] ReadAll(Stream fs) diff --git a/plist-cil/XmlPropertyListParser.cs b/plist-cil/XmlPropertyListParser.cs index e1b2f32..203d0b3 100644 --- a/plist-cil/XmlPropertyListParser.cs +++ b/plist-cil/XmlPropertyListParser.cs @@ -23,6 +23,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -173,13 +174,13 @@ namespace Claunia.PropertyList return array; } - case "true": return new NSNumber(true); - case "false": return new NSNumber(false); - case "integer": return new NSNumber(GetNodeTextContents(n), NSNumber.INTEGER); - case "real": return new NSNumber(GetNodeTextContents(n), NSNumber.REAL); - case "string": return new NSString(GetNodeTextContents(n)); - case "data": return new NSData(GetNodeTextContents(n)); - default: return n.Name.Equals("date") ? new NSDate(GetNodeTextContents(n)) : null; + 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; } } From eca9239b5c9436308e6be02bbf9de69a55d74552 Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Tue, 21 Jan 2025 19:59:07 +0100 Subject: [PATCH 3/9] Fix typo --- plist-cil/BinaryPropertyListParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plist-cil/BinaryPropertyListParser.cs b/plist-cil/BinaryPropertyListParser.cs index c94fbe9..3dbeda4 100644 --- a/plist-cil/BinaryPropertyListParser.cs +++ b/plist-cil/BinaryPropertyListParser.cs @@ -158,7 +158,7 @@ namespace Claunia.PropertyList //Read all bytes into a list byte[] buf = PropertyListParser.ReadAll(fs); - // Don't close the stream - that would be the responisibility of code that class + // Don't close the stream - that would be the responsibility of code that class // Parse return Parse(buf); } From 864cca7e7dd149b87331d29e49a7bd075f44c659 Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Tue, 21 Jan 2025 20:14:00 +0100 Subject: [PATCH 4/9] Add basic unit tests, a little cleanup --- plist-cil.test/ValuePreprocessorTests.cs | 92 ++++++++++++++++++++++++ plist-cil/ValuePreprocessor.cs | 23 +++--- 2 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 plist-cil.test/ValuePreprocessorTests.cs diff --git a/plist-cil.test/ValuePreprocessorTests.cs b/plist-cil.test/ValuePreprocessorTests.cs new file mode 100644 index 0000000..af30ce4 --- /dev/null +++ b/plist-cil.test/ValuePreprocessorTests.cs @@ -0,0 +1,92 @@ +// plist-cil - An open source library to Parse and generate property lists for .NET +// Copyright (C) 2015 Natalia Portillo +// +// This code is based on: +// plist - An open source library to Parse and generate property lists +// Copyright (C) 2014 Daniel Dreibrodt +// +// 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. + +using System; +using System.Linq; +using Claunia.PropertyList; +using Xunit; + +namespace plistcil.test +{ + + public static class ValuePreprocessorTests + { + [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"); + 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); + } + + [Fact] + public static void TestRegisterPreprocessor() + { + Func examplePreprocessor = value => new string(value.Reverse().ToArray()); + string testString = "TestString"; + string expected = "gnirtStseT"; + + ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Types.STRING); + string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Types.STRING); + + Assert.Equal(actual, expected); + + ValuePreprocessor.Unregister(ValuePreprocessor.Types.STRING); + } + + [Fact] + public static void TestRegisteredPreprocessorSelection() + { + 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); + + // assert unchanged, since the selected preprocessor != registered preprocessor + Assert.Equal(actual, testString); + + ValuePreprocessor.Unregister(ValuePreprocessor.Types.STRING); + } + + [Fact] + public static void TestUnregisterdPreprocessorThrows() + { + 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)); + } + } +} diff --git a/plist-cil/ValuePreprocessor.cs b/plist-cil/ValuePreprocessor.cs index 498d48e..f9f9ab6 100644 --- a/plist-cil/ValuePreprocessor.cs +++ b/plist-cil/ValuePreprocessor.cs @@ -20,21 +20,24 @@ namespace Claunia.PropertyList private static readonly Dictionary _preprocessors = new() { - { new(Types.BOOL, typeof(bool)), NullPreprocessor }, - { new(Types.BOOL, typeof(string)), NullPreprocessor }, - { new(Types.INTEGER, typeof(string)), NullPreprocessor }, - { new(Types.FLOATING_POINT, typeof(string)), NullPreprocessor }, - { new(Types.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor }, - { new(Types.STRING, typeof(string)), NullPreprocessor }, - { new(Types.DATA, typeof(string)), NullPreprocessor }, - { new(Types.DATA, typeof(byte[])), NullPreprocessor }, - { new(Types.DATE, typeof(string)), NullPreprocessor }, - { new(Types.DATE, typeof(double)), NullPreprocessor }, + { 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 }, }; public static void Register(Func preprocessor, Types type) => _preprocessors[new(type, typeof(T))] = preprocessor; + public static void Unregister(Types type) => + _preprocessors[new(type, typeof(T))] = NullPreprocessor; + public static T Preprocess(T value, Types type) => TryGetPreprocessor(type, out Func preprocess) ? preprocess(value) From 462668f6059b7660809a4d530f5b5cd95c460008 Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Thu, 23 Jan 2025 17:04:01 +0100 Subject: [PATCH 5/9] Some cleanup around the ValuePreprocessor --- plist-cil.test/ValuePreprocessorTests.cs | 36 +++++++------- plist-cil/ASCIIPropertyListParser.cs | 24 +++++----- plist-cil/BinaryPropertyListParser.cs | 6 +-- plist-cil/NSDate.cs | 2 +- plist-cil/NSNumber.cs | 6 +-- plist-cil/NSString.cs | 4 +- plist-cil/PropertyListParser.cs | 2 +- plist-cil/ValuePreprocessor.cs | 60 +++++++++++++++++------- plist-cil/XmlPropertyListParser.cs | 14 +++--- 9 files changed, 89 insertions(+), 65 deletions(-) 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; } } From 12794730dcf954450ce615458172dfd82806353d Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Fri, 24 Jan 2025 14:16:46 +0100 Subject: [PATCH 6/9] Apply some auto-formatting changes --- plist-cil/PropertyListParser.cs | 2 +- plist-cil/ValuePreprocessor.cs | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/plist-cil/PropertyListParser.cs b/plist-cil/PropertyListParser.cs index eea8073..6cfc071 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.Type 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 df5295b..f6153de 100644 --- a/plist-cil/ValuePreprocessor.cs +++ b/plist-cil/ValuePreprocessor.cs @@ -15,14 +15,16 @@ namespace Claunia.PropertyList /// public enum Type { - BOOL, INTEGER, FLOATING_POINT, UNDEFINED_NUMBER, - STRING, DATA, DATE + BOOL, INTEGER, FLOATING_POINT, + UNDEFINED_NUMBER, STRING, DATA, + DATE }; /// - /// A Null-Implementation of a preprocessor for registered, but passive, use cases. + /// 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); /// @@ -45,24 +47,22 @@ namespace Claunia.PropertyList /// /// Register a custom preprocessor. /// - public static void Register(Func preprocessor, Type type) => + public static void Register(Func preprocessor, Type type) => _preprocessors[new(type, typeof(T))] = preprocessor; /// - /// Unregister a specific preprocessor--replaces it with a null implementation + /// 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 void Unregister(Type type) => _preprocessors[new(type, typeof(T))] = NullPreprocessor; /// /// 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}'."); + /// 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}'."); /// /// Gets the appropriate registered implementation--or null--and casts it back to the required type. @@ -71,13 +71,14 @@ namespace Claunia.PropertyList { if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor)) { - preprocess = (Func) preprocessor; + preprocess = (Func)preprocessor; + return true; } preprocess = default; + return false; } } - } \ No newline at end of file From 44f0fcc763b88fc1291c2c4fcbf2e946a9fe34b9 Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Mon, 27 Jan 2025 18:06:12 +0100 Subject: [PATCH 7/9] More cleanup, handle binary parsing more cleanly --- plist-cil.test/ValuePreprocessorTests.cs | 134 +++++++++++++---------- plist-cil/BinaryPropertyListParser.cs | 4 +- plist-cil/NSNumber.cs | 6 +- plist-cil/PropertyListParser.cs | 6 +- plist-cil/ValuePreprocessor.cs | 51 +++++++-- 5 files changed, 129 insertions(+), 72 deletions(-) diff --git a/plist-cil.test/ValuePreprocessorTests.cs b/plist-cil.test/ValuePreprocessorTests.cs index 349631b..12869d8 100644 --- a/plist-cil.test/ValuePreprocessorTests.cs +++ b/plist-cil.test/ValuePreprocessorTests.cs @@ -1,28 +1,3 @@ -// plist-cil - An open source library to Parse and generate property lists for .NET -// Copyright (C) 2015 Natalia Portillo -// -// This code is based on: -// plist - An open source library to Parse and generate property lists -// Copyright (C) 2014 Daniel Dreibrodt -// -// 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. - using System; using System.Linq; using Claunia.PropertyList; @@ -30,63 +5,110 @@ using Xunit; namespace plistcil.test { - public static class ValuePreprocessorTests { + // lock tests to make sure temporarily added / replaced preprocessors don't interfere with the other tests in this suite + private static readonly object _testLock = new(); + [Fact] public static void TestPassiveDefaultPreprocessorsRegistered() { - 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.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); + Assert.Equal(true, ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); + Assert.Equal(false, ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); + Assert.Equal("true", ValuePreprocessor.Preprocess("true", ValuePreprocessor.Type.BOOL)); + Assert.Equal("42", ValuePreprocessor.Preprocess("42", ValuePreprocessor.Type.INTEGER)); + Assert.Equal("3.14159", ValuePreprocessor.Preprocess("3.14159", ValuePreprocessor.Type.FLOATING_POINT)); + Assert.Equal("2.71828", ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Type.UNDEFINED_NUMBER)); + Assert.Equal("TestString", ValuePreprocessor.Preprocess("TestString", ValuePreprocessor.Type.STRING)); + Assert.Equal("TestData", ValuePreprocessor.Preprocess("TestData", ValuePreprocessor.Type.DATA)); + byte[] value = [0x1, 0x2, 0x4, 0x8]; + Assert.Equal(value, ValuePreprocessor.Preprocess(value, ValuePreprocessor.Type.DATA)); + Assert.Equal("01.02.1903", ValuePreprocessor.Preprocess("01.02.1903", ValuePreprocessor.Type.DATE)); + Assert.Equal(23.0, ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Type.DATE)); } [Fact] public static void TestRegisterPreprocessor() { - Func examplePreprocessor = value => new string(value.Reverse().ToArray()); - string testString = "TestString"; - string expected = "gnirtStseT"; + lock(_testLock) + { + Func examplePreprocessor = value => new string(value.Reverse().ToArray()); + string testString = "TestString"; + string expected = "gnirtStseT"; - ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Type.STRING); - string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Type.STRING); + var testType = (ValuePreprocessor.Type)42; - Assert.Equal(actual, expected); + ValuePreprocessor.Set(examplePreprocessor, testType); + string actual = ValuePreprocessor.Preprocess(testString, testType); - ValuePreprocessor.Unregister(ValuePreprocessor.Type.STRING); + Assert.Equal(actual, expected); + + ValuePreprocessor.Unset(testType); + } } [Fact] - public static void TestRegisteredPreprocessorSelection() + public static void TestRegisteredPreprocessorSelection1() { - Func examplePreprocessor = value => new string(value.Reverse().ToArray()); - string testString = "TestString"; + lock(_testLock) + { + Func examplePreprocessor = value => (short)(value - 1); + short testShort = 42; + string testString = "TestString"; - ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Type.STRING); - string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Type.DATA); + var testType = (ValuePreprocessor.Type)42; - // assert unchanged, since the selected preprocessor != registered preprocessor - Assert.Equal(actual, testString); + // correct value type, differing data type + ValuePreprocessor.Set(examplePreprocessor, testType); + ValuePreprocessor.Set(ValuePreprocessor.GetDefault(), testType); - ValuePreprocessor.Unregister(ValuePreprocessor.Type.STRING); + string actual1 = ValuePreprocessor.Preprocess(testString, testType); + short actual2 = ValuePreprocessor.Preprocess(testShort, testType); + + // assert unchanged, since the selected preprocessor != tested preprocessor + Assert.Equal(actual1, testString); + Assert.NotEqual(actual2, testShort); + + ValuePreprocessor.Remove(testType); + ValuePreprocessor.Remove(testType); + } } [Fact] - public static void TestUnregisterdPreprocessorThrows() + public static void TestRegisteredPreprocessorSelection2() { - byte[] testArray = { 0x1, 0x2, 0x4, 0x8 }; + lock(_testLock) + { + Func examplePreprocessor = value => new string(value.Reverse().ToArray()); + byte[] testByteArray = [0x42,]; + string testString = "TestString"; + + var testType = (ValuePreprocessor.Type)42; + + // correct value type, differing data type + ValuePreprocessor.Set(examplePreprocessor, testType); + ValuePreprocessor.Set(ValuePreprocessor.GetDefault(), testType); + + string actual1 = ValuePreprocessor.Preprocess(testString, testType); + byte[] actual2 = ValuePreprocessor.Preprocess(testByteArray, testType); + + Assert.NotEqual(actual1, testString); + + // assert unchanged, since the selected preprocessor != tested preprocessor + Assert.Equal(actual2, testByteArray); + + ValuePreprocessor.Unset(testType); + ValuePreprocessor.Remove(testType); + } + } + + [Fact] + public static void TestUnregisteredPreprocessorThrows() + { + byte[] testArray = [0x1, 0x2, 0x4, 0x8]; // there's no registered preprocessor for byte array arguments for STRING Assert.Throws(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING)); } } -} +} \ No newline at end of file diff --git a/plist-cil/BinaryPropertyListParser.cs b/plist-cil/BinaryPropertyListParser.cs index 947459c..308b582 100644 --- a/plist-cil/BinaryPropertyListParser.cs +++ b/plist-cil/BinaryPropertyListParser.cs @@ -243,14 +243,14 @@ namespace Claunia.PropertyList //integer int length = 1 << objInfo; - return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.INTEGER); + return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), ValuePreprocessor.Type.INTEGER), NSNumber.INTEGER); } case 0x2: { //real int length = 1 << objInfo; - return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.REAL); + return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), ValuePreprocessor.Type.FLOATING_POINT), NSNumber.REAL); } case 0x3: { diff --git a/plist-cil/NSNumber.cs b/plist-cil/NSNumber.cs index 018f875..380530a 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.Type.INTEGER); + doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes); break; case REAL: - doubleValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Type.FLOATING_POINT); - longValue = ValuePreprocessor.Preprocess((long)Math.Round(doubleValue), ValuePreprocessor.Type.INTEGER); + doubleValue = BinaryPropertyListParser.ParseDouble(bytes); + longValue = (long)Math.Round(doubleValue); break; diff --git a/plist-cil/PropertyListParser.cs b/plist-cil/PropertyListParser.cs index 6cfc071..688de80 100644 --- a/plist-cil/PropertyListParser.cs +++ b/plist-cil/PropertyListParser.cs @@ -147,11 +147,11 @@ namespace Claunia.PropertyList return type; } - /// Register preprocessing functions for for plist values. + /// Set up preprocessing functions 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.Type type) => - ValuePreprocessor.Register(preprocessor, type); + public static void SetValuePreprocessor(Func preprocessor, ValuePreprocessor.Type type) => + ValuePreprocessor.Set(preprocessor, type); /// Reads all bytes from an Stream and stores them in an array, up to a maximum count. /// The Stream pointing to the data that should be stored in the array. diff --git a/plist-cil/ValuePreprocessor.cs b/plist-cil/ValuePreprocessor.cs index f6153de..436fa3f 100644 --- a/plist-cil/ValuePreprocessor.cs +++ b/plist-cil/ValuePreprocessor.cs @@ -25,7 +25,7 @@ namespace Claunia.PropertyList /// private static T NullPreprocessor(T value) => value; - private record struct TypeIdentifier(Type ValueType, System.Type ProcessingType); + private record struct TypeIdentifier(Type ValueType, System.Type DataType); /// /// Default preprocessors for all the standard cases. @@ -35,7 +35,9 @@ namespace Claunia.PropertyList { new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor }, { new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor }, { new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.INTEGER, typeof(byte[])), NullPreprocessor }, { new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor }, { new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor }, { new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor }, { new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor }, @@ -45,16 +47,32 @@ namespace Claunia.PropertyList }; /// - /// Register a custom preprocessor. + /// Get a default preprocessor. /// - public static void Register(Func preprocessor, Type type) => - _preprocessors[new(type, typeof(T))] = preprocessor; + public static Func GetDefault() => NullPreprocessor; /// - /// Unregister a specific preprocessor--replaces it with a null-implementation + /// Set up a custom preprocessor. + /// + public static void Set(Func preprocessor, Type type) => + _preprocessors[new(type, typeof(T))] = preprocessor; + + + /// + /// Unset 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; + /// If no appropriate preprocessor--not even a default null-implementation--was set up. + public static void Unset(Type type) => + _preprocessors[GetValidTypeIdentifier(type)] = NullPreprocessor; + + /// + /// Completely unregister a specific preprocessor--remove it instead of + /// replacing it with a null-implementation. + /// + /// If no appropriate preprocessor--not even a default null-implementation--was registered. + public static void Remove(Type type) => + _preprocessors.Remove(GetValidTypeIdentifier(type)); /// /// Preprocess the supplied data using the appropriate registered implementation. @@ -65,7 +83,8 @@ namespace Claunia.PropertyList : throw new ArgumentException($"Failed to find a preprocessor for value '{value}'."); /// - /// Gets the appropriate registered implementation--or null--and casts it back to the required type. + /// Gets the appropriate registered implementation--or null--and casts it back to + /// the required type. /// private static bool TryGetPreprocessor(Type type, out Func preprocess) { @@ -80,5 +99,21 @@ namespace Claunia.PropertyList return false; } + + /// + /// Gets a type identifier if a preprocessor exists for it. + /// + /// If no appropriate preprocessor--not even a default null-implementation--was set up. + private static TypeIdentifier GetValidTypeIdentifier(Type type) + { + var identifier = new TypeIdentifier(type, typeof(T)); + + if(!_preprocessors.ContainsKey(identifier)) + { + throw new ArgumentException($"Failed to find a valid preprocessor type identifier."); + } + + return identifier; + } } -} \ No newline at end of file +} From b507a380fb8e291cb2878bb5f2c920d5a0035983 Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Mon, 27 Jan 2025 18:21:22 +0100 Subject: [PATCH 8/9] Cleanup for NSString and NSDate, too --- plist-cil.test/ValuePreprocessorTests.cs | 17 ++++++++++++++--- plist-cil/BinaryPropertyListParser.cs | 4 ++-- plist-cil/NSDate.cs | 2 +- plist-cil/NSString.cs | 4 ++-- plist-cil/ValuePreprocessor.cs | 2 ++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/plist-cil.test/ValuePreprocessorTests.cs b/plist-cil.test/ValuePreprocessorTests.cs index 12869d8..07ff529 100644 --- a/plist-cil.test/ValuePreprocessorTests.cs +++ b/plist-cil.test/ValuePreprocessorTests.cs @@ -13,16 +13,27 @@ namespace plistcil.test [Fact] public static void TestPassiveDefaultPreprocessorsRegistered() { + byte[] testByteArray = [0x1, 0x2, 0x4, 0x8]; + Assert.Equal(true, ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); Assert.Equal(false, ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); Assert.Equal("true", ValuePreprocessor.Preprocess("true", ValuePreprocessor.Type.BOOL)); + Assert.Equal("42", ValuePreprocessor.Preprocess("42", ValuePreprocessor.Type.INTEGER)); + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.INTEGER)); + Assert.Equal("3.14159", ValuePreprocessor.Preprocess("3.14159", ValuePreprocessor.Type.FLOATING_POINT)); + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.FLOATING_POINT)); + Assert.Equal("2.71828", ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Type.UNDEFINED_NUMBER)); + Assert.Equal("TestString", ValuePreprocessor.Preprocess("TestString", ValuePreprocessor.Type.STRING)); + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.STRING)); + Assert.Equal("TestData", ValuePreprocessor.Preprocess("TestData", ValuePreprocessor.Type.DATA)); - byte[] value = [0x1, 0x2, 0x4, 0x8]; - Assert.Equal(value, ValuePreprocessor.Preprocess(value, ValuePreprocessor.Type.DATA)); + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.DATA)); + + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.DATE)); Assert.Equal("01.02.1903", ValuePreprocessor.Preprocess("01.02.1903", ValuePreprocessor.Type.DATE)); Assert.Equal(23.0, ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Type.DATE)); } @@ -105,7 +116,7 @@ namespace plistcil.test [Fact] public static void TestUnregisteredPreprocessorThrows() { - byte[] testArray = [0x1, 0x2, 0x4, 0x8]; + int[] testArray = [1, 2, 4, 8]; // there's no registered preprocessor for byte array arguments for STRING Assert.Throws(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING)); diff --git a/plist-cil/BinaryPropertyListParser.cs b/plist-cil/BinaryPropertyListParser.cs index 308b582..3559714 100644 --- a/plist-cil/BinaryPropertyListParser.cs +++ b/plist-cil/BinaryPropertyListParser.cs @@ -260,7 +260,7 @@ namespace Claunia.PropertyList PropertyListFormatException("The given binary property list contains a date object of an unknown type (" + objInfo + ")"); - return new NSDate(bytes.Slice(offset + 1, 8)); + return new NSDate(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, 8).ToArray(), ValuePreprocessor.Type.DATE)); } case 0x4: { @@ -274,7 +274,7 @@ namespace Claunia.PropertyList //ASCII String, each character is 1 byte ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset); - return new NSString(bytes.Slice(offset + stroffset, length), Encoding.ASCII); + return new NSString(ValuePreprocessor.Preprocess(bytes.Slice(offset + stroffset, length).ToArray(), ValuePreprocessor.Type.STRING), Encoding.ASCII); } case 0x6: { diff --git a/plist-cil/NSDate.cs b/plist-cil/NSDate.cs index 2ff9778..d424925 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.Type.DATE)); + Date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes)); /// /// Parses a date from its textual representation. That representation has the following pattern: diff --git a/plist-cil/NSString.cs b/plist-cil/NSString.cs index 4426973..cfad91a 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.Type.STRING); + Content = encoding.GetString(bytes); #else - Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes.ToArray()), ValuePreprocessor.Type.STRING); + Content = encoding.GetString(bytes.ToArray()); #endif } diff --git a/plist-cil/ValuePreprocessor.cs b/plist-cil/ValuePreprocessor.cs index 436fa3f..d205a8e 100644 --- a/plist-cil/ValuePreprocessor.cs +++ b/plist-cil/ValuePreprocessor.cs @@ -40,10 +40,12 @@ namespace Claunia.PropertyList { new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor }, { new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor }, { new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor }, + { new TypeIdentifier(Type.STRING, typeof(byte[])), 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 }, + { new TypeIdentifier(Type.DATE, typeof(byte[])), NullPreprocessor }, }; /// From d83ebcaf66cd51688dd27ec4d7b9070227283fbf Mon Sep 17 00:00:00 2001 From: Johann Studanski Date: Mon, 27 Jan 2025 18:25:33 +0100 Subject: [PATCH 9/9] Remove superfluous using --- plist-cil/XmlPropertyListParser.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/plist-cil/XmlPropertyListParser.cs b/plist-cil/XmlPropertyListParser.cs index b4c02a2..644602f 100644 --- a/plist-cil/XmlPropertyListParser.cs +++ b/plist-cil/XmlPropertyListParser.cs @@ -23,7 +23,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -using System; using System.Collections.Generic; using System.IO; using System.Linq;