General refactor and clean-up.

This commit is contained in:
2025-08-07 22:31:12 +01:00
parent 0683e2217b
commit 5f6bf00855
59 changed files with 6772 additions and 6828 deletions

View File

@@ -7,15 +7,16 @@ It is mostly based on [dd-plist for Java](https://github.com/3breadt/dd-plist).
And as it, this is licensed under the terms of the MIT license. And as it, this is licensed under the terms of the MIT license.
Property lists are files used to store user settings and serialized objects. Property lists are files used to store user settings and serialized objects.
They originate from the NeXTSTEP programming environment and are now a basic part of thhe Cocoa framework (macOS and iOS) as well as the GNUstep framework. They originate from the NeXTSTEP programming environment and are now a basic part of thhe Cocoa framework (macOS and
iOS) as well as the GNUstep framework.
## Features ## Features
* Read / write property lists from / to files, streams or byte arrays * Read / write property lists from / to files, streams or byte arrays
* Convert between property lists formats * Convert between property lists formats
* Property list contents are provided as objects from the NeXTSTEP environment (NSDictionary, NSArray, NSString, etc.) * Property list contents are provided as objects from the NeXTSTEP environment (NSDictionary, NSArray, NSString, etc.)
* Serialize native .NET data structures to property list objects * Serialize native .NET data structures to property list objects
* Deserialize from property list objects to native .NET data structures * Deserialize from property list objects to native .NET data structures
## Supported formats ## Supported formats
@@ -24,6 +25,7 @@ They originate from the NeXTSTEP programming environment and are now a basic par
* Cocoa / NeXTSTEP / GNUstep ASCII * Cocoa / NeXTSTEP / GNUstep ASCII
## Requirements ## Requirements
plist-cil targets: plist-cil targets:
- .NET Framework 4.5, - .NET Framework 4.5,
@@ -32,16 +34,20 @@ plist-cil targets:
- .NET Core 3.1. - .NET Core 3.1.
- .NET 5.0 - .NET 5.0
This means it should be compatible with Mono, Xamarin.iOS, Xamarin.Mac, UWP, etc. If you find an incompatibility, please create an issue. This means it should be compatible with Mono, Xamarin.iOS, Xamarin.Mac, UWP, etc. If you find an incompatibility, please
create an issue.
## Download ## Download
The latest releases can be downloaded [here](https://github.com/claunia/plist-cil/releases). The latest releases can be downloaded [here](https://github.com/claunia/plist-cil/releases).
## NuGet support ## NuGet support
You can download the NuGet package directly from the [release](https://github.com/claunia/plist-cil/releases) page or from the [NuGet Gallery](https://www.nuget.org/) or from [here](https://www.nuget.org/packages/plist-cil/).
You can download the NuGet package directly from the [release](https://github.com/claunia/plist-cil/releases) page or
from the [NuGet Gallery](https://www.nuget.org/) or from [here](https://www.nuget.org/packages/plist-cil/).
## Help ## Help
The API documentation is included in the download. The API documentation is included in the download.
When you encounter a bug please report it by on the [issue tracker](https://github.com/claunia/plist-cil/issues). When you encounter a bug please report it by on the [issue tracker](https://github.com/claunia/plist-cil/issues).
@@ -50,28 +56,39 @@ When you encounter a bug please report it by on the [issue tracker](https://gith
### Reading ### Reading
Parsing can be done with the PropertyListParser class. You can feed the `PropertyListParser` with a `FileInfo`, a `Stream` or a `byte` array. Parsing can be done with the PropertyListParser class. You can feed the `PropertyListParser` with a `FileInfo`, a
The `Parse` method of the `PropertyListParser` will parse the input and give you a `NSObject` as result. Generally this is a `NSDictionary` but it can also be a `NSArray`. `Stream` or a `byte` array.
The `Parse` method of the `PropertyListParser` will parse the input and give you a `NSObject` as result. Generally this
is a `NSDictionary` but it can also be a `NSArray`.
_Note: Property lists created by `NSKeyedArchiver` are not yet supported._ _Note: Property lists created by `NSKeyedArchiver` are not yet supported._
You can then navigate the contents of the property lists using the various classes extending `NSObject`. These are modeled in such a way as to closely resemble the respective Cocoa classes. You can then navigate the contents of the property lists using the various classes extending `NSObject`. These are
modeled in such a way as to closely resemble the respective Cocoa classes.
You can also directly convert the contained `NSObject` objects into native .NET Objects with the `NSOBject.ToObject()` method. Using this method you can avoid working with `NSObject` instances altogether. You can also directly convert the contained `NSObject` objects into native .NET Objects with the `NSOBject.ToObject()`
method. Using this method you can avoid working with `NSObject` instances altogether.
### Writing ### Writing
You can create your own property list using the various constructors of the different `NSObject` classes. Or you can wrap existing native .NET structures with the method `NSObject.Wrap(Object o)`. Just make sure that the root object of the property list is either a `NSDictionary` (can be created from objects of the type `Dictionary<string, Object>`) or a `NSArray` (can be created from object arrays). You can create your own property list using the various constructors of the different `NSObject` classes. Or you can
wrap existing native .NET structures with the method `NSObject.Wrap(Object o)`. Just make sure that the root object of
the property list is either a `NSDictionary` (can be created from objects of the type `Dictionary<string, Object>`) or a
`NSArray` (can be created from object arrays).
For building a XML property list you can then call the `ToXml` method on the root object of your property list. It will give you an UTF-8 `string` containing the property list in XML format. For building a XML property list you can then call the `ToXml` method on the root object of your property list. It will
give you an UTF-8 `string` containing the property list in XML format.
If you want to have the property list in binary format use the `BinaryPropertyListWriter` class. It can write the binary property list directly to a file or to a `Stream`. If you want to have the property list in binary format use the `BinaryPropertyListWriter` class. It can write the binary
property list directly to a file or to a `Stream`.
When you directly want to save your property list to a file, you can also use the `SaveAsXml` or `SaveAsBinary` methods of the `PropertyListParser` class. When you directly want to save your property list to a file, you can also use the `SaveAsXml` or `SaveAsBinary` methods
of the `PropertyListParser` class.
### Converting ### Converting
For converting a file into another format there exist convenience methods in the `PropertyListParser` class: `ConvertToXml`, `ConvertToBinary`, `ConvertToASCII` and `ConvertToGnuStepASCII`. For converting a file into another format there exist convenience methods in the `PropertyListParser` class:
`ConvertToXml`, `ConvertToBinary`, `ConvertToASCII` and `ConvertToGnuStepASCII`.
## Code snippets ## Code snippets

View File

@@ -1 +1 @@
{"projectId":"c0f6bf32-f9bb-44e9-8988-33fb7a11f847","projectName":"plist-cil"} {"projectId": "c0f6bf32-f9bb-44e9-8988-33fb7a11f847", "projectName": "plist-cil"}

View File

@@ -2,11 +2,12 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
namespace Claunia.PropertyList.Benchmark namespace Claunia.PropertyList.Benchmark;
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]
public class BinaryPropertyListParserBenchmarks
{ {
[SimpleJob(RuntimeMoniker.NetCoreApp50), MemoryDiagnoser]
public class BinaryPropertyListParserBenchmarks
{
byte[] data; byte[] data;
[GlobalSetup] [GlobalSetup]
@@ -19,5 +20,4 @@ namespace Claunia.PropertyList.Benchmark
return nsObject; return nsObject;
} }
}
} }

View File

@@ -1,11 +1,12 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
namespace Claunia.PropertyList.Benchmark namespace Claunia.PropertyList.Benchmark;
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]
public class BinaryPropertyListWriterBenchmarks
{ {
[SimpleJob(RuntimeMoniker.NetCoreApp50), MemoryDiagnoser]
public class BinaryPropertyListWriterBenchmarks
{
NSObject data; NSObject data;
[GlobalSetup] [GlobalSetup]
@@ -13,5 +14,4 @@ namespace Claunia.PropertyList.Benchmark
[Benchmark] [Benchmark]
public byte[] WriteLargePropertylistTest() => BinaryPropertyListWriter.WriteToArray(data); public byte[] WriteLargePropertylistTest() => BinaryPropertyListWriter.WriteToArray(data);
}
} }

View File

@@ -1,13 +1,12 @@
using BenchmarkDotNet.Running; using BenchmarkDotNet.Running;
namespace Claunia.PropertyList.Benchmark namespace Claunia.PropertyList.Benchmark;
internal class Program
{ {
internal class Program
{
static void Main(string[] args) static void Main(string[] args)
{ {
BenchmarkRunner.Run<BinaryPropertyListParserBenchmarks>(); BenchmarkRunner.Run<BinaryPropertyListParserBenchmarks>();
BenchmarkRunner.Run<BinaryPropertyListWriterBenchmarks>(); BenchmarkRunner.Run<BinaryPropertyListWriterBenchmarks>();
} }
}
} }

View File

@@ -7,11 +7,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.2" /> <PackageReference Include="BenchmarkDotNet" Version="0.15.2"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\plist-cil\plist-cil.csproj" /> <ProjectReference Include="..\plist-cil\plist-cil.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,3 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xml:space="preserve">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Claunia/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Claunia/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dreibrodt/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=Dreibrodt/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -1,68 +1,99 @@
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public class BinaryPropertyListParserTests
{ {
public class BinaryPropertyListParserTests [Theory]
{ [InlineData(new byte[]
[Theory, InlineData(new byte[]
{ {
0x08 0x08
}, 0x08), InlineData(new byte[] },
0x08)]
[InlineData(new byte[]
{ {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07
}, 7), InlineData(new byte[] },
7)]
[InlineData(new byte[]
{ {
0x00, 0x0e, 0x47, 0x7b 0x00, 0x0e, 0x47, 0x7b
}, 0x00000000000e477b)] },
0x00000000000e477b)]
public void ParseUnsignedIntTest(byte[] binaryValue, int expectedValue) => public void ParseUnsignedIntTest(byte[] binaryValue, int expectedValue) =>
Assert.Equal(expectedValue, BinaryPropertyListParser.ParseUnsignedInt(binaryValue)); Assert.Equal(expectedValue, BinaryPropertyListParser.ParseUnsignedInt(binaryValue));
[Theory, InlineData(new byte[] [Theory]
[InlineData(new byte[]
{ {
0x57 0x57
}, 0x57), InlineData(new byte[] },
0x57)]
[InlineData(new byte[]
{ {
0x12, 0x34 0x12, 0x34
}, 0x1234), InlineData(new byte[] },
0x1234)]
[InlineData(new byte[]
{ {
0x12, 0x34, 0x56 0x12, 0x34, 0x56
}, 0x123456), InlineData(new byte[] },
0x123456)]
[InlineData(new byte[]
{ {
0x40, 0x2d, 0xf8, 0x4d 0x40, 0x2d, 0xf8, 0x4d
}, 0x402df84d), InlineData(new byte[] },
0x402df84d)]
[InlineData(new byte[]
{ {
0x12, 0x34, 0x56, 0x78, 0x9a 0x12, 0x34, 0x56, 0x78, 0x9a
}, 0x123456789a), InlineData(new byte[] },
0x123456789a)]
[InlineData(new byte[]
{ {
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc
}, 0x123456789abc), InlineData(new byte[] },
0x123456789abc)]
[InlineData(new byte[]
{ {
0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde
}, 0x123456789abcde), InlineData(new byte[] },
0x123456789abcde)]
[InlineData(new byte[]
{ {
0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00 0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00
}, 0x41b483982a000000), InlineData(new byte[] },
0x41b483982a000000)]
[InlineData(new byte[]
{ {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19
}, unchecked((long)0xfffffffffffffc19)), InlineData(new byte[] },
unchecked((long)0xfffffffffffffc19))]
[InlineData(new byte[]
{ {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19
}, unchecked((long)0xfffffffffffffc19))] },
unchecked((long)0xfffffffffffffc19))]
public void ParseLongTest(byte[] binaryValue, long expectedValue) => public void ParseLongTest(byte[] binaryValue, long expectedValue) =>
Assert.Equal(expectedValue, BinaryPropertyListParser.ParseLong(binaryValue)); Assert.Equal(expectedValue, BinaryPropertyListParser.ParseLong(binaryValue));
[Theory, InlineData(new byte[] [Theory]
[InlineData(new byte[]
{ {
0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00 0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00
}, 344168490), InlineData(new byte[] },
344168490)]
[InlineData(new byte[]
{ {
0x40, 0x09, 0x21, 0xf9, 0xf0, 0x1b, 0x86, 0x6e 0x40, 0x09, 0x21, 0xf9, 0xf0, 0x1b, 0x86, 0x6e
}, 3.14159), InlineData(new byte[] },
3.14159)]
[InlineData(new byte[]
{ {
0x40, 0x2d, 0xf8, 0x4d 0x40, 0x2d, 0xf8, 0x4d
}, 2.71828007698059)] },
2.71828007698059)]
public void ParseDoubleTest(byte[] binaryValue, double expectedValue) => public void ParseDoubleTest(byte[] binaryValue, double expectedValue) =>
Assert.Equal(expectedValue, BinaryPropertyListParser.ParseDouble(binaryValue), 14); Assert.Equal(expectedValue, BinaryPropertyListParser.ParseDouble(binaryValue), 14);
}
} }

View File

@@ -2,10 +2,10 @@
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public class BinaryPropertyListWriterTests
{ {
public class BinaryPropertyListWriterTests
{
[Fact] [Fact]
public void Roundtrip2Test() public void Roundtrip2Test()
{ {
@@ -38,8 +38,11 @@ namespace plistcil.test
using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); using var validatingStream = new ValidatingStream(actualOutput, expectedOutput);
var writer = new BinaryPropertyListWriter(validatingStream); var writer = new BinaryPropertyListWriter(validatingStream)
writer.ReuseObjectIds = false; {
ReuseObjectIds = false
};
writer.Write(root); writer.Write(root);
} }
@@ -82,5 +85,4 @@ namespace plistcil.test
writer.Write(root); writer.Write(root);
} }
}
} }

View File

@@ -27,10 +27,10 @@ using System.IO;
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public static class IssueTest
{ {
public static class IssueTest
{
/// <summary> /// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario where the binary /// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario where the binary
/// data is not indented (no leading whitespace). /// data is not indented (no leading whitespace).
@@ -128,17 +128,17 @@ namespace plistcil.test
[Fact(Skip = "Support for property lists with a root element which is not plist is not implemented")] [Fact(Skip = "Support for property lists with a root element which is not plist is not implemented")]
public static void TestIssue30() public static void TestIssue30()
{ {
#pragma warning disable 219 #pragma warning disable 219
var arr = (NSArray)PropertyListParser.Parse(new FileInfo("test-files/issue30.plist")); var arr = (NSArray)PropertyListParser.Parse(new FileInfo("test-files/issue30.plist"));
#pragma warning restore 219 #pragma warning restore 219
} }
[Fact] [Fact]
public static void TestIssue33() public static void TestIssue33()
{ {
#pragma warning disable 219 #pragma warning disable 219
var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj")); var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj"));
#pragma warning restore 219 #pragma warning restore 219
} }
[Fact] [Fact]
@@ -188,5 +188,4 @@ namespace plistcil.test
Assert.IsType<double>(weight); Assert.IsType<double>(weight);
Assert.Equal(10d, (double)weight); Assert.Equal(10d, (double)weight);
} }
}
} }

View File

@@ -2,10 +2,10 @@
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public class NSArrayTests
{ {
public class NSArrayTests
{
/// <summary>Tests the addition of a .NET object to the NSArray</summary> /// <summary>Tests the addition of a .NET object to the NSArray</summary>
[Fact] [Fact]
public void AddAndContainsObjectTest() public void AddAndContainsObjectTest()
@@ -87,5 +87,4 @@ namespace plistcil.test
Assert.Empty(array); Assert.Empty(array);
} }
}
} }

View File

