From 5f6bf00855028fe792e28f6cc704337fe6e49bdc Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Thu, 7 Aug 2025 22:31:12 +0100 Subject: [PATCH] General refactor and clean-up. --- README.md | 51 +- codealike.json | 2 +- .../BinaryPropertyListParserBenchmarks.cs | 28 +- .../BinaryPropertyListWriterBenchmarks.cs | 20 +- plist-cil.benchmark/Program.cs | 13 +- .../plist-cil.benchmark.csproj | 4 +- plist-cil.sln.DotSettings | 6 +- .../BinaryPropertyListParserTests.cs | 153 +- .../BinaryPropertyListWriterTests.cs | 114 +- plist-cil.test/IssueTest.cs | 271 ++- plist-cil.test/NSArrayTests.cs | 125 +- plist-cil.test/NSDateTests.cs | 33 +- plist-cil.test/NSNumberTests.cs | 954 ++++++----- plist-cil.test/NSStringTests.cs | 41 +- plist-cil.test/ParseTest.cs | 608 +++---- plist-cil.test/PropertyListParserTests.cs | 21 +- plist-cil.test/UIDTests.cs | 209 +-- plist-cil.test/UseCultureAttribute.cs | 12 +- plist-cil.test/ValidatingStream.cs | 136 +- plist-cil.test/ValuePreprocessorTests.cs | 217 ++- plist-cil.test/plist-cil.test.csproj | 8 +- plist-cil.test/test-files/ResourceRules.plist | 4 +- plist-cil.test/test-files/Roundtrip.plist | 156 +- .../test-files/RoundtripBinary.plist | 14 +- .../RoundtripBinaryIndentation.plist | 20 +- plist-cil.test/test-files/RoundtripReal.plist | 6 +- plist-cil/ASCIIPropertyListParser.cs | 1471 ++++++++--------- plist-cil/BinaryPropertyListParser.cs | 1000 +++++------ ...rtyListWriter.AddObjectEqualityComparer.cs | 41 +- ...rtyListWriter.GetObjectEqualityComparer.cs | 72 +- plist-cil/BinaryPropertyListWriter.cs | 768 +++++---- plist-cil/NSArray.IList.cs | 101 +- plist-cil/NSArray.cs | 583 ++++--- plist-cil/NSData.cs | 299 ++-- plist-cil/NSDate.cs | 286 ++-- plist-cil/NSDictionary.cs | 1018 ++++++------ plist-cil/NSNumber.cs | 949 ++++++----- plist-cil/NSObject.cs | 769 +++++---- plist-cil/NSSet.cs | 643 ++++--- plist-cil/NSString.cs | 463 +++--- plist-cil/PropertyListException.cs | 37 +- plist-cil/PropertyListFormatException.cs | 25 +- plist-cil/PropertyListParser.cs | 686 ++++---- plist-cil/UID.cs | 353 ++-- plist-cil/ValuePreprocessor.cs | 260 +-- plist-cil/XmlPropertyListParser.cs | 382 +++-- plist-cil/plist-cil.csproj | 6 +- test-files/issue21.plist | 6 +- test-files/issue22-emoji-xml.plist | 12 +- test-files/issue30.plist | 10 +- test-files/issue4.plist | 12 +- test-files/test-ascii-utf8.plist | 6 +- test-files/test-ascii.plist | 6 +- test-files/test1-ascii-gnustep.plist | 14 +- test-files/test1-ascii.plist | 14 +- test-files/test1.plist | 38 +- test-files/testNegative-bin.plist | Bin 167 -> 225 bytes test-files/testNegative-xml.plist | 32 +- version.json | 12 +- 59 files changed, 6772 insertions(+), 6828 deletions(-) diff --git a/README.md b/README.md index 993d5cc..f06a7b7 100644 --- a/README.md +++ b/README.md @@ -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. 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 - * Read / write property lists from / to files, streams or byte arrays - * Convert between property lists formats - * Property list contents are provided as objects from the NeXTSTEP environment (NSDictionary, NSArray, NSString, etc.) - * Serialize native .NET data structures to property list objects - * Deserialize from property list objects to native .NET data structures +* Read / write property lists from / to files, streams or byte arrays +* Convert between property lists formats +* Property list contents are provided as objects from the NeXTSTEP environment (NSDictionary, NSArray, NSString, etc.) +* Serialize native .NET data structures to property list objects +* Deserialize from property list objects to native .NET data structures ## Supported formats @@ -24,6 +25,7 @@ They originate from the NeXTSTEP programming environment and are now a basic par * Cocoa / NeXTSTEP / GNUstep ASCII ## Requirements + plist-cil targets: - .NET Framework 4.5, @@ -32,16 +34,20 @@ plist-cil targets: - .NET Core 3.1. - .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 The latest releases can be downloaded [here](https://github.com/claunia/plist-cil/releases). ## 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 + 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). @@ -50,28 +56,39 @@ When you encounter a bug please report it by on the [issue tracker](https://gith ### Reading -Parsing can be done with the PropertyListParser class. You can feed the `PropertyListParser` with a `FileInfo`, a `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`. +Parsing can be done with the PropertyListParser class. You can feed the `PropertyListParser` with a `FileInfo`, a +`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._ -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 -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`) 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`) 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 -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 diff --git a/codealike.json b/codealike.json index 147c100..2eac2dd 100644 --- a/codealike.json +++ b/codealike.json @@ -1 +1 @@ -{"projectId":"c0f6bf32-f9bb-44e9-8988-33fb7a11f847","projectName":"plist-cil"} \ No newline at end of file +{"projectId": "c0f6bf32-f9bb-44e9-8988-33fb7a11f847", "projectName": "plist-cil"} \ No newline at end of file diff --git a/plist-cil.benchmark/BinaryPropertyListParserBenchmarks.cs b/plist-cil.benchmark/BinaryPropertyListParserBenchmarks.cs index 008b73d..b8839a3 100644 --- a/plist-cil.benchmark/BinaryPropertyListParserBenchmarks.cs +++ b/plist-cil.benchmark/BinaryPropertyListParserBenchmarks.cs @@ -2,22 +2,22 @@ using BenchmarkDotNet.Attributes; 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; + + [GlobalSetup] + public void Setup() => data = File.ReadAllBytes("plist.bin"); + + [Benchmark] + public NSObject ReadLargePropertylistTest() { - byte[] data; + NSObject nsObject = PropertyListParser.Parse(data); - [GlobalSetup] - public void Setup() => data = File.ReadAllBytes("plist.bin"); - - [Benchmark] - public NSObject ReadLargePropertylistTest() - { - NSObject nsObject = PropertyListParser.Parse(data); - - return nsObject; - } + return nsObject; } } \ No newline at end of file diff --git a/plist-cil.benchmark/BinaryPropertyListWriterBenchmarks.cs b/plist-cil.benchmark/BinaryPropertyListWriterBenchmarks.cs index 6b0e3b3..a8b5f3a 100644 --- a/plist-cil.benchmark/BinaryPropertyListWriterBenchmarks.cs +++ b/plist-cil.benchmark/BinaryPropertyListWriterBenchmarks.cs @@ -1,17 +1,17 @@ using BenchmarkDotNet.Attributes; 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] - public void Setup() => data = PropertyListParser.Parse("plist.bin"); + [GlobalSetup] + public void Setup() => data = PropertyListParser.Parse("plist.bin"); - [Benchmark] - public byte[] WriteLargePropertylistTest() => BinaryPropertyListWriter.WriteToArray(data); - } + [Benchmark] + public byte[] WriteLargePropertylistTest() => BinaryPropertyListWriter.WriteToArray(data); } \ No newline at end of file diff --git a/plist-cil.benchmark/Program.cs b/plist-cil.benchmark/Program.cs index 4728f8f..c4c1bf4 100644 --- a/plist-cil.benchmark/Program.cs +++ b/plist-cil.benchmark/Program.cs @@ -1,13 +1,12 @@ 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(); - BenchmarkRunner.Run(); - } + BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } } \ No newline at end of file diff --git a/plist-cil.benchmark/plist-cil.benchmark.csproj b/plist-cil.benchmark/plist-cil.benchmark.csproj index b54d894..4c636d0 100644 --- a/plist-cil.benchmark/plist-cil.benchmark.csproj +++ b/plist-cil.benchmark/plist-cil.benchmark.csproj @@ -7,11 +7,11 @@ - + - + diff --git a/plist-cil.sln.DotSettings b/plist-cil.sln.DotSettings index e3c9757..271353a 100644 --- a/plist-cil.sln.DotSettings +++ b/plist-cil.sln.DotSettings @@ -1,3 +1,5 @@ - + True - True \ No newline at end of file + True \ No newline at end of file diff --git a/plist-cil.test/BinaryPropertyListParserTests.cs b/plist-cil.test/BinaryPropertyListParserTests.cs index b1a48cc..8dd6fa1 100644 --- a/plist-cil.test/BinaryPropertyListParserTests.cs +++ b/plist-cil.test/BinaryPropertyListParserTests.cs @@ -1,68 +1,99 @@ using Claunia.PropertyList; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +public class BinaryPropertyListParserTests { - public class BinaryPropertyListParserTests - { - [Theory, InlineData(new byte[] - { - 0x08 - }, 0x08), InlineData(new byte[] - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 - }, 7), InlineData(new byte[] - { - 0x00, 0x0e, 0x47, 0x7b - }, 0x00000000000e477b)] - public void ParseUnsignedIntTest(byte[] binaryValue, int expectedValue) => - Assert.Equal(expectedValue, BinaryPropertyListParser.ParseUnsignedInt(binaryValue)); + [Theory] + [InlineData(new byte[] + { + 0x08 + }, + 0x08)] + [InlineData(new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 + }, + 7)] + [InlineData(new byte[] + { + 0x00, 0x0e, 0x47, 0x7b + }, + 0x00000000000e477b)] + public void ParseUnsignedIntTest(byte[] binaryValue, int expectedValue) => + Assert.Equal(expectedValue, BinaryPropertyListParser.ParseUnsignedInt(binaryValue)); - [Theory, InlineData(new byte[] - { - 0x57 - }, 0x57), InlineData(new byte[] - { - 0x12, 0x34 - }, 0x1234), InlineData(new byte[] - { - 0x12, 0x34, 0x56 - }, 0x123456), InlineData(new byte[] - { - 0x40, 0x2d, 0xf8, 0x4d - }, 0x402df84d), InlineData(new byte[] - { - 0x12, 0x34, 0x56, 0x78, 0x9a - }, 0x123456789a), InlineData(new byte[] - { - 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc - }, 0x123456789abc), InlineData(new byte[] - { - 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde - }, 0x123456789abcde), InlineData(new byte[] - { - 0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00 - }, 0x41b483982a000000), InlineData(new byte[] - { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19 - }, unchecked((long)0xfffffffffffffc19)), InlineData(new byte[] - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19 - }, unchecked((long)0xfffffffffffffc19))] - public void ParseLongTest(byte[] binaryValue, long expectedValue) => - Assert.Equal(expectedValue, BinaryPropertyListParser.ParseLong(binaryValue)); + [Theory] + [InlineData(new byte[] + { + 0x57 + }, + 0x57)] + [InlineData(new byte[] + { + 0x12, 0x34 + }, + 0x1234)] + [InlineData(new byte[] + { + 0x12, 0x34, 0x56 + }, + 0x123456)] + [InlineData(new byte[] + { + 0x40, 0x2d, 0xf8, 0x4d + }, + 0x402df84d)] + [InlineData(new byte[] + { + 0x12, 0x34, 0x56, 0x78, 0x9a + }, + 0x123456789a)] + [InlineData(new byte[] + { + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc + }, + 0x123456789abc)] + [InlineData(new byte[] + { + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde + }, + 0x123456789abcde)] + [InlineData(new byte[] + { + 0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00 + }, + 0x41b483982a000000)] + [InlineData(new byte[] + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19 + }, + unchecked((long)0xfffffffffffffc19))] + [InlineData(new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19 + }, + unchecked((long)0xfffffffffffffc19))] + public void ParseLongTest(byte[] binaryValue, long expectedValue) => + Assert.Equal(expectedValue, BinaryPropertyListParser.ParseLong(binaryValue)); - [Theory, InlineData(new byte[] - { - 0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00 - }, 344168490), InlineData(new byte[] - { - 0x40, 0x09, 0x21, 0xf9, 0xf0, 0x1b, 0x86, 0x6e - }, 3.14159), InlineData(new byte[] - { - 0x40, 0x2d, 0xf8, 0x4d - }, 2.71828007698059)] - public void ParseDoubleTest(byte[] binaryValue, double expectedValue) => - Assert.Equal(expectedValue, BinaryPropertyListParser.ParseDouble(binaryValue), 14); - } + [Theory] + [InlineData(new byte[] + { + 0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00 + }, + 344168490)] + [InlineData(new byte[] + { + 0x40, 0x09, 0x21, 0xf9, 0xf0, 0x1b, 0x86, 0x6e + }, + 3.14159)] + [InlineData(new byte[] + { + 0x40, 0x2d, 0xf8, 0x4d + }, + 2.71828007698059)] + public void ParseDoubleTest(byte[] binaryValue, double expectedValue) => + Assert.Equal(expectedValue, BinaryPropertyListParser.ParseDouble(binaryValue), 14); } \ No newline at end of file diff --git a/plist-cil.test/BinaryPropertyListWriterTests.cs b/plist-cil.test/BinaryPropertyListWriterTests.cs index a144fe5..713a337 100644 --- a/plist-cil.test/BinaryPropertyListWriterTests.cs +++ b/plist-cil.test/BinaryPropertyListWriterTests.cs @@ -2,85 +2,87 @@ using Claunia.PropertyList; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +public class BinaryPropertyListWriterTests { - public class BinaryPropertyListWriterTests + [Fact] + public void Roundtrip2Test() { - [Fact] - public void Roundtrip2Test() + byte[] data = File.ReadAllBytes("test-files/plist2.bin"); + NSObject root = PropertyListParser.Parse(data); + + using var actualOutput = new MemoryStream(); + + using Stream expectedOutput = File.OpenRead("test-files/plist2.bin"); + + using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); + + var writer = new BinaryPropertyListWriter(validatingStream) { - byte[] data = File.ReadAllBytes("test-files/plist2.bin"); - NSObject root = PropertyListParser.Parse(data); + ReuseObjectIds = false + }; - using var actualOutput = new MemoryStream(); + writer.Write(root); + } - using Stream expectedOutput = File.OpenRead("test-files/plist2.bin"); + [Fact] + public void Roundtrip3Test() + { + byte[] data = File.ReadAllBytes("test-files/plist3.bin"); + NSObject root = PropertyListParser.Parse(data); - using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); + using var actualOutput = new MemoryStream(); - var writer = new BinaryPropertyListWriter(validatingStream) - { - ReuseObjectIds = false - }; + using Stream expectedOutput = File.OpenRead("test-files/plist3.bin"); - writer.Write(root); - } + using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); - [Fact] - public void Roundtrip3Test() + var writer = new BinaryPropertyListWriter(validatingStream) { - byte[] data = File.ReadAllBytes("test-files/plist3.bin"); - NSObject root = PropertyListParser.Parse(data); + ReuseObjectIds = false + }; - using var actualOutput = new MemoryStream(); + writer.Write(root); + } - using Stream expectedOutput = File.OpenRead("test-files/plist3.bin"); + [Fact] + public void Roundtrip4Test() + { + byte[] data = File.ReadAllBytes("test-files/plist4.bin"); + NSObject root = PropertyListParser.Parse(data); - using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); + using var actualOutput = new MemoryStream(); - var writer = new BinaryPropertyListWriter(validatingStream); - writer.ReuseObjectIds = false; - writer.Write(root); - } + using Stream expectedOutput = File.OpenRead("test-files/plist4.bin"); - [Fact] - public void Roundtrip4Test() + using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); + + var writer = new BinaryPropertyListWriter(validatingStream) { - byte[] data = File.ReadAllBytes("test-files/plist4.bin"); - NSObject root = PropertyListParser.Parse(data); + ReuseObjectIds = false + }; - using var actualOutput = new MemoryStream(); + writer.Write(root); + } - using Stream expectedOutput = File.OpenRead("test-files/plist4.bin"); + [Fact] + public void RoundtripTest() + { + byte[] data = File.ReadAllBytes("test-files/plist.bin"); + NSObject root = PropertyListParser.Parse(data); - using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); + using var actualOutput = new MemoryStream(); - var writer = new BinaryPropertyListWriter(validatingStream) - { - ReuseObjectIds = false - }; + using Stream expectedOutput = File.OpenRead("test-files/plist.bin"); - writer.Write(root); - } + using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); - [Fact] - public void RoundtripTest() + var writer = new BinaryPropertyListWriter(validatingStream) { - byte[] data = File.ReadAllBytes("test-files/plist.bin"); - NSObject root = PropertyListParser.Parse(data); + ReuseObjectIds = false + }; - using var actualOutput = new MemoryStream(); - - using Stream expectedOutput = File.OpenRead("test-files/plist.bin"); - - using var validatingStream = new ValidatingStream(actualOutput, expectedOutput); - - var writer = new BinaryPropertyListWriter(validatingStream) - { - ReuseObjectIds = false - }; - - writer.Write(root); - } + writer.Write(root); } } \ No newline at end of file diff --git a/plist-cil.test/IssueTest.cs b/plist-cil.test/IssueTest.cs index 0ff1678..4b7a603 100644 --- a/plist-cil.test/IssueTest.cs +++ b/plist-cil.test/IssueTest.cs @@ -27,166 +27,165 @@ using System.IO; using Claunia.PropertyList; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +public static class IssueTest { - public static class IssueTest + /// + /// 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). + /// + [Fact] + public static void RoundtripDataTest() { - /// - /// 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). - /// - [Fact] - public static void RoundtripDataTest() - { - string expected = File.ReadAllText(@"test-files/RoundtripBinary.plist"); - NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinary.plist")); - string actual = value.ToXmlPropertyList(); + string expected = File.ReadAllText(@"test-files/RoundtripBinary.plist"); + NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinary.plist")); + string actual = value.ToXmlPropertyList(); - Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); - } + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + } - /// - /// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario where the binary - /// data is indented. - /// - [Fact] - public static void RoundtripDataTest2() - { - string expected = File.ReadAllText(@"test-files/RoundtripBinaryIndentation.plist"); - NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinaryIndentation.plist")); - string actual = value.ToXmlPropertyList(); + /// + /// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario where the binary + /// data is indented. + /// + [Fact] + public static void RoundtripDataTest2() + { + string expected = File.ReadAllText(@"test-files/RoundtripBinaryIndentation.plist"); + NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinaryIndentation.plist")); + string actual = value.ToXmlPropertyList(); - Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); - } + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + } - [Fact] - public static void RoundtripRealTest() - { - string expected = File.ReadAllText(@"test-files/RoundtripReal.plist"); - NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripReal.plist")); - string actual = value.ToXmlPropertyList(); + [Fact] + public static void RoundtripRealTest() + { + string expected = File.ReadAllText(@"test-files/RoundtripReal.plist"); + NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripReal.plist")); + string actual = value.ToXmlPropertyList(); - Assert.Equal(expected, actual, false, true); - } + Assert.Equal(expected, actual, false, true); + } - [Fact] - public static void RoundtripTest() - { - string expected = File.ReadAllText(@"test-files/Roundtrip.plist"); - NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/Roundtrip.plist")); - string actual = value.ToXmlPropertyList(); + [Fact] + public static void RoundtripTest() + { + string expected = File.ReadAllText(@"test-files/Roundtrip.plist"); + NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/Roundtrip.plist")); + string actual = value.ToXmlPropertyList(); - Assert.Equal(expected, actual, false, true); - } + Assert.Equal(expected, actual, false, true); + } - [Fact] - public static void TestIssue16() - { - float x = ((NSNumber)PropertyListParser.Parse(new FileInfo("test-files/issue16.plist"))).floatValue(); - Assert.True(x == (float)2.71828); - } + [Fact] + public static void TestIssue16() + { + float x = ((NSNumber)PropertyListParser.Parse(new FileInfo("test-files/issue16.plist"))).floatValue(); + Assert.True(x == (float)2.71828); + } - [Fact] - public static void TestIssue18() - { - var x = new NSNumber(-999); - PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testIssue18.plist")); - NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testIssue18.plist")); - Assert.True(x.Equals(y)); - } + [Fact] + public static void TestIssue18() + { + var x = new NSNumber(-999); + PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testIssue18.plist")); + NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testIssue18.plist")); + Assert.True(x.Equals(y)); + } - [Fact] - public static void TestIssue21() - { - string x = ((NSString)PropertyListParser.Parse(new FileInfo("test-files/issue21.plist"))).ToString(); - Assert.Equal("Lot&s of &persand&s and other escapable \"\'<>€ characters", x); - } + [Fact] + public static void TestIssue21() + { + string x = ((NSString)PropertyListParser.Parse(new FileInfo("test-files/issue21.plist"))).ToString(); + Assert.Equal("Lot&s of &persand&s and other escapable \"\'<>€ characters", x); + } - [Fact] - public static void TestIssue22() - { - var x1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji.plist")); - var x2 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji-xml.plist")); - PropertyListParser.SaveAsBinary(x1, new FileInfo("test-files/out-testIssue22.plist")); - var y1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22.plist")); - PropertyListParser.SaveAsXml(x2, new FileInfo("test-files/out-testIssue22-xml.plist")); - var y2 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22-xml.plist")); - Assert.True(x1.Equals(x2)); - Assert.True(x1.Equals(y1)); - Assert.True(x1.Equals(y2)); - Assert.True(x2.Equals(y1)); - Assert.True(x2.Equals(y2)); + [Fact] + public static void TestIssue22() + { + var x1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji.plist")); + var x2 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji-xml.plist")); + PropertyListParser.SaveAsBinary(x1, new FileInfo("test-files/out-testIssue22.plist")); + var y1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22.plist")); + PropertyListParser.SaveAsXml(x2, new FileInfo("test-files/out-testIssue22-xml.plist")); + var y2 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22-xml.plist")); + Assert.True(x1.Equals(x2)); + Assert.True(x1.Equals(y1)); + Assert.True(x1.Equals(y2)); + Assert.True(x2.Equals(y1)); + Assert.True(x2.Equals(y2)); - string emojiString = "Test Test, \uD83D\uDE30\u2754\uD83D\uDC4D\uD83D\uDC4E\uD83D\uDD25"; + string emojiString = "Test Test, \uD83D\uDE30\u2754\uD83D\uDC4D\uD83D\uDC4E\uD83D\uDD25"; - Assert.Equal(emojiString, x1.ObjectForKey("emojiString").ToString()); - Assert.Equal(emojiString, x2.ObjectForKey("emojiString").ToString()); - Assert.Equal(emojiString, y1.ObjectForKey("emojiString").ToString()); - Assert.Equal(emojiString, y2.ObjectForKey("emojiString").ToString()); - } + Assert.Equal(emojiString, x1.ObjectForKey("emojiString").ToString()); + Assert.Equal(emojiString, x2.ObjectForKey("emojiString").ToString()); + Assert.Equal(emojiString, y1.ObjectForKey("emojiString").ToString()); + Assert.Equal(emojiString, y2.ObjectForKey("emojiString").ToString()); + } - [Fact(Skip = "Support for property lists with a root element which is not plist is not implemented")] - public static void TestIssue30() - { - #pragma warning disable 219 - var arr = (NSArray)PropertyListParser.Parse(new FileInfo("test-files/issue30.plist")); - #pragma warning restore 219 - } + [Fact(Skip = "Support for property lists with a root element which is not plist is not implemented")] + public static void TestIssue30() + { +#pragma warning disable 219 + var arr = (NSArray)PropertyListParser.Parse(new FileInfo("test-files/issue30.plist")); +#pragma warning restore 219 + } - [Fact] - public static void TestIssue33() - { - #pragma warning disable 219 - var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj")); - #pragma warning restore 219 - } + [Fact] + public static void TestIssue33() + { +#pragma warning disable 219 + var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj")); +#pragma warning restore 219 + } - [Fact] - public static void TestIssue38() - { - var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj")); + [Fact] + public static void TestIssue38() + { + var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj")); - NSObject fileRef = - ((NSDictionary)((NSDictionary)dict.Get("objects")).Get("65541A9C16D13B8C00A968D5")).Get("fileRef"); + NSObject fileRef = + ((NSDictionary)((NSDictionary)dict.Get("objects")).Get("65541A9C16D13B8C00A968D5")).Get("fileRef"); - Assert.True(fileRef.Equals(new NSString("65541A9B16D13B8C00A968D5"))); - } + Assert.True(fileRef.Equals(new NSString("65541A9B16D13B8C00A968D5"))); + } - [Fact] - public static void TestIssue4() - { - var d = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue4.plist")); - Assert.Equal("Kid\u2019s iPhone", ((NSString)d.ObjectForKey("Device Name")).ToString()); - } + [Fact] + public static void TestIssue4() + { + var d = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue4.plist")); + Assert.Equal("Kid\u2019s iPhone", ((NSString)d.ObjectForKey("Device Name")).ToString()); + } - [Fact] - public static void TestIssue49() - { - var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue49.plist")); - Assert.Empty(dict); - } + [Fact] + public static void TestIssue49() + { + var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue49.plist")); + Assert.Empty(dict); + } - [Fact] - public static void TestIssue7() - { - // also a test for issue 12 - // the issue4 test has a UTF-16-BE string in its binary representation - NSObject x = PropertyListParser.Parse(new FileInfo("test-files/issue4.plist")); - PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testIssue7.plist")); - NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testIssue7.plist")); - Assert.True(x.Equals(y)); - } + [Fact] + public static void TestIssue7() + { + // also a test for issue 12 + // the issue4 test has a UTF-16-BE string in its binary representation + NSObject x = PropertyListParser.Parse(new FileInfo("test-files/issue4.plist")); + PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testIssue7.plist")); + NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testIssue7.plist")); + Assert.True(x.Equals(y)); + } - [Fact] - public static void TestRealInResourceRule() - { - var dict = (NSDictionary)XmlPropertyListParser.Parse(new FileInfo("test-files/ResourceRules.plist")); - Assert.Single(dict); - Assert.True(dict.ContainsKey("weight")); + [Fact] + public static void TestRealInResourceRule() + { + var dict = (NSDictionary)XmlPropertyListParser.Parse(new FileInfo("test-files/ResourceRules.plist")); + Assert.Single(dict); + Assert.True(dict.ContainsKey("weight")); - object weight = dict["weight"].ToObject(); - Assert.IsType(weight); - Assert.Equal(10d, (double)weight); - } + object weight = dict["weight"].ToObject(); + Assert.IsType(weight); + Assert.Equal(10d, (double)weight); } } \ No newline at end of file diff --git a/plist-cil.test/NSArrayTests.cs b/plist-cil.test/NSArrayTests.cs index 308a60d..4453b6d 100644 --- a/plist-cil.test/NSArrayTests.cs +++ b/plist-cil.test/NSArrayTests.cs @@ -2,90 +2,89 @@ using Claunia.PropertyList; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +public class NSArrayTests { - public class NSArrayTests + /// Tests the addition of a .NET object to the NSArray + [Fact] + public void AddAndContainsObjectTest() { - /// Tests the addition of a .NET object to the NSArray - [Fact] - public void AddAndContainsObjectTest() + var array = new NSArray { - var array = new NSArray - { - 1 - }; + 1 + }; - Assert.True(array.Contains(1)); - Assert.False(array.Contains(2)); - } + Assert.True(array.Contains(1)); + Assert.False(array.Contains(2)); + } - /// Tests the method. - [Fact] - public void EnumeratorTest() + /// Tests the method. + [Fact] + public void EnumeratorTest() + { + var array = new NSArray { - var array = new NSArray - { - 0, - 1 - }; + 0, + 1 + }; - using IEnumerator enumerator = array.GetEnumerator(); + using IEnumerator enumerator = array.GetEnumerator(); - Assert.Null(enumerator.Current); + Assert.Null(enumerator.Current); - Assert.True(enumerator.MoveNext()); - Assert.Equal(new NSNumber(0), enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal(new NSNumber(0), enumerator.Current); - Assert.True(enumerator.MoveNext()); - Assert.Equal(new NSNumber(1), enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal(new NSNumber(1), enumerator.Current); - Assert.False(enumerator.MoveNext()); - } + Assert.False(enumerator.MoveNext()); + } - /// Tests the method for .NET objects. - [Fact] - public void IndexOfTest() + /// Tests the method for .NET objects. + [Fact] + public void IndexOfTest() + { + var array = new NSArray { - var array = new NSArray - { - 1, - "test" - }; + 1, + "test" + }; - Assert.Equal(0, array.IndexOf(1)); - Assert.Equal(1, array.IndexOf("test")); - } + Assert.Equal(0, array.IndexOf(1)); + Assert.Equal(1, array.IndexOf("test")); + } - /// Tests the method for a .NET object. - [Fact] - public void InsertTest() + /// Tests the method for a .NET object. + [Fact] + public void InsertTest() + { + var array = new NSArray { - var array = new NSArray - { - 0, - 1, - 2 - }; + 0, + 1, + 2 + }; - array.Insert(1, "test"); + array.Insert(1, "test"); - Assert.Equal(4, array.Count); - Assert.Equal("test", array[1].ToObject()); - } + Assert.Equal(4, array.Count); + Assert.Equal("test", array[1].ToObject()); + } - /// Tests the method for a .NET object. - [Fact] - public void RemoveTest() + /// Tests the method for a .NET object. + [Fact] + public void RemoveTest() + { + var array = new NSArray { - var array = new NSArray - { - 0 - }; + 0 + }; - Assert.False(array.Remove((object)1)); - Assert.True(array.Remove((object)0)); + Assert.False(array.Remove((object)1)); + Assert.True(array.Remove((object)0)); - Assert.Empty(array); - } + Assert.Empty(array); } } \ No newline at end of file diff --git a/plist-cil.test/NSDateTests.cs b/plist-cil.test/NSDateTests.cs index 72f3a62..f135702 100644 --- a/plist-cil.test/NSDateTests.cs +++ b/plist-cil.test/NSDateTests.cs @@ -2,26 +2,25 @@ using Claunia.PropertyList; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +public class NSDateTests { - public class NSDateTests + [Fact] + public static void ConstructorTest() { - [Fact] - public static void ConstructorTest() - { - var actual = new NSDate("2000-01-01T00:00:00Z"); - var expected = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); - Assert.Equal(expected, actual.Date.ToUniversalTime()); - } + var actual = new NSDate("2000-01-01T00:00:00Z"); + var expected = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); + Assert.Equal(expected, actual.Date.ToUniversalTime()); + } - [Fact] - public static void MakeDateStringTest() - { - var date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); - string expected = "2000-01-01T00:00:00Z"; - string actual = NSDate.MakeDateString(date); + [Fact] + public static void MakeDateStringTest() + { + var date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); + string expected = "2000-01-01T00:00:00Z"; + string actual = NSDate.MakeDateString(date); - Assert.Equal(expected, actual); - } + Assert.Equal(expected, actual); } } \ No newline at end of file diff --git a/plist-cil.test/NSNumberTests.cs b/plist-cil.test/NSNumberTests.cs index 03a06a4..281c438 100644 --- a/plist-cil.test/NSNumberTests.cs +++ b/plist-cil.test/NSNumberTests.cs @@ -3,521 +3,513 @@ using System.Collections.Generic; using Claunia.PropertyList; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +public class NSNumberTests { - public class NSNumberTests + public static IEnumerable SpanConstructorTestData() => new List { - public static IEnumerable SpanConstructorTestData() => new List + // INTEGER values + // 0 + new object[] { - // INTEGER values - // 0 - new object[] + new byte[] { - new byte[] - { - 0x00 - }, - NSNumber.INTEGER, false, 0, 0.0 + 0x00 }, + NSNumber.INTEGER, false, 0, 0.0 + }, - // 1-byte value < sbyte.maxValue - new object[] - { - new byte[] - { - 0x10 - }, - NSNumber.INTEGER, true, 16, 16.0 - }, - - // 1-byte value > sbyte.MaxValue - new object[] - { - new byte[] - { - 0xFF - }, - NSNumber.INTEGER, true, byte.MaxValue, (double)byte.MaxValue - }, - - // 2-byte value < short.maxValue - new object[] - { - new byte[] - { - 0x10, 0x00 - }, - NSNumber.INTEGER, true, 4096, 4096.0 - }, - - // 2-byte value > short.maxValue - new object[] - { - new byte[] - { - 0xFF, 0xFF - }, - NSNumber.INTEGER, true, ushort.MaxValue, (double)ushort.MaxValue - }, - - // 4-byte value < int.maxValue - new object[] - { - new byte[] - { - 0x10, 0x00, 0x00, 0x00 - }, - NSNumber.INTEGER, true, 0x10000000, 1.0 * 0x10000000 - }, - - // 4-bit value > int.MaxValue - new object[] - { - new byte[] - { - 0xFF, 0xFF, 0xFF, 0xFF - }, - NSNumber.INTEGER, true, uint.MaxValue, (double)uint.MaxValue - }, - - // 64-bit value < long.MaxValue - new object[] - { - new byte[] - { - 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, - NSNumber.INTEGER, true, 0x1000000000000000, 1.0 * 0x1000000000000000 - }, - - // 64-bit value > long.MaxValue - new object[] - { - new byte[] - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - }, - NSNumber.INTEGER, true, -1, -1.0 - }, - - // 128-bit positive value - new object[] - { - new byte[] - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x00 - }, - NSNumber.INTEGER, true, unchecked((long)0xffffffffffffa000), 1.0 * unchecked((long)0xffffffffffffa000) - }, - - // 128-bit negative value - new object[] - { - new byte[] - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff - }, - NSNumber.INTEGER, true, -1, -1.0 - }, - - // REAL values - // 4-byte value (float) - new object[] - { - new byte[] - { - 0x00, 0x00, 0x00, 0x00 - }, - NSNumber.REAL, false, 0, 0.0 - }, - - new object[] - { - new byte[] - { - 0x41, 0x20, 0x00, 0x00 - }, - NSNumber.REAL, true, 10, 10.0 - }, - - new object[] - { - new byte[] - { - 0x3d, 0xcc, 0xcc, 0xcd - }, - NSNumber.REAL, false, 0, 0.1 - }, - - // 8-byte value (double) - new object[] - { - new byte[] - { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, - NSNumber.REAL, false, 0, 0.0 - }, - - new object[] - { - new byte[] - { - 0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, - NSNumber.REAL, true, 10, 10.0 - }, - - new object[] - { - new byte[] - { - 0x3f, 0xb9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a - }, - NSNumber.REAL, false, 0, 0.1 - } - }; - - [Theory, MemberData(nameof(SpanConstructorTestData))] - public void SpanConstructorTest(byte[] data, int type, bool boolValue, long longValue, double doubleValue) + // 1-byte value < sbyte.maxValue + new object[] { - var number = new NSNumber((Span)data, type); - Assert.Equal(boolValue, number.ToBool()); - Assert.Equal(longValue, number.ToLong()); - Assert.Equal(doubleValue, number.ToDouble(), 5); + new byte[] + { + 0x10 + }, + NSNumber.INTEGER, true, 16, 16.0 + }, + + // 1-byte value > sbyte.MaxValue + new object[] + { + new byte[] + { + 0xFF + }, + NSNumber.INTEGER, true, byte.MaxValue, (double)byte.MaxValue + }, + + // 2-byte value < short.maxValue + new object[] + { + new byte[] + { + 0x10, 0x00 + }, + NSNumber.INTEGER, true, 4096, 4096.0 + }, + + // 2-byte value > short.maxValue + new object[] + { + new byte[] + { + 0xFF, 0xFF + }, + NSNumber.INTEGER, true, ushort.MaxValue, (double)ushort.MaxValue + }, + + // 4-byte value < int.maxValue + new object[] + { + new byte[] + { + 0x10, 0x00, 0x00, 0x00 + }, + NSNumber.INTEGER, true, 0x10000000, 1.0 * 0x10000000 + }, + + // 4-bit value > int.MaxValue + new object[] + { + new byte[] + { + 0xFF, 0xFF, 0xFF, 0xFF + }, + NSNumber.INTEGER, true, uint.MaxValue, (double)uint.MaxValue + }, + + // 64-bit value < long.MaxValue + new object[] + { + new byte[] + { + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, + NSNumber.INTEGER, true, 0x1000000000000000, 1.0 * 0x1000000000000000 + }, + + // 64-bit value > long.MaxValue + new object[] + { + new byte[] + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }, + NSNumber.INTEGER, true, -1, -1.0 + }, + + // 128-bit positive value + new object[] + { + new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x00 + }, + NSNumber.INTEGER, true, unchecked((long)0xffffffffffffa000), 1.0 * unchecked((long)0xffffffffffffa000) + }, + + // 128-bit negative value + new object[] + { + new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }, + NSNumber.INTEGER, true, -1, -1.0 + }, + + // REAL values + // 4-byte value (float) + new object[] + { + "\0\0\0\0"u8.ToArray(), + NSNumber.REAL, false, 0, 0.0 + }, + new object[] + { + "A \0\0"u8.ToArray(), + NSNumber.REAL, true, 10, 10.0 + }, + new object[] + { + new byte[] + { + 0x3d, 0xcc, 0xcc, 0xcd + }, + NSNumber.REAL, false, 0, 0.1 + }, + + // 8-byte value (double) + new object[] + { + "\0\0\0\0\0\0\0\0"u8.ToArray(), + NSNumber.REAL, false, 0, 0.0 + }, + new object[] + { + "@$\0\0\0\0\0\0"u8.ToArray(), + NSNumber.REAL, true, 10, 10.0 + }, + new object[] + { + new byte[] + { + 0x3f, 0xb9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a + }, + NSNumber.REAL, false, 0, 0.1 } + }; - [Fact] - public void SpanConstructorInvalidValuesTest() - { - Assert.Throws(() => new NSNumber((Span)null, NSNumber.INTEGER)); - Assert.Throws(() => new NSNumber((Span)null, NSNumber.REAL)); + [Theory] + [MemberData(nameof(SpanConstructorTestData))] + public void SpanConstructorTest(byte[] data, int type, bool boolValue, long longValue, double doubleValue) + { + var number = new NSNumber((Span)data, type); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } - Assert.Throws(() => new NSNumber((Span)Array.Empty(), - NSNumber.INTEGER)); + [Fact] + public void SpanConstructorInvalidValuesTest() + { + Assert.Throws(() => new NSNumber((Span)null, NSNumber.INTEGER)); + Assert.Throws(() => new NSNumber((Span)null, NSNumber.REAL)); - Assert.Throws(() => new NSNumber((Span)Array.Empty(), NSNumber.REAL)); - Assert.Throws(() => new NSNumber((Span)Array.Empty(), 9)); - } + Assert.Throws(() => new NSNumber((Span)Array.Empty(), + NSNumber.INTEGER)); - [Fact] - public void StringAndTypeConstructorInvalidValuesTest() - { - Assert.Throws(() => new NSNumber((string)null, NSNumber.INTEGER)); - Assert.Throws(() => new NSNumber((string)null, NSNumber.REAL)); - Assert.Throws(() => new NSNumber("0", 9)); - } + Assert.Throws(() => new NSNumber((Span)Array.Empty(), NSNumber.REAL)); + Assert.Throws(() => new NSNumber((Span)Array.Empty(), 9)); + } - [Fact] - public static void NSNumberConstructorTest() - { - var number = new NSNumber("10032936613", NSNumber.INTEGER); - Assert.Equal(NSNumber.INTEGER, number.GetNSNumberType()); - Assert.Equal(10032936613, number.ToObject()); - } + [Fact] + public void StringAndTypeConstructorInvalidValuesTest() + { + Assert.Throws(() => new NSNumber((string)null, NSNumber.INTEGER)); + Assert.Throws(() => new NSNumber((string)null, NSNumber.REAL)); + Assert.Throws(() => new NSNumber("0", 9)); + } - [Fact] - public static void NSNumberWithDecimalTest() - { - var number = new NSNumber("1360155352.748765", NSNumber.REAL); - Assert.Equal("1360155352.748765", number.ToString()); - } + [Fact] + public static void NSNumberConstructorTest() + { + var number = new NSNumber("10032936613", NSNumber.INTEGER); + Assert.Equal(NSNumber.INTEGER, number.GetNSNumberType()); + Assert.Equal(10032936613, number.ToObject()); + } - // The tests below make sure the numbers are being parsed correctly, and do not depend on the culture info - // being set. Especially, decimal point may vary between cultures and we don't want to take a dependency on that - // The value being used comes seen in a real property list: + [Fact] + public static void NSNumberWithDecimalTest() + { + var number = new NSNumber("1360155352.748765", NSNumber.REAL); + Assert.Equal("1360155352.748765", number.ToString()); + } + + // The tests below make sure the numbers are being parsed correctly, and do not depend on the culture info + // being set. Especially, decimal point may vary between cultures and we don't want to take a dependency on that + // The value being used comes seen in a real property list: + // TimeZoneOffsetFromUTC + // 7200.000000 + [Fact] + [UseCulture("en-US")] + public static void ParseNumberEnTest() + { + var number = new NSNumber("7200.000001"); + Assert.True(number.isReal()); + Assert.Equal(7200.000001d, number.ToDouble()); + } + + [Fact] + [UseCulture("nl-BE")] + public static void ParseNumberNlTest() + { + // As seen in a real property list: // TimeZoneOffsetFromUTC // 7200.000000 - [Fact, UseCulture("en-US")] - public static void ParseNumberEnTest() + var number = new NSNumber("7200.000001"); + Assert.True(number.isReal()); + Assert.Equal(7200.000001d, number.ToDouble()); + } + + [Fact] + [UseCulture("en-US")] + public static void ParseNumberEnTest2() + { + // As seen in a real property list: + // TimeZoneOffsetFromUTC + // 7200.000000 + var number = new NSNumber("7200.000000", NSNumber.REAL); + Assert.True(number.isReal()); + Assert.Equal(7200d, number.ToDouble()); + } + + [Fact] + [UseCulture("nl-BE")] + public static void ParseNumberNlTest2() + { + // As seen in a real property list: + // TimeZoneOffsetFromUTC + // 7200.000000 + var number = new NSNumber("7200.000000", NSNumber.REAL); + Assert.True(number.isReal()); + Assert.Equal(7200d, number.ToDouble()); + } + + public static IEnumerable StringConstructorTestData() => new List + { + // Long values, formatted as hexadecimal values + new object[] { - var number = new NSNumber("7200.000001"); - Assert.True(number.isReal()); - Assert.Equal(7200.000001d, number.ToDouble()); + "0x00", false, 0, 0.0 + }, + new object[] + { + "0x1000", true, 0x1000, 1.0 * 0x1000 + }, + new object[] + { + "0x00001000", true, 0x1000, 1.0 * 0x1000 + }, + new object[] + { + "0x0000000000001000", true, 0x1000, 1.0 * 0x1000 + }, + + // Long values, formatted as decimal values + new object[] + { + "0", false, 0, 0.0 + }, + new object[] + { + "10", true, 10, 10.0 + }, + + // Decimal values + new object[] + { + "0.0", false, 0, 0.0 + }, + new object[] + { + "0.10", false, 0, 0.1 + }, + new object[] + { + "3.14", true, 3, 3.14 + }, + + // Boolean values + new object[] + { + "yes", true, 1, 1 + }, + new object[] + { + "true", true, 1, 1 + }, + new object[] + { + "Yes", true, 1, 1 + }, + new object[] + { + "True", true, 1, 1 + }, + new object[] + { + "YES", true, 1, 1 + }, + new object[] + { + "TRUE", true, 1, 1 + }, + new object[] + { + "no", false, 0, 0 + }, + new object[] + { + "false", false, 0, 0 + }, + new object[] + { + "No", false, 0, 0 + }, + new object[] + { + "False", false, 0, 0 + }, + new object[] + { + "NO", false, 0, 0 + }, + new object[] + { + "FALSE", false, 0, 0 } + }; - [Fact, UseCulture("nl-BE")] - public static void ParseNumberNlTest() + [Theory] + [MemberData(nameof(StringConstructorTestData))] + public void StringConstructorTest(string value, bool boolValue, long longValue, double doubleValue) + { + var number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } + + [Fact] + public void StringConstructorInvalidValuesTest() + { + Assert.Throws(() => new NSNumber(null)); + Assert.Throws(() => new NSNumber("plist")); + } + + public static IEnumerable Int32ConstructorTestData() => new List + { + // Long values, formatted as hexadecimal values + new object[] { - // As seen in a real property list: - // TimeZoneOffsetFromUTC - // 7200.000000 - var number = new NSNumber("7200.000001"); - Assert.True(number.isReal()); - Assert.Equal(7200.000001d, number.ToDouble()); + 0, false, 0, 0.0 + }, + new object[] + { + 1, true, 1, 1.0 + }, + new object[] + { + -1, true, -1, -1.0 + }, + new object[] + { + int.MaxValue, true, int.MaxValue, int.MaxValue + }, + new object[] + { + int.MinValue, true, int.MinValue, int.MinValue } + }; - [Fact, UseCulture("en-US")] - public static void ParseNumberEnTest2() + [Theory] + [MemberData(nameof(Int32ConstructorTestData))] + public void Int32ConstructorTest(int value, bool boolValue, long longValue, double doubleValue) + { + var number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } + + public static IEnumerable Int64ConstructorTestData() => new List + { + // Long values, formatted as hexadecimal values + new object[] { - // As seen in a real property list: - // TimeZoneOffsetFromUTC - // 7200.000000 - var number = new NSNumber("7200.000000", NSNumber.REAL); - Assert.True(number.isReal()); - Assert.Equal(7200d, number.ToDouble()); + 0, false, 0, 0.0 + }, + new object[] + { + 1, true, 1, 1.0 + }, + new object[] + { + -1, true, -1, -1.0 + }, + new object[] + { + long.MaxValue, true, long.MaxValue, long.MaxValue + }, + new object[] + { + long.MinValue, true, long.MinValue, long.MinValue } + }; - [Fact, UseCulture("nl-BE")] - public static void ParseNumberNlTest2() + [Theory] + [MemberData(nameof(Int64ConstructorTestData))] + public void Int64ConstructorTest(long value, bool boolValue, long longValue, double doubleValue) + { + var number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } + + public static IEnumerable DoubleConstructorTestData() => new List + { + // Long values, formatted as hexadecimal values + new object[] { - // As seen in a real property list: - // TimeZoneOffsetFromUTC - // 7200.000000 - var number = new NSNumber("7200.000000", NSNumber.REAL); - Assert.True(number.isReal()); - Assert.Equal(7200d, number.ToDouble()); + 0.0, false, 0, 0.0 + }, + new object[] + { + 1.0, true, 1, 1.0 + }, + new object[] + { + -1.0, true, -1, -1.0 + }, + new object[] + { + double.Epsilon, false, 0, double.Epsilon + }, + new object[] + { + double.MaxValue, true, long.MinValue /* Overflow! */, double.MaxValue + }, + new object[] + { + double.MinValue, true, long.MinValue, double.MinValue } + }; - public static IEnumerable StringConstructorTestData() => new List + [Theory] + [MemberData(nameof(DoubleConstructorTestData))] + public void DoubleConstructorTest(double value, bool boolValue, long longValue, double doubleValue) + { + var number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } + + public static IEnumerable BoolConstructorTestData() => new List + { + // Long values, formatted as hexadecimal values + new object[] { - // Long values, formatted as hexadecimal values - new object[] - { - "0x00", false, 0, 0.0 - }, - new object[] - { - "0x1000", true, 0x1000, 1.0 * 0x1000 - }, - new object[] - { - "0x00001000", true, 0x1000, 1.0 * 0x1000 - }, - new object[] - { - "0x0000000000001000", true, 0x1000, 1.0 * 0x1000 - }, - - // Long values, formatted as decimal values - new object[] - { - "0", false, 0, 0.0 - }, - new object[] - { - "10", true, 10, 10.0 - }, - - // Decimal values - new object[] - { - "0.0", false, 0, 0.0 - }, - new object[] - { - "0.10", false, 0, 0.1 - }, - new object[] - { - "3.14", true, 3, 3.14 - }, - - // Boolean values - new object[] - { - "yes", true, 1, 1 - }, - new object[] - { - "true", true, 1, 1 - }, - new object[] - { - "Yes", true, 1, 1 - }, - new object[] - { - "True", true, 1, 1 - }, - new object[] - { - "YES", true, 1, 1 - }, - new object[] - { - "TRUE", true, 1, 1 - }, - - new object[] - { - "no", false, 0, 0 - }, - new object[] - { - "false", false, 0, 0 - }, - new object[] - { - "No", false, 0, 0 - }, - new object[] - { - "False", false, 0, 0 - }, - new object[] - { - "NO", false, 0, 0 - }, - new object[] - { - "FALSE", false, 0, 0 - } - }; - - [Theory, MemberData(nameof(StringConstructorTestData))] - public void StringConstructorTest(string value, bool boolValue, long longValue, double doubleValue) + false, false, 0, 0.0 + }, + new object[] { - var number = new NSNumber(value); - Assert.Equal(boolValue, number.ToBool()); - Assert.Equal(longValue, number.ToLong()); - Assert.Equal(doubleValue, number.ToDouble(), 5); + true, true, 1, 1.0 } + }; - [Fact] - public void StringConstructorInvalidValuesTest() - { - Assert.Throws(() => new NSNumber(null)); - Assert.Throws(() => new NSNumber("plist")); - } + [Theory] + [MemberData(nameof(BoolConstructorTestData))] + public void BoolConstructorTest(bool value, bool boolValue, long longValue, double doubleValue) + { + var number = new NSNumber(value); + Assert.Equal(boolValue, number.ToBool()); + Assert.Equal(longValue, number.ToLong()); + Assert.Equal(doubleValue, number.ToDouble(), 5); + } - public static IEnumerable Int32ConstructorTestData() => new List - { - // Long values, formatted as hexadecimal values - new object[] - { - 0, false, 0, 0.0 - }, - new object[] - { - 1, true, 1, 1.0 - }, - new object[] - { - -1, true, -1, -1.0 - }, - new object[] - { - int.MaxValue, true, int.MaxValue, int.MaxValue - }, - new object[] - { - int.MinValue, true, int.MinValue, int.MinValue - } - }; + [Fact] + public void EqualTest() + { + var a = new NSNumber(2); + var b = new NSNumber(2); - [Theory, MemberData(nameof(Int32ConstructorTestData))] - public void Int32ConstructorTest(int value, bool boolValue, long longValue, double doubleValue) - { - var number = new NSNumber(value); - Assert.Equal(boolValue, number.ToBool()); - Assert.Equal(longValue, number.ToLong()); - Assert.Equal(doubleValue, number.ToDouble(), 5); - } - - public static IEnumerable Int64ConstructorTestData() => new List - { - // Long values, formatted as hexadecimal values - new object[] - { - 0, false, 0, 0.0 - }, - new object[] - { - 1, true, 1, 1.0 - }, - new object[] - { - -1, true, -1, -1.0 - }, - new object[] - { - long.MaxValue, true, long.MaxValue, long.MaxValue - }, - new object[] - { - long.MinValue, true, long.MinValue, long.MinValue - } - }; - - [Theory, MemberData(nameof(Int64ConstructorTestData))] - public void Int64ConstructorTest(long value, bool boolValue, long longValue, double doubleValue) - { - var number = new NSNumber(value); - Assert.Equal(boolValue, number.ToBool()); - Assert.Equal(longValue, number.ToLong()); - Assert.Equal(doubleValue, number.ToDouble(), 5); - } - - public static IEnumerable DoubleConstructorTestData() => new List - { - // Long values, formatted as hexadecimal values - new object[] - { - 0.0, false, 0, 0.0 - }, - new object[] - { - 1.0, true, 1, 1.0 - }, - new object[] - { - -1.0, true, -1, -1.0 - }, - new object[] - { - double.Epsilon, false, 0, double.Epsilon - }, - new object[] - { - double.MaxValue, true, long.MinValue /* Overflow! */, double.MaxValue - }, - new object[] - { - double.MinValue, true, long.MinValue, double.MinValue - } - }; - - [Theory, MemberData(nameof(DoubleConstructorTestData))] - public void DoubleConstructorTest(double value, bool boolValue, long longValue, double doubleValue) - { - var number = new NSNumber(value); - Assert.Equal(boolValue, number.ToBool()); - Assert.Equal(longValue, number.ToLong()); - Assert.Equal(doubleValue, number.ToDouble(), 5); - } - - public static IEnumerable BoolConstructorTestData() => new List - { - // Long values, formatted as hexadecimal values - new object[] - { - false, false, 0, 0.0 - }, - new object[] - { - true, true, 1, 1.0 - } - }; - - [Theory, MemberData(nameof(BoolConstructorTestData))] - public void BoolConstructorTest(bool value, bool boolValue, long longValue, double doubleValue) - { - var number = new NSNumber(value); - Assert.Equal(boolValue, number.ToBool()); - Assert.Equal(longValue, number.ToLong()); - Assert.Equal(doubleValue, number.ToDouble(), 5); - } - - [Fact] - public void EqualTest() - { - var a = new NSNumber(2); - var b = new NSNumber(2); - - Assert.Equal(a.GetHashCode(), b.GetHashCode()); - Assert.True(a.Equals(b)); - Assert.True(b.Equals(a)); - } + Assert.Equal(a.GetHashCode(), b.GetHashCode()); + Assert.True(a.Equals(b)); + Assert.True(b.Equals(a)); } } \ No newline at end of file diff --git a/plist-cil.test/NSStringTests.cs b/plist-cil.test/NSStringTests.cs index 380728d..bc0310c 100644 --- a/plist-cil.test/NSStringTests.cs +++ b/plist-cil.test/NSStringTests.cs @@ -1,29 +1,28 @@ using Claunia.PropertyList; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +public class NSStringTests { - public class NSStringTests + const string START_TOKEN = ""; + const string END_TOKEN = ""; + + [InlineData("abc", "abc")] + [InlineData("a>b", "a>b")] + [InlineData("ab", "a>b")] - [InlineData("a list = new(array); - - Dictionary map = new(); - map.Add("int", i); - map.Add("long", lng); - map.Add("date", date); - - List> listOfMaps = new() - { - new Dictionary - { - { "int", i }, - { "long", lng }, - { "date", date } - } - }; - - var WrappedO = NSObject.Wrap((object)bl); - Assert.True(WrappedO is (NSNumber)); - Assert.True(WrappedO.ToObject().Equals(bl)); - - WrappedO = NSObject.Wrap((object)byt); - Assert.True(WrappedO is (NSNumber)); - Assert.True((int)WrappedO.ToObject() == byt); - - WrappedO = NSObject.Wrap((object)shrt); - Assert.True(WrappedO is (NSNumber)); - Assert.True((int)WrappedO.ToObject() == shrt); - - WrappedO = NSObject.Wrap((object)i); - Assert.True(WrappedO is (NSNumber)); - Assert.True((int)WrappedO.ToObject() == i); - - WrappedO = NSObject.Wrap((object)lng); - Assert.True(WrappedO is (NSNumber)); - Assert.True((long)WrappedO.ToObject() == lng); - - WrappedO = NSObject.Wrap((object)flt); - Assert.True(WrappedO is (NSNumber)); - Assert.True((double)WrappedO.ToObject() == flt); - - WrappedO = NSObject.Wrap((object)dbl); - Assert.True(WrappedO is (NSNumber)); - Assert.True((double)WrappedO.ToObject() == dbl); - - WrappedO = NSObject.Wrap(date); - Assert.True(WrappedO is (NSDate)); - Assert.True(((DateTime)WrappedO.ToObject()).Equals(date)); - - WrappedO = NSObject.Wrap(strg); - Assert.True(WrappedO is (NSString)); - Assert.Equal((string)WrappedO.ToObject(), strg); - - WrappedO = NSObject.Wrap((object)bytes); - Assert.True(WrappedO is (NSData)); - byte[] data = (byte[])WrappedO.ToObject(); - Assert.True(data.Length == bytes.Length); - - for(int x = 0; x < bytes.Length; x++) - Assert.True(data[x] == bytes[x]); - - WrappedO = NSObject.Wrap((object)array); - Assert.True(WrappedO is (NSArray)); - object[] objArray = (object[])WrappedO.ToObject(); - Assert.True(objArray.Length == array.Length); - - WrappedO = NSObject.Wrap(array2); - Assert.True(WrappedO is (NSArray)); - Assert.True(((NSArray)WrappedO).Count == array2.Length); - - WrappedO = NSObject.Wrap((object)list); - Assert.True(WrappedO is (NSArray)); - objArray = (object[])WrappedO.ToObject(); - Assert.True(objArray.Length == array.Length); - - WrappedO = NSObject.Wrap((object)map); - Assert.True(WrappedO is (NSDictionary)); - var dict = (NSDictionary)WrappedO; - Assert.True(((NSNumber)dict.ObjectForKey("int")).ToLong() == i); - Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng); - Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date)); - - WrappedO = NSObject.Wrap((object)listOfMaps); - Assert.True(WrappedO is (NSArray)); - var arrayOfMaps = (NSArray)WrappedO; - Assert.True(arrayOfMaps.Count == 1); - var firstMap = (NSDictionary)arrayOfMaps[0]; - Assert.True(((NSNumber)firstMap.ObjectForKey("int")).ToLong() == i); - Assert.True(((NSNumber)firstMap.ObjectForKey("long")).ToLong() == lng); - Assert.True(((NSDate)firstMap.ObjectForKey("date")).Date.Equals(date)); - - // TODO - /* - Object unWrappedO = WrappedO.ToObject(); - Map map2 = (Map)unWrappedO; - Assert.True(((int)map.get("int")) == i); - Assert.True(((long)map.get("long")) == lng); - Assert.True(((DateTime)map.get("date")).Equals(date));*/ - } - - /** - * Test the xml reader/writer + /** + * NSSet only occurs in binary property lists, so we have to test it separately. + * NSSets are not yet supported in reading/writing, as binary property list format v1+ is required. */ - [Fact] - public static void TestXml() - { - // Parse an example plist file - NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist")); + /* + [Fact] + public static void TestSet() + { + NSSet s = new NSSet(); + s.AddObject(new NSNumber(1)); + s.AddObject(new NSNumber(3)); + s.AddObject(new NSNumber(2)); - // check the data in it - var d = (NSDictionary)x; - Assert.True(d.Count == 5); - Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); - Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); + NSSet orderedSet = new NSSet(true); + s.AddObject(new NSNumber(1)); + s.AddObject(new NSNumber(3)); + s.AddObject(new NSNumber(2)); - Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 10, 21, 30, - DateTimeKind.Utc)) || - ((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30, - DateTimeKind.Utc))); + NSDictionary dict = new NSDictionary(); + dict.Add("set1", s); + dict.Add("set2", orderedSet); - Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[] + PropertyListParser.SaveAsBinary(dict, new FileInfo("test-files/out-testSet.plist")); + NSObject ParsedRoot = PropertyListParser.Parse(new FileInfo("test-files/out-testSet.plist")); + Assert.True(ParsedRoot.Equals(dict)); + }*/ + [Fact] + public static void TestASCII() + { + NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii.plist")); + var d = (NSDictionary)x; + Assert.True(d.Count == 5); + Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); + Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); + + var actualDate = (NSDate)d.ObjectForKey("date"); + DateTime expectedDate = new DateTime(2011, 11, 28, 9, 21, 30, DateTimeKind.Utc).ToLocalTime(); + + Assert.Equal(actualDate.Date, expectedDate); + + Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, + [ + 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82 + ])); + + var a = (NSArray)d.ObjectForKey("array"); + Assert.True(a.Count == 4); + Assert.True(a[0].Equals(new NSString("YES"))); + Assert.True(a[1].Equals(new NSString("NO"))); + Assert.True(a[2].Equals(new NSString("87"))); + Assert.True(a[3].Equals(new NSString("3.14159"))); + } + + [Fact] + public static void testAsciiUtf8CharactersInQuotedString() + { + NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test-ascii-utf8.plist")); + var d = (NSDictionary)x; + Assert.Equal(2, d.Count); + Assert.Equal("JÔÖú@2x.jpg", d.ObjectForKey("path").ToString()); + Assert.Equal("QÔÖú@2x 啕.jpg", d.ObjectForKey("Key QÔÖª@2x 䌡").ToString()); + } + + [Fact] + public static void TestASCIIWriting() + { + var inf = new FileInfo("test-files/test1.plist"); + var outf = new FileInfo("test-files/out-test1-ascii.plist"); + var in2 = new FileInfo("test-files/test1-ascii.plist"); + var x = (NSDictionary)PropertyListParser.Parse(inf); + PropertyListParser.SaveAsASCII(x, outf); + + //Information gets lost when saving into the ASCII format (NSNumbers are converted to NSStrings) + + var y = (NSDictionary)PropertyListParser.Parse(outf); + var z = (NSDictionary)PropertyListParser.Parse(in2); + Assert.True(y.Equals(z)); + } + + /** + * Test the binary reader/writer. + */ + [Fact] + public static void TestBinary() + { + NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist")); + + // save and load as binary + PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testBinary.plist")); + NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testBinary.plist")); + Assert.True(x.Equals(y)); + } + + [Fact] + public static void TestGnuStepASCII() + { + NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii-gnustep.plist")); + var d = (NSDictionary)x; + Assert.True(d.Count == 5); + Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).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, DateTimeKind.Utc) + .ToLocalTime())); + + Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, + [ + 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82 + ])); + + var a = (NSArray)d.ObjectForKey("array"); + Assert.True(a.Count == 4); + Assert.True(a[0].Equals(new NSNumber(true))); + Assert.True(a[1].Equals(new NSNumber(false))); + Assert.True(a[2].Equals(new NSNumber(87))); + Assert.True(a[3].Equals(new NSNumber(3.14159))); + } + + [Fact] + public static void TestGnuStepASCIIWriting() + { + var inf = new FileInfo("test-files/test1.plist"); + var outf = new FileInfo("test-files/out-test1-ascii-gnustep.plist"); + var x = (NSDictionary)PropertyListParser.Parse(inf); + PropertyListParser.SaveAsGnuStepASCII(x, outf); + NSObject y = PropertyListParser.Parse(outf); + Assert.True(x.Equals(y)); + } + + [Fact] + public static void TestWrap() + { + bool bl = true; + byte byt = 24; + short shrt = 12; + int i = 42; + long lng = 30000000000L; + float flt = 124.3f; + double dbl = 32.0; + var date = new DateTime(); + string strg = "Hello World"; + + byte[] bytes = + [ + 0x00, 0xAF, 0xAF + ]; + + object[] array = + [ + bl, byt, shrt, i, lng, flt, dbl, date, strg, bytes + ]; + + int[] array2 = + [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3000 + ]; + + List list = new(array); + + Dictionary map = new(); + map.Add("int", i); + map.Add("long", lng); + map.Add("date", date); + + List> listOfMaps = + [ + new Dictionary { - 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82 - })); + { + "int", i + }, + { + "long", lng + }, + { + "date", date + } + } + ]; - var a = (NSArray)d.ObjectForKey("array"); - Assert.True(a.Count == 4); - Assert.True(a[0].Equals(new NSNumber(true))); - Assert.True(a[1].Equals(new NSNumber(false))); - Assert.True(a[2].Equals(new NSNumber(87))); - Assert.True(a[3].Equals(new NSNumber(3.14159))); + var WrappedO = NSObject.Wrap((object)bl); + Assert.True(WrappedO is (NSNumber)); + Assert.True(WrappedO.ToObject().Equals(bl)); - // read/write it, make sure we get the same thing - PropertyListParser.SaveAsXml(x, new FileInfo("test-files/out-testXml.plist")); - NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testXml.plist")); - Assert.True(x.Equals(y)); - } + WrappedO = NSObject.Wrap((object)byt); + Assert.True(WrappedO is (NSNumber)); + Assert.True((int)WrappedO.ToObject() == byt); + + WrappedO = NSObject.Wrap((object)shrt); + Assert.True(WrappedO is (NSNumber)); + Assert.True((int)WrappedO.ToObject() == shrt); + + WrappedO = NSObject.Wrap((object)i); + Assert.True(WrappedO is (NSNumber)); + Assert.True((int)WrappedO.ToObject() == i); + + WrappedO = NSObject.Wrap((object)lng); + Assert.True(WrappedO is (NSNumber)); + Assert.True((long)WrappedO.ToObject() == lng); + + WrappedO = NSObject.Wrap((object)flt); + Assert.True(WrappedO is (NSNumber)); + Assert.True((double)WrappedO.ToObject() == flt); + + WrappedO = NSObject.Wrap((object)dbl); + Assert.True(WrappedO is (NSNumber)); + Assert.True((double)WrappedO.ToObject() == dbl); + + WrappedO = NSObject.Wrap(date); + Assert.True(WrappedO is (NSDate)); + Assert.True(((DateTime)WrappedO.ToObject()).Equals(date)); + + WrappedO = NSObject.Wrap(strg); + Assert.True(WrappedO is (NSString)); + Assert.Equal((string)WrappedO.ToObject(), strg); + + WrappedO = NSObject.Wrap((object)bytes); + Assert.True(WrappedO is (NSData)); + byte[] data = (byte[])WrappedO.ToObject(); + Assert.True(data.Length == bytes.Length); + + for(int x = 0; x < bytes.Length; x++) Assert.True(data[x] == bytes[x]); + + WrappedO = NSObject.Wrap((object)array); + Assert.True(WrappedO is (NSArray)); + object[] objArray = (object[])WrappedO.ToObject(); + Assert.True(objArray.Length == array.Length); + + WrappedO = NSObject.Wrap(array2); + Assert.True(WrappedO is (NSArray)); + Assert.True(((NSArray)WrappedO).Count == array2.Length); + + WrappedO = NSObject.Wrap((object)list); + Assert.True(WrappedO is (NSArray)); + objArray = (object[])WrappedO.ToObject(); + Assert.True(objArray.Length == array.Length); + + WrappedO = NSObject.Wrap((object)map); + Assert.True(WrappedO is (NSDictionary)); + var dict = (NSDictionary)WrappedO; + Assert.True(((NSNumber)dict.ObjectForKey("int")).ToLong() == i); + Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng); + Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date)); + + WrappedO = NSObject.Wrap(listOfMaps); + Assert.True(WrappedO is (NSArray)); + var arrayOfMaps = (NSArray)WrappedO; + Assert.True(arrayOfMaps.Count == 1); + var firstMap = (NSDictionary)arrayOfMaps[0]; + Assert.True(((NSNumber)firstMap.ObjectForKey("int")).ToLong() == i); + Assert.True(((NSNumber)firstMap.ObjectForKey("long")).ToLong() == lng); + Assert.True(((NSDate)firstMap.ObjectForKey("date")).Date.Equals(date)); + + // TODO + /* + Object unWrappedO = WrappedO.ToObject(); + Map map2 = (Map)unWrappedO; + Assert.True(((int)map.get("int")) == i); + Assert.True(((long)map.get("long")) == lng); + Assert.True(((DateTime)map.get("date")).Equals(date));*/ + } + + /** + * Test the xml reader/writer + */ + [Fact] + public static void TestXml() + { + // Parse an example plist file + NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist")); + + // check the data in it + var d = (NSDictionary)x; + Assert.True(d.Count == 5); + Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).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, + DateTimeKind.Utc)) || + ((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, + 11, + 28, + 9, + 21, + 30, + DateTimeKind.Utc))); + + Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, + [ + 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82 + ])); + + var a = (NSArray)d.ObjectForKey("array"); + Assert.True(a.Count == 4); + Assert.True(a[0].Equals(new NSNumber(true))); + Assert.True(a[1].Equals(new NSNumber(false))); + Assert.True(a[2].Equals(new NSNumber(87))); + Assert.True(a[3].Equals(new NSNumber(3.14159))); + + // read/write it, make sure we get the same thing + PropertyListParser.SaveAsXml(x, new FileInfo("test-files/out-testXml.plist")); + NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testXml.plist")); + Assert.True(x.Equals(y)); } } \ No newline at end of file diff --git a/plist-cil.test/PropertyListParserTests.cs b/plist-cil.test/PropertyListParserTests.cs index 2635c15..ae462b7 100644 --- a/plist-cil.test/PropertyListParserTests.cs +++ b/plist-cil.test/PropertyListParserTests.cs @@ -2,19 +2,18 @@ using Claunia.PropertyList; 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(); - PropertyListParser.Parse(stream); - } - - [Fact] - public static void ParseEmptyStreamTest() => - Assert.Throws(ParseEmptyStreamTestDelegate); + PropertyListParser.Parse(stream); } + + [Fact] + public static void ParseEmptyStreamTest() => + Assert.Throws(ParseEmptyStreamTestDelegate); } \ No newline at end of file diff --git a/plist-cil.test/UIDTests.cs b/plist-cil.test/UIDTests.cs index c54ea2f..2d0bc4b 100644 --- a/plist-cil.test/UIDTests.cs +++ b/plist-cil.test/UIDTests.cs @@ -2,131 +2,140 @@ using Claunia.PropertyList; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +public class UIDTests { - public class UIDTests + [Theory] + [InlineData(new byte[] { - [Theory, InlineData(new byte[] - { - 0xAB - }), InlineData(new byte[] - { - 0xAB, 0xCD - }), InlineData(new byte[] - { - 0xAB, 0xCD, 0xEF, 0xFE - }), InlineData(new byte[] - { - 0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB - })] - public void UidFromArrayTest(byte[] array) - { - var uid = new UID(array); - Assert.Equal(array, uid.Bytes); - } + 0xAB + })] + [InlineData(new byte[] + { + 0xAB, 0xCD + })] + [InlineData(new byte[] + { + 0xAB, 0xCD, 0xEF, 0xFE + })] + [InlineData(new byte[] + { + 0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB + })] + public void UidFromArrayTest(byte[] array) + { + var uid = new UID(array); + Assert.Equal(array, uid.Bytes); + } - [Fact] - public void BinaryRoundTripTest() - { - var original = new UID(0xabcd); + [Fact] + public void BinaryRoundTripTest() + { + var original = new UID(0xabcd); - using var stream = new MemoryStream(); + using var stream = new MemoryStream(); - BinaryPropertyListWriter.Write(stream, original); - stream.Position = 0; - var roundtrip = BinaryPropertyListParser.Parse(stream) as UID; - Assert.Equal(original.Bytes, roundtrip.Bytes); - } + BinaryPropertyListWriter.Write(stream, original); + stream.Position = 0; + var roundtrip = BinaryPropertyListParser.Parse(stream) as UID; + Assert.Equal(original.Bytes, roundtrip.Bytes); + } - [Fact] - public void ByteUidTest() - { - var uid = new UID(0xAB); + [Fact] + public void ByteUidTest() + { + var uid = new UID(0xAB); - Assert.Equal(new byte[] - { - 0xAB - }, uid.Bytes); + Assert.Equal(new byte[] + { + 0xAB + }, + uid.Bytes); - Assert.Equal(0xABu, uid.ToUInt64()); - } + Assert.Equal(0xABu, uid.ToUInt64()); + } - [Fact] - public void IntUidTest() - { - var uid = new UID(0xABCDEF00); + [Fact] + public void IntUidTest() + { + var uid = new UID(0xABCDEF00); - Assert.Equal(new byte[] - { - 0xAB, 0xCD, 0xEF, 0x00 - }, uid.Bytes); + Assert.Equal(new byte[] + { + 0xAB, 0xCD, 0xEF, 0x00 + }, + uid.Bytes); - Assert.Equal(0xABCDEF00, uid.ToUInt64()); - } + Assert.Equal(0xABCDEF00, uid.ToUInt64()); + } - [Fact] - public void LongUidTest() - { - var uid = new UID(0xABCDEF0000EFCDAB); + [Fact] + public void LongUidTest() + { + var uid = new UID(0xABCDEF0000EFCDAB); - Assert.Equal(new byte[] - { - 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB - }, uid.Bytes); + Assert.Equal(new byte[] + { + 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB + }, + uid.Bytes); - Assert.Equal(0xABCDEF0000EFCDAB, uid.ToUInt64()); - } + Assert.Equal(0xABCDEF0000EFCDAB, uid.ToUInt64()); + } - [Fact] - public void UIntUidTest() - { - var uid = new UID(0xABCDEF00u); + [Fact] + public void UIntUidTest() + { + var uid = new UID(0xABCDEF00u); - Assert.Equal(new byte[] - { - 0xAB, 0xCD, 0xEF, 0x00 - }, uid.Bytes); + Assert.Equal(new byte[] + { + 0xAB, 0xCD, 0xEF, 0x00 + }, + uid.Bytes); - Assert.Equal(0xABCDEF00u, uid.ToUInt64()); - } + Assert.Equal(0xABCDEF00u, uid.ToUInt64()); + } - [Fact] - public void ULongUidTest() - { - var uid = new UID(0xABCDEF0000EFCDABu); + [Fact] + public void ULongUidTest() + { + var uid = new UID(0xABCDEF0000EFCDABu); - Assert.Equal(new byte[] - { - 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB - }, uid.Bytes); + Assert.Equal(new byte[] + { + 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB + }, + uid.Bytes); - Assert.Equal(0xABCDEF0000EFCDABu, uid.ToUInt64()); - } + Assert.Equal(0xABCDEF0000EFCDABu, uid.ToUInt64()); + } - [Fact] - public void UShortUidTest() - { - var uid = new UID(0xABCDu); + [Fact] + public void UShortUidTest() + { + var uid = new UID(0xABCDu); - Assert.Equal(new byte[] - { - 0xAB, 0xCD - }, uid.Bytes); + Assert.Equal(new byte[] + { + 0xAB, 0xCD + }, + uid.Bytes); - Assert.Equal(0xABCDu, uid.ToUInt64()); - } + Assert.Equal(0xABCDu, uid.ToUInt64()); + } - [Fact] - public void XmlRoundTripTest() - { - var original = new UID(0xabcd); + [Fact] + public void XmlRoundTripTest() + { + var original = new UID(0xabcd); - string plist = original.ToXmlPropertyList(); + string plist = original.ToXmlPropertyList(); - // UIDs don't exist in XML property lists, but they are represented as dictionaries - // for compability purposes - var roundtrip = XmlPropertyListParser.ParseString(plist) as UID; - Assert.Equal(0xabcdUL, roundtrip.ToUInt64()); - } + // UIDs don't exist in XML property lists, but they are represented as dictionaries + // for compability purposes + var roundtrip = XmlPropertyListParser.ParseString(plist) as UID; + Assert.Equal(0xabcdUL, roundtrip.ToUInt64()); } } \ No newline at end of file diff --git a/plist-cil.test/UseCultureAttribute.cs b/plist-cil.test/UseCultureAttribute.cs index a236757..3052722 100644 --- a/plist-cil.test/UseCultureAttribute.cs +++ b/plist-cil.test/UseCultureAttribute.cs @@ -52,19 +52,19 @@ public class UseCultureAttribute : BeforeAfterTestAttribute /// The method under test public override void Before(MethodInfo methodUnderTest) { - #if NETCORE +#if NETCORE originalCulture = CultureInfo.CurrentCulture; originalUICulture = CultureInfo.CurrentUICulture; CultureInfo.CurrentCulture = Culture; CultureInfo.CurrentUICulture = Culture; - #else +#else originalCulture = Thread.CurrentThread.CurrentCulture; originalUICulture = Thread.CurrentThread.CurrentUICulture; Thread.CurrentThread.CurrentCulture = Culture; Thread.CurrentThread.CurrentUICulture = UICulture; - #endif +#endif } /// @@ -74,12 +74,12 @@ public class UseCultureAttribute : BeforeAfterTestAttribute /// The method under test public override void After(MethodInfo methodUnderTest) { - #if NETCORE +#if NETCORE CultureInfo.CurrentCulture = originalCulture; CultureInfo.CurrentUICulture = originalUICulture; - #else +#else Thread.CurrentThread.CurrentCulture = originalCulture; Thread.CurrentThread.CurrentUICulture = originalUICulture; - #endif +#endif } } \ No newline at end of file diff --git a/plist-cil.test/ValidatingStream.cs b/plist-cil.test/ValidatingStream.cs index 0c16718..79e5c91 100644 --- a/plist-cil.test/ValidatingStream.cs +++ b/plist-cil.test/ValidatingStream.cs @@ -9,93 +9,91 @@ using System.Threading; using System.Threading.Tasks; using Xunit; -namespace plistcil.test +namespace plistcil.test; + +/// +/// A which writes its output to a and validates that the data which +/// is being written to the output stream matches the data in a reference stream. +/// +internal class ValidatingStream : Stream { - /// - /// A which writes its output to a and validates that the data which - /// is being written to the output stream matches the data in a reference stream. - /// - internal class ValidatingStream : Stream + readonly Stream expectedOutput; + readonly Stream output; + + /// Initializes a new instance of the class. + /// The to which to write data. + /// The reference stream for . + public ValidatingStream(Stream output, Stream expectedOutput) { - readonly Stream expectedOutput; - readonly Stream output; + this.output = output ?? throw new ArgumentNullException(nameof(output)); + this.expectedOutput = expectedOutput ?? throw new ArgumentNullException(nameof(expectedOutput)); + } - /// Initializes a new instance of the class. - /// The to which to write data. - /// The reference stream for . - public ValidatingStream(Stream output, Stream expectedOutput) - { - this.output = output ?? throw new ArgumentNullException(nameof(output)); - this.expectedOutput = expectedOutput ?? throw new ArgumentNullException(nameof(expectedOutput)); - } + /// + public override bool CanRead => false; - /// - public override bool CanRead => false; + /// + public override bool CanSeek => false; - /// - public override bool CanSeek => false; + /// + public override bool CanWrite => true; - /// - public override bool CanWrite => true; + /// + public override long Length => output.Length; - /// - public override long Length => output.Length; + /// + public override long Position + { + get => output.Position; + set => throw new NotImplementedException(); + } - /// - public override long Position - { - get => output.Position; - set => throw new NotImplementedException(); - } + /// + public override void Flush() => output.Flush(); - /// - public override void Flush() => output.Flush(); + /// + 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(); + /// + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + throw new NotSupportedException(); - /// - public override Task - ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => - throw new NotSupportedException(); + /// + public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); - /// - public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); + /// + public override void SetLength(long value) => throw new NotImplementedException(); - /// - public override void SetLength(long value) => throw new NotImplementedException(); + /// + public override void Write(byte[] buffer, int offset, int count) + { + byte[] expected = new byte[buffer.Length]; + expectedOutput.Read(expected, offset, count); - /// - public override void Write(byte[] buffer, int offset, int count) - { - byte[] expected = new byte[buffer.Length]; - expectedOutput.Read(expected, offset, count); + byte[] bufferChunk = buffer.Skip(offset).Take(count).ToArray(); + byte[] expectedChunk = expected.Skip(offset).Take(count).ToArray(); - byte[] bufferChunk = buffer.Skip(offset).Take(count).ToArray(); - byte[] expectedChunk = expected.Skip(offset).Take(count).ToArray(); + // Make sure the data being writen matches the data which was written to the expected stream. + // This will detect any errors as the invalid data is being written out - as opposed to post- + // test binary validation. + Assert.Equal(expectedChunk, bufferChunk); + output.Write(buffer, offset, count); + } - // Make sure the data being writen matches the data which was written to the expected stream. - // This will detect any errors as the invalid data is being written out - as opposed to post- - // test binary validation. - Assert.Equal(expectedChunk, bufferChunk); - output.Write(buffer, offset, count); - } + /// + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + byte[] expected = new byte[buffer.Length]; + await expectedOutput.ReadAsync(expected, offset, count, cancellationToken).ConfigureAwait(false); - /// - public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - byte[] expected = new byte[buffer.Length]; - await expectedOutput.ReadAsync(expected, offset, count, cancellationToken).ConfigureAwait(false); + byte[] bufferChunk = buffer.Skip(offset).Take(count).ToArray(); + byte[] expectedChunk = expected.Skip(offset).Take(count).ToArray(); - byte[] bufferChunk = buffer.Skip(offset).Take(count).ToArray(); - byte[] expectedChunk = expected.Skip(offset).Take(count).ToArray(); + // Make sure the data being writen matches the data which was written to the expected stream. + // This will detect any errors as the invalid data is being written out - as opposed to post- + // test binary validation. + Assert.Equal(expectedChunk, bufferChunk); - // Make sure the data being writen matches the data which was written to the expected stream. - // This will detect any errors as the invalid data is being written out - as opposed to post- - // test binary validation. - Assert.Equal(expectedChunk, bufferChunk); - - await output.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); - } + await output.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); } } \ No newline at end of file diff --git a/plist-cil.test/ValuePreprocessorTests.cs b/plist-cil.test/ValuePreprocessorTests.cs index 07ff529..d662af2 100644 --- a/plist-cil.test/ValuePreprocessorTests.cs +++ b/plist-cil.test/ValuePreprocessorTests.cs @@ -3,123 +3,122 @@ using System.Linq; using Claunia.PropertyList; 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 + private static readonly object _testLock = new(); + + [Fact] + public static void TestPassiveDefaultPreprocessorsRegistered() { - // 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(); + byte[] testByteArray = [0x1, 0x2, 0x4, 0x8]; - [Fact] - public static void TestPassiveDefaultPreprocessorsRegistered() + Assert.Equal(true, ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); + Assert.Equal(false, ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); + Assert.Equal("true", ValuePreprocessor.Preprocess("true", ValuePreprocessor.Type.BOOL)); + + Assert.Equal("42", ValuePreprocessor.Preprocess("42", ValuePreprocessor.Type.INTEGER)); + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.INTEGER)); + + Assert.Equal("3.14159", ValuePreprocessor.Preprocess("3.14159", ValuePreprocessor.Type.FLOATING_POINT)); + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.FLOATING_POINT)); + + Assert.Equal("2.71828", ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Type.UNDEFINED_NUMBER)); + + Assert.Equal("TestString", ValuePreprocessor.Preprocess("TestString", ValuePreprocessor.Type.STRING)); + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.STRING)); + + Assert.Equal("TestData", ValuePreprocessor.Preprocess("TestData", ValuePreprocessor.Type.DATA)); + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.DATA)); + + Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.DATE)); + Assert.Equal("01.02.1903", ValuePreprocessor.Preprocess("01.02.1903", ValuePreprocessor.Type.DATE)); + Assert.Equal(23.0, ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Type.DATE)); + } + + [Fact] + public static void TestRegisterPreprocessor() + { + lock(_testLock) { - byte[] testByteArray = [0x1, 0x2, 0x4, 0x8]; + Func examplePreprocessor = value => new string(value.Reverse().ToArray()); + string testString = "TestString"; + string expected = "gnirtStseT"; - Assert.Equal(true, ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); - Assert.Equal(false, ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); - Assert.Equal("true", ValuePreprocessor.Preprocess("true", ValuePreprocessor.Type.BOOL)); + var testType = (ValuePreprocessor.Type)42; - Assert.Equal("42", ValuePreprocessor.Preprocess("42", ValuePreprocessor.Type.INTEGER)); - Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.INTEGER)); + ValuePreprocessor.Set(examplePreprocessor, testType); + string actual = ValuePreprocessor.Preprocess(testString, testType); - Assert.Equal("3.14159", ValuePreprocessor.Preprocess("3.14159", ValuePreprocessor.Type.FLOATING_POINT)); - Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.FLOATING_POINT)); + Assert.Equal(actual, expected); - Assert.Equal("2.71828", ValuePreprocessor.Preprocess("2.71828", ValuePreprocessor.Type.UNDEFINED_NUMBER)); - - Assert.Equal("TestString", ValuePreprocessor.Preprocess("TestString", ValuePreprocessor.Type.STRING)); - Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.STRING)); - - Assert.Equal("TestData", ValuePreprocessor.Preprocess("TestData", ValuePreprocessor.Type.DATA)); - Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.DATA)); - - Assert.Equal(testByteArray, ValuePreprocessor.Preprocess(testByteArray, ValuePreprocessor.Type.DATE)); - Assert.Equal("01.02.1903", ValuePreprocessor.Preprocess("01.02.1903", ValuePreprocessor.Type.DATE)); - Assert.Equal(23.0, ValuePreprocessor.Preprocess(23.0, ValuePreprocessor.Type.DATE)); - } - - [Fact] - public static void TestRegisterPreprocessor() - { - lock(_testLock) - { - Func examplePreprocessor = value => new string(value.Reverse().ToArray()); - string testString = "TestString"; - string expected = "gnirtStseT"; - - var testType = (ValuePreprocessor.Type)42; - - ValuePreprocessor.Set(examplePreprocessor, testType); - string actual = ValuePreprocessor.Preprocess(testString, testType); - - Assert.Equal(actual, expected); - - ValuePreprocessor.Unset(testType); - } - } - - [Fact] - public static void TestRegisteredPreprocessorSelection1() - { - lock(_testLock) - { - Func examplePreprocessor = value => (short)(value - 1); - short testShort = 42; - string testString = "TestString"; - - var testType = (ValuePreprocessor.Type)42; - - // correct value type, differing data type - ValuePreprocessor.Set(examplePreprocessor, testType); - ValuePreprocessor.Set(ValuePreprocessor.GetDefault(), testType); - - string actual1 = ValuePreprocessor.Preprocess(testString, testType); - short actual2 = ValuePreprocessor.Preprocess(testShort, testType); - - // assert unchanged, since the selected preprocessor != tested preprocessor - Assert.Equal(actual1, testString); - Assert.NotEqual(actual2, testShort); - - ValuePreprocessor.Remove(testType); - ValuePreprocessor.Remove(testType); - } - } - - [Fact] - public static void TestRegisteredPreprocessorSelection2() - { - lock(_testLock) - { - Func examplePreprocessor = value => new string(value.Reverse().ToArray()); - byte[] testByteArray = [0x42,]; - string testString = "TestString"; - - var testType = (ValuePreprocessor.Type)42; - - // correct value type, differing data type - ValuePreprocessor.Set(examplePreprocessor, testType); - ValuePreprocessor.Set(ValuePreprocessor.GetDefault(), testType); - - string actual1 = ValuePreprocessor.Preprocess(testString, testType); - byte[] actual2 = ValuePreprocessor.Preprocess(testByteArray, testType); - - Assert.NotEqual(actual1, testString); - - // assert unchanged, since the selected preprocessor != tested preprocessor - Assert.Equal(actual2, testByteArray); - - ValuePreprocessor.Unset(testType); - ValuePreprocessor.Remove(testType); - } - } - - [Fact] - public static void TestUnregisteredPreprocessorThrows() - { - int[] testArray = [1, 2, 4, 8]; - - // there's no registered preprocessor for byte array arguments for STRING - Assert.Throws(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING)); + ValuePreprocessor.Unset(testType); } } + + [Fact] + public static void TestRegisteredPreprocessorSelection1() + { + lock(_testLock) + { + Func examplePreprocessor = value => (short)(value - 1); + short testShort = 42; + string testString = "TestString"; + + var testType = (ValuePreprocessor.Type)42; + + // correct value type, differing data type + ValuePreprocessor.Set(examplePreprocessor, testType); + ValuePreprocessor.Set(ValuePreprocessor.GetDefault(), testType); + + string actual1 = ValuePreprocessor.Preprocess(testString, testType); + short actual2 = ValuePreprocessor.Preprocess(testShort, testType); + + // assert unchanged, since the selected preprocessor != tested preprocessor + Assert.Equal(actual1, testString); + Assert.NotEqual(actual2, testShort); + + ValuePreprocessor.Remove(testType); + ValuePreprocessor.Remove(testType); + } + } + + [Fact] + public static void TestRegisteredPreprocessorSelection2() + { + lock(_testLock) + { + Func examplePreprocessor = value => new string(value.Reverse().ToArray()); + byte[] testByteArray = [0x42]; + string testString = "TestString"; + + var testType = (ValuePreprocessor.Type)42; + + // correct value type, differing data type + ValuePreprocessor.Set(examplePreprocessor, testType); + ValuePreprocessor.Set(ValuePreprocessor.GetDefault(), testType); + + string actual1 = ValuePreprocessor.Preprocess(testString, testType); + byte[] actual2 = ValuePreprocessor.Preprocess(testByteArray, testType); + + Assert.NotEqual(actual1, testString); + + // assert unchanged, since the selected preprocessor != tested preprocessor + Assert.Equal(actual2, testByteArray); + + ValuePreprocessor.Unset(testType); + ValuePreprocessor.Remove(testType); + } + } + + [Fact] + public static void TestUnregisteredPreprocessorThrows() + { + int[] testArray = [1, 2, 4, 8]; + + // there's no registered preprocessor for byte array arguments for STRING + Assert.Throws(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING)); + } } \ No newline at end of file diff --git a/plist-cil.test/plist-cil.test.csproj b/plist-cil.test/plist-cil.test.csproj index 4037bc1..37b335d 100644 --- a/plist-cil.test/plist-cil.test.csproj +++ b/plist-cil.test/plist-cil.test.csproj @@ -5,8 +5,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -14,7 +14,7 @@ - + @@ -111,7 +111,7 @@ - + diff --git a/plist-cil.test/test-files/ResourceRules.plist b/plist-cil.test/test-files/ResourceRules.plist index ba8e503..a26d3cf 100644 --- a/plist-cil.test/test-files/ResourceRules.plist +++ b/plist-cil.test/test-files/ResourceRules.plist @@ -1,5 +1,5 @@ - - + weight diff --git a/plist-cil.test/test-files/Roundtrip.plist b/plist-cil.test/test-files/Roundtrip.plist index 132aa3a..ec216b2 100644 --- a/plist-cil.test/test-files/Roundtrip.plist +++ b/plist-cil.test/test-files/Roundtrip.plist @@ -1,80 +1,80 @@ - - + - - files - - PkgInfo - - n57qDP4tZfLD1rCS43W0B4LQjzE= - - icon.png - - EUOeOW/HpmiAZeEGzJm8j3hE6vo= - - - files2 - - PkgInfo - - n57qDP4tZfLD1rCS43W0B4LQjzE= - - icon.png - - EUOeOW/HpmiAZeEGzJm8j3hE6vo= - - - rules - - .* - - Info.plist - - omit - - weight - 10 - - ResourceRules.plist - - omit - - weight - 100 - - - rules2 - - .* - - Info.plist - - omit - - weight - 10 - - ResourceRules.plist - - omit - - weight - 100 - - ^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/ - - nested - - weight - 0.0 - - ^[^/]+$ - - top - - weight - 0.0 - - - + + files + + PkgInfo + + n57qDP4tZfLD1rCS43W0B4LQjzE= + + icon.png + + EUOeOW/HpmiAZeEGzJm8j3hE6vo= + + + files2 + + PkgInfo + + n57qDP4tZfLD1rCS43W0B4LQjzE= + + icon.png + + EUOeOW/HpmiAZeEGzJm8j3hE6vo= + + + rules + + .* + + Info.plist + + omit + + weight + 10 + + ResourceRules.plist + + omit + + weight + 100 + + + rules2 + + .* + + Info.plist + + omit + + weight + 10 + + ResourceRules.plist + + omit + + weight + 100 + + ^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/ + + nested + + weight + 0.0 + + ^[^/]+$ + + top + + weight + 0.0 + + + diff --git a/plist-cil.test/test-files/RoundtripBinary.plist b/plist-cil.test/test-files/RoundtripBinary.plist index 95cc5d0..d54ed77 100644 --- a/plist-cil.test/test-files/RoundtripBinary.plist +++ b/plist-cil.test/test-files/RoundtripBinary.plist @@ -1,9 +1,9 @@ - - + - -MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp -bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl -IHVwb24gdGhlZS4= - + + MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp + bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl + IHVwb24gdGhlZS4= + diff --git a/plist-cil.test/test-files/RoundtripBinaryIndentation.plist b/plist-cil.test/test-files/RoundtripBinaryIndentation.plist index 832f9d9..bcfcbd8 100644 --- a/plist-cil.test/test-files/RoundtripBinaryIndentation.plist +++ b/plist-cil.test/test-files/RoundtripBinaryIndentation.plist @@ -1,12 +1,12 @@ - - + - - keyA - - MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp - bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl - IHVwb24gdGhlZS4= - - + + keyA + + MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp + bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl + IHVwb24gdGhlZS4= + + diff --git a/plist-cil.test/test-files/RoundtripReal.plist b/plist-cil.test/test-files/RoundtripReal.plist index 3ea7eb1..bcacfd6 100644 --- a/plist-cil.test/test-files/RoundtripReal.plist +++ b/plist-cil.test/test-files/RoundtripReal.plist @@ -1,5 +1,5 @@ - - + -1360155352.748765 + 1360155352.748765 diff --git a/plist-cil/ASCIIPropertyListParser.cs b/plist-cil/ASCIIPropertyListParser.cs index 9303561..5b1d9d3 100644 --- a/plist-cil/ASCIIPropertyListParser.cs +++ b/plist-cil/ASCIIPropertyListParser.cs @@ -29,785 +29,760 @@ using System.IO; using System.Text; using System.Text.RegularExpressions; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// +/// 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. +/// +/// Resources on ASCII property list format: +/// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html +/// Property List Programming Guide - Old-Style ASCII Property Lists +/// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html +/// GnuStep - NSPropertyListSerialization class documentation +/// +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class ASCIIPropertyListParser { - /// - /// - /// 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. - /// - /// Resources on ASCII property list format: - /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html - /// Property List Programming Guide - Old-Style ASCII Property Lists - /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html - /// GnuStep - NSPropertyListSerialization class documentation - /// - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class ASCIIPropertyListParser + /// A space + public const char WHITESPACE_SPACE = ' '; + /// A tabulator + public const char WHITESPACE_TAB = '\t'; + /// A newline + public const char WHITESPACE_NEWLINE = '\n'; + /// A carriage return + public const char WHITESPACE_CARRIAGE_RETURN = '\r'; + + /// Token of NSArray start + public const char ARRAY_BEGIN_TOKEN = '('; + /// Token of NSArray end + public const char ARRAY_END_TOKEN = ')'; + /// Token of NSArray item delimiter + public const char ARRAY_ITEM_DELIMITER_TOKEN = ','; + + /// Token of NSDictionary start + public const char DICTIONARY_BEGIN_TOKEN = '{'; + /// Token of NSDictionary end + public const char DICTIONARY_END_TOKEN = '}'; + /// Token of NSDictionary assignment + public const char DICTIONARY_ASSIGN_TOKEN = '='; + /// Token of NSDictionary item delimiter + public const char DICTIONARY_ITEM_DELIMITER_TOKEN = ';'; + + /// Token of quoted NSString start + public const char QUOTEDSTRING_BEGIN_TOKEN = '"'; + /// Token of quoted NSString end + public const char QUOTEDSTRING_END_TOKEN = '"'; + /// Token of quoted NSString escaped character + public const char QUOTEDSTRING_ESCAPE_TOKEN = '\\'; + + /// Token of NSData start + public const char DATA_BEGIN_TOKEN = '<'; + /// Token of NSData end + public const char DATA_END_TOKEN = '>'; + + /// Token of GSObject start + public const char DATA_GSOBJECT_BEGIN_TOKEN = '*'; + /// Token of GSDate start + public const char DATA_GSDATE_BEGIN_TOKEN = 'D'; + /// Token of GSBoolean start + public const char DATA_GSBOOL_BEGIN_TOKEN = 'B'; + /// Token for GSBoolen's true + public const char DATA_GSBOOL_TRUE_TOKEN = 'Y'; + /// Token for GSBoolen's false + public const char DATA_GSBOOL_FALSE_TOKEN = 'N'; + /// Token for GSInteger + public const char DATA_GSINT_BEGIN_TOKEN = 'I'; + /// Token for GSReal + public const char DATA_GSREAL_BEGIN_TOKEN = 'R'; + + /// Token for NSDate date field delimited + public const char DATE_DATE_FIELD_DELIMITER = '-'; + /// Token for NSDate time field delimiter + public const char DATE_TIME_FIELD_DELIMITER = ':'; + /// Token for GSDate date and time delimiter + public const char DATE_GS_DATE_TIME_DELIMITER = ' '; + /// Token for NSDate date and time delimiter + public const char DATE_APPLE_DATE_TIME_DELIMITER = 'T'; + /// Token for NSDate end + public const char DATE_APPLE_END_TOKEN = 'Z'; + + /// Token for comment start + public const char COMMENT_BEGIN_TOKEN = '/'; + /// Second token for multiline comment + public const char MULTILINE_COMMENT_SECOND_TOKEN = '*'; + /// Second token for singleline comment + public const char SINGLELINE_COMMENT_SECOND_TOKEN = '/'; + /// End token for multiline comment + public const char MULTILINE_COMMENT_END_TOKEN = '/'; + + /** + * Property list source data + */ + readonly char[] data; + /** + * Current parsing index + */ + int index; + + /** + * Only allow subclasses to change instantiation. + */ + protected ASCIIPropertyListParser() {} + + /// Creates a new parser for the given property list content. + /// The content of the property list that is to be parsed. + ASCIIPropertyListParser(char[] propertyListContent) => data = propertyListContent; + + /// Parses an ASCII property list file. + /// The ASCII property list file.. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When an error occurs during parsing. + /// When an error occured while reading from the input stream. + public static NSObject Parse(FileInfo f) => Parse(f.OpenRead()); + + /// Parses an ASCII property list from an input stream. + /// The input stream that points to the property list's data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When an error occurs during parsing. + /// + public static NSObject Parse(Stream fs) { - /// A space - public const char WHITESPACE_SPACE = ' '; - /// A tabulator - public const char WHITESPACE_TAB = '\t'; - /// A newline - public const char WHITESPACE_NEWLINE = '\n'; - /// A carriage return - public const char WHITESPACE_CARRIAGE_RETURN = '\r'; + byte[] buf = PropertyListParser.ReadAll(fs); - /// Token of NSArray start - public const char ARRAY_BEGIN_TOKEN = '('; - /// Token of NSArray end - public const char ARRAY_END_TOKEN = ')'; - /// Token of NSArray item delimiter - public const char ARRAY_ITEM_DELIMITER_TOKEN = ','; + // Don't close the stream - that would be the responsibility of code that class + // Parse + return Parse(buf); + } - /// Token of NSDictionary start - public const char DICTIONARY_BEGIN_TOKEN = '{'; - /// Token of NSDictionary end - public const char DICTIONARY_END_TOKEN = '}'; - /// Token of NSDictionary assignment - public const char DICTIONARY_ASSIGN_TOKEN = '='; - /// Token of NSDictionary item delimiter - public const char DICTIONARY_ITEM_DELIMITER_TOKEN = ';'; + /// Parses an ASCII property list from a byte array. + /// The ASCII property list data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When an error occurs during parsing. + public static NSObject Parse(byte[] bytes) => Parse(bytes.AsSpan()); - /// Token of quoted NSString start - public const char QUOTEDSTRING_BEGIN_TOKEN = '"'; - /// Token of quoted NSString end - public const char QUOTEDSTRING_END_TOKEN = '"'; - /// Token of quoted NSString escaped character - public const char QUOTEDSTRING_ESCAPE_TOKEN = '\\'; + /// Parses an ASCII property list from a byte array. + /// The ASCII property list data. + /// The offset at which to start reading the property list. + /// The length of the property list. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When an error occurs during parsing. + public static NSObject Parse(byte[] bytes, int offset, int count) => Parse(bytes.AsSpan(offset, count)); - /// Token of NSData start - public const char DATA_BEGIN_TOKEN = '<'; - /// Token of NSData end - public const char DATA_END_TOKEN = '>'; - - /// Token of GSObject start - public const char DATA_GSOBJECT_BEGIN_TOKEN = '*'; - /// Token of GSDate start - public const char DATA_GSDATE_BEGIN_TOKEN = 'D'; - /// Token of GSBoolean start - public const char DATA_GSBOOL_BEGIN_TOKEN = 'B'; - /// Token for GSBoolen's true - public const char DATA_GSBOOL_TRUE_TOKEN = 'Y'; - /// Token for GSBoolen's false - public const char DATA_GSBOOL_FALSE_TOKEN = 'N'; - /// Token for GSInteger - public const char DATA_GSINT_BEGIN_TOKEN = 'I'; - /// Token for GSReal - public const char DATA_GSREAL_BEGIN_TOKEN = 'R'; - - /// Token for NSDate date field delimited - public const char DATE_DATE_FIELD_DELIMITER = '-'; - /// Token for NSDate time field delimiter - public const char DATE_TIME_FIELD_DELIMITER = ':'; - /// Token for GSDate date and time delimiter - public const char DATE_GS_DATE_TIME_DELIMITER = ' '; - /// Token for NSDate date and time delimiter - public const char DATE_APPLE_DATE_TIME_DELIMITER = 'T'; - /// Token for NSDate end - public const char DATE_APPLE_END_TOKEN = 'Z'; - - /// Token for comment start - public const char COMMENT_BEGIN_TOKEN = '/'; - /// Second token for multiline comment - public const char MULTILINE_COMMENT_SECOND_TOKEN = '*'; - /// Second token for singleline comment - public const char SINGLELINE_COMMENT_SECOND_TOKEN = '/'; - /// End token for multiline comment - public const char MULTILINE_COMMENT_END_TOKEN = '/'; - - /** - * Property list source data - */ - readonly char[] data; - /** - * Current parsing index - */ - int index; - - /** - * Only allow subclasses to change instantiation. - */ - protected ASCIIPropertyListParser() {} - - /// Creates a new parser for the given property list content. - /// The content of the property list that is to be parsed. - ASCIIPropertyListParser(char[] propertyListContent) => data = propertyListContent; - - /// Parses an ASCII property list file. - /// The ASCII property list file.. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When an error occurs during parsing. - /// When an error occured while reading from the input stream. - public static NSObject Parse(FileInfo f) => Parse(f.OpenRead()); - - /// Parses an ASCII property list from an input stream. - /// The input stream that points to the property list's data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When an error occurs during parsing. - /// - public static NSObject Parse(Stream fs) - { - byte[] buf = PropertyListParser.ReadAll(fs); - - // Don't close the stream - that would be the responsibility of code that class - // Parse - return Parse(buf); - } - - /// Parses an ASCII property list from a byte array. - /// The ASCII property list data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When an error occurs during parsing. - public static NSObject Parse(byte[] bytes) => Parse(bytes.AsSpan()); - - /// Parses an ASCII property list from a byte array. - /// The ASCII property list data. - /// The offset at which to start reading the property list. - /// The length of the property list. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When an error occurs during parsing. - public static NSObject Parse(byte[] bytes, int offset, int count) => Parse(bytes.AsSpan(offset, count)); - - /// Parses an ASCII property list from a byte span. - /// The ASCII property list data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When an error occurs during parsing. - public static NSObject Parse(ReadOnlySpan bytes) - { - #if NATIVE_SPAN - return ParseString(Encoding.UTF8.GetString(bytes)); - #else + /// Parses an ASCII property list from a byte span. + /// The ASCII property list data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When an error occurs during parsing. + public static NSObject Parse(ReadOnlySpan bytes) + { +#if NATIVE_SPAN + return ParseString(Encoding.UTF8.GetString(bytes)); +#else return ParseString(Encoding.UTF8.GetString(bytes.ToArray())); - #endif - } +#endif + } - /// Parses an ASCII property list from a string. - /// The ASCII property list data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When an error occurs during parsing. - public static NSObject ParseString(string value) + /// Parses an ASCII property list from a string. + /// The ASCII property list data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When an error occurs during parsing. + public static NSObject ParseString(string value) + { + var parser = new ASCIIPropertyListParser(value.ToCharArray()); + + return parser.Parse(); + } + + /// Checks whether the given sequence of symbols can be accepted. + /// Whether the given tokens occur at the current parsing position. + /// The sequence of tokens to look for. + bool AcceptSequence(params char[] sequence) + { + for(int i = 0; i < sequence.Length; i++) + if(data[index + i] != sequence[i]) return false; + + return true; + } + + /// + /// Checks whether the given symbols can be accepted, that is, if one of the given symbols is found at the current + /// parsing position. + /// + /// The symbols to check. + /// Whether one of the symbols can be accepted or not. + bool Accept(params char[] acceptableSymbols) + { + bool symbolPresent = false; + + foreach(char c in acceptableSymbols) symbolPresent |= data[index] == c; + + return symbolPresent; + } + + /// + /// Checks whether the given symbol can be accepted, that is, if the given symbols is found at the current parsing + /// position. + /// + /// The symbol to check. + /// Whether the symbol can be accepted or not. + bool Accept(char acceptableSymbol) => data[index] == acceptableSymbol; + + /// Expects the input to have one of the given symbols at the current parsing position. + /// The expected symbols. + /// If none of the expected symbols could be found. + void Expect(params char[] expectedSymbols) + { + if(Accept(expectedSymbols)) return; + + string excString = "Expected '" + expectedSymbols[0] + "'"; + + for(int i = 1; i < expectedSymbols.Length; i++) excString += " or '" + expectedSymbols[i] + "'"; + + excString += " but found '" + data[index] + "'"; + + throw new FormatException($"{excString} at {index}"); + } + + /// Expects the input to have the given symbol at the current parsing position. + /// The expected symbol. + /// If the expected symbol could be found. + void Expect(char expectedSymbol) + { + if(!Accept(expectedSymbol)) + throw new FormatException($"Expected '{expectedSymbol}' but found '{data[index]}' at {index}"); + } + + /// Reads an expected symbol. + /// The symbol to read. + /// If the expected symbol could not be read. + void Read(char symbol) + { + Expect(symbol); + index++; + } + + /** + * Skips the current symbol. + */ + void Skip() => index++; + + /// Skips several symbols + /// The amount of symbols to skip. + void Skip(int numSymbols) => index += numSymbols; + + /** + * Skips all whitespaces and comments from the current parsing position onward. + */ + void SkipWhitespacesAndComments() + { + bool commentSkipped; + + do { - var parser = new ASCIIPropertyListParser(value.ToCharArray()); + commentSkipped = false; - return parser.Parse(); - } + //Skip whitespaces + while(Accept(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE, WHITESPACE_SPACE, WHITESPACE_TAB)) Skip(); - /// Checks whether the given sequence of symbols can be accepted. - /// Whether the given tokens occur at the current parsing position. - /// The sequence of tokens to look for. - bool AcceptSequence(params char[] sequence) - { - for(int i = 0; i < sequence.Length; i++) - if(data[index + i] != sequence[i]) - return false; - - return true; - } - - /// - /// Checks whether the given symbols can be accepted, that is, if one of the given symbols is found at the current - /// parsing position. - /// - /// The symbols to check. - /// Whether one of the symbols can be accepted or not. - bool Accept(params char[] acceptableSymbols) - { - bool symbolPresent = false; - - foreach(char c in acceptableSymbols) - symbolPresent |= data[index] == c; - - return symbolPresent; - } - - /// - /// Checks whether the given symbol can be accepted, that is, if the given symbols is found at the current parsing - /// position. - /// - /// The symbol to check. - /// Whether the symbol can be accepted or not. - bool Accept(char acceptableSymbol) => data[index] == acceptableSymbol; - - /// Expects the input to have one of the given symbols at the current parsing position. - /// The expected symbols. - /// If none of the expected symbols could be found. - void Expect(params char[] expectedSymbols) - { - if(Accept(expectedSymbols)) - return; - - string excString = "Expected '" + expectedSymbols[0] + "'"; - - for(int i = 1; i < expectedSymbols.Length; i++) - excString += " or '" + expectedSymbols[i] + "'"; - - excString += " but found '" + data[index] + "'"; - - throw new FormatException($"{excString} at {index}"); - } - - /// Expects the input to have the given symbol at the current parsing position. - /// The expected symbol. - /// If the expected symbol could be found. - void Expect(char expectedSymbol) - { - if(!Accept(expectedSymbol)) - throw new FormatException($"Expected '{expectedSymbol}' but found '{data[index]}' at {index}"); - } - - /// Reads an expected symbol. - /// The symbol to read. - /// If the expected symbol could not be read. - void Read(char symbol) - { - Expect(symbol); - index++; - } - - /** - * Skips the current symbol. - */ - void Skip() => index++; - - /// Skips several symbols - /// The amount of symbols to skip. - void Skip(int numSymbols) => index += numSymbols; - - /** - * Skips all whitespaces and comments from the current parsing position onward. - */ - void SkipWhitespacesAndComments() - { - bool commentSkipped; - - do + //Skip single line comments "//..." + if(AcceptSequence(COMMENT_BEGIN_TOKEN, SINGLELINE_COMMENT_SECOND_TOKEN)) { - commentSkipped = false; + Skip(2); + ReadInputUntil(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE); + commentSkipped = true; + } - //Skip whitespaces - while(Accept(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE, WHITESPACE_SPACE, WHITESPACE_TAB)) - Skip(); + //Skip multi line comments "/* ... */" + else if(AcceptSequence(COMMENT_BEGIN_TOKEN, MULTILINE_COMMENT_SECOND_TOKEN)) + { + Skip(2); - //Skip single line comments "//..." - if(AcceptSequence(COMMENT_BEGIN_TOKEN, SINGLELINE_COMMENT_SECOND_TOKEN)) + while(true) { - Skip(2); - ReadInputUntil(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE); - commentSkipped = true; - } - - //Skip multi line comments "/* ... */" - else if(AcceptSequence(COMMENT_BEGIN_TOKEN, MULTILINE_COMMENT_SECOND_TOKEN)) - { - Skip(2); - - while(true) + if(AcceptSequence(MULTILINE_COMMENT_SECOND_TOKEN, MULTILINE_COMMENT_END_TOKEN)) { - if(AcceptSequence(MULTILINE_COMMENT_SECOND_TOKEN, MULTILINE_COMMENT_END_TOKEN)) - { - Skip(2); - - break; - } - - Skip(); - } - - commentSkipped = true; - } - } while( - commentSkipped); //if a comment was skipped more whitespace or another comment can follow, so skip again - } - - /// Reads input until one of the given symbols is found. - /// The input until one the given symbols. - /// The symbols that can occur after the string to read. - string ReadInputUntil(params char[] symbols) - { - string s = ""; - - while(!Accept(symbols)) - { - s += data[index]; - Skip(); - } - - return s; - } - - /// Reads input until the given symbol is found. - /// The input until the given symbol. - /// The symbol that can occur after the string to read. - string ReadInputUntil(char symbol) - { - string s = ""; - - while(!Accept(symbol)) - { - s += data[index]; - Skip(); - } - - return s; - } - - /// Parses the property list from the beginning and returns the root object of the property list. - /// The root object of the property list. This can either be a NSDictionary or a NSArray. - /// When an error occured during parsing - public NSObject Parse() - { - index = 0; - - //Skip Unicode byte order mark (BOM) - if(data.Length >= 3 && - (data[0] & 0xFF) == 0xEF && - (data[1] & 0xFF) == 0xBB && - (data[2] & 0xFF) == 0xBF) - Skip(3); - - SkipWhitespacesAndComments(); - Expect(DICTIONARY_BEGIN_TOKEN, ARRAY_BEGIN_TOKEN, COMMENT_BEGIN_TOKEN); - - try - { - return ParseObject(); - } - catch(IndexOutOfRangeException) - { - throw new FormatException($"Reached end of input unexpectedly at {index}."); - } - } - - /// Parses the NSObject found at the current position in the property list data stream. - /// The parsed NSObject. - /// - NSObject ParseObject() - { - switch(data[index]) - { - case ARRAY_BEGIN_TOKEN: - { - return ParseArray(); - } - case DICTIONARY_BEGIN_TOKEN: - { - return ParseDictionary(); - } - case DATA_BEGIN_TOKEN: - { - return ParseData(); - } - case QUOTEDSTRING_BEGIN_TOKEN: - { - string quotedString = ParseQuotedString(); - - //apple dates are quoted strings of length 20 and after the 4 year digits a dash is found - if(quotedString.Length == 20 && - quotedString[4] == DATE_DATE_FIELD_DELIMITER) - try - { - return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.DATE)); - } - catch(Exception) - { - //not a date? --> return string - return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING)); - } - - return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING)); - } - default: - { - //0-9 - if(data[index] > 0x2F && - data[index] < 0x3A) - return ParseDateString(); - - //non-numerical -> string or boolean - string parsedString = ParseString(); - - return new NSString(ValuePreprocessor.Preprocess(parsedString, ValuePreprocessor.Type.STRING)); - } - } - } - - /// - /// Parses an array from the current parsing position. The prerequisite for calling this method is, that an array - /// begin token has been read. - /// - /// The array found at the parsing position. - NSArray ParseArray() - { - //Skip begin token - Skip(); - SkipWhitespacesAndComments(); - List objects = new(); - - while(!Accept(ARRAY_END_TOKEN)) - { - objects.Add(ParseObject()); - SkipWhitespacesAndComments(); - - if(Accept(ARRAY_ITEM_DELIMITER_TOKEN)) - Skip(); - else - break; //must have reached end of array - - SkipWhitespacesAndComments(); - } - - //parse end token - Read(ARRAY_END_TOKEN); - - return new NSArray(objects.ToArray()); - } - - /// - /// Parses a dictionary from the current parsing position. The prerequisite for calling this method is, that a - /// dictionary begin token has been read. - /// - /// The dictionary found at the parsing position. - NSDictionary ParseDictionary() - { - //Skip begin token - Skip(); - SkipWhitespacesAndComments(); - var dict = new NSDictionary(); - - while(!Accept(DICTIONARY_END_TOKEN)) - { - //Parse key - string keyString; - - keyString = Accept(QUOTEDSTRING_BEGIN_TOKEN) ? ParseQuotedString() : ParseString(); - - SkipWhitespacesAndComments(); - - //Parse assign token - Read(DICTIONARY_ASSIGN_TOKEN); - SkipWhitespacesAndComments(); - - NSObject nso = ParseObject(); - dict.Add(keyString, nso); - SkipWhitespacesAndComments(); - Read(DICTIONARY_ITEM_DELIMITER_TOKEN); - SkipWhitespacesAndComments(); - } - - //skip end token - Skip(); - - return dict; - } - - /// - /// Parses a data object from the current parsing position. This can either be a NSData object or a GnuStep - /// NSNumber or NSDate. The prerequisite for calling this method is, that a data begin token has been read. - /// - /// The data object found at the parsing position. - NSObject ParseData() - { - NSObject obj = null; - - //Skip begin token - Skip(); - - if(Accept(DATA_GSOBJECT_BEGIN_TOKEN)) - { - Skip(); - - Expect(DATA_GSBOOL_BEGIN_TOKEN, DATA_GSDATE_BEGIN_TOKEN, DATA_GSINT_BEGIN_TOKEN, - DATA_GSREAL_BEGIN_TOKEN); - - if(Accept(DATA_GSBOOL_BEGIN_TOKEN)) - { - //Boolean - Skip(); - Expect(DATA_GSBOOL_TRUE_TOKEN, DATA_GSBOOL_FALSE_TOKEN); - - if(Accept(DATA_GSBOOL_TRUE_TOKEN)) - obj = new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); - else - obj = new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); - - //Skip the parsed boolean token - Skip(); - } - else if(Accept(DATA_GSDATE_BEGIN_TOKEN)) - { - //Date - Skip(); - string dateString = ReadInputUntil(DATA_END_TOKEN); - obj = new NSDate(ValuePreprocessor.Preprocess(dateString, ValuePreprocessor.Type.DATE)); - } - else if(Accept(DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN)) - { - //Number - Skip(); - string numberString = ReadInputUntil(DATA_END_TOKEN); - obj = new NSNumber(ValuePreprocessor.Preprocess(numberString, ValuePreprocessor.Type.UNDEFINED_NUMBER)); - } - - //parse data end token - Read(DATA_END_TOKEN); - } - else - { - string dataString = ReadInputUntil(DATA_END_TOKEN); - dataString = Regex.Replace(dataString, "\\s+", ""); - - int numBytes = dataString.Length / 2; - byte[] bytes = new byte[numBytes]; - - for(int i = 0; i < bytes.Length; i++) - { - string byteString = dataString.Substring(i * 2, 2); - int byteValue = Convert.ToInt32(byteString, 16); - bytes[i] = (byte)byteValue; - } - - obj = new NSData(ValuePreprocessor.Preprocess(bytes, ValuePreprocessor.Type.DATA)); - - //skip end token - Skip(); - } - - return obj; - } - - /// Attempts to parse a plain string as a date if possible. - /// A NSDate if the string represents such an object. Otherwise a NSString is returned. - NSObject ParseDateString() - { - string numericalString = ParseString(); - - if(numericalString.Length <= 4 || - numericalString[4] != DATE_DATE_FIELD_DELIMITER) - return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.STRING)); - - try - { - return new NSDate(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.DATE)); - } - catch(Exception) - { - //An exception occurs if the string is not a date but just a string - } - - return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.STRING)); - } - - /// - /// Parses a plain string from the current parsing position. The string is made up of all characters to the next - /// whitespace, delimiter token or assignment token. - /// - /// The string found at the current parsing position. - string ParseString() => ReadInputUntil(WHITESPACE_SPACE, WHITESPACE_TAB, WHITESPACE_NEWLINE, - WHITESPACE_CARRIAGE_RETURN, ARRAY_ITEM_DELIMITER_TOKEN, - DICTIONARY_ITEM_DELIMITER_TOKEN, DICTIONARY_ASSIGN_TOKEN, - ARRAY_END_TOKEN); - - /// - /// Parses a quoted string from the current parsing position. The prerequisite for calling this method is, that a - /// quoted string begin token has been read. - /// - /// The quoted string found at the parsing method with all special characters unescaped. - /// If an error occured during parsing. - string ParseQuotedString() - { - //Skip begin token - Skip(); - string quotedString = ""; - bool unescapedBackslash = true; - - //Read from opening quotation marks to closing quotation marks and skip escaped quotation marks - while(data[index] != QUOTEDSTRING_END_TOKEN || - (data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash)) - { - quotedString += data[index]; - - if(Accept(QUOTEDSTRING_ESCAPE_TOKEN)) - unescapedBackslash = !(data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash); - - Skip(); - } - - string unescapedString; - - try - { - unescapedString = ParseQuotedString(quotedString); - } - catch(Exception) - { - throw new FormatException($"The quoted string could not be parsed at {index}."); - } - - //skip end token - Skip(); - - return unescapedString; - } - - /// - /// Parses a string according to the format specified for ASCII property lists. Such strings can contain escape - /// sequences which are unescaped in this method. - /// - /// The unescaped string in UTF-8 or ASCII format, depending on the contained characters. - /// - /// The escaped string according to the ASCII property list format, without leading and trailing quotation - /// marks. - /// - /// If the en-/decoder for the UTF-8 or ASCII encoding could not be loaded - /// If the string is encoded neither in ASCII nor in UTF-8 - public static string ParseQuotedString(string s) - { - List strBytes = new(); - - IEnumerable characters = s.ToCharArray(); - IEnumerator c = characters.GetEnumerator(); - - while(c.MoveNext()) - switch(c.Current) - { - case '\\': - { - //An escaped sequence is following - byte[] bts = Encoding.UTF8.GetBytes(ParseEscapedSequence(c)); - - foreach(byte b in bts) - strBytes.Add(b); + Skip(2); break; } - default: - { - //a normal ASCII char - strBytes.AddRange(Encoding.BigEndianUnicode.GetBytes(new[] - { - c.Current - })); - break; - } + Skip(); } - byte[] bytArr = new byte[strBytes.Count]; - int i = 0; - - foreach(byte b in strBytes) - { - bytArr[i] = b; - i++; + commentSkipped = true; } + } while(commentSkipped); //if a comment was skipped more whitespace or another comment can follow, so skip again + } - //Build string - string result = Encoding.BigEndianUnicode.GetString(bytArr); + /// Reads input until one of the given symbols is found. + /// The input until one the given symbols. + /// The symbols that can occur after the string to read. + string ReadInputUntil(params char[] symbols) + { + string s = ""; - //If the string can be represented in the ASCII codepage - // --> use ASCII encoding - if(IsASCIIEncodable(result)) - return Encoding.ASCII.GetString(Encoding.Convert(Encoding.BigEndianUnicode, Encoding.ASCII, bytArr)); - - //The string contains characters outside the ASCII codepage - // --> use the UTF-8 encoded string - return result; + while(!Accept(symbols)) + { + s += data[index]; + Skip(); } - /// Unescapes an escaped character sequence, e.g. \\u00FC. - /// The unescaped character as a string. - /// The string character iterator pointing to the first character after the backslash - /// If an invalid Unicode or ASCII escape sequence is found. - static string ParseEscapedSequence(IEnumerator iterator) + return s; + } + + /// Reads input until the given symbol is found. + /// The input until the given symbol. + /// The symbol that can occur after the string to read. + string ReadInputUntil(char symbol) + { + string s = ""; + + while(!Accept(symbol)) { - iterator.MoveNext(); - char c = iterator.Current; - - switch(c) - { - case '\\': - return Encoding.UTF8.GetString(new byte[] - { - 0, (byte)'\\' - }); - case '"': - return Encoding.UTF8.GetString(new byte[] - { - 0, (byte)'\"' - }); - case 'b': - return Encoding.UTF8.GetString(new byte[] - { - 0, (byte)'\b' - }); - case 'n': - return Encoding.UTF8.GetString(new byte[] - { - 0, (byte)'\n' - }); - case 'r': - return Encoding.UTF8.GetString(new byte[] - { - 0, (byte)'\r' - }); - case 't': - return Encoding.UTF8.GetString(new byte[] - { - 0, (byte)'\t' - }); - case 'U': - case 'u': - { - //4 digit hex Unicode value - string byte1 = ""; - iterator.MoveNext(); - byte1 += iterator.Current; - iterator.MoveNext(); - byte1 += iterator.Current; - string byte2 = ""; - iterator.MoveNext(); - byte2 += iterator.Current; - iterator.MoveNext(); - byte2 += iterator.Current; - - byte[] stringBytes = - { - (byte)Convert.ToInt32(byte1, 16), (byte)Convert.ToInt32(byte2, 16) - }; - - return Encoding.UTF8.GetString(stringBytes); - } - default: - { - //3 digit octal ASCII value - string num = ""; - num += c; - iterator.MoveNext(); - num += iterator.Current; - iterator.MoveNext(); - num += iterator.Current; - int asciiCode = Convert.ToInt32(num, 8); - - byte[] stringBytes = - { - 0, (byte)asciiCode - }; - - return Encoding.UTF8.GetString(stringBytes); - } - } + s += data[index]; + Skip(); } - internal static bool IsASCIIEncodable(string text) - { - foreach(char c in text) - if(c > 0x7F) - return false; + return s; + } - return true; + /// Parses the property list from the beginning and returns the root object of the property list. + /// The root object of the property list. This can either be a NSDictionary or a NSArray. + /// When an error occured during parsing + public NSObject Parse() + { + index = 0; + + //Skip Unicode byte order mark (BOM) + if(data.Length >= 3 && (data[0] & 0xFF) == 0xEF && (data[1] & 0xFF) == 0xBB && (data[2] & 0xFF) == 0xBF) + Skip(3); + + SkipWhitespacesAndComments(); + Expect(DICTIONARY_BEGIN_TOKEN, ARRAY_BEGIN_TOKEN, COMMENT_BEGIN_TOKEN); + + try + { + return ParseObject(); + } + catch(IndexOutOfRangeException) + { + throw new FormatException($"Reached end of input unexpectedly at {index}."); } } + + /// Parses the NSObject found at the current position in the property list data stream. + /// The parsed NSObject. + /// + NSObject ParseObject() + { + switch(data[index]) + { + case ARRAY_BEGIN_TOKEN: + { + return ParseArray(); + } + case DICTIONARY_BEGIN_TOKEN: + { + return ParseDictionary(); + } + case DATA_BEGIN_TOKEN: + { + return ParseData(); + } + case QUOTEDSTRING_BEGIN_TOKEN: + { + string quotedString = ParseQuotedString(); + + //apple dates are quoted strings of length 20 and after the 4 year digits a dash is found + if(quotedString.Length == 20 && quotedString[4] == DATE_DATE_FIELD_DELIMITER) + { + try + { + return new NSDate(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.DATE)); + } + catch(Exception) + { + //not a date? --> return string + return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING)); + } + } + + return new NSString(ValuePreprocessor.Preprocess(quotedString, ValuePreprocessor.Type.STRING)); + } + default: + { + //0-9 + if(data[index] > 0x2F && data[index] < 0x3A) return ParseDateString(); + + //non-numerical -> string or boolean + string parsedString = ParseString(); + + return new NSString(ValuePreprocessor.Preprocess(parsedString, ValuePreprocessor.Type.STRING)); + } + } + } + + /// + /// Parses an array from the current parsing position. The prerequisite for calling this method is, that an array + /// begin token has been read. + /// + /// The array found at the parsing position. + NSArray ParseArray() + { + //Skip begin token + Skip(); + SkipWhitespacesAndComments(); + List objects = []; + + while(!Accept(ARRAY_END_TOKEN)) + { + objects.Add(ParseObject()); + SkipWhitespacesAndComments(); + + if(Accept(ARRAY_ITEM_DELIMITER_TOKEN)) + Skip(); + else + break; //must have reached end of array + + SkipWhitespacesAndComments(); + } + + //parse end token + Read(ARRAY_END_TOKEN); + + return new NSArray(objects.ToArray()); + } + + /// + /// Parses a dictionary from the current parsing position. The prerequisite for calling this method is, that a + /// dictionary begin token has been read. + /// + /// The dictionary found at the parsing position. + NSDictionary ParseDictionary() + { + //Skip begin token + Skip(); + SkipWhitespacesAndComments(); + var dict = new NSDictionary(); + + while(!Accept(DICTIONARY_END_TOKEN)) + { + //Parse key + string keyString; + + keyString = Accept(QUOTEDSTRING_BEGIN_TOKEN) ? ParseQuotedString() : ParseString(); + + SkipWhitespacesAndComments(); + + //Parse assign token + Read(DICTIONARY_ASSIGN_TOKEN); + SkipWhitespacesAndComments(); + + NSObject nso = ParseObject(); + dict.Add(keyString, nso); + SkipWhitespacesAndComments(); + Read(DICTIONARY_ITEM_DELIMITER_TOKEN); + SkipWhitespacesAndComments(); + } + + //skip end token + Skip(); + + return dict; + } + + /// + /// Parses a data object from the current parsing position. This can either be a NSData object or a GnuStep + /// NSNumber or NSDate. The prerequisite for calling this method is, that a data begin token has been read. + /// + /// The data object found at the parsing position. + NSObject ParseData() + { + NSObject obj = null; + + //Skip begin token + Skip(); + + if(Accept(DATA_GSOBJECT_BEGIN_TOKEN)) + { + Skip(); + + Expect(DATA_GSBOOL_BEGIN_TOKEN, DATA_GSDATE_BEGIN_TOKEN, DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN); + + if(Accept(DATA_GSBOOL_BEGIN_TOKEN)) + { + //Boolean + Skip(); + Expect(DATA_GSBOOL_TRUE_TOKEN, DATA_GSBOOL_FALSE_TOKEN); + + if(Accept(DATA_GSBOOL_TRUE_TOKEN)) + obj = new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); + else + obj = new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); + + //Skip the parsed boolean token + Skip(); + } + else if(Accept(DATA_GSDATE_BEGIN_TOKEN)) + { + //Date + Skip(); + string dateString = ReadInputUntil(DATA_END_TOKEN); + obj = new NSDate(ValuePreprocessor.Preprocess(dateString, ValuePreprocessor.Type.DATE)); + } + else if(Accept(DATA_GSINT_BEGIN_TOKEN, DATA_GSREAL_BEGIN_TOKEN)) + { + //Number + Skip(); + string numberString = ReadInputUntil(DATA_END_TOKEN); + obj = new NSNumber(ValuePreprocessor.Preprocess(numberString, ValuePreprocessor.Type.UNDEFINED_NUMBER)); + } + + //parse data end token + Read(DATA_END_TOKEN); + } + else + { + string dataString = ReadInputUntil(DATA_END_TOKEN); + dataString = Regex.Replace(dataString, "\\s+", ""); + + int numBytes = dataString.Length / 2; + byte[] bytes = new byte[numBytes]; + + for(int i = 0; i < bytes.Length; i++) + { + string byteString = dataString.Substring(i * 2, 2); + int byteValue = Convert.ToInt32(byteString, 16); + bytes[i] = (byte)byteValue; + } + + obj = new NSData(ValuePreprocessor.Preprocess(bytes, ValuePreprocessor.Type.DATA)); + + //skip end token + Skip(); + } + + return obj; + } + + /// Attempts to parse a plain string as a date if possible. + /// A NSDate if the string represents such an object. Otherwise a NSString is returned. + NSObject ParseDateString() + { + string numericalString = ParseString(); + + if(numericalString.Length <= 4 || numericalString[4] != DATE_DATE_FIELD_DELIMITER) + return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.STRING)); + + try + { + return new NSDate(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.DATE)); + } + catch(Exception) + { + //An exception occurs if the string is not a date but just a string + } + + return new NSString(ValuePreprocessor.Preprocess(numericalString, ValuePreprocessor.Type.STRING)); + } + + /// + /// Parses a plain string from the current parsing position. The string is made up of all characters to the next + /// whitespace, delimiter token or assignment token. + /// + /// The string found at the current parsing position. + string ParseString() => ReadInputUntil(WHITESPACE_SPACE, + WHITESPACE_TAB, + WHITESPACE_NEWLINE, + WHITESPACE_CARRIAGE_RETURN, + ARRAY_ITEM_DELIMITER_TOKEN, + DICTIONARY_ITEM_DELIMITER_TOKEN, + DICTIONARY_ASSIGN_TOKEN, + ARRAY_END_TOKEN); + + /// + /// Parses a quoted string from the current parsing position. The prerequisite for calling this method is, that a + /// quoted string begin token has been read. + /// + /// The quoted string found at the parsing method with all special characters unescaped. + /// If an error occured during parsing. + string ParseQuotedString() + { + //Skip begin token + Skip(); + string quotedString = ""; + bool unescapedBackslash = true; + + //Read from opening quotation marks to closing quotation marks and skip escaped quotation marks + while(data[index] != QUOTEDSTRING_END_TOKEN || + data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash) + { + quotedString += data[index]; + + if(Accept(QUOTEDSTRING_ESCAPE_TOKEN)) + unescapedBackslash = !(data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash); + + Skip(); + } + + string unescapedString; + + try + { + unescapedString = ParseQuotedString(quotedString); + } + catch(Exception) + { + throw new FormatException($"The quoted string could not be parsed at {index}."); + } + + //skip end token + Skip(); + + return unescapedString; + } + + /// + /// Parses a string according to the format specified for ASCII property lists. Such strings can contain escape + /// sequences which are unescaped in this method. + /// + /// The unescaped string in UTF-8 or ASCII format, depending on the contained characters. + /// + /// The escaped string according to the ASCII property list format, without leading and trailing quotation + /// marks. + /// + /// If the en-/decoder for the UTF-8 or ASCII encoding could not be loaded + /// If the string is encoded neither in ASCII nor in UTF-8 + public static string ParseQuotedString(string s) + { + List strBytes = []; + + IEnumerable characters = s.ToCharArray(); + IEnumerator c = characters.GetEnumerator(); + + while(c.MoveNext()) + { + switch(c.Current) + { + case '\\': + { + //An escaped sequence is following + byte[] bts = Encoding.UTF8.GetBytes(ParseEscapedSequence(c)); + + strBytes.AddRange(bts); + + break; + } + default: + { + //a normal ASCII char + strBytes.AddRange(Encoding.BigEndianUnicode.GetBytes([ + c.Current + ])); + + break; + } + } + } + + byte[] bytArr = new byte[strBytes.Count]; + int i = 0; + + foreach(byte b in strBytes) + { + bytArr[i] = b; + i++; + } + + //Build string + string result = Encoding.BigEndianUnicode.GetString(bytArr); + + //If the string can be represented in the ASCII codepage + // --> use ASCII encoding + if(IsASCIIEncodable(result)) + return Encoding.ASCII.GetString(Encoding.Convert(Encoding.BigEndianUnicode, Encoding.ASCII, bytArr)); + + //The string contains characters outside the ASCII codepage + // --> use the UTF-8 encoded string + return result; + } + + /// Unescapes an escaped character sequence, e.g. \\u00FC. + /// The unescaped character as a string. + /// The string character iterator pointing to the first character after the backslash + /// If an invalid Unicode or ASCII escape sequence is found. + static string ParseEscapedSequence(IEnumerator iterator) + { + iterator.MoveNext(); + char c = iterator.Current; + + switch(c) + { + case '\\': + return Encoding.UTF8.GetString("\0\\"u8.ToArray()); + case '"': + return Encoding.UTF8.GetString("\0\""u8.ToArray()); + case 'b': + return Encoding.UTF8.GetString(new byte[] + { + 0, (byte)'\b' + }); + case 'n': + return Encoding.UTF8.GetString("\0\n"u8.ToArray()); + case 'r': + return Encoding.UTF8.GetString("\0\r"u8.ToArray()); + case 't': + return Encoding.UTF8.GetString("\0\t"u8.ToArray()); + case 'U': + case 'u': + { + //4 digit hex Unicode value + string byte1 = ""; + iterator.MoveNext(); + byte1 += iterator.Current; + iterator.MoveNext(); + byte1 += iterator.Current; + string byte2 = ""; + iterator.MoveNext(); + byte2 += iterator.Current; + iterator.MoveNext(); + byte2 += iterator.Current; + + byte[] stringBytes = + [ + (byte)Convert.ToInt32(byte1, 16), (byte)Convert.ToInt32(byte2, 16) + ]; + + return Encoding.UTF8.GetString(stringBytes); + } + default: + { + //3 digit octal ASCII value + string num = ""; + num += c; + iterator.MoveNext(); + num += iterator.Current; + iterator.MoveNext(); + num += iterator.Current; + int asciiCode = Convert.ToInt32(num, 8); + + byte[] stringBytes = + [ + 0, (byte)asciiCode + ]; + + return Encoding.UTF8.GetString(stringBytes); + } + } + } + + internal static bool IsASCIIEncodable(string text) + { + foreach(char c in text) + if(c > 0x7F) return false; + + return true; + } } \ No newline at end of file diff --git a/plist-cil/BinaryPropertyListParser.cs b/plist-cil/BinaryPropertyListParser.cs index 3559714..7dae212 100644 --- a/plist-cil/BinaryPropertyListParser.cs +++ b/plist-cil/BinaryPropertyListParser.cs @@ -30,568 +30,570 @@ using System.IO; using System.Numerics; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// +/// 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. +/// +/// +/// Parsing is done by calling the static , and +/// methods. +/// +/// +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class BinaryPropertyListParser { - /// - /// - /// 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. - /// - /// - /// Parsing is done by calling the static , and - /// methods. - /// - /// - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class BinaryPropertyListParser + static readonly Encoding utf16BigEndian = Encoding.GetEncoding("UTF-16BE"); + + /// Major version of the property list format + int majorVersion; + + /// Minor version of the property list format + int minorVersion; + + /// Length of an object reference in bytes + int objectRefSize; + + /// The table holding the information at which offset each object is found + int[] offsetTable; + + /// Protected constructor so that instantiation is fully controlled by the static parse methods. + /// + protected BinaryPropertyListParser() {} + + /// Parses a binary property list from a byte array. + /// The binary property list's data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When the property list's format could not be parsed. + public static NSObject Parse(byte[] data) => Parse(data.AsSpan()); + + /// Parses a binary property list from a byte array. + /// The binary property list's data. + /// The length of the property list. + /// The offset at which to start reading the property list. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When the property list's format could not be parsed. + public static NSObject Parse(byte[] data, int offset, int length) => Parse(data.AsSpan(offset, length)); + + /// Parses a binary property list from a byte span. + /// The binary property list's data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When the property list's format could not be parsed. + public static NSObject Parse(ReadOnlySpan data) { - static readonly Encoding utf16BigEndian = Encoding.GetEncoding("UTF-16BE"); + var parser = new BinaryPropertyListParser(); - /// Major version of the property list format - int majorVersion; + return parser.DoParse(data); + } - /// Minor version of the property list format - int minorVersion; - - /// Length of an object reference in bytes - int objectRefSize; - - /// The table holding the information at which offset each object is found - int[] offsetTable; - - /// Protected constructor so that instantiation is fully controlled by the static parse methods. - /// - protected BinaryPropertyListParser() {} - - /// Parses a binary property list from a byte array. - /// The binary property list's data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When the property list's format could not be parsed. - public static NSObject Parse(byte[] data) => Parse(data.AsSpan()); - - /// Parses a binary property list from a byte array. - /// The binary property list's data. - /// The length of the property list. - /// The offset at which to start reading the property list. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When the property list's format could not be parsed. - public static NSObject Parse(byte[] data, int offset, int length) => Parse(data.AsSpan(offset, length)); - - /// Parses a binary property list from a byte span. - /// The binary property list's data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When the property list's format could not be parsed. - public static NSObject Parse(ReadOnlySpan data) + /// Parses a binary property list from a byte array. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// The binary property list's data. + /// When the property list's format could not be parsed. + protected NSObject DoParse(ReadOnlySpan bytes) + { + if(bytes.Length < 8 || + bytes[0] != 'b' || + bytes[1] != 'p' || + bytes[2] != 'l' || + bytes[3] != 'i' || + bytes[4] != 's' || + bytes[5] != 't') { - var parser = new BinaryPropertyListParser(); + string magic = Encoding.ASCII.GetString(bytes.Slice(0, 8).ToArray()); - return parser.DoParse(data); + throw new PropertyListFormatException("The given data is no binary property list. Wrong magic bytes: " + + magic); } - /// Parses a binary property list from a byte array. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// The binary property list's data. - /// When the property list's format could not be parsed. - protected NSObject DoParse(ReadOnlySpan bytes) + majorVersion = bytes[6] - 0x30; //ASCII number + minorVersion = bytes[7] - 0x30; //ASCII number + + // 0.0 - OS X Tiger and earlier + // 0.1 - Leopard + // 0.? - Snow Leopard + // 1.5 - Lion + // 2.0 - Snow Lion + + if(majorVersion > 0) { - if(bytes.Length < 8 || - bytes[0] != 'b' || - bytes[1] != 'p' || - bytes[2] != 'l' || - bytes[3] != 'i' || - bytes[4] != 's' || - bytes[5] != 't') - { - string magic = Encoding.ASCII.GetString(bytes.Slice(0, 8).ToArray()); - - throw new PropertyListFormatException("The given data is no binary property list. Wrong magic bytes: " + - magic); - } - - majorVersion = bytes[6] - 0x30; //ASCII number - minorVersion = bytes[7] - 0x30; //ASCII number - - // 0.0 - OS X Tiger and earlier - // 0.1 - Leopard - // 0.? - Snow Leopard - // 1.5 - Lion - // 2.0 - Snow Lion - - if(majorVersion > 0) - throw new PropertyListFormatException("Unsupported binary property list format: v" + majorVersion + - "." + minorVersion + ". " + - "Version 1.0 and later are not yet supported."); - - /* - * Handle trailer, last 32 bytes of the file - */ - ReadOnlySpan trailer = bytes.Slice(bytes.Length - 32, 32); - - //6 null bytes (index 0 to 5) - int offsetSize = trailer[6]; - objectRefSize = trailer[7]; - int numObjects = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(8, 8)); - int topObject = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(16, 8)); - int offsetTableOffset = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(24, 8)); - - /* - * Handle offset table - */ - offsetTable = new int[numObjects]; - - for(int i = 0; i < numObjects; i++) - { - ReadOnlySpan offsetBytes = bytes.Slice(offsetTableOffset + (i * offsetSize), offsetSize); - offsetTable[i] = (int)ParseUnsignedInt(offsetBytes); - } - - return ParseObject(bytes, topObject); + throw new PropertyListFormatException("Unsupported binary property list format: v" + + majorVersion + + "." + + minorVersion + + ". " + + "Version 1.0 and later are not yet supported."); } - /// Parses a binary property list from an input stream. - /// The input stream that points to the property list's data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When the property list's format could not be parsed. - public static NSObject Parse(Stream fs) - { - //Read all bytes into a list - byte[] buf = PropertyListParser.ReadAll(fs); + /* + * Handle trailer, last 32 bytes of the file + */ + ReadOnlySpan trailer = bytes.Slice(bytes.Length - 32, 32); - // Don't close the stream - that would be the responsibility of code that class - // Parse - return Parse(buf); + //6 null bytes (index 0 to 5) + int offsetSize = trailer[6]; + objectRefSize = trailer[7]; + int numObjects = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(8, 8)); + int topObject = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(16, 8)); + int offsetTableOffset = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(24, 8)); + + /* + * Handle offset table + */ + offsetTable = new int[numObjects]; + + for(int i = 0; i < numObjects; i++) + { + ReadOnlySpan offsetBytes = bytes.Slice(offsetTableOffset + i * offsetSize, offsetSize); + offsetTable[i] = (int)ParseUnsignedInt(offsetBytes); } - /// Parses a binary property list file. - /// The binary property list file - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - /// When the property list's format could not be parsed. - public static NSObject Parse(FileInfo f) => Parse(f.OpenRead()); + return ParseObject(bytes, topObject); + } - protected int GetOffset(int obj) => offsetTable[obj]; + /// Parses a binary property list from an input stream. + /// The input stream that points to the property list's data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When the property list's format could not be parsed. + public static NSObject Parse(Stream fs) + { + //Read all bytes into a list + byte[] buf = PropertyListParser.ReadAll(fs); - /// - /// Parses an object inside the currently parsed binary property list. For the format specification check - /// - /// Apple's binary property list parser - /// implementation - /// - /// . - /// - /// The parsed object. - /// The object ID. - /// When the property list's format could not be parsed. - protected virtual NSObject ParseObject(ReadOnlySpan bytes, int obj) + // Don't close the stream - that would be the responsibility of code that class + // Parse + return Parse(buf); + } + + /// Parses a binary property list file. + /// The binary property list file + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + /// When the property list's format could not be parsed. + public static NSObject Parse(FileInfo f) => Parse(f.OpenRead()); + + protected int GetOffset(int obj) => offsetTable[obj]; + + /// + /// Parses an object inside the currently parsed binary property list. For the format specification check + /// + /// Apple's binary property list parser + /// implementation + /// + /// . + /// + /// The parsed object. + /// The object ID. + /// When the property list's format could not be parsed. + protected virtual NSObject ParseObject(ReadOnlySpan bytes, int obj) + { + int offset = offsetTable[obj]; + byte type = bytes[offset]; + int objType = (type & 0xF0) >> 4; //First 4 bits + int objInfo = type & 0x0F; //Second 4 bits + + switch(objType) { - int offset = offsetTable[obj]; - byte type = bytes[offset]; - int objType = (type & 0xF0) >> 4; //First 4 bits - int objInfo = type & 0x0F; //Second 4 bits - - switch(objType) + case 0x0: { - case 0x0: + //Simple + switch(objInfo) { - //Simple - switch(objInfo) + case 0x0: { - case 0x0: - { - //null object (v1.0 and later) - return null; - } - case 0x8: - { - //false - return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); - } - case 0x9: - { - //true - return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); - } - case 0xC: - { - //URL with no base URL (v1.0 and later) - //TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17) - break; - } - case 0xD: - { - //URL with base URL (v1.0 and later) - //TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17) - break; - } - case 0xE: - { - //16-byte UUID (v1.0 and later) - //TODO Implement binary UUID parsing (not yet even implemented in Core Foundation as of revision 855.17) - break; - } - case 0xF: - { - //filler byte - return null; - } + //null object (v1.0 and later) + return null; } - - break; - } - case 0x1: - { - //integer - int length = 1 << objInfo; - - return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), ValuePreprocessor.Type.INTEGER), NSNumber.INTEGER); - } - case 0x2: - { - //real - int length = 1 << objInfo; - - return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), ValuePreprocessor.Type.FLOATING_POINT), NSNumber.REAL); - } - case 0x3: - { - //Date - if(objInfo != 0x3) - throw new - PropertyListFormatException("The given binary property list contains a date object of an unknown type (" + - objInfo + ")"); - - return new NSDate(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, 8).ToArray(), ValuePreprocessor.Type.DATE)); - } - case 0x4: - { - //Data - 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)); - } - case 0x5: - { - //ASCII String, each character is 1 byte - 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); - } - case 0x6: - { - //UTF-16-BE String - ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset); - - //UTF-16 characters can have variable length, but the Core Foundation reference implementation - //assumes 2 byte characters, thus only covering the Basic Multilingual Plane - length *= 2; - - return new NSString(bytes.Slice(offset + stroffset, length), utf16BigEndian); - } - case 0x7: - { - //UTF-8 string (v1.0 and later) - ReadLengthAndOffset(bytes, objInfo, offset, out int strOffset, out int characters); - - //UTF-8 characters can have variable length, so we need to calculate the byte length dynamically - //by reading the UTF-8 characters one by one - int length = CalculateUtf8StringLength(bytes, offset + strOffset, characters); - - return new NSString(bytes.Slice(offset + strOffset, length), Encoding.UTF8); - } - case 0x8: - { - //UID (v1.0 and later) - int length = objInfo + 1; - - return new UID(bytes.Slice(offset + 1, length)); - } - case 0xA: - { - //Array - ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int arrayOffset); - - var array = new NSArray(length); - - for(int i = 0; i < length; i++) + case 0x8: { - int objRef = - (int)ParseUnsignedInt(bytes.Slice(offset + arrayOffset + (i * objectRefSize), - objectRefSize)); - - array.Add(ParseObject(bytes, objRef)); + //false + return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); } - - return array; - } - case 0xB: - { - //Ordered set (v1.0 and later) - ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); - - var set = new NSSet(true); - - for(int i = 0; i < length; i++) + case 0x9: { - int objRef = - (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize), - objectRefSize)); - - set.AddObject(ParseObject(bytes, objRef)); + //true + return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); } - - return set; - } - case 0xC: - { - //Set (v1.0 and later) - ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); - - var set = new NSSet(); - - for(int i = 0; i < length; i++) + case 0xC: { - int objRef = - (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize), - objectRefSize)); - - set.AddObject(ParseObject(bytes, objRef)); + //URL with no base URL (v1.0 and later) + //TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17) + break; } - - return set; - } - case 0xD: - { - //Dictionary - ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); - - //System.out.println("Parsing dictionary #"+obj); - var dict = new NSDictionary(length); - - for(int i = 0; i < length; i++) + case 0xD: { - int keyRef = - (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize), - objectRefSize)); - - int valRef = - (int) - ParseUnsignedInt(bytes.Slice(offset + contentOffset + (length * objectRefSize) + (i * objectRefSize), - objectRefSize)); - - NSObject key = ParseObject(bytes, keyRef); - NSObject val = ParseObject(bytes, valRef); - dict.Add(key.ToString(), val); + //URL with base URL (v1.0 and later) + //TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17) + break; + } + case 0xE: + { + //16-byte UUID (v1.0 and later) + //TODO Implement binary UUID parsing (not yet even implemented in Core Foundation as of revision 855.17) + break; + } + case 0xF: + { + //filler byte + return null; } - - return dict; } - default: + + break; + } + case 0x1: + { + //integer + int length = 1 << objInfo; + + return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), + ValuePreprocessor.Type.INTEGER), + NSNumber.INTEGER); + } + case 0x2: + { + //real + int length = 1 << objInfo; + + return new NSNumber(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, length).ToArray(), + ValuePreprocessor.Type.FLOATING_POINT), + NSNumber.REAL); + } + case 0x3: + { + //Date + if(objInfo != 0x3) { - Debug.WriteLine("WARNING: The given binary property list contains an object of unknown type (" + - objType + ")"); - - break; + throw new + PropertyListFormatException("The given binary property list contains a date object of an unknown type (" + + objInfo + + ")"); } + + return new NSDate(ValuePreprocessor.Preprocess(bytes.Slice(offset + 1, 8).ToArray(), + ValuePreprocessor.Type.DATE)); } - - return null; - } - - /// Reads the length for arrays, sets and dictionaries. - /// An array with the length two. First entry is the length, second entry the offset at which the content starts. - /// Object information byte. - /// Offset in the byte array at which the object is located. - void ReadLengthAndOffset(ReadOnlySpan bytes, int objInfo, int offset, out int lengthValue, - out int offsetValue) - { - lengthValue = objInfo; - offsetValue = 1; - - if(objInfo == 0xF) + case 0x4: { - int int_type = bytes[offset + 1]; - int intType = (int_type & 0xF0) >> 4; + //Data + ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int dataoffset); - if(intType != 0x1) - Debug.WriteLine("BinaryPropertyListParser: Length integer has an unexpected type" + intType + - ". Attempting to parse anyway..."); + return new NSData(ValuePreprocessor.Preprocess(CopyOfRange(bytes, + offset + dataoffset, + offset + dataoffset + length), + ValuePreprocessor.Type.DATA)); + } + case 0x5: + { + //ASCII String, each character is 1 byte + ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset); - int intInfo = int_type & 0x0F; - int intLength = 1 << intInfo; - offsetValue = 2 + intLength; + return new NSString(ValuePreprocessor.Preprocess(bytes.Slice(offset + stroffset, length).ToArray(), + ValuePreprocessor.Type.STRING), + Encoding.ASCII); + } + case 0x6: + { + //UTF-16-BE String + ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset); - if(intLength < 3) - lengthValue = (int)ParseUnsignedInt(bytes.Slice(offset + 2, intLength)); - else + //UTF-16 characters can have variable length, but the Core Foundation reference implementation + //assumes 2 byte characters, thus only covering the Basic Multilingual Plane + length *= 2; + + return new NSString(bytes.Slice(offset + stroffset, length), utf16BigEndian); + } + case 0x7: + { + //UTF-8 string (v1.0 and later) + ReadLengthAndOffset(bytes, objInfo, offset, out int strOffset, out int characters); + + //UTF-8 characters can have variable length, so we need to calculate the byte length dynamically + //by reading the UTF-8 characters one by one + int length = CalculateUtf8StringLength(bytes, offset + strOffset, characters); + + return new NSString(bytes.Slice(offset + strOffset, length), Encoding.UTF8); + } + case 0x8: + { + //UID (v1.0 and later) + int length = objInfo + 1; + + return new UID(bytes.Slice(offset + 1, length)); + } + case 0xA: + { + //Array + ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int arrayOffset); + + var array = new NSArray(length); + + for(int i = 0; i < length; i++) { - // BigInteger is Little-Endian in .NET, swap the thing. - // Also BigInteger is of .NET 4.0, maybe there's a better way to do it. - byte[] bigEBigInteger = bytes.Slice(offset + 2, intLength).ToArray(); - Array.Reverse(bigEBigInteger); - bigEBigInteger[bigEBigInteger.Length - 1] = 0x00; // Be sure to get unsigned BigInteger + int objRef = + (int)ParseUnsignedInt(bytes.Slice(offset + arrayOffset + i * objectRefSize, objectRefSize)); - lengthValue = (int)new BigInteger(bigEBigInteger); + array.Add(ParseObject(bytes, objRef)); } + + return array; } - } - - /// Calculates the length in bytes of the UTF-8 string. - /// The UTF-8 string length. - /// Array containing the UTF-8 string. - /// Offset in the array where the UTF-8 string resides. - /// How many UTF-8 characters are in the string. - int CalculateUtf8StringLength(ReadOnlySpan bytes, int offset, int numCharacters) - { - int length = 0; - - for(int i = 0; i < numCharacters; i++) + case 0xB: { - int tempOffset = offset + length; + //Ordered set (v1.0 and later) + ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); - if(bytes.Length <= tempOffset) - return numCharacters; + var set = new NSSet(true); - if(bytes[tempOffset] < 0x80) - length++; - - if(bytes[tempOffset] < 0xC2) - return numCharacters; - - if(bytes[tempOffset] < 0xE0) + for(int i = 0; i < length; i++) { - if((bytes[tempOffset + 1] & 0xC0) != 0x80) - return numCharacters; + int objRef = + (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize)); - length += 2; + set.AddObject(ParseObject(bytes, objRef)); } - else if(bytes[tempOffset] < 0xF0) + + return set; + } + case 0xC: + { + //Set (v1.0 and later) + ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); + + var set = new NSSet(); + + for(int i = 0; i < length; i++) { - if((bytes[tempOffset + 1] & 0xC0) != 0x80 || - (bytes[tempOffset + 2] & 0xC0) != 0x80) - return numCharacters; + int objRef = + (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize)); - length += 3; + set.AddObject(ParseObject(bytes, objRef)); } - else if(bytes[tempOffset] < 0xF5) + + return set; + } + case 0xD: + { + //Dictionary + ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); + + //System.out.println("Parsing dictionary #"+obj); + var dict = new NSDictionary(length); + + for(int i = 0; i < length; i++) { - if((bytes[tempOffset + 1] & 0xC0) != 0x80 || - (bytes[tempOffset + 2] & 0xC0) != 0x80 || - (bytes[tempOffset + 3] & 0xC0) != 0x80) - return numCharacters; + int keyRef = + (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize)); - length += 4; + int valRef = + (int)ParseUnsignedInt(bytes.Slice(offset + + contentOffset + + length * objectRefSize + + i * objectRefSize, + objectRefSize)); + + NSObject key = ParseObject(bytes, keyRef); + NSObject val = ParseObject(bytes, valRef); + dict.Add(key.ToString(), val); } - } - return length; + return dict; + } + default: + { + Debug.WriteLine("WARNING: The given binary property list contains an object of unknown type (" + + objType + + ")"); + + break; + } } - /// Parses an unsigned integer from a span. - /// The byte array containing the unsigned integer. - /// The unsigned integer represented by the given bytes. - public static long ParseUnsignedInt(ReadOnlySpan bytes) + return null; + } + + /// Reads the length for arrays, sets and dictionaries. + /// An array with the length two. First entry is the length, second entry the offset at which the content starts. + /// Object information byte. + /// Offset in the byte array at which the object is located. + void ReadLengthAndOffset(ReadOnlySpan bytes, int objInfo, int offset, out int lengthValue, + out int offsetValue) + { + lengthValue = objInfo; + offsetValue = 1; + + if(objInfo == 0xF) { - if(bytes.Length <= 4) - return ParseLong(bytes); + int int_type = bytes[offset + 1]; + int intType = (int_type & 0xF0) >> 4; - return ParseLong(bytes) & 0xFFFFFFFFL; - } - - /// Parses an unsigned integers from a byte array. - /// The byte array containing the unsigned integer. - /// The unsigned integer represented by the given bytes. - public static long ParseUnsignedInt(byte[] bytes) => ParseUnsignedInt(bytes.AsSpan()); - - /// Parses a long from a (big-endian) byte array. - /// The long integer represented by the given bytes. - /// The bytes representing the long integer. - public static long ParseLong(ReadOnlySpan bytes) - { - if(bytes == null) + if(intType != 0x1) { - throw new ArgumentNullException(nameof(bytes)); + Debug.WriteLine("BinaryPropertyListParser: Length integer has an unexpected type" + + intType + + ". Attempting to parse anyway..."); } - if(bytes.Length == 0) + int intInfo = int_type & 0x0F; + int intLength = 1 << intInfo; + offsetValue = 2 + intLength; + + if(intLength < 3) + lengthValue = (int)ParseUnsignedInt(bytes.Slice(offset + 2, intLength)); + else { - throw new ArgumentOutOfRangeException(nameof(bytes)); + // BigInteger is Little-Endian in .NET, swap the thing. + // Also BigInteger is of .NET 4.0, maybe there's a better way to do it. + byte[] bigEBigInteger = bytes.Slice(offset + 2, intLength).ToArray(); + Array.Reverse(bigEBigInteger); + bigEBigInteger[bigEBigInteger.Length - 1] = 0x00; // Be sure to get unsigned BigInteger + + lengthValue = (int)new BigInteger(bigEBigInteger); } - - // https://opensource.apple.com/source/CF/CF-1153.18/CFBinaryPList.c, - // __CFBinaryPlistCreateObjectFiltered, case kCFBinaryPlistMarkerInt: - // - // in format version '00', 1, 2, and 4-byte integers have to be interpreted as unsigned, - // whereas 8-byte integers are signed (and 16-byte when available) - // negative 1, 2, 4-byte integers are always emitted as 8 bytes in format '00' - // integers are not required to be in the most compact possible representation, - // but only the last 64 bits are significant currently - switch(bytes.Length) - { - case 1: return bytes[0]; - - case 2: return BinaryPrimitives.ReadUInt16BigEndian(bytes); - - case 4: return BinaryPrimitives.ReadUInt32BigEndian(bytes); - - // Transition from unsigned to signed - case 8: return BinaryPrimitives.ReadInt64BigEndian(bytes); - - // Only the last 64 bits are significant currently - case 16: return BinaryPrimitives.ReadInt64BigEndian(bytes.Slice(8)); - } - - if(bytes.Length >= 8) - 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 - // size and 16-byte values, and architectures that don't support unaligned access - long value = 0; - - for(int i = 0; i < bytes.Length; i++) - { - value = (value << 8) + bytes[i]; - } - - return value; - - // Theoretically we could handle non-power-of-2 byte arrays larger than 8, with the code - // above, and it appears the reference implementation does exactly that. But it seems to - // be an extreme edge case. - } - - /// Parses a double from a (big-endian) byte array. - /// The double represented by the given bytes. - /// The bytes representing the double. - public static double ParseDouble(ReadOnlySpan bytes) - { - if(bytes == null) - { - throw new ArgumentNullException(nameof(bytes)); - } - - return bytes.Length switch - { - 8 => BitConverter.Int64BitsToDouble(ParseLong(bytes)), - 4 => BitConverter.ToSingle(BitConverter.GetBytes(ParseLong(bytes)), 0), - _ => throw new ArgumentException("bad byte array length " + bytes.Length) - }; - } - - /// Copies a part of a byte array into a new array. - /// The copied array. - /// The source array. - /// The index from which to start copying. - /// The index until which to copy. - public static byte[] CopyOfRange(ReadOnlySpan src, int startIndex, int endIndex) - { - int length = endIndex - startIndex; - - if(length < 0) - throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex + - ")"); - - return src.Slice(startIndex, endIndex - startIndex).ToArray(); } } + + /// Calculates the length in bytes of the UTF-8 string. + /// The UTF-8 string length. + /// Array containing the UTF-8 string. + /// Offset in the array where the UTF-8 string resides. + /// How many UTF-8 characters are in the string. + int CalculateUtf8StringLength(ReadOnlySpan bytes, int offset, int numCharacters) + { + int length = 0; + + for(int i = 0; i < numCharacters; i++) + { + int tempOffset = offset + length; + + if(bytes.Length <= tempOffset) return numCharacters; + + if(bytes[tempOffset] < 0x80) length++; + + if(bytes[tempOffset] < 0xC2) return numCharacters; + + if(bytes[tempOffset] < 0xE0) + { + if((bytes[tempOffset + 1] & 0xC0) != 0x80) return numCharacters; + + length += 2; + } + else if(bytes[tempOffset] < 0xF0) + { + if((bytes[tempOffset + 1] & 0xC0) != 0x80 || (bytes[tempOffset + 2] & 0xC0) != 0x80) + return numCharacters; + + length += 3; + } + else if(bytes[tempOffset] < 0xF5) + { + if((bytes[tempOffset + 1] & 0xC0) != 0x80 || + (bytes[tempOffset + 2] & 0xC0) != 0x80 || + (bytes[tempOffset + 3] & 0xC0) != 0x80) + return numCharacters; + + length += 4; + } + } + + return length; + } + + /// Parses an unsigned integer from a span. + /// The byte array containing the unsigned integer. + /// The unsigned integer represented by the given bytes. + public static long ParseUnsignedInt(ReadOnlySpan bytes) + { + if(bytes.Length <= 4) return ParseLong(bytes); + + return ParseLong(bytes) & 0xFFFFFFFFL; + } + + /// Parses an unsigned integers from a byte array. + /// The byte array containing the unsigned integer. + /// The unsigned integer represented by the given bytes. + public static long ParseUnsignedInt(byte[] bytes) => ParseUnsignedInt(bytes.AsSpan()); + + /// Parses a long from a (big-endian) byte array. + /// The long integer represented by the given bytes. + /// The bytes representing the long integer. + public static long ParseLong(ReadOnlySpan bytes) + { + if(bytes == null) throw new ArgumentNullException(nameof(bytes)); + + if(bytes.Length == 0) throw new ArgumentOutOfRangeException(nameof(bytes)); + + // https://opensource.apple.com/source/CF/CF-1153.18/CFBinaryPList.c, + // __CFBinaryPlistCreateObjectFiltered, case kCFBinaryPlistMarkerInt: + // + // in format version '00', 1, 2, and 4-byte integers have to be interpreted as unsigned, + // whereas 8-byte integers are signed (and 16-byte when available) + // negative 1, 2, 4-byte integers are always emitted as 8 bytes in format '00' + // integers are not required to be in the most compact possible representation, + // but only the last 64 bits are significant currently + switch(bytes.Length) + { + case 1: + return bytes[0]; + + case 2: + return BinaryPrimitives.ReadUInt16BigEndian(bytes); + + case 4: + return BinaryPrimitives.ReadUInt32BigEndian(bytes); + + // Transition from unsigned to signed + case 8: + return BinaryPrimitives.ReadInt64BigEndian(bytes); + + // Only the last 64 bits are significant currently + case 16: + return BinaryPrimitives.ReadInt64BigEndian(bytes.Slice(8)); + } + + if(bytes.Length >= 8) 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 + // size and 16-byte values, and architectures that don't support unaligned access + long value = 0; + + for(int i = 0; i < bytes.Length; i++) value = (value << 8) + bytes[i]; + + return value; + + // Theoretically we could handle non-power-of-2 byte arrays larger than 8, with the code + // above, and it appears the reference implementation does exactly that. But it seems to + // be an extreme edge case. + } + + /// Parses a double from a (big-endian) byte array. + /// The double represented by the given bytes. + /// The bytes representing the double. + public static double ParseDouble(ReadOnlySpan bytes) + { + if(bytes == null) throw new ArgumentNullException(nameof(bytes)); + + return bytes.Length switch + { + 8 => BitConverter.Int64BitsToDouble(ParseLong(bytes)), + 4 => BitConverter.ToSingle(BitConverter.GetBytes(ParseLong(bytes)), 0), + _ => throw new ArgumentException("bad byte array length " + bytes.Length) + }; + } + + /// Copies a part of a byte array into a new array. + /// The copied array. + /// The source array. + /// The index from which to start copying. + /// The index until which to copy. + public static byte[] CopyOfRange(ReadOnlySpan src, int startIndex, int endIndex) + { + int length = endIndex - startIndex; + + if(length < 0) throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex + ")"); + + return src.Slice(startIndex, endIndex - startIndex).ToArray(); + } } \ No newline at end of file diff --git a/plist-cil/BinaryPropertyListWriter.AddObjectEqualityComparer.cs b/plist-cil/BinaryPropertyListWriter.AddObjectEqualityComparer.cs index cf1aa2d..3f8ffcf 100644 --- a/plist-cil/BinaryPropertyListWriter.AddObjectEqualityComparer.cs +++ b/plist-cil/BinaryPropertyListWriter.AddObjectEqualityComparer.cs @@ -1,37 +1,30 @@ using System; using System.Collections.Generic; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +public partial class BinaryPropertyListWriter { - public partial class BinaryPropertyListWriter + /// + /// The equality comparer which is used when adding an object to the + /// . In most cases, objects are always added. The only exception are very specific strings, which are only added once. + /// + class AddObjectEqualityComparer : EqualityComparer { - /// - /// The equality comparer which is used when adding an object to the - /// . In most cases, objects are always added. The only exception are very specific strings, which are only added once. - /// - class AddObjectEqualityComparer : EqualityComparer + public override bool Equals(NSObject x, NSObject y) { - public override bool Equals(NSObject x, NSObject y) - { - if(x is not NSString a || - y is not NSString b) - return ReferenceEquals(x, y); + if(x is not NSString a || y is not NSString b) return ReferenceEquals(x, y); - if(!IsSerializationPrimitive(a) || - !IsSerializationPrimitive(b)) - return ReferenceEquals(x, y); + if(!IsSerializationPrimitive(a) || !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) - { - if(obj is NSString s && - IsSerializationPrimitive(s)) - return s.Content.GetHashCode(); + public override int GetHashCode(NSObject obj) + { + if(obj is NSString s && IsSerializationPrimitive(s)) return s.Content.GetHashCode(); - return obj.GetHashCode(); - } + return obj.GetHashCode(); } } } \ No newline at end of file diff --git a/plist-cil/BinaryPropertyListWriter.GetObjectEqualityComparer.cs b/plist-cil/BinaryPropertyListWriter.GetObjectEqualityComparer.cs index e6be1de..0057146 100644 --- a/plist-cil/BinaryPropertyListWriter.GetObjectEqualityComparer.cs +++ b/plist-cil/BinaryPropertyListWriter.GetObjectEqualityComparer.cs @@ -1,39 +1,43 @@ using System.Collections.Generic; -namespace Claunia.PropertyList -{ - public partial class BinaryPropertyListWriter - { - /// - /// The equality comparer which is used when retrieving objects in the - /// . The logic is slightly different from - /// , results in two equivalent objects (UIDs mainly) being added to the - /// . Whenever the ID for one of those equivalent objects is requested, - /// the first ID is always returned. This means that there are "orphan" objects in binary property lists - duplicate - /// objects which are never referenced -; this logic exists purely to maintain binary compatibility with Apple's - /// format. - /// - class GetObjectEqualityComparer : EqualityComparer - { - public override bool Equals(NSObject x, NSObject y) => x switch - { - // By default, use reference equality. Even if there are two objects - say a NSString - with the same - // value, do not consider them equal unless they are the same instance of NSString. - // 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". - UID => x.Equals(y), - NSNumber number when IsSerializationPrimitive(number) => number.Equals(y), - NSString nsString when IsSerializationPrimitive(nsString) => nsString.Equals(y), - _ => ReferenceEquals(x, y) - }; +namespace Claunia.PropertyList; - public override int GetHashCode(NSObject obj) => obj switch - { - UID u => u.GetHashCode(), - NSNumber n when IsSerializationPrimitive(n) => n.ToObject().GetHashCode(), - NSString s when IsSerializationPrimitive(s) => s.Content.GetHashCode(), - _ => obj.GetHashCode() - }; - } +public partial class BinaryPropertyListWriter +{ + /// + /// The equality comparer which is used when retrieving objects in the + /// . The logic is slightly different from + /// , results in two equivalent objects (UIDs mainly) being added to the + /// . Whenever the ID for one of those equivalent objects is requested, + /// the first ID is always returned. This means that there are "orphan" objects in binary property lists - duplicate + /// objects which are never referenced -; this logic exists purely to maintain binary compatibility with Apple's + /// format. + /// + class GetObjectEqualityComparer : EqualityComparer + { + public override bool Equals(NSObject x, NSObject y) => x switch + { + // By default, use reference equality. Even if there are two objects - say a NSString - with the same + // value, do not consider them equal unless they are the same instance of NSString. + // 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". + UID => x.Equals(y), + NSNumber number when IsSerializationPrimitive(number) + => number.Equals(y), + NSString nsString when + IsSerializationPrimitive(nsString) => nsString + .Equals(y), + _ => ReferenceEquals(x, y) + }; + + public override int GetHashCode(NSObject obj) => obj switch + { + UID u => u.GetHashCode(), + NSNumber n when IsSerializationPrimitive(n) => n.ToObject() + .GetHashCode(), + NSString s when IsSerializationPrimitive(s) => s.Content + .GetHashCode(), + _ => obj.GetHashCode() + }; } } \ No newline at end of file diff --git a/plist-cil/BinaryPropertyListWriter.cs b/plist-cil/BinaryPropertyListWriter.cs index 5ecbe4f..a5fc98b 100644 --- a/plist-cil/BinaryPropertyListWriter.cs +++ b/plist-cil/BinaryPropertyListWriter.cs @@ -27,396 +27,386 @@ using System; using System.Collections.Generic; using System.IO; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// A BinaryPropertyListWriter is a helper class for writing out binary property list files. +/// +/// 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. +/// +/// +/// @author Keith Randall +/// @author Natalia Portillo +public partial class BinaryPropertyListWriter { - /// - /// A BinaryPropertyListWriter is a helper class for writing out binary property list files. - /// - /// 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. - /// - /// - /// @author Keith Randall - /// @author Natalia Portillo - public partial class BinaryPropertyListWriter + /// Binary property list version 0.0 + public const int VERSION_00 = 0; + /// Binary property list version 1.0 + public const int VERSION_10 = 10; + /// Binary property list version 1.5 + public const int VERSION_15 = 15; + /// Binary property list version 2.0 + public const int VERSION_20 = 20; + + // map from object to its ID + protected readonly Dictionary idDict = new(new AddObjectEqualityComparer()); + protected readonly Dictionary idDict2 = new(new GetObjectEqualityComparer()); + + // raw output stream to result file + readonly Stream outStream; + + readonly int version = VERSION_00; + + // # of bytes written so far + long count; + protected int currentId; + int idSizeInBytes; + + /// Creates a new binary property list writer + /// The output stream into which the binary property list will be written + /// If an error occured while writing to the stream + public BinaryPropertyListWriter(Stream outStr) => outStream = outStr; + + public BinaryPropertyListWriter(Stream outStr, int version) { - /// Binary property list version 0.0 - public const int VERSION_00 = 0; - /// Binary property list version 1.0 - public const int VERSION_10 = 10; - /// Binary property list version 1.5 - public const int VERSION_15 = 15; - /// Binary property list version 2.0 - public const int VERSION_20 = 20; - - // map from object to its ID - protected readonly Dictionary idDict = new(new AddObjectEqualityComparer()); - protected readonly Dictionary idDict2 = new(new GetObjectEqualityComparer()); - - // raw output stream to result file - readonly Stream outStream; - - readonly int version = VERSION_00; - - // # of bytes written so far - long count; - protected int currentId; - int idSizeInBytes; - - /// Creates a new binary property list writer - /// The output stream into which the binary property list will be written - /// If an error occured while writing to the stream - public BinaryPropertyListWriter(Stream outStr) => outStream = outStr; - - public BinaryPropertyListWriter(Stream outStr, int version) - { - this.version = version; - outStream = outStr; - } - - public BinaryPropertyListWriter(Stream outStr, int version, - IEqualityComparer addObjectEqualityComparer, - IEqualityComparer getObjectEqualityComparer) - { - this.version = version; - outStream = outStr; - idDict = new Dictionary(addObjectEqualityComparer); - idDict2 = new Dictionary(getObjectEqualityComparer); - } - - /// - /// Gets or sets a value indicating whether two equivalent objects should be serialized once in the binary - /// property list file, or whether the value should be stored multiple times in the binary property list file. The - /// default is . - /// - /// - /// In most scenarios, you want this to be , as it reduces the size of the binary proeprty - /// list file. However, by default, the Apple tools do not seem to implement this optimization, so set this value to - /// if you want to maintain binary compatibility with the Apple tools. - /// - public bool ReuseObjectIds { get; set; } - - /// Finds out the minimum binary property list format version that can be used to save the given NSObject tree. - /// Version code - /// Object root. - static int GetMinimumRequiredVersion(NSObject root) - { - int minVersion = VERSION_00; - - switch(root) - { - case null: - minVersion = VERSION_10; - - break; - case NSDictionary dict: - { - foreach(NSObject o in dict.GetDictionary().Values) - { - int v = GetMinimumRequiredVersion(o); - - if(v > minVersion) - minVersion = v; - } - - break; - } - case NSArray array: - { - foreach(NSObject o in array) - { - int v = GetMinimumRequiredVersion(o); - - if(v > minVersion) - minVersion = v; - } - - break; - } - case NSSet set: - { - //Sets are only allowed in property lists v1+ - minVersion = VERSION_10; - - foreach(NSObject o in set.AllObjects()) - { - int v = GetMinimumRequiredVersion(o); - - if(v > minVersion) - minVersion = v; - } - - break; - } - } - - return minVersion; - } - - /// Writes a binary plist file with the given object as the root. - /// the file to write to - /// the source of the data to write to the file - /// - public static void Write(FileInfo file, NSObject root) - { - using FileStream fous = file.OpenWrite(); - - Write(fous, root); - } - - /// Writes a binary plist serialization of the given object as the root. - /// the stream to write to - /// the source of the data to write to the stream - /// - public static void Write(Stream outStream, NSObject root) - { - int minVersion = GetMinimumRequiredVersion(root); - - if(minVersion > VERSION_00) - { - string versionString = minVersion == VERSION_10 - ? "v1.0" - : minVersion == VERSION_15 - ? "v1.5" - : minVersion == VERSION_20 - ? "v2.0" - : "v0.0"; - - throw new IOException("The given property list structure cannot be saved. " + - "The required version of the binary format (" + versionString + - ") is not yet supported."); - } - - var w = new BinaryPropertyListWriter(outStream, minVersion); - w.Write(root); - } - - /// Writes a binary plist serialization of the given object as the root into a byte array. - /// The byte array containing the serialized property list - /// The root object of the property list - /// - public static byte[] WriteToArray(NSObject root) - { - var bout = new MemoryStream(); - Write(bout, root); - - return bout.ToArray(); - } - - public void Write(NSObject root) - { - // magic bytes - Write(new[] - { - (byte)'b', (byte)'p', (byte)'l', (byte)'i', (byte)'s', (byte)'t' - }); - - //version - switch(version) - { - case VERSION_00: - { - Write(new[] - { - (byte)'0', (byte)'0' - }); - - break; - } - case VERSION_10: - { - Write(new[] - { - (byte)'1', (byte)'0' - }); - - break; - } - case VERSION_15: - { - Write(new[] - { - (byte)'1', (byte)'5' - }); - - break; - } - case VERSION_20: - { - Write(new[] - { - (byte)'2', (byte)'0' - }); - - break; - } - } - - // assign IDs to all the objects. - root.AssignIDs(this); - - idSizeInBytes = ComputeIdSizeInBytes(idDict.Count); - - // offsets of each object, indexed by ID - long[] offsets = new long[idDict.Count]; - - // write each object, save offset - foreach(KeyValuePair pair in idDict) - { - NSObject obj = pair.Key; - int id = pair.Value; - offsets[id] = count; - - if(obj == null) - Write(0x00); - else - obj.ToBinary(this); - } - - // write offset table - long offsetTableOffset = count; - int offsetSizeInBytes = ComputeOffsetSizeInBytes(count); - - foreach(long offset in offsets) - WriteBytes(offset, offsetSizeInBytes); - - if(version != VERSION_15) - { - // write trailer - // 6 null bytes - Write(new byte[6]); - - // size of an offset - Write(offsetSizeInBytes); - - // size of a ref - Write(idSizeInBytes); - - // number of objects - WriteLong(idDict.Count); - - // top object - int rootID = idDict[root]; - WriteLong(rootID); - - // offset table offset - WriteLong(offsetTableOffset); - } - - outStream.Flush(); - } - - protected internal virtual void AssignID(NSObject obj) - { - if(ReuseObjectIds) - { - if(!idDict.ContainsKey(obj)) - idDict.Add(obj, currentId++); - } - else - { - if(!idDict2.ContainsKey(obj)) - idDict2.Add(obj, currentId); - - if(!idDict.ContainsKey(obj)) - idDict.Add(obj, currentId++); - } - } - - internal int GetID(NSObject obj) => ReuseObjectIds ? idDict[obj] : idDict2[obj]; - - static int ComputeIdSizeInBytes(int numberOfIds) - { - if(numberOfIds < 256) - return 1; - - return numberOfIds < 65536 ? 2 : 4; - } - - static int ComputeOffsetSizeInBytes(long maxOffset) => maxOffset switch - { - < 256 => 1, - < 65536 => 2, - < 4294967296L => 4, - _ => 8 - }; - - internal void WriteIntHeader(int kind, int value) - { - switch(value) - { - case < 0: throw new ArgumentException("value must be greater than or equal to 0", "value"); - case < 15: - Write((kind << 4) + value); - - break; - case < 256: - Write((kind << 4) + 15); - Write(0x10); - WriteBytes(value, 1); - - break; - case < 65536: - Write((kind << 4) + 15); - Write(0x11); - WriteBytes(value, 2); - - break; - default: - Write((kind << 4) + 15); - Write(0x12); - WriteBytes(value, 4); - - break; - } - } - - internal void Write(int b) - { - outStream.WriteByte((byte)b); - count++; - } - - internal void Write(byte[] bytes) - { - outStream.Write(bytes, 0, bytes.Length); - count += bytes.Length; - } - - internal void Write(Span bytes) - { - #if NATIVE_SPAN - outStream.Write(bytes); - count += bytes.Length; - #else - Write(bytes.ToArray()); - #endif - } - - internal void WriteBytes(long value, int bytes) - { - // write low-order bytes big-endian style - for(int i = bytes - 1; i >= 0; i--) - Write((int)(value >> (8 * i))); - } - - internal void WriteID(int id) => WriteBytes(id, idSizeInBytes); - - internal void WriteLong(long value) => WriteBytes(value, 8); - - internal void WriteDouble(double value) => WriteLong(BitConverter.DoubleToInt64Bits(value)); - - internal static bool IsSerializationPrimitive(NSString obj) - { - string content = obj.Content; - - // This is a list of "special" values which are only added once to a binary property - // 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 - "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(); + this.version = version; + outStream = outStr; } + + public BinaryPropertyListWriter(Stream outStr, int version, IEqualityComparer addObjectEqualityComparer, + IEqualityComparer getObjectEqualityComparer) + { + this.version = version; + outStream = outStr; + idDict = new Dictionary(addObjectEqualityComparer); + idDict2 = new Dictionary(getObjectEqualityComparer); + } + + /// + /// Gets or sets a value indicating whether two equivalent objects should be serialized once in the binary + /// property list file, or whether the value should be stored multiple times in the binary property list file. The + /// default is . + /// + /// + /// In most scenarios, you want this to be , as it reduces the size of the binary proeprty + /// list file. However, by default, the Apple tools do not seem to implement this optimization, so set this value to + /// if you want to maintain binary compatibility with the Apple tools. + /// + public bool ReuseObjectIds { get; set; } + + /// Finds out the minimum binary property list format version that can be used to save the given NSObject tree. + /// Version code + /// Object root. + static int GetMinimumRequiredVersion(NSObject root) + { + int minVersion = VERSION_00; + + switch(root) + { + case null: + minVersion = VERSION_10; + + break; + case NSDictionary dict: + { + foreach(NSObject o in dict.GetDictionary().Values) + { + int v = GetMinimumRequiredVersion(o); + + if(v > minVersion) minVersion = v; + } + + break; + } + case NSArray array: + { + foreach(NSObject o in array) + { + int v = GetMinimumRequiredVersion(o); + + if(v > minVersion) minVersion = v; + } + + break; + } + case NSSet set: + { + //Sets are only allowed in property lists v1+ + minVersion = VERSION_10; + + foreach(NSObject o in set.AllObjects()) + { + int v = GetMinimumRequiredVersion(o); + + if(v > minVersion) minVersion = v; + } + + break; + } + } + + return minVersion; + } + + /// Writes a binary plist file with the given object as the root. + /// the file to write to + /// the source of the data to write to the file + /// + public static void Write(FileInfo file, NSObject root) + { + using FileStream fous = file.OpenWrite(); + + Write(fous, root); + } + + /// Writes a binary plist serialization of the given object as the root. + /// the stream to write to + /// the source of the data to write to the stream + /// + public static void Write(Stream outStream, NSObject root) + { + int minVersion = GetMinimumRequiredVersion(root); + + if(minVersion > VERSION_00) + { + string versionString = minVersion == VERSION_10 + ? "v1.0" + : minVersion == VERSION_15 + ? "v1.5" + : minVersion == VERSION_20 + ? "v2.0" + : "v0.0"; + + throw new IOException("The given property list structure cannot be saved. " + + "The required version of the binary format (" + + versionString + + ") is not yet supported."); + } + + var w = new BinaryPropertyListWriter(outStream, minVersion); + w.Write(root); + } + + /// Writes a binary plist serialization of the given object as the root into a byte array. + /// The byte array containing the serialized property list + /// The root object of the property list + /// + public static byte[] WriteToArray(NSObject root) + { + var bout = new MemoryStream(); + Write(bout, root); + + return bout.ToArray(); + } + + public void Write(NSObject root) + { + // magic bytes + Write("bplist"u8.ToArray()); + + //version + switch(version) + { + case VERSION_00: + { + Write("00"u8.ToArray()); + + break; + } + case VERSION_10: + { + Write("10"u8.ToArray()); + + break; + } + case VERSION_15: + { + Write("15"u8.ToArray()); + + break; + } + case VERSION_20: + { + Write("20"u8.ToArray()); + + break; + } + } + + // assign IDs to all the objects. + root.AssignIDs(this); + + idSizeInBytes = ComputeIdSizeInBytes(idDict.Count); + + // offsets of each object, indexed by ID + long[] offsets = new long[idDict.Count]; + + // write each object, save offset + foreach(KeyValuePair pair in idDict) + { + NSObject obj = pair.Key; + int id = pair.Value; + offsets[id] = count; + + if(obj == null) + Write(0x00); + else + obj.ToBinary(this); + } + + // write offset table + long offsetTableOffset = count; + int offsetSizeInBytes = ComputeOffsetSizeInBytes(count); + + foreach(long offset in offsets) WriteBytes(offset, offsetSizeInBytes); + + if(version != VERSION_15) + { + // write trailer + // 6 null bytes + Write(new byte[6]); + + // size of an offset + Write(offsetSizeInBytes); + + // size of a ref + Write(idSizeInBytes); + + // number of objects + WriteLong(idDict.Count); + + // top object + int rootID = idDict[root]; + WriteLong(rootID); + + // offset table offset + WriteLong(offsetTableOffset); + } + + outStream.Flush(); + } + + protected internal virtual void AssignID(NSObject obj) + { + if(ReuseObjectIds) + { + if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++); + } + else + { + if(!idDict2.ContainsKey(obj)) idDict2.Add(obj, currentId); + + if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++); + } + } + + internal int GetID(NSObject obj) => ReuseObjectIds ? idDict[obj] : idDict2[obj]; + + static int ComputeIdSizeInBytes(int numberOfIds) + { + if(numberOfIds < 256) return 1; + + return numberOfIds < 65536 ? 2 : 4; + } + + static int ComputeOffsetSizeInBytes(long maxOffset) => maxOffset switch + { + < 256 => 1, + < 65536 => 2, + < 4294967296L => 4, + _ => 8 + }; + + internal void WriteIntHeader(int kind, int value) + { + switch(value) + { + case < 0: + throw new ArgumentException("value must be greater than or equal to 0", "value"); + case < 15: + Write((kind << 4) + value); + + break; + case < 256: + Write((kind << 4) + 15); + Write(0x10); + WriteBytes(value, 1); + + break; + case < 65536: + Write((kind << 4) + 15); + Write(0x11); + WriteBytes(value, 2); + + break; + default: + Write((kind << 4) + 15); + Write(0x12); + WriteBytes(value, 4); + + break; + } + } + + internal void Write(int b) + { + outStream.WriteByte((byte)b); + count++; + } + + internal void Write(byte[] bytes) + { + outStream.Write(bytes, 0, bytes.Length); + count += bytes.Length; + } + + internal void Write(Span bytes) + { +#if NATIVE_SPAN + outStream.Write(bytes); + count += bytes.Length; +#else + Write(bytes.ToArray()); +#endif + } + + internal void WriteBytes(long value, int bytes) + { + // write low-order bytes big-endian style + for(int i = bytes - 1; i >= 0; i--) Write((int)(value >> 8 * i)); + } + + internal void WriteID(int id) => WriteBytes(id, idSizeInBytes); + + internal void WriteLong(long value) => WriteBytes(value, 8); + + internal void WriteDouble(double value) => WriteLong(BitConverter.DoubleToInt64Bits(value)); + + internal static bool IsSerializationPrimitive(NSString obj) + { + string content = obj.Content; + + // This is a list of "special" values which are only added once to a binary property + // 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 "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(); } \ No newline at end of file diff --git a/plist-cil/NSArray.IList.cs b/plist-cil/NSArray.IList.cs index bc948cb..2f8aa9a 100644 --- a/plist-cil/NSArray.IList.cs +++ b/plist-cil/NSArray.IList.cs @@ -22,59 +22,58 @@ using System.Collections; using System.Collections.Generic; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +partial class NSArray : IList { - partial class NSArray : IList + /// + public NSObject this[int index] { - /// - public NSObject this[int index] - { - get => array[index]; + get => array[index]; - set => array[index] = value; - } - - /// - public bool IsReadOnly => false; - - /// - public void Add(NSObject item) => array.Add(item); - - /// - public void Clear() => array.Clear(); - - /// - public bool Contains(NSObject item) => array.Contains(item); - - /// - public void CopyTo(NSObject[] array, int arrayIndex) => this.array.CopyTo(array, arrayIndex); - - /// - public IEnumerator GetEnumerator() => array.GetEnumerator(); - - /// - public int IndexOf(NSObject item) => array.IndexOf(item); - - /// - public void Insert(int index, NSObject item) => array.Insert(index, item); - - /// - public bool Remove(NSObject item) => array.Remove(item); - - /// - public void RemoveAt(int index) => array.RemoveAt(index); - - /// - IEnumerator IEnumerable.GetEnumerator() => array.GetEnumerator(); - - public void Add(object item) => Add(Wrap(item)); - - public bool Contains(object item) => Contains(Wrap(item)); - - public int IndexOf(object item) => array.IndexOf(Wrap(item)); - - public void Insert(int index, object item) => Insert(index, Wrap(item)); - - public bool Remove(object item) => Remove(Wrap(item)); + set => array[index] = value; } + + /// + public bool IsReadOnly => false; + + /// + public void Add(NSObject item) => array.Add(item); + + /// + public void Clear() => array.Clear(); + + /// + public bool Contains(NSObject item) => array.Contains(item); + + /// + public void CopyTo(NSObject[] array, int arrayIndex) => this.array.CopyTo(array, arrayIndex); + + /// + public IEnumerator GetEnumerator() => array.GetEnumerator(); + + /// + public int IndexOf(NSObject item) => array.IndexOf(item); + + /// + public void Insert(int index, NSObject item) => array.Insert(index, item); + + /// + public bool Remove(NSObject item) => array.Remove(item); + + /// + public void RemoveAt(int index) => array.RemoveAt(index); + + /// + IEnumerator IEnumerable.GetEnumerator() => array.GetEnumerator(); + + public void Add(object item) => Add(Wrap(item)); + + public bool Contains(object item) => Contains(Wrap(item)); + + public int IndexOf(object item) => array.IndexOf(Wrap(item)); + + public void Insert(int index, object item) => Insert(index, Wrap(item)); + + public bool Remove(object item) => Remove(Wrap(item)); } \ No newline at end of file diff --git a/plist-cil/NSArray.cs b/plist-cil/NSArray.cs index 588cd66..63ab103 100644 --- a/plist-cil/NSArray.cs +++ b/plist-cil/NSArray.cs @@ -27,336 +27,317 @@ using System; using System.Collections.Generic; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// Represents an Array. +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public partial class NSArray : NSObject { - /// Represents an Array. - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public partial class NSArray : NSObject + readonly List array; + + /// Creates an empty array of the given length. + /// The number of elements this array will be able to hold. + public NSArray(int length) => array = new List(length); + + /// Creates a array from an existing one + /// The array which should be wrapped by the NSArray. + public NSArray(params NSObject[] a) => array = [..a]; + + /// Returns the size of the array. + /// The number of elements that this array can store. + public int Count => array.Count; + + /// Returns the object stored at the given index. + /// The object at the given index. + /// The index of the object. + [Obsolete] + public NSObject ObjectAtIndex(int i) => array[i]; + + /// Remove the i-th element from the array. The array will be resized. + /// The index of the object + [Obsolete] + public void Remove(int i) => array.RemoveAt(i); + + /// Stores an object at the specified index. If there was another object stored at that index it will be replaced. + /// The index where to store the object. + /// The object. + [Obsolete] + public void SetValue(int key, object value) { - readonly List array; + if(value == null) throw new ArgumentNullException("value", "Cannot add null values to an NSArray!"); - /// Creates an empty array of the given length. - /// The number of elements this array will be able to hold. - public NSArray(int length) => array = new List(length); + array[key] = Wrap(value); + } - /// Creates a array from an existing one - /// The array which should be wrapped by the NSArray. - public NSArray(params NSObject[] a) => array = new List(a); + /// + /// Returns the array of NSObjects represented by this NSArray. Any changes to the values of this array will also + /// affect the NSArray. + /// + /// The actual array represented by this NSArray. + [Obsolete] + public NSObject[] GetArray() => array.ToArray(); - /// Returns the size of the array. - /// The number of elements that this array can store. - public int Count => array.Count; + /// Checks whether an object is present in the array or whether it is equal to any of the objects in the array. + /// true, when the object could be found. false otherwise. + /// The object to look for. + [Obsolete] + public bool ContainsObject(object obj) + { + NSObject nso = Wrap(obj); - /// Returns the object stored at the given index. - /// The object at the given index. - /// The index of the object. - [Obsolete] - public NSObject ObjectAtIndex(int i) => array[i]; + foreach(NSObject elem in array) + if(elem.Equals(nso)) return true; - /// Remove the i-th element from the array. The array will be resized. - /// The index of the object - [Obsolete] - public void Remove(int i) => array.RemoveAt(i); + return false; + } - /// Stores an object at the specified index. If there was another object stored at that index it will be replaced. - /// The index where to store the object. - /// The object. - [Obsolete] - public void SetValue(int key, object value) + /// + /// Searches for an object in the array. If it is found its index will be returned. This method also returns an + /// index if the object is not the same as the one stored in the array but has equal contents. + /// + /// The index of the object, if it was found. -1 otherwise. + /// The object to look for. + [Obsolete] + public int IndexOfObject(object obj) + { + NSObject nso = Wrap(obj); + + for(int i = 0; i < array.Count; i++) + if(array[i].Equals(nso)) return i; + + return -1; + } + + /// + /// Searches for an object in the array. If it is found its index will be returned. This method only returns the + /// index of an object that is identical to the given one. Thus objects that might contain the same value as the + /// given one will not be considered. + /// + /// The index of the object, if it was found. -1 otherwise. + /// The object to look for. + [Obsolete] + public int IndexOfIdenticalObject(object obj) + { + NSObject nso = Wrap(obj); + + for(int i = 0; i < array.Count; i++) + if(array[i] == nso) return i; + + return -1; + } + + /// Returns the last object contained in this array. + /// The value of the highest index in the array. + public NSObject LastObject() => array[array.Count - 1]; + + /// + /// Returns a new array containing only the values stored at the given indices. The values are sorted by their + /// index. + /// + /// The new array containing the objects stored at the given indices. + /// The indices of the objects. + public NSObject[] ObjectsAtIndexes(params int[] indexes) + { + var result = new NSObject[indexes.Length]; + Array.Sort(indexes); + + for(int i = 0; i < indexes.Length; i++) result[i] = array[indexes[i]]; + + return result; + } + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(object obj) + { + if(obj is NSArray nsArray) return ArrayEquals(nsArray, this); + + NSObject nso = Wrap(obj); + + if(nso is NSArray nsoArray) return ArrayEquals(nsoArray, this); + + return false; + } + + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a + /// hash table. + /// + public override int GetHashCode() + { + int hash = 7; + hash = 89 * hash + array.GetHashCode(); + + return hash; + } + + internal override void ToXml(StringBuilder xml, int level) + { + Indent(xml, level); + xml.Append(""); + xml.Append(NEWLINE); + + foreach(NSObject o in array) { - if(value == null) - throw new ArgumentNullException("value", "Cannot add null values to an NSArray!"); - - array[key] = Wrap(value); - } - - /// - /// Returns the array of NSObjects represented by this NSArray. Any changes to the values of this array will also - /// affect the NSArray. - /// - /// The actual array represented by this NSArray. - [Obsolete] - public NSObject[] GetArray() => array.ToArray(); - - /// Checks whether an object is present in the array or whether it is equal to any of the objects in the array. - /// true, when the object could be found. false otherwise. - /// The object to look for. - [Obsolete] - public bool ContainsObject(object obj) - { - NSObject nso = Wrap(obj); - - foreach(NSObject elem in array) - if(elem.Equals(nso)) - return true; - - return false; - } - - /// - /// Searches for an object in the array. If it is found its index will be returned. This method also returns an - /// index if the object is not the same as the one stored in the array but has equal contents. - /// - /// The index of the object, if it was found. -1 otherwise. - /// The object to look for. - [Obsolete] - public int IndexOfObject(object obj) - { - NSObject nso = Wrap(obj); - - for(int i = 0; i < array.Count; i++) - if(array[i].Equals(nso)) - return i; - - return -1; - } - - /// - /// Searches for an object in the array. If it is found its index will be returned. This method only returns the - /// index of an object that is identical to the given one. Thus objects that might contain the same value as the - /// given one will not be considered. - /// - /// The index of the object, if it was found. -1 otherwise. - /// The object to look for. - [Obsolete] - public int IndexOfIdenticalObject(object obj) - { - NSObject nso = Wrap(obj); - - for(int i = 0; i < array.Count; i++) - if(array[i] == nso) - return i; - - return -1; - } - - /// Returns the last object contained in this array. - /// The value of the highest index in the array. - public NSObject LastObject() => array[array.Count - 1]; - - /// - /// Returns a new array containing only the values stored at the given indices. The values are sorted by their - /// index. - /// - /// The new array containing the objects stored at the given indices. - /// The indices of the objects. - public NSObject[] ObjectsAtIndexes(params int[] indexes) - { - NSObject[] result = new NSObject[indexes.Length]; - Array.Sort(indexes); - - for(int i = 0; i < indexes.Length; i++) - result[i] = array[indexes[i]]; - - return result; - } - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(object obj) - { - if(obj is NSArray nsArray) - return ArrayEquals(nsArray, this); - - NSObject nso = Wrap(obj); - - if(nso is NSArray nsoArray) - return ArrayEquals(nsoArray, this); - - return false; - } - - /// Serves as a hash function for a object. - /// - /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a - /// hash table. - /// - public override int GetHashCode() - { - int hash = 7; - hash = (89 * hash) + array.GetHashCode(); - - return hash; - } - - internal override void ToXml(StringBuilder xml, int level) - { - Indent(xml, level); - xml.Append(""); + o.ToXml(xml, level + 1); xml.Append(NEWLINE); + } - foreach(NSObject o in array) + Indent(xml, level); + xml.Append(""); + } + + internal override void AssignIDs(BinaryPropertyListWriter outPlist) + { + base.AssignIDs(outPlist); + + foreach(NSObject obj in array) obj.AssignIDs(outPlist); + } + + internal override void ToBinary(BinaryPropertyListWriter outPlist) + { + outPlist.WriteIntHeader(0xA, array.Count); + + foreach(NSObject obj in array) outPlist.WriteID(outPlist.GetID(obj)); + } + + /// + /// Generates a valid ASCII property list which has this NSArray as its root object. + /// + /// The generated property list complies with the format as described in + /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html + /// Property List Programming Guide - Old-Style ASCII Property Lists. + /// + /// + /// ASCII representation of this object. + public string ToASCIIPropertyList() + { + var ascii = new StringBuilder(); + ToASCII(ascii, 0); + ascii.Append(NEWLINE); + + return ascii.ToString(); + } + + /// + /// Generates a valid ASCII property list in GnuStep format which has this NSArray as its root object. + /// + /// The generated property list complies with the format as described in + /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html GnuStep - + /// NSPropertyListSerialization class documentation. + /// + /// + /// GnuStep ASCII representation of this object. + public string ToGnuStepASCIIPropertyList() + { + var ascii = new StringBuilder(); + ToASCIIGnuStep(ascii, 0); + ascii.Append(NEWLINE); + + return ascii.ToString(); + } + + internal override void ToASCII(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); + int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); + + for(int i = 0; i < array.Count; i++) + { + if((array[i] is NSDictionary || array[i] is NSArray || array[i] is NSData) && + indexOfLastNewLine != ascii.Length) { - o.ToXml(xml, level + 1); - xml.Append(NEWLINE); - } - - Indent(xml, level); - xml.Append(""); - } - - internal override void AssignIDs(BinaryPropertyListWriter outPlist) - { - base.AssignIDs(outPlist); - - foreach(NSObject obj in array) - obj.AssignIDs(outPlist); - } - - internal override void ToBinary(BinaryPropertyListWriter outPlist) - { - outPlist.WriteIntHeader(0xA, array.Count); - - foreach(NSObject obj in array) - outPlist.WriteID(outPlist.GetID(obj)); - } - - /// - /// Generates a valid ASCII property list which has this NSArray as its root object. - /// - /// The generated property list complies with the format as described in - /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html - /// Property List Programming Guide - Old-Style ASCII Property Lists. - /// - /// - /// ASCII representation of this object. - public string ToASCIIPropertyList() - { - var ascii = new StringBuilder(); - ToASCII(ascii, 0); - ascii.Append(NEWLINE); - - return ascii.ToString(); - } - - /// - /// Generates a valid ASCII property list in GnuStep format which has this NSArray as its root object. - /// - /// The generated property list complies with the format as described in - /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html GnuStep - - /// NSPropertyListSerialization class documentation. - /// - /// - /// GnuStep ASCII representation of this object. - public string ToGnuStepASCIIPropertyList() - { - var ascii = new StringBuilder(); - ToASCIIGnuStep(ascii, 0); - ascii.Append(NEWLINE); - - return ascii.ToString(); - } - - internal override void ToASCII(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); - int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); - - for(int i = 0; i < array.Count; i++) - { - if((array[i] is NSDictionary || array[i] is NSArray || array[i] is NSData) && - indexOfLastNewLine != ascii.Length) - { - ascii.Append(NEWLINE); - indexOfLastNewLine = ascii.Length; - array[i].ToASCII(ascii, level + 1); - } - else - { - if(i != 0) - ascii.Append(" "); - - array[i].ToASCII(ascii, 0); - } - - if(i != array.Count - 1) - ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN); - - if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) - continue; - ascii.Append(NEWLINE); indexOfLastNewLine = ascii.Length; + array[i].ToASCII(ascii, level + 1); + } + else + { + if(i != 0) ascii.Append(" "); + + array[i].ToASCII(ascii, 0); } - ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); + if(i != array.Count - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN); + + if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue; + + ascii.Append(NEWLINE); + indexOfLastNewLine = ascii.Length; } - internal override void ToASCIIGnuStep(StringBuilder ascii, int level) + ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); + } + + internal override void ToASCIIGnuStep(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); + int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); + + for(int i = 0; i < array.Count; i++) { - Indent(ascii, level); - ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); - int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); + Type objClass = array[i].GetType(); - for(int i = 0; i < array.Count; i++) + if((array[i] is NSDictionary || array[i] is NSArray || array[i] is NSData) && + indexOfLastNewLine != ascii.Length) { - Type objClass = array[i].GetType(); - - if((array[i] is NSDictionary || array[i] is NSArray || array[i] is NSData) && - indexOfLastNewLine != ascii.Length) - { - ascii.Append(NEWLINE); - indexOfLastNewLine = ascii.Length; - array[i].ToASCIIGnuStep(ascii, level + 1); - } - else - { - if(i != 0) - ascii.Append(" "); - - array[i].ToASCIIGnuStep(ascii, 0); - } - - if(i != array.Count - 1) - ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN); - - if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) - continue; - ascii.Append(NEWLINE); indexOfLastNewLine = ascii.Length; + array[i].ToASCIIGnuStep(ascii, level + 1); + } + else + { + if(i != 0) ascii.Append(" "); + + array[i].ToASCIIGnuStep(ascii, 0); } - ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); + if(i != array.Count - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN); + + if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue; + + ascii.Append(NEWLINE); + indexOfLastNewLine = ascii.Length; } - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(NSObject obj) - { - if(obj is not NSArray nsArray) - return false; + ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); + } - if(array.Count != nsArray.array.Count) - return false; + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(NSObject obj) + { + if(obj is not NSArray nsArray) return false; - for(int i = 0; i < array.Count; i++) - if(!array[i].Equals(nsArray[i])) - return false; + if(array.Count != nsArray.array.Count) return false; - return true; - } + for(int i = 0; i < array.Count; i++) + if(!array[i].Equals(nsArray[i])) return false; + + return true; } } \ No newline at end of file diff --git a/plist-cil/NSData.cs b/plist-cil/NSData.cs index 6a58f19..c2dc686 100644 --- a/plist-cil/NSData.cs +++ b/plist-cil/NSData.cs @@ -27,162 +27,161 @@ using System; using System.IO; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// NSData objects are wrappers for byte buffers +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class NSData : NSObject { - /// NSData objects are wrappers for byte buffers - /// @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. + // Each line contains 68 characters. + const int DataLineLength = 68; + + /// Creates the NSData object from the binary representation of it. + /// The raw data contained in the NSData object. + public NSData(byte[] bytes) => Bytes = bytes; + + /// Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes. + /// The Base64 encoded contents of the NSData object. + /// When the given string is not a proper Base64 formatted string. + public NSData(string base64) => Bytes = Convert.FromBase64String(base64); + + /// Creates a NSData object from a file. Using the files contents as the contents of this NSData object. + /// The file containing the data. + /// If the file could not be found. + /// If the file could not be read. + public NSData(FileInfo file) { - // In the XML property list format, the base-64 encoded data is split across multiple lines. - // Each line contains 68 characters. - const int DataLineLength = 68; + Bytes = new byte[(int)file.Length]; - /// Creates the NSData object from the binary representation of it. - /// The raw data contained in the NSData object. - public NSData(byte[] bytes) => Bytes = bytes; - - /// Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes. - /// The Base64 encoded contents of the NSData object. - /// When the given string is not a proper Base64 formatted string. - public NSData(string base64) => Bytes = Convert.FromBase64String(base64); - - /// Creates a NSData object from a file. Using the files contents as the contents of this NSData object. - /// The file containing the data. - /// If the file could not be found. - /// If the file could not be read. - public NSData(FileInfo file) - { - Bytes = new byte[(int)file.Length]; - - using FileStream raf = file.OpenRead(); + using FileStream raf = file.OpenRead(); #if NET7_0_OR_GREATER - raf.ReadExactly(Bytes, 0, (int)file.Length); + raf.ReadExactly(Bytes, 0, (int)file.Length); #else - raf.Read(Bytes, 0, (int)file.Length); + raf.Read(Bytes, 0, (int)file.Length); #endif - } - - /// The bytes contained in this NSData object. - /// The data as bytes - public byte[] Bytes { get; } - - /// Gets the amount of data stored in this object. - /// The number of bytes contained in this object. - public int Length => Bytes.Length; - - /// Loads the bytes from this NSData object into a byte buffer. - /// The byte buffer which will contain the data - /// The amount of data to copy - public void GetBytes(MemoryStream buf, int length) => buf.Write(Bytes, 0, Math.Min(Bytes.Length, length)); - - /// Loads the bytes from this NSData object into a byte buffer. - /// The byte buffer which will contain the data - /// The start index. - /// The stop index. - public void GetBytes(MemoryStream buf, int rangeStart, int rangeStop) => - buf.Write(Bytes, rangeStart, Math.Min(Bytes.Length, rangeStop)); - - /// Gets the Base64 encoded data contained in this NSData object. - /// The Base64 encoded data as a string. - public string GetBase64EncodedData() => Convert.ToBase64String(Bytes); - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(object obj) => - obj.GetType().Equals(GetType()) && ArrayEquals(((NSData)obj).Bytes, Bytes); - - /// Serves as a hash function for a object. - /// - /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a - /// hash table. - /// - public override int GetHashCode() - { - int hash = 5; - hash = (67 * hash) + Bytes.GetHashCode(); - - return hash; - } - - internal override void ToXml(StringBuilder xml, int level) - { - Indent(xml, level); - xml.Append(""); - xml.Append(NEWLINE); - string base64 = GetBase64EncodedData(); - - foreach(string line in base64.Split('\n')) - for(int offset = 0; offset < base64.Length; offset += DataLineLength) - { - Indent(xml, level); - xml.Append(line.Substring(offset, Math.Min(DataLineLength, line.Length - offset))); - xml.Append(NEWLINE); - } - - Indent(xml, level); - xml.Append(""); - } - - internal override void ToBinary(BinaryPropertyListWriter outPlist) - { - outPlist.WriteIntHeader(0x4, Bytes.Length); - outPlist.Write(Bytes); - } - - internal override void ToASCII(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append(ASCIIPropertyListParser.DATA_BEGIN_TOKEN); - int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); - - for(int i = 0; i < Bytes.Length; i++) - { - int b = Bytes[i] & 0xFF; - ascii.Append($"{b:x2}"); - - if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH) - { - ascii.Append(NEWLINE); - indexOfLastNewLine = ascii.Length; - } - else if((i + 1) % 2 == 0 && - i != Bytes.Length - 1) - ascii.Append(" "); - } - - ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN); - } - - internal override void ToASCIIGnuStep(StringBuilder ascii, int level) => ToASCII(ascii, level); - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(NSObject obj) => obj is NSData data && ArrayEquals(Bytes, data.Bytes); - - public static explicit operator byte[](NSData value) => value.Bytes; - - public static explicit operator NSData(byte[] value) => new(value); } + + /// The bytes contained in this NSData object. + /// The data as bytes + public byte[] Bytes { get; } + + /// Gets the amount of data stored in this object. + /// The number of bytes contained in this object. + public int Length => Bytes.Length; + + /// Loads the bytes from this NSData object into a byte buffer. + /// The byte buffer which will contain the data + /// The amount of data to copy + public void GetBytes(MemoryStream buf, int length) => buf.Write(Bytes, 0, Math.Min(Bytes.Length, length)); + + /// Loads the bytes from this NSData object into a byte buffer. + /// The byte buffer which will contain the data + /// The start index. + /// The stop index. + public void GetBytes(MemoryStream buf, int rangeStart, int rangeStop) => + buf.Write(Bytes, rangeStart, Math.Min(Bytes.Length, rangeStop)); + + /// Gets the Base64 encoded data contained in this NSData object. + /// The Base64 encoded data as a string. + public string GetBase64EncodedData() => Convert.ToBase64String(Bytes); + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(object obj) => + obj.GetType().Equals(GetType()) && ArrayEquals(((NSData)obj).Bytes, Bytes); + + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a + /// hash table. + /// + public override int GetHashCode() + { + int hash = 5; + hash = 67 * hash + Bytes.GetHashCode(); + + return hash; + } + + internal override void ToXml(StringBuilder xml, int level) + { + Indent(xml, level); + xml.Append(""); + xml.Append(NEWLINE); + string base64 = GetBase64EncodedData(); + + foreach(string line in base64.Split('\n')) + { + for(int offset = 0; offset < base64.Length; offset += DataLineLength) + { + Indent(xml, level); + xml.Append(line.Substring(offset, Math.Min(DataLineLength, line.Length - offset))); + xml.Append(NEWLINE); + } + } + + Indent(xml, level); + xml.Append(""); + } + + internal override void ToBinary(BinaryPropertyListWriter outPlist) + { + outPlist.WriteIntHeader(0x4, Bytes.Length); + outPlist.Write(Bytes); + } + + internal override void ToASCII(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append(ASCIIPropertyListParser.DATA_BEGIN_TOKEN); + int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); + + for(int i = 0; i < Bytes.Length; i++) + { + int b = Bytes[i] & 0xFF; + ascii.Append($"{b:x2}"); + + if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH) + { + ascii.Append(NEWLINE); + indexOfLastNewLine = ascii.Length; + } + else if((i + 1) % 2 == 0 && i != Bytes.Length - 1) ascii.Append(" "); + } + + ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN); + } + + internal override void ToASCIIGnuStep(StringBuilder ascii, int level) => ToASCII(ascii, level); + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(NSObject obj) => obj is NSData data && ArrayEquals(Bytes, data.Bytes); + + public static explicit operator byte[](NSData value) => value.Bytes; + + public static explicit operator NSData(byte[] value) => new(value); } \ No newline at end of file diff --git a/plist-cil/NSDate.cs b/plist-cil/NSDate.cs index d424925..3548ffb 100644 --- a/plist-cil/NSDate.cs +++ b/plist-cil/NSDate.cs @@ -27,151 +27,149 @@ using System; using System.Globalization; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// Represents a date +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class NSDate : NSObject { - /// Represents a date - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class NSDate : NSObject + 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 + // understands the 'Z' character as a timezone, specify the 'K' format string. + 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[] sdfAll = + [ + sdfDefault, sdfGnuStep + ]; + + static readonly CultureInfo provider = CultureInfo.InvariantCulture; + + /// Creates a date from its binary representation. + /// bytes The date bytes + public NSDate(ReadOnlySpan bytes) => + + //dates are 8 byte big-endian double, seconds since the epoch + Date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes)); + + /// + /// Parses a date from its textual representation. That representation has the following pattern: + /// yyyy-MM-dd'T'HH:mm:ss'Z' + /// + /// The textual representation of the date (ISO 8601 format) + /// When the date could not be parsed, i.e. it does not match the expected pattern. + public NSDate(string textRepresentation) => Date = ParseDateString(textRepresentation); + + /// Creates a NSDate from a .NET DateTime + /// The date + public NSDate(DateTime d) => Date = d; + + /// Gets the date. + /// The date. + public DateTime Date { get; } + + /// Parses the XML date string and creates a .NET DateTime object from it. + /// The parsed Date + /// The date string as found in the XML property list + /// Given string cannot be parsed + static DateTime ParseDateString(string textRepresentation) => + DateTime.ParseExact(textRepresentation, sdfAll, provider, DateTimeStyles.None); + + /// + /// Generates a String representation of a .NET DateTime object. The string is formatted according to the + /// specification for XML property list dates. + /// + /// The date which should be represented. + /// The string representation of the date. + public static string MakeDateString(DateTime date) => date.ToUniversalTime().ToString(sdfDefault, provider); + + /// + /// Generates a String representation of a .NET DateTime object. The string is formatted according to the + /// specification for GnuStep ASCII property list dates. + /// + /// The date which should be represented. + /// The string representation of the date. + static string MakeDateStringGnuStep(DateTime date) => date.ToString(sdfGnuStep, provider); + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(object obj) => obj.GetType().Equals(GetType()) && Date.Equals(((NSDate)obj).Date); + + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a + /// hash table. + /// + public override int GetHashCode() => Date.GetHashCode(); + + internal override void ToXml(StringBuilder xml, int level) { - 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 - // understands the 'Z' character as a timezone, specify the 'K' format string. - 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[] sdfAll = - { - sdfDefault, sdfGnuStep - }; - - static readonly CultureInfo provider = CultureInfo.InvariantCulture; - - /// Creates a date from its binary representation. - /// bytes The date bytes - public NSDate(ReadOnlySpan bytes) => - - //dates are 8 byte big-endian double, seconds since the epoch - Date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes)); - - /// - /// Parses a date from its textual representation. That representation has the following pattern: - /// yyyy-MM-dd'T'HH:mm:ss'Z' - /// - /// The textual representation of the date (ISO 8601 format) - /// When the date could not be parsed, i.e. it does not match the expected pattern. - public NSDate(string textRepresentation) => Date = ParseDateString(textRepresentation); - - /// Creates a NSDate from a .NET DateTime - /// The date - public NSDate(DateTime d) => Date = d; - - /// Gets the date. - /// The date. - public DateTime Date { get; } - - /// Parses the XML date string and creates a .NET DateTime object from it. - /// The parsed Date - /// The date string as found in the XML property list - /// Given string cannot be parsed - static DateTime ParseDateString(string textRepresentation) => - DateTime.ParseExact(textRepresentation, sdfAll, provider, DateTimeStyles.None); - - /// - /// Generates a String representation of a .NET DateTime object. The string is formatted according to the - /// specification for XML property list dates. - /// - /// The date which should be represented. - /// The string representation of the date. - public static string MakeDateString(DateTime date) => date.ToUniversalTime().ToString(sdfDefault, provider); - - /// - /// Generates a String representation of a .NET DateTime object. The string is formatted according to the - /// specification for GnuStep ASCII property list dates. - /// - /// The date which should be represented. - /// The string representation of the date. - static string MakeDateStringGnuStep(DateTime date) => date.ToString(sdfGnuStep, provider); - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(object obj) => obj.GetType().Equals(GetType()) && Date.Equals(((NSDate)obj).Date); - - /// Serves as a hash function for a object. - /// - /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a - /// hash table. - /// - public override int GetHashCode() => Date.GetHashCode(); - - internal override void ToXml(StringBuilder xml, int level) - { - Indent(xml, level); - xml.Append(""); - xml.Append(MakeDateString(Date)); - xml.Append(""); - } - - internal override void ToBinary(BinaryPropertyListWriter outPlist) - { - outPlist.Write(0x33); - outPlist.WriteDouble((Date - EPOCH).TotalSeconds); - } - - /// Generates a string representation of the date. - /// A string representation of the date. - public override string ToString() => Date.ToString(); - - internal override void ToASCII(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append("\""); - ascii.Append(MakeDateString(Date)); - ascii.Append("\""); - } - - internal override void ToASCIIGnuStep(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append("<*D"); - ascii.Append(MakeDateStringGnuStep(Date)); - ascii.Append(">"); - } - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(NSObject obj) - { - if(obj is not NSDate date) - return false; - - int equality = DateTime.Compare(Date, date.Date); - - return equality == 0; - } - - public static explicit operator DateTime(NSDate value) => value.Date; - - public static explicit operator NSDate(DateTime value) => new(value); + Indent(xml, level); + xml.Append(""); + xml.Append(MakeDateString(Date)); + xml.Append(""); } + + internal override void ToBinary(BinaryPropertyListWriter outPlist) + { + outPlist.Write(0x33); + outPlist.WriteDouble((Date - EPOCH).TotalSeconds); + } + + /// Generates a string representation of the date. + /// A string representation of the date. + public override string ToString() => Date.ToString(); + + internal override void ToASCII(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append("\""); + ascii.Append(MakeDateString(Date)); + ascii.Append("\""); + } + + internal override void ToASCIIGnuStep(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append("<*D"); + ascii.Append(MakeDateStringGnuStep(Date)); + ascii.Append(">"); + } + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(NSObject obj) + { + if(obj is not NSDate date) return false; + + int equality = DateTime.Compare(Date, date.Date); + + return equality == 0; + } + + public static explicit operator DateTime(NSDate value) => value.Date; + + public static explicit operator NSDate(DateTime value) => new(value); } \ No newline at end of file diff --git a/plist-cil/NSDictionary.cs b/plist-cil/NSDictionary.cs index 61647b3..03b145b 100644 --- a/plist-cil/NSDictionary.cs +++ b/plist-cil/NSDictionary.cs @@ -28,527 +28,505 @@ using System.Collections; using System.Collections.Generic; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// +/// 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. +/// +/// You can access the keys through the function . +/// Access to the objects stored for each key is given through the function . +/// +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class NSDictionary : NSObject, IDictionary { - /// - /// - /// 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. - /// - /// You can access the keys through the function . - /// Access to the objects stored for each key is given through the function . - /// - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class NSDictionary : NSObject, IDictionary + readonly Dictionary dict; + + // Maps the keys in this dictionary to their NSString equivalent. Makes sure the NSString + // object remains constant across calls to AssignIDs and ToBinary + readonly Dictionary keys; + + /// Creates a new empty NSDictionary with a specific capacity. + /// The capacity of the dictionary. + public NSDictionary(int capacity) { - readonly Dictionary dict; - - // Maps the keys in this dictionary to their NSString equivalent. Makes sure the NSString - // object remains constant across calls to AssignIDs and ToBinary - readonly Dictionary keys; - - /// Creates a new empty NSDictionary with a specific capacity. - /// The capacity of the dictionary. - public NSDictionary(int capacity) - { - dict = new Dictionary(capacity); - keys = new Dictionary(capacity); - } - - /// Creates a new empty NSDictionary. - public NSDictionary() : this(0) {} - - /// Gets a value indicating whether this instance is empty. - /// true if this instance is empty; otherwise, false. - public bool IsEmpty => dict.Count == 0; - - #region IEnumerable implementation - /// Gets the enumerator. - /// The enumerator. - public IEnumerator> GetEnumerator() => dict.GetEnumerator(); - #endregion - - #region IEnumerable implementation - IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator(); - #endregion - - /// - /// Gets the hashmap which stores the keys and values of this dictionary. Changes to the hashmap's contents are - /// directly reflected in this dictionary. - /// - /// The hashmap which is used by this dictionary to store its contents. - public Dictionary GetDictionary() => dict; - - /// Gets the NSObject stored for the given key. - /// The object. - /// The key. - public NSObject ObjectForKey(string key) - { - NSObject nso; - - return dict.TryGetValue(key, out nso) ? nso : null; - } - - /// Checks if the specified object key is contained in the current instance. - /// true, if key is contained, false otherwise. - /// Key. - public bool ContainsKey(object key) => key is string s && dict.ContainsKey(s); - - /// Removes the item corresponding to the specified key from the current instance, if found. - /// Key. - /// true, if removed, false otherwise. - public bool Remove(object key) => key is string s && dict.Remove(s); - - /// Gets the corresponding to the specified key from the current instance. - /// Key. - /// The object corresponding to the specified key, null if not found in the current instance. - public NSObject Get(object key) - { - if(key is string s) - return ObjectForKey(s); - - return null; - } - - /// Checks if the current instance contains the object corresponding to the specified key. - /// true, if value is contained, false otherwise. - /// Object to search up in the current instance. - public bool ContainsValue(object value) - { - if(value == null) - return false; - - NSObject wrap = Wrap(value); - - return dict.ContainsValue(wrap); - } - - /// - /// Puts a new key-value pair into this dictionary. If the value is null, no operation will be performed on the - /// dictionary. - /// - /// The key. - /// - /// The value. Supported object types are numbers, byte-arrays, dates, strings and arrays or sets of - /// those. - /// - public void Add(string key, object obj) - { - if(obj == null) - return; - - Add(key, Wrap(obj)); - } - - /// Puts a new key-value pair into this dictionary. - /// The key. - /// The value. - public void Add(string key, long obj) => Add(key, new NSNumber(obj)); - - /// Puts a new key-value pair into this dictionary. - /// The key. - /// The value. - public void Add(string key, double obj) => Add(key, new NSNumber(obj)); - - /// Puts a new key-value pair into this dictionary. - /// The key. - /// The value. - public void Add(string key, bool obj) => Add(key, new NSNumber(obj)); - - /// Checks whether a given value is contained in this dictionary. - /// The value that will be searched for. - /// Whether the key is contained in this dictionary. - public bool ContainsValue(string val) - { - foreach(NSObject o in dict.Values) - if(o is NSString str && - str.Content.Equals(val)) - return true; - - return false; - } - - /// Checks whether a given value is contained in this dictionary. - /// The value that will be searched for. - /// Whether the key is contained in this dictionary. - public bool ContainsValue(long val) - { - foreach(NSObject o in dict.Values) - if(o is NSNumber num && - num.isInteger() && - num.ToInt() == val) - return true; - - return false; - } - - /// Checks whether a given value is contained in this dictionary. - /// The value that will be searched for. - /// Whether the key is contained in this dictionary. - public bool ContainsValue(double val) - { - foreach(NSObject o in dict.Values) - if(o is NSNumber num && - num.isReal() && - num.ToDouble() == val) - return true; - - return false; - } - - /// Checks whether a given value is contained in this dictionary. - /// The value that will be searched for. - /// Whether the key is contained in this dictionary. - public bool ContainsValue(bool val) - { - foreach(NSObject o in dict.Values) - if(o is NSNumber num && - num.isBoolean() && - num.ToBool() == val) - return true; - - return false; - } - - /// Checks whether a given value is contained in this dictionary. - /// The value that will be searched for. - /// Whether the key is contained in this dictionary. - public bool ContainsValue(DateTime val) - { - foreach(NSObject o in dict.Values) - if(o is NSDate dat && - dat.Date.Equals(val)) - return true; - - return false; - } - - /// Checks whether a given value is contained in this dictionary. - /// The value that will be searched for. - /// Whether the key is contained in this dictionary. - public bool ContainsValue(byte[] val) - { - foreach(NSObject o in dict.Values) - if(o is NSData dat && - ArrayEquals(dat.Bytes, val)) - return true; - - return false; - } - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(NSObject obj) - { - if(obj is not NSDictionary dictionary) - return false; - - if(dictionary.dict.Count != dict.Count) - return false; - - foreach(KeyValuePair kvp in dict) - { - bool found = dictionary.dict.TryGetValue(kvp.Key, out NSObject nsoB); - - if(!found) - return false; - - if(!kvp.Value.Equals(nsoB)) - return false; - } - - return true; - } - - /// Serves as a hash function for a object. - /// - /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a - /// hash table. - /// - public override int GetHashCode() - { - int hash = 7; - hash = (83 * hash) + (dict != null ? dict.GetHashCode() : 0); - - return hash; - } - - internal override void ToXml(StringBuilder xml, int level) - { - Indent(xml, level); - xml.Append(""); - xml.Append(NEWLINE); - - foreach(KeyValuePair kvp in dict) - { - Indent(xml, level + 1); - xml.Append(""); - - //According to http://www.w3.org/TR/REC-xml/#syntax node values must not - //contain the characters < or &. Also the > character should be escaped. - if(kvp.Key.Contains("&") || - kvp.Key.Contains("<") || - kvp.Key.Contains(">")) - { - xml.Append("", "]]]]>")); - xml.Append("]]>"); - } - else - xml.Append(kvp.Key); - - xml.Append(""); - xml.Append(NEWLINE); - kvp.Value.ToXml(xml, level + 1); - xml.Append(NEWLINE); - } - - Indent(xml, level); - xml.Append(""); - } - - internal override void AssignIDs(BinaryPropertyListWriter outPlist) - { - base.AssignIDs(outPlist); - - foreach(KeyValuePair entry in dict) - keys[entry.Key].AssignIDs(outPlist); - - foreach(KeyValuePair entry in dict) - entry.Value.AssignIDs(outPlist); - } - - internal override void ToBinary(BinaryPropertyListWriter outPlist) - { - outPlist.WriteIntHeader(0xD, dict.Count); - - foreach(KeyValuePair entry in dict) - outPlist.WriteID(outPlist.GetID(keys[entry.Key])); - - foreach(KeyValuePair entry in dict) - outPlist.WriteID(outPlist.GetID(entry.Value)); - } - - /// - /// Generates a valid ASCII property list which has this NSDictionary as its root object. The generated property - /// list complies with the format as described in - /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html - /// Property List Programming Guide - Old-Style ASCII Property Lists. - /// - /// ASCII representation of this object. - public string ToASCIIPropertyList() - { - var ascii = new StringBuilder(); - ToASCII(ascii, 0); - ascii.Append(NEWLINE); - - return ascii.ToString(); - } - - /// - /// Generates a valid ASCII property list in GnuStep format which has this NSDictionary as its root object. The - /// generated property list complies with the format as described in - /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html GnuStep - - /// NSPropertyListSerialization class documentation. - /// - /// GnuStep ASCII representation of this object. - public string ToGnuStepASCIIPropertyList() - { - var ascii = new StringBuilder(); - ToASCIIGnuStep(ascii, 0); - ascii.Append(NEWLINE); - - return ascii.ToString(); - } - - internal override void ToASCII(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN); - ascii.Append(NEWLINE); - - foreach(string key in Keys) - { - NSObject val = ObjectForKey(key); - Indent(ascii, level + 1); - ascii.Append("\""); - ascii.Append(NSString.EscapeStringForASCII(key)); - ascii.Append("\" ="); - - if(val is NSDictionary or NSArray or NSData) - { - ascii.Append(NEWLINE); - val.ToASCII(ascii, level + 2); - } - else - { - ascii.Append(" "); - val.ToASCII(ascii, 0); - } - - ascii.Append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN); - ascii.Append(NEWLINE); - } - - Indent(ascii, level); - ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN); - } - - internal override void ToASCIIGnuStep(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN); - ascii.Append(NEWLINE); - - foreach(string key in Keys) - { - NSObject val = ObjectForKey(key); - Indent(ascii, level + 1); - ascii.Append("\""); - ascii.Append(NSString.EscapeStringForASCII(key)); - ascii.Append("\" ="); - - if(val is NSDictionary or NSArray or NSData) - { - ascii.Append(NEWLINE); - val.ToASCIIGnuStep(ascii, level + 2); - } - else - { - ascii.Append(" "); - val.ToASCIIGnuStep(ascii, 0); - } - - ascii.Append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN); - ascii.Append(NEWLINE); - } - - Indent(ascii, level); - ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN); - } - - #region IDictionary implementation - /// Add the specified key and value. - /// Key. - /// Value. - public void Add(string key, NSObject value) - { - dict.Add(key, value); - keys.Add(key, new NSString(key)); - } - - /// Checks if there is any item contained in the current instance corresponding with the specified key. - /// true, if key was contained, false otherwise. - /// Key. - public bool ContainsKey(string key) => dict.ContainsKey(key); - - /// Checks if there is any item contained in the current instance corresponding with the specified value. - /// true, if value is contained, false otherwise. - /// Key. - public bool ContainsValue(NSObject value) => dict.ContainsValue(value); - - /// Removes the item belonging to the specified key. - /// Key. - public bool Remove(string key) - { - keys.Remove(key); - - return dict.Remove(key); - } - - /// Tries to get the item corresponding to the specified key - /// true, if get value was successfully found and retrieved, false otherwise. - /// Key. - /// Where to store the value. - public bool TryGetValue(string key, out NSObject value) => dict.TryGetValue(key, out value); - - /// Gets or sets the at the specified index. - /// Index. - public NSObject this[string index] - { - get => dict[index]; - set - { - if(!keys.ContainsKey(index)) - keys.Add(index, new NSString(index)); - - dict[index] = value; - } - } - - /// Gets an array with all the keys contained in the current instance. - /// The keys. - public ICollection Keys => dict.Keys; - - /// Gets an array with all the objects contained in the current instance. - /// The objects. - public ICollection Values => dict.Values; - #endregion - - #region ICollection implementation - /// Adds the specified item. - /// Item. - public void Add(KeyValuePair item) - { - keys.Add(item.Key, new NSString(item.Key)); - dict.Add(item.Key, item.Value); - } - - /// Clears this instance. - public void Clear() - { - keys.Clear(); - dict.Clear(); - } - - /// Checks if the current instance contains the specified item. - /// Item. - /// true if it is found, false otherwise. - public bool Contains(KeyValuePair item) => dict.ContainsKey(item.Key); - - /// - /// Copies the elements to an existing one-dimensional - /// , starting at the specified array index. - /// - /// - /// The one-dimensional that is the destination of the elements copied from - /// . The must have zero-based indexing. - /// - /// The zero-based index in array at which copying begins. - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ICollection> coll = dict; - coll.CopyTo(array, arrayIndex); - } - - /// Removes the specified item. - /// Item to remove. - /// true if successfully removed, false if not, or if item is not in current instance. - public bool Remove(KeyValuePair item) - { - keys.Remove(item.Key); - - return dict.Remove(item.Key); - } - - /// Gets the count of items in the current instance. - /// How many items are contained in the current instance. - public int Count => dict.Count; - - /// Gets a value indicating whether this instance is read only. - /// true if this instance is read only; otherwise, false. - public bool IsReadOnly => false; - #endregion + dict = []; + keys = []; } + + /// Creates a new empty NSDictionary. + public NSDictionary() : this(0) {} + + /// Gets a value indicating whether this instance is empty. + /// true if this instance is empty; otherwise, false. + public bool IsEmpty => dict.Count == 0; + +#region IEnumerable implementation + + /// Gets the enumerator. + /// The enumerator. + public IEnumerator> GetEnumerator() => dict.GetEnumerator(); + +#endregion + +#region IEnumerable implementation + + IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator(); + +#endregion + + /// + /// Gets the hashmap which stores the keys and values of this dictionary. Changes to the hashmap's contents are + /// directly reflected in this dictionary. + /// + /// The hashmap which is used by this dictionary to store its contents. + public Dictionary GetDictionary() => dict; + + /// Gets the NSObject stored for the given key. + /// The object. + /// The key. + public NSObject ObjectForKey(string key) + { + NSObject nso; + + return dict.TryGetValue(key, out nso) ? nso : null; + } + + /// Checks if the specified object key is contained in the current instance. + /// true, if key is contained, false otherwise. + /// Key. + public bool ContainsKey(object key) => key is string s && dict.ContainsKey(s); + + /// Removes the item corresponding to the specified key from the current instance, if found. + /// Key. + /// true, if removed, false otherwise. + public bool Remove(object key) => key is string s && dict.Remove(s); + + /// Gets the corresponding to the specified key from the current instance. + /// Key. + /// The object corresponding to the specified key, null if not found in the current instance. + public NSObject Get(object key) + { + if(key is string s) return ObjectForKey(s); + + return null; + } + + /// Checks if the current instance contains the object corresponding to the specified key. + /// true, if value is contained, false otherwise. + /// Object to search up in the current instance. + public bool ContainsValue(object value) + { + if(value == null) return false; + + NSObject wrap = Wrap(value); + + return dict.ContainsValue(wrap); + } + + /// + /// Puts a new key-value pair into this dictionary. If the value is null, no operation will be performed on the + /// dictionary. + /// + /// The key. + /// + /// The value. Supported object types are numbers, byte-arrays, dates, strings and arrays or sets of + /// those. + /// + public void Add(string key, object obj) + { + if(obj == null) return; + + Add(key, Wrap(obj)); + } + + /// Puts a new key-value pair into this dictionary. + /// The key. + /// The value. + public void Add(string key, long obj) => Add(key, new NSNumber(obj)); + + /// Puts a new key-value pair into this dictionary. + /// The key. + /// The value. + public void Add(string key, double obj) => Add(key, new NSNumber(obj)); + + /// Puts a new key-value pair into this dictionary. + /// The key. + /// The value. + public void Add(string key, bool obj) => Add(key, new NSNumber(obj)); + + /// Checks whether a given value is contained in this dictionary. + /// The value that will be searched for. + /// Whether the key is contained in this dictionary. + public bool ContainsValue(string val) + { + foreach(NSObject o in dict.Values) + if(o is NSString str && str.Content.Equals(val)) return true; + + return false; + } + + /// Checks whether a given value is contained in this dictionary. + /// The value that will be searched for. + /// Whether the key is contained in this dictionary. + public bool ContainsValue(long val) + { + foreach(NSObject o in dict.Values) + if(o is NSNumber num && num.isInteger() && num.ToInt() == val) return true; + + return false; + } + + /// Checks whether a given value is contained in this dictionary. + /// The value that will be searched for. + /// Whether the key is contained in this dictionary. + public bool ContainsValue(double val) + { + foreach(NSObject o in dict.Values) + if(o is NSNumber num && num.isReal() && num.ToDouble() == val) return true; + + return false; + } + + /// Checks whether a given value is contained in this dictionary. + /// The value that will be searched for. + /// Whether the key is contained in this dictionary. + public bool ContainsValue(bool val) + { + foreach(NSObject o in dict.Values) + if(o is NSNumber num && num.isBoolean() && num.ToBool() == val) return true; + + return false; + } + + /// Checks whether a given value is contained in this dictionary. + /// The value that will be searched for. + /// Whether the key is contained in this dictionary. + public bool ContainsValue(DateTime val) + { + foreach(NSObject o in dict.Values) + if(o is NSDate dat && dat.Date.Equals(val)) return true; + + return false; + } + + /// Checks whether a given value is contained in this dictionary. + /// The value that will be searched for. + /// Whether the key is contained in this dictionary. + public bool ContainsValue(byte[] val) + { + foreach(NSObject o in dict.Values) + if(o is NSData dat && ArrayEquals(dat.Bytes, val)) return true; + + return false; + } + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(NSObject obj) + { + if(obj is not NSDictionary dictionary) return false; + + if(dictionary.dict.Count != dict.Count) return false; + + foreach(KeyValuePair kvp in dict) + { + bool found = dictionary.dict.TryGetValue(kvp.Key, out NSObject nsoB); + + if(!found) return false; + + if(!kvp.Value.Equals(nsoB)) return false; + } + + return true; + } + + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a + /// hash table. + /// + public override int GetHashCode() + { + int hash = 7; + hash = 83 * hash + (dict != null ? dict.GetHashCode() : 0); + + return hash; + } + + internal override void ToXml(StringBuilder xml, int level) + { + Indent(xml, level); + xml.Append(""); + xml.Append(NEWLINE); + + foreach(KeyValuePair kvp in dict) + { + Indent(xml, level + 1); + xml.Append(""); + + //According to http://www.w3.org/TR/REC-xml/#syntax node values must not + //contain the characters < or &. Also the > character should be escaped. + if(kvp.Key.Contains("&") || kvp.Key.Contains("<") || kvp.Key.Contains(">")) + { + xml.Append("", "]]]]>")); + xml.Append("]]>"); + } + else + xml.Append(kvp.Key); + + xml.Append(""); + xml.Append(NEWLINE); + kvp.Value.ToXml(xml, level + 1); + xml.Append(NEWLINE); + } + + Indent(xml, level); + xml.Append(""); + } + + internal override void AssignIDs(BinaryPropertyListWriter outPlist) + { + base.AssignIDs(outPlist); + + foreach(KeyValuePair entry in dict) keys[entry.Key].AssignIDs(outPlist); + + foreach(KeyValuePair entry in dict) entry.Value.AssignIDs(outPlist); + } + + internal override void ToBinary(BinaryPropertyListWriter outPlist) + { + outPlist.WriteIntHeader(0xD, dict.Count); + + foreach(KeyValuePair entry in dict) outPlist.WriteID(outPlist.GetID(keys[entry.Key])); + + foreach(KeyValuePair entry in dict) outPlist.WriteID(outPlist.GetID(entry.Value)); + } + + /// + /// Generates a valid ASCII property list which has this NSDictionary as its root object. The generated property + /// list complies with the format as described in + /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html + /// Property List Programming Guide - Old-Style ASCII Property Lists. + /// + /// ASCII representation of this object. + public string ToASCIIPropertyList() + { + var ascii = new StringBuilder(); + ToASCII(ascii, 0); + ascii.Append(NEWLINE); + + return ascii.ToString(); + } + + /// + /// Generates a valid ASCII property list in GnuStep format which has this NSDictionary as its root object. The + /// generated property list complies with the format as described in + /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html GnuStep - + /// NSPropertyListSerialization class documentation. + /// + /// GnuStep ASCII representation of this object. + public string ToGnuStepASCIIPropertyList() + { + var ascii = new StringBuilder(); + ToASCIIGnuStep(ascii, 0); + ascii.Append(NEWLINE); + + return ascii.ToString(); + } + + internal override void ToASCII(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN); + ascii.Append(NEWLINE); + + foreach(string key in Keys) + { + NSObject val = ObjectForKey(key); + Indent(ascii, level + 1); + ascii.Append("\""); + ascii.Append(NSString.EscapeStringForASCII(key)); + ascii.Append("\" ="); + + if(val is NSDictionary or NSArray or NSData) + { + ascii.Append(NEWLINE); + val.ToASCII(ascii, level + 2); + } + else + { + ascii.Append(" "); + val.ToASCII(ascii, 0); + } + + ascii.Append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN); + ascii.Append(NEWLINE); + } + + Indent(ascii, level); + ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN); + } + + internal override void ToASCIIGnuStep(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN); + ascii.Append(NEWLINE); + + foreach(string key in Keys) + { + NSObject val = ObjectForKey(key); + Indent(ascii, level + 1); + ascii.Append("\""); + ascii.Append(NSString.EscapeStringForASCII(key)); + ascii.Append("\" ="); + + if(val is NSDictionary or NSArray or NSData) + { + ascii.Append(NEWLINE); + val.ToASCIIGnuStep(ascii, level + 2); + } + else + { + ascii.Append(" "); + val.ToASCIIGnuStep(ascii, 0); + } + + ascii.Append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN); + ascii.Append(NEWLINE); + } + + Indent(ascii, level); + ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN); + } + +#region IDictionary implementation + + /// Add the specified key and value. + /// Key. + /// Value. + public void Add(string key, NSObject value) + { + dict.Add(key, value); + keys.Add(key, new NSString(key)); + } + + /// Checks if there is any item contained in the current instance corresponding with the specified key. + /// true, if key was contained, false otherwise. + /// Key. + public bool ContainsKey(string key) => dict.ContainsKey(key); + + /// Checks if there is any item contained in the current instance corresponding with the specified value. + /// true, if value is contained, false otherwise. + /// Key. + public bool ContainsValue(NSObject value) => dict.ContainsValue(value); + + /// Removes the item belonging to the specified key. + /// Key. + public bool Remove(string key) + { + keys.Remove(key); + + return dict.Remove(key); + } + + /// Tries to get the item corresponding to the specified key + /// true, if get value was successfully found and retrieved, false otherwise. + /// Key. + /// Where to store the value. + public bool TryGetValue(string key, out NSObject value) => dict.TryGetValue(key, out value); + + /// Gets or sets the at the specified index. + /// Index. + public NSObject this[string index] + { + get => dict[index]; + set + { + if(!keys.ContainsKey(index)) keys.Add(index, new NSString(index)); + + dict[index] = value; + } + } + + /// Gets an array with all the keys contained in the current instance. + /// The keys. + public ICollection Keys => dict.Keys; + + /// Gets an array with all the objects contained in the current instance. + /// The objects. + public ICollection Values => dict.Values; + +#endregion + +#region ICollection implementation + + /// Adds the specified item. + /// Item. + public void Add(KeyValuePair item) + { + keys.Add(item.Key, new NSString(item.Key)); + dict.Add(item.Key, item.Value); + } + + /// Clears this instance. + public void Clear() + { + keys.Clear(); + dict.Clear(); + } + + /// Checks if the current instance contains the specified item. + /// Item. + /// true if it is found, false otherwise. + public bool Contains(KeyValuePair item) => dict.ContainsKey(item.Key); + + /// + /// Copies the elements to an existing one-dimensional + /// , starting at the specified array index. + /// + /// + /// The one-dimensional that is the destination of the elements copied from + /// . The must have zero-based indexing. + /// + /// The zero-based index in array at which copying begins. + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ICollection> coll = dict; + coll.CopyTo(array, arrayIndex); + } + + /// Removes the specified item. + /// Item to remove. + /// true if successfully removed, false if not, or if item is not in current instance. + public bool Remove(KeyValuePair item) + { + keys.Remove(item.Key); + + return dict.Remove(item.Key); + } + + /// Gets the count of items in the current instance. + /// How many items are contained in the current instance. + public int Count => dict.Count; + + /// Gets a value indicating whether this instance is read only. + /// true if this instance is read only; otherwise, false. + public bool IsReadOnly => false; + +#endregion } \ No newline at end of file diff --git a/plist-cil/NSNumber.cs b/plist-cil/NSNumber.cs index 380530a..2dd2b85 100644 --- a/plist-cil/NSNumber.cs +++ b/plist-cil/NSNumber.cs @@ -27,197 +27,183 @@ using System; using System.Globalization; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// A number whose value is either an integer, a real number or bool. +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class NSNumber : NSObject, IComparable { - /// A number whose value is either an integer, a real number or bool. - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class NSNumber : NSObject, IComparable + /// + /// Indicates that the number's value is an integer. The number is stored as a .NET . Its + /// original value could have been char, short, int, long or even long long. + /// + public const int INTEGER = 0; + + /// + /// Indicates that the number's value is a real number. The number is stored as a .NET . Its + /// original value could have been float or double. + /// + public const int REAL = 1; + + /// Indicates that the number's value is bool. + public const int BOOLEAN = 2; + readonly bool boolValue; + readonly double doubleValue; + + readonly long longValue; + + //Holds the current type of this number + readonly int type; + + /// + /// Parses integers and real numbers from their binary representation. + /// Note: real numbers are not yet supported. + /// + /// The binary representation + /// The type of number + /// + /// + public NSNumber(ReadOnlySpan bytes, int type) { - /// - /// Indicates that the number's value is an integer. The number is stored as a .NET . Its - /// original value could have been char, short, int, long or even long long. - /// - public const int INTEGER = 0; - - /// - /// Indicates that the number's value is a real number. The number is stored as a .NET . Its - /// original value could have been float or double. - /// - public const int REAL = 1; - - /// Indicates that the number's value is bool. - public const int BOOLEAN = 2; - readonly bool boolValue; - readonly double doubleValue; - - readonly long longValue; - - //Holds the current type of this number - readonly int type; - - /// - /// Parses integers and real numbers from their binary representation. - /// Note: real numbers are not yet supported. - /// - /// The binary representation - /// The type of number - /// - /// - public NSNumber(ReadOnlySpan bytes, int type) + switch(type) { - switch(type) - { - case INTEGER: - doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes); + case INTEGER: + doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes); - break; + break; - case REAL: - doubleValue = BinaryPropertyListParser.ParseDouble(bytes); - longValue = (long)Math.Round(doubleValue); - - break; - - default: throw new ArgumentException("Type argument is not valid.", nameof(type)); - } - - this.type = type; - } - - public NSNumber(string text, int type) - { - switch(type) - { - case INTEGER: - { - doubleValue = longValue = long.Parse(text, CultureInfo.InvariantCulture); - - break; - } - case REAL: - { - doubleValue = double.Parse(text, CultureInfo.InvariantCulture); - longValue = (long)Math.Round(doubleValue); - - break; - } - default: - { - throw new ArgumentException("Type argument is not valid."); - } - } - - this.type = type; - } - - /// Creates a number from its textual representation. - /// The textual representation of the number. - /// - /// - /// - public NSNumber(string text) - { - if(text == null) - throw new ArgumentException("The given string is null and cannot be parsed as number."); - - if(text.StartsWith("0x") && - long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, - out long l)) - { - doubleValue = longValue = l; - type = INTEGER; - } - else if(long.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out l)) - { - doubleValue = longValue = l; - type = INTEGER; - } - else if(double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out double d)) - { - doubleValue = d; + case REAL: + doubleValue = BinaryPropertyListParser.ParseDouble(bytes); longValue = (long)Math.Round(doubleValue); - type = REAL; - } - else - { - bool isTrue = string.Equals(text, "true", StringComparison.CurrentCultureIgnoreCase) || - string.Equals(text, "yes", StringComparison.CurrentCultureIgnoreCase); - bool isFalse = string.Equals(text, "false", StringComparison.CurrentCultureIgnoreCase) || - string.Equals(text, "no", StringComparison.CurrentCultureIgnoreCase); + break; - if(isTrue || isFalse) - { - type = BOOLEAN; - boolValue = isTrue; - doubleValue = longValue = boolValue ? 1 : 0; - } - else - throw new - ArgumentException("The given string neither represents a double, an int nor a bool value."); - } + default: + throw new ArgumentException("Type argument is not valid.", nameof(type)); } - /// Creates an integer number. - /// The integer value. - public NSNumber(int i) + this.type = type; + } + + public NSNumber(string text, int type) + { + switch(type) { - doubleValue = longValue = i; - type = INTEGER; + case INTEGER: + { + doubleValue = longValue = long.Parse(text, CultureInfo.InvariantCulture); + + break; + } + case REAL: + { + doubleValue = double.Parse(text, CultureInfo.InvariantCulture); + longValue = (long)Math.Round(doubleValue); + + break; + } + default: + { + throw new ArgumentException("Type argument is not valid."); + } } - /// Creates an integer number. - /// The long integer value. - public NSNumber(long l) + this.type = type; + } + + /// Creates a number from its textual representation. + /// The textual representation of the number. + /// + /// + /// + public NSNumber(string text) + { + if(text == null) throw new ArgumentException("The given string is null and cannot be parsed as number."); + + if(text.StartsWith("0x") && + long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long l)) { doubleValue = longValue = l; type = INTEGER; } - - /// Creates a real number. - /// The real value. - public NSNumber(double d) + else if(long.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out l)) { - longValue = (long)(doubleValue = d); - type = REAL; + doubleValue = longValue = l; + type = INTEGER; } - - /// Creates a bool number. - /// The bool value. - public NSNumber(bool b) + else if(double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out double d)) { - boolValue = b; - doubleValue = longValue = b ? 1 : 0; - type = BOOLEAN; + doubleValue = d; + longValue = (long)Math.Round(doubleValue); + type = REAL; } - - /// Compares the current to the specified object. - /// - /// 0 if the numbers are equal, 1 if the current is greater than the - /// argument and -1 if it is less, or the argument is not a number. - /// - /// Object to compare to the current . - public int CompareTo(object o) + else { - double x = ToDouble(); - double y; + bool isTrue = string.Equals(text, "true", StringComparison.CurrentCultureIgnoreCase) || + string.Equals(text, "yes", StringComparison.CurrentCultureIgnoreCase); - if(o is NSNumber num) + bool isFalse = string.Equals(text, "false", StringComparison.CurrentCultureIgnoreCase) || + string.Equals(text, "no", StringComparison.CurrentCultureIgnoreCase); + + if(isTrue || isFalse) { - y = num.ToDouble(); - - return x < y - ? -1 - : x == y - ? 0 - : 1; + type = BOOLEAN; + boolValue = isTrue; + doubleValue = longValue = boolValue ? 1 : 0; } + else + throw new ArgumentException("The given string neither represents a double, an int nor a bool value."); + } + } - if(!IsNumber(o)) - return -1; + /// Creates an integer number. + /// The integer value. + public NSNumber(int i) + { + doubleValue = longValue = i; + type = INTEGER; + } - y = GetDoubleFromObject(o); + /// Creates an integer number. + /// The long integer value. + public NSNumber(long l) + { + doubleValue = longValue = l; + type = INTEGER; + } + + /// Creates a real number. + /// The real value. + public NSNumber(double d) + { + longValue = (long)(doubleValue = d); + type = REAL; + } + + /// Creates a bool number. + /// The bool value. + public NSNumber(bool b) + { + boolValue = b; + doubleValue = longValue = b ? 1 : 0; + type = BOOLEAN; + } + + /// Compares the current to the specified object. + /// + /// 0 if the numbers are equal, 1 if the current is greater than the + /// argument and -1 if it is less, or the argument is not a number. + /// + /// Object to compare to the current . + public int CompareTo(object o) + { + double x = ToDouble(); + double y; + + if(o is NSNumber num) + { + y = num.ToDouble(); return x < y ? -1 @@ -226,320 +212,329 @@ namespace Claunia.PropertyList : 1; } - /// Gets the type of this number's value. - /// The type flag. - /// - /// - /// - public int GetNSNumberType() => type; + if(!IsNumber(o)) return -1; - /// Checks whether the value of this NSNumber is a bool. - /// Whether the number's value is a bool. - public bool isBoolean() => type == BOOLEAN; + y = GetDoubleFromObject(o); - /// Checks whether the value of this NSNumber is an integer. - /// Whether the number's value is an integer. - public bool isInteger() => type == INTEGER; - - /// Checks whether the value of this NSNumber is a real number. - /// Whether the number's value is a real number. - public bool isReal() => type == REAL; - - /// The number's bool value. - /// true if the value is true or non-zero, false otherwise. - public bool ToBool() - { - if(type == BOOLEAN) - return boolValue; - - return longValue != 0; - } - - /// The number's long value. - /// The value of the number as long - public long ToLong() => longValue; - - /// - /// The number's int value. - /// - /// Note: Even though the number's type might be INTEGER it can be larger than a Java int. Use intValue() only if - /// you are certain that it contains a number from the int range. Otherwise the value might be inaccurate. - /// - /// - /// The value of the number as int. - public int ToInt() => (int)longValue; - - /// The number's double value. - /// The value of the number as double. - public double ToDouble() => doubleValue; - - /// The number's float value. WARNING: Possible loss of precision if the value is outside the float range. - /// The value of the number as float. - public float floatValue() => (float)doubleValue; - - /// Checks whether the other object is a NSNumber of the same value. - /// The object to compare to. - /// Whether the objects are equal in terms of numeric value and type. - public override bool Equals(object obj) - { - if(obj is not NSNumber number) - return false; - - return type == number.type && longValue == number.longValue && doubleValue == number.doubleValue && - boolValue == number.boolValue; - } - - /// Serves as a hash function for a object. - /// - /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a - /// hash table. - /// - public override int GetHashCode() - { - int hash = type; - hash = (37 * hash) + (int)(longValue ^ ((uint)longValue >> 32)); - - hash = (37 * hash) + (int)(BitConverter.DoubleToInt64Bits(doubleValue) ^ - (uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32)); - - hash = (37 * hash) + (ToBool() ? 1 : 0); - - return hash; - } - - /// - /// Returns a that represents the current - /// . - /// - /// A that represents the current . - public override string ToString() => type switch - { - INTEGER => ToLong().ToString(), - REAL => ToDouble().ToString("R", CultureInfo.InvariantCulture), - BOOLEAN => ToBool().ToString(), - _ => base.ToString() - }; - - internal override void ToXml(StringBuilder xml, int level) - { - Indent(xml, level); - - switch(type) - { - case INTEGER: - { - xml.Append(""); - xml.Append(ToLong()); - xml.Append(""); - - break; - } - case REAL: - { - xml.Append(""); - - if(doubleValue == 0) - xml.Append("0.0"); - else - xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture)); - - xml.Append(""); - - break; - } - case BOOLEAN: - { - xml.Append(ToBool() ? "" : ""); - - break; - } - } - } - - internal override void ToBinary(BinaryPropertyListWriter outPlist) - { - switch(GetNSNumberType()) - { - case INTEGER: - { - if(ToLong() < 0) - { - outPlist.Write(0x13); - outPlist.WriteBytes(ToLong(), 8); - } - else if(ToLong() <= 0xff) - { - outPlist.Write(0x10); - outPlist.WriteBytes(ToLong(), 1); - } - else if(ToLong() <= 0xffff) - { - outPlist.Write(0x11); - outPlist.WriteBytes(ToLong(), 2); - } - else if(ToLong() <= 0xffffffffL) - { - outPlist.Write(0x12); - outPlist.WriteBytes(ToLong(), 4); - } - else - { - outPlist.Write(0x13); - outPlist.WriteBytes(ToLong(), 8); - } - - break; - } - case REAL: - { - outPlist.Write(0x23); - outPlist.WriteDouble(ToDouble()); - - break; - } - case BOOLEAN: - { - outPlist.Write(ToBool() ? 0x09 : 0x08); - - break; - } - } - } - - internal override void ToASCII(StringBuilder ascii, int level) - { - Indent(ascii, level); - - if(type == BOOLEAN) - ascii.Append(boolValue ? "YES" : "NO"); - else - ascii.Append(ToString()); - } - - internal override void ToASCIIGnuStep(StringBuilder ascii, int level) - { - Indent(ascii, level); - - switch(type) - { - case INTEGER: - { - ascii.Append("<*I"); - ascii.Append(ToString()); - ascii.Append(">"); - - break; - } - case REAL: - { - ascii.Append("<*R"); - ascii.Append(ToString()); - ascii.Append(">"); - - break; - } - case BOOLEAN: - { - ascii.Append(boolValue ? "<*BY>" : "<*BN>"); - - break; - } - } - } - - /// Determines if an object is a number. Substitutes .NET's Number class comparison - /// true if it is a number. - /// Object. - static bool IsNumber(object o) => - o is sbyte or byte or short or ushort or int or uint or long or ulong or float or double or decimal; - - static double GetDoubleFromObject(object o) => o switch - { - sbyte @sbyte => @sbyte, - byte b => b, - short s => s, - ushort @ushort => @ushort, - int i => i, - uint u => u, - long l => l, - ulong @ulong => @ulong, - float f => f, - double d => d, - decimal @decimal => (double)@decimal, - _ => 0 - }; - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(NSObject obj) - { - if(obj is not NSNumber number) - return false; - - if(number.GetNSNumberType() != type) - return false; - - return type switch - { - INTEGER => longValue == number.ToLong(), - REAL => doubleValue == number.ToDouble(), - BOOLEAN => boolValue == number.ToBool(), - _ => false - }; - } - - public static explicit operator ulong(NSNumber value) => (ulong)value.longValue; - - public static explicit operator long(NSNumber value) => value.longValue; - - public static explicit operator uint(NSNumber value) => (uint)value.longValue; - - public static explicit operator int(NSNumber value) => (int)value.longValue; - - public static explicit operator ushort(NSNumber value) => (ushort)value.longValue; - - public static explicit operator short(NSNumber value) => (short)value.longValue; - - public static explicit operator byte(NSNumber value) => (byte)value.longValue; - - public static explicit operator sbyte(NSNumber value) => (sbyte)value.longValue; - - public static explicit operator double(NSNumber value) => value.doubleValue; - - public static explicit operator float(NSNumber value) => (float)value.doubleValue; - - public static explicit operator bool(NSNumber value) => value.boolValue; - - public static explicit operator NSNumber(ulong value) => new(value); - - public static explicit operator NSNumber(long value) => new(value); - - public static explicit operator NSNumber(uint value) => new(value); - - public static explicit operator NSNumber(int value) => new(value); - - public static explicit operator NSNumber(ushort value) => new(value); - - public static explicit operator NSNumber(short value) => new(value); - - public static explicit operator NSNumber(byte value) => new(value); - - public static explicit operator NSNumber(sbyte value) => new(value); - - public static explicit operator NSNumber(double value) => new(value); - - public static explicit operator NSNumber(float value) => new(value); - - public static explicit operator NSNumber(bool value) => new(value); + return x < y + ? -1 + : x == y + ? 0 + : 1; } + + /// Gets the type of this number's value. + /// The type flag. + /// + /// + /// + public int GetNSNumberType() => type; + + /// Checks whether the value of this NSNumber is a bool. + /// Whether the number's value is a bool. + public bool isBoolean() => type == BOOLEAN; + + /// Checks whether the value of this NSNumber is an integer. + /// Whether the number's value is an integer. + public bool isInteger() => type == INTEGER; + + /// Checks whether the value of this NSNumber is a real number. + /// Whether the number's value is a real number. + public bool isReal() => type == REAL; + + /// The number's bool value. + /// true if the value is true or non-zero, false otherwise. + public bool ToBool() + { + if(type == BOOLEAN) return boolValue; + + return longValue != 0; + } + + /// The number's long value. + /// The value of the number as long + public long ToLong() => longValue; + + /// + /// The number's int value. + /// + /// Note: Even though the number's type might be INTEGER it can be larger than a Java int. Use intValue() only if + /// you are certain that it contains a number from the int range. Otherwise the value might be inaccurate. + /// + /// + /// The value of the number as int. + public int ToInt() => (int)longValue; + + /// The number's double value. + /// The value of the number as double. + public double ToDouble() => doubleValue; + + /// The number's float value. WARNING: Possible loss of precision if the value is outside the float range. + /// The value of the number as float. + public float floatValue() => (float)doubleValue; + + /// Checks whether the other object is a NSNumber of the same value. + /// The object to compare to. + /// Whether the objects are equal in terms of numeric value and type. + public override bool Equals(object obj) + { + if(obj is not NSNumber number) return false; + + return type == number.type && + longValue == number.longValue && + doubleValue == number.doubleValue && + boolValue == number.boolValue; + } + + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a + /// hash table. + /// + public override int GetHashCode() + { + int hash = type; + hash = 37 * hash + (int)(longValue ^ (uint)longValue >> 32); + + hash = 37 * hash + + (int)(BitConverter.DoubleToInt64Bits(doubleValue) ^ + (uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32)); + + hash = 37 * hash + (ToBool() ? 1 : 0); + + return hash; + } + + /// + /// Returns a that represents the current + /// . + /// + /// A that represents the current . + public override string ToString() => type switch + { + INTEGER => ToLong().ToString(), + REAL => ToDouble().ToString("R", CultureInfo.InvariantCulture), + BOOLEAN => ToBool().ToString(), + _ => base.ToString() + }; + + internal override void ToXml(StringBuilder xml, int level) + { + Indent(xml, level); + + switch(type) + { + case INTEGER: + { + xml.Append(""); + xml.Append(ToLong()); + xml.Append(""); + + break; + } + case REAL: + { + xml.Append(""); + + if(doubleValue == 0) + xml.Append("0.0"); + else + xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture)); + + xml.Append(""); + + break; + } + case BOOLEAN: + { + xml.Append(ToBool() ? "" : ""); + + break; + } + } + } + + internal override void ToBinary(BinaryPropertyListWriter outPlist) + { + switch(GetNSNumberType()) + { + case INTEGER: + { + if(ToLong() < 0) + { + outPlist.Write(0x13); + outPlist.WriteBytes(ToLong(), 8); + } + else if(ToLong() <= 0xff) + { + outPlist.Write(0x10); + outPlist.WriteBytes(ToLong(), 1); + } + else if(ToLong() <= 0xffff) + { + outPlist.Write(0x11); + outPlist.WriteBytes(ToLong(), 2); + } + else if(ToLong() <= 0xffffffffL) + { + outPlist.Write(0x12); + outPlist.WriteBytes(ToLong(), 4); + } + else + { + outPlist.Write(0x13); + outPlist.WriteBytes(ToLong(), 8); + } + + break; + } + case REAL: + { + outPlist.Write(0x23); + outPlist.WriteDouble(ToDouble()); + + break; + } + case BOOLEAN: + { + outPlist.Write(ToBool() ? 0x09 : 0x08); + + break; + } + } + } + + internal override void ToASCII(StringBuilder ascii, int level) + { + Indent(ascii, level); + + if(type == BOOLEAN) + ascii.Append(boolValue ? "YES" : "NO"); + else + ascii.Append(ToString()); + } + + internal override void ToASCIIGnuStep(StringBuilder ascii, int level) + { + Indent(ascii, level); + + switch(type) + { + case INTEGER: + { + ascii.Append("<*I"); + ascii.Append(ToString()); + ascii.Append(">"); + + break; + } + case REAL: + { + ascii.Append("<*R"); + ascii.Append(ToString()); + ascii.Append(">"); + + break; + } + case BOOLEAN: + { + ascii.Append(boolValue ? "<*BY>" : "<*BN>"); + + break; + } + } + } + + /// Determines if an object is a number. Substitutes .NET's Number class comparison + /// true if it is a number. + /// Object. + static bool IsNumber(object o) => + o is sbyte or byte or short or ushort or int or uint or long or ulong or float or double or decimal; + + static double GetDoubleFromObject(object o) => o switch + { + sbyte @sbyte => @sbyte, + byte b => b, + short s => s, + ushort @ushort => @ushort, + int i => i, + uint u => u, + long l => l, + ulong @ulong => @ulong, + float f => f, + double d => d, + decimal @decimal => (double)@decimal, + _ => 0 + }; + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(NSObject obj) + { + if(obj is not NSNumber number) return false; + + if(number.GetNSNumberType() != type) return false; + + return type switch + { + INTEGER => longValue == number.ToLong(), + REAL => doubleValue == number.ToDouble(), + BOOLEAN => boolValue == number.ToBool(), + _ => false + }; + } + + public static explicit operator ulong(NSNumber value) => (ulong)value.longValue; + + public static explicit operator long(NSNumber value) => value.longValue; + + public static explicit operator uint(NSNumber value) => (uint)value.longValue; + + public static explicit operator int(NSNumber value) => (int)value.longValue; + + public static explicit operator ushort(NSNumber value) => (ushort)value.longValue; + + public static explicit operator short(NSNumber value) => (short)value.longValue; + + public static explicit operator byte(NSNumber value) => (byte)value.longValue; + + public static explicit operator sbyte(NSNumber value) => (sbyte)value.longValue; + + public static explicit operator double(NSNumber value) => value.doubleValue; + + public static explicit operator float(NSNumber value) => (float)value.doubleValue; + + public static explicit operator bool(NSNumber value) => value.boolValue; + + public static explicit operator NSNumber(ulong value) => new(value); + + public static explicit operator NSNumber(long value) => new(value); + + public static explicit operator NSNumber(uint value) => new(value); + + public static explicit operator NSNumber(int value) => new(value); + + public static explicit operator NSNumber(ushort value) => new(value); + + public static explicit operator NSNumber(short value) => new(value); + + public static explicit operator NSNumber(byte value) => new(value); + + public static explicit operator NSNumber(sbyte value) => new(value); + + public static explicit operator NSNumber(double value) => new(value); + + public static explicit operator NSNumber(float value) => new(value); + + public static explicit operator NSNumber(bool value) => new(value); } \ No newline at end of file diff --git a/plist-cil/NSObject.cs b/plist-cil/NSObject.cs index d367052..95d5b5d 100644 --- a/plist-cil/NSObject.cs +++ b/plist-cil/NSObject.cs @@ -27,426 +27,401 @@ using System; using System.Collections.Generic; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// Abstract interface for any object contained in a property list. +/// The names and functions of the various objects orient themselves towards Apple's Cocoa API. +/// +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public abstract class NSObject { /// - /// Abstract interface for any object contained in a property list. - /// The names and functions of the various objects orient themselves towards Apple's Cocoa API. + /// 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). /// - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public abstract class NSObject + internal static readonly string NEWLINE = "\n"; + + /// The indentation character used for generating the XML output. This is the tabulator character. + static readonly string INDENT = "\t"; + + /// + /// The maximum length of the text lines to be used when generating ASCII property lists. But this number is only + /// a guideline it is not guaranteed that it will not be overstepped. + /// + internal static readonly int ASCII_LINE_LENGTH = 80; + + /// Generates the XML representation of the object (without XML headers or enclosing plist-tags). + /// The StringBuilder onto which the XML representation is appended. + /// The indentation level of the object. + internal abstract void ToXml(StringBuilder xml, int level); + + /// Assigns IDs to all the objects in this NSObject subtree. + /// The writer object that handles the binary serialization. + internal virtual void AssignIDs(BinaryPropertyListWriter outPlist) => outPlist.AssignID(this); + + /// Generates the binary representation of the object. + /// The output stream to serialize the object to. + internal abstract void ToBinary(BinaryPropertyListWriter outPlist); + + /// Generates a valid XML property list including headers using this object as root. + /// The XML representation of the property list including XML header and doctype information. + public string ToXmlPropertyList() { - /// - /// 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). - /// - internal static readonly string NEWLINE = "\n"; + var xml = new StringBuilder(""); + xml.Append(NEWLINE); + xml.Append(""); + xml.Append(NEWLINE); + xml.Append(""); + xml.Append(NEWLINE); + ToXml(xml, 0); + xml.Append(NEWLINE); + xml.Append(""); + xml.Append(NEWLINE); - /// The indentation character used for generating the XML output. This is the tabulator character. - static readonly string INDENT = "\t"; + return xml.ToString(); + } - /// - /// The maximum length of the text lines to be used when generating ASCII property lists. But this number is only - /// a guideline it is not guaranteed that it will not be overstepped. - /// - internal static readonly int ASCII_LINE_LENGTH = 80; + /// + /// Generates the ASCII representation of this object. The generated ASCII representation does not end with a + /// newline. Complies with + /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html + /// + /// The StringBuilder onto which the ASCII representation is appended. + /// The indentation level of the object. + internal abstract void ToASCII(StringBuilder ascii, int level); - /// Generates the XML representation of the object (without XML headers or enclosing plist-tags). - /// The StringBuilder onto which the XML representation is appended. - /// The indentation level of the object. - internal abstract void ToXml(StringBuilder xml, int level); + /// + /// Generates the ASCII representation of this object in the GnuStep format. The generated ASCII representation + /// does not end with a newline. + /// + /// The StringBuilder onto which the ASCII representation is appended. + /// The indentation level of the object. + internal abstract void ToASCIIGnuStep(StringBuilder ascii, int level); - /// Assigns IDs to all the objects in this NSObject subtree. - /// The writer object that handles the binary serialization. - internal virtual void AssignIDs(BinaryPropertyListWriter outPlist) => outPlist.AssignID(this); + /// + /// Helper method that adds correct indentation to the xml output. Calling this method will add level + /// number of tab characters to the xml string. + /// + /// The string builder for the XML document. + /// The level of indentation. + internal static void Indent(StringBuilder xml, int level) + { + for(int i = 0; i < level; i++) xml.Append(INDENT); + } - /// Generates the binary representation of the object. - /// The output stream to serialize the object to. - internal abstract void ToBinary(BinaryPropertyListWriter outPlist); + /// Wraps the given value inside a NSObject. + /// The value to represent as a NSObject. + /// A NSObject representing the given value. + public static NSNumber Wrap(long value) => new(value); - /// Generates a valid XML property list including headers using this object as root. - /// The XML representation of the property list including XML header and doctype information. - public string ToXmlPropertyList() + /// Wraps the given value inside a NSObject. + /// The value to represent as a NSObject. + /// A NSObject representing the given value. + public static NSNumber Wrap(double value) => new(value); + + /// Wraps the given value inside a NSObject. + /// The value to represent as a NSObject. + /// A NSObject representing the given value. + public static NSNumber Wrap(bool value) => new(value); + + /// Wraps the given value inside a NSObject. + /// The value to represent as a NSObject. + /// A NSObject representing the given value. + public static NSData Wrap(byte[] value) => new(value); + + /// Creates a NSArray with the contents of the given array. + /// The value to represent as a NSObject. + /// A NSObject representing the given value. + /// When one of the objects contained in the array cannot be represented by a NSObject. + public static NSArray Wrap(object[] value) + { + var arr = new NSArray(value.Length); + + for(int i = 0; i < value.Length; i++) arr.Add(Wrap(value[i])); + + return arr; + } + + /// Creates a NSDictionary with the contents of the given map. + /// The value to represent as a NSObject. + /// A NSObject representing the given value. + /// When one of the values contained in the map cannot be represented by a NSObject. + public static NSDictionary Wrap(Dictionary value) + { + var dict = new NSDictionary(); + + foreach(KeyValuePair kvp in value) dict.Add(kvp.Key, Wrap(kvp.Value)); + + return dict; + } + + /// Creates a NSSet with the contents of this set. + /// The value to represent as a NSObject. + /// A NSObject representing the given value. + /// When one of the values contained in the map cannot be represented by a NSObject. + public static NSSet Wrap(List value) + { + var set = new NSSet(); + + foreach(object o in value) set.AddObject(Wrap(o)); + + return set; + } + + /// + /// Creates a NSObject representing the given .NET Object. + /// + /// Numerics of type , , , , + /// , or are wrapped as NSNumber objects. + /// + /// Strings are wrapped as objects and byte arrays as objects. + /// DateTime objects are wrapped as objects. + /// Serializable classes are serialized and their data is stored in objects. + /// + /// Arrays and Collection objects are converted to where each array member is wrapped into + /// a . + /// + /// + /// Dictionaries are converted to . Each key is converted to a string and each value + /// wrapped into a . + /// + /// + /// The object to represent. + /// A NSObject equivalent to the given object. + public static NSObject Wrap(object o) + { + if(o == null) throw new NullReferenceException("A null object cannot be wrapped as a NSObject"); + + if(o is NSObject nsObject) return nsObject; + + Type c = o.GetType(); + + if(typeof(bool).Equals(c)) return Wrap((bool)o); + + if(typeof(byte).Equals(c)) return Wrap((byte)o); + + if(typeof(short).Equals(c)) return Wrap((short)o); + + if(typeof(int).Equals(c)) return Wrap((int)o); + + if(typeof(long).IsAssignableFrom(c)) return Wrap((long)o); + + if(typeof(float).Equals(c)) return Wrap((float)o); + + if(typeof(double).IsAssignableFrom(c)) return Wrap((double)o); + + if(typeof(string).Equals(c)) return new NSString((string)o); + + if(typeof(DateTime).Equals(c)) return new NSDate((DateTime)o); + + if(c.IsArray) { - var xml = new StringBuilder(""); - xml.Append(NEWLINE); - xml.Append(""); - xml.Append(NEWLINE); - xml.Append(""); - xml.Append(NEWLINE); - ToXml(xml, 0); - xml.Append(NEWLINE); - xml.Append(""); - xml.Append(NEWLINE); + Type cc = c.GetElementType(); - return xml.ToString(); + if(cc.Equals(typeof(byte))) return Wrap((byte[])o); + + if(cc.Equals(typeof(bool))) + { + bool[] array = (bool[])o; + var nsa = new NSArray(array.Length); + + for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i])); + + return nsa; + } + + if(cc.Equals(typeof(float))) + { + float[] array = (float[])o; + var nsa = new NSArray(array.Length); + + for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i])); + + return nsa; + } + + if(cc.Equals(typeof(double))) + { + double[] array = (double[])o; + var nsa = new NSArray(array.Length); + + for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i])); + + return nsa; + } + + if(cc.Equals(typeof(short))) + { + short[] array = (short[])o; + var nsa = new NSArray(array.Length); + + for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i])); + + return nsa; + } + + if(cc.Equals(typeof(int))) + { + int[] array = (int[])o; + var nsa = new NSArray(array.Length); + + for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i])); + + return nsa; + } + + if(cc.Equals(typeof(long))) + { + long[] array = (long[])o; + var nsa = new NSArray(array.Length); + + for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i])); + + return nsa; + } + + return Wrap((object[])o); } - /// - /// Generates the ASCII representation of this object. The generated ASCII representation does not end with a - /// newline. Complies with - /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html - /// - /// The StringBuilder onto which the ASCII representation is appended. - /// The indentation level of the object. - internal abstract void ToASCII(StringBuilder ascii, int level); - - /// - /// Generates the ASCII representation of this object in the GnuStep format. The generated ASCII representation - /// does not end with a newline. - /// - /// The StringBuilder onto which the ASCII representation is appended. - /// The indentation level of the object. - internal abstract void ToASCIIGnuStep(StringBuilder ascii, int level); - - /// - /// Helper method that adds correct indentation to the xml output. Calling this method will add level - /// number of tab characters to the xml string. - /// - /// The string builder for the XML document. - /// The level of indentation. - internal static void Indent(StringBuilder xml, int level) + if(typeof(Dictionary).IsAssignableFrom(c)) { - for(int i = 0; i < level; i++) - xml.Append(INDENT); - } + var netDict = (Dictionary)o; + var dict = new NSDictionary(); - /// Wraps the given value inside a NSObject. - /// The value to represent as a NSObject. - /// A NSObject representing the given value. - public static NSNumber Wrap(long value) => new(value); - - /// Wraps the given value inside a NSObject. - /// The value to represent as a NSObject. - /// A NSObject representing the given value. - public static NSNumber Wrap(double value) => new(value); - - /// Wraps the given value inside a NSObject. - /// The value to represent as a NSObject. - /// A NSObject representing the given value. - public static NSNumber Wrap(bool value) => new(value); - - /// Wraps the given value inside a NSObject. - /// The value to represent as a NSObject. - /// A NSObject representing the given value. - public static NSData Wrap(byte[] value) => new(value); - - /// Creates a NSArray with the contents of the given array. - /// The value to represent as a NSObject. - /// A NSObject representing the given value. - /// When one of the objects contained in the array cannot be represented by a NSObject. - public static NSArray Wrap(object[] value) - { - var arr = new NSArray(value.Length); - - for(int i = 0; i < value.Length; i++) - arr.Add(Wrap(value[i])); - - return arr; - } - - /// Creates a NSDictionary with the contents of the given map. - /// The value to represent as a NSObject. - /// A NSObject representing the given value. - /// When one of the values contained in the map cannot be represented by a NSObject. - public static NSDictionary Wrap(Dictionary value) - { - var dict = new NSDictionary(); - - foreach(KeyValuePair kvp in value) - dict.Add(kvp.Key, Wrap(kvp.Value)); + foreach(KeyValuePair kvp in netDict) dict.Add(kvp.Key, Wrap(kvp.Value)); return dict; } - /// Creates a NSSet with the contents of this set. - /// The value to represent as a NSObject. - /// A NSObject representing the given value. - /// When one of the values contained in the map cannot be represented by a NSObject. - public static NSSet Wrap(List value) + if(typeof(List).IsAssignableFrom(c)) return Wrap(((List)o).ToArray()); + + if(typeof(List>).IsAssignableFrom(c)) { - var set = new NSSet(); + var list = new NSArray(); + foreach(Dictionary dict in (List>)o) list.Add(Wrap(dict)); - foreach(object o in value) - set.AddObject(Wrap(o)); - - return set; + return list; } - /// - /// Creates a NSObject representing the given .NET Object. - /// - /// Numerics of type , , , , - /// , or are wrapped as NSNumber objects. - /// - /// Strings are wrapped as objects and byte arrays as objects. - /// DateTime objects are wrapped as objects. - /// Serializable classes are serialized and their data is stored in objects. - /// - /// Arrays and Collection objects are converted to where each array member is wrapped into - /// a . - /// - /// - /// Dictionaries are converted to . Each key is converted to a string and each value - /// wrapped into a . - /// - /// - /// The object to represent. - /// A NSObject equivalent to the given object. - public static NSObject Wrap(object o) - { - if(o == null) - throw new NullReferenceException("A null object cannot be wrapped as a NSObject"); - - if(o is NSObject nsObject) - return nsObject; - - Type c = o.GetType(); - - if(typeof(bool).Equals(c)) - return Wrap((bool)o); - - if(typeof(byte).Equals(c)) - return Wrap((byte)o); - - if(typeof(short).Equals(c)) - return Wrap((short)o); - - if(typeof(int).Equals(c)) - return Wrap((int)o); - - if(typeof(long).IsAssignableFrom(c)) - return Wrap((long)o); - - if(typeof(float).Equals(c)) - return Wrap((float)o); - - if(typeof(double).IsAssignableFrom(c)) - return Wrap((double)o); - - if(typeof(string).Equals(c)) - return new NSString((string)o); - - if(typeof(DateTime).Equals(c)) - return new NSDate((DateTime)o); - - if(c.IsArray) - { - Type cc = c.GetElementType(); - - if(cc.Equals(typeof(byte))) - return Wrap((byte[])o); - - if(cc.Equals(typeof(bool))) - { - bool[] array = (bool[])o; - var nsa = new NSArray(array.Length); - - for(int i = 0; i < array.Length; i++) - nsa.Add(Wrap(array[i])); - - return nsa; - } - - if(cc.Equals(typeof(float))) - { - float[] array = (float[])o; - var nsa = new NSArray(array.Length); - - for(int i = 0; i < array.Length; i++) - nsa.Add(Wrap(array[i])); - - return nsa; - } - - if(cc.Equals(typeof(double))) - { - double[] array = (double[])o; - var nsa = new NSArray(array.Length); - - for(int i = 0; i < array.Length; i++) - nsa.Add(Wrap(array[i])); - - return nsa; - } - - if(cc.Equals(typeof(short))) - { - short[] array = (short[])o; - var nsa = new NSArray(array.Length); - - for(int i = 0; i < array.Length; i++) - nsa.Add(Wrap(array[i])); - - return nsa; - } - - if(cc.Equals(typeof(int))) - { - int[] array = (int[])o; - var nsa = new NSArray(array.Length); - - for(int i = 0; i < array.Length; i++) - nsa.Add(Wrap(array[i])); - - return nsa; - } - - if(cc.Equals(typeof(long))) - { - long[] array = (long[])o; - var nsa = new NSArray(array.Length); - - for(int i = 0; i < array.Length; i++) - nsa.Add(Wrap(array[i])); - - return nsa; - } - - return Wrap((object[])o); - } - - if(typeof(Dictionary).IsAssignableFrom(c)) - { - Dictionary netDict = (Dictionary)o; - var dict = new NSDictionary(); - - foreach(KeyValuePair kvp in netDict) - dict.Add(kvp.Key, Wrap(kvp.Value)); - - return dict; - } - - if(typeof(List).IsAssignableFrom(c)) - return Wrap(((List)o).ToArray()); - - if(typeof(List>).IsAssignableFrom(c)) - { - var list = new NSArray(); - foreach(Dictionary dict in (List>)o) - list.Add(Wrap(dict)); - return list; - } - - throw new PropertyListException($"Cannot wrap an object of type {o.GetType().Name}."); - } - - /// - /// Converts this NSObject into an equivalent object of the .NET Runtime Environment. - /// objects are converted to arrays. - /// - /// objects are converted to objects extending the - /// class. - /// - /// objects are converted to objects extending the class. - /// - /// objects are converted to primitive number values (, - /// , or ). - /// - /// objects are converted to objects. - /// objects are converted to arrays. - /// objects are converted to objects. - /// objects are converted to arrays. - /// - /// A native .NET object representing this NSObject's value. - public object ToObject() - { - switch(this) - { - case NSArray: - { - var nsArray = (NSArray)this; - object[] array = new object[nsArray.Count]; - - for(int i = 0; i < nsArray.Count; i++) - array[i] = nsArray[i].ToObject(); - - return array; - } - case NSDictionary: - { - Dictionary dictA = ((NSDictionary)this).GetDictionary(); - Dictionary dictB = new(dictA.Count); - - foreach(KeyValuePair kvp in dictA) - dictB.Add(kvp.Key, kvp.Value.ToObject()); - - return dictB; - } - case NSSet: - { - List setA = ((NSSet)this).GetSet(); - List setB = new(); - - foreach(NSObject o in setA) - setB.Add(o.ToObject()); - - return setB; - } - case NSNumber: - { - var num = (NSNumber)this; - - switch(num.GetNSNumberType()) - { - case NSNumber.INTEGER: - { - long longVal = num.ToLong(); - - if(longVal is > int.MaxValue or < int.MinValue) - return longVal; - - return num.ToInt(); - } - case NSNumber.REAL: return num.ToDouble(); - case NSNumber.BOOLEAN: return num.ToBool(); - default: return num.ToDouble(); - } - - break; - } - case NSString: return ((NSString)this).Content; - case NSData: return ((NSData)this).Bytes; - case NSDate: return ((NSDate)this).Date; - case UID: return ((UID)this).Bytes; - default: return this; - } - } - - internal static bool ArrayEquals(byte[] arrayA, byte[] arrayB) - { - if(arrayA.Length != arrayB.Length) - return false; - - for(int i = 0; i < arrayA.Length; i++) - if(arrayA[i] != arrayB[i]) - return false; - - return true; - } - - internal static bool ArrayEquals(IList arrayA, IList arrayB) - { - if(arrayA.Count != arrayB.Count) - return false; - - for(int i = 0; i < arrayA.Count; i++) - if(arrayA[i] != arrayB[i]) - return false; - - return true; - } - - /// Determines if the specific NSObject is the same as the NSObject overriding this method. - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public abstract bool Equals(NSObject obj); + throw new PropertyListException($"Cannot wrap an object of type {o.GetType().Name}."); } + + /// + /// Converts this NSObject into an equivalent object of the .NET Runtime Environment. + /// objects are converted to arrays. + /// + /// objects are converted to objects extending the + /// class. + /// + /// objects are converted to objects extending the class. + /// + /// objects are converted to primitive number values (, + /// , or ). + /// + /// objects are converted to objects. + /// objects are converted to arrays. + /// objects are converted to objects. + /// objects are converted to arrays. + /// + /// A native .NET object representing this NSObject's value. + public object ToObject() + { + switch(this) + { + case NSArray: + { + var nsArray = (NSArray)this; + object[] array = new object[nsArray.Count]; + + for(int i = 0; i < nsArray.Count; i++) array[i] = nsArray[i].ToObject(); + + return array; + } + case NSDictionary: + { + Dictionary dictA = ((NSDictionary)this).GetDictionary(); + Dictionary dictB = new(dictA.Count); + + foreach(KeyValuePair kvp in dictA) dictB.Add(kvp.Key, kvp.Value.ToObject()); + + return dictB; + } + case NSSet: + { + List setA = ((NSSet)this).GetSet(); + List setB = new(); + + foreach(NSObject o in setA) setB.Add(o.ToObject()); + + return setB; + } + case NSNumber: + { + var num = (NSNumber)this; + + switch(num.GetNSNumberType()) + { + case NSNumber.INTEGER: + { + long longVal = num.ToLong(); + + if(longVal is > int.MaxValue or < int.MinValue) return longVal; + + return num.ToInt(); + } + case NSNumber.REAL: + return num.ToDouble(); + case NSNumber.BOOLEAN: + return num.ToBool(); + default: + return num.ToDouble(); + } + + break; + } + case NSString: + return ((NSString)this).Content; + case NSData: + return ((NSData)this).Bytes; + case NSDate: + return ((NSDate)this).Date; + case UID: + return ((UID)this).Bytes; + default: + return this; + } + } + + internal static bool ArrayEquals(byte[] arrayA, byte[] arrayB) + { + if(arrayA.Length != arrayB.Length) return false; + + for(int i = 0; i < arrayA.Length; i++) + if(arrayA[i] != arrayB[i]) return false; + + return true; + } + + internal static bool ArrayEquals(IList arrayA, IList arrayB) + { + if(arrayA.Count != arrayB.Count) return false; + + for(int i = 0; i < arrayA.Count; i++) + if(arrayA[i] != arrayB[i]) return false; + + return true; + } + + /// Determines if the specific NSObject is the same as the NSObject overriding this method. + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public abstract bool Equals(NSObject obj); } \ No newline at end of file diff --git a/plist-cil/NSSet.cs b/plist-cil/NSSet.cs index 8345adf..55fa17a 100644 --- a/plist-cil/NSSet.cs +++ b/plist-cil/NSSet.cs @@ -28,374 +28,359 @@ using System.Collections; using System.Collections.Generic; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// A set is an interface to an unordered collection of objects. +/// This implementation uses a as the underlying data structure. +/// +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class NSSet : NSObject, IEnumerable { - /// - /// A set is an interface to an unordered collection of objects. - /// This implementation uses a as the underlying data structure. - /// - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class NSSet : NSObject, IEnumerable + readonly bool ordered; + readonly List set; + + /// Creates an empty unordered set. + public NSSet() => set = []; + + /// Creates an empty set. + /// Should the set be ordered on operations? + public NSSet(bool ordered) { - readonly bool ordered; - readonly List set; + this.ordered = ordered; + set = []; + } - /// Creates an empty unordered set. - public NSSet() => set = new List(); + /// Creates a set and fill it with the given objects. + /// The objects to populate the set. + public NSSet(params NSObject[] objects) => set = [..objects]; - /// Creates an empty set. - /// Should the set be ordered on operations? - public NSSet(bool ordered) - { - this.ordered = ordered; - set = new List(); - } + /// Creates a set and fill it with the given objects. + /// The objects to populate the set. + /// Should the set be ordered on operations? + public NSSet(bool ordered, params NSObject[] objects) + { + this.ordered = ordered; + set = new List(objects); - /// Creates a set and fill it with the given objects. - /// The objects to populate the set. - public NSSet(params NSObject[] objects) => set = new List(objects); + if(ordered) set.Sort(); + } - /// Creates a set and fill it with the given objects. - /// The objects to populate the set. - /// Should the set be ordered on operations? - public NSSet(bool ordered, params NSObject[] objects) - { - this.ordered = ordered; - set = new List(objects); - - if(ordered) - set.Sort(); - } - - /// Gets the number of elements in the set. - /// The number of elements in the set. - public int Count - { - get - { - lock(set) - return set.Count; - } - } - - /// - /// Returns an enumerator object that lets you iterate over all elements of the set. This is the equivalent to - /// objectEnumerator in the Cocoa implementation of NSSet. - /// - /// The iterator for the set. - public IEnumerator GetEnumerator() - { - lock(set) - return set.GetEnumerator(); - } - - /// Adds an object to the set. - /// The object to add. - public void AddObject(NSObject obj) + /// Gets the number of elements in the set. + /// The number of elements in the set. + public int Count + { + get { lock(set) { - set.Add(obj); - - if(ordered) - set.Sort(); + return set.Count; } } + } - /// Removes an object from the set. - /// The object to remove. - public void RemoveObject(NSObject obj) + /// + /// Returns an enumerator object that lets you iterate over all elements of the set. This is the equivalent to + /// objectEnumerator in the Cocoa implementation of NSSet. + /// + /// The iterator for the set. + public IEnumerator GetEnumerator() + { + lock(set) { - lock(set) - { - set.Remove(obj); - - if(ordered) - set.Sort(); - } + return set.GetEnumerator(); } + } - /// Returns all objects contained in the set. - /// An array of all objects in the set. - public NSObject[] AllObjects() + /// Adds an object to the set. + /// The object to add. + public void AddObject(NSObject obj) + { + lock(set) { - lock(set) - return set.ToArray(); + set.Add(obj); + + if(ordered) set.Sort(); } + } - /// Returns one of the objects in the set, or null if the set contains no objects. - /// The first object in the set, or null if the set is empty. - public NSObject AnyObject() + /// Removes an object from the set. + /// The object to remove. + public void RemoveObject(NSObject obj) + { + lock(set) { - lock(set) - return set.Count == 0 ? null : set[0]; + set.Remove(obj); + + if(ordered) set.Sort(); } + } - /// Finds out whether a given object is contained in the set. - /// true, when the object was found, false otherwise. - /// The object to look for. - public bool ContainsObject(NSObject obj) => set.Contains(obj); - - /// - /// Determines whether the set contains an object equal to a given object and returns that object if it is - /// present. - /// - /// The object to look for. - /// The object if it is present, null otherwise. - public NSObject Member(NSObject obj) + /// Returns all objects contained in the set. + /// An array of all objects in the set. + public NSObject[] AllObjects() + { + lock(set) { - lock(set) - { - foreach(NSObject o in set) - if(o.Equals(obj)) - return o; - - return null; - } + return set.ToArray(); } + } - /// Finds out whether at least one object is present in both sets. - /// true if the intersection of both sets is empty, false otherwise. - /// The other set. - public bool IntersectsSet(NSSet otherSet) + /// Returns one of the objects in the set, or null if the set contains no objects. + /// The first object in the set, or null if the set is empty. + public NSObject AnyObject() + { + lock(set) { - lock(set) - { - foreach(NSObject o in set) - if(otherSet.ContainsObject(o)) - return true; - - return false; - } + return set.Count == 0 ? null : set[0]; } + } - /// Finds out if this set is a subset of the given set. - /// true if all elements in this set are also present in the other set, falseotherwise. - /// The other set. - public bool IsSubsetOfSet(NSSet otherSet) + /// Finds out whether a given object is contained in the set. + /// true, when the object was found, false otherwise. + /// The object to look for. + public bool ContainsObject(NSObject obj) => set.Contains(obj); + + /// + /// Determines whether the set contains an object equal to a given object and returns that object if it is + /// present. + /// + /// The object to look for. + /// The object if it is present, null otherwise. + public NSObject Member(NSObject obj) + { + lock(set) { - lock(set) - { - foreach(NSObject o in set) - if(!otherSet.ContainsObject(o)) - return false; - - return true; - } - } - - /// Gets the underlying data structure in which this NSSets stores its content. - /// A Set object. - internal List GetSet() => set; - - /// Serves as a hash function for a object. - /// - /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a - /// hash table. - /// - public override int GetHashCode() - { - int hash = 7; - hash = (29 * hash) + (set != null ? set.GetHashCode() : 0); - - return hash; - } - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(object obj) - { - if(obj == null) - return false; - - if(GetType() != obj.GetType()) - return false; - - var other = (NSSet)obj; - - return !(set != other.set && (set == null || !set.Equals(other.set))); - } - - /// - /// Returns the XML representation for this set. There is no official XML representation specified for sets. In - /// this implementation it is represented by an array. - /// - /// The XML StringBuilder - /// The indentation level - internal override void ToXml(StringBuilder xml, int level) - { - Indent(xml, level); - xml.Append(""); - xml.Append(NEWLINE); - - if(ordered) - set.Sort(); - foreach(NSObject o in set) - { - o.ToXml(xml, level + 1); - xml.Append(NEWLINE); - } + if(o.Equals(obj)) return o; - Indent(xml, level); - xml.Append(""); + return null; } + } - internal override void AssignIDs(BinaryPropertyListWriter outPlist) + /// Finds out whether at least one object is present in both sets. + /// true if the intersection of both sets is empty, false otherwise. + /// The other set. + public bool IntersectsSet(NSSet otherSet) + { + lock(set) { - base.AssignIDs(outPlist); + foreach(NSObject o in set) + if(otherSet.ContainsObject(o)) return true; - foreach(NSObject obj in set) - obj.AssignIDs(outPlist); + return false; } + } - internal override void ToBinary(BinaryPropertyListWriter outPlist) + /// Finds out if this set is a subset of the given set. + /// true if all elements in this set are also present in the other set, falseotherwise. + /// The other set. + public bool IsSubsetOfSet(NSSet otherSet) + { + lock(set) { - if(ordered) - { - set.Sort(); - outPlist.WriteIntHeader(0xB, set.Count); - } - else - outPlist.WriteIntHeader(0xC, set.Count); - - foreach(NSObject obj in set) - outPlist.WriteID(outPlist.GetID(obj)); - } - - /// - /// Returns the ASCII representation of this set. There is no official ASCII representation for sets. In this - /// implementation sets are represented as arrays. - /// - /// The ASCII file string builder - /// The indentation level - internal override void ToASCII(StringBuilder ascii, int level) - { - Indent(ascii, level); - - if(ordered) - set.Sort(); - - NSObject[] array = AllObjects(); - ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); - int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); - - for(int i = 0; i < array.Length; i++) - { - Type objClass = array[i].GetType(); - - if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || - objClass.Equals(typeof(NSData))) && - indexOfLastNewLine != ascii.Length) - { - ascii.Append(NEWLINE); - indexOfLastNewLine = ascii.Length; - array[i].ToASCII(ascii, level + 1); - } - else - { - if(i != 0) - ascii.Append(" "); - - array[i].ToASCII(ascii, 0); - } - - if(i != array.Length - 1) - ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN); - - if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) - continue; - - ascii.Append(NEWLINE); - indexOfLastNewLine = ascii.Length; - } - - ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); - } - - /// - /// Returns the ASCII representation of this set according to the GnuStep format. There is no official ASCII - /// representation for sets. In this implementation sets are represented as arrays. - /// - /// The ASCII file string builder - /// The indentation level - internal override void ToASCIIGnuStep(StringBuilder ascii, int level) - { - Indent(ascii, level); - - if(ordered) - set.Sort(); - - NSObject[] array = AllObjects(); - ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); - int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); - - for(int i = 0; i < array.Length; i++) - { - if(array[i] is NSDictionary or NSArray or NSData && - indexOfLastNewLine != ascii.Length) - { - ascii.Append(NEWLINE); - indexOfLastNewLine = ascii.Length; - array[i].ToASCIIGnuStep(ascii, level + 1); - } - else - { - if(i != 0) - ascii.Append(" "); - - array[i].ToASCIIGnuStep(ascii, 0); - } - - if(i != array.Length - 1) - ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN); - - if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) - continue; - - ascii.Append(NEWLINE); - indexOfLastNewLine = ascii.Length; - } - - ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); - } - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(NSObject obj) - { - if(obj is not NSSet nsSet) - return false; - - if(set.Count != nsSet.Count) - return false; - - foreach(NSObject objS in nsSet) - if(!set.Contains(objS)) - return false; + foreach(NSObject o in set) + if(!otherSet.ContainsObject(o)) return false; return true; } } + + /// Gets the underlying data structure in which this NSSets stores its content. + /// A Set object. + internal List GetSet() => set; + + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a + /// hash table. + /// + public override int GetHashCode() + { + int hash = 7; + hash = 29 * hash + (set != null ? set.GetHashCode() : 0); + + return hash; + } + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(object obj) + { + if(obj == null) return false; + + if(GetType() != obj.GetType()) return false; + + var other = (NSSet)obj; + + return !(set != other.set && (set == null || !set.Equals(other.set))); + } + + /// + /// Returns the XML representation for this set. There is no official XML representation specified for sets. In + /// this implementation it is represented by an array. + /// + /// The XML StringBuilder + /// The indentation level + internal override void ToXml(StringBuilder xml, int level) + { + Indent(xml, level); + xml.Append(""); + xml.Append(NEWLINE); + + if(ordered) set.Sort(); + + foreach(NSObject o in set) + { + o.ToXml(xml, level + 1); + xml.Append(NEWLINE); + } + + Indent(xml, level); + xml.Append(""); + } + + internal override void AssignIDs(BinaryPropertyListWriter outPlist) + { + base.AssignIDs(outPlist); + + foreach(NSObject obj in set) obj.AssignIDs(outPlist); + } + + internal override void ToBinary(BinaryPropertyListWriter outPlist) + { + if(ordered) + { + set.Sort(); + outPlist.WriteIntHeader(0xB, set.Count); + } + else + outPlist.WriteIntHeader(0xC, set.Count); + + foreach(NSObject obj in set) outPlist.WriteID(outPlist.GetID(obj)); + } + + /// + /// Returns the ASCII representation of this set. There is no official ASCII representation for sets. In this + /// implementation sets are represented as arrays. + /// + /// The ASCII file string builder + /// The indentation level + internal override void ToASCII(StringBuilder ascii, int level) + { + Indent(ascii, level); + + if(ordered) set.Sort(); + + NSObject[] array = AllObjects(); + ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); + int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); + + for(int i = 0; i < array.Length; i++) + { + Type objClass = array[i].GetType(); + + if((objClass.Equals(typeof(NSDictionary)) || + objClass.Equals(typeof(NSArray)) || + objClass.Equals(typeof(NSData))) && + indexOfLastNewLine != ascii.Length) + { + ascii.Append(NEWLINE); + indexOfLastNewLine = ascii.Length; + array[i].ToASCII(ascii, level + 1); + } + else + { + if(i != 0) ascii.Append(" "); + + array[i].ToASCII(ascii, 0); + } + + if(i != array.Length - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN); + + if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue; + + ascii.Append(NEWLINE); + indexOfLastNewLine = ascii.Length; + } + + ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); + } + + /// + /// Returns the ASCII representation of this set according to the GnuStep format. There is no official ASCII + /// representation for sets. In this implementation sets are represented as arrays. + /// + /// The ASCII file string builder + /// The indentation level + internal override void ToASCIIGnuStep(StringBuilder ascii, int level) + { + Indent(ascii, level); + + if(ordered) set.Sort(); + + NSObject[] array = AllObjects(); + ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); + int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); + + for(int i = 0; i < array.Length; i++) + { + if(array[i] is NSDictionary or NSArray or NSData && indexOfLastNewLine != ascii.Length) + { + ascii.Append(NEWLINE); + indexOfLastNewLine = ascii.Length; + array[i].ToASCIIGnuStep(ascii, level + 1); + } + else + { + if(i != 0) ascii.Append(" "); + + array[i].ToASCIIGnuStep(ascii, 0); + } + + if(i != array.Length - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN); + + if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue; + + ascii.Append(NEWLINE); + indexOfLastNewLine = ascii.Length; + } + + ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); + } + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(NSObject obj) + { + if(obj is not NSSet nsSet) return false; + + if(set.Count != nsSet.Count) return false; + + foreach(NSObject objS in nsSet) + if(!set.Contains(objS)) return false; + + return true; + } } \ No newline at end of file diff --git a/plist-cil/NSString.cs b/plist-cil/NSString.cs index 872033a..5d7fe75 100644 --- a/plist-cil/NSString.cs +++ b/plist-cil/NSString.cs @@ -27,237 +27,240 @@ using System; using System.Security; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// A NSString contains a string. +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class NSString : NSObject, IComparable { - /// A NSString contains a string. - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class NSString : NSObject, IComparable + static Encoding asciiEncoder, utf16beEncoder, utf8Encoder; + + /// Creates a NSString from its binary representation. + /// The binary representation. + /// The encoding of the binary representation, the name of a supported charset. + /// The encoding charset is invalid or not supported by the underlying platform. + public NSString(ReadOnlySpan bytes, string encoding) : this(bytes, Encoding.GetEncoding(encoding)) {} + + /// Creates a NSString from its binary representation. + /// The binary representation. + /// The encoding of the binary representation. + /// The encoding charset is invalid or not supported by the underlying platform. + public NSString(ReadOnlySpan bytes, Encoding encoding) { - static Encoding asciiEncoder, utf16beEncoder, utf8Encoder; - - /// Creates a NSString from its binary representation. - /// The binary representation. - /// The encoding of the binary representation, the name of a supported charset. - /// The encoding charset is invalid or not supported by the underlying platform. - public NSString(ReadOnlySpan bytes, string encoding) : this(bytes, Encoding.GetEncoding(encoding)) {} - - /// Creates a NSString from its binary representation. - /// The binary representation. - /// The encoding of the binary representation. - /// The encoding charset is invalid or not supported by the underlying platform. - public NSString(ReadOnlySpan bytes, Encoding encoding) - { - #if NATIVE_SPAN - Content = encoding.GetString(bytes); - #else - Content = encoding.GetString(bytes.ToArray()); - #endif - } - - /// Creates a NSString from a string. - /// The string that will be contained in the NSString. - public NSString(string text) => Content = text; - - /// Gets this strings content. - /// This NSString as .NET string object. - public string Content { get; set; } - - /// Compares the current to the specified object. - /// A 32-bit signed integer that indicates the lexical relationship between the two comparands. - /// Object to compare to the current . - public int CompareTo(object o) => o switch - { - NSString nsString => string.Compare(Content, nsString.Content, StringComparison.Ordinal), - string s => string.Compare(Content, s, StringComparison.Ordinal), - _ => -1 - }; - - /// Appends a string to this string. - /// The string to append. - public void Append(NSString s) => Append(s.Content); - - /// Appends a string to this string. - /// The string to append. - public void Append(string s) => Content += s; - - /// Prepends a string to this string. - /// The string to prepend. - public void Prepend(string s) => Content = s + Content; - - /// Prepends a string to this string. - /// The string to prepend. - public void Prepend(NSString s) => Prepend(s.Content); - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(object obj) => obj is NSString nsString && Content.Equals(nsString.Content); - - /// Serves as a hash function for a object. - /// - /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a - /// hash table. - /// - public override int GetHashCode() => Content.GetHashCode(); - - /// The textual representation of this NSString. - /// The NSString's contents. - public override string ToString() => Content; - - internal override void ToXml(StringBuilder xml, int level) - { - Indent(xml, level); - xml.Append(""); - - //Make sure that the string is encoded in UTF-8 for the XML output - lock(typeof(NSString)) - { - utf8Encoder ??= Encoding.GetEncoding("UTF-8"); - - try - { - byte[] bytes = utf8Encoder.GetBytes(Content); - Content = utf8Encoder.GetString(bytes); - } - catch(Exception ex) - { - throw new PropertyListException("Could not encode the NSString into UTF-8: " + ex.Message); - } - } - - //According to http://www.w3.org/TR/REC-xml/#syntax node values must not - //contain the characters < or &. Also the > character should be escaped. - xml.Append(SecurityElement.Escape(Content)); - - xml.Append(""); - } - - internal override void ToBinary(BinaryPropertyListWriter outPlist) - { - int kind; - byte[] byteBuf; - - lock(typeof(NSString)) - { - // Not much use, because some characters do not fallback to exception, even if not ASCII - asciiEncoder ??= Encoding.GetEncoding("ascii", EncoderFallback.ExceptionFallback, - DecoderFallback.ExceptionFallback); - - if(IsASCIIEncodable(Content)) - { - kind = 0x5; // standard ASCII - byteBuf = asciiEncoder.GetBytes(Content); - } - else - { - utf16beEncoder ??= Encoding.BigEndianUnicode; - - kind = 0x6; // UTF-16-BE - byteBuf = utf16beEncoder.GetBytes(Content); - } - } - - outPlist.WriteIntHeader(kind, Content.Length); - outPlist.Write(byteBuf); - } - - internal override void ToASCII(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append("\""); - - //According to https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html - //non-ASCII characters are not escaped but simply written into the - //file, thus actually violating the ASCII plain text format. - //We will escape the string anyway because current Xcode project files (ASCII property lists) also escape their strings. - ascii.Append(EscapeStringForASCII(Content)); - ascii.Append("\""); - } - - internal override void ToASCIIGnuStep(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append("\""); - ascii.Append(EscapeStringForASCII(Content)); - ascii.Append("\""); - } - - /// Escapes a string for use in ASCII property lists. - /// The unescaped string. - /// S. - internal static string EscapeStringForASCII(string s) - { - string outString = ""; - char[] cArray = s.ToCharArray(); - - foreach(char c in cArray) - if(c > 127) - { - //non-ASCII Unicode - outString += "\\U"; - string hex = $"{c:x}"; - - while(hex.Length < 4) - hex = "0" + hex; - - outString += hex; - } - else - outString += c switch - { - '\\' => "\\\\", - '\"' => "\\\"", - '\b' => "\\b", - '\n' => "\\n", - '\r' => "\\r", - '\t' => "\\t", - _ => c - }; - - return outString; - } - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(NSObject obj) - { - if(obj is not NSString nsString) - return false; - - return Content == nsString.Content; - } - - internal static bool IsASCIIEncodable(string text) - { - foreach(char c in text) - if(c > 0x7F) - return false; - - return true; - } - - public static explicit operator string(NSString value) => value.Content; - - public static explicit operator NSString(string value) => new(value); +#if NATIVE_SPAN + Content = encoding.GetString(bytes); +#else + Content = encoding.GetString(bytes.ToArray()); +#endif } + + /// Creates a NSString from a string. + /// The string that will be contained in the NSString. + public NSString(string text) => Content = text; + + /// Gets this strings content. + /// This NSString as .NET string object. + public string Content { get; set; } + + /// Compares the current to the specified object. + /// A 32-bit signed integer that indicates the lexical relationship between the two comparands. + /// Object to compare to the current . + public int CompareTo(object o) => o switch + { + NSString nsString => string.Compare(Content, + nsString.Content, + StringComparison.Ordinal), + string s => string.Compare(Content, s, StringComparison.Ordinal), + _ => -1 + }; + + /// Appends a string to this string. + /// The string to append. + public void Append(NSString s) => Append(s.Content); + + /// Appends a string to this string. + /// The string to append. + public void Append(string s) => Content += s; + + /// Prepends a string to this string. + /// The string to prepend. + public void Prepend(string s) => Content = s + Content; + + /// Prepends a string to this string. + /// The string to prepend. + public void Prepend(NSString s) => Prepend(s.Content); + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(object obj) => obj is NSString nsString && Content.Equals(nsString.Content); + + /// Serves as a hash function for a object. + /// + /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a + /// hash table. + /// + public override int GetHashCode() => Content.GetHashCode(); + + /// The textual representation of this NSString. + /// The NSString's contents. + public override string ToString() => Content; + + internal override void ToXml(StringBuilder xml, int level) + { + Indent(xml, level); + xml.Append(""); + + //Make sure that the string is encoded in UTF-8 for the XML output + lock(typeof(NSString)) + { + utf8Encoder ??= Encoding.GetEncoding("UTF-8"); + + try + { + byte[] bytes = utf8Encoder.GetBytes(Content); + Content = utf8Encoder.GetString(bytes); + } + catch(Exception ex) + { + throw new PropertyListException("Could not encode the NSString into UTF-8: " + ex.Message); + } + } + + //According to http://www.w3.org/TR/REC-xml/#syntax node values must not + //contain the characters < or &. Also the > character should be escaped. + xml.Append(SecurityElement.Escape(Content)); + + xml.Append(""); + } + + internal override void ToBinary(BinaryPropertyListWriter outPlist) + { + int kind; + byte[] byteBuf; + + lock(typeof(NSString)) + { + // Not much use, because some characters do not fallback to exception, even if not ASCII + asciiEncoder ??= Encoding.GetEncoding("ascii", + EncoderFallback.ExceptionFallback, + DecoderFallback.ExceptionFallback); + + if(IsASCIIEncodable(Content)) + { + kind = 0x5; // standard ASCII + byteBuf = asciiEncoder.GetBytes(Content); + } + else + { + utf16beEncoder ??= Encoding.BigEndianUnicode; + + kind = 0x6; // UTF-16-BE + byteBuf = utf16beEncoder.GetBytes(Content); + } + } + + outPlist.WriteIntHeader(kind, Content.Length); + outPlist.Write(byteBuf); + } + + internal override void ToASCII(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append("\""); + + //According to https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html + //non-ASCII characters are not escaped but simply written into the + //file, thus actually violating the ASCII plain text format. + //We will escape the string anyway because current Xcode project files (ASCII property lists) also escape their strings. + ascii.Append(EscapeStringForASCII(Content)); + ascii.Append("\""); + } + + internal override void ToASCIIGnuStep(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append("\""); + ascii.Append(EscapeStringForASCII(Content)); + ascii.Append("\""); + } + + /// Escapes a string for use in ASCII property lists. + /// The unescaped string. + /// S. + internal static string EscapeStringForASCII(string s) + { + string outString = ""; + char[] cArray = s.ToCharArray(); + + foreach(char c in cArray) + { + if(c > 127) + { + //non-ASCII Unicode + outString += "\\U"; + string hex = $"{c:x}"; + + while(hex.Length < 4) hex = "0" + hex; + + outString += hex; + } + else + { + outString += c switch + { + '\\' => "\\\\", + '\"' => "\\\"", + '\b' => "\\b", + '\n' => "\\n", + '\r' => "\\r", + '\t' => "\\t", + _ => c + }; + } + } + + return outString; + } + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(NSObject obj) + { + if(obj is not NSString nsString) return false; + + return Content == nsString.Content; + } + + internal static bool IsASCIIEncodable(string text) + { + foreach(char c in text) + if(c > 0x7F) return false; + + return true; + } + + public static explicit operator string(NSString value) => value.Content; + + public static explicit operator NSString(string value) => new(value); } \ No newline at end of file diff --git a/plist-cil/PropertyListException.cs b/plist-cil/PropertyListException.cs index 854c278..8cc8e2a 100644 --- a/plist-cil/PropertyListException.cs +++ b/plist-cil/PropertyListException.cs @@ -27,27 +27,26 @@ using System; using System.Runtime.Serialization; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// The exception that is thrown when an property list file could not be processed correctly. +[Serializable] +public class PropertyListException : Exception { - /// The exception that is thrown when an property list file could not be processed correctly. - [Serializable] - public class PropertyListException : Exception - { - /// Initializes a new instance of the class. - public PropertyListException() {} + /// Initializes a new instance of the class. + public PropertyListException() {} - /// Initializes a new instance of the class. - /// The error message that explains the reason for the exception. - public PropertyListException(string message) : base(message) {} + /// Initializes a new instance of the class. + /// The error message that explains the reason for the exception. + public PropertyListException(string message) : base(message) {} - /// Initializes a new instance of the class. - /// The error message that explains the reason for the exception. - /// - /// The exception that is the cause of the current exception, or if no inner - /// exception is specified. - /// - public PropertyListException(string message, Exception inner) : base(message, inner) {} + /// Initializes a new instance of the class. + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or if no inner + /// exception is specified. + /// + 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) {} } \ No newline at end of file diff --git a/plist-cil/PropertyListFormatException.cs b/plist-cil/PropertyListFormatException.cs index dd6083a..c86de12 100644 --- a/plist-cil/PropertyListFormatException.cs +++ b/plist-cil/PropertyListFormatException.cs @@ -23,18 +23,17 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// A PropertyListFormatException is thrown by the various property list format parsers when an error in the +/// format of the given property list is encountered. +/// +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class PropertyListFormatException : PropertyListException { - /// - /// A PropertyListFormatException is thrown by the various property list format parsers when an error in the - /// format of the given property list is encountered. - /// - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class PropertyListFormatException : PropertyListException - { - /// Creates a new exception with the given message. - /// A message containing information about the nature of the exception. - public PropertyListFormatException(string message) : base(message) {} - } + /// Creates a new exception with the given message. + /// A message containing information about the nature of the exception. + public PropertyListFormatException(string message) : base(message) {} } \ No newline at end of file diff --git a/plist-cil/PropertyListParser.cs b/plist-cil/PropertyListParser.cs index 688de80..b983cbf 100644 --- a/plist-cil/PropertyListParser.cs +++ b/plist-cil/PropertyListParser.cs @@ -27,379 +27,367 @@ using System; using System.IO; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// 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. +/// +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public static class PropertyListParser { - /// - /// 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. - /// - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public static class PropertyListParser + const int TYPE_XML = 0; + const int TYPE_BINARY = 1; + const int TYPE_ASCII = 2; + const int TYPE_ERROR_BLANK = 10; + const int TYPE_ERROR_UNKNOWN = 11; + + /// Determines the type of a property list by means of the first bytes of its data + /// The type of the property list + /// The very first bytes of data of the property list (minus any whitespace) as a string + static int DetermineTypeExact(ReadOnlySpan dataBeginning) { - const int TYPE_XML = 0; - const int TYPE_BINARY = 1; - const int TYPE_ASCII = 2; - const int TYPE_ERROR_BLANK = 10; - const int TYPE_ERROR_UNKNOWN = 11; + if(dataBeginning.Length == 0) return TYPE_ERROR_BLANK; - /// Determines the type of a property list by means of the first bytes of its data - /// The type of the property list - /// The very first bytes of data of the property list (minus any whitespace) as a string - static int DetermineTypeExact(ReadOnlySpan dataBeginning) + if(dataBeginning[0] == '(' || dataBeginning[0] == '{' || dataBeginning[0] == '/') return TYPE_ASCII; + + if(dataBeginning[0] == '<') return TYPE_XML; + + if(dataBeginning.Length >= 6 && + dataBeginning[0] == 'b' && + dataBeginning[1] == 'p' && + dataBeginning[2] == 'l' && + dataBeginning[3] == 'i' && + dataBeginning[4] == 's' && + dataBeginning[5] == 't') + return TYPE_BINARY; + + return TYPE_ERROR_UNKNOWN; + } + + /// Determines the type of a property list by means of the first bytes of its data + /// The very first bytes of data of the property list (minus any whitespace) + /// The type of the property list + static int DetermineType(ReadOnlySpan bytes) + { + if(bytes.Length == 0) return TYPE_ERROR_BLANK; + + //Skip any possible whitespace at the beginning of the file + int offset = 0; + + if(bytes.Length >= 3 && (bytes[0] & 0xFF) == 0xEF && (bytes[1] & 0xFF) == 0xBB && (bytes[2] & 0xFF) == 0xBF) + offset += 3; + + while(offset < bytes.Length && + (bytes[offset] == ' ' || + bytes[offset] == '\t' || + bytes[offset] == '\r' || + bytes[offset] == '\n' || + bytes[offset] == '\f')) + offset++; + + ReadOnlySpan header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset)); + + return DetermineTypeExact(header); + } + + /// Determines the type of a property list by means of the first bytes of its data + /// The type of the property list + /// + /// An input stream pointing to the beginning of the property list data. The stream will be reset to the + /// beginning of the property list data after the type has been determined. + /// + static int DetermineType(Stream fs, long offset = 0) + { + if(fs.Length == 0) return TYPE_ERROR_BLANK; + + long index = offset; + long readLimit = index + 1024; + long mark = readLimit; + fs.Seek(offset, SeekOrigin.Current); + int b; + bool bom = false; + + //Skip any possible whitespace at the beginning of the file + do { - if(dataBeginning.Length == 0) - return TYPE_ERROR_BLANK; - - if(dataBeginning[0] == '(' || - dataBeginning[0] == '{' || - dataBeginning[0] == '/') - return TYPE_ASCII; - - if(dataBeginning[0] == '<') - return TYPE_XML; - - if(dataBeginning.Length >= 6 && - dataBeginning[0] == 'b' && - dataBeginning[1] == 'p' && - dataBeginning[2] == 'l' && - dataBeginning[3] == 'i' && - dataBeginning[4] == 's' && - dataBeginning[5] == 't') - return TYPE_BINARY; - - return TYPE_ERROR_UNKNOWN; - } - - /// Determines the type of a property list by means of the first bytes of its data - /// The very first bytes of data of the property list (minus any whitespace) - /// The type of the property list - static int DetermineType(ReadOnlySpan bytes) - { - if(bytes.Length == 0) - return TYPE_ERROR_BLANK; - - //Skip any possible whitespace at the beginning of the file - int offset = 0; - - if(bytes.Length >= 3 && - (bytes[0] & 0xFF) == 0xEF && - (bytes[1] & 0xFF) == 0xBB && - (bytes[2] & 0xFF) == 0xBF) - offset += 3; - - while(offset < bytes.Length && - (bytes[offset] == ' ' || bytes[offset] == '\t' || bytes[offset] == '\r' || bytes[offset] == '\n' || - bytes[offset] == '\f')) - offset++; - - ReadOnlySpan header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset)); - - return DetermineTypeExact(header); - } - - /// Determines the type of a property list by means of the first bytes of its data - /// The type of the property list - /// - /// An input stream pointing to the beginning of the property list data. The stream will be reset to the - /// beginning of the property list data after the type has been determined. - /// - static int DetermineType(Stream fs, long offset = 0) - { - if(fs.Length == 0) - return TYPE_ERROR_BLANK; - - long index = offset; - long readLimit = index + 1024; - long mark = readLimit; - fs.Seek(offset, SeekOrigin.Current); - int b; - bool bom = false; - - //Skip any possible whitespace at the beginning of the file - do + if(++index > readLimit) { - if(++index > readLimit) - { - fs.Seek(mark, SeekOrigin.Begin); + fs.Seek(mark, SeekOrigin.Begin); - return DetermineType(fs, readLimit); - } - - b = fs.ReadByte(); - - //Check if we are reading the Unicode byte order mark (BOM) and skip it - bom = index < 3 && ((index == 0 && b == 0xEF) || - (bom && ((index == 1 && b == 0xBB) || (index == 2 && b == 0xBF)))); - } while(b != -1 && - (b is ' ' or '\t' or '\r' or '\n' or '\f' || bom)); - - if(b == -1) - return TYPE_ERROR_BLANK; - - byte[] magicBytes = new byte[8]; - magicBytes[0] = (byte)b; - int read = fs.Read(magicBytes, 1, 7); - - int type = DetermineTypeExact(magicBytes.AsSpan(0, read)); - fs.Seek(mark, SeekOrigin.Begin); - - return type; - } - - /// Set up preprocessing functions for plist values. - /// A function that preprocesses the passed string and returns the adjusted value. - /// The type of value preprocessor to use. - public static void SetValuePreprocessor(Func preprocessor, ValuePreprocessor.Type type) => - ValuePreprocessor.Set(preprocessor, type); - - /// Reads all bytes from an Stream and stores them in an array, up to a maximum count. - /// The Stream pointing to the data that should be stored in the array. - internal static byte[] ReadAll(Stream fs) - { - using var outputStream = new MemoryStream(); - - fs.CopyTo(outputStream); - - return outputStream.ToArray(); - } - - /// Parses a property list from a file. - /// Path to the property list file. - /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(string filePath) => Parse(new FileInfo(filePath)); - - /// Parses a property list from a file. - /// The property list file. - /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(FileInfo f) - { - using FileStream fis = f.OpenRead(); - - return Parse(fis); - } - - /// Parses a property list from a byte array. - /// The property list data as a byte array. - /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(byte[] bytes) - { - switch(DetermineType(bytes)) - { - case TYPE_BINARY: return BinaryPropertyListParser.Parse(bytes); - case TYPE_XML: return XmlPropertyListParser.Parse(bytes); - case TYPE_ASCII: return ASCIIPropertyListParser.Parse(bytes); - default: - throw new - PropertyListFormatException("The given data is not a property list of a supported format."); + return DetermineType(fs, readLimit); } - } - /// Parses a property list from a byte array. - /// The property list data as a byte array. - /// The length of the property list. - /// The offset at which to start reading the property list. - /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(byte[] bytes, int offset, int length) => Parse(bytes.AsSpan(offset, length)); + b = fs.ReadByte(); - /// Parses a property list from a byte span. - /// The property list data as a byte array. - /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(ReadOnlySpan bytes) + //Check if we are reading the Unicode byte order mark (BOM) and skip it + bom = index < 3 && (index == 0 && b == 0xEF || bom && (index == 1 && b == 0xBB || index == 2 && b == 0xBF)); + } while(b != -1 && (b is ' ' or '\t' or '\r' or '\n' or '\f' || bom)); + + if(b == -1) return TYPE_ERROR_BLANK; + + byte[] magicBytes = new byte[8]; + magicBytes[0] = (byte)b; + int read = fs.Read(magicBytes, 1, 7); + + int type = DetermineTypeExact(magicBytes.AsSpan(0, read)); + fs.Seek(mark, SeekOrigin.Begin); + + return type; + } + + /// Set up preprocessing functions for plist values. + /// A function that preprocesses the passed string and returns the adjusted value. + /// The type of value preprocessor to use. + public static void SetValuePreprocessor(Func preprocessor, ValuePreprocessor.Type type) => + ValuePreprocessor.Set(preprocessor, type); + + /// Reads all bytes from an Stream and stores them in an array, up to a maximum count. + /// The Stream pointing to the data that should be stored in the array. + internal static byte[] ReadAll(Stream fs) + { + using var outputStream = new MemoryStream(); + + fs.CopyTo(outputStream); + + return outputStream.ToArray(); + } + + /// Parses a property list from a file. + /// Path to the property list file. + /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(string filePath) => Parse(new FileInfo(filePath)); + + /// Parses a property list from a file. + /// The property list file. + /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(FileInfo f) + { + using FileStream fis = f.OpenRead(); + + return Parse(fis); + } + + /// Parses a property list from a byte array. + /// The property list data as a byte array. + /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(byte[] bytes) + { + switch(DetermineType(bytes)) { - switch(DetermineType(bytes)) - { - case TYPE_BINARY: return BinaryPropertyListParser.Parse(bytes); - case TYPE_XML: return XmlPropertyListParser.Parse(bytes.ToArray()); - case TYPE_ASCII: return ASCIIPropertyListParser.Parse(bytes); - default: - throw new - PropertyListFormatException("The given data is not a property list of a supported format."); - } + case TYPE_BINARY: + return BinaryPropertyListParser.Parse(bytes); + case TYPE_XML: + return XmlPropertyListParser.Parse(bytes); + case TYPE_ASCII: + return ASCIIPropertyListParser.Parse(bytes); + default: + throw new PropertyListFormatException("The given data is not a property list of a supported format."); } + } - /// Parses a property list from an Stream. - /// The Stream delivering the property list data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(Stream fs) => Parse(ReadAll(fs)); + /// Parses a property list from a byte array. + /// The property list data as a byte array. + /// The length of the property list. + /// The offset at which to start reading the property list. + /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(byte[] bytes, int offset, int length) => Parse(bytes.AsSpan(offset, length)); - /// Saves a property list with the given object as root into a XML file. - /// The root object. - /// The output file. - /// When an error occurs during the writing process. - public static void SaveAsXml(NSObject root, FileInfo outFile) + /// Parses a property list from a byte span. + /// The property list data as a byte array. + /// The root object in the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(ReadOnlySpan bytes) + { + switch(DetermineType(bytes)) { - string parent = outFile.DirectoryName; - - if(!Directory.Exists(parent)) - Directory.CreateDirectory(parent); - - // 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. - using Stream fous = outFile.Open(FileMode.Create, FileAccess.ReadWrite); - - SaveAsXml(root, fous); + case TYPE_BINARY: + return BinaryPropertyListParser.Parse(bytes); + case TYPE_XML: + return XmlPropertyListParser.Parse(bytes.ToArray()); + case TYPE_ASCII: + return ASCIIPropertyListParser.Parse(bytes); + default: + throw new PropertyListFormatException("The given data is not a property list of a supported format."); } + } - /// Saves a property list with the given object as root in XML format into an output stream. - /// The root object. - /// The output stream. - /// When an error occurs during the writing process. - public static void SaveAsXml(NSObject root, Stream outStream) + /// Parses a property list from an Stream. + /// The Stream delivering the property list data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(Stream fs) => Parse(ReadAll(fs)); + + /// Saves a property list with the given object as root into a XML file. + /// The root object. + /// The output file. + /// When an error occurs during the writing process. + public static void SaveAsXml(NSObject root, FileInfo outFile) + { + string parent = outFile.DirectoryName; + + if(!Directory.Exists(parent)) Directory.CreateDirectory(parent); + + // 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. + using Stream fous = outFile.Open(FileMode.Create, FileAccess.ReadWrite); + + SaveAsXml(root, fous); + } + + /// Saves a property list with the given object as root in XML format into an output stream. + /// The root object. + /// The output stream. + /// When an error occurs during the writing process. + public static void SaveAsXml(NSObject root, Stream outStream) + { + using var w = new StreamWriter(outStream, Encoding.UTF8, 1024, true); + + w.Write(root.ToXmlPropertyList()); + } + + /// Converts a given property list file into the OS X and iOS XML format. + /// The source file. + /// The target file. + public static void ConvertToXml(FileInfo inFile, FileInfo outFile) + { + NSObject root = Parse(inFile); + SaveAsXml(root, outFile); + } + + /// Saves a property list with the given object as root into a binary file. + /// The root object. + /// The output file. + /// When an error occurs during the writing process. + public static void SaveAsBinary(NSObject root, FileInfo outFile) + { + string parent = outFile.DirectoryName; + + if(!Directory.Exists(parent)) Directory.CreateDirectory(parent); + + BinaryPropertyListWriter.Write(outFile, root); + } + + /// Saves a property list with the given object as root in binary format into an output stream. + /// The root object. + /// The output stream. + /// When an error occurs during the writing process. + public static void SaveAsBinary(NSObject root, Stream outStream) => BinaryPropertyListWriter.Write(outStream, root); + + /// Converts a given property list file into the OS X and iOS binary format. + /// The source file. + /// The target file. + public static void ConvertToBinary(FileInfo inFile, FileInfo outFile) + { + NSObject root = Parse(inFile); + SaveAsBinary(root, outFile); + } + + /// Saves a property list with the given object as root into a ASCII file. + /// The root object. + /// The output file. + /// When an error occurs during the writing process. + public static void SaveAsASCII(NSDictionary root, FileInfo outFile) + { + string parent = outFile.DirectoryName; + + if(!Directory.Exists(parent)) Directory.CreateDirectory(parent); + + using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); + + using var w = new StreamWriter(fous, Encoding.ASCII); + + w.Write(root.ToASCIIPropertyList()); + } + + /// Saves a property list with the given object as root into a ASCII file. + /// The root object. + /// The output file. + /// When an error occurs during the writing process. + public static void SaveAsASCII(NSArray root, FileInfo outFile) + { + string parent = outFile.DirectoryName; + + if(!Directory.Exists(parent)) Directory.CreateDirectory(parent); + + using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); + + using var w = new StreamWriter(fous, Encoding.ASCII); + + w.Write(root.ToASCIIPropertyList()); + } + + /// Converts a given property list file into ASCII format. + /// The source file. + /// The target file. + public static void ConvertToASCII(FileInfo inFile, FileInfo outFile) + { + NSObject root = Parse(inFile); + + if(root is NSDictionary dictionary) + SaveAsASCII(dictionary, outFile); + else if(root is NSArray array) + SaveAsASCII(array, outFile); + else { - using var w = new StreamWriter(outStream, Encoding.UTF8, 1024, true); - - w.Write(root.ToXmlPropertyList()); + throw new PropertyListFormatException("The root of the given input property list " + + "is neither a Dictionary nor an Array!"); } + } - /// Converts a given property list file into the OS X and iOS XML format. - /// The source file. - /// The target file. - public static void ConvertToXml(FileInfo inFile, FileInfo outFile) + /// Saves a property list with the given object as root into a GnuStep ASCII file. + /// The root object. + /// The output file. + /// When an error occurs during the writing process. + public static void SaveAsGnuStepASCII(NSDictionary root, FileInfo outFile) + { + string parent = outFile.DirectoryName; + + if(!Directory.Exists(parent)) Directory.CreateDirectory(parent); + + using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); + + using var w = new StreamWriter(fous, Encoding.ASCII); + + w.Write(root.ToGnuStepASCIIPropertyList()); + } + + /// Saves a property list with the given object as root into a GnuStep ASCII file. + /// The root object. + /// The output file. + /// When an error occurs during the writing process. + public static void SaveAsGnuStepASCII(NSArray root, FileInfo outFile) + { + string parent = outFile.DirectoryName; + + if(!Directory.Exists(parent)) Directory.CreateDirectory(parent); + + using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); + + using var w = new StreamWriter(fous, Encoding.ASCII); + + w.Write(root.ToGnuStepASCIIPropertyList()); + } + + /// Converts a given property list file into GnuStep ASCII format. + /// The source file. + /// The target file. + public static void ConvertToGnuStepASCII(FileInfo inFile, FileInfo outFile) + { + NSObject root = Parse(inFile); + + switch(root) { - NSObject root = Parse(inFile); - SaveAsXml(root, outFile); - } + case NSDictionary dictionary: + SaveAsGnuStepASCII(dictionary, outFile); - /// Saves a property list with the given object as root into a binary file. - /// The root object. - /// The output file. - /// When an error occurs during the writing process. - public static void SaveAsBinary(NSObject root, FileInfo outFile) - { - string parent = outFile.DirectoryName; + break; + case NSArray array: + SaveAsGnuStepASCII(array, outFile); - if(!Directory.Exists(parent)) - Directory.CreateDirectory(parent); - - BinaryPropertyListWriter.Write(outFile, root); - } - - /// Saves a property list with the given object as root in binary format into an output stream. - /// The root object. - /// The output stream. - /// When an error occurs during the writing process. - public static void SaveAsBinary(NSObject root, Stream outStream) => - BinaryPropertyListWriter.Write(outStream, root); - - /// Converts a given property list file into the OS X and iOS binary format. - /// The source file. - /// The target file. - public static void ConvertToBinary(FileInfo inFile, FileInfo outFile) - { - NSObject root = Parse(inFile); - SaveAsBinary(root, outFile); - } - - /// Saves a property list with the given object as root into a ASCII file. - /// The root object. - /// The output file. - /// When an error occurs during the writing process. - public static void SaveAsASCII(NSDictionary root, FileInfo outFile) - { - string parent = outFile.DirectoryName; - - if(!Directory.Exists(parent)) - Directory.CreateDirectory(parent); - - using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); - - using var w = new StreamWriter(fous, Encoding.ASCII); - - w.Write(root.ToASCIIPropertyList()); - } - - /// Saves a property list with the given object as root into a ASCII file. - /// The root object. - /// The output file. - /// When an error occurs during the writing process. - public static void SaveAsASCII(NSArray root, FileInfo outFile) - { - string parent = outFile.DirectoryName; - - if(!Directory.Exists(parent)) - Directory.CreateDirectory(parent); - - using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); - - using var w = new StreamWriter(fous, Encoding.ASCII); - - w.Write(root.ToASCIIPropertyList()); - } - - /// Converts a given property list file into ASCII format. - /// The source file. - /// The target file. - public static void ConvertToASCII(FileInfo inFile, FileInfo outFile) - { - NSObject root = Parse(inFile); - - if(root is NSDictionary dictionary) - SaveAsASCII(dictionary, outFile); - else if(root is NSArray array) - SaveAsASCII(array, outFile); - else + break; + default: throw new PropertyListFormatException("The root of the given input property list " + "is neither a Dictionary nor an Array!"); } - - /// Saves a property list with the given object as root into a GnuStep ASCII file. - /// The root object. - /// The output file. - /// When an error occurs during the writing process. - public static void SaveAsGnuStepASCII(NSDictionary root, FileInfo outFile) - { - string parent = outFile.DirectoryName; - - if(!Directory.Exists(parent)) - Directory.CreateDirectory(parent); - - using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); - - using var w = new StreamWriter(fous, Encoding.ASCII); - - w.Write(root.ToGnuStepASCIIPropertyList()); - } - - /// Saves a property list with the given object as root into a GnuStep ASCII file. - /// The root object. - /// The output file. - /// When an error occurs during the writing process. - public static void SaveAsGnuStepASCII(NSArray root, FileInfo outFile) - { - string parent = outFile.DirectoryName; - - if(!Directory.Exists(parent)) - Directory.CreateDirectory(parent); - - using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); - - using var w = new StreamWriter(fous, Encoding.ASCII); - - w.Write(root.ToGnuStepASCIIPropertyList()); - } - - /// Converts a given property list file into GnuStep ASCII format. - /// The source file. - /// The target file. - public static void ConvertToGnuStepASCII(FileInfo inFile, FileInfo outFile) - { - NSObject root = Parse(inFile); - - switch(root) - { - case NSDictionary dictionary: - SaveAsGnuStepASCII(dictionary, outFile); - - break; - case NSArray array: - SaveAsGnuStepASCII(array, outFile); - - break; - default: - throw new PropertyListFormatException("The root of the given input property list " + - "is neither a Dictionary nor an Array!"); - } - } } } \ No newline at end of file diff --git a/plist-cil/UID.cs b/plist-cil/UID.cs index dcec001..6163b6a 100644 --- a/plist-cil/UID.cs +++ b/plist-cil/UID.cs @@ -27,187 +27,182 @@ using System; using System.Buffers.Binary; using System.Text; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// An UID. Only found in binary property lists that are keyed archives. +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public class UID : NSObject { - /// An UID. Only found in binary property lists that are keyed archives. - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public class UID : NSObject + readonly ulong value; + + /// Initializes a new instance of the class. + /// Bytes. + public UID(ReadOnlySpan bytes) { - readonly ulong value; + if(bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8) + throw new ArgumentException("Type argument is not valid."); - /// Initializes a new instance of the class. - /// Bytes. - public UID(ReadOnlySpan bytes) - { - if(bytes.Length != 1 && - bytes.Length != 2 && - bytes.Length != 4 && - bytes.Length != 8) - throw new ArgumentException("Type argument is not valid."); - - value = (ulong)BinaryPropertyListParser.ParseLong(bytes); - } - - /// - /// Initializes a new instance of the class using an unsigned 8-bit - /// number. - /// - /// Unsigned 8-bit number. - public UID(byte number) => value = number; - - /// - /// Initializes a new instance of the class using an unsigned 16-bit - /// number. - /// - /// Name. - /// Unsigned 16-bit number. - public UID(ushort number) => value = number; - - /// - /// Initializes a new instance of the class using an unsigned 32-bit - /// number. - /// - /// Unsigned 32-bit number. - public UID(uint number) => value = number; - - /// - /// Initializes a new instance of the class using an unsigned 64-bit - /// number. - /// - /// Unsigned 64-bit number. - public UID(ulong number) => value = number; - - /// Gets the bytes. - /// The bytes. - public byte[] Bytes - { - get - { - byte[] bytes = new byte[ByteCount]; - GetBytes(bytes); - - return bytes; - } - } - - /// Gets the number of bytes required to represent this . - public int ByteCount => value switch - { - <= byte.MaxValue => 1, - <= ushort.MaxValue => 2, - <= uint.MaxValue => 4, - _ => 8 - }; - - /// Writes the bytes required to represent this to a byte span. - /// The byte span to which to write the byte representation of this UID. - public void GetBytes(Span bytes) - { - switch(ByteCount) - { - case 1: - bytes[0] = (byte)value; - - break; - - case 2: - BinaryPrimitives.WriteUInt16BigEndian(bytes, (ushort)value); - - break; - - case 4: - BinaryPrimitives.WriteUInt32BigEndian(bytes, (uint)value); - - break; - - case 8: - BinaryPrimitives.WriteUInt64BigEndian(bytes, value); - - break; - - default: throw new InvalidOperationException(); - } - } - - /// - /// UIDs are represented as dictionaries in XML property lists, where the key is always CF$UID and the - /// value is the integer representation of the UID. - /// - /// The xml StringBuilder - /// The indentation level - internal override void ToXml(StringBuilder xml, int level) - { - Indent(xml, level); - xml.Append(""); - xml.AppendLine(); - - Indent(xml, level + 1); - xml.Append("CF$UID"); - xml.AppendLine(); - - Indent(xml, level + 1); - xml.Append($"{value}"); - xml.AppendLine(); - - Indent(xml, level); - xml.Append(""); - } - - internal override void ToBinary(BinaryPropertyListWriter outPlist) - { - outPlist.Write(0x80 + ByteCount - 1); - Span bytes = stackalloc byte[ByteCount]; - GetBytes(bytes); - outPlist.Write(bytes); - } - - internal override void ToASCII(StringBuilder ascii, int level) - { - Indent(ascii, level); - ascii.Append("\""); - Span bytes = stackalloc byte[ByteCount]; - GetBytes(bytes); - - foreach(byte b in bytes) - ascii.Append($"{b:x2}"); - - ascii.Append("\""); - } - - internal override void ToASCIIGnuStep(StringBuilder ascii, int level) => ToASCII(ascii, level); - - /// - /// Determines whether the specified is equal to the current - /// . - /// - /// - /// The to compare with the current - /// . - /// - /// - /// true if the specified is equal to the current - /// ; otherwise, false. - /// - public override bool Equals(NSObject obj) => Equals((object)obj); - - /// - public override bool Equals(object obj) - { - if(obj is not UID uid) - return false; - - return uid.value == value; - } - - /// - public override int GetHashCode() => value.GetHashCode(); - - /// - public override string ToString() => $"{value} (UID)"; - - /// Gets a which represents this . - /// A which represents this . - public ulong ToUInt64() => value; + value = (ulong)BinaryPropertyListParser.ParseLong(bytes); } + + /// + /// Initializes a new instance of the class using an unsigned 8-bit + /// number. + /// + /// Unsigned 8-bit number. + public UID(byte number) => value = number; + + /// + /// Initializes a new instance of the class using an unsigned 16-bit + /// number. + /// + /// Name. + /// Unsigned 16-bit number. + public UID(ushort number) => value = number; + + /// + /// Initializes a new instance of the class using an unsigned 32-bit + /// number. + /// + /// Unsigned 32-bit number. + public UID(uint number) => value = number; + + /// + /// Initializes a new instance of the class using an unsigned 64-bit + /// number. + /// + /// Unsigned 64-bit number. + public UID(ulong number) => value = number; + + /// Gets the bytes. + /// The bytes. + public byte[] Bytes + { + get + { + byte[] bytes = new byte[ByteCount]; + GetBytes(bytes); + + return bytes; + } + } + + /// Gets the number of bytes required to represent this . + public int ByteCount => value switch + { + <= byte.MaxValue => 1, + <= ushort.MaxValue => 2, + <= uint.MaxValue => 4, + _ => 8 + }; + + /// Writes the bytes required to represent this to a byte span. + /// The byte span to which to write the byte representation of this UID. + public void GetBytes(Span bytes) + { + switch(ByteCount) + { + case 1: + bytes[0] = (byte)value; + + break; + + case 2: + BinaryPrimitives.WriteUInt16BigEndian(bytes, (ushort)value); + + break; + + case 4: + BinaryPrimitives.WriteUInt32BigEndian(bytes, (uint)value); + + break; + + case 8: + BinaryPrimitives.WriteUInt64BigEndian(bytes, value); + + break; + + default: + throw new InvalidOperationException(); + } + } + + /// + /// UIDs are represented as dictionaries in XML property lists, where the key is always CF$UID and the + /// value is the integer representation of the UID. + /// + /// The xml StringBuilder + /// The indentation level + internal override void ToXml(StringBuilder xml, int level) + { + Indent(xml, level); + xml.Append(""); + xml.AppendLine(); + + Indent(xml, level + 1); + xml.Append("CF$UID"); + xml.AppendLine(); + + Indent(xml, level + 1); + xml.Append($"{value}"); + xml.AppendLine(); + + Indent(xml, level); + xml.Append(""); + } + + internal override void ToBinary(BinaryPropertyListWriter outPlist) + { + outPlist.Write(0x80 + ByteCount - 1); + Span bytes = stackalloc byte[ByteCount]; + GetBytes(bytes); + outPlist.Write(bytes); + } + + internal override void ToASCII(StringBuilder ascii, int level) + { + Indent(ascii, level); + ascii.Append("\""); + Span bytes = stackalloc byte[ByteCount]; + GetBytes(bytes); + + foreach(byte b in bytes) ascii.Append($"{b:x2}"); + + ascii.Append("\""); + } + + internal override void ToASCIIGnuStep(StringBuilder ascii, int level) => ToASCII(ascii, level); + + /// + /// Determines whether the specified is equal to the current + /// . + /// + /// + /// The to compare with the current + /// . + /// + /// + /// true if the specified is equal to the current + /// ; otherwise, false. + /// + public override bool Equals(NSObject obj) => Equals((object)obj); + + /// + public override bool Equals(object obj) + { + if(obj is not UID uid) return false; + + return uid.value == value; + } + + /// + public override int GetHashCode() => value.GetHashCode(); + + /// + public override string ToString() => $"{value} (UID)"; + + /// Gets a which represents this . + /// A which represents this . + public ulong ToUInt64() => value; } \ No newline at end of file diff --git a/plist-cil/ValuePreprocessor.cs b/plist-cil/ValuePreprocessor.cs index d205a8e..1dbe8cd 100644 --- a/plist-cil/ValuePreprocessor.cs +++ b/plist-cil/ValuePreprocessor.cs @@ -1,121 +1,155 @@ using System; using System.Collections.Generic; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// +/// Allows you to override the default value class initialization for the values found +/// in the parsed plists by registering your own preprocessing implementations. +/// +public static class ValuePreprocessor { /// - /// Allows you to override the default value class initialization for the values found - /// in the parsed plists by registering your own preprocessing implementations. + /// Indicates the semantic type of content the preprocessor will work on--independent + /// from the underlying data type (which will be string in most cases anyway). /// - public static class ValuePreprocessor + public enum Type { - /// - /// Indicates the semantic type of content the preprocessor will work on--independent - /// from the underlying data type (which will be string in most cases anyway). - /// - public enum Type - { - BOOL, INTEGER, FLOATING_POINT, - UNDEFINED_NUMBER, STRING, DATA, - DATE - }; - - /// - /// A null-implementation of a preprocessor for registered, but passive, use cases. - /// - private static T NullPreprocessor(T value) => value; - - private record struct TypeIdentifier(Type ValueType, System.Type DataType); - - /// - /// Default preprocessors for all the standard cases. - /// - private static readonly Dictionary _preprocessors = new() - { - { new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor }, - { new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Type.INTEGER, typeof(byte[])), NullPreprocessor }, - { new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor }, - { new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Type.STRING, typeof(byte[])), NullPreprocessor }, - { new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Type.DATA, typeof(byte[])), NullPreprocessor }, - { new TypeIdentifier(Type.DATE, typeof(string)), NullPreprocessor }, - { new TypeIdentifier(Type.DATE, typeof(double)), NullPreprocessor }, - { new TypeIdentifier(Type.DATE, typeof(byte[])), NullPreprocessor }, - }; - - /// - /// Get a default preprocessor. - /// - public static Func GetDefault() => NullPreprocessor; - - /// - /// Set up a custom preprocessor. - /// - public static void Set(Func preprocessor, Type type) => - _preprocessors[new(type, typeof(T))] = preprocessor; - - - /// - /// Unset a specific preprocessor--replaces it with a null-implementation - /// to prevent argument errors. - /// - /// If no appropriate preprocessor--not even a default null-implementation--was set up. - public static void Unset(Type type) => - _preprocessors[GetValidTypeIdentifier(type)] = NullPreprocessor; - - /// - /// Completely unregister a specific preprocessor--remove it instead of - /// replacing it with a null-implementation. - /// - /// If no appropriate preprocessor--not even a default null-implementation--was registered. - public static void Remove(Type type) => - _preprocessors.Remove(GetValidTypeIdentifier(type)); - - /// - /// Preprocess the supplied data using the appropriate registered implementation. - /// - /// If no appropriate preprocessor--not even a default null-implementation--was registered. - public static T Preprocess(T value, Type type) => TryGetPreprocessor(type, out Func preprocess) - ? preprocess(value) - : throw new ArgumentException($"Failed to find a preprocessor for value '{value}'."); - - /// - /// Gets the appropriate registered implementation--or null--and casts it back to - /// the required type. - /// - private static bool TryGetPreprocessor(Type type, out Func preprocess) - { - if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor)) - { - preprocess = (Func)preprocessor; - - return true; - } - - preprocess = default; - - return false; - } - - /// - /// Gets a type identifier if a preprocessor exists for it. - /// - /// If no appropriate preprocessor--not even a default null-implementation--was set up. - private static TypeIdentifier GetValidTypeIdentifier(Type type) - { - var identifier = new TypeIdentifier(type, typeof(T)); - - if(!_preprocessors.ContainsKey(identifier)) - { - throw new ArgumentException($"Failed to find a valid preprocessor type identifier."); - } - - return identifier; - } + BOOL, + INTEGER, + FLOATING_POINT, + UNDEFINED_NUMBER, + STRING, + DATA, + DATE } -} + + /// + /// Default preprocessors for all the standard cases. + /// + private static readonly Dictionary _preprocessors = new() + { + { + new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor + }, + { + new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor + }, + { + new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor + }, + { + new TypeIdentifier(Type.INTEGER, typeof(byte[])), NullPreprocessor + }, + { + new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor + }, + { + new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor + }, + { + new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor + }, + { + new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor + }, + { + new TypeIdentifier(Type.STRING, typeof(byte[])), NullPreprocessor + }, + { + new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor + }, + { + new TypeIdentifier(Type.DATA, typeof(byte[])), NullPreprocessor + }, + { + new TypeIdentifier(Type.DATE, typeof(string)), NullPreprocessor + }, + { + new TypeIdentifier(Type.DATE, typeof(double)), NullPreprocessor + }, + { + new TypeIdentifier(Type.DATE, typeof(byte[])), NullPreprocessor + } + }; + + /// + /// A null-implementation of a preprocessor for registered, but passive, use cases. + /// + private static T NullPreprocessor(T value) => value; + + /// + /// Get a default preprocessor. + /// + public static Func GetDefault() => NullPreprocessor; + + /// + /// Set up a custom preprocessor. + /// + public static void Set(Func preprocessor, Type type) => + _preprocessors[new TypeIdentifier(type, typeof(T))] = preprocessor; + + + /// + /// Unset a specific preprocessor--replaces it with a null-implementation + /// to prevent argument errors. + /// + /// If no appropriate preprocessor--not even a default null-implementation--was set up. + public static void Unset(Type type) => _preprocessors[GetValidTypeIdentifier(type)] = NullPreprocessor; + + /// + /// Completely unregister a specific preprocessor--remove it instead of + /// replacing it with a null-implementation. + /// + /// + /// If no appropriate preprocessor--not even a default null-implementation--was + /// registered. + /// + public static void Remove(Type type) => _preprocessors.Remove(GetValidTypeIdentifier(type)); + + /// + /// Preprocess the supplied data using the appropriate registered implementation. + /// + /// + /// If no appropriate preprocessor--not even a default null-implementation--was + /// registered. + /// + public static T Preprocess(T value, Type type) => TryGetPreprocessor(type, out Func preprocess) + ? preprocess(value) + : throw new + ArgumentException($"Failed to find a preprocessor for value '{value}'."); + + /// + /// Gets the appropriate registered implementation--or null--and casts it back to + /// the required type. + /// + private static bool TryGetPreprocessor(Type type, out Func preprocess) + { + if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor)) + { + preprocess = (Func)preprocessor; + + return true; + } + + preprocess = default(Func); + + return false; + } + + /// + /// Gets a type identifier if a preprocessor exists for it. + /// + /// If no appropriate preprocessor--not even a default null-implementation--was set up. + private static TypeIdentifier GetValidTypeIdentifier(Type type) + { + var identifier = new TypeIdentifier(type, typeof(T)); + + if(!_preprocessors.ContainsKey(identifier)) + throw new ArgumentException("Failed to find a valid preprocessor type identifier."); + + return identifier; + } + + private record struct TypeIdentifier(Type ValueType, System.Type DataType); +} \ No newline at end of file diff --git a/plist-cil/XmlPropertyListParser.cs b/plist-cil/XmlPropertyListParser.cs index 644602f..9589ca1 100644 --- a/plist-cil/XmlPropertyListParser.cs +++ b/plist-cil/XmlPropertyListParser.cs @@ -28,206 +28,230 @@ using System.IO; using System.Linq; using System.Xml; -namespace Claunia.PropertyList +namespace Claunia.PropertyList; + +/// Parses XML property lists. +/// @author Daniel Dreibrodt +/// @author Natalia Portillo +public static class XmlPropertyListParser { - /// Parses XML property lists. - /// @author Daniel Dreibrodt - /// @author Natalia Portillo - public static class XmlPropertyListParser + /// Parses a XML property list file. + /// The XML property list file. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(FileInfo f) { - /// Parses a XML property list file. - /// The XML property list file. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(FileInfo f) - { - var doc = new XmlDocument(); + var doc = new XmlDocument(); - var settings = new XmlReaderSettings + var settings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Ignore + }; + + using(Stream stream = f.OpenRead()) + { + using(var reader = XmlReader.Create(stream, settings)) { - DtdProcessing = DtdProcessing.Ignore - }; - - using(Stream stream = f.OpenRead()) - using(var reader = XmlReader.Create(stream, settings)) - doc.Load(reader); - - return ParseDocument(doc); - } - - /// Parses a XML property list from a byte array. - /// The byte array containing the property list's data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(byte[] bytes) - { - var bis = new MemoryStream(bytes); - - return Parse(bis); - } - - /// Parses a XML property list from an input stream. - /// The input stream pointing to the property list's data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject Parse(Stream str) - { - var doc = new XmlDocument(); - - var settings = new XmlReaderSettings(); - settings.DtdProcessing = DtdProcessing.Ignore; - - using(var reader = XmlReader.Create(str, settings)) doc.Load(reader); - - return ParseDocument(doc); - } - - /// Parses a XML property list from a string. - /// The string pointing to the property list's data. - /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. - public static NSObject ParseString(string value) - { - var doc = new XmlDocument(); - - var settings = new XmlReaderSettings(); - settings.DtdProcessing = DtdProcessing.Ignore; - - doc.LoadXml(value); - - return ParseDocument(doc); - } - - /// Parses the XML document by generating the appropriate NSObjects for each XML node. - /// The root NSObject of the property list contained in the XML document. - /// The XML document. - static NSObject ParseDocument(XmlDocument doc) - { - XmlNode docType = doc.ChildNodes.OfType(). - SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType); - - if(docType == null) - { - if(doc.DocumentElement != null && - !doc.DocumentElement.Name.Equals("plist")) - throw new XmlException("The given XML document is not a property list."); } - else if(!docType.Name.Equals("plist")) + } + + return ParseDocument(doc); + } + + /// Parses a XML property list from a byte array. + /// The byte array containing the property list's data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(byte[] bytes) + { + var bis = new MemoryStream(bytes); + + return Parse(bis); + } + + /// Parses a XML property list from an input stream. + /// The input stream pointing to the property list's data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject Parse(Stream str) + { + var doc = new XmlDocument(); + + var settings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Ignore + }; + + using(var reader = XmlReader.Create(str, settings)) + { + doc.Load(reader); + } + + return ParseDocument(doc); + } + + /// Parses a XML property list from a string. + /// The string pointing to the property list's data. + /// The root object of the property list. This is usually a NSDictionary but can also be a NSArray. + public static NSObject ParseString(string value) + { + var doc = new XmlDocument(); + + var settings = new XmlReaderSettings + { + DtdProcessing = DtdProcessing.Ignore + }; + + doc.LoadXml(value); + + return ParseDocument(doc); + } + + /// Parses the XML document by generating the appropriate NSObjects for each XML node. + /// The root NSObject of the property list contained in the XML document. + /// The XML document. + static NSObject ParseDocument(XmlDocument doc) + { + XmlNode docType = doc.ChildNodes.OfType().SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType); + + if(docType == null) + { + if(doc.DocumentElement != null && !doc.DocumentElement.Name.Equals("plist")) throw new XmlException("The given XML document is not a property list."); - - XmlNode rootNode; - - if(doc.DocumentElement is { Name: "plist" }) - { - //Root element wrapped in plist tag - List rootNodes = FilterElementNodes(doc.DocumentElement.ChildNodes); - - rootNode = rootNodes.Count switch - { - 0 => throw new PropertyListFormatException("The given XML property list has no root element!"), - 1 => rootNodes[0], - _ => throw new - PropertyListFormatException("The given XML property list has more than one root element!") - }; - } - else - - //Root NSObject not wrapped in plist-tag - rootNode = doc.DocumentElement; - - return ParseObject(rootNode); } + else if(!docType.Name.Equals("plist")) throw new XmlException("The given XML document is not a property list."); - /// Parses a node in the XML structure and returns the corresponding NSObject - /// The corresponding NSObject. - /// The XML node. - static NSObject ParseObject(XmlNode n) + XmlNode rootNode; + + if(doc.DocumentElement is { Name: "plist" }) { - switch(n.Name) + //Root element wrapped in plist tag + List rootNodes = FilterElementNodes(doc.DocumentElement.ChildNodes); + + rootNode = rootNodes.Count switch + { + 0 => throw new + PropertyListFormatException("The given XML property list has no root element!"), + 1 => rootNodes[0], + _ => throw new + PropertyListFormatException("The given XML property list has more than one root element!") + }; + } + else + + //Root NSObject not wrapped in plist-tag + rootNode = doc.DocumentElement; + + return ParseObject(rootNode); + } + + /// Parses a node in the XML structure and returns the corresponding NSObject + /// The corresponding NSObject. + /// The XML node. + static NSObject ParseObject(XmlNode n) + { + switch(n.Name) + { + // Special case for UID values + case "dict" when n.ChildNodes.Count == 2 && + n.ChildNodes[0].Name == "key" && + 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": { - // Special case for UID values - case "dict" when n.ChildNodes.Count == 2 && n.ChildNodes[0].Name == "key" && - 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": + var dict = new NSDictionary(); + List children = FilterElementNodes(n.ChildNodes); + + for(int i = 0; i < children.Count; i += 2) { - var dict = new NSDictionary(); - List children = FilterElementNodes(n.ChildNodes); + XmlNode key = children[i]; + XmlNode val = children[i + 1]; - for(int i = 0; i < children.Count; i += 2) - { - XmlNode key = children[i]; - XmlNode val = children[i + 1]; + string keyString = GetNodeTextContents(key); - string keyString = GetNodeTextContents(key); - - dict.Add(keyString, ParseObject(val)); - } - - return dict; + dict.Add(keyString, ParseObject(val)); } - case "array": - { - List children = FilterElementNodes(n.ChildNodes); - var array = new NSArray(children.Count); - for(int i = 0; i < children.Count; i++) - array.Add(ParseObject(children[i])); - - return array; - } - case "true": return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); - case "false": return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); - case "integer": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.INTEGER), NSNumber.INTEGER); - case "real": return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.FLOATING_POINT), NSNumber.REAL); - case "string": return new NSString(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.STRING)); - case "data": return new NSData(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.DATA)); - default: return n.Name.Equals("date") ? new NSDate(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.DATE)) : null; + return dict; } - } - - /// Returns all element nodes that are contained in a list of nodes. - /// The sublist containing only nodes representing actual elements. - /// The list of nodes to search. - static List FilterElementNodes(XmlNodeList list) - { - List result = new(); - - foreach(XmlNode child in list) - if(child.NodeType == XmlNodeType.Element) - result.Add(child); - - return result; - } - - /// - /// Returns a node's text content. This method will return the text value represented by the node's direct - /// children. If the given node is a TEXT or CDATA node, then its value is returned. - /// - /// The node's text content. - /// The node. - static string GetNodeTextContents(XmlNode n) - { - if(n.NodeType is XmlNodeType.Text or XmlNodeType.CDATA) + case "array": { - string content = n.Value; //This concatenates any adjacent text/cdata/entity nodes + List children = FilterElementNodes(n.ChildNodes); + var array = new NSArray(children.Count); + + for(int i = 0; i < children.Count; i++) array.Add(ParseObject(children[i])); + + return array; + } + case "true": + return new NSNumber(ValuePreprocessor.Preprocess(true, ValuePreprocessor.Type.BOOL)); + case "false": + return new NSNumber(ValuePreprocessor.Preprocess(false, ValuePreprocessor.Type.BOOL)); + case "integer": + return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), + ValuePreprocessor.Type.INTEGER), + NSNumber.INTEGER); + case "real": + return new NSNumber(ValuePreprocessor.Preprocess(GetNodeTextContents(n), + ValuePreprocessor.Type.FLOATING_POINT), + NSNumber.REAL); + case "string": + return new NSString(ValuePreprocessor.Preprocess(GetNodeTextContents(n), + ValuePreprocessor.Type.STRING)); + case "data": + return new NSData(ValuePreprocessor.Preprocess(GetNodeTextContents(n), ValuePreprocessor.Type.DATA)); + default: + return n.Name.Equals("date") + ? new NSDate(ValuePreprocessor.Preprocess(GetNodeTextContents(n), + ValuePreprocessor.Type.DATE)) + : null; + } + } + + /// Returns all element nodes that are contained in a list of nodes. + /// The sublist containing only nodes representing actual elements. + /// The list of nodes to search. + static List FilterElementNodes(XmlNodeList list) + { + List result = []; + + foreach(XmlNode child in list) + if(child.NodeType == XmlNodeType.Element) result.Add(child); + + return result; + } + + /// + /// Returns a node's text content. This method will return the text value represented by the node's direct + /// children. If the given node is a TEXT or CDATA node, then its value is returned. + /// + /// The node's text content. + /// The node. + static string GetNodeTextContents(XmlNode n) + { + if(n.NodeType is XmlNodeType.Text or XmlNodeType.CDATA) + { + string content = n.Value; //This concatenates any adjacent text/cdata/entity nodes + + return content ?? ""; + } + + if(!n.HasChildNodes) return ""; + + XmlNodeList children = n.ChildNodes; + + foreach(XmlNode child in children) + + //Skip any non-text nodes, like comments or entities + { + if(child.NodeType is XmlNodeType.Text or XmlNodeType.CDATA) + { + string content = child.Value; //This concatenates any adjacent text/cdata/entity nodes return content ?? ""; } - - if(!n.HasChildNodes) - return ""; - - XmlNodeList children = n.ChildNodes; - - foreach(XmlNode child in children) - - //Skip any non-text nodes, like comments or entities - if(child.NodeType is XmlNodeType.Text or XmlNodeType.CDATA) - { - string content = child.Value; //This concatenates any adjacent text/cdata/entity nodes - - return content ?? ""; - } - - return ""; } + + return ""; } } \ No newline at end of file diff --git a/plist-cil/plist-cil.csproj b/plist-cil/plist-cil.csproj index bbba8ad..191716b 100644 --- a/plist-cil/plist-cil.csproj +++ b/plist-cil/plist-cil.csproj @@ -45,12 +45,12 @@ - - + + - + diff --git a/test-files/issue21.plist b/test-files/issue21.plist index 286f7ab..1bfe2ce 100644 --- a/test-files/issue21.plist +++ b/test-files/issue21.plist @@ -1,5 +1,5 @@ - - + -Lot&s of &persand&s and other escapable "'<>€ characters + Lot&s of &persand&s and other escapable "'<>€ characters diff --git a/test-files/issue22-emoji-xml.plist b/test-files/issue22-emoji-xml.plist index 6d3c3cb..f244606 100644 --- a/test-files/issue22-emoji-xml.plist +++ b/test-files/issue22-emoji-xml.plist @@ -1,8 +1,8 @@ - - + - - emojiString - Test Test, 😰❔👍👎🔥 - + + emojiString + Test Test, 😰❔👍👎🔥 + diff --git a/test-files/issue30.plist b/test-files/issue30.plist index 500d3c8..70ee5cd 100644 --- a/test-files/issue30.plist +++ b/test-files/issue30.plist @@ -1,7 +1,7 @@ - - + - - 0 - + + 0 + diff --git a/test-files/issue4.plist b/test-files/issue4.plist index f2498ff..3f1dfeb 100644 --- a/test-files/issue4.plist +++ b/test-files/issue4.plist @@ -1,8 +1,8 @@ - - + - - Device Name - Kid’s iPhone - + + Device Name + Kid’s iPhone + diff --git a/test-files/test-ascii-utf8.plist b/test-files/test-ascii-utf8.plist index 00ecac5..a01c9d2 100644 --- a/test-files/test-ascii-utf8.plist +++ b/test-files/test-ascii-utf8.plist @@ -1,5 +1 @@ -// !$*UTF8*$! -{ - path = "JÔÖú@2x.jpg"; - "Key QÔÖª@2x \u4321" = "QÔÖú@2x 啕.jpg"; -} \ No newline at end of file +// !$*UTF8*$!{path = "JÔÖú@2x.jpg";"Key QÔÖª@2x \u4321" = "QÔÖú@2x 啕.jpg";} \ No newline at end of file diff --git a/test-files/test-ascii.plist b/test-files/test-ascii.plist index 5725064..38719f0 100644 --- a/test-files/test-ascii.plist +++ b/test-files/test-ascii.plist @@ -1,5 +1 @@ -{ - "key&\102"="value&\U0042=="; - key2 = "strangestring\\\""; - key3 = "strangestring\\"; -} \ No newline at end of file +{"key&\102"="value&\U0042==";key2 = "strangestring\\\"";key3 = "strangestring\\";} \ No newline at end of file diff --git a/test-files/test1-ascii-gnustep.plist b/test-files/test1-ascii-gnustep.plist index 463dc42..e1a2699 100644 --- a/test-files/test1-ascii-gnustep.plist +++ b/test-files/test1-ascii-gnustep.plist @@ -1,12 +1,2 @@ -{ - keyA = valueA; - "key&\102" = "value&\U0042"; - date = <*D2011-11-28 09:21:30 +0000>; - data = <00000004 10410820 82>; - array = ( - <*BY>, - <*BN>, - <*I87>, - <*R3.14159> - ); -} \ No newline at end of file +{ keyA = valueA; "key&\102" = "value&\U0042"; date = +<*D2011-11-28 09:21:30 +0000>; data = <00000004 10410820 82>; array = ( <*BY>, <*BN>, <*I87>, <*R3.14159> );} \ No newline at end of file diff --git a/test-files/test1-ascii.plist b/test-files/test1-ascii.plist index 4f21173..6feeda7 100644 --- a/test-files/test1-ascii.plist +++ b/test-files/test1-ascii.plist @@ -1,12 +1,2 @@ -{ - keyA = valueA; - "key&\102" = "value&\U0042"; - date = "2011-11-28T09:21:30Z"; - data = <00000004 10410820 82>; - array = ( - YES, - NO, - 87, - 3.14159 - ); -} \ No newline at end of file +{ keyA = valueA; "key&\102" = "value&\U0042"; date = "2011-11-28T09:21:30Z"; data = +<00000004 10410820 82>; array = ( YES, NO, 87, 3.14159 );} \ No newline at end of file diff --git a/test-files/test1.plist b/test-files/test1.plist index 1dc2bc4..b2819f5 100644 --- a/test-files/test1.plist +++ b/test-files/test1.plist @@ -1,21 +1,21 @@ - - + - - keyA - valueA - key&B - value&B - date - 2011-11-28T09:21:30Z - data - AAAA BBBB CCCC - array - - - - 87 - 3.14159 - - + + keyA + valueA + key&B + value&B + date + 2011-11-28T09:21:30Z + data + AAAA BBBB CCCC + array + + + + 87 + 3.14159 + + diff --git a/test-files/testNegative-bin.plist b/test-files/testNegative-bin.plist index 4c0461be0961ede967712dce04a1c51a57e76e5e..4f35f1d6c82cf64d687ecfd460693625663b9fdc 100644 GIT binary patch literal 225 zcmYc)$jK}&F)(<)e=j2wGYcylI|sKySYByvQfg5+gffBB#!%V_N}EDyGvW99_hKSF zkT3%TD8Jvom*f5Zy&7<3ro#1bHZnum`ThRA=@60F!uR|4g0w1RLj-am6o;scijE1F Ujf-zsd`2O}21Z5*jZRhp06ErW%>V!Z literal 167 zcmYc)$jK}&F)+Bs$i&RT%Er#Y$;HhRmRFjalv)%Hp-iB(F_bof(xy<_O!z+-{MHk0 zV1NMS0~~)e`hGn(6|M(M{r|7*{3U&DTWsM9<- - + - - number - -1234 - number2 - 9223372036854775807 - number3 - -3.12312423423 - number4 - -9223372036854775808 - number5 - 2.352535353543534e+19 - number6 - -999992312312312.2 - + + number + -1234 + number2 + 9223372036854775807 + number3 + -3.12312423423 + number4 + -9223372036854775808 + number5 + 2.352535353543534e+19 + number6 + -999992312312312.2 + diff --git a/version.json b/version.json index eabfd33..36abc50 100644 --- a/version.json +++ b/version.json @@ -1,8 +1,8 @@ { - "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.2", - "publicReleaseRefSpec": [ - "^refs/heads/master$", - "^refs/heads/releases/*" - ] + "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "2.2", + "publicReleaseRefSpec": [ + "^refs/heads/master$", + "^refs/heads/releases/*" + ] } \ No newline at end of file