Some cleanup around the ValuePreprocessor

This commit is contained in:
Johann Studanski
2025-01-23 17:04:01 +01:00
parent 864cca7e7d
commit 462668f605
9 changed files with 89 additions and 65 deletions

View File

@@ -36,18 +36,18 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestPassiveDefaultPreprocessorsRegistered() public static void TestPassiveDefaultPreprocessorsRegistered()
{ {
Assert.Equal(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL), true); Assert.Equal(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL), true);
Assert.Equal(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.BOOL), false); Assert.Equal(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL), false);
Assert.Equal(ValuePreprocessor.Preprocess("true", ValuePreprocessor.Types.BOOL), "true"); Assert.Equal(ValuePreprocessor.Preprocess("true", ValuePreprocessor.Type.BOOL), "true");
Assert.Equal(ValuePreprocessor.Preprocess("42",ValuePreprocessor.Types.INTEGER), "42"); Assert.Equal(ValuePreprocessor.Preprocess("42",ValuePreprocessor.Type.INTEGER), "42");
Assert.Equal(ValuePreprocessor.Preprocess("3.14159",ValuePreprocessor.Types.FLOATING_POINT), "3.14159"); Assert.Equal(ValuePreprocessor.Preprocess("3.14159",ValuePreprocessor.Type.FLOATING_POINT), "3.14159");
Assert.Equal(ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Types.UNDEFINED_NUMBER), "2.71828"); Assert.Equal(ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Type.UNDEFINED_NUMBER), "2.71828");
Assert.Equal(ValuePreprocessor.Preprocess("TestString",ValuePreprocessor.Types.STRING), "TestString"); Assert.Equal(ValuePreprocessor.Preprocess("TestString",ValuePreprocessor.Type.STRING), "TestString");
Assert.Equal(ValuePreprocessor.Preprocess("TestData",ValuePreprocessor.Types.DATA), "TestData"); Assert.Equal(ValuePreprocessor.Preprocess("TestData",ValuePreprocessor.Type.DATA), "TestData");
byte[] value = { 0x1, 0x2, 0x4, 0x8 }; byte[] value = { 0x1, 0x2, 0x4, 0x8 };
Assert.Equal(ValuePreprocessor.Preprocess(value,ValuePreprocessor.Types.DATA), value); Assert.Equal(ValuePreprocessor.Preprocess(value,ValuePreprocessor.Type.DATA), value);
Assert.Equal(ValuePreprocessor.Preprocess("01.02.1903",ValuePreprocessor.Types.DATE), "01.02.1903"); Assert.Equal(ValuePreprocessor.Preprocess("01.02.1903",ValuePreprocessor.Type.DATE), "01.02.1903");
Assert.Equal(ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Types.DATE), 23.0); Assert.Equal(ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Type.DATE), 23.0);
} }
[Fact] [Fact]
@@ -57,12 +57,12 @@ namespace plistcil.test
string testString = "TestString"; string testString = "TestString";
string expected = "gnirtStseT"; string expected = "gnirtStseT";
ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Types.STRING); ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Type.STRING);
string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Types.STRING); string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Type.STRING);
Assert.Equal(actual, expected); Assert.Equal(actual, expected);
ValuePreprocessor.Unregister<string>(ValuePreprocessor.Types.STRING); ValuePreprocessor.Unregister<string>(ValuePreprocessor.Type.STRING);
} }
[Fact] [Fact]
@@ -71,13 +71,13 @@ namespace plistcil.test
Func<string, string> examplePreprocessor = value => new string(value.Reverse().ToArray()); Func<string, string> examplePreprocessor = value => new string(value.Reverse().ToArray());
string testString = "TestString"; string testString = "TestString";
ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Types.STRING); ValuePreprocessor.Register(examplePreprocessor, ValuePreprocessor.Type.STRING);
string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Types.DATA); string actual = ValuePreprocessor.Preprocess(testString, ValuePreprocessor.Type.DATA);
// assert unchanged, since the selected preprocessor != registered preprocessor // assert unchanged, since the selected preprocessor != registered preprocessor
Assert.Equal(actual, testString); Assert.Equal(actual, testString);
ValuePreprocessor.Unregister<string>(ValuePreprocessor.Types.STRING); ValuePreprocessor.Unregister<string>(ValuePreprocessor.Type.STRING);
} }
[Fact] [Fact]
@@ -86,7 +86,7 @@ namespace plistcil.test
byte[] testArray = { 0x1, 0x2, 0x4, 0x8 }; byte[] testArray = { 0x1, 0x2, 0x4, 0x8 };
// there's no registered preprocessor for byte array arguments for STRING // there's no registered preprocessor for byte array arguments for STRING
Assert.Throws<ArgumentException>(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Types.STRING)); Assert.Throws<ArgumentException>(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING));
} }
} }
} }

View File