@@ -2,10 +2,10 @@
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public class NSDateTests
{ {
public class NSDateTests
{
[Fact] [Fact]
public static void ConstructorTest() public static void ConstructorTest()
{ {
@@ -23,5 +23,4 @@ namespace plistcil.test
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
}
} }

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public class NSNumberTests
{ {
public class NSNumberTests
{
public static IEnumerable<object[]> SpanConstructorTestData() => new List<object[]> public static IEnumerable<object[]> SpanConstructorTestData() => new List<object[]>
{ {
// INTEGER values // INTEGER values
@@ -124,22 +124,14 @@ namespace plistcil.test
// 4-byte value (float) // 4-byte value (float)
new object[] new object[]
{ {
new byte[] "\0\0\0\0"u8.ToArray(),
{
0x00, 0x00, 0x00, 0x00
},
NSNumber.REAL, false, 0, 0.0 NSNumber.REAL, false, 0, 0.0
}, },
new object[] new object[]
{ {
new byte[] "A \0\0"u8.ToArray(),
{
0x41, 0x20, 0x00, 0x00
},
NSNumber.REAL, true, 10, 10.0 NSNumber.REAL, true, 10, 10.0
}, },
new object[] new object[]
{ {
new byte[] new byte[]
@@ -152,22 +144,14 @@ namespace plistcil.test
// 8-byte value (double) // 8-byte value (double)
new object[] new object[]
{ {
new byte[] "\0\0\0\0\0\0\0\0"u8.ToArray(),
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
},
NSNumber.REAL, false, 0, 0.0 NSNumber.REAL, false, 0, 0.0
}, },
new object[] new object[]
{ {
new byte[] "@$\0\0\0\0\0\0"u8.ToArray(),
{
0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
},
NSNumber.REAL, true, 10, 10.0 NSNumber.REAL, true, 10, 10.0
}, },
new object[] new object[]
{ {
new byte[] new byte[]
@@ -178,7 +162,8 @@ namespace plistcil.test
} }
}; };
[Theory, MemberData(nameof(SpanConstructorTestData))] [Theory]
[MemberData(nameof(SpanConstructorTestData))]
public void SpanConstructorTest(byte[] data, int type, bool boolValue, long longValue, double doubleValue) public void SpanConstructorTest(byte[] data, int type, bool boolValue, long longValue, double doubleValue)
{ {
var number = new NSNumber((Span<byte>)data, type); var number = new NSNumber((Span<byte>)data, type);
@@ -228,7 +213,8 @@ namespace plistcil.test
// The value being used comes seen in a real property list: // The value being used comes seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
[Fact, UseCulture("en-US")] [Fact]
[UseCulture("en-US")]
public static void ParseNumberEnTest() public static void ParseNumberEnTest()
{ {
var number = new NSNumber("7200.000001"); var number = new NSNumber("7200.000001");
@@ -236,7 +222,8 @@ namespace plistcil.test
Assert.Equal(7200.000001d, number.ToDouble()); Assert.Equal(7200.000001d, number.ToDouble());
} }
[Fact, UseCulture("nl-BE")] [Fact]
[UseCulture("nl-BE")]
public static void ParseNumberNlTest() public static void ParseNumberNlTest()
{ {
// As seen in a real property list: // As seen in a real property list:
@@ -247,7 +234,8 @@ namespace plistcil.test
Assert.Equal(7200.000001d, number.ToDouble()); Assert.Equal(7200.000001d, number.ToDouble());
} }
[Fact, UseCulture("en-US")] [Fact]
[UseCulture("en-US")]
public static void ParseNumberEnTest2() public static void ParseNumberEnTest2()
{ {
// As seen in a real property list: // As seen in a real property list:
@@ -258,7 +246,8 @@ namespace plistcil.test
Assert.Equal(7200d, number.ToDouble()); Assert.Equal(7200d, number.ToDouble());
} }
[Fact, UseCulture("nl-BE")] [Fact]
[UseCulture("nl-BE")]
public static void ParseNumberNlTest2() public static void ParseNumberNlTest2()
{ {
// As seen in a real property list: // As seen in a real property list:
@@ -338,7 +327,6 @@ namespace plistcil.test
{ {
"TRUE", true, 1, 1 "TRUE", true, 1, 1
}, },
new object[] new object[]
{ {
"no", false, 0, 0 "no", false, 0, 0
@@ -365,7 +353,8 @@ namespace plistcil.test
} }
}; };
[Theory, MemberData(nameof(StringConstructorTestData))] [Theory]
[MemberData(nameof(StringConstructorTestData))]
public void StringConstructorTest(string value, bool boolValue, long longValue, double doubleValue) public void StringConstructorTest(string value, bool boolValue, long longValue, double doubleValue)
{ {
var number = new NSNumber(value); var number = new NSNumber(value);
@@ -406,7 +395,8 @@ namespace plistcil.test
} }
}; };
[Theory, MemberData(nameof(Int32ConstructorTestData))] [Theory]
[MemberData(nameof(Int32ConstructorTestData))]
public void Int32ConstructorTest(int value, bool boolValue, long longValue, double doubleValue) public void Int32ConstructorTest(int value, bool boolValue, long longValue, double doubleValue)
{ {
var number = new NSNumber(value); var number = new NSNumber(value);
@@ -440,7 +430,8 @@ namespace plistcil.test
} }
}; };
[Theory, MemberData(nameof(Int64ConstructorTestData))] [Theory]
[MemberData(nameof(Int64ConstructorTestData))]
public void Int64ConstructorTest(long value, bool boolValue, long longValue, double doubleValue) public void Int64ConstructorTest(long value, bool boolValue, long longValue, double doubleValue)
{ {
var number = new NSNumber(value); var number = new NSNumber(value);
@@ -478,7 +469,8 @@ namespace plistcil.test
} }
}; };
[Theory, MemberData(nameof(DoubleConstructorTestData))] [Theory]
[MemberData(nameof(DoubleConstructorTestData))]
public void DoubleConstructorTest(double value, bool boolValue, long longValue, double doubleValue) public void DoubleConstructorTest(double value, bool boolValue, long longValue, double doubleValue)
{ {
var number = new NSNumber(value); var number = new NSNumber(value);
@@ -500,7 +492,8 @@ namespace plistcil.test
} }
}; };
[Theory, MemberData(nameof(BoolConstructorTestData))] [Theory]
[MemberData(nameof(BoolConstructorTestData))]
public void BoolConstructorTest(bool value, bool boolValue, long longValue, double doubleValue) public void BoolConstructorTest(bool value, bool boolValue, long longValue, double doubleValue)
{ {
var number = new NSNumber(value); var number = new NSNumber(value);
@@ -519,5 +512,4 @@ namespace plistcil.test
Assert.True(a.Equals(b)); Assert.True(a.Equals(b));
Assert.True(b.Equals(a)); Assert.True(b.Equals(a));
} }
}
} }

View File

@@ -1,10 +1,10 @@
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public class NSStringTests
{ {
public class NSStringTests
{
const string START_TOKEN = "<string>"; const string START_TOKEN = "<string>";
const string END_TOKEN = "</string>"; const string END_TOKEN = "</string>";
@@ -25,5 +25,4 @@ namespace plistcil.test
Assert.Equal(content, actualContent); Assert.Equal(content, actualContent);
} }
}
} }

View File

@@ -29,18 +29,16 @@ using System.IO;
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public static class ParseTest
{ {
public static class ParseTest
{
static bool ArrayEquals(byte[] arrayA, byte[] arrayB) static bool ArrayEquals(byte[] arrayA, byte[] arrayB)
{ {
if(arrayA.Length != arrayB.Length) if(arrayA.Length != arrayB.Length) return false;
return false;
for(int i = 0; i < arrayA.Length; i++) for(int i = 0; i < arrayA.Length; i++)
if(arrayA[i] != arrayB[i]) if(arrayA[i] != arrayB[i]) return false;
return false;
return true; return true;
} }
@@ -85,10 +83,10 @@ namespace plistcil.test
Assert.Equal(actualDate.Date, expectedDate); Assert.Equal(actualDate.Date, expectedDate);
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[] Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
{ [
0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82
})); ]));
var a = (NSArray)d.ObjectForKey("array"); var a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4); Assert.True(a.Count == 4);
@@ -147,13 +145,13 @@ namespace plistcil.test
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30, Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30, DateTimeKind.Utc)
DateTimeKind.Utc).ToLocalTime())); .ToLocalTime()));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[] Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
{ [
0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82
})); ]));
var a = (NSArray)d.ObjectForKey("array"); var a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4); Assert.True(a.Count == 4);
@@ -188,19 +186,19 @@ namespace plistcil.test
string strg = "Hello World"; string strg = "Hello World";
byte[] bytes = byte[] bytes =
{ [
0x00, 0xAF, 0xAF 0x00, 0xAF, 0xAF
}; ];
object[] array = object[] array =
{ [
bl, byt, shrt, i, lng, flt, dbl, date, strg, bytes bl, byt, shrt, i, lng, flt, dbl, date, strg, bytes
}; ];
int[] array2 = int[] array2 =
{ [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3000 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3000
}; ];
List<object> list = new(array); List<object> list = new(array);
@@ -209,15 +207,21 @@ namespace plistcil.test
map.Add("long", lng); map.Add("long", lng);
map.Add("date", date); map.Add("date", date);
List<Dictionary<string,object>> listOfMaps = new() List<Dictionary<string, object>> listOfMaps =
{ [
new Dictionary<string, object> new Dictionary<string, object>
{ {
{ "int", i }, {
{ "long", lng }, "int", i
{ "date", date } },
{
"long", lng
},
{
"date", date
} }
}; }
];
var WrappedO = NSObject.Wrap((object)bl); var WrappedO = NSObject.Wrap((object)bl);
Assert.True(WrappedO is (NSNumber)); Assert.True(WrappedO is (NSNumber));
@@ -260,8 +264,7 @@ namespace plistcil.test
byte[] data = (byte[])WrappedO.ToObject(); byte[] data = (byte[])WrappedO.ToObject();
Assert.True(data.Length == bytes.Length); Assert.True(data.Length == bytes.Length);
for(int x = 0; x < bytes.Length; x++) for(int x = 0; x < bytes.Length; x++) Assert.True(data[x] == bytes[x]);
Assert.True(data[x] == bytes[x]);
WrappedO = NSObject.Wrap((object)array); WrappedO = NSObject.Wrap((object)array);
Assert.True(WrappedO is (NSArray)); Assert.True(WrappedO is (NSArray));
@@ -284,7 +287,7 @@ namespace plistcil.test
Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng); Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng);
Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date)); Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date));
WrappedO = NSObject.Wrap((object)listOfMaps); WrappedO = NSObject.Wrap(listOfMaps);
Assert.True(WrappedO is (NSArray)); Assert.True(WrappedO is (NSArray));
var arrayOfMaps = (NSArray)WrappedO; var arrayOfMaps = (NSArray)WrappedO;
Assert.True(arrayOfMaps.Count == 1); Assert.True(arrayOfMaps.Count == 1);
@@ -317,15 +320,25 @@ namespace plistcil.test
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 10, 21, 30, Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011,
11,
28,
10,
21,
30,
DateTimeKind.Utc)) || DateTimeKind.Utc)) ||
((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30, ((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011,
11,
28,
9,
21,
30,
DateTimeKind.Utc))); DateTimeKind.Utc)));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[] Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
{ [
0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82
})); ]));
var a = (NSArray)d.ObjectForKey("array"); var a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4); Assert.True(a.Count == 4);
@@ -339,5 +352,4 @@ namespace plistcil.test
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testXml.plist")); NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testXml.plist"));
Assert.True(x.Equals(y)); Assert.True(x.Equals(y));
} }
}
} }

View File

@@ -2,10 +2,10 @@
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public class PropertyListParserTests
{ {
public class PropertyListParserTests
{
static void ParseEmptyStreamTestDelegate() static void ParseEmptyStreamTestDelegate()
{ {
using var stream = new MemoryStream(); using var stream = new MemoryStream();
@@ -16,5 +16,4 @@ namespace plistcil.test
[Fact] [Fact]
public static void ParseEmptyStreamTest() => public static void ParseEmptyStreamTest() =>
Assert.Throws<PropertyListFormatException>(ParseEmptyStreamTestDelegate); Assert.Throws<PropertyListFormatException>(ParseEmptyStreamTestDelegate);
}
} }

View File

@@ -2,20 +2,24 @@
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public class UIDTests
{ {
public class UIDTests [Theory]
{ [InlineData(new byte[]
[Theory, InlineData(new byte[]
{ {
0xAB 0xAB
}), InlineData(new byte[] })]
[InlineData(new byte[]
{ {
0xAB, 0xCD 0xAB, 0xCD
}), InlineData(new byte[] })]
[InlineData(new byte[]
{ {
0xAB, 0xCD, 0xEF, 0xFE 0xAB, 0xCD, 0xEF, 0xFE
}), InlineData(new byte[] })]
[InlineData(new byte[]
{ {
0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB 0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB
})] })]
@@ -46,7 +50,8 @@ namespace plistcil.test
Assert.Equal(new byte[] Assert.Equal(new byte[]
{ {
0xAB 0xAB
}, uid.Bytes); },
uid.Bytes);
Assert.Equal(0xABu, uid.ToUInt64()); Assert.Equal(0xABu, uid.ToUInt64());
} }
@@ -59,7 +64,8 @@ namespace plistcil.test
Assert.Equal(new byte[] Assert.Equal(new byte[]
{ {
0xAB, 0xCD, 0xEF, 0x00 0xAB, 0xCD, 0xEF, 0x00
}, uid.Bytes); },
uid.Bytes);
Assert.Equal(0xABCDEF00, uid.ToUInt64()); Assert.Equal(0xABCDEF00, uid.ToUInt64());
} }
@@ -72,7 +78,8 @@ namespace plistcil.test
Assert.Equal(new byte[] Assert.Equal(new byte[]
{ {
0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB
}, uid.Bytes); },
uid.Bytes);
Assert.Equal(0xABCDEF0000EFCDAB, uid.ToUInt64()); Assert.Equal(0xABCDEF0000EFCDAB, uid.ToUInt64());
} }
@@ -85,7 +92,8 @@ namespace plistcil.test
Assert.Equal(new byte[] Assert.Equal(new byte[]
{ {
0xAB, 0xCD, 0xEF, 0x00 0xAB, 0xCD, 0xEF, 0x00
}, uid.Bytes); },
uid.Bytes);
Assert.Equal(0xABCDEF00u, uid.ToUInt64()); Assert.Equal(0xABCDEF00u, uid.ToUInt64());
} }
@@ -98,7 +106,8 @@ namespace plistcil.test
Assert.Equal(new byte[] Assert.Equal(new byte[]
{ {
0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB
}, uid.Bytes); },
uid.Bytes);
Assert.Equal(0xABCDEF0000EFCDABu, uid.ToUInt64()); Assert.Equal(0xABCDEF0000EFCDABu, uid.ToUInt64());
} }
@@ -111,7 +120,8 @@ namespace plistcil.test
Assert.Equal(new byte[] Assert.Equal(new byte[]
{ {
0xAB, 0xCD 0xAB, 0xCD
}, uid.Bytes); },
uid.Bytes);
Assert.Equal(0xABCDu, uid.ToUInt64()); Assert.Equal(0xABCDu, uid.ToUInt64());
} }
@@ -128,5 +138,4 @@ namespace plistcil.test
var roundtrip = XmlPropertyListParser.ParseString(plist) as UID; var roundtrip = XmlPropertyListParser.ParseString(plist) as UID;
Assert.Equal(0xabcdUL, roundtrip.ToUInt64()); Assert.Equal(0xabcdUL, roundtrip.ToUInt64());
} }
}
} }

View File

@@ -52,19 +52,19 @@ public class UseCultureAttribute : BeforeAfterTestAttribute
/// <param name="methodUnderTest">The method under test</param> /// <param name="methodUnderTest">The method under test</param>
public override void Before(MethodInfo methodUnderTest) public override void Before(MethodInfo methodUnderTest)
{ {
#if NETCORE #if NETCORE
originalCulture = CultureInfo.CurrentCulture; originalCulture = CultureInfo.CurrentCulture;
originalUICulture = CultureInfo.CurrentUICulture; originalUICulture = CultureInfo.CurrentUICulture;
CultureInfo.CurrentCulture = Culture; CultureInfo.CurrentCulture = Culture;
CultureInfo.CurrentUICulture = Culture; CultureInfo.CurrentUICulture = Culture;
#else #else
originalCulture = Thread.CurrentThread.CurrentCulture; originalCulture = Thread.CurrentThread.CurrentCulture;
originalUICulture = Thread.CurrentThread.CurrentUICulture; originalUICulture = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentCulture = Culture; Thread.CurrentThread.CurrentCulture = Culture;
Thread.CurrentThread.CurrentUICulture = UICulture; Thread.CurrentThread.CurrentUICulture = UICulture;
#endif #endif
} }
/// <summary> /// <summary>
@@ -74,12 +74,12 @@ public class UseCultureAttribute : BeforeAfterTestAttribute
/// <param name="methodUnderTest">The method under test</param> /// <param name="methodUnderTest">The method under test</param>
public override void After(MethodInfo methodUnderTest) public override void After(MethodInfo methodUnderTest)
{ {
#if NETCORE #if NETCORE
CultureInfo.CurrentCulture = originalCulture; CultureInfo.CurrentCulture = originalCulture;
CultureInfo.CurrentUICulture = originalUICulture; CultureInfo.CurrentUICulture = originalUICulture;
#else #else
Thread.CurrentThread.CurrentCulture = originalCulture; Thread.CurrentThread.CurrentCulture = originalCulture;
Thread.CurrentThread.CurrentUICulture = originalUICulture; Thread.CurrentThread.CurrentUICulture = originalUICulture;
#endif #endif
} }
} }

View File

@@ -9,14 +9,14 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
/// <summary>
/// A <see cref="Stream" /> which writes its output to a <see cref="Stream" /> and validates that the data which
/// is being written to the output stream matches the data in a reference stream.
/// </summary>
internal class ValidatingStream : Stream
{ {
/// <summary>
/// A <see cref="Stream" /> which writes its output to a <see cref="Stream" /> and validates that the data which
/// is being written to the output stream matches the data in a reference stream.
/// </summary>
internal class ValidatingStream : Stream
{
readonly Stream expectedOutput; readonly Stream expectedOutput;
readonly Stream output; readonly Stream output;
@@ -55,8 +55,7 @@ namespace plistcil.test
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <inheritdoc /> /// <inheritdoc />
public override Task<int> public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
throw new NotSupportedException(); throw new NotSupportedException();
/// <inheritdoc /> /// <inheritdoc />
@@ -97,5 +96,4 @@ namespace plistcil.test
await output.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); await output.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
} }
}
} }

View File

@@ -3,10 +3,10 @@ using System.Linq;
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test;
public static class ValuePreprocessorTests
{ {
public static class ValuePreprocessorTests
{
// lock tests to make sure temporarily added / replaced preprocessors don't interfere with the other tests in this suite // 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(); private static readonly object _testLock = new();
@@ -91,7 +91,7 @@ namespace plistcil.test
lock(_testLock) lock(_testLock)
{ {
Func<string, string> examplePreprocessor = value => new string(value.Reverse().ToArray()); Func<string, string> examplePreprocessor = value => new string(value.Reverse().ToArray());
byte[] testByteArray = [0x42,]; byte[] testByteArray = [0x42];
string testString = "TestString"; string testString = "TestString";
var testType = (ValuePreprocessor.Type)42; var testType = (ValuePreprocessor.Type)42;
@@ -121,5 +121,4 @@ namespace plistcil.test
// 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.Type.STRING)); Assert.Throws<ArgumentException>(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING));
} }
}
} }

View File

@@ -5,8 +5,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3"> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -14,7 +14,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\plist-cil\plist-cil.csproj" /> <ProjectReference Include="..\plist-cil\plist-cil.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -111,7 +111,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" /> <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>weight</key> <key>weight</key>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>files</key> <key>files</key>
<dict> <dict>
<key>PkgInfo</key> <key>PkgInfo</key>
@@ -76,5 +76,5 @@
<real>0.0</real> <real>0.0</real>
</dict> </dict>
</dict> </dict>
</dict> </dict>
</plist> </plist>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<data> <data>
MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp
bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl
IHVwb24gdGhlZS4= IHVwb24gdGhlZS4=
</data> </data>
</plist> </plist>

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>keyA</key> <key>keyA</key>
<data> <data>
MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp
bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl
IHVwb24gdGhlZS4= IHVwb24gdGhlZS4=
</data> </data>
</dict> </dict>
</plist> </plist>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<real>1360155352.748765</real> <real>1360155352.748765</real>
</plist> </plist>

View File