@@ -409,15 +409,15 @@ namespace Claunia.PropertyList
quotedString[4] == DATE_DATE_FIELD_DELIMITER) quotedString[4] == DATE_DATE_FIELD_DELIMITER)
try try
{ {
return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Types.DATE)); return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.DATE));
} }
catch(Exception) catch(Exception)
{ {
//not a date? --> return string //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: default:
{ {
@@ -429,7 +429,7 @@ namespace Claunia.PropertyList
//non-numerical -> string or boolean //non-numerical -> string or boolean
string parsedString = ParseString(); 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); Expect(DATA_GSBOOL_TRUE_TOKEN, DATA_GSBOOL_FALSE_TOKEN);
if(Accept(DATA_GSBOOL_TRUE_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 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 the parsed boolean token
Skip(); Skip();
@@ -541,14 +541,14 @@ namespace Claunia.PropertyList
//Date //Date
Skip(); Skip();
string dateString = ReadInputUntil(DATA_END_TOKEN); 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)) else if(Accept(DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN))
{ {
//Number //Number
Skip(); Skip();
string numberString = ReadInputUntil(DATA_END_TOKEN); 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 //parse data end token
@@ -569,7 +569,7 @@ namespace Claunia.PropertyList
bytes[i] = (byte)byteValue; 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 end token
Skip(); Skip();
@@ -586,18 +586,18 @@ namespace Claunia.PropertyList
if(numericalString.Length <= 4 || if(numericalString.Length <= 4 ||
numericalString[4] != DATE_DATE_FIELD_DELIMITER) 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 try
{ {
return new NSDate(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Types.DATE)); return new NSDate(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.DATE));
} }
catch(Exception) catch(Exception)
{ {
//An exception occurs if the string is not a date but just a string //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));
} }
/// <summary> /// <summary>

View File

@@ -204,12 +204,12 @@ namespace Claunia.PropertyList
case 0x8: case 0x8:
{ {
//false //false
return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.BOOL)); return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL));
} }
case 0x9: case 0x9:
{ {
//true //true
return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL)); return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL));
} }
case 0xC: case 0xC:
{ {
@@ -267,7 +267,7 @@ namespace Claunia.PropertyList
//Data //Data
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int dataoffset); 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: case 0x5:
{ {

View File

@@ -52,7 +52,7 @@ namespace Claunia.PropertyList
public NSDate(ReadOnlySpan<byte> bytes) => public NSDate(ReadOnlySpan<byte> bytes) =>
//dates are 8 byte big-endian double, seconds since the epoch //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));
/// <summary> /// <summary>
/// Parses a date from its textual representation. That representation has the following pattern: /// Parses a date from its textual representation. That representation has the following pattern:

View File

@@ -69,13 +69,13 @@ namespace Claunia.PropertyList
switch(type) switch(type)
{ {
case INTEGER: case INTEGER:
doubleValue = longValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseLong(bytes), ValuePreprocessor.Types.INTEGER); doubleValue = longValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseLong(bytes), ValuePreprocessor.Type.INTEGER);
break; break;
case REAL: case REAL:
doubleValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Types.FLOATING_POINT); doubleValue = ValuePreprocessor.Preprocess(BinaryPropertyListParser.ParseDouble(bytes), ValuePreprocessor.Type.FLOATING_POINT);
longValue = ValuePreprocessor.Preprocess((long)Math.Round(doubleValue), ValuePreprocessor.Types.INTEGER); longValue = ValuePreprocessor.Preprocess((long)Math.Round(doubleValue), ValuePreprocessor.Type.INTEGER);
break; break;

View File

@@ -48,9 +48,9 @@ namespace Claunia.PropertyList
public NSString(ReadOnlySpan<byte> bytes, Encoding encoding) public NSString(ReadOnlySpan<byte> bytes, Encoding encoding)
{ {
#if NATIVE_SPAN #if NATIVE_SPAN
Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes), ValuePreprocessor.Types.STRING); Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes), ValuePreprocessor.Type.STRING);
#else #else
Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes.ToArray()), ValuePreprocessor.Types.STRING); Content = ValuePreprocessor.Preprocess(encoding.GetString(bytes.ToArray()), ValuePreprocessor.Type.STRING);
#endif #endif
} }

View File

@@ -150,7 +150,7 @@ namespace Claunia.PropertyList
/// <summary>Register preprocessing functions for for plist values.</summary> /// <summary>Register preprocessing functions for for plist values.</summary>
/// <param name="preprocessor">A function that preprocesses the passed string and returns the adjusted value.</param> /// <param name="preprocessor">A function that preprocesses the passed string and returns the adjusted value.</param>
/// <param name="type">The type of value preprocessor to use.</param> /// <param name="type">The type of value preprocessor to use.</param>
public static void RegisterValuePreprocessor<T>(Func<T, T> preprocessor, ValuePreprocessor.Types type) => public static void RegisterValuePreprocessor<T>(Func<T, T> preprocessor, ValuePreprocessor.Type type) =>
ValuePreprocessor.Register(preprocessor, type); ValuePreprocessor.Register(preprocessor, type);
/// <summary>Reads all bytes from an Stream and stores them in an array, up to a maximum count.</summary> /// <summary>Reads all bytes from an Stream and stores them in an array, up to a maximum count.</summary>

View File

@@ -9,41 +9,65 @@ namespace Claunia.PropertyList
/// </summary> /// </summary>
public static class ValuePreprocessor public static class ValuePreprocessor
{ {
public enum Types /// <summary>
/// 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).
/// </summary>
public enum Type
{ {
BOOL, INTEGER, FLOATING_POINT, UNDEFINED_NUMBER, BOOL, INTEGER, FLOATING_POINT, UNDEFINED_NUMBER,
STRING, DATA, DATE STRING, DATA, DATE
}; };
private record struct TypeIdentifier(Types ValueType, Type ProcessingType); /// <summary>
/// A Null-Implementation of a preprocessor for registered, but passive, use cases.
/// </summary>
private static T NullPreprocessor<T>(T value) => value; private static T NullPreprocessor<T>(T value) => value;
private record struct TypeIdentifier(Type ValueType, System.Type ProcessingType);
/// <summary>
/// Default preprocessors for all the standard cases.
/// </summary>
private static readonly Dictionary<TypeIdentifier, Delegate> _preprocessors = new() private static readonly Dictionary<TypeIdentifier, Delegate> _preprocessors = new()
{ {
{ new TypeIdentifier(Types.BOOL, typeof(bool)), NullPreprocessor<bool> }, { new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor<bool> },
{ new TypeIdentifier(Types.BOOL, typeof(string)), NullPreprocessor<string> }, { new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Types.INTEGER, typeof(string)), NullPreprocessor<string> }, { new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Types.FLOATING_POINT, typeof(string)), NullPreprocessor<string> }, { new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Types.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor<string> }, { new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Types.STRING, typeof(string)), NullPreprocessor<string> }, { new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Types.DATA, typeof(string)), NullPreprocessor<string> }, { new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Types.DATA, typeof(byte[])), NullPreprocessor<byte[]> }, { new TypeIdentifier(Type.DATA, typeof(byte[])), NullPreprocessor<byte[]> },
{ new TypeIdentifier(Types.DATE, typeof(string)), NullPreprocessor<string> }, { new TypeIdentifier(Type.DATE, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Types.DATE, typeof(double)), NullPreprocessor<double> }, { new TypeIdentifier(Type.DATE, typeof(double)), NullPreprocessor<double> },
}; };
public static void Register<T>(Func<T, T> preprocessor, Types type) => /// <summary>
/// Register a custom preprocessor.
/// </summary>
public static void Register<T>(Func<T, T> preprocessor, Type type) =>
_preprocessors[new(type, typeof(T))] = preprocessor; _preprocessors[new(type, typeof(T))] = preprocessor;
public static void Unregister<T>(Types type) => /// <summary>
/// Unregister a specific preprocessor--replaces it with a null implementation
/// to prevent argument errors.
/// </summary>
public static void Unregister<T>(Type type) =>
_preprocessors[new(type, typeof(T))] = NullPreprocessor<T>; _preprocessors[new(type, typeof(T))] = NullPreprocessor<T>;
public static T Preprocess<T>(T value, Types type) => /// <summary>
/// Preprocess the supplied data using the appropriate registered implementation.
/// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null implementation--was registered.</exception>
public static T Preprocess<T>(T value, Type type) =>
TryGetPreprocessor(type, out Func<T, T> preprocess) TryGetPreprocessor(type, out Func<T, T> preprocess)
? preprocess(value) ? preprocess(value)
: throw new ArgumentException($"Failed to find a preprocessor for value '{value}'."); : throw new ArgumentException($"Failed to find a preprocessor for value '{value}'.");
private static bool TryGetPreprocessor<T>(Types type, out Func<T, T> preprocess) /// <summary>
/// Gets the appropriate registered implementation--or null--and casts it back to the required type.
/// </summary>
private static bool TryGetPreprocessor<T>(Type type, out Func<T, T> preprocess)
{ {
if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor)) if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor))
{ {

View File

@@ -174,13 +174,13 @@ namespace Claunia.PropertyList
return array; return array;
} }
case "true": return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Types.BOOL)); case "true": return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL));
case "false": return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Types.BOOL)); case "false": return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL));
case "integer": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Types.INTEGER)); case "integer": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.INTEGER), NSNumber.INTEGER);
case "real": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Types.FLOATING_POINT)); 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.Types.STRING)); case "string": return new NSString(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.STRING));
case "data": return new NSData(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Types.DATA)); 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.Types.DATE)) : null; default: return n.Name.Equals("date") ? new NSDate(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.DATE)) : null;
} }
} }