@@ -29,23 +29,23 @@ using System.IO;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// <para>
/// Parser for ASCII property lists. Supports Apple OS X/iOS and GnuStep/NeXTSTEP format. This parser is based on
/// the recursive descent paradigm, but the underlying grammar is not explicitly defined.
/// </para>
/// <para>Resources on ASCII property list format:</para>
/// <para>https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html</para>
/// <para>Property List Programming Guide - Old-Style ASCII Property Lists</para>
/// <para>http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html</para>
/// <para>GnuStep - NSPropertyListSerialization class documentation</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class ASCIIPropertyListParser
{ {
/// <summary>
/// <para>
/// Parser for ASCII property lists. Supports Apple OS X/iOS and GnuStep/NeXTSTEP format. This parser is based on
/// the recursive descent paradigm, but the underlying grammar is not explicitly defined.
/// </para>
/// <para>Resources on ASCII property list format:</para>
/// <para>https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html</para>
/// <para>Property List Programming Guide - Old-Style ASCII Property Lists</para>
/// <para>http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html</para>
/// <para>GnuStep - NSPropertyListSerialization class documentation</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class ASCIIPropertyListParser
{
/// <summary>A space</summary> /// <summary>A space</summary>
public const char WHITESPACE_SPACE = ' '; public const char WHITESPACE_SPACE = ' ';
/// <summary>A tabulator</summary> /// <summary>A tabulator</summary>
@@ -177,11 +177,11 @@ namespace Claunia.PropertyList
/// <exception cref="FormatException">When an error occurs during parsing.</exception> /// <exception cref="FormatException">When an error occurs during parsing.</exception>
public static NSObject Parse(ReadOnlySpan<byte> bytes) public static NSObject Parse(ReadOnlySpan<byte> bytes)
{ {
#if NATIVE_SPAN #if NATIVE_SPAN
return ParseString(Encoding.UTF8.GetString(bytes)); return ParseString(Encoding.UTF8.GetString(bytes));
#else #else
return ParseString(Encoding.UTF8.GetString(bytes.ToArray())); return ParseString(Encoding.UTF8.GetString(bytes.ToArray()));
#endif #endif
} }
/// <summary>Parses an ASCII property list from a string.</summary> /// <summary>Parses an ASCII property list from a string.</summary>
@@ -201,8 +201,7 @@ namespace Claunia.PropertyList
bool AcceptSequence(params char[] sequence) bool AcceptSequence(params char[] sequence)
{ {
for(int i = 0; i < sequence.Length; i++) for(int i = 0; i < sequence.Length; i++)
if(data[index + i] != sequence[i]) if(data[index + i] != sequence[i]) return false;
return false;
return true; return true;
} }
@@ -217,8 +216,7 @@ namespace Claunia.PropertyList
{ {
bool symbolPresent = false; bool symbolPresent = false;
foreach(char c in acceptableSymbols) foreach(char c in acceptableSymbols) symbolPresent |= data[index] == c;
symbolPresent |= data[index] == c;
return symbolPresent; return symbolPresent;
} }
@@ -236,13 +234,11 @@ namespace Claunia.PropertyList
/// <exception cref="FormatException">If none of the expected symbols could be found.</exception> /// <exception cref="FormatException">If none of the expected symbols could be found.</exception>
void Expect(params char[] expectedSymbols) void Expect(params char[] expectedSymbols)
{ {
if(Accept(expectedSymbols)) if(Accept(expectedSymbols)) return;
return;
string excString = "Expected '" + expectedSymbols[0] + "'"; string excString = "Expected '" + expectedSymbols[0] + "'";
for(int i = 1; i < expectedSymbols.Length; i++) for(int i = 1; i < expectedSymbols.Length; i++) excString += " or '" + expectedSymbols[i] + "'";
excString += " or '" + expectedSymbols[i] + "'";
excString += " but found '" + data[index] + "'"; excString += " but found '" + data[index] + "'";
@@ -288,8 +284,7 @@ namespace Claunia.PropertyList
commentSkipped = false; commentSkipped = false;
//Skip whitespaces //Skip whitespaces
while(Accept(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE, WHITESPACE_SPACE, WHITESPACE_TAB)) while(Accept(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE, WHITESPACE_SPACE, WHITESPACE_TAB)) Skip();
Skip();
//Skip single line comments "//..." //Skip single line comments "//..."
if(AcceptSequence(COMMENT_BEGIN_TOKEN, SINGLELINE_COMMENT_SECOND_TOKEN)) if(AcceptSequence(COMMENT_BEGIN_TOKEN, SINGLELINE_COMMENT_SECOND_TOKEN))
@@ -318,8 +313,7 @@ namespace Claunia.PropertyList
commentSkipped = true; commentSkipped = true;
} }
} while( } while(commentSkipped); //if a comment was skipped more whitespace or another comment can follow, so skip again
commentSkipped); //if a comment was skipped more whitespace or another comment can follow, so skip again
} }
/// <summary>Reads input until one of the given symbols is found.</summary> /// <summary>Reads input until one of the given symbols is found.</summary>
@@ -362,10 +356,7 @@ namespace Claunia.PropertyList
index = 0; index = 0;
//Skip Unicode byte order mark (BOM) //Skip Unicode byte order mark (BOM)
if(data.Length >= 3 && if(data.Length >= 3 && (data[0] & 0xFF) == 0xEF && (data[1] & 0xFF) == 0xBB && (data[2] & 0xFF) == 0xBF)
(data[0] & 0xFF) == 0xEF &&
(data[1] & 0xFF) == 0xBB &&
(data[2] & 0xFF) == 0xBF)
Skip(3); Skip(3);
SkipWhitespacesAndComments(); SkipWhitespacesAndComments();
@@ -405,8 +396,8 @@ namespace Claunia.PropertyList
string quotedString = ParseQuotedString(); string quotedString = ParseQuotedString();
//apple dates are quoted strings of length 20 and after the 4 year digits a dash is found //apple dates are quoted strings of length 20 and after the 4 year digits a dash is found
if(quotedString.Length == 20 && if(quotedString.Length == 20 && quotedString[4] == DATE_DATE_FIELD_DELIMITER)
quotedString[4] == DATE_DATE_FIELD_DELIMITER) {
try try
{ {
return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.DATE)); return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.DATE));
@@ -416,15 +407,14 @@ namespace Claunia.PropertyList
//not a date? --> return string //not a date? --> return string
return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING)); return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING));
} }
}
return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING)); return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING));
} }
default: default:
{ {
//0-9 //0-9
if(data[index] > 0x2F && if(data[index] > 0x2F && data[index] < 0x3A) return ParseDateString();
data[index] < 0x3A)
return ParseDateString();
//non-numerical -> string or boolean //non-numerical -> string or boolean
string parsedString = ParseString(); string parsedString = ParseString();
@@ -444,7 +434,7 @@ namespace Claunia.PropertyList
//Skip begin token //Skip begin token
Skip(); Skip();
SkipWhitespacesAndComments(); SkipWhitespacesAndComments();
List<NSObject> objects = new(); List<NSObject> objects = [];
while(!Accept(ARRAY_END_TOKEN)) while(!Accept(ARRAY_END_TOKEN))
{ {
@@ -519,8 +509,7 @@ namespace Claunia.PropertyList
{ {
Skip(); Skip();
Expect(DATA_GSBOOL_BEGIN_TOKEN, DATA_GSDATE_BEGIN_TOKEN, DATA_GSINT_BEGIN_TOKEN, Expect(DATA_GSBOOL_BEGIN_TOKEN, DATA_GSDATE_BEGIN_TOKEN, DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN);
DATA_GSREAL_BEGIN_TOKEN);
if(Accept(DATA_GSBOOL_BEGIN_TOKEN)) if(Accept(DATA_GSBOOL_BEGIN_TOKEN))
{ {
@@ -584,8 +573,7 @@ namespace Claunia.PropertyList
{ {
string numericalString = ParseString(); string numericalString = ParseString();
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.Type.STRING)); return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.STRING));
try try
@@ -605,9 +593,13 @@ namespace Claunia.PropertyList
/// whitespace, delimiter token or assignment token. /// whitespace, delimiter token or assignment token.
/// </summary> /// </summary>
/// <returns>The string found at the current parsing position.</returns> /// <returns>The string found at the current parsing position.</returns>
string ParseString() => ReadInputUntil(WHITESPACE_SPACE, WHITESPACE_TAB, WHITESPACE_NEWLINE, string ParseString() => ReadInputUntil(WHITESPACE_SPACE,
WHITESPACE_CARRIAGE_RETURN, ARRAY_ITEM_DELIMITER_TOKEN, WHITESPACE_TAB,
DICTIONARY_ITEM_DELIMITER_TOKEN, DICTIONARY_ASSIGN_TOKEN, WHITESPACE_NEWLINE,
WHITESPACE_CARRIAGE_RETURN,
ARRAY_ITEM_DELIMITER_TOKEN,
DICTIONARY_ITEM_DELIMITER_TOKEN,
DICTIONARY_ASSIGN_TOKEN,
ARRAY_END_TOKEN); ARRAY_END_TOKEN);
/// <summary> /// <summary>
@@ -625,7 +617,7 @@ namespace Claunia.PropertyList
//Read from opening quotation marks to closing quotation marks and skip escaped quotation marks //Read from opening quotation marks to closing quotation marks and skip escaped quotation marks
while(data[index] != QUOTEDSTRING_END_TOKEN || while(data[index] != QUOTEDSTRING_END_TOKEN ||
(data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash)) data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash)
{ {
quotedString += data[index]; quotedString += data[index];
@@ -665,12 +657,13 @@ namespace Claunia.PropertyList
/// <exception cref="EncoderFallbackException">If the string is encoded neither in ASCII nor in UTF-8</exception> /// <exception cref="EncoderFallbackException">If the string is encoded neither in ASCII nor in UTF-8</exception>
public static string ParseQuotedString(string s) public static string ParseQuotedString(string s)
{ {
List<byte> strBytes = new(); List<byte> strBytes = [];
IEnumerable<char> characters = s.ToCharArray(); IEnumerable<char> characters = s.ToCharArray();
IEnumerator<char> c = characters.GetEnumerator(); IEnumerator<char> c = characters.GetEnumerator();
while(c.MoveNext()) while(c.MoveNext())
{
switch(c.Current) switch(c.Current)
{ {
case '\\': case '\\':
@@ -678,22 +671,21 @@ namespace Claunia.PropertyList
//An escaped sequence is following //An escaped sequence is following
byte[] bts = Encoding.UTF8.GetBytes(ParseEscapedSequence(c)); byte[] bts = Encoding.UTF8.GetBytes(ParseEscapedSequence(c));
foreach(byte b in bts) strBytes.AddRange(bts);
strBytes.Add(b);
break; break;
} }
default: default:
{ {
//a normal ASCII char //a normal ASCII char
strBytes.AddRange(Encoding.BigEndianUnicode.GetBytes(new[] strBytes.AddRange(Encoding.BigEndianUnicode.GetBytes([
{
c.Current c.Current
})); ]));
break; break;
} }
} }
}
byte[] bytArr = new byte[strBytes.Count]; byte[] bytArr = new byte[strBytes.Count];
int i = 0; int i = 0;
@@ -729,35 +721,20 @@ namespace Claunia.PropertyList
switch(c) switch(c)
{ {
case '\\': case '\\':
return Encoding.UTF8.GetString(new byte[] return Encoding.UTF8.GetString("\0\\"u8.ToArray());
{
0, (byte)'\\'
});
case '"': case '"':
return Encoding.UTF8.GetString(new byte[] return Encoding.UTF8.GetString("\0\""u8.ToArray());
{
0, (byte)'\"'
});
case 'b': case 'b':
return Encoding.UTF8.GetString(new byte[] return Encoding.UTF8.GetString(new byte[]
{ {
0, (byte)'\b' 0, (byte)'\b'
}); });
case 'n': case 'n':
return Encoding.UTF8.GetString(new byte[] return Encoding.UTF8.GetString("\0\n"u8.ToArray());
{
0, (byte)'\n'
});
case 'r': case 'r':
return Encoding.UTF8.GetString(new byte[] return Encoding.UTF8.GetString("\0\r"u8.ToArray());
{
0, (byte)'\r'
});
case 't': case 't':
return Encoding.UTF8.GetString(new byte[] return Encoding.UTF8.GetString("\0\t"u8.ToArray());
{
0, (byte)'\t'
});
case 'U': case 'U':
case 'u': case 'u':
{ {
@@ -774,9 +751,9 @@ namespace Claunia.PropertyList
byte2 += iterator.Current; byte2 += iterator.Current;
byte[] stringBytes = byte[] stringBytes =
{ [
(byte)Convert.ToInt32(byte1, 16), (byte)Convert.ToInt32(byte2, 16) (byte)Convert.ToInt32(byte1, 16), (byte)Convert.ToInt32(byte2, 16)
}; ];
return Encoding.UTF8.GetString(stringBytes); return Encoding.UTF8.GetString(stringBytes);
} }
@@ -792,9 +769,9 @@ namespace Claunia.PropertyList
int asciiCode = Convert.ToInt32(num, 8); int asciiCode = Convert.ToInt32(num, 8);
byte[] stringBytes = byte[] stringBytes =
{ [
0, (byte)asciiCode 0, (byte)asciiCode
}; ];
return Encoding.UTF8.GetString(stringBytes); return Encoding.UTF8.GetString(stringBytes);
} }
@@ -804,10 +781,8 @@ namespace Claunia.PropertyList
internal static bool IsASCIIEncodable(string text) internal static bool IsASCIIEncodable(string text)
{ {
foreach(char c in text) foreach(char c in text)
if(c > 0x7F) if(c > 0x7F) return false;
return false;
return true; return true;
} }
}
} }

View File

@@ -30,22 +30,22 @@ using System.IO;
using System.Numerics; using System.Numerics;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// <para>
/// Parses property lists that are in Apple's binary format. Use this class when you are sure about the format of
/// the property list. Otherwise use the PropertyListParser class.
/// </para>
/// <para>
/// Parsing is done by calling the static <see cref="Parse(byte[])" />, <see cref="Parse(FileInfo)" /> and
/// <see cref="Parse(Stream)" /> methods.
/// </para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class BinaryPropertyListParser
{ {
/// <summary>
/// <para>
/// Parses property lists that are in Apple's binary format. Use this class when you are sure about the format of
/// the property list. Otherwise use the PropertyListParser class.
/// </para>
/// <para>
/// Parsing is done by calling the static <see cref="Parse(byte[])" />, <see cref="Parse(FileInfo)" /> and
/// <see cref="Parse(Stream)" /> methods.
/// </para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class BinaryPropertyListParser
{
static readonly Encoding utf16BigEndian = Encoding.GetEncoding("UTF-16BE"); static readonly Encoding utf16BigEndian = Encoding.GetEncoding("UTF-16BE");
/// <summary>Major version of the property list format</summary> /// <summary>Major version of the property list format</summary>
@@ -119,9 +119,14 @@ namespace Claunia.PropertyList
// 2.0 - Snow Lion // 2.0 - Snow Lion
if(majorVersion > 0) if(majorVersion > 0)
throw new PropertyListFormatException("Unsupported binary property list format: v" + majorVersion + {
"." + minorVersion + ". " + throw new PropertyListFormatException("Unsupported binary property list format: v" +
majorVersion +
"." +
minorVersion +
". " +
"Version 1.0 and later are not yet supported."); "Version 1.0 and later are not yet supported.");
}
/* /*
* Handle trailer, last 32 bytes of the file * Handle trailer, last 32 bytes of the file
@@ -142,7 +147,7 @@ namespace Claunia.PropertyList
for(int i = 0; i < numObjects; i++) for(int i = 0; i < numObjects; i++)
{ {
ReadOnlySpan<byte> offsetBytes = bytes.Slice(offsetTableOffset + (i * offsetSize), offsetSize); ReadOnlySpan<byte> offsetBytes = bytes.Slice(offsetTableOffset + i * offsetSize, offsetSize);
offsetTable[i] = (int)ParseUnsignedInt(offsetBytes); offsetTable[i] = (int)ParseUnsignedInt(offsetBytes);
} }
@@ -243,38 +248,51 @@ namespace Claunia.PropertyList
//integer //integer
int length = 1 << objInfo; int length = 1 << objInfo;
return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), ValuePreprocessor.Type.INTEGER), NSNumber.INTEGER); return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(),
ValuePreprocessor.Type.INTEGER),
NSNumber.INTEGER);
} }
case 0x2: case 0x2:
{ {
//real //real
int length = 1 << objInfo; int length = 1 << objInfo;
return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), ValuePreprocessor.Type.FLOATING_POINT), NSNumber.REAL); return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(),
ValuePreprocessor.Type.FLOATING_POINT),
NSNumber.REAL);
} }
case 0x3: case 0x3:
{ {
//Date //Date
if(objInfo != 0x3) if(objInfo != 0x3)
{
throw new throw new
PropertyListFormatException("The given binary property list contains a date object of an unknown type (" + PropertyListFormatException("The given binary property list contains a date object of an unknown type (" +
objInfo + ")"); objInfo +
")");
}
return new NSDate(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, 8).ToArray(), ValuePreprocessor.Type.DATE)); return new NSDate(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, 8).ToArray(),
ValuePreprocessor.Type.DATE));
} }
case 0x4: case 0x4:
{ {
//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.Type.DATA)); return new NSData(ValuePreprocessor.Preprocess(CopyOfRange(bytes,
offset + dataoffset,
offset + dataoffset + length),
ValuePreprocessor.Type.DATA));
} }
case 0x5: case 0x5:
{ {
//ASCII String, each character is 1 byte //ASCII String, each character is 1 byte
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset);
return new NSString(ValuePreprocessor.Preprocess(bytes.Slice(offset + stroffset, length).ToArray(), ValuePreprocessor.Type.STRING), Encoding.ASCII); return new NSString(ValuePreprocessor.Preprocess(bytes.Slice(offset + stroffset, length).ToArray(),
ValuePreprocessor.Type.STRING),
Encoding.ASCII);
} }
case 0x6: case 0x6:
{ {
@@ -315,8 +333,7 @@ namespace Claunia.PropertyList
for(int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int objRef = int objRef =
(int)ParseUnsignedInt(bytes.Slice(offset + arrayOffset + (i * objectRefSize), (int)ParseUnsignedInt(bytes.Slice(offset + arrayOffset + i * objectRefSize, objectRefSize));
objectRefSize));
array.Add(ParseObject(bytes, objRef)); array.Add(ParseObject(bytes, objRef));
} }
@@ -333,8 +350,7 @@ namespace Claunia.PropertyList
for(int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int objRef = int objRef =
(int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize), (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize));
objectRefSize));
set.AddObject(ParseObject(bytes, objRef)); set.AddObject(ParseObject(bytes, objRef));
} }
@@ -351,8 +367,7 @@ namespace Claunia.PropertyList
for(int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int objRef = int objRef =
(int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize), (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize));
objectRefSize));
set.AddObject(ParseObject(bytes, objRef)); set.AddObject(ParseObject(bytes, objRef));
} }
@@ -370,12 +385,13 @@ namespace Claunia.PropertyList
for(int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int keyRef = int keyRef =
(int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize), (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize));
objectRefSize));
int valRef = int valRef =
(int) (int)ParseUnsignedInt(bytes.Slice(offset +
ParseUnsignedInt(bytes.Slice(offset + contentOffset + (length * objectRefSize) + (i * objectRefSize), contentOffset +
length * objectRefSize +
i * objectRefSize,
objectRefSize)); objectRefSize));
NSObject key = ParseObject(bytes, keyRef); NSObject key = ParseObject(bytes, keyRef);
@@ -388,7 +404,8 @@ namespace Claunia.PropertyList
default: default:
{ {
Debug.WriteLine("WARNING: The given binary property list contains an object of unknown type (" + Debug.WriteLine("WARNING: The given binary property list contains an object of unknown type (" +
objType + ")"); objType +
")");
break; break;
} }
@@ -413,8 +430,11 @@ namespace Claunia.PropertyList
int intType = (int_type & 0xF0) >> 4; int intType = (int_type & 0xF0) >> 4;
if(intType != 0x1) if(intType != 0x1)
Debug.WriteLine("BinaryPropertyListParser: Length integer has an unexpected type" + intType + {
Debug.WriteLine("BinaryPropertyListParser: Length integer has an unexpected type" +
intType +
". Attempting to parse anyway..."); ". Attempting to parse anyway...");
}
int intInfo = int_type & 0x0F; int intInfo = int_type & 0x0F;
int intLength = 1 << intInfo; int intLength = 1 << intInfo;
@@ -448,26 +468,21 @@ namespace Claunia.PropertyList
{ {
int tempOffset = offset + length; int tempOffset = offset + length;
if(bytes.Length <= tempOffset) if(bytes.Length <= tempOffset) return numCharacters;
return numCharacters;
if(bytes[tempOffset] < 0x80) if(bytes[tempOffset] < 0x80) length++;
length++;
if(bytes[tempOffset] < 0xC2) if(bytes[tempOffset] < 0xC2) return numCharacters;
return numCharacters;
if(bytes[tempOffset] < 0xE0) if(bytes[tempOffset] < 0xE0)
{ {
if((bytes[tempOffset + 1] & 0xC0) != 0x80) if((bytes[tempOffset + 1] & 0xC0) != 0x80) return numCharacters;
return numCharacters;
length += 2; length += 2;
} }
else if(bytes[tempOffset] < 0xF0) else if(bytes[tempOffset] < 0xF0)
{ {
if((bytes[tempOffset + 1] & 0xC0) != 0x80 || if((bytes[tempOffset + 1] & 0xC0) != 0x80 || (bytes[tempOffset + 2] & 0xC0) != 0x80)
(bytes[tempOffset + 2] & 0xC0) != 0x80)
return numCharacters; return numCharacters;
length += 3; length += 3;
@@ -491,8 +506,7 @@ namespace Claunia.PropertyList
/// <param name="bytes">The unsigned integer represented by the given bytes.</param> /// <param name="bytes">The unsigned integer represented by the given bytes.</param>
public static long ParseUnsignedInt(ReadOnlySpan<byte> bytes) public static long ParseUnsignedInt(ReadOnlySpan<byte> bytes)
{ {
if(bytes.Length <= 4) if(bytes.Length <= 4) return ParseLong(bytes);
return ParseLong(bytes);
return ParseLong(bytes) & 0xFFFFFFFFL; return ParseLong(bytes) & 0xFFFFFFFFL;
} }
@@ -507,15 +521,9 @@ namespace Claunia.PropertyList
/// <param name="bytes">The bytes representing the long integer.</param> /// <param name="bytes">The bytes representing the long integer.</param>
public static long ParseLong(ReadOnlySpan<byte> bytes) public static long ParseLong(ReadOnlySpan<byte> bytes)
{ {
if(bytes == null) if(bytes == null) throw new ArgumentNullException(nameof(bytes));
{
throw new ArgumentNullException(nameof(bytes));
}
if(bytes.Length == 0) if(bytes.Length == 0) throw new ArgumentOutOfRangeException(nameof(bytes));
{
throw new ArgumentOutOfRangeException(nameof(bytes));
}
// https://opensource.apple.com/source/CF/CF-1153.18/CFBinaryPList.c, // https://opensource.apple.com/source/CF/CF-1153.18/CFBinaryPList.c,
// __CFBinaryPlistCreateObjectFiltered, case kCFBinaryPlistMarkerInt: // __CFBinaryPlistCreateObjectFiltered, case kCFBinaryPlistMarkerInt:
@@ -527,31 +535,31 @@ namespace Claunia.PropertyList
// but only the last 64 bits are significant currently // but only the last 64 bits are significant currently
switch(bytes.Length) switch(bytes.Length)
{ {
case 1: return bytes[0]; case 1:
return bytes[0];
case 2: return BinaryPrimitives.ReadUInt16BigEndian(bytes); case 2:
return BinaryPrimitives.ReadUInt16BigEndian(bytes);
case 4: return BinaryPrimitives.ReadUInt32BigEndian(bytes); case 4:
return BinaryPrimitives.ReadUInt32BigEndian(bytes);
// Transition from unsigned to signed // Transition from unsigned to signed
case 8: return BinaryPrimitives.ReadInt64BigEndian(bytes); case 8:
return BinaryPrimitives.ReadInt64BigEndian(bytes);
// Only the last 64 bits are significant currently // Only the last 64 bits are significant currently
case 16: return BinaryPrimitives.ReadInt64BigEndian(bytes.Slice(8)); case 16:
return BinaryPrimitives.ReadInt64BigEndian(bytes.Slice(8));
} }
if(bytes.Length >= 8) if(bytes.Length >= 8) throw new ArgumentOutOfRangeException(nameof(bytes), $"Cannot read a byte span of length {bytes.Length}");
throw new ArgumentOutOfRangeException(nameof(bytes),
$"Cannot read a byte span of length {bytes.Length}");
// Compatibility with existing archives, including anything with a non-power-of-2 // Compatibility with existing archives, including anything with a non-power-of-2
// size and 16-byte values, and architectures that don't support unaligned access // size and 16-byte values, and architectures that don't support unaligned access
long value = 0; long value = 0;
for(int i = 0; i < bytes.Length; i++) for(int i = 0; i < bytes.Length; i++) value = (value << 8) + bytes[i];
{
value = (value << 8) + bytes[i];
}
return value; return value;
@@ -565,10 +573,7 @@ namespace Claunia.PropertyList
/// <param name="bytes">The bytes representing the double.</param> /// <param name="bytes">The bytes representing the double.</param>
public static double ParseDouble(ReadOnlySpan<byte> bytes) public static double ParseDouble(ReadOnlySpan<byte> bytes)
{ {
if(bytes == null) if(bytes == null) throw new ArgumentNullException(nameof(bytes));
{
throw new ArgumentNullException(nameof(bytes));
}
return bytes.Length switch return bytes.Length switch
{ {
@@ -587,11 +592,8 @@ namespace Claunia.PropertyList
{ {
int length = endIndex - startIndex; int length = endIndex - startIndex;
if(length < 0) if(length < 0) throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex + ")");
throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex +
")");
return src.Slice(startIndex, endIndex - startIndex).ToArray(); return src.Slice(startIndex, endIndex - startIndex).ToArray();
} }
}
} }

View File

@@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
public partial class BinaryPropertyListWriter
{ {
public partial class BinaryPropertyListWriter
{
/// <summary> /// <summary>
/// The equality comparer which is used when adding an object to the <see cref="BinaryPropertyListWriter.idMap" /> /// The equality comparer which is used when adding an object to the <see cref="BinaryPropertyListWriter.idMap" />
/// . In most cases, objects are always added. The only exception are very specific strings, which are only added once. /// . In most cases, objects are always added. The only exception are very specific strings, which are only added once.
@@ -13,25 +13,18 @@ namespace Claunia.PropertyList
{ {
public override bool Equals(NSObject x, NSObject y) public override bool Equals(NSObject x, NSObject y)
{ {
if(x is not NSString a || if(x is not NSString a || y is not NSString b) return ReferenceEquals(x, y);
y is not NSString b)
return ReferenceEquals(x, y);
if(!IsSerializationPrimitive(a) || if(!IsSerializationPrimitive(a) || !IsSerializationPrimitive(b)) return ReferenceEquals(x, y);
!IsSerializationPrimitive(b))
return ReferenceEquals(x, y);
return string.Equals(a.Content, b.Content, StringComparison.Ordinal); return string.Equals(a.Content, b.Content, StringComparison.Ordinal);
} }
public override int GetHashCode(NSObject obj) public override int GetHashCode(NSObject obj)
{ {
if(obj is NSString s && if(obj is NSString s && IsSerializationPrimitive(s)) return s.Content.GetHashCode();
IsSerializationPrimitive(s))
return s.Content.GetHashCode();
return obj.GetHashCode(); return obj.GetHashCode();
} }
} }
}
} }

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
public partial class BinaryPropertyListWriter
{ {
public partial class BinaryPropertyListWriter
{
/// <summary> /// <summary>
/// The equality comparer which is used when retrieving objects in the /// The equality comparer which is used when retrieving objects in the
/// <see cref="BinaryPropertyListWriter.idMap" />. The logic is slightly different from /// <see cref="BinaryPropertyListWriter.idMap" />. The logic is slightly different from
@@ -22,18 +22,22 @@ namespace Claunia.PropertyList
// The exceptions are UIDs, where we always compare by value, and "primitive" strings (a list of well-known // The exceptions are UIDs, where we always compare by value, and "primitive" strings (a list of well-known
// strings), which are treaded specially and "recycled". // strings), which are treaded specially and "recycled".
UID => x.Equals(y), UID => x.Equals(y),
NSNumber number when IsSerializationPrimitive(number) => number.Equals(y), NSNumber number when IsSerializationPrimitive(number)
NSString nsString when IsSerializationPrimitive(nsString) => nsString.Equals(y), => number.Equals(y),
NSString nsString when
IsSerializationPrimitive(nsString) => nsString
.Equals(y),
_ => ReferenceEquals(x, y) _ => ReferenceEquals(x, y)
}; };
public override int GetHashCode(NSObject obj) => obj switch public override int GetHashCode(NSObject obj) => obj switch
{ {
UID u => u.GetHashCode(), UID u => u.GetHashCode(),
NSNumber n when IsSerializationPrimitive(n) => n.ToObject().GetHashCode(), NSNumber n when IsSerializationPrimitive(n) => n.ToObject()
NSString s when IsSerializationPrimitive(s) => s.Content.GetHashCode(), .GetHashCode(),
NSString s when IsSerializationPrimitive(s) => s.Content
.GetHashCode(),
_ => obj.GetHashCode() _ => obj.GetHashCode()
}; };
} }
}
} }

View File

@@ -27,19 +27,19 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// <para>A BinaryPropertyListWriter is a helper class for writing out binary property list files.</para>
/// <para>
/// It contains an output stream and various structures for keeping track of which NSObjects have already been
/// serialized, and where they were put in the file.
/// </para>
/// </summary>
/// @author Keith Randall
/// @author Natalia Portillo
public partial class BinaryPropertyListWriter
{ {
/// <summary>
/// <para>A BinaryPropertyListWriter is a helper class for writing out binary property list files.</para>
/// <para>
/// It contains an output stream and various structures for keeping track of which NSObjects have already been
/// serialized, and where they were put in the file.
/// </para>
/// </summary>
/// @author Keith Randall
/// @author Natalia Portillo
public partial class BinaryPropertyListWriter
{
/// <summary>Binary property list version 0.0</summary> /// <summary>Binary property list version 0.0</summary>
public const int VERSION_00 = 0; public const int VERSION_00 = 0;
/// <summary>Binary property list version 1.0</summary> /// <summary>Binary property list version 1.0</summary>
@@ -74,8 +74,7 @@ namespace Claunia.PropertyList
outStream = outStr; outStream = outStr;
} }
public BinaryPropertyListWriter(Stream outStr, int version, public BinaryPropertyListWriter(Stream outStr, int version, IEqualityComparer<NSObject> addObjectEqualityComparer,
IEqualityComparer<NSObject> addObjectEqualityComparer,
IEqualityComparer<NSObject> getObjectEqualityComparer) IEqualityComparer<NSObject> getObjectEqualityComparer)
{ {
this.version = version; this.version = version;
@@ -115,8 +114,7 @@ namespace Claunia.PropertyList
{ {
int v = GetMinimumRequiredVersion(o); int v = GetMinimumRequiredVersion(o);
if(v > minVersion) if(v > minVersion) minVersion = v;
minVersion = v;
} }
break; break;
@@ -127,8 +125,7 @@ namespace Claunia.PropertyList
{ {
int v = GetMinimumRequiredVersion(o); int v = GetMinimumRequiredVersion(o);
if(v > minVersion) if(v > minVersion) minVersion = v;
minVersion = v;
} }
break; break;
@@ -142,8 +139,7 @@ namespace Claunia.PropertyList
{ {
int v = GetMinimumRequiredVersion(o); int v = GetMinimumRequiredVersion(o);
if(v > minVersion) if(v > minVersion) minVersion = v;
minVersion = v;
} }
break; break;
@@ -183,7 +179,8 @@ namespace Claunia.PropertyList
: "v0.0"; : "v0.0";
throw new IOException("The given property list structure cannot be saved. " + throw new IOException("The given property list structure cannot be saved. " +
"The required version of the binary format (" + versionString + "The required version of the binary format (" +
versionString +
") is not yet supported."); ") is not yet supported.");
} }
@@ -206,47 +203,32 @@ namespace Claunia.PropertyList
public void Write(NSObject root) public void Write(NSObject root)
{ {
// magic bytes // magic bytes
Write(new[] Write("bplist"u8.ToArray());
{
(byte)'b', (byte)'p', (byte)'l', (byte)'i', (byte)'s', (byte)'t'
});
//version //version
switch(version) switch(version)
{ {
case VERSION_00: case VERSION_00:
{ {
Write(new[] Write("00"u8.ToArray());
{
(byte)'0', (byte)'0'
});
break; break;
} }
case VERSION_10: case VERSION_10:
{ {
Write(new[] Write("10"u8.ToArray());
{
(byte)'1', (byte)'0'
});
break; break;
} }
case VERSION_15: case VERSION_15:
{ {
Write(new[] Write("15"u8.ToArray());
{
(byte)'1', (byte)'5'
});
break; break;
} }
case VERSION_20: case VERSION_20:
{ {
Write(new[] Write("20"u8.ToArray());
{
(byte)'2', (byte)'0'
});
break; break;
} }
@@ -277,8 +259,7 @@ namespace Claunia.PropertyList
long offsetTableOffset = count; long offsetTableOffset = count;
int offsetSizeInBytes = ComputeOffsetSizeInBytes(count); int offsetSizeInBytes = ComputeOffsetSizeInBytes(count);
foreach(long offset in offsets) foreach(long offset in offsets) WriteBytes(offset, offsetSizeInBytes);
WriteBytes(offset, offsetSizeInBytes);
if(version != VERSION_15) if(version != VERSION_15)
{ {
@@ -310,16 +291,13 @@ namespace Claunia.PropertyList
{ {
if(ReuseObjectIds) if(ReuseObjectIds)
{ {
if(!idDict.ContainsKey(obj)) if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++);
idDict.Add(obj, currentId++);
} }
else else
{ {
if(!idDict2.ContainsKey(obj)) if(!idDict2.ContainsKey(obj)) idDict2.Add(obj, currentId);
idDict2.Add(obj, currentId);
if(!idDict.ContainsKey(obj)) if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++);
idDict.Add(obj, currentId++);
} }
} }
@@ -327,8 +305,7 @@ namespace Claunia.PropertyList
static int ComputeIdSizeInBytes(int numberOfIds) static int ComputeIdSizeInBytes(int numberOfIds)
{ {
if(numberOfIds < 256) if(numberOfIds < 256) return 1;
return 1;
return numberOfIds < 65536 ? 2 : 4; return numberOfIds < 65536 ? 2 : 4;
} }
@@ -345,7 +322,8 @@ namespace Claunia.PropertyList
{ {
switch(value) switch(value)
{ {
case < 0: throw new ArgumentException("value must be greater than or equal to 0", "value"); case < 0:
throw new ArgumentException("value must be greater than or equal to 0", "value");
case < 15: case < 15:
Write((kind << 4) + value); Write((kind << 4) + value);
@@ -385,19 +363,18 @@ namespace Claunia.PropertyList
internal void Write(Span<byte> bytes) internal void Write(Span<byte> bytes)
{ {
#if NATIVE_SPAN #if NATIVE_SPAN
outStream.Write(bytes); outStream.Write(bytes);
count += bytes.Length; count += bytes.Length;
#else #else
Write(bytes.ToArray()); Write(bytes.ToArray());
#endif #endif
} }
internal void WriteBytes(long value, int bytes) internal void WriteBytes(long value, int bytes)
{ {
// write low-order bytes big-endian style // write low-order bytes big-endian style
for(int i = bytes - 1; i >= 0; i--) for(int i = bytes - 1; i >= 0; i--) Write((int)(value >> 8 * i));
Write((int)(value >> (8 * i)));
} }
internal void WriteID(int id) => WriteBytes(id, idSizeInBytes); internal void WriteID(int id) => WriteBytes(id, idSizeInBytes);
@@ -412,11 +389,24 @@ namespace Claunia.PropertyList
// This is a list of "special" values which are only added once to a binary property // This is a list of "special" values which are only added once to a binary property
// list, and can be referenced multiple times. // list, and can be referenced multiple times.
return content is "$class" or "$classes" or "$classname" or "NS.objects" or "NS.keys" or "NS.base" or return content is "$class"
"NS.relative" or "NS.string" or "NSURL" or "NSDictionary" or "NSObject" or "NSMutableDictionary" or "$classes"
or "NSMutableArray" or "NSArray" or "NSUUID" or "NSKeyedArchiver" or "NSMutableString"; or "$classname"
or "NS.objects"
or "NS.keys"
or "NS.base"
or "NS.relative"
or "NS.string"
or "NSURL"
or "NSDictionary"
or "NSObject"
or "NSMutableDictionary"
or "NSMutableArray"
or "NSArray"
or "NSUUID"
or "NSKeyedArchiver"
or "NSMutableString";
} }
internal static bool IsSerializationPrimitive(NSNumber n) => n.isBoolean(); internal static bool IsSerializationPrimitive(NSNumber n) => n.isBoolean();
}
} }

View File

@@ -22,10 +22,10 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
partial class NSArray : IList<NSObject>
{ {
partial class NSArray : IList<NSObject>
{
/// <inheritdoc /> /// <inheritdoc />
public NSObject this[int index] public NSObject this[int index]
{ {
@@ -76,5 +76,4 @@ namespace Claunia.PropertyList
public void Insert(int index, object item) => Insert(index, Wrap(item)); public void Insert(int index, object item) => Insert(index, Wrap(item));
public bool Remove(object item) => Remove(Wrap(item)); public bool Remove(object item) => Remove(Wrap(item));
}
} }

View File

@@ -27,13 +27,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>Represents an Array.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public partial class NSArray : NSObject
{ {
/// <summary>Represents an Array.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public partial class NSArray : NSObject
{
readonly List<NSObject> array; readonly List<NSObject> array;
/// <summary>Creates an empty array of the given length.</summary> /// <summary>Creates an empty array of the given length.</summary>
@@ -42,7 +42,7 @@ namespace Claunia.PropertyList
/// <summary>Creates a array from an existing one</summary> /// <summary>Creates a array from an existing one</summary>
/// <param name="a">The array which should be wrapped by the NSArray.</param> /// <param name="a">The array which should be wrapped by the NSArray.</param>
public NSArray(params NSObject[] a) => array = new List<NSObject>(a); public NSArray(params NSObject[] a) => array = [..a];
/// <summary>Returns the size of the array.</summary> /// <summary>Returns the size of the array.</summary>
/// <value>The number of elements that this array can store.</value> /// <value>The number of elements that this array can store.</value>
@@ -65,8 +65,7 @@ namespace Claunia.PropertyList
[Obsolete] [Obsolete]
public void SetValue(int key, object value) public void SetValue(int key, object value)
{ {
if(value == null) if(value == null) throw new ArgumentNullException("value", "Cannot add null values to an NSArray!");
throw new ArgumentNullException("value", "Cannot add null values to an NSArray!");
array[key] = Wrap(value); array[key] = Wrap(value);
} }
@@ -88,8 +87,7 @@ namespace Claunia.PropertyList
NSObject nso = Wrap(obj); NSObject nso = Wrap(obj);
foreach(NSObject elem in array) foreach(NSObject elem in array)
if(elem.Equals(nso)) if(elem.Equals(nso)) return true;
return true;
return false; return false;
} }
@@ -106,8 +104,7 @@ namespace Claunia.PropertyList
NSObject nso = Wrap(obj); NSObject nso = Wrap(obj);
for(int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
if(array[i].Equals(nso)) if(array[i].Equals(nso)) return i;
return i;
return -1; return -1;
} }
@@ -125,8 +122,7 @@ namespace Claunia.PropertyList
NSObject nso = Wrap(obj); NSObject nso = Wrap(obj);
for(int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
if(array[i] == nso) if(array[i] == nso) return i;
return i;
return -1; return -1;
} }
@@ -143,11 +139,10 @@ namespace Claunia.PropertyList
/// <param name="indexes">The indices of the objects.</param> /// <param name="indexes">The indices of the objects.</param>
public NSObject[] ObjectsAtIndexes(params int[] indexes) public NSObject[] ObjectsAtIndexes(params int[] indexes)
{ {
NSObject[] result = new NSObject[indexes.Length]; var result = new NSObject[indexes.Length];
Array.Sort(indexes); Array.Sort(indexes);
for(int i = 0; i < indexes.Length; i++) for(int i = 0; i < indexes.Length; i++) result[i] = array[indexes[i]];
result[i] = array[indexes[i]];
return result; return result;
} }
@@ -166,13 +161,11 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if(obj is NSArray nsArray) if(obj is NSArray nsArray) return ArrayEquals(nsArray, this);
return ArrayEquals(nsArray, this);
NSObject nso = Wrap(obj); NSObject nso = Wrap(obj);
if(nso is NSArray nsoArray) if(nso is NSArray nsoArray) return ArrayEquals(nsoArray, this);
return ArrayEquals(nsoArray, this);
return false; return false;
} }
@@ -185,7 +178,7 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
hash = (89 * hash) + array.GetHashCode(); hash = 89 * hash + array.GetHashCode();
return hash; return hash;
} }
@@ -210,16 +203,14 @@ namespace Claunia.PropertyList
{ {
base.AssignIDs(outPlist); base.AssignIDs(outPlist);
foreach(NSObject obj in array) foreach(NSObject obj in array) obj.AssignIDs(outPlist);
obj.AssignIDs(outPlist);
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.WriteIntHeader(0xA, array.Count); outPlist.WriteIntHeader(0xA, array.Count);
foreach(NSObject obj in array) foreach(NSObject obj in array) outPlist.WriteID(outPlist.GetID(obj));
outPlist.WriteID(outPlist.GetID(obj));
} }
/// <summary> /// <summary>
@@ -275,17 +266,14 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(i != 0) if(i != 0) ascii.Append(" ");
ascii.Append(" ");
array[i].ToASCII(ascii, 0); array[i].ToASCII(ascii, 0);
} }
if(i != array.Count - 1) if(i != array.Count - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue;
continue;
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -313,17 +301,14 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(i != 0) if(i != 0) ascii.Append(" ");
ascii.Append(" ");
array[i].ToASCIIGnuStep(ascii, 0); array[i].ToASCIIGnuStep(ascii, 0);
} }
if(i != array.Count - 1) if(i != array.Count - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue;
continue;
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -346,17 +331,13 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(obj is not NSArray nsArray) if(obj is not NSArray nsArray) return false;
return false;
if(array.Count != nsArray.array.Count) if(array.Count != nsArray.array.Count) return false;
return false;
for(int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
if(!array[i].Equals(nsArray[i])) if(!array[i].Equals(nsArray[i])) return false;
return false;
return true; return true;
} }
}
} }

View File

@@ -27,13 +27,13 @@ using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>NSData objects are wrappers for byte buffers</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSData : NSObject
{ {
/// <summary>NSData objects are wrappers for byte buffers</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSData : NSObject
{
// In the XML property list format, the base-64 encoded data is split across multiple lines. // In the XML property list format, the base-64 encoded data is split across multiple lines.
// Each line contains 68 characters. // Each line contains 68 characters.
const int DataLineLength = 68; const int DataLineLength = 68;
@@ -111,7 +111,7 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 5; int hash = 5;
hash = (67 * hash) + Bytes.GetHashCode(); hash = 67 * hash + Bytes.GetHashCode();
return hash; return hash;
} }
@@ -124,12 +124,14 @@ namespace Claunia.PropertyList
string base64 = GetBase64EncodedData(); string base64 = GetBase64EncodedData();
foreach(string line in base64.Split('\n')) foreach(string line in base64.Split('\n'))
{
for(int offset = 0; offset < base64.Length; offset += DataLineLength) for(int offset = 0; offset < base64.Length; offset += DataLineLength)
{ {
Indent(xml, level); Indent(xml, level);
xml.Append(line.Substring(offset, Math.Min(DataLineLength, line.Length - offset))); xml.Append(line.Substring(offset, Math.Min(DataLineLength, line.Length - offset)));
xml.Append(NEWLINE); xml.Append(NEWLINE);
} }
}
Indent(xml, level); Indent(xml, level);
xml.Append("</data>"); xml.Append("</data>");
@@ -157,9 +159,7 @@ namespace Claunia.PropertyList
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
else if((i + 1) % 2 == 0 && else if((i + 1) % 2 == 0 && i != Bytes.Length - 1) ascii.Append(" ");
i != Bytes.Length - 1)
ascii.Append(" ");
} }
ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN); ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN);
@@ -184,5 +184,4 @@ namespace Claunia.PropertyList
public static explicit operator byte[](NSData value) => value.Bytes; public static explicit operator byte[](NSData value) => value.Bytes;
public static explicit operator NSData(byte[] value) => new(value); public static explicit operator NSData(byte[] value) => new(value);
}
} }

View File

@@ -27,13 +27,13 @@ using System;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>Represents a date</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSDate : NSObject
{ {
/// <summary>Represents a date</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSDate : NSObject
{
static readonly DateTime EPOCH = new(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc); static readonly DateTime EPOCH = new(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// The datetime ends with 'Z', which indicates UTC time. To make sure .NET // The datetime ends with 'Z', which indicates UTC time. To make sure .NET
@@ -41,9 +41,9 @@ namespace Claunia.PropertyList
static readonly string sdfDefault = "yyyy-MM-dd'T'HH:mm:ssK"; static readonly string sdfDefault = "yyyy-MM-dd'T'HH:mm:ssK";
static readonly string sdfGnuStep = "yyyy-MM-dd HH:mm:ss zzz"; static readonly string sdfGnuStep = "yyyy-MM-dd HH:mm:ss zzz";
static readonly string[] sdfAll = static readonly string[] sdfAll =
{ [
sdfDefault, sdfGnuStep sdfDefault, sdfGnuStep
}; ];
static readonly CultureInfo provider = CultureInfo.InvariantCulture; static readonly CultureInfo provider = CultureInfo.InvariantCulture;
@@ -162,8 +162,7 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(obj is not NSDate date) if(obj is not NSDate date) return false;
return false;
int equality = DateTime.Compare(Date, date.Date); int equality = DateTime.Compare(Date, date.Date);
@@ -173,5 +172,4 @@ namespace Claunia.PropertyList
public static explicit operator DateTime(NSDate value) => value.Date; public static explicit operator DateTime(NSDate value) => value.Date;
public static explicit operator NSDate(DateTime value) => new(value); public static explicit operator NSDate(DateTime value) => new(value);
}
} }

View File

@@ -28,20 +28,20 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// <para>
/// A NSDictionary is a collection of keys and values, essentially a Dictionary. The keys are simple Strings
/// whereas the values can be any kind of NSObject.
/// </para>
/// <para>You can access the keys through the function <see cref="Keys" />.</para>
/// <para>Access to the objects stored for each key is given through the function <see cref="ObjectForKey" />.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSDictionary : NSObject, IDictionary<string, NSObject>
{ {
/// <summary>
/// <para>
/// A NSDictionary is a collection of keys and values, essentially a Dictionary. The keys are simple Strings
/// whereas the values can be any kind of NSObject.
/// </para>
/// <para>You can access the keys through the function <see cref="Keys" />.</para>
/// <para>Access to the objects stored for each key is given through the function <see cref="ObjectForKey" />.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSDictionary : NSObject, IDictionary<string, NSObject>
{
readonly Dictionary<string, NSObject> dict; readonly Dictionary<string, NSObject> dict;
// Maps the keys in this dictionary to their NSString equivalent. Makes sure the NSString // Maps the keys in this dictionary to their NSString equivalent. Makes sure the NSString
@@ -52,8 +52,8 @@ namespace Claunia.PropertyList
/// <param name="capacity">The capacity of the dictionary.</param> /// <param name="capacity">The capacity of the dictionary.</param>
public NSDictionary(int capacity) public NSDictionary(int capacity)
{ {
dict = new Dictionary<string, NSObject>(capacity); dict = [];
keys = new Dictionary<string, NSString>(capacity); keys = [];
} }
/// <summary>Creates a new empty NSDictionary.</summary> /// <summary>Creates a new empty NSDictionary.</summary>
@@ -63,15 +63,19 @@ namespace Claunia.PropertyList
/// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value>
public bool IsEmpty => dict.Count == 0; public bool IsEmpty => dict.Count == 0;
#region IEnumerable implementation #region IEnumerable implementation
/// <summary>Gets the enumerator.</summary> /// <summary>Gets the enumerator.</summary>
/// <returns>The enumerator.</returns> /// <returns>The enumerator.</returns>
public IEnumerator<KeyValuePair<string, NSObject>> GetEnumerator() => dict.GetEnumerator(); public IEnumerator<KeyValuePair<string, NSObject>> GetEnumerator() => dict.GetEnumerator();
#endregion
#region IEnumerable implementation #endregion
#region IEnumerable implementation
IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator();
#endregion
#endregion
/// <summary> /// <summary>
/// Gets the hashmap which stores the keys and values of this dictionary. Changes to the hashmap's contents are /// Gets the hashmap which stores the keys and values of this dictionary. Changes to the hashmap's contents are
@@ -105,8 +109,7 @@ namespace Claunia.PropertyList
/// <returns>The object corresponding to the specified key, null if not found in the current instance.</returns> /// <returns>The object corresponding to the specified key, null if not found in the current instance.</returns>
public NSObject Get(object key) public NSObject Get(object key)
{ {
if(key is string s) if(key is string s) return ObjectForKey(s);
return ObjectForKey(s);
return null; return null;
} }
@@ -116,8 +119,7 @@ namespace Claunia.PropertyList
/// <param name="value">Object to search up in the current instance.</param> /// <param name="value">Object to search up in the current instance.</param>
public bool ContainsValue(object value) public bool ContainsValue(object value)
{ {
if(value == null) if(value == null) return false;
return false;
NSObject wrap = Wrap(value); NSObject wrap = Wrap(value);
@@ -135,8 +137,7 @@ namespace Claunia.PropertyList
/// </param> /// </param>
public void Add(string key, object obj) public void Add(string key, object obj)
{ {
if(obj == null) if(obj == null) return;
return;
Add(key, Wrap(obj)); Add(key, Wrap(obj));
} }
@@ -162,9 +163,7 @@ namespace Claunia.PropertyList
public bool ContainsValue(string val) public bool ContainsValue(string val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o is NSString str && if(o is NSString str && str.Content.Equals(val)) return true;
str.Content.Equals(val))
return true;
return false; return false;
} }
@@ -175,10 +174,7 @@ namespace Claunia.PropertyList
public bool ContainsValue(long val) public bool ContainsValue(long val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o is NSNumber num && if(o is NSNumber num && num.isInteger() && num.ToInt() == val) return true;
num.isInteger() &&
num.ToInt() == val)
return true;
return false; return false;
} }
@@ -189,10 +185,7 @@ namespace Claunia.PropertyList
public bool ContainsValue(double val) public bool ContainsValue(double val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o is NSNumber num && if(o is NSNumber num && num.isReal() && num.ToDouble() == val) return true;
num.isReal() &&
num.ToDouble() == val)
return true;
return false; return false;
} }
@@ -203,10 +196,7 @@ namespace Claunia.PropertyList
public bool ContainsValue(bool val) public bool ContainsValue(bool val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o is NSNumber num && if(o is NSNumber num && num.isBoolean() && num.ToBool() == val) return true;
num.isBoolean() &&
num.ToBool() == val)
return true;
return false; return false;
} }
@@ -217,9 +207,7 @@ namespace Claunia.PropertyList
public bool ContainsValue(DateTime val) public bool ContainsValue(DateTime val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o is NSDate dat && if(o is NSDate dat && dat.Date.Equals(val)) return true;
dat.Date.Equals(val))
return true;
return false; return false;
} }
@@ -230,9 +218,7 @@ namespace Claunia.PropertyList
public bool ContainsValue(byte[] val) public bool ContainsValue(byte[] val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o is NSData dat && if(o is NSData dat && ArrayEquals(dat.Bytes, val)) return true;
ArrayEquals(dat.Bytes, val))
return true;
return false; return false;
} }
@@ -251,21 +237,17 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(obj is not NSDictionary dictionary) if(obj is not NSDictionary dictionary) return false;
return false;
if(dictionary.dict.Count != dict.Count) if(dictionary.dict.Count != dict.Count) return false;
return false;
foreach(KeyValuePair<string, NSObject> kvp in dict) foreach(KeyValuePair<string, NSObject> kvp in dict)
{ {
bool found = dictionary.dict.TryGetValue(kvp.Key, out NSObject nsoB); bool found = dictionary.dict.TryGetValue(kvp.Key, out NSObject nsoB);
if(!found) if(!found) return false;
return false;
if(!kvp.Value.Equals(nsoB)) if(!kvp.Value.Equals(nsoB)) return false;
return false;
} }
return true; return true;
@@ -279,7 +261,7 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
hash = (83 * hash) + (dict != null ? dict.GetHashCode() : 0); hash = 83 * hash + (dict != null ? dict.GetHashCode() : 0);
return hash; return hash;
} }
@@ -297,9 +279,7 @@ namespace Claunia.PropertyList
//According to http://www.w3.org/TR/REC-xml/#syntax node values must not //According to http://www.w3.org/TR/REC-xml/#syntax node values must not
//contain the characters < or &. Also the > character should be escaped. //contain the characters < or &. Also the > character should be escaped.
if(kvp.Key.Contains("&") || if(kvp.Key.Contains("&") || kvp.Key.Contains("<") || kvp.Key.Contains(">"))
kvp.Key.Contains("<") ||
kvp.Key.Contains(">"))
{ {
xml.Append("<![CDATA["); xml.Append("<![CDATA[");
xml.Append(kvp.Key.Replace("]]>", "]]]]><![CDATA[>")); xml.Append(kvp.Key.Replace("]]>", "]]]]><![CDATA[>"));
@@ -322,22 +302,18 @@ namespace Claunia.PropertyList
{ {
base.AssignIDs(outPlist); base.AssignIDs(outPlist);
foreach(KeyValuePair<string, NSObject> entry in dict) foreach(KeyValuePair<string, NSObject> entry in dict) keys[entry.Key].AssignIDs(outPlist);
keys[entry.Key].AssignIDs(outPlist);
foreach(KeyValuePair<string, NSObject> entry in dict) foreach(KeyValuePair<string, NSObject> entry in dict) entry.Value.AssignIDs(outPlist);
entry.Value.AssignIDs(outPlist);
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.WriteIntHeader(0xD, dict.Count); outPlist.WriteIntHeader(0xD, dict.Count);
foreach(KeyValuePair<string, NSObject> entry in dict) foreach(KeyValuePair<string, NSObject> entry in dict) outPlist.WriteID(outPlist.GetID(keys[entry.Key]));
outPlist.WriteID(outPlist.GetID(keys[entry.Key]));
foreach(KeyValuePair<string, NSObject> entry in dict) foreach(KeyValuePair<string, NSObject> entry in dict) outPlist.WriteID(outPlist.GetID(entry.Value));
outPlist.WriteID(outPlist.GetID(entry.Value));
} }
/// <summary> /// <summary>
@@ -438,7 +414,8 @@ namespace Claunia.PropertyList
ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN);
} }
#region IDictionary implementation #region IDictionary implementation
/// <summary>Add the specified key and value.</summary> /// <summary>Add the specified key and value.</summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
/// <param name="value">Value.</param> /// <param name="value">Value.</param>
@@ -480,8 +457,7 @@ namespace Claunia.PropertyList
get => dict[index]; get => dict[index];
set set
{ {
if(!keys.ContainsKey(index)) if(!keys.ContainsKey(index)) keys.Add(index, new NSString(index));
keys.Add(index, new NSString(index));
dict[index] = value; dict[index] = value;
} }
@@ -494,9 +470,11 @@ namespace Claunia.PropertyList
/// <summary>Gets an array with all the objects contained in the current instance.</summary> /// <summary>Gets an array with all the objects contained in the current instance.</summary>
/// <value>The objects.</value> /// <value>The objects.</value>
public ICollection<NSObject> Values => dict.Values; public ICollection<NSObject> Values => dict.Values;
#endregion
#region ICollection implementation #endregion
#region ICollection implementation
/// <summary>Adds the specified item.</summary> /// <summary>Adds the specified item.</summary>
/// <param name="item">Item.</param> /// <param name="item">Item.</param>
public void Add(KeyValuePair<string, NSObject> item) public void Add(KeyValuePair<string, NSObject> item)
@@ -549,6 +527,6 @@ namespace Claunia.PropertyList
/// <summary>Gets a value indicating whether this instance is read only.</summary> /// <summary>Gets a value indicating whether this instance is read only.</summary>
/// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value>
public bool IsReadOnly => false; public bool IsReadOnly => false;
#endregion
} #endregion
} }

View File

@@ -27,13 +27,13 @@ using System;
using System.Globalization; using System.Globalization;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>A number whose value is either an integer, a real number or bool.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSNumber : NSObject, IComparable
{ {
/// <summary>A number whose value is either an integer, a real number or bool.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSNumber : NSObject, IComparable
{
/// <summary> /// <summary>
/// Indicates that the number's value is an integer. The number is stored as a .NET <see cref="long" />. Its /// Indicates that the number's value is an integer. The number is stored as a .NET <see cref="long" />. Its
/// original value could have been char, short, int, long or even long long. /// original value could have been char, short, int, long or even long long.
@@ -79,7 +79,8 @@ namespace Claunia.PropertyList
break; break;
default: throw new ArgumentException("Type argument is not valid.", nameof(type)); default:
throw new ArgumentException("Type argument is not valid.", nameof(type));
} }
this.type = type; this.type = type;
@@ -118,12 +119,10 @@ namespace Claunia.PropertyList
/// <seealso cref="double.Parse(string, IFormatProvider)" /> /// <seealso cref="double.Parse(string, IFormatProvider)" />
public NSNumber(string text) public NSNumber(string text)
{ {
if(text == null) if(text == null) throw new ArgumentException("The given string is null and cannot be parsed as number.");
throw new ArgumentException("The given string is null and cannot be parsed as number.");
if(text.StartsWith("0x") && if(text.StartsWith("0x") &&
long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long l))
out long l))
{ {
doubleValue = longValue = l; doubleValue = longValue = l;
type = INTEGER; type = INTEGER;
@@ -154,8 +153,7 @@ namespace Claunia.PropertyList
doubleValue = longValue = boolValue ? 1 : 0; doubleValue = longValue = boolValue ? 1 : 0;
} }
else else
throw new throw new ArgumentException("The given string neither represents a double, an int nor a bool value.");
ArgumentException("The given string neither represents a double, an int nor a bool value.");
} }
} }
@@ -214,8 +212,7 @@ namespace Claunia.PropertyList
: 1; : 1;
} }
if(!IsNumber(o)) if(!IsNumber(o)) return -1;
return -1;
y = GetDoubleFromObject(o); y = GetDoubleFromObject(o);
@@ -249,8 +246,7 @@ namespace Claunia.PropertyList
/// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns>
public bool ToBool() public bool ToBool()
{ {
if(type == BOOLEAN) if(type == BOOLEAN) return boolValue;
return boolValue;
return longValue != 0; return longValue != 0;
} }
@@ -282,10 +278,11 @@ namespace Claunia.PropertyList
/// <returns>Whether the objects are equal in terms of numeric value and type.</returns> /// <returns>Whether the objects are equal in terms of numeric value and type.</returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if(obj is not NSNumber number) if(obj is not NSNumber number) return false;
return false;
return type == number.type && longValue == number.longValue && doubleValue == number.doubleValue && return type == number.type &&
longValue == number.longValue &&
doubleValue == number.doubleValue &&
boolValue == number.boolValue; boolValue == number.boolValue;
} }
@@ -297,12 +294,13 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = type; int hash = type;
hash = (37 * hash) + (int)(longValue ^ ((uint)longValue >> 32)); hash = 37 * hash + (int)(longValue ^ (uint)longValue >> 32);
hash = (37 * hash) + (int)(BitConverter.DoubleToInt64Bits(doubleValue) ^ hash = 37 * hash +
(int)(BitConverter.DoubleToInt64Bits(doubleValue) ^
(uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32)); (uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32));
hash = (37 * hash) + (ToBool() ? 1 : 0); hash = 37 * hash + (ToBool() ? 1 : 0);
return hash; return hash;
} }
@@ -483,11 +481,9 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(obj is not NSNumber number) if(obj is not NSNumber number) return false;
return false;
if(number.GetNSNumberType() != type) if(number.GetNSNumberType() != type) return false;
return false;
return type switch return type switch
{ {
@@ -541,5 +537,4 @@ namespace Claunia.PropertyList
public static explicit operator NSNumber(float value) => new(value); public static explicit operator NSNumber(float value) => new(value);
public static explicit operator NSNumber(bool value) => new(value); public static explicit operator NSNumber(bool value) => new(value);
}
} }

View File

@@ -27,16 +27,16 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// <para>Abstract interface for any object contained in a property list.</para>
/// <para>The names and functions of the various objects orient themselves towards Apple's Cocoa API.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public abstract class NSObject
{ {
/// <summary>
/// <para>Abstract interface for any object contained in a property list.</para>
/// <para>The names and functions of the various objects orient themselves towards Apple's Cocoa API.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public abstract class NSObject
{
/// <summary> /// <summary>
/// The newline character used for generating the XML output. To maintain compatibility with the Apple format, /// The newline character used for generating the XML output. To maintain compatibility with the Apple format,
/// only a newline character is used (as opposed to cr+lf which is normally used on Windows). /// only a newline character is used (as opposed to cr+lf which is normally used on Windows).
@@ -108,8 +108,7 @@ namespace Claunia.PropertyList
/// <param name="level">The level of indentation.</param> /// <param name="level">The level of indentation.</param>
internal static void Indent(StringBuilder xml, int level) internal static void Indent(StringBuilder xml, int level)
{ {
for(int i = 0; i < level; i++) for(int i = 0; i < level; i++) xml.Append(INDENT);
xml.Append(INDENT);
} }
/// <summary>Wraps the given value inside a NSObject.</summary> /// <summary>Wraps the given value inside a NSObject.</summary>
@@ -140,8 +139,7 @@ namespace Claunia.PropertyList
{ {
var arr = new NSArray(value.Length); var arr = new NSArray(value.Length);
for(int i = 0; i < value.Length; i++) for(int i = 0; i < value.Length; i++) arr.Add(Wrap(value[i]));
arr.Add(Wrap(value[i]));
return arr; return arr;
} }
@@ -154,8 +152,7 @@ namespace Claunia.PropertyList
{ {
var dict = new NSDictionary(); var dict = new NSDictionary();
foreach(KeyValuePair<string, object> kvp in value) foreach(KeyValuePair<string, object> kvp in value) dict.Add(kvp.Key, Wrap(kvp.Value));
dict.Add(kvp.Key, Wrap(kvp.Value));
return dict; return dict;
} }
@@ -168,8 +165,7 @@ namespace Claunia.PropertyList
{ {
var set = new NSSet(); var set = new NSSet();
foreach(object o in value) foreach(object o in value) set.AddObject(Wrap(o));
set.AddObject(Wrap(o));
return set; return set;
} }
@@ -196,55 +192,42 @@ namespace Claunia.PropertyList
/// <returns>A NSObject equivalent to the given object.</returns> /// <returns>A NSObject equivalent to the given object.</returns>
public static NSObject Wrap(object o) public static NSObject Wrap(object o)
{ {
if(o == null) if(o == null) throw new NullReferenceException("A null object cannot be wrapped as a NSObject");
throw new NullReferenceException("A null object cannot be wrapped as a NSObject");
if(o is NSObject nsObject) if(o is NSObject nsObject) return nsObject;
return nsObject;
Type c = o.GetType(); Type c = o.GetType();
if(typeof(bool).Equals(c)) if(typeof(bool).Equals(c)) return Wrap((bool)o);
return Wrap((bool)o);
if(typeof(byte).Equals(c)) if(typeof(byte).Equals(c)) return Wrap((byte)o);
return Wrap((byte)o);
if(typeof(short).Equals(c)) if(typeof(short).Equals(c)) return Wrap((short)o);
return Wrap((short)o);
if(typeof(int).Equals(c)) if(typeof(int).Equals(c)) return Wrap((int)o);
return Wrap((int)o);
if(typeof(long).IsAssignableFrom(c)) if(typeof(long).IsAssignableFrom(c)) return Wrap((long)o);
return Wrap((long)o);
if(typeof(float).Equals(c)) if(typeof(float).Equals(c)) return Wrap((float)o);
return Wrap((float)o);
if(typeof(double).IsAssignableFrom(c)) if(typeof(double).IsAssignableFrom(c)) return Wrap((double)o);
return Wrap((double)o);
if(typeof(string).Equals(c)) if(typeof(string).Equals(c)) return new NSString((string)o);
return new NSString((string)o);
if(typeof(DateTime).Equals(c)) if(typeof(DateTime).Equals(c)) return new NSDate((DateTime)o);
return new NSDate((DateTime)o);
if(c.IsArray) if(c.IsArray)
{ {
Type cc = c.GetElementType(); Type cc = c.GetElementType();
if(cc.Equals(typeof(byte))) if(cc.Equals(typeof(byte))) return Wrap((byte[])o);
return Wrap((byte[])o);
if(cc.Equals(typeof(bool))) if(cc.Equals(typeof(bool)))
{ {
bool[] array = (bool[])o; bool[] array = (bool[])o;
var nsa = new NSArray(array.Length); var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
nsa.Add(Wrap(array[i]));
return nsa; return nsa;
} }
@@ -254,8 +237,7 @@ namespace Claunia.PropertyList
float[] array = (float[])o; float[] array = (float[])o;
var nsa = new NSArray(array.Length); var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
nsa.Add(Wrap(array[i]));
return nsa; return nsa;
} }
@@ -265,8 +247,7 @@ namespace Claunia.PropertyList
double[] array = (double[])o; double[] array = (double[])o;
var nsa = new NSArray(array.Length); var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
nsa.Add(Wrap(array[i]));
return nsa; return nsa;
} }
@@ -276,8 +257,7 @@ namespace Claunia.PropertyList
short[] array = (short[])o; short[] array = (short[])o;
var nsa = new NSArray(array.Length); var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
nsa.Add(Wrap(array[i]));
return nsa; return nsa;
} }
@@ -287,8 +267,7 @@ namespace Claunia.PropertyList
int[] array = (int[])o; int[] array = (int[])o;
var nsa = new NSArray(array.Length); var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
nsa.Add(Wrap(array[i]));
return nsa; return nsa;
} }
@@ -298,8 +277,7 @@ namespace Claunia.PropertyList
long[] array = (long[])o; long[] array = (long[])o;
var nsa = new NSArray(array.Length); var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
nsa.Add(Wrap(array[i]));
return nsa; return nsa;
} }
@@ -309,23 +287,21 @@ namespace Claunia.PropertyList
if(typeof(Dictionary<string, object>).IsAssignableFrom(c)) if(typeof(Dictionary<string, object>).IsAssignableFrom(c))
{ {
Dictionary<string, object> netDict = (Dictionary<string, object>)o; var netDict = (Dictionary<string, object>)o;
var dict = new NSDictionary(); var dict = new NSDictionary();
foreach(KeyValuePair<string, object> kvp in netDict) foreach(KeyValuePair<string, object> kvp in netDict) dict.Add(kvp.Key, Wrap(kvp.Value));
dict.Add(kvp.Key, Wrap(kvp.Value));
return dict; return dict;
} }
if(typeof(List<object>).IsAssignableFrom(c)) if(typeof(List<object>).IsAssignableFrom(c)) return Wrap(((List<object>)o).ToArray());
return Wrap(((List<object>)o).ToArray());
if(typeof(List<Dictionary<string, object>>).IsAssignableFrom(c)) if(typeof(List<Dictionary<string, object>>).IsAssignableFrom(c))
{ {
var list = new NSArray(); var list = new NSArray();
foreach(Dictionary<string, object> dict in (List<Dictionary<string, object>>)o) foreach(Dictionary<string, object> dict in (List<Dictionary<string, object>>)o) list.Add(Wrap(dict));
list.Add(Wrap(dict));
return list; return list;
} }
@@ -359,8 +335,7 @@ namespace Claunia.PropertyList
var nsArray = (NSArray)this; var nsArray = (NSArray)this;
object[] array = new object[nsArray.Count]; object[] array = new object[nsArray.Count];
for(int i = 0; i < nsArray.Count; i++) for(int i = 0; i < nsArray.Count; i++) array[i] = nsArray[i].ToObject();
array[i] = nsArray[i].ToObject();
return array; return array;
} }
@@ -369,8 +344,7 @@ namespace Claunia.PropertyList
Dictionary<string, NSObject> dictA = ((NSDictionary)this).GetDictionary(); Dictionary<string, NSObject> dictA = ((NSDictionary)this).GetDictionary();
Dictionary<string, object> dictB = new(dictA.Count); Dictionary<string, object> dictB = new(dictA.Count);
foreach(KeyValuePair<string, NSObject> kvp in dictA) foreach(KeyValuePair<string, NSObject> kvp in dictA) dictB.Add(kvp.Key, kvp.Value.ToObject());
dictB.Add(kvp.Key, kvp.Value.ToObject());
return dictB; return dictB;
} }
@@ -379,8 +353,7 @@ namespace Claunia.PropertyList
List<NSObject> setA = ((NSSet)this).GetSet(); List<NSObject> setA = ((NSSet)this).GetSet();
List<object> setB = new(); List<object> setB = new();
foreach(NSObject o in setA) foreach(NSObject o in setA) setB.Add(o.ToObject());
setB.Add(o.ToObject());
return setB; return setB;
} }
@@ -394,46 +367,49 @@ namespace Claunia.PropertyList
{ {
long longVal = num.ToLong(); long longVal = num.ToLong();
if(longVal is > int.MaxValue or < int.MinValue) if(longVal is > int.MaxValue or < int.MinValue) return longVal;
return longVal;
return num.ToInt(); return num.ToInt();
} }
case NSNumber.REAL: return num.ToDouble(); case NSNumber.REAL:
case NSNumber.BOOLEAN: return num.ToBool(); return num.ToDouble();
default: return num.ToDouble(); case NSNumber.BOOLEAN:
return num.ToBool();
default:
return num.ToDouble();
} }
break; break;
} }
case NSString: return ((NSString)this).Content; case NSString:
case NSData: return ((NSData)this).Bytes; return ((NSString)this).Content;
case NSDate: return ((NSDate)this).Date; case NSData:
case UID: return ((UID)this).Bytes; return ((NSData)this).Bytes;
default: return this; case NSDate:
return ((NSDate)this).Date;
case UID:
return ((UID)this).Bytes;
default:
return this;
} }
} }
internal static bool ArrayEquals(byte[] arrayA, byte[] arrayB) internal static bool ArrayEquals(byte[] arrayA, byte[] arrayB)
{ {
if(arrayA.Length != arrayB.Length) if(arrayA.Length != arrayB.Length) return false;
return false;
for(int i = 0; i < arrayA.Length; i++) for(int i = 0; i < arrayA.Length; i++)
if(arrayA[i] != arrayB[i]) if(arrayA[i] != arrayB[i]) return false;
return false;
return true; return true;
} }
internal static bool ArrayEquals(IList<NSObject> arrayA, IList<NSObject> arrayB) internal static bool ArrayEquals(IList<NSObject> arrayA, IList<NSObject> arrayB)
{ {
if(arrayA.Count != arrayB.Count) if(arrayA.Count != arrayB.Count) return false;
return false;
for(int i = 0; i < arrayA.Count; i++) for(int i = 0; i < arrayA.Count; i++)
if(arrayA[i] != arrayB[i]) if(arrayA[i] != arrayB[i]) return false;
return false;
return true; return true;
} }
@@ -448,5 +424,4 @@ namespace Claunia.PropertyList
/// <see cref="Claunia.PropertyList.NSObject" />; otherwise, <c>false</c>. /// <see cref="Claunia.PropertyList.NSObject" />; otherwise, <c>false</c>.
/// </returns> /// </returns>
public abstract bool Equals(NSObject obj); public abstract bool Equals(NSObject obj);
}
} }

View File

@@ -28,33 +28,33 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// <para>A set is an interface to an unordered collection of objects.</para>
/// <para>This implementation uses a <see cref="List{T}" />as the underlying data structure.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSSet : NSObject, IEnumerable
{ {
/// <summary>
/// <para>A set is an interface to an unordered collection of objects.</para>
/// <para>This implementation uses a <see cref="List{T}" />as the underlying data structure.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSSet : NSObject, IEnumerable
{
readonly bool ordered; readonly bool ordered;
readonly List<NSObject> set; readonly List<NSObject> set;
/// <summary>Creates an empty unordered set.</summary> /// <summary>Creates an empty unordered set.</summary>
public NSSet() => set = new List<NSObject>(); public NSSet() => set = [];
/// <summary>Creates an empty set.</summary> /// <summary>Creates an empty set.</summary>
/// <param name="ordered">Should the set be ordered on operations?</param> /// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered) public NSSet(bool ordered)
{ {
this.ordered = ordered; this.ordered = ordered;
set = new List<NSObject>(); set = [];
} }
/// <summary>Creates a set and fill it with the given objects.</summary> /// <summary>Creates a set and fill it with the given objects.</summary>
/// <param name="objects">The objects to populate the set.</param> /// <param name="objects">The objects to populate the set.</param>
public NSSet(params NSObject[] objects) => set = new List<NSObject>(objects); public NSSet(params NSObject[] objects) => set = [..objects];
/// <summary>Creates a set and fill it with the given objects.</summary> /// <summary>Creates a set and fill it with the given objects.</summary>
/// <param name="objects">The objects to populate the set.</param> /// <param name="objects">The objects to populate the set.</param>
@@ -64,8 +64,7 @@ namespace Claunia.PropertyList
this.ordered = ordered; this.ordered = ordered;
set = new List<NSObject>(objects); set = new List<NSObject>(objects);
if(ordered) if(ordered) set.Sort();
set.Sort();
} }
/// <summary>Gets the number of elements in the set.</summary> /// <summary>Gets the number of elements in the set.</summary>
@@ -75,9 +74,11 @@ namespace Claunia.PropertyList
get get
{ {
lock(set) lock(set)
{
return set.Count; return set.Count;
} }
} }
}
/// <summary> /// <summary>
/// Returns an enumerator object that lets you iterate over all elements of the set. This is the equivalent to /// Returns an enumerator object that lets you iterate over all elements of the set. This is the equivalent to
@@ -87,8 +88,10 @@ namespace Claunia.PropertyList
public IEnumerator GetEnumerator() public IEnumerator GetEnumerator()
{ {
lock(set) lock(set)
{
return set.GetEnumerator(); return set.GetEnumerator();
} }
}
/// <summary>Adds an object to the set.</summary> /// <summary>Adds an object to the set.</summary>
/// <param name="obj">The object to add.</param> /// <param name="obj">The object to add.</param>
@@ -98,8 +101,7 @@ namespace Claunia.PropertyList
{ {
set.Add(obj); set.Add(obj);
if(ordered) if(ordered) set.Sort();
set.Sort();
} }
} }
@@ -111,8 +113,7 @@ namespace Claunia.PropertyList
{ {
set.Remove(obj); set.Remove(obj);
if(ordered) if(ordered) set.Sort();
set.Sort();
} }
} }
@@ -121,16 +122,20 @@ namespace Claunia.PropertyList
public NSObject[] AllObjects() public NSObject[] AllObjects()
{ {
lock(set) lock(set)
{
return set.ToArray(); return set.ToArray();
} }
}
/// <summary>Returns one of the objects in the set, or <c>null</c> if the set contains no objects.</summary> /// <summary>Returns one of the objects in the set, or <c>null</c> if the set contains no objects.</summary>
/// <returns>The first object in the set, or <c>null</c> if the set is empty.</returns> /// <returns>The first object in the set, or <c>null</c> if the set is empty.</returns>
public NSObject AnyObject() public NSObject AnyObject()
{ {
lock(set) lock(set)
{
return set.Count == 0 ? null : set[0]; return set.Count == 0 ? null : set[0];
} }
}
/// <summary>Finds out whether a given object is contained in the set.</summary> /// <summary>Finds out whether a given object is contained in the set.</summary>
/// <returns><c>true</c>, when the object was found, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, when the object was found, <c>false</c> otherwise.</returns>
@@ -148,8 +153,7 @@ namespace Claunia.PropertyList
lock(set) lock(set)
{ {
foreach(NSObject o in set) foreach(NSObject o in set)
if(o.Equals(obj)) if(o.Equals(obj)) return o;
return o;
return null; return null;
} }
@@ -163,8 +167,7 @@ namespace Claunia.PropertyList
lock(set) lock(set)
{ {
foreach(NSObject o in set) foreach(NSObject o in set)
if(otherSet.ContainsObject(o)) if(otherSet.ContainsObject(o)) return true;
return true;
return false; return false;
} }
@@ -178,8 +181,7 @@ namespace Claunia.PropertyList
lock(set) lock(set)
{ {
foreach(NSObject o in set) foreach(NSObject o in set)
if(!otherSet.ContainsObject(o)) if(!otherSet.ContainsObject(o)) return false;
return false;
return true; return true;
} }
@@ -197,7 +199,7 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
hash = (29 * hash) + (set != null ? set.GetHashCode() : 0); hash = 29 * hash + (set != null ? set.GetHashCode() : 0);
return hash; return hash;
} }
@@ -216,11 +218,9 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if(obj == null) if(obj == null) return false;
return false;
if(GetType() != obj.GetType()) if(GetType() != obj.GetType()) return false;
return false;
var other = (NSSet)obj; var other = (NSSet)obj;
@@ -239,8 +239,7 @@ namespace Claunia.PropertyList
xml.Append("<array>"); xml.Append("<array>");
xml.Append(NEWLINE); xml.Append(NEWLINE);
if(ordered) if(ordered) set.Sort();
set.Sort();
foreach(NSObject o in set) foreach(NSObject o in set)
{ {
@@ -256,8 +255,7 @@ namespace Claunia.PropertyList
{ {
base.AssignIDs(outPlist); base.AssignIDs(outPlist);
foreach(NSObject obj in set) foreach(NSObject obj in set) obj.AssignIDs(outPlist);
obj.AssignIDs(outPlist);
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
@@ -270,8 +268,7 @@ namespace Claunia.PropertyList
else else
outPlist.WriteIntHeader(0xC, set.Count); outPlist.WriteIntHeader(0xC, set.Count);
foreach(NSObject obj in set) foreach(NSObject obj in set) outPlist.WriteID(outPlist.GetID(obj));
outPlist.WriteID(outPlist.GetID(obj));
} }
/// <summary> /// <summary>
@@ -284,8 +281,7 @@ namespace Claunia.PropertyList
{ {
Indent(ascii, level); Indent(ascii, level);
if(ordered) if(ordered) set.Sort();
set.Sort();
NSObject[] array = AllObjects(); NSObject[] array = AllObjects();
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
@@ -295,7 +291,8 @@ namespace Claunia.PropertyList
{ {
Type objClass = array[i].GetType(); Type objClass = array[i].GetType();
if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || if((objClass.Equals(typeof(NSDictionary)) ||
objClass.Equals(typeof(NSArray)) ||
objClass.Equals(typeof(NSData))) && objClass.Equals(typeof(NSData))) &&
indexOfLastNewLine != ascii.Length) indexOfLastNewLine != ascii.Length)
{ {
@@ -305,17 +302,14 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(i != 0) if(i != 0) ascii.Append(" ");
ascii.Append(" ");
array[i].ToASCII(ascii, 0); array[i].ToASCII(ascii, 0);
} }
if(i != array.Length - 1) if(i != array.Length - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue;
continue;
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -334,8 +328,7 @@ namespace Claunia.PropertyList
{ {
Indent(ascii, level); Indent(ascii, level);
if(ordered) if(ordered) set.Sort();
set.Sort();
NSObject[] array = AllObjects(); NSObject[] array = AllObjects();
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
@@ -343,8 +336,7 @@ namespace Claunia.PropertyList
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++)
{ {
if(array[i] is NSDictionary or NSArray or NSData && if(array[i] is NSDictionary or NSArray or NSData && indexOfLastNewLine != ascii.Length)
indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -352,17 +344,14 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(i != 0) if(i != 0) ascii.Append(" ");
ascii.Append(" ");
array[i].ToASCIIGnuStep(ascii, 0); array[i].ToASCIIGnuStep(ascii, 0);
} }
if(i != array.Length - 1) if(i != array.Length - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue;
continue;
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -385,17 +374,13 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(obj is not NSSet nsSet) if(obj is not NSSet nsSet) return false;
return false;
if(set.Count != nsSet.Count) if(set.Count != nsSet.Count) return false;
return false;
foreach(NSObject objS in nsSet) foreach(NSObject objS in nsSet)
if(!set.Contains(objS)) if(!set.Contains(objS)) return false;
return false;
return true; return true;
} }
}
} }

View File

@@ -27,13 +27,13 @@ using System;
using System.Security; using System.Security;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>A NSString contains a string.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSString : NSObject, IComparable
{ {
/// <summary>A NSString contains a string.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSString : NSObject, IComparable
{
static Encoding asciiEncoder, utf16beEncoder, utf8Encoder; static Encoding asciiEncoder, utf16beEncoder, utf8Encoder;
/// <summary>Creates a NSString from its binary representation.</summary> /// <summary>Creates a NSString from its binary representation.</summary>
@@ -48,11 +48,11 @@ namespace Claunia.PropertyList
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception> /// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
public NSString(ReadOnlySpan<byte> bytes, Encoding encoding) public NSString(ReadOnlySpan<byte> bytes, Encoding encoding)
{ {
#if NATIVE_SPAN #if NATIVE_SPAN
Content = encoding.GetString(bytes); Content = encoding.GetString(bytes);
#else #else
Content = encoding.GetString(bytes.ToArray()); Content = encoding.GetString(bytes.ToArray());
#endif #endif
} }
/// <summary>Creates a NSString from a string.</summary> /// <summary>Creates a NSString from a string.</summary>
@@ -68,7 +68,9 @@ namespace Claunia.PropertyList
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param> /// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param>
public int CompareTo(object o) => o switch public int CompareTo(object o) => o switch
{ {
NSString nsString => string.Compare(Content, nsString.Content, StringComparison.Ordinal), NSString nsString => string.Compare(Content,
nsString.Content,
StringComparison.Ordinal),
string s => string.Compare(Content, s, StringComparison.Ordinal), string s => string.Compare(Content, s, StringComparison.Ordinal),
_ => -1 _ => -1
}; };
@@ -150,7 +152,8 @@ namespace Claunia.PropertyList
lock(typeof(NSString)) lock(typeof(NSString))
{ {
// Not much use, because some characters do not fallback to exception, even if not ASCII // Not much use, because some characters do not fallback to exception, even if not ASCII
asciiEncoder ??= Encoding.GetEncoding("ascii", EncoderFallback.ExceptionFallback, asciiEncoder ??= Encoding.GetEncoding("ascii",
EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback); DecoderFallback.ExceptionFallback);
if(IsASCIIEncodable(Content)) if(IsASCIIEncodable(Content))
@@ -201,18 +204,19 @@ namespace Claunia.PropertyList
char[] cArray = s.ToCharArray(); char[] cArray = s.ToCharArray();
foreach(char c in cArray) foreach(char c in cArray)
{
if(c > 127) if(c > 127)
{ {
//non-ASCII Unicode //non-ASCII Unicode
outString += "\\U"; outString += "\\U";
string hex = $"{c:x}"; string hex = $"{c:x}";
while(hex.Length < 4) while(hex.Length < 4) hex = "0" + hex;
hex = "0" + hex;
outString += hex; outString += hex;
} }
else else
{
outString += c switch outString += c switch
{ {
'\\' => "\\\\", '\\' => "\\\\",
@@ -223,6 +227,8 @@ namespace Claunia.PropertyList
'\t' => "\\t", '\t' => "\\t",
_ => c _ => c
}; };
}
}
return outString; return outString;
} }
@@ -241,8 +247,7 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(obj is not NSString nsString) if(obj is not NSString nsString) return false;
return false;
return Content == nsString.Content; return Content == nsString.Content;
} }
@@ -250,8 +255,7 @@ namespace Claunia.PropertyList
internal static bool IsASCIIEncodable(string text) internal static bool IsASCIIEncodable(string text)
{ {
foreach(char c in text) foreach(char c in text)
if(c > 0x7F) if(c > 0x7F) return false;
return false;
return true; return true;
} }
@@ -259,5 +263,4 @@ namespace Claunia.PropertyList
public static explicit operator string(NSString value) => value.Content; public static explicit operator string(NSString value) => value.Content;
public static explicit operator NSString(string value) => new(value); public static explicit operator NSString(string value) => new(value);
}
} }

View File

@@ -27,12 +27,12 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>The exception that is thrown when an property list file could not be processed correctly.</summary>
[Serializable]
public class PropertyListException : Exception
{ {
/// <summary>The exception that is thrown when an property list file could not be processed correctly.</summary>
[Serializable]
public class PropertyListException : Exception
{
/// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary> /// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
public PropertyListException() {} public PropertyListException() {}
@@ -49,5 +49,4 @@ namespace Claunia.PropertyList
public PropertyListException(string message, Exception inner) : base(message, inner) {} public PropertyListException(string message, Exception inner) : base(message, inner) {}
protected PropertyListException(SerializationInfo info, StreamingContext context) : base(info, context) {} protected PropertyListException(SerializationInfo info, StreamingContext context) : base(info, context) {}
}
} }

View File

@@ -23,18 +23,17 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// A PropertyListFormatException is thrown by the various property list format parsers when an error in the
/// format of the given property list is encountered.
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class PropertyListFormatException : PropertyListException
{ {
/// <summary>
/// A PropertyListFormatException is thrown by the various property list format parsers when an error in the
/// format of the given property list is encountered.
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class PropertyListFormatException : PropertyListException
{
/// <summary>Creates a new exception with the given message.</summary> /// <summary>Creates a new exception with the given message.</summary>
/// <param name="message">A message containing information about the nature of the exception.</param> /// <param name="message">A message containing information about the nature of the exception.</param>
public PropertyListFormatException(string message) : base(message) {} public PropertyListFormatException(string message) : base(message) {}
}
} }

View File

@@ -27,16 +27,16 @@ using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// This class provides methods to parse property lists. It can handle files, input streams and byte arrays. All
/// known property list formats are supported. This class also provides methods to save and convert property lists.
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public static class PropertyListParser
{ {
/// <summary>
/// This class provides methods to parse property lists. It can handle files, input streams and byte arrays. All
/// known property list formats are supported. This class also provides methods to save and convert property lists.
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public static class PropertyListParser
{
const int TYPE_XML = 0; const int TYPE_XML = 0;
const int TYPE_BINARY = 1; const int TYPE_BINARY = 1;
const int TYPE_ASCII = 2; const int TYPE_ASCII = 2;
@@ -48,16 +48,11 @@ namespace Claunia.PropertyList
/// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param> /// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param>
static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning) static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning)
{ {
if(dataBeginning.Length == 0) if(dataBeginning.Length == 0) return TYPE_ERROR_BLANK;
return TYPE_ERROR_BLANK;
if(dataBeginning[0] == '(' || if(dataBeginning[0] == '(' || dataBeginning[0] == '{' || dataBeginning[0] == '/') return TYPE_ASCII;
dataBeginning[0] == '{' ||
dataBeginning[0] == '/')
return TYPE_ASCII;
if(dataBeginning[0] == '<') if(dataBeginning[0] == '<') return TYPE_XML;
return TYPE_XML;
if(dataBeginning.Length >= 6 && if(dataBeginning.Length >= 6 &&
dataBeginning[0] == 'b' && dataBeginning[0] == 'b' &&
@@ -76,20 +71,19 @@ namespace Claunia.PropertyList
/// <param name="bytes">The type of the property list</param> /// <param name="bytes">The type of the property list</param>
static int DetermineType(ReadOnlySpan<byte> bytes) static int DetermineType(ReadOnlySpan<byte> bytes)
{ {
if(bytes.Length == 0) if(bytes.Length == 0) return TYPE_ERROR_BLANK;
return TYPE_ERROR_BLANK;
//Skip any possible whitespace at the beginning of the file //Skip any possible whitespace at the beginning of the file
int offset = 0; int offset = 0;
if(bytes.Length >= 3 && if(bytes.Length >= 3 && (bytes[0] & 0xFF) == 0xEF && (bytes[1] & 0xFF) == 0xBB && (bytes[2] & 0xFF) == 0xBF)
(bytes[0] & 0xFF) == 0xEF &&
(bytes[1] & 0xFF) == 0xBB &&
(bytes[2] & 0xFF) == 0xBF)
offset += 3; offset += 3;
while(offset < bytes.Length && while(offset < bytes.Length &&
(bytes[offset] == ' ' || bytes[offset] == '\t' || bytes[offset] == '\r' || bytes[offset] == '\n' || (bytes[offset] == ' ' ||
bytes[offset] == '\t' ||
bytes[offset] == '\r' ||
bytes[offset] == '\n' ||
bytes[offset] == '\f')) bytes[offset] == '\f'))
offset++; offset++;
@@ -106,8 +100,7 @@ namespace Claunia.PropertyList
/// </param> /// </param>
static int DetermineType(Stream fs, long offset = 0) static int DetermineType(Stream fs, long offset = 0)
{ {
if(fs.Length == 0) if(fs.Length == 0) return TYPE_ERROR_BLANK;
return TYPE_ERROR_BLANK;
long index = offset; long index = offset;
long readLimit = index + 1024; long readLimit = index + 1024;
@@ -129,13 +122,10 @@ namespace Claunia.PropertyList
b = fs.ReadByte(); b = fs.ReadByte();
//Check if we are reading the Unicode byte order mark (BOM) and skip it //Check if we are reading the Unicode byte order mark (BOM) and skip it
bom = index < 3 && ((index == 0 && b == 0xEF) || bom = index < 3 && (index == 0 && b == 0xEF || bom && (index == 1 && b == 0xBB || index == 2 && b == 0xBF));
(bom && ((index == 1 && b == 0xBB) || (index == 2 && b == 0xBF)))); } while(b != -1 && (b is ' ' or '\t' or '\r' or '\n' or '\f' || bom));
} while(b != -1 &&
(b is ' ' or '\t' or '\r' or '\n' or '\f' || bom));
if(b == -1) if(b == -1) return TYPE_ERROR_BLANK;
return TYPE_ERROR_BLANK;
byte[] magicBytes = new byte[8]; byte[] magicBytes = new byte[8];
magicBytes[0] = (byte)b; magicBytes[0] = (byte)b;
@@ -186,12 +176,14 @@ namespace Claunia.PropertyList
{ {
switch(DetermineType(bytes)) switch(DetermineType(bytes))
{ {
case TYPE_BINARY: return BinaryPropertyListParser.Parse(bytes); case TYPE_BINARY:
case TYPE_XML: return XmlPropertyListParser.Parse(bytes); return BinaryPropertyListParser.Parse(bytes);
case TYPE_ASCII: return ASCIIPropertyListParser.Parse(bytes); case TYPE_XML:
return XmlPropertyListParser.Parse(bytes);
case TYPE_ASCII:
return ASCIIPropertyListParser.Parse(bytes);
default: default:
throw new throw new PropertyListFormatException("The given data is not a property list of a supported format.");
PropertyListFormatException("The given data is not a property list of a supported format.");
} }
} }
@@ -209,12 +201,14 @@ namespace Claunia.PropertyList
{ {
switch(DetermineType(bytes)) switch(DetermineType(bytes))
{ {
case TYPE_BINARY: return BinaryPropertyListParser.Parse(bytes); case TYPE_BINARY:
case TYPE_XML: return XmlPropertyListParser.Parse(bytes.ToArray()); return BinaryPropertyListParser.Parse(bytes);
case TYPE_ASCII: return ASCIIPropertyListParser.Parse(bytes); case TYPE_XML:
return XmlPropertyListParser.Parse(bytes.ToArray());
case TYPE_ASCII:
return ASCIIPropertyListParser.Parse(bytes);
default: default:
throw new throw new PropertyListFormatException("The given data is not a property list of a supported format.");
PropertyListFormatException("The given data is not a property list of a supported format.");
} }
} }
@@ -231,8 +225,7 @@ namespace Claunia.PropertyList
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent);
// Use Create here -- to make sure that when the updated file is shorter than // Use Create here -- to make sure that when the updated file is shorter than
// the original file, no "obsolete" data is left at the end. // the original file, no "obsolete" data is left at the end.
@@ -269,8 +262,7 @@ namespace Claunia.PropertyList
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent);
BinaryPropertyListWriter.Write(outFile, root); BinaryPropertyListWriter.Write(outFile, root);
} }
@@ -279,8 +271,7 @@ namespace Claunia.PropertyList
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param> /// <param name="outStream">The output stream.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsBinary(NSObject root, Stream outStream) => public static void SaveAsBinary(NSObject root, Stream outStream) => BinaryPropertyListWriter.Write(outStream, root);
BinaryPropertyListWriter.Write(outStream, root);
/// <summary>Converts a given property list file into the OS X and iOS binary format.</summary> /// <summary>Converts a given property list file into the OS X and iOS binary format.</summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
@@ -299,8 +290,7 @@ namespace Claunia.PropertyList
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
@@ -317,8 +307,7 @@ namespace Claunia.PropertyList
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
@@ -339,9 +328,11 @@ namespace Claunia.PropertyList
else if(root is NSArray array) else if(root is NSArray array)
SaveAsASCII(array, outFile); SaveAsASCII(array, outFile);
else else
{
throw new PropertyListFormatException("The root of the given input property list " + throw new PropertyListFormatException("The root of the given input property list " +
"is neither a Dictionary nor an Array!"); "is neither a Dictionary nor an Array!");
} }
}
/// <summary>Saves a property list with the given object as root into a GnuStep ASCII file.</summary> /// <summary>Saves a property list with the given object as root into a GnuStep ASCII file.</summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
@@ -351,8 +342,7 @@ namespace Claunia.PropertyList
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
@@ -369,8 +359,7 @@ namespace Claunia.PropertyList
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
@@ -401,5 +390,4 @@ namespace Claunia.PropertyList
"is neither a Dictionary nor an Array!"); "is neither a Dictionary nor an Array!");
} }
} }
}
} }

View File

@@ -27,23 +27,20 @@ using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>An UID. Only found in binary property lists that are keyed archives.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class UID : NSObject
{ {
/// <summary>An UID. Only found in binary property lists that are keyed archives.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class UID : NSObject
{
readonly ulong value; readonly ulong value;
/// <summary>Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class.</summary> /// <summary>Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class.</summary>
/// <param name="bytes">Bytes.</param> /// <param name="bytes">Bytes.</param>
public UID(ReadOnlySpan<byte> bytes) public UID(ReadOnlySpan<byte> bytes)
{ {
if(bytes.Length != 1 && if(bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8)
bytes.Length != 2 &&
bytes.Length != 4 &&
bytes.Length != 8)
throw new ArgumentException("Type argument is not valid."); throw new ArgumentException("Type argument is not valid.");
value = (ulong)BinaryPropertyListParser.ParseLong(bytes); value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
@@ -126,7 +123,8 @@ namespace Claunia.PropertyList
break; break;
default: throw new InvalidOperationException(); default:
throw new InvalidOperationException();
} }
} }
@@ -169,8 +167,7 @@ namespace Claunia.PropertyList
Span<byte> bytes = stackalloc byte[ByteCount]; Span<byte> bytes = stackalloc byte[ByteCount];
GetBytes(bytes); GetBytes(bytes);
foreach(byte b in bytes) foreach(byte b in bytes) ascii.Append($"{b:x2}");
ascii.Append($"{b:x2}");
ascii.Append("\""); ascii.Append("\"");
} }
@@ -194,8 +191,7 @@ namespace Claunia.PropertyList
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if(obj is not UID uid) if(obj is not UID uid) return false;
return false;
return uid.value == value; return uid.value == value;
} }
@@ -209,5 +205,4 @@ namespace Claunia.PropertyList
/// <summary>Gets a <see cref="ulong" /> which represents this <see cref="UID" />.</summary> /// <summary>Gets a <see cref="ulong" /> which represents this <see cref="UID" />.</summary>
/// <returns>A <see cref="ulong" /> which represents this <see cref="UID" />.</returns> /// <returns>A <see cref="ulong" /> which represents this <see cref="UID" />.</returns>
public ulong ToUInt64() => value; public ulong ToUInt64() => value;
}
} }

View File

@@ -1,53 +1,83 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>
/// Allows you to override the default value class initialization for the values found
/// in the parsed plists by registering your own preprocessing implementations.
/// </summary>
public static class ValuePreprocessor
{ {
/// <summary>
/// Allows you to override the default value class initialization for the values found
/// in the parsed plists by registering your own preprocessing implementations.
/// </summary>
public static class ValuePreprocessor
{
/// <summary> /// <summary>
/// Indicates the semantic type of content the preprocessor will work on--independent /// 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). /// from the underlying data type (which will be string in most cases anyway).
/// </summary> /// </summary>
public enum Type public enum Type
{ {
BOOL, INTEGER, FLOATING_POINT, BOOL,
UNDEFINED_NUMBER, STRING, DATA, INTEGER,
FLOATING_POINT,
UNDEFINED_NUMBER,
STRING,
DATA,
DATE DATE
}; }
/// <summary>
/// A null-implementation of a preprocessor for registered, but passive, use cases.
/// </summary>
private static T NullPreprocessor<T>(T value) => value;
private record struct TypeIdentifier(Type ValueType, System.Type DataType);
/// <summary> /// <summary>
/// Default preprocessors for all the standard cases. /// Default preprocessors for all the standard cases.
/// </summary> /// </summary>
private static readonly Dictionary<TypeIdentifier, Delegate> _preprocessors = new() private static readonly Dictionary<TypeIdentifier, Delegate> _preprocessors = new()
{ {
{ new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor<bool> }, {
{ new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor<string> }, new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor<bool>
{ new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor<string> }, },
{ new TypeIdentifier(Type.INTEGER, typeof(byte[])), NullPreprocessor<byte[]> }, {
{ new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor<string> }, new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor<string>
{ new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor<byte[]> }, },
{ new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor<string> }, {
{ new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor<string> }, new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor<string>
{ new TypeIdentifier(Type.STRING, typeof(byte[])), NullPreprocessor<byte[]> }, },
{ new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor<string> }, {
{ new TypeIdentifier(Type.DATA, typeof(byte[])), NullPreprocessor<byte[]> }, new TypeIdentifier(Type.INTEGER, typeof(byte[])), NullPreprocessor<byte[]>
{ new TypeIdentifier(Type.DATE, typeof(string)), NullPreprocessor<string> }, },
{ new TypeIdentifier(Type.DATE, typeof(double)), NullPreprocessor<double> }, {
{ new TypeIdentifier(Type.DATE, typeof(byte[])), NullPreprocessor<byte[]> }, new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor<byte[]>
},
{
new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.STRING, typeof(byte[])), NullPreprocessor<byte[]>
},
{
new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.DATA, typeof(byte[])), NullPreprocessor<byte[]>
},
{
new TypeIdentifier(Type.DATE, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.DATE, typeof(double)), NullPreprocessor<double>
},
{
new TypeIdentifier(Type.DATE, typeof(byte[])), NullPreprocessor<byte[]>
}
}; };
/// <summary>
/// A null-implementation of a preprocessor for registered, but passive, use cases.
/// </summary>
private static T NullPreprocessor<T>(T value) => value;
/// <summary> /// <summary>
/// Get a default preprocessor. /// Get a default preprocessor.
/// </summary> /// </summary>
@@ -57,7 +87,7 @@ namespace Claunia.PropertyList
/// Set up a custom preprocessor. /// Set up a custom preprocessor.
/// </summary> /// </summary>
public static void Set<T>(Func<T, T> preprocessor, Type type) => public static void Set<T>(Func<T, T> preprocessor, Type type) =>
_preprocessors[new(type, typeof(T))] = preprocessor; _preprocessors[new TypeIdentifier(type, typeof(T))] = preprocessor;
/// <summary> /// <summary>
@@ -65,24 +95,29 @@ namespace Claunia.PropertyList
/// to prevent argument errors. /// to prevent argument errors.
/// </summary> /// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was set up.</exception> /// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was set up.</exception>
public static void Unset<T>(Type type) => public static void Unset<T>(Type type) => _preprocessors[GetValidTypeIdentifier<T>(type)] = NullPreprocessor<T>;
_preprocessors[GetValidTypeIdentifier<T>(type)] = NullPreprocessor<T>;
/// <summary> /// <summary>
/// Completely unregister a specific preprocessor--remove it instead of /// Completely unregister a specific preprocessor--remove it instead of
/// replacing it with a null-implementation. /// replacing it with a null-implementation.
/// </summary> /// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was registered.</exception> /// <exception cref="ArgumentException">
public static void Remove<T>(Type type) => /// If no appropriate preprocessor--not even a default null-implementation--was
_preprocessors.Remove(GetValidTypeIdentifier<T>(type)); /// registered.
/// </exception>
public static void Remove<T>(Type type) => _preprocessors.Remove(GetValidTypeIdentifier<T>(type));
/// <summary> /// <summary>
/// Preprocess the supplied data using the appropriate registered implementation. /// Preprocess the supplied data using the appropriate registered implementation.
/// </summary> /// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was registered.</exception> /// <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) public static T Preprocess<T>(T value, Type type) => 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}'.");
/// <summary> /// <summary>
/// Gets the appropriate registered implementation--or null--and casts it back to /// Gets the appropriate registered implementation--or null--and casts it back to
@@ -97,7 +132,7 @@ namespace Claunia.PropertyList
return true; return true;
} }
preprocess = default; preprocess = default(Func<T, T>);
return false; return false;
} }
@@ -111,11 +146,10 @@ namespace Claunia.PropertyList
var identifier = new TypeIdentifier(type, typeof(T)); var identifier = new TypeIdentifier(type, typeof(T));
if(!_preprocessors.ContainsKey(identifier)) if(!_preprocessors.ContainsKey(identifier))
{ throw new ArgumentException("Failed to find a valid preprocessor type identifier.");
throw new ArgumentException($"Failed to find a valid preprocessor type identifier.");
}
return identifier; return identifier;
} }
}
private record struct TypeIdentifier(Type ValueType, System.Type DataType);
} }

View File

@@ -28,13 +28,13 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
namespace Claunia.PropertyList namespace Claunia.PropertyList;
/// <summary>Parses XML property lists.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public static class XmlPropertyListParser
{ {
/// <summary>Parses XML property lists.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public static class XmlPropertyListParser
{
/// <summary>Parses a XML property list file.</summary> /// <summary>Parses a XML property list file.</summary>
/// <param name="f">The XML property list file.</param> /// <param name="f">The XML property list file.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -48,8 +48,12 @@ namespace Claunia.PropertyList
}; };
using(Stream stream = f.OpenRead()) using(Stream stream = f.OpenRead())
{
using(var reader = XmlReader.Create(stream, settings)) using(var reader = XmlReader.Create(stream, settings))
{
doc.Load(reader); doc.Load(reader);
}
}
return ParseDocument(doc); return ParseDocument(doc);
} }
@@ -71,11 +75,15 @@ namespace Claunia.PropertyList
{ {
var doc = new XmlDocument(); var doc = new XmlDocument();
var settings = new XmlReaderSettings(); var settings = new XmlReaderSettings
settings.DtdProcessing = DtdProcessing.Ignore; {
DtdProcessing = DtdProcessing.Ignore
};
using(var reader = XmlReader.Create(str, settings)) using(var reader = XmlReader.Create(str, settings))
{
doc.Load(reader); doc.Load(reader);
}
return ParseDocument(doc); return ParseDocument(doc);
} }
@@ -87,8 +95,10 @@ namespace Claunia.PropertyList
{ {
var doc = new XmlDocument(); var doc = new XmlDocument();
var settings = new XmlReaderSettings(); var settings = new XmlReaderSettings
settings.DtdProcessing = DtdProcessing.Ignore; {
DtdProcessing = DtdProcessing.Ignore
};
doc.LoadXml(value); doc.LoadXml(value);
@@ -100,17 +110,14 @@ namespace Claunia.PropertyList
/// <param name="doc">The XML document.</param> /// <param name="doc">The XML document.</param>
static NSObject ParseDocument(XmlDocument doc) static NSObject ParseDocument(XmlDocument doc)
{ {
XmlNode docType = doc.ChildNodes.OfType<XmlNode>(). XmlNode docType = doc.ChildNodes.OfType<XmlNode>().SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType);
SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType);
if(docType == null) if(docType == null)
{ {
if(doc.DocumentElement != null && if(doc.DocumentElement != null && !doc.DocumentElement.Name.Equals("plist"))
!doc.DocumentElement.Name.Equals("plist"))
throw new XmlException("The given XML document is not a property list."); throw new XmlException("The given XML document is not a property list.");
} }
else if(!docType.Name.Equals("plist")) else if(!docType.Name.Equals("plist")) throw new XmlException("The given XML document is not a property list.");
throw new XmlException("The given XML document is not a property list.");
XmlNode rootNode; XmlNode rootNode;
@@ -121,7 +128,8 @@ namespace Claunia.PropertyList
rootNode = rootNodes.Count switch rootNode = rootNodes.Count switch
{ {
0 => throw new PropertyListFormatException("The given XML property list has no root element!"), 0 => throw new
PropertyListFormatException("The given XML property list has no root element!"),
1 => rootNodes[0], 1 => rootNodes[0],
_ => throw new _ => throw new
PropertyListFormatException("The given XML property list has more than one root element!") PropertyListFormatException("The given XML property list has more than one root element!")
@@ -143,9 +151,12 @@ namespace Claunia.PropertyList
switch(n.Name) switch(n.Name)
{ {
// Special case for UID values // Special case for UID values
case "dict" when n.ChildNodes.Count == 2 && n.ChildNodes[0].Name == "key" && case "dict" when n.ChildNodes.Count == 2 &&
n.ChildNodes[0].InnerText == "CF$UID" && n.ChildNodes[1].Name == "integer" && n.ChildNodes[0].Name == "key" &&
uint.TryParse(n.ChildNodes[1].InnerText, out uint uidValue): return new UID(uidValue); n.ChildNodes[0].InnerText == "CF$UID" &&
n.ChildNodes[1].Name == "integer" &&
uint.TryParse(n.ChildNodes[1].InnerText, out uint uidValue):
return new UID(uidValue);
case "dict": case "dict":
{ {
var dict = new NSDictionary(); var dict = new NSDictionary();
@@ -168,18 +179,32 @@ namespace Claunia.PropertyList
List<XmlNode> children = FilterElementNodes(n.ChildNodes); List<XmlNode> children = FilterElementNodes(n.ChildNodes);
var array = new NSArray(children.Count); var array = new NSArray(children.Count);
for(int i = 0; i < children.Count; i++) for(int i = 0; i < children.Count; i++) array.Add(ParseObject(children[i]));
array.Add(ParseObject(children[i]));
return array; return array;
} }
case "true": return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); case "true":
case "false": return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL));
case "integer": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.INTEGER), NSNumber.INTEGER); case "false":
case "real": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.FLOATING_POINT), NSNumber.REAL); return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL));
case "string": return new NSString(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.STRING)); case "integer":
case "data": return new NSData(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.DATA)); return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n),
default: return n.Name.Equals("date") ? new NSDate(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.DATE)) : null; 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;
} }
} }
@@ -188,11 +213,10 @@ namespace Claunia.PropertyList
/// <param name="list">The list of nodes to search.</param> /// <param name="list">The list of nodes to search.</param>
static List<XmlNode> FilterElementNodes(XmlNodeList list) static List<XmlNode> FilterElementNodes(XmlNodeList list)
{ {
List<XmlNode> result = new(); List<XmlNode> result = [];
foreach(XmlNode child in list) foreach(XmlNode child in list)
if(child.NodeType == XmlNodeType.Element) if(child.NodeType == XmlNodeType.Element) result.Add(child);
result.Add(child);
return result; return result;
} }
@@ -212,22 +236,22 @@ namespace Claunia.PropertyList
return content ?? ""; return content ?? "";
} }
if(!n.HasChildNodes) if(!n.HasChildNodes) return "";
return "";
XmlNodeList children = n.ChildNodes; XmlNodeList children = n.ChildNodes;
foreach(XmlNode child in children) foreach(XmlNode child in children)
//Skip any non-text nodes, like comments or entities //Skip any non-text nodes, like comments or entities
{
if(child.NodeType is XmlNodeType.Text or XmlNodeType.CDATA) if(child.NodeType is XmlNodeType.Text or XmlNodeType.CDATA)
{ {
string content = child.Value; //This concatenates any adjacent text/cdata/entity nodes string content = child.Value; //This concatenates any adjacent text/cdata/entity nodes
return content ?? ""; return content ?? "";
} }
}
return ""; return "";
} }
}
} }

View File

@@ -45,12 +45,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" PrivateAssets="all" /> <PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" PrivateAssets="all"/>
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net462'"> <ItemGroup Condition=" '$(TargetFramework)' == 'net462'">
<PackageReference Include="System.Memory" Version="4.6.0" /> <PackageReference Include="System.Memory" Version="4.6.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<string>Lot&amp;s of &amp;persand&amp;s and other escapable &quot;&apos;&lt;&gt;&#x20ac; characters</string> <string>Lot&amp;s of &amp;persand&amp;s and other escapable &quot;&apos;&lt;&gt;&#x20ac; characters</string>
</plist> </plist>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>emojiString</key> <key>emojiString</key>
<string>Test Test, 😰❔👍👎🔥</string> <string>Test Test, 😰❔👍👎🔥</string>
</dict> </dict>
</plist> </plist>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<array> <array>
<dict/> <dict/>
<integer>0</integer> <integer>0</integer>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>Device Name</key> <key>Device Name</key>
<string>Kids iPhone</string> <string>Kids iPhone</string>
</dict> </dict>
</plist> </plist>

View File

@@ -1,5 +1 @@
// !$*UTF8*$! // !$*UTF8*$!{path = "JÔÖú@2x.jpg";"Key QÔÖª@2x \u4321" = "QÔÖú@2x 啕.jpg";}
{
path = "JÔÖú@2x.jpg";
"Key QÔÖª@2x \u4321" = "QÔÖú@2x 啕.jpg";
}

View File

@@ -1,5 +1 @@
{ {"key&\102"="value&\U0042==";key2 = "strangestring\\\"";key3 = "strangestring\\";}
"key&\102"="value&\U0042==";
key2 = "strangestring\\\"";
key3 = "strangestring\\";
}

View File

@@ -1,12 +1,2 @@
{ { keyA = valueA; "key&\102" = "value&\U0042"; date =
keyA = valueA; <*D2011-11-28 09:21:30 +0000>; data = <00000004 10410820 82>; array = ( <*BY>, <*BN>, <*I87>, <*R3.14159> );}
"key&\102" = "value&\U0042";
date = <*D2011-11-28 09:21:30 +0000>;
data = <00000004 10410820 82>;
array = (
<*BY>,
<*BN>,
<*I87>,
<*R3.14159>
);
}

View File

@@ -1,12 +1,2 @@
{ { keyA = valueA; "key&\102" = "value&\U0042"; date = "2011-11-28T09:21:30Z"; data =
keyA = valueA; <00000004 10410820 82>; array = ( YES, NO, 87, 3.14159 );}
"key&\102" = "value&\U0042";
date = "2011-11-28T09:21:30Z";
data = <00000004 10410820 82>;
array = (
YES,
NO,
87,
3.14159
);
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>keyA</key> <key>keyA</key>
<string>valueA</string> <string>valueA</string>
<key>key&amp;B</key> <key>key&amp;B</key>
@@ -17,5 +17,5 @@
<integer>87</integer> <integer>87</integer>
<real>3.14159</real> <real>3.14159</real>
</array> </array>
</dict> </dict>
</plist> </plist>

Binary file not shown.

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>number</key> <key>number</key>
<integer>-1234</integer> <integer>-1234</integer>
<key>number2</key> <key>number2</key>
@@ -14,5 +14,5 @@
<real>2.352535353543534e+19</real> <real>2.352535353543534e+19</real>
<key>number6</key> <key>number6</key>
<real>-999992312312312.2</real> <real>-999992312312312.2</real>
</dict> </dict>
</plist> </plist>