Compare commits

...

12 Commits

65 changed files with 8383 additions and 7475 deletions

File diff suppressed because it is too large Load Diff

7
.idea/.idea.plist-cil/.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

View File

@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="vcsConfiguration" value="2" />
<option name="singleClickDiffPreview" value="1" />
<option name="unhandledExceptionsIgnoreList" value="1" />
<option name="vcsConfiguration" value="3" />
</component>
</project>

View File

@@ -1,4 +1,4 @@
Copyright (c) 2015 Natalia Portillo
Copyright (C) 2015-2025 Natalia Portillo
Based on dd-plist,
Copyright (c) 2014 Daniel Dreibrodt

View File

@@ -7,15 +7,16 @@ It is mostly based on [dd-plist for Java](https://github.com/3breadt/dd-plist).
And as it, this is licensed under the terms of the MIT license.
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<string, Object>`) or a `NSArray` (can be created from object arrays).
You can create your own property list using the various constructors of the different `NSObject` classes. Or you can
wrap existing native .NET structures with the method `NSObject.Wrap(Object o)`. Just make sure that the root object of
the property list is either a `NSDictionary` (can be created from objects of the type `Dictionary<string, Object>`) or a
`NSArray` (can be created from object arrays).
For building a XML property list you can then call the `ToXml` method on the root object of your property list. It will give you an UTF-8 `string` containing the property list in XML format.
For building a XML property list you can then call the `ToXml` method on the root object of your property list. It will
give you an UTF-8 `string` containing the property list in XML format.
If you want to have the property list in binary format use the `BinaryPropertyListWriter` class. It can write the binary property list directly to a file or to a `Stream`.
If you want to have the property list in binary format use the `BinaryPropertyListWriter` class. It can write the binary
property list directly to a file or to a `Stream`.
When you directly want to save your property list to a file, you can also use the `SaveAsXml` or `SaveAsBinary` methods of the `PropertyListParser` class.
When you directly want to save your property list to a file, you can also use the `SaveAsXml` or `SaveAsBinary` methods
of the `PropertyListParser` class.
### Converting
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

1
codealike.json Normal file
View File

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

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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<BinaryPropertyListParserBenchmarks>();
BenchmarkRunner.Run<BinaryPropertyListWriterBenchmarks>();
}
BenchmarkRunner.Run<BinaryPropertyListParserBenchmarks>();
BenchmarkRunner.Run<BinaryPropertyListWriterBenchmarks>();
}
}

View File

@@ -2,16 +2,16 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>Claunia.PropertyList.Benchmark</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\plist-cil\plist-cil.csproj" />
<ProjectReference Include="..\plist-cil\plist-cil.csproj"/>
</ItemGroup>
<ItemGroup>

View File

@@ -4,6 +4,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00
VisualStudioVersion = 15.0.26430.6
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{25B9F55C-9830-4526-9539-949838B09EAC}"
ProjectSection(SolutionItems) = preProject
codealike.json = codealike.json
LICENSE = LICENSE
plist-cil.sln.DotSettings = plist-cil.sln.DotSettings
README.md = README.md
version.json = version.json
ChangeLog = ChangeLog
.editorconfig = .editorconfig
.gitignore = .gitignore
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "plist-cil", "plist-cil\plist-cil.csproj", "{2A906AEB-BDE0-4356-8114-064F80596C7D}"
EndProject
@@ -116,7 +126,7 @@ Global
$4.inheritsScope = text/plain
$4.scope = text/plain
$0.StandardHeader = $5
$5.Text = @plist-cil - An open source library to parse and generate property lists for .NET\nCopyright (C) 2015 Natalia Portillo\n\nThis code is based on:\nplist - An open source library to parse and generate property lists\nCopyright (C) 2014 Daniel Dreibrodt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.
$5.Text = @plist-cil - An open source library to parse and generate property lists for .NET\nCopyright (C) 2015-2025 Natalia Portillo\n\nThis code is based on:\nplist - An open source library to parse and generate property lists\nCopyright (C) 2014 Daniel Dreibrodt\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.
$5.IncludeInNewFiles = True
$0.NameConventionPolicy = $6
$6.Rules = $7

View File

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

View File

@@ -1,68 +1,99 @@
using Claunia.PropertyList;
using 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);
}

View File

@@ -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);
}
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -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
/// <summary>
/// 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).
/// </summary>
[Fact]
public static void RoundtripDataTest()
{
/// <summary>
/// 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).
/// </summary>
[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);
}
/// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario where the binary
/// data is indented.
/// </summary>
[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();
/// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario where the binary
/// data is indented.
/// </summary>
[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<double>(weight);
Assert.Equal(10d, (double)weight);
}
object weight = dict["weight"].ToObject();
Assert.IsType<double>(weight);
Assert.Equal(10d, (double)weight);
}
}

View File

@@ -2,90 +2,89 @@
using Claunia.PropertyList;
using Xunit;
namespace plistcil.test
namespace plistcil.test;
public class NSArrayTests
{
public class NSArrayTests
/// <summary>Tests the addition of a .NET object to the NSArray</summary>
[Fact]
public void AddAndContainsObjectTest()
{
/// <summary>Tests the addition of a .NET object to the NSArray</summary>
[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));
}
/// <summary>Tests the <see cref="NSArray.GetEnumerator" /> method.</summary>
[Fact]
public void EnumeratorTest()
/// <summary>Tests the <see cref="NSArray.GetEnumerator" /> method.</summary>
[Fact]
public void EnumeratorTest()
{
var array = new NSArray
{
var array = new NSArray
{
0,
1
};
0,
1
};
using IEnumerator<NSObject> enumerator = array.GetEnumerator();
using IEnumerator<NSObject> 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());
}
/// <summary>Tests the <see cref="NSArray.IndexOf(object)" /> method for .NET objects.</summary>
[Fact]
public void IndexOfTest()
/// <summary>Tests the <see cref="NSArray.IndexOf(object)" /> method for .NET objects.</summary>
[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"));
}
/// <summary>Tests the <see cref="NSArray.Insert(int, object)" /> method for a .NET object.</summary>
[Fact]
public void InsertTest()
/// <summary>Tests the <see cref="NSArray.Insert(int, object)" /> method for a .NET object.</summary>
[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());
}
/// <summary>Tests the <see cref="NSArray.Remove(object)" /> method for a .NET object.</summary>
[Fact]
public void RemoveTest()
/// <summary>Tests the <see cref="NSArray.Remove(object)" /> method for a .NET object.</summary>
[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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<object[]> SpanConstructorTestData() => new List<object[]>
{
public static IEnumerable<object[]> SpanConstructorTestData() => new List<object[]>
// 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<byte>)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<ArgumentNullException>(() => new NSNumber((Span<byte>)null, NSNumber.INTEGER));
Assert.Throws<ArgumentNullException>(() => new NSNumber((Span<byte>)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<byte>)data, type);
Assert.Equal(boolValue, number.ToBool());
Assert.Equal(longValue, number.ToLong());
Assert.Equal(doubleValue, number.ToDouble(), 5);
}
Assert.Throws<ArgumentOutOfRangeException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(),
NSNumber.INTEGER));
[Fact]
public void SpanConstructorInvalidValuesTest()
{
Assert.Throws<ArgumentNullException>(() => new NSNumber((Span<byte>)null, NSNumber.INTEGER));
Assert.Throws<ArgumentNullException>(() => new NSNumber((Span<byte>)null, NSNumber.REAL));
Assert.Throws<ArgumentException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), NSNumber.REAL));
Assert.Throws<ArgumentException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), 9));
}
Assert.Throws<ArgumentOutOfRangeException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(),
NSNumber.INTEGER));
[Fact]
public void StringAndTypeConstructorInvalidValuesTest()
{
Assert.Throws<ArgumentNullException>(() => new NSNumber((string)null, NSNumber.INTEGER));
Assert.Throws<ArgumentNullException>(() => new NSNumber((string)null, NSNumber.REAL));
Assert.Throws<ArgumentException>(() => new NSNumber("0", 9));
}
Assert.Throws<ArgumentException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), NSNumber.REAL));
Assert.Throws<ArgumentException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), 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<ArgumentNullException>(() => new NSNumber((string)null, NSNumber.INTEGER));
Assert.Throws<ArgumentNullException>(() => new NSNumber((string)null, NSNumber.REAL));
Assert.Throws<ArgumentException>(() => 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:
// <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real>
[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:
// <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real>
[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:
// <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real>
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:
// <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real>
var number = new NSNumber("7200.000000", NSNumber.REAL);
Assert.True(number.isReal());
Assert.Equal(7200d, number.ToDouble());
}
public static IEnumerable<object[]> StringConstructorTestData() => new List<object[]>
{
// 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<ArgumentException>(() => new NSNumber(null));
Assert.Throws<ArgumentException>(() => new NSNumber("plist"));
}
public static IEnumerable<object[]> Int32ConstructorTestData() => new List<object[]>
{
// Long values, formatted as hexadecimal values
new object[]
{
// As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real>
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<object[]> Int64ConstructorTestData() => new List<object[]>
{
// Long values, formatted as hexadecimal values
new object[]
{
// As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real>
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<object[]> DoubleConstructorTestData() => new List<object[]>
{
// Long values, formatted as hexadecimal values
new object[]
{
// As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real>
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<object[]> StringConstructorTestData() => new List<object[]>
[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<object[]> BoolConstructorTestData() => new List<object[]>
{
// 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<ArgumentException>(() => new NSNumber(null));
Assert.Throws<ArgumentException>(() => 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<object[]> Int32ConstructorTestData() => new List<object[]>
{
// 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<object[]> Int64ConstructorTestData() => new List<object[]>
{
// 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<object[]> DoubleConstructorTestData() => new List<object[]>
{
// 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<object[]> BoolConstructorTestData() => new List<object[]>
{
// 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));
}
}

View File

@@ -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 = "<string>";
const string END_TOKEN = "</string>";
[InlineData("abc", "abc")]
[InlineData("a>b", "a&gt;b")]
[InlineData("a<b", "a&lt;b")]
[InlineData("a&b", "a&amp;b")]
[Theory]
public void Content_IsEscaped(string value, string content)
{
const string START_TOKEN = "<string>";
const string END_TOKEN = "</string>";
var element = new NSString(value);
string xml = element.ToXmlPropertyList();
[InlineData("abc", "abc")]
[InlineData("a>b", "a&gt;b")]
[InlineData("a<b", "a&lt;b")]
[InlineData("a&b", "a&amp;b")]
[Theory]
public void Content_IsEscaped(string value, string content)
{
var element = new NSString(value);
string xml = element.ToXmlPropertyList();
// Strip the leading and trailing data, so we just get the string element itself
int start = xml.IndexOf(START_TOKEN) + START_TOKEN.Length;
int end = xml.IndexOf(END_TOKEN);
string actualContent = xml.Substring(start, end - start);
// Strip the leading and trailing data, so we just get the string element itself
int start = xml.IndexOf(START_TOKEN) + START_TOKEN.Length;
int end = xml.IndexOf(END_TOKEN);
string actualContent = xml.Substring(start, end - start);
Assert.Equal(content, actualContent);
}
Assert.Equal(content, actualContent);
}
}
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to Parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to Parse and generate property lists
@@ -29,315 +29,327 @@ using System.IO;
using Claunia.PropertyList;
using Xunit;
namespace plistcil.test
namespace plistcil.test;
public static class ParseTest
{
public static class ParseTest
static bool ArrayEquals(byte[] arrayA, byte[] arrayB)
{
static bool ArrayEquals(byte[] arrayA, byte[] arrayB)
{
if(arrayA.Length != arrayB.Length)
return false;
if(arrayA.Length != arrayB.Length) return false;
for(int i = 0; i < arrayA.Length; i++)
if(arrayA[i] != arrayB[i])
return false;
for(int i = 0; i < arrayA.Length; i++)
if(arrayA[i] != arrayB[i]) return false;
return true;
}
return true;
}
/**
* 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 TestSet()
{
NSSet s = new NSSet();
s.AddObject(new NSNumber(1));
s.AddObject(new NSNumber(3));
s.AddObject(new NSNumber(2));
NSSet orderedSet = new NSSet(true);
s.AddObject(new NSNumber(1));
s.AddObject(new NSNumber(3));
s.AddObject(new NSNumber(2));
NSDictionary dict = new NSDictionary();
dict.Add("set1", s);
dict.Add("set2", orderedSet);
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, new byte[]
{
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, new byte[]
{
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<object> list = new(array);
Dictionary<string, object> map = new();
map.Add("int", i);
map.Add("long", lng);
map.Add("date", date);
List<Dictionary<string,object>> listOfMaps = new()
{
new Dictionary<string, object>
{
{ "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<object> list = new(array);
Dictionary<string, object> map = new();
map.Add("int", i);
map.Add("long", lng);
map.Add("date", date);
List<Dictionary<string, object>> listOfMaps =
[
new Dictionary<string, object>
{
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));
}
}

View File

@@ -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<PropertyListFormatException>(ParseEmptyStreamTestDelegate);
PropertyListParser.Parse(stream);
}
[Fact]
public static void ParseEmptyStreamTest() =>
Assert.Throws<PropertyListFormatException>(ParseEmptyStreamTestDelegate);
}

View File

@@ -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());
}
}

View File

@@ -52,19 +52,19 @@ public class UseCultureAttribute : BeforeAfterTestAttribute
/// <param name="methodUnderTest">The method under test</param>
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
}
/// <summary>
@@ -74,12 +74,12 @@ public class UseCultureAttribute : BeforeAfterTestAttribute
/// <param name="methodUnderTest">The method under test</param>
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
}
}

View File

@@ -9,93 +9,91 @@ using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace plistcil.test
namespace plistcil.test;
/// <summary>
/// A <see cref="Stream" /> which writes its output to a <see cref="Stream" /> and validates that the data which
/// is being written to the output stream matches the data in a reference stream.
/// </summary>
internal class ValidatingStream : Stream
{
/// <summary>
/// A <see cref="Stream" /> which writes its output to a <see cref="Stream" /> and validates that the data which
/// is being written to the output stream matches the data in a reference stream.
/// </summary>
internal class ValidatingStream : Stream
readonly Stream expectedOutput;
readonly Stream output;
/// <summary>Initializes a new instance of the <see cref="ValidatingCompositeStream" /> class.</summary>
/// <param name="output">The <see cref="Stream" /> to which to write data.</param>
/// <param name="expectedOutput">The reference stream for <paramref name="output" />.</param>
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));
}
/// <summary>Initializes a new instance of the <see cref="ValidatingCompositeStream" /> class.</summary>
/// <param name="output">The <see cref="Stream" /> to which to write data.</param>
/// <param name="expectedOutput">The reference stream for <paramref name="output" />.</param>
public ValidatingStream(Stream output, Stream expectedOutput)
{
this.output = output ?? throw new ArgumentNullException(nameof(output));
this.expectedOutput = expectedOutput ?? throw new ArgumentNullException(nameof(expectedOutput));
}
/// <inheritdoc />
public override bool CanRead => false;
/// <inheritdoc />
public override bool CanRead => false;
/// <inheritdoc />
public override bool CanSeek => false;
/// <inheritdoc />
public override bool CanSeek => false;
/// <inheritdoc />
public override bool CanWrite => true;
/// <inheritdoc />
public override bool CanWrite => true;
/// <inheritdoc />
public override long Length => output.Length;
/// <inheritdoc />
public override long Length => output.Length;
/// <inheritdoc />
public override long Position
{
get => output.Position;
set => throw new NotImplementedException();
}
/// <inheritdoc />
public override long Position
{
get => output.Position;
set => throw new NotImplementedException();
}
/// <inheritdoc />
public override void Flush() => output.Flush();
/// <inheritdoc />
public override void Flush() => output.Flush();
/// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <inheritdoc />
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
throw new NotSupportedException();
/// <inheritdoc />
public override Task<int>
ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
throw new NotSupportedException();
/// <inheritdoc />
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
/// <inheritdoc />
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
/// <inheritdoc />
public override void SetLength(long value) => throw new NotImplementedException();
/// <inheritdoc />
public override void SetLength(long value) => throw new NotImplementedException();
/// <inheritdoc />
public override void Write(byte[] buffer, int offset, int count)
{
byte[] expected = new byte[buffer.Length];
expectedOutput.Read(expected, offset, count);
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
/// <inheritdoc />
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);
}
}

View File

@@ -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<string, string> 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<string, string> 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<string>(testType);
}
}
[Fact]
public static void TestRegisteredPreprocessorSelection1()
{
lock(_testLock)
{
Func<short, short> 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<string>(), 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<short>(testType);
ValuePreprocessor.Remove<string>(testType);
}
}
[Fact]
public static void TestRegisteredPreprocessorSelection2()
{
lock(_testLock)
{
Func<string, string> 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<byte[]>(), 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<string>(testType);
ValuePreprocessor.Remove<byte[]>(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<ArgumentException>(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING));
ValuePreprocessor.Unset<string>(testType);
}
}
[Fact]
public static void TestRegisteredPreprocessorSelection1()
{
lock(_testLock)
{
Func<short, short> 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<string>(), 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<short>(testType);
ValuePreprocessor.Remove<string>(testType);
}
}
[Fact]
public static void TestRegisteredPreprocessorSelection2()
{
lock(_testLock)
{
Func<string, string> 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<byte[]>(), 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<string>(testType);
ValuePreprocessor.Remove<byte[]>(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<ArgumentException>(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING));
}
}

View File

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

View File

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

View File

@@ -1,80 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>files</key>
<dict>
<key>PkgInfo</key>
<data>
n57qDP4tZfLD1rCS43W0B4LQjzE=
</data>
<key>icon.png</key>
<data>
EUOeOW/HpmiAZeEGzJm8j3hE6vo=
</data>
</dict>
<key>files2</key>
<dict>
<key>PkgInfo</key>
<data>
n57qDP4tZfLD1rCS43W0B4LQjzE=
</data>
<key>icon.png</key>
<data>
EUOeOW/HpmiAZeEGzJm8j3hE6vo=
</data>
</dict>
<key>rules</key>
<dict>
<key>.*</key>
<true/>
<key>Info.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>ResourceRules.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>100</real>
</dict>
</dict>
<key>rules2</key>
<dict>
<key>.*</key>
<true/>
<key>Info.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>ResourceRules.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>100</real>
</dict>
<key>^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>0.0</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>top</key>
<true/>
<key>weight</key>
<real>0.0</real>
</dict>
</dict>
</dict>
<dict>
<key>files</key>
<dict>
<key>PkgInfo</key>
<data>
n57qDP4tZfLD1rCS43W0B4LQjzE=
</data>
<key>icon.png</key>
<data>
EUOeOW/HpmiAZeEGzJm8j3hE6vo=
</data>
</dict>
<key>files2</key>
<dict>
<key>PkgInfo</key>
<data>
n57qDP4tZfLD1rCS43W0B4LQjzE=
</data>
<key>icon.png</key>
<data>
EUOeOW/HpmiAZeEGzJm8j3hE6vo=
</data>
</dict>
<key>rules</key>
<dict>
<key>.*</key>
<true/>
<key>Info.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>ResourceRules.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>100</real>
</dict>
</dict>
<key>rules2</key>
<dict>
<key>.*</key>
<true/>
<key>Info.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>10</real>
</dict>
<key>ResourceRules.plist</key>
<dict>
<key>omit</key>
<true/>
<key>weight</key>
<real>100</real>
</dict>
<key>^(Frameworks|SharedFrameworks|Plugins|Plug-ins|XPCServices|Helpers|MacOS)/</key>
<dict>
<key>nested</key>
<true/>
<key>weight</key>
<real>0.0</real>
</dict>
<key>^[^/]+$</key>
<dict>
<key>top</key>
<true/>
<key>weight</key>
<real>0.0</real>
</dict>
</dict>
</dict>
</plist>

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +1,55 @@
using System;
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
// Copyright (C) 2014 Daniel Dreibrodt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
public partial class BinaryPropertyListWriter
{
public partial class BinaryPropertyListWriter
/// <summary>
/// The equality comparer which is used when adding an object to the <see cref="BinaryPropertyListWriter.idMap" />
/// . In most cases, objects are always added. The only exception are very specific strings, which are only added once.
/// </summary>
class AddObjectEqualityComparer : EqualityComparer<NSObject>
{
/// <summary>
/// The equality comparer which is used when adding an object to the <see cref="BinaryPropertyListWriter.idMap" />
/// . In most cases, objects are always added. The only exception are very specific strings, which are only added once.
/// </summary>
class AddObjectEqualityComparer : EqualityComparer<NSObject>
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();
}
}
}

View File

@@ -1,39 +1,68 @@
using System.Collections.Generic;
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
// Copyright (C) 2014 Daniel Dreibrodt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
namespace Claunia.PropertyList
using System.Collections.Generic;
namespace Claunia.PropertyList;
public partial class BinaryPropertyListWriter
{
public partial class BinaryPropertyListWriter
/// <summary>
/// The equality comparer which is used when retrieving objects in the
/// <see cref="BinaryPropertyListWriter.idMap" />. The logic is slightly different from
/// <see cref="AddObjectEqualityComparer" />, results in two equivalent objects (UIDs mainly) being added to the
/// <see cref="BinaryPropertyListWriter.idMap" />. 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.
/// </summary>
class GetObjectEqualityComparer : EqualityComparer<NSObject>
{
/// <summary>
/// The equality comparer which is used when retrieving objects in the
/// <see cref="BinaryPropertyListWriter.idMap" />. The logic is slightly different from
/// <see cref="AddObjectEqualityComparer" />, results in two equivalent objects (UIDs mainly) being added to the
/// <see cref="BinaryPropertyListWriter.idMap" />. 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.
/// </summary>
class GetObjectEqualityComparer : EqualityComparer<NSObject>
{
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 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()
};
}
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()
};
}
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,396 +27,386 @@ using System;
using System.Collections.Generic;
using System.IO;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>
/// <para>A BinaryPropertyListWriter is a helper class for writing out binary property list files.</para>
/// <para>
/// It contains an output stream and various structures for keeping track of which NSObjects have already been
/// serialized, and where they were put in the file.
/// </para>
/// </summary>
/// @author Keith Randall
/// @author Natalia Portillo
public partial class BinaryPropertyListWriter
{
/// <summary>
/// <para>A BinaryPropertyListWriter is a helper class for writing out binary property list files.</para>
/// <para>
/// It contains an output stream and various structures for keeping track of which NSObjects have already been
/// serialized, and where they were put in the file.
/// </para>
/// </summary>
/// @author Keith Randall
/// @author Natalia Portillo
public partial class BinaryPropertyListWriter
/// <summary>Binary property list version 0.0</summary>
public const int VERSION_00 = 0;
/// <summary>Binary property list version 1.0</summary>
public const int VERSION_10 = 10;
/// <summary>Binary property list version 1.5</summary>
public const int VERSION_15 = 15;
/// <summary>Binary property list version 2.0</summary>
public const int VERSION_20 = 20;
// map from object to its ID
protected readonly Dictionary<NSObject, int> idDict = new(new AddObjectEqualityComparer());
protected readonly Dictionary<NSObject, int> 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;
/// <summary>Creates a new binary property list writer</summary>
/// <param name="outStr">The output stream into which the binary property list will be written</param>
/// <exception cref="IOException">If an error occured while writing to the stream</exception>
public BinaryPropertyListWriter(Stream outStr) => outStream = outStr;
public BinaryPropertyListWriter(Stream outStr, int version)
{
/// <summary>Binary property list version 0.0</summary>
public const int VERSION_00 = 0;
/// <summary>Binary property list version 1.0</summary>
public const int VERSION_10 = 10;
/// <summary>Binary property list version 1.5</summary>
public const int VERSION_15 = 15;
/// <summary>Binary property list version 2.0</summary>
public const int VERSION_20 = 20;
// map from object to its ID
protected readonly Dictionary<NSObject, int> idDict = new(new AddObjectEqualityComparer());
protected readonly Dictionary<NSObject, int> 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;
/// <summary>Creates a new binary property list writer</summary>
/// <param name="outStr">The output stream into which the binary property list will be written</param>
/// <exception cref="IOException">If an error occured while writing to the stream</exception>
public BinaryPropertyListWriter(Stream outStr) => outStream = outStr;
public BinaryPropertyListWriter(Stream outStr, int version)
{
this.version = version;
outStream = outStr;
}
public BinaryPropertyListWriter(Stream outStr, int version,
IEqualityComparer<NSObject> addObjectEqualityComparer,
IEqualityComparer<NSObject> getObjectEqualityComparer)
{
this.version = version;
outStream = outStr;
idDict = new Dictionary<NSObject, int>(addObjectEqualityComparer);
idDict2 = new Dictionary<NSObject, int>(getObjectEqualityComparer);
}
/// <summary>
/// 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 <see langword="false" /> .
/// </summary>
/// <remarks>
/// In most scenarios, you want this to be <see langword="true" />, 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
/// <see langword="false" /> if you want to maintain binary compatibility with the Apple tools.
/// </remarks>
public bool ReuseObjectIds { get; set; }
/// <summary>Finds out the minimum binary property list format version that can be used to save the given NSObject tree.</summary>
/// <returns>Version code</returns>
/// <param name="root">Object root.</param>
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;
}
/// <summary>Writes a binary plist file with the given object as the root.</summary>
/// <param name="file">the file to write to</param>
/// <param name="root">the source of the data to write to the file</param>
/// <exception cref="IOException"></exception>
public static void Write(FileInfo file, NSObject root)
{
using FileStream fous = file.OpenWrite();
Write(fous, root);
}
/// <summary>Writes a binary plist serialization of the given object as the root.</summary>
/// <param name="outStream">the stream to write to</param>
/// <param name="root">the source of the data to write to the stream</param>
/// <exception cref="IOException"></exception>
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);
}
/// <summary>Writes a binary plist serialization of the given object as the root into a byte array.</summary>
/// <returns>The byte array containing the serialized property list</returns>
/// <param name="root">The root object of the property list</param>
/// <exception cref="IOException"></exception>
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<NSObject, int> 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<byte> 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<NSObject> addObjectEqualityComparer,
IEqualityComparer<NSObject> getObjectEqualityComparer)
{
this.version = version;
outStream = outStr;
idDict = new Dictionary<NSObject, int>(addObjectEqualityComparer);
idDict2 = new Dictionary<NSObject, int>(getObjectEqualityComparer);
}
/// <summary>
/// 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 <see langword="false" /> .
/// </summary>
/// <remarks>
/// In most scenarios, you want this to be <see langword="true" />, 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
/// <see langword="false" /> if you want to maintain binary compatibility with the Apple tools.
/// </remarks>
public bool ReuseObjectIds { get; set; }
/// <summary>Finds out the minimum binary property list format version that can be used to save the given NSObject tree.</summary>
/// <returns>Version code</returns>
/// <param name="root">Object root.</param>
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;
}
/// <summary>Writes a binary plist file with the given object as the root.</summary>
/// <param name="file">the file to write to</param>
/// <param name="root">the source of the data to write to the file</param>
/// <exception cref="IOException"></exception>
public static void Write(FileInfo file, NSObject root)
{
using FileStream fous = file.OpenWrite();
Write(fous, root);
}
/// <summary>Writes a binary plist serialization of the given object as the root.</summary>
/// <param name="outStream">the stream to write to</param>
/// <param name="root">the source of the data to write to the stream</param>
/// <exception cref="IOException"></exception>
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);
}
/// <summary>Writes a binary plist serialization of the given object as the root into a byte array.</summary>
/// <returns>The byte array containing the serialized property list</returns>
/// <param name="root">The root object of the property list</param>
/// <exception cref="IOException"></exception>
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<NSObject, int> 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<byte> 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();
}

View File

@@ -22,59 +22,58 @@
using System.Collections;
using System.Collections.Generic;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
partial class NSArray : IList<NSObject>
{
partial class NSArray : IList<NSObject>
/// <inheritdoc />
public NSObject this[int index]
{
/// <inheritdoc />
public NSObject this[int index]
{
get => array[index];
get => array[index];
set => array[index] = value;
}
/// <inheritdoc />
public bool IsReadOnly => false;
/// <inheritdoc />
public void Add(NSObject item) => array.Add(item);
/// <inheritdoc />
public void Clear() => array.Clear();
/// <inheritdoc />
public bool Contains(NSObject item) => array.Contains(item);
/// <inheritdoc />
public void CopyTo(NSObject[] array, int arrayIndex) => this.array.CopyTo(array, arrayIndex);
/// <inheritdoc />
public IEnumerator<NSObject> GetEnumerator() => array.GetEnumerator();
/// <inheritdoc />
public int IndexOf(NSObject item) => array.IndexOf(item);
/// <inheritdoc />
public void Insert(int index, NSObject item) => array.Insert(index, item);
/// <inheritdoc />
public bool Remove(NSObject item) => array.Remove(item);
/// <inheritdoc />
public void RemoveAt(int index) => array.RemoveAt(index);
/// <inheritdoc />
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;
}
/// <inheritdoc />
public bool IsReadOnly => false;
/// <inheritdoc />
public void Add(NSObject item) => array.Add(item);
/// <inheritdoc />
public void Clear() => array.Clear();
/// <inheritdoc />
public bool Contains(NSObject item) => array.Contains(item);
/// <inheritdoc />
public void CopyTo(NSObject[] array, int arrayIndex) => this.array.CopyTo(array, arrayIndex);
/// <inheritdoc />
public IEnumerator<NSObject> GetEnumerator() => array.GetEnumerator();
/// <inheritdoc />
public int IndexOf(NSObject item) => array.IndexOf(item);
/// <inheritdoc />
public void Insert(int index, NSObject item) => array.Insert(index, item);
/// <inheritdoc />
public bool Remove(NSObject item) => array.Remove(item);
/// <inheritdoc />
public void RemoveAt(int index) => array.RemoveAt(index);
/// <inheritdoc />
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));
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,336 +27,317 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>Represents an Array.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public partial class NSArray : NSObject
{
/// <summary>Represents an Array.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public partial class NSArray : NSObject
readonly List<NSObject> array;
/// <summary>Creates an empty array of the given length.</summary>
/// <param name="length">The number of elements this array will be able to hold.</param>
public NSArray(int length) => array = new List<NSObject>(length);
/// <summary>Creates a array from an existing one</summary>
/// <param name="a">The array which should be wrapped by the NSArray.</param>
public NSArray(params NSObject[] a) => array = [..a];
/// <summary>Returns the size of the array.</summary>
/// <value>The number of elements that this array can store.</value>
public int Count => array.Count;
/// <summary>Returns the object stored at the given index.</summary>
/// <returns>The object at the given index.</returns>
/// <param name="i">The index of the object.</param>
[Obsolete]
public NSObject ObjectAtIndex(int i) => array[i];
/// <summary>Remove the i-th element from the array. The array will be resized.</summary>
/// <param name="i">The index of the object</param>
[Obsolete]
public void Remove(int i) => array.RemoveAt(i);
/// <summary>Stores an object at the specified index. If there was another object stored at that index it will be replaced.</summary>
/// <param name="key">The index where to store the object.</param>
/// <param name="value">The object.</param>
[Obsolete]
public void SetValue(int key, object value)
{
readonly List<NSObject> array;
if(value == null) throw new ArgumentNullException("value", "Cannot add null values to an NSArray!");
/// <summary>Creates an empty array of the given length.</summary>
/// <param name="length">The number of elements this array will be able to hold.</param>
public NSArray(int length) => array = new List<NSObject>(length);
array[key] = Wrap(value);
}
/// <summary>Creates a array from an existing one</summary>
/// <param name="a">The array which should be wrapped by the NSArray.</param>
public NSArray(params NSObject[] a) => array = new List<NSObject>(a);
/// <summary>
/// Returns the array of NSObjects represented by this NSArray. Any changes to the values of this array will also
/// affect the NSArray.
/// </summary>
/// <returns>The actual array represented by this NSArray.</returns>
[Obsolete]
public NSObject[] GetArray() => array.ToArray();
/// <summary>Returns the size of the array.</summary>
/// <value>The number of elements that this array can store.</value>
public int Count => array.Count;
/// <summary>Checks whether an object is present in the array or whether it is equal to any of the objects in the array.</summary>
/// <returns><c>true</c>, when the object could be found. <c>false</c> otherwise.</returns>
/// <param name="obj">The object to look for.</param>
[Obsolete]
public bool ContainsObject(object obj)
{
NSObject nso = Wrap(obj);
/// <summary>Returns the object stored at the given index.</summary>
/// <returns>The object at the given index.</returns>
/// <param name="i">The index of the object.</param>
[Obsolete]
public NSObject ObjectAtIndex(int i) => array[i];
foreach(NSObject elem in array)
if(elem.Equals(nso)) return true;
/// <summary>Remove the i-th element from the array. The array will be resized.</summary>
/// <param name="i">The index of the object</param>
[Obsolete]
public void Remove(int i) => array.RemoveAt(i);
return false;
}
/// <summary>Stores an object at the specified index. If there was another object stored at that index it will be replaced.</summary>
/// <param name="key">The index where to store the object.</param>
/// <param name="value">The object.</param>
[Obsolete]
public void SetValue(int key, object value)
/// <summary>
/// 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.
/// </summary>
/// <returns>The index of the object, if it was found. -1 otherwise.</returns>
/// <param name="obj">The object to look for.</param>
[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;
}
/// <summary>
/// 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 <b>identical</b> to the given one. Thus objects that might contain the same value as the
/// given one will not be considered.
/// </summary>
/// <returns>The index of the object, if it was found. -1 otherwise.</returns>
/// <param name="obj">The object to look for.</param>
[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;
}
/// <summary>Returns the last object contained in this array.</summary>
/// <returns>The value of the highest index in the array.</returns>
public NSObject LastObject() => array[array.Count - 1];
/// <summary>
/// Returns a new array containing only the values stored at the given indices. The values are sorted by their
/// index.
/// </summary>
/// <returns>The new array containing the objects stored at the given indices.</returns>
/// <param name="indexes">The indices of the objects.</param>
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;
}
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />; otherwise, <c>false</c>.
/// </returns>
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;
}
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSArray" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
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("<array>");
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);
}
/// <summary>
/// Returns the array of NSObjects represented by this NSArray. Any changes to the values of this array will also
/// affect the NSArray.
/// </summary>
/// <returns>The actual array represented by this NSArray.</returns>
[Obsolete]
public NSObject[] GetArray() => array.ToArray();
/// <summary>Checks whether an object is present in the array or whether it is equal to any of the objects in the array.</summary>
/// <returns><c>true</c>, when the object could be found. <c>false</c> otherwise.</returns>
/// <param name="obj">The object to look for.</param>
[Obsolete]
public bool ContainsObject(object obj)
{
NSObject nso = Wrap(obj);
foreach(NSObject elem in array)
if(elem.Equals(nso))
return true;
return false;
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The index of the object, if it was found. -1 otherwise.</returns>
/// <param name="obj">The object to look for.</param>
[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;
}
/// <summary>
/// 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 <b>identical</b> to the given one. Thus objects that might contain the same value as the
/// given one will not be considered.
/// </summary>
/// <returns>The index of the object, if it was found. -1 otherwise.</returns>
/// <param name="obj">The object to look for.</param>
[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;
}
/// <summary>Returns the last object contained in this array.</summary>
/// <returns>The value of the highest index in the array.</returns>
public NSObject LastObject() => array[array.Count - 1];
/// <summary>
/// Returns a new array containing only the values stored at the given indices. The values are sorted by their
/// index.
/// </summary>
/// <returns>The new array containing the objects stored at the given indices.</returns>
/// <param name="indexes">The indices of the objects.</param>
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;
}
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />; otherwise, <c>false</c>.
/// </returns>
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;
}
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSArray" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
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("<array>");
o.ToXml(xml, level + 1);
xml.Append(NEWLINE);
}
foreach(NSObject o in array)
Indent(xml, level);
xml.Append("</array>");
}
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));
}
/// <summary>
/// <para>Generates a valid ASCII property list which has this NSArray as its root object.</para>
/// <para>
/// 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.
/// </para>
/// </summary>
/// <returns>ASCII representation of this object.</returns>
public string ToASCIIPropertyList()
{
var ascii = new StringBuilder();
ToASCII(ascii, 0);
ascii.Append(NEWLINE);
return ascii.ToString();
}
/// <summary>
/// <para>Generates a valid ASCII property list in GnuStep format which has this NSArray as its root object.</para>
/// <para>
/// 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.
/// </para>
/// </summary>
/// <returns>GnuStep ASCII representation of this object.</returns>
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("</array>");
}
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));
}
/// <summary>
/// <para>Generates a valid ASCII property list which has this NSArray as its root object.</para>
/// <para>
/// 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.
/// </para>
/// </summary>
/// <returns>ASCII representation of this object.</returns>
public string ToASCIIPropertyList()
{
var ascii = new StringBuilder();
ToASCII(ascii, 0);
ascii.Append(NEWLINE);
return ascii.ToString();
}
/// <summary>
/// <para>Generates a valid ASCII property list in GnuStep format which has this NSArray as its root object.</para>
/// <para>
/// 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.
/// </para>
/// </summary>
/// <returns>GnuStep ASCII representation of this object.</returns>
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;
}
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />; otherwise, <c>false</c>.
/// </returns>
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;
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />; otherwise, <c>false</c>.
/// </returns>
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;
}
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,158 +27,161 @@ using System;
using System.IO;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>NSData objects are wrappers for byte buffers</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSData : NSObject
{
/// <summary>NSData objects are wrappers for byte buffers</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSData : NSObject
// In the XML property list format, the base-64 encoded data is split across multiple lines.
// Each line contains 68 characters.
const int DataLineLength = 68;
/// <summary>Creates the NSData object from the binary representation of it.</summary>
/// <param name="bytes">The raw data contained in the NSData object.</param>
public NSData(byte[] bytes) => Bytes = bytes;
/// <summary>Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes.</summary>
/// <param name="base64">The Base64 encoded contents of the NSData object.</param>
/// <exception cref="FormatException">When the given string is not a proper Base64 formatted string.</exception>
public NSData(string base64) => Bytes = Convert.FromBase64String(base64);
/// <summary>Creates a NSData object from a file. Using the files contents as the contents of this NSData object.</summary>
/// <param name="file">The file containing the data.</param>
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
/// <exception cref="IOException">If the file could not be read.</exception>
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];
/// <summary>Creates the NSData object from the binary representation of it.</summary>
/// <param name="bytes">The raw data contained in the NSData object.</param>
public NSData(byte[] bytes) => Bytes = bytes;
using FileStream raf = file.OpenRead();
/// <summary>Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes.</summary>
/// <param name="base64">The Base64 encoded contents of the NSData object.</param>
/// <exception cref="FormatException">When the given string is not a proper Base64 formatted string.</exception>
public NSData(string base64) => Bytes = Convert.FromBase64String(base64);
/// <summary>Creates a NSData object from a file. Using the files contents as the contents of this NSData object.</summary>
/// <param name="file">The file containing the data.</param>
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
/// <exception cref="IOException">If the file could not be read.</exception>
public NSData(FileInfo file)
{
Bytes = new byte[(int)file.Length];
using FileStream raf = file.OpenRead();
raf.Read(Bytes, 0, (int)file.Length);
}
/// <summary>The bytes contained in this NSData object.</summary>
/// <value>The data as bytes</value>
public byte[] Bytes { get; }
/// <summary>Gets the amount of data stored in this object.</summary>
/// <value>The number of bytes contained in this object.</value>
public int Length => Bytes.Length;
/// <summary>Loads the bytes from this NSData object into a byte buffer.</summary>
/// <param name="buf">The byte buffer which will contain the data</param>
/// <param name="length">The amount of data to copy</param>
public void GetBytes(MemoryStream buf, int length) => buf.Write(Bytes, 0, Math.Min(Bytes.Length, length));
/// <summary>Loads the bytes from this NSData object into a byte buffer.</summary>
/// <param name="buf">The byte buffer which will contain the data</param>
/// <param name="rangeStart">The start index.</param>
/// <param name="rangeStop">The stop index.</param>
public void GetBytes(MemoryStream buf, int rangeStart, int rangeStop) =>
buf.Write(Bytes, rangeStart, Math.Min(Bytes.Length, rangeStop));
/// <summary>Gets the Base64 encoded data contained in this NSData object.</summary>
/// <returns>The Base64 encoded data as a <c>string</c>.</returns>
public string GetBase64EncodedData() => Convert.ToBase64String(Bytes);
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj) =>
obj.GetType().Equals(GetType()) && ArrayEquals(((NSData)obj).Bytes, Bytes);
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSData" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
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("<data>");
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("</data>");
}
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);
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>.
/// </returns>
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);
#if NET7_0_OR_GREATER
raf.ReadExactly(Bytes, 0, (int)file.Length);
#else
raf.Read(Bytes, 0, (int)file.Length);
#endif
}
/// <summary>The bytes contained in this NSData object.</summary>
/// <value>The data as bytes</value>
public byte[] Bytes { get; }
/// <summary>Gets the amount of data stored in this object.</summary>
/// <value>The number of bytes contained in this object.</value>
public int Length => Bytes.Length;
/// <summary>Loads the bytes from this NSData object into a byte buffer.</summary>
/// <param name="buf">The byte buffer which will contain the data</param>
/// <param name="length">The amount of data to copy</param>
public void GetBytes(MemoryStream buf, int length) => buf.Write(Bytes, 0, Math.Min(Bytes.Length, length));
/// <summary>Loads the bytes from this NSData object into a byte buffer.</summary>
/// <param name="buf">The byte buffer which will contain the data</param>
/// <param name="rangeStart">The start index.</param>
/// <param name="rangeStop">The stop index.</param>
public void GetBytes(MemoryStream buf, int rangeStart, int rangeStop) =>
buf.Write(Bytes, rangeStart, Math.Min(Bytes.Length, rangeStop));
/// <summary>Gets the Base64 encoded data contained in this NSData object.</summary>
/// <returns>The Base64 encoded data as a <c>string</c>.</returns>
public string GetBase64EncodedData() => Convert.ToBase64String(Bytes);
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj) =>
obj.GetType().Equals(GetType()) && ArrayEquals(((NSData)obj).Bytes, Bytes);
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSData" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
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("<data>");
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("</data>");
}
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);
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>.
/// </returns>
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);
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,151 +27,149 @@ using System;
using System.Globalization;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>Represents a date</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSDate : NSObject
{
/// <summary>Represents a date</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSDate : NSObject
static readonly DateTime EPOCH = new(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// 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;
/// <summary>Creates a date from its binary representation.</summary>
/// <param name="bytes">bytes The date bytes</param>
public NSDate(ReadOnlySpan<byte> bytes) =>
//dates are 8 byte big-endian double, seconds since the epoch
Date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes));
/// <summary>
/// Parses a date from its textual representation. That representation has the following pattern:
/// <code>yyyy-MM-dd'T'HH:mm:ss'Z'</code>
/// </summary>
/// <param name="textRepresentation">The textual representation of the date (ISO 8601 format)</param>
/// <exception cref="FormatException">When the date could not be parsed, i.e. it does not match the expected pattern.</exception>
public NSDate(string textRepresentation) => Date = ParseDateString(textRepresentation);
/// <summary>Creates a NSDate from a .NET DateTime</summary>
/// <param name="d">The date</param>
public NSDate(DateTime d) => Date = d;
/// <summary>Gets the date.</summary>
/// <returns>The date.</returns>
public DateTime Date { get; }
/// <summary>Parses the XML date string and creates a .NET DateTime object from it.</summary>
/// <returns>The parsed Date</returns>
/// <param name="textRepresentation">The date string as found in the XML property list</param>
/// <exception cref="FormatException">Given string cannot be parsed</exception>
static DateTime ParseDateString(string textRepresentation) =>
DateTime.ParseExact(textRepresentation, sdfAll, provider, DateTimeStyles.None);
/// <summary>
/// Generates a String representation of a .NET DateTime object. The string is formatted according to the
/// specification for XML property list dates.
/// </summary>
/// <param name="date">The date which should be represented.</param>
/// <returns>The string representation of the date.</returns>
public static string MakeDateString(DateTime date) => date.ToUniversalTime().ToString(sdfDefault, provider);
/// <summary>
/// Generates a String representation of a .NET DateTime object. The string is formatted according to the
/// specification for GnuStep ASCII property list dates.
/// </summary>
/// <param name="date">The date which should be represented.</param>
/// <returns>The string representation of the date.</returns>
static string MakeDateStringGnuStep(DateTime date) => date.ToString(sdfGnuStep, provider);
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj) => obj.GetType().Equals(GetType()) && Date.Equals(((NSDate)obj).Date);
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSDate" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
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;
/// <summary>Creates a date from its binary representation.</summary>
/// <param name="bytes">bytes The date bytes</param>
public NSDate(ReadOnlySpan<byte> bytes) =>
//dates are 8 byte big-endian double, seconds since the epoch
Date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes));
/// <summary>
/// Parses a date from its textual representation. That representation has the following pattern:
/// <code>yyyy-MM-dd'T'HH:mm:ss'Z'</code>
/// </summary>
/// <param name="textRepresentation">The textual representation of the date (ISO 8601 format)</param>
/// <exception cref="FormatException">When the date could not be parsed, i.e. it does not match the expected pattern.</exception>
public NSDate(string textRepresentation) => Date = ParseDateString(textRepresentation);
/// <summary>Creates a NSDate from a .NET DateTime</summary>
/// <param name="d">The date</param>
public NSDate(DateTime d) => Date = d;
/// <summary>Gets the date.</summary>
/// <returns>The date.</returns>
public DateTime Date { get; }
/// <summary>Parses the XML date string and creates a .NET DateTime object from it.</summary>
/// <returns>The parsed Date</returns>
/// <param name="textRepresentation">The date string as found in the XML property list</param>
/// <exception cref="FormatException">Given string cannot be parsed</exception>
static DateTime ParseDateString(string textRepresentation) =>
DateTime.ParseExact(textRepresentation, sdfAll, provider, DateTimeStyles.None);
/// <summary>
/// Generates a String representation of a .NET DateTime object. The string is formatted according to the
/// specification for XML property list dates.
/// </summary>
/// <param name="date">The date which should be represented.</param>
/// <returns>The string representation of the date.</returns>
public static string MakeDateString(DateTime date) => date.ToUniversalTime().ToString(sdfDefault, provider);
/// <summary>
/// Generates a String representation of a .NET DateTime object. The string is formatted according to the
/// specification for GnuStep ASCII property list dates.
/// </summary>
/// <param name="date">The date which should be represented.</param>
/// <returns>The string representation of the date.</returns>
static string MakeDateStringGnuStep(DateTime date) => date.ToString(sdfGnuStep, provider);
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj) => obj.GetType().Equals(GetType()) && Date.Equals(((NSDate)obj).Date);
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSDate" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() => Date.GetHashCode();
internal override void ToXml(StringBuilder xml, int level)
{
Indent(xml, level);
xml.Append("<date>");
xml.Append(MakeDateString(Date));
xml.Append("</date>");
}
internal override void ToBinary(BinaryPropertyListWriter outPlist)
{
outPlist.Write(0x33);
outPlist.WriteDouble((Date - EPOCH).TotalSeconds);
}
/// <summary>Generates a string representation of the date.</summary>
/// <returns>A string representation of the date.</returns>
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(">");
}
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />; otherwise, <c>false</c>.
/// </returns>
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("<date>");
xml.Append(MakeDateString(Date));
xml.Append("</date>");
}
internal override void ToBinary(BinaryPropertyListWriter outPlist)
{
outPlist.Write(0x33);
outPlist.WriteDouble((Date - EPOCH).TotalSeconds);
}
/// <summary>Generates a string representation of the date.</summary>
/// <returns>A string representation of the date.</returns>
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(">");
}
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />; otherwise, <c>false</c>.
/// </returns>
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);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,197 +27,183 @@ using System;
using System.Globalization;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>A number whose value is either an integer, a real number or bool.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSNumber : NSObject, IComparable
{
/// <summary>A number whose value is either an integer, a real number or bool.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSNumber : NSObject, IComparable
/// <summary>
/// Indicates that the number's value is an integer. The number is stored as a .NET <see cref="long" />. Its
/// original value could have been char, short, int, long or even long long.
/// </summary>
public const int INTEGER = 0;
/// <summary>
/// Indicates that the number's value is a real number. The number is stored as a .NET <see cref="double" />. Its
/// original value could have been float or double.
/// </summary>
public const int REAL = 1;
/// <summary>Indicates that the number's value is bool.</summary>
public const int BOOLEAN = 2;
readonly bool boolValue;
readonly double doubleValue;
readonly long longValue;
//Holds the current type of this number
readonly int type;
/// <summary>
/// Parses integers and real numbers from their binary representation.
/// <i>Note: real numbers are not yet supported.</i>
/// </summary>
/// <param name="bytes">The binary representation</param>
/// <param name="type">The type of number</param>
/// <seealso cref="INTEGER" />
/// <seealso cref="REAL" />
public NSNumber(ReadOnlySpan<byte> bytes, int type)
{
/// <summary>
/// Indicates that the number's value is an integer. The number is stored as a .NET <see cref="long" />. Its
/// original value could have been char, short, int, long or even long long.
/// </summary>
public const int INTEGER = 0;
/// <summary>
/// Indicates that the number's value is a real number. The number is stored as a .NET <see cref="double" />. Its
/// original value could have been float or double.
/// </summary>
public const int REAL = 1;
/// <summary>Indicates that the number's value is bool.</summary>
public const int BOOLEAN = 2;
readonly bool boolValue;
readonly double doubleValue;
readonly long longValue;
//Holds the current type of this number
readonly int type;
/// <summary>
/// Parses integers and real numbers from their binary representation.
/// <i>Note: real numbers are not yet supported.</i>
/// </summary>
/// <param name="bytes">The binary representation</param>
/// <param name="type">The type of number</param>
/// <seealso cref="INTEGER" />
/// <seealso cref="REAL" />
public NSNumber(ReadOnlySpan<byte> 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;
}
/// <summary>Creates a number from its textual representation.</summary>
/// <param name="text">The textual representation of the number.</param>
/// <seealso cref="bool.Parse(string)" />
/// <seealso cref="long.Parse(string)" />
/// <seealso cref="double.Parse(string, IFormatProvider)" />
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));
}
/// <summary>Creates an integer number.</summary>
/// <param name="i">The integer value.</param>
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.");
}
}
/// <summary>Creates an integer number.</summary>
/// <param name="l">The long integer value.</param>
public NSNumber(long l)
this.type = type;
}
/// <summary>Creates a number from its textual representation.</summary>
/// <param name="text">The textual representation of the number.</param>
/// <seealso cref="bool.Parse(string)" />
/// <seealso cref="long.Parse(string)" />
/// <seealso cref="double.Parse(string, IFormatProvider)" />
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;
}
/// <summary>Creates a real number.</summary>
/// <param name="d">The real value.</param>
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;
}
/// <summary>Creates a bool number.</summary>
/// <param name="b">The bool value.</param>
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;
}
/// <summary>Compares the current <see cref="Claunia.PropertyList.NSNumber" /> to the specified object.</summary>
/// <returns>
/// 0 if the numbers are equal, 1 if the current <see cref="Claunia.PropertyList.NSNumber" /> is greater than the
/// argument and -1 if it is less, or the argument is not a number.
/// </returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSNumber" />.</param>
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;
/// <summary>Creates an integer number.</summary>
/// <param name="i">The integer value.</param>
public NSNumber(int i)
{
doubleValue = longValue = i;
type = INTEGER;
}
y = GetDoubleFromObject(o);
/// <summary>Creates an integer number.</summary>
/// <param name="l">The long integer value.</param>
public NSNumber(long l)
{
doubleValue = longValue = l;
type = INTEGER;
}
/// <summary>Creates a real number.</summary>
/// <param name="d">The real value.</param>
public NSNumber(double d)
{
longValue = (long)(doubleValue = d);
type = REAL;
}
/// <summary>Creates a bool number.</summary>
/// <param name="b">The bool value.</param>
public NSNumber(bool b)
{
boolValue = b;
doubleValue = longValue = b ? 1 : 0;
type = BOOLEAN;
}
/// <summary>Compares the current <see cref="Claunia.PropertyList.NSNumber" /> to the specified object.</summary>
/// <returns>
/// 0 if the numbers are equal, 1 if the current <see cref="Claunia.PropertyList.NSNumber" /> is greater than the
/// argument and -1 if it is less, or the argument is not a number.
/// </returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSNumber" />.</param>
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;
}
/// <summary>Gets the type of this number's value.</summary>
/// <returns>The type flag.</returns>
/// <seealso cref="BOOLEAN" />
/// <seealso cref="INTEGER" />
/// <seealso cref="REAL" />
public int GetNSNumberType() => type;
if(!IsNumber(o)) return -1;
/// <summary>Checks whether the value of this NSNumber is a bool.</summary>
/// <returns>Whether the number's value is a bool.</returns>
public bool isBoolean() => type == BOOLEAN;
y = GetDoubleFromObject(o);
/// <summary>Checks whether the value of this NSNumber is an integer.</summary>
/// <returns>Whether the number's value is an integer.</returns>
public bool isInteger() => type == INTEGER;
/// <summary>Checks whether the value of this NSNumber is a real number.</summary>
/// <returns>Whether the number's value is a real number.</returns>
public bool isReal() => type == REAL;
/// <summary>The number's bool value.</summary>
/// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns>
public bool ToBool()
{
if(type == BOOLEAN)
return boolValue;
return longValue != 0;
}
/// <summary>The number's long value.</summary>
/// <returns>The value of the number as long</returns>
public long ToLong() => longValue;
/// <summary>
/// The number's int value.
/// <i>
/// 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.
/// </i>
/// </summary>
/// <returns>The value of the number as int.</returns>
public int ToInt() => (int)longValue;
/// <summary>The number's double value.</summary>
/// <returns>The value of the number as double.</returns>
public double ToDouble() => doubleValue;
/// <summary>The number's float value. WARNING: Possible loss of precision if the value is outside the float range.</summary>
/// <returns>The value of the number as float.</returns>
public float floatValue() => (float)doubleValue;
/// <summary>Checks whether the other object is a NSNumber of the same value.</summary>
/// <param name="obj">The object to compare to.</param>
/// <returns>Whether the objects are equal in terms of numeric value and type.</returns>
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;
}
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSNumber" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
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;
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents the current
/// <see cref="Claunia.PropertyList.NSNumber" />.
/// </summary>
/// <returns>A <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />.</returns>
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("<integer>");
xml.Append(ToLong());
xml.Append("</integer>");
break;
}
case REAL:
{
xml.Append("<real>");
if(doubleValue == 0)
xml.Append("0.0");
else
xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture));
xml.Append("</real>");
break;
}
case BOOLEAN:
{
xml.Append(ToBool() ? "<true/>" : "<false/>");
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;
}
}
}
/// <summary>Determines if an object is a number. Substitutes .NET's Number class comparison</summary>
/// <returns><c>true</c> if it is a number.</returns>
/// <param name="o">Object.</param>
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
};
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSNumber" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSNumber" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSNumber" />; otherwise, <c>false</c>.
/// </returns>
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;
}
/// <summary>Gets the type of this number's value.</summary>
/// <returns>The type flag.</returns>
/// <seealso cref="BOOLEAN" />
/// <seealso cref="INTEGER" />
/// <seealso cref="REAL" />
public int GetNSNumberType() => type;
/// <summary>Checks whether the value of this NSNumber is a bool.</summary>
/// <returns>Whether the number's value is a bool.</returns>
public bool isBoolean() => type == BOOLEAN;
/// <summary>Checks whether the value of this NSNumber is an integer.</summary>
/// <returns>Whether the number's value is an integer.</returns>
public bool isInteger() => type == INTEGER;
/// <summary>Checks whether the value of this NSNumber is a real number.</summary>
/// <returns>Whether the number's value is a real number.</returns>
public bool isReal() => type == REAL;
/// <summary>The number's bool value.</summary>
/// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns>
public bool ToBool()
{
if(type == BOOLEAN) return boolValue;
return longValue != 0;
}
/// <summary>The number's long value.</summary>
/// <returns>The value of the number as long</returns>
public long ToLong() => longValue;
/// <summary>
/// The number's int value.
/// <i>
/// 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.
/// </i>
/// </summary>
/// <returns>The value of the number as int.</returns>
public int ToInt() => (int)longValue;
/// <summary>The number's double value.</summary>
/// <returns>The value of the number as double.</returns>
public double ToDouble() => doubleValue;
/// <summary>The number's float value. WARNING: Possible loss of precision if the value is outside the float range.</summary>
/// <returns>The value of the number as float.</returns>
public float floatValue() => (float)doubleValue;
/// <summary>Checks whether the other object is a NSNumber of the same value.</summary>
/// <param name="obj">The object to compare to.</param>
/// <returns>Whether the objects are equal in terms of numeric value and type.</returns>
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;
}
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSNumber" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
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;
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents the current
/// <see cref="Claunia.PropertyList.NSNumber" />.
/// </summary>
/// <returns>A <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />.</returns>
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("<integer>");
xml.Append(ToLong());
xml.Append("</integer>");
break;
}
case REAL:
{
xml.Append("<real>");
if(doubleValue == 0)
xml.Append("0.0");
else
xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture));
xml.Append("</real>");
break;
}
case BOOLEAN:
{
xml.Append(ToBool() ? "<true/>" : "<false/>");
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;
}
}
}
/// <summary>Determines if an object is a number. Substitutes .NET's Number class comparison</summary>
/// <returns><c>true</c> if it is a number.</returns>
/// <param name="o">Object.</param>
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
};
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSNumber" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSNumber" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSNumber" />; otherwise, <c>false</c>.
/// </returns>
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);
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,426 +27,401 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>
/// <para>Abstract interface for any object contained in a property list.</para>
/// <para>The names and functions of the various objects orient themselves towards Apple's Cocoa API.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public abstract class NSObject
{
/// <summary>
/// <para>Abstract interface for any object contained in a property list.</para>
/// <para>The names and functions of the various objects orient themselves towards Apple's Cocoa API.</para>
/// 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).
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public abstract class NSObject
internal static readonly string NEWLINE = "\n";
/// <summary>The indentation character used for generating the XML output. This is the tabulator character.</summary>
static readonly string INDENT = "\t";
/// <summary>
/// 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.
/// </summary>
internal static readonly int ASCII_LINE_LENGTH = 80;
/// <summary>Generates the XML representation of the object (without XML headers or enclosing plist-tags).</summary>
/// <param name="xml">The StringBuilder onto which the XML representation is appended.</param>
/// <param name="level">The indentation level of the object.</param>
internal abstract void ToXml(StringBuilder xml, int level);
/// <summary>Assigns IDs to all the objects in this NSObject subtree.</summary>
/// <param name="outPlist">The writer object that handles the binary serialization.</param>
internal virtual void AssignIDs(BinaryPropertyListWriter outPlist) => outPlist.AssignID(this);
/// <summary>Generates the binary representation of the object.</summary>
/// <param name="outPlist">The output stream to serialize the object to.</param>
internal abstract void ToBinary(BinaryPropertyListWriter outPlist);
/// <summary>Generates a valid XML property list including headers using this object as root.</summary>
/// <returns>The XML representation of the property list including XML header and doctype information.</returns>
public string ToXmlPropertyList()
{
/// <summary>
/// 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).
/// </summary>
internal static readonly string NEWLINE = "\n";
var xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
xml.Append(NEWLINE);
xml.Append("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
xml.Append(NEWLINE);
xml.Append("<plist version=\"1.0\">");
xml.Append(NEWLINE);
ToXml(xml, 0);
xml.Append(NEWLINE);
xml.Append("</plist>");
xml.Append(NEWLINE);
/// <summary>The indentation character used for generating the XML output. This is the tabulator character.</summary>
static readonly string INDENT = "\t";
return xml.ToString();
}
/// <summary>
/// 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.
/// </summary>
internal static readonly int ASCII_LINE_LENGTH = 80;
/// <summary>
/// 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
/// </summary>
/// <param name="ascii">The StringBuilder onto which the ASCII representation is appended.</param>
/// <param name="level">The indentation level of the object.</param>
internal abstract void ToASCII(StringBuilder ascii, int level);
/// <summary>Generates the XML representation of the object (without XML headers or enclosing plist-tags).</summary>
/// <param name="xml">The StringBuilder onto which the XML representation is appended.</param>
/// <param name="level">The indentation level of the object.</param>
internal abstract void ToXml(StringBuilder xml, int level);
/// <summary>
/// Generates the ASCII representation of this object in the GnuStep format. The generated ASCII representation
/// does not end with a newline.
/// </summary>
/// <param name="ascii">The StringBuilder onto which the ASCII representation is appended.</param>
/// <param name="level">The indentation level of the object.</param>
internal abstract void ToASCIIGnuStep(StringBuilder ascii, int level);
/// <summary>Assigns IDs to all the objects in this NSObject subtree.</summary>
/// <param name="outPlist">The writer object that handles the binary serialization.</param>
internal virtual void AssignIDs(BinaryPropertyListWriter outPlist) => outPlist.AssignID(this);
/// <summary>
/// Helper method that adds correct indentation to the xml output. Calling this method will add <c>level</c>
/// number of tab characters to the <c>xml</c> string.
/// </summary>
/// <param name="xml">The string builder for the XML document.</param>
/// <param name="level">The level of indentation.</param>
internal static void Indent(StringBuilder xml, int level)
{
for(int i = 0; i < level; i++) xml.Append(INDENT);
}
/// <summary>Generates the binary representation of the object.</summary>
/// <param name="outPlist">The output stream to serialize the object to.</param>
internal abstract void ToBinary(BinaryPropertyListWriter outPlist);
/// <summary>Wraps the given value inside a NSObject.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(long value) => new(value);
/// <summary>Generates a valid XML property list including headers using this object as root.</summary>
/// <returns>The XML representation of the property list including XML header and doctype information.</returns>
public string ToXmlPropertyList()
/// <summary>Wraps the given value inside a NSObject.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(double value) => new(value);
/// <summary>Wraps the given value inside a NSObject.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(bool value) => new(value);
/// <summary>Wraps the given value inside a NSObject.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
public static NSData Wrap(byte[] value) => new(value);
/// <summary>Creates a NSArray with the contents of the given array.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the objects contained in the array cannot be represented by a NSObject.</exception>
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;
}
/// <summary>Creates a NSDictionary with the contents of the given map.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the values contained in the map cannot be represented by a NSObject.</exception>
public static NSDictionary Wrap(Dictionary<string, object> value)
{
var dict = new NSDictionary();
foreach(KeyValuePair<string, object> kvp in value) dict.Add(kvp.Key, Wrap(kvp.Value));
return dict;
}
/// <summary>Creates a NSSet with the contents of this set.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the values contained in the map cannot be represented by a NSObject.</exception>
public static NSSet Wrap(List<object> value)
{
var set = new NSSet();
foreach(object o in value) set.AddObject(Wrap(o));
return set;
}
/// <summary>
/// <para>Creates a NSObject representing the given .NET Object.</para>
/// <para>
/// Numerics of type <see cref="bool" />, <see cref="int" />, <see cref="long" />, <see cref="short" />,
/// <see cref="byte" />, <see cref="float" /> or <see cref="double" /> are wrapped as NSNumber objects.
/// </para>
/// <para>Strings are wrapped as <see cref="NSString" /> objects and byte arrays as <see cref="NSData" /> objects.</para>
/// <para>DateTime objects are wrapped as <see cref="NSDate" /> objects.</para>
/// <para>Serializable classes are serialized and their data is stored in <see cref="NSData" /> objects.</para>
/// <para>
/// Arrays and Collection objects are converted to <see cref="NSArray" /> where each array member is wrapped into
/// a <see cref="NSObject" />.
/// </para>
/// <para>
/// Dictionaries are converted to <see cref="NSDictionary" />. Each key is converted to a string and each value
/// wrapped into a <see cref="NSObject" />.
/// </para>
/// </summary>
/// <param name="o">The object to represent.</param>
/// <returns>A NSObject equivalent to the given object.</returns>
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 version=\"1.0\" encoding=\"UTF-8\"?>");
xml.Append(NEWLINE);
xml.Append("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
xml.Append(NEWLINE);
xml.Append("<plist version=\"1.0\">");
xml.Append(NEWLINE);
ToXml(xml, 0);
xml.Append(NEWLINE);
xml.Append("</plist>");
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);
}
/// <summary>
/// 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
/// </summary>
/// <param name="ascii">The StringBuilder onto which the ASCII representation is appended.</param>
/// <param name="level">The indentation level of the object.</param>
internal abstract void ToASCII(StringBuilder ascii, int level);
/// <summary>
/// Generates the ASCII representation of this object in the GnuStep format. The generated ASCII representation
/// does not end with a newline.
/// </summary>
/// <param name="ascii">The StringBuilder onto which the ASCII representation is appended.</param>
/// <param name="level">The indentation level of the object.</param>
internal abstract void ToASCIIGnuStep(StringBuilder ascii, int level);
/// <summary>
/// Helper method that adds correct indentation to the xml output. Calling this method will add <c>level</c>
/// number of tab characters to the <c>xml</c> string.
/// </summary>
/// <param name="xml">The string builder for the XML document.</param>
/// <param name="level">The level of indentation.</param>
internal static void Indent(StringBuilder xml, int level)
if(typeof(Dictionary<string, object>).IsAssignableFrom(c))
{
for(int i = 0; i < level; i++)
xml.Append(INDENT);
}
var netDict = (Dictionary<string, object>)o;
var dict = new NSDictionary();
/// <summary>Wraps the given value inside a NSObject.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(long value) => new(value);
/// <summary>Wraps the given value inside a NSObject.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(double value) => new(value);
/// <summary>Wraps the given value inside a NSObject.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(bool value) => new(value);
/// <summary>Wraps the given value inside a NSObject.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
public static NSData Wrap(byte[] value) => new(value);
/// <summary>Creates a NSArray with the contents of the given array.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the objects contained in the array cannot be represented by a NSObject.</exception>
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;
}
/// <summary>Creates a NSDictionary with the contents of the given map.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the values contained in the map cannot be represented by a NSObject.</exception>
public static NSDictionary Wrap(Dictionary<string, object> value)
{
var dict = new NSDictionary();
foreach(KeyValuePair<string, object> kvp in value)
dict.Add(kvp.Key, Wrap(kvp.Value));
foreach(KeyValuePair<string, object> kvp in netDict) dict.Add(kvp.Key, Wrap(kvp.Value));
return dict;
}
/// <summary>Creates a NSSet with the contents of this set.</summary>
/// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the values contained in the map cannot be represented by a NSObject.</exception>
public static NSSet Wrap(List<object> value)
if(typeof(List<object>).IsAssignableFrom(c)) return Wrap(((List<object>)o).ToArray());
if(typeof(List<Dictionary<string, object>>).IsAssignableFrom(c))
{
var set = new NSSet();
var list = new NSArray();
foreach(Dictionary<string, object> dict in (List<Dictionary<string, object>>)o) list.Add(Wrap(dict));
foreach(object o in value)
set.AddObject(Wrap(o));
return set;
return list;
}
/// <summary>
/// <para>Creates a NSObject representing the given .NET Object.</para>
/// <para>
/// Numerics of type <see cref="bool" />, <see cref="int" />, <see cref="long" />, <see cref="short" />,
/// <see cref="byte" />, <see cref="float" /> or <see cref="double" /> are wrapped as NSNumber objects.
/// </para>
/// <para>Strings are wrapped as <see cref="NSString" /> objects and byte arrays as <see cref="NSData" /> objects.</para>
/// <para>DateTime objects are wrapped as <see cref="NSDate" /> objects.</para>
/// <para>Serializable classes are serialized and their data is stored in <see cref="NSData" /> objects.</para>
/// <para>
/// Arrays and Collection objects are converted to <see cref="NSArray" /> where each array member is wrapped into
/// a <see cref="NSObject" />.
/// </para>
/// <para>
/// Dictionaries are converted to <see cref="NSDictionary" />. Each key is converted to a string and each value
/// wrapped into a <see cref="NSObject" />.
/// </para>
/// </summary>
/// <param name="o">The object to represent.</param>
/// <returns>A NSObject equivalent to the given object.</returns>
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<string, object>).IsAssignableFrom(c))
{
Dictionary<string, object> netDict = (Dictionary<string, object>)o;
var dict = new NSDictionary();
foreach(KeyValuePair<string, object> kvp in netDict)
dict.Add(kvp.Key, Wrap(kvp.Value));
return dict;
}
if(typeof(List<object>).IsAssignableFrom(c))
return Wrap(((List<object>)o).ToArray());
if(typeof(List<Dictionary<string, object>>).IsAssignableFrom(c))
{
var list = new NSArray();
foreach(Dictionary<string, object> dict in (List<Dictionary<string, object>>)o)
list.Add(Wrap(dict));
return list;
}
throw new PropertyListException($"Cannot wrap an object of type {o.GetType().Name}.");
}
/// <summary>
/// Converts this NSObject into an equivalent object of the .NET Runtime Environment.
/// <para><see cref="NSArray" /> objects are converted to arrays.</para>
/// <para>
/// <see cref="NSDictionary" /> objects are converted to objects extending the
/// <see cref="Dictionary{TKey, TValue}" /> class.
/// </para>
/// <para><see cref="NSSet" /> objects are converted to objects extending the <see cref="List{NSObject}" /> class.</para>
/// <para>
/// <see cref="NSNumber" /> objects are converted to primitive number values (<see cref="int" />,
/// <see cref="long" />, <see cref="double" /> or <see cref="bool" />).
/// </para>
/// <para><see cref="NSString" /> objects are converted to <see cref="string" /> objects.</para>
/// <para><see cref="NSData" /> objects are converted to <see cref="byte" /> arrays.</para>
/// <para><see cref="NSDate" /> objects are converted to <see cref="System.DateTime" /> objects.</para>
/// <para><see cref="UID" /> objects are converted to <see cref="byte" /> arrays.</para>
/// </summary>
/// <returns>A native .NET object representing this NSObject's value.</returns>
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<string, NSObject> dictA = ((NSDictionary)this).GetDictionary();
Dictionary<string, object> dictB = new(dictA.Count);
foreach(KeyValuePair<string, NSObject> kvp in dictA)
dictB.Add(kvp.Key, kvp.Value.ToObject());
return dictB;
}
case NSSet:
{
List<NSObject> setA = ((NSSet)this).GetSet();
List<object> 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<NSObject> arrayA, IList<NSObject> 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;
}
/// <summary>Determines if the specific NSObject is the same as the NSObject overriding this method.</summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSObject" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSObject" />; otherwise, <c>false</c>.
/// </returns>
public abstract bool Equals(NSObject obj);
throw new PropertyListException($"Cannot wrap an object of type {o.GetType().Name}.");
}
/// <summary>
/// Converts this NSObject into an equivalent object of the .NET Runtime Environment.
/// <para><see cref="NSArray" /> objects are converted to arrays.</para>
/// <para>
/// <see cref="NSDictionary" /> objects are converted to objects extending the
/// <see cref="Dictionary{TKey, TValue}" /> class.
/// </para>
/// <para><see cref="NSSet" /> objects are converted to objects extending the <see cref="List{NSObject}" /> class.</para>
/// <para>
/// <see cref="NSNumber" /> objects are converted to primitive number values (<see cref="int" />,
/// <see cref="long" />, <see cref="double" /> or <see cref="bool" />).
/// </para>
/// <para><see cref="NSString" /> objects are converted to <see cref="string" /> objects.</para>
/// <para><see cref="NSData" /> objects are converted to <see cref="byte" /> arrays.</para>
/// <para><see cref="NSDate" /> objects are converted to <see cref="System.DateTime" /> objects.</para>
/// <para><see cref="UID" /> objects are converted to <see cref="byte" /> arrays.</para>
/// </summary>
/// <returns>A native .NET object representing this NSObject's value.</returns>
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<string, NSObject> dictA = ((NSDictionary)this).GetDictionary();
Dictionary<string, object> dictB = new(dictA.Count);
foreach(KeyValuePair<string, NSObject> kvp in dictA) dictB.Add(kvp.Key, kvp.Value.ToObject());
return dictB;
}
case NSSet:
{
List<NSObject> setA = ((NSSet)this).GetSet();
List<object> 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<NSObject> arrayA, IList<NSObject> 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;
}
/// <summary>Determines if the specific NSObject is the same as the NSObject overriding this method.</summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSObject" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSObject" />; otherwise, <c>false</c>.
/// </returns>
public abstract bool Equals(NSObject obj);
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -28,374 +28,359 @@ using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>
/// <para>A set is an interface to an unordered collection of objects.</para>
/// <para>This implementation uses a <see cref="List{T}" />as the underlying data structure.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSSet : NSObject, IEnumerable
{
/// <summary>
/// <para>A set is an interface to an unordered collection of objects.</para>
/// <para>This implementation uses a <see cref="List{T}" />as the underlying data structure.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSSet : NSObject, IEnumerable
readonly bool ordered;
readonly List<NSObject> set;
/// <summary>Creates an empty unordered set.</summary>
public NSSet() => set = [];
/// <summary>Creates an empty set.</summary>
/// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered)
{
readonly bool ordered;
readonly List<NSObject> set;
this.ordered = ordered;
set = [];
}
/// <summary>Creates an empty unordered set.</summary>
public NSSet() => set = new List<NSObject>();
/// <summary>Creates a set and fill it with the given objects.</summary>
/// <param name="objects">The objects to populate the set.</param>
public NSSet(params NSObject[] objects) => set = [..objects];
/// <summary>Creates an empty set.</summary>
/// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered)
{
this.ordered = ordered;
set = new List<NSObject>();
}
/// <summary>Creates a set and fill it with the given objects.</summary>
/// <param name="objects">The objects to populate the set.</param>
/// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered, params NSObject[] objects)
{
this.ordered = ordered;
set = new List<NSObject>(objects);
/// <summary>Creates a set and fill it with the given objects.</summary>
/// <param name="objects">The objects to populate the set.</param>
public NSSet(params NSObject[] objects) => set = new List<NSObject>(objects);
if(ordered) set.Sort();
}
/// <summary>Creates a set and fill it with the given objects.</summary>
/// <param name="objects">The objects to populate the set.</param>
/// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered, params NSObject[] objects)
{
this.ordered = ordered;
set = new List<NSObject>(objects);
if(ordered)
set.Sort();
}
/// <summary>Gets the number of elements in the set.</summary>
/// <value>The number of elements in the set.</value>
public int Count
{
get
{
lock(set)
return set.Count;
}
}
/// <summary>
/// Returns an enumerator object that lets you iterate over all elements of the set. This is the equivalent to
/// <c>objectEnumerator</c> in the Cocoa implementation of NSSet.
/// </summary>
/// <returns>The iterator for the set.</returns>
public IEnumerator GetEnumerator()
{
lock(set)
return set.GetEnumerator();
}
/// <summary>Adds an object to the set.</summary>
/// <param name="obj">The object to add.</param>
public void AddObject(NSObject obj)
/// <summary>Gets the number of elements in the set.</summary>
/// <value>The number of elements in the set.</value>
public int Count
{
get
{
lock(set)
{
set.Add(obj);
if(ordered)
set.Sort();
return set.Count;
}
}
}
/// <summary>Removes an object from the set.</summary>
/// <param name="obj">The object to remove.</param>
public void RemoveObject(NSObject obj)
/// <summary>
/// Returns an enumerator object that lets you iterate over all elements of the set. This is the equivalent to
/// <c>objectEnumerator</c> in the Cocoa implementation of NSSet.
/// </summary>
/// <returns>The iterator for the set.</returns>
public IEnumerator GetEnumerator()
{
lock(set)
{
lock(set)
{
set.Remove(obj);
if(ordered)
set.Sort();
}
return set.GetEnumerator();
}
}
/// <summary>Returns all objects contained in the set.</summary>
/// <returns>An array of all objects in the set.</returns>
public NSObject[] AllObjects()
/// <summary>Adds an object to the set.</summary>
/// <param name="obj">The object to add.</param>
public void AddObject(NSObject obj)
{
lock(set)
{
lock(set)
return set.ToArray();
set.Add(obj);
if(ordered) set.Sort();
}
}
/// <summary>Returns one of the objects in the set, or <c>null</c> if the set contains no objects.</summary>
/// <returns>The first object in the set, or <c>null</c> if the set is empty.</returns>
public NSObject AnyObject()
/// <summary>Removes an object from the set.</summary>
/// <param name="obj">The object to remove.</param>
public void RemoveObject(NSObject obj)
{
lock(set)
{
lock(set)
return set.Count == 0 ? null : set[0];
set.Remove(obj);
if(ordered) set.Sort();
}
}
/// <summary>Finds out whether a given object is contained in the set.</summary>
/// <returns><c>true</c>, when the object was found, <c>false</c> otherwise.</returns>
/// <param name="obj">The object to look for.</param>
public bool ContainsObject(NSObject obj) => set.Contains(obj);
/// <summary>
/// Determines whether the set contains an object equal to a given object and returns that object if it is
/// present.
/// </summary>
/// <param name="obj">The object to look for.</param>
/// <returns>The object if it is present, <c>null</c> otherwise.</returns>
public NSObject Member(NSObject obj)
/// <summary>Returns all objects contained in the set.</summary>
/// <returns>An array of all objects in the set.</returns>
public NSObject[] AllObjects()
{
lock(set)
{
lock(set)
{
foreach(NSObject o in set)
if(o.Equals(obj))
return o;
return null;
}
return set.ToArray();
}
}
/// <summary>Finds out whether at least one object is present in both sets.</summary>
/// <returns><c>true</c> if the intersection of both sets is empty, <c>false</c> otherwise.</returns>
/// <param name="otherSet">The other set.</param>
public bool IntersectsSet(NSSet otherSet)
/// <summary>Returns one of the objects in the set, or <c>null</c> if the set contains no objects.</summary>
/// <returns>The first object in the set, or <c>null</c> if the set is empty.</returns>
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];
}
}
/// <summary>Finds out if this set is a subset of the given set.</summary>
/// <returns><c>true</c> if all elements in this set are also present in the other set, <c>false</c>otherwise.</returns>
/// <param name="otherSet">The other set.</param>
public bool IsSubsetOfSet(NSSet otherSet)
/// <summary>Finds out whether a given object is contained in the set.</summary>
/// <returns><c>true</c>, when the object was found, <c>false</c> otherwise.</returns>
/// <param name="obj">The object to look for.</param>
public bool ContainsObject(NSObject obj) => set.Contains(obj);
/// <summary>
/// Determines whether the set contains an object equal to a given object and returns that object if it is
/// present.
/// </summary>
/// <param name="obj">The object to look for.</param>
/// <returns>The object if it is present, <c>null</c> otherwise.</returns>
public NSObject Member(NSObject obj)
{
lock(set)
{
lock(set)
{
foreach(NSObject o in set)
if(!otherSet.ContainsObject(o))
return false;
return true;
}
}
/// <summary>Gets the underlying data structure in which this NSSets stores its content.</summary>
/// <returns>A Set object.</returns>
internal List<NSObject> GetSet() => set;
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSSet" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode()
{
int hash = 7;
hash = (29 * hash) + (set != null ? set.GetHashCode() : 0);
return hash;
}
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />; otherwise, <c>false</c>.
/// </returns>
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)));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="xml">The XML StringBuilder</param>
/// <param name="level">The indentation level</param>
internal override void ToXml(StringBuilder xml, int level)
{
Indent(xml, level);
xml.Append("<array>");
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("</array>");
return null;
}
}
internal override void AssignIDs(BinaryPropertyListWriter outPlist)
/// <summary>Finds out whether at least one object is present in both sets.</summary>
/// <returns><c>true</c> if the intersection of both sets is empty, <c>false</c> otherwise.</returns>
/// <param name="otherSet">The other set.</param>
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)
/// <summary>Finds out if this set is a subset of the given set.</summary>
/// <returns><c>true</c> if all elements in this set are also present in the other set, <c>false</c>otherwise.</returns>
/// <param name="otherSet">The other set.</param>
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));
}
/// <summary>
/// Returns the ASCII representation of this set. There is no official ASCII representation for sets. In this
/// implementation sets are represented as arrays.
/// </summary>
/// <param name="ascii">The ASCII file string builder</param>
/// <param name="level">The indentation level</param>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="ascii">The ASCII file string builder</param>
/// <param name="level">The indentation level</param>
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);
}
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />; otherwise, <c>false</c>.
/// </returns>
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;
}
}
/// <summary>Gets the underlying data structure in which this NSSets stores its content.</summary>
/// <returns>A Set object.</returns>
internal List<NSObject> GetSet() => set;
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSSet" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode()
{
int hash = 7;
hash = 29 * hash + (set != null ? set.GetHashCode() : 0);
return hash;
}
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />; otherwise, <c>false</c>.
/// </returns>
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)));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="xml">The XML StringBuilder</param>
/// <param name="level">The indentation level</param>
internal override void ToXml(StringBuilder xml, int level)
{
Indent(xml, level);
xml.Append("<array>");
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("</array>");
}
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));
}
/// <summary>
/// Returns the ASCII representation of this set. There is no official ASCII representation for sets. In this
/// implementation sets are represented as arrays.
/// </summary>
/// <param name="ascii">The ASCII file string builder</param>
/// <param name="level">The indentation level</param>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="ascii">The ASCII file string builder</param>
/// <param name="level">The indentation level</param>
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);
}
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />; otherwise, <c>false</c>.
/// </returns>
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;
}
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,237 +27,240 @@ using System;
using System.Security;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>A NSString contains a string.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSString : NSObject, IComparable
{
/// <summary>A NSString contains a string.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class NSString : NSObject, IComparable
static Encoding asciiEncoder, utf16beEncoder, utf8Encoder;
/// <summary>Creates a NSString from its binary representation.</summary>
/// <param name="bytes">The binary representation.</param>
/// <param name="encoding">The encoding of the binary representation, the name of a supported charset.</param>
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
public NSString(ReadOnlySpan<byte> bytes, string encoding) : this(bytes, Encoding.GetEncoding(encoding)) {}
/// <summary>Creates a NSString from its binary representation.</summary>
/// <param name="bytes">The binary representation.</param>
/// <param name="encoding">The encoding of the binary representation.</param>
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
public NSString(ReadOnlySpan<byte> bytes, Encoding encoding)
{
static Encoding asciiEncoder, utf16beEncoder, utf8Encoder;
/// <summary>Creates a NSString from its binary representation.</summary>
/// <param name="bytes">The binary representation.</param>
/// <param name="encoding">The encoding of the binary representation, the name of a supported charset.</param>
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
public NSString(ReadOnlySpan<byte> bytes, string encoding) : this(bytes, Encoding.GetEncoding(encoding)) {}
/// <summary>Creates a NSString from its binary representation.</summary>
/// <param name="bytes">The binary representation.</param>
/// <param name="encoding">The encoding of the binary representation.</param>
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
public NSString(ReadOnlySpan<byte> bytes, Encoding encoding)
{
#if NATIVE_SPAN
Content = encoding.GetString(bytes);
#else
Content = encoding.GetString(bytes.ToArray());
#endif
}
/// <summary>Creates a NSString from a string.</summary>
/// <param name="text">The string that will be contained in the NSString.</param>
public NSString(string text) => Content = text;
/// <summary>Gets this strings content.</summary>
/// <returns>This NSString as .NET string object.</returns>
public string Content { get; set; }
/// <summary>Compares the current <see cref="Claunia.PropertyList.NSString" /> to the specified object.</summary>
/// <returns>A 32-bit signed integer that indicates the lexical relationship between the two comparands.</returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param>
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
};
/// <summary>Appends a string to this string.</summary>
/// <param name="s">The string to append.</param>
public void Append(NSString s) => Append(s.Content);
/// <summary>Appends a string to this string.</summary>
/// <param name="s">The string to append.</param>
public void Append(string s) => Content += s;
/// <summary>Prepends a string to this string.</summary>
/// <param name="s">The string to prepend.</param>
public void Prepend(string s) => Content = s + Content;
/// <summary>Prepends a string to this string.</summary>
/// <param name="s">The string to prepend.</param>
public void Prepend(NSString s) => Prepend(s.Content);
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj) => obj is NSString nsString && Content.Equals(nsString.Content);
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSString" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() => Content.GetHashCode();
/// <summary>The textual representation of this NSString.</summary>
/// <returns>The NSString's contents.</returns>
public override string ToString() => Content;
internal override void ToXml(StringBuilder xml, int level)
{
Indent(xml, level);
xml.Append("<string>");
//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("</string>");
}
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("\"");
}
/// <summary>Escapes a string for use in ASCII property lists.</summary>
/// <returns>The unescaped string.</returns>
/// <param name="s">S.</param>
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;
}
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />; otherwise, <c>false</c>.
/// </returns>
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
}
/// <summary>Creates a NSString from a string.</summary>
/// <param name="text">The string that will be contained in the NSString.</param>
public NSString(string text) => Content = text;
/// <summary>Gets this strings content.</summary>
/// <returns>This NSString as .NET string object.</returns>
public string Content { get; set; }
/// <summary>Compares the current <see cref="Claunia.PropertyList.NSString" /> to the specified object.</summary>
/// <returns>A 32-bit signed integer that indicates the lexical relationship between the two comparands.</returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param>
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
};
/// <summary>Appends a string to this string.</summary>
/// <param name="s">The string to append.</param>
public void Append(NSString s) => Append(s.Content);
/// <summary>Appends a string to this string.</summary>
/// <param name="s">The string to append.</param>
public void Append(string s) => Content += s;
/// <summary>Prepends a string to this string.</summary>
/// <param name="s">The string to prepend.</param>
public void Prepend(string s) => Content = s + Content;
/// <summary>Prepends a string to this string.</summary>
/// <param name="s">The string to prepend.</param>
public void Prepend(NSString s) => Prepend(s.Content);
/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </summary>
/// <param name="obj">
/// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj) => obj is NSString nsString && Content.Equals(nsString.Content);
/// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSString" /> object.</summary>
/// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() => Content.GetHashCode();
/// <summary>The textual representation of this NSString.</summary>
/// <returns>The NSString's contents.</returns>
public override string ToString() => Content;
internal override void ToXml(StringBuilder xml, int level)
{
Indent(xml, level);
xml.Append("<string>");
//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("</string>");
}
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("\"");
}
/// <summary>Escapes a string for use in ASCII property lists.</summary>
/// <returns>The unescaped string.</returns>
/// <param name="s">S.</param>
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;
}
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />; otherwise, <c>false</c>.
/// </returns>
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);
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
// Copyright (C) 2016 Quamotion
//
// This code is based on:
@@ -27,27 +27,26 @@
using System;
using System.Runtime.Serialization;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>The exception that is thrown when an property list file could not be processed correctly.</summary>
[Serializable]
public class PropertyListException : Exception
{
/// <summary>The exception that is thrown when an property list file could not be processed correctly.</summary>
[Serializable]
public class PropertyListException : Exception
{
/// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
public PropertyListException() {}
/// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
public PropertyListException() {}
/// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
public PropertyListException(string message) : base(message) {}
/// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
public PropertyListException(string message) : base(message) {}
/// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="inner">
/// The exception that is the cause of the current exception, or <see langword="null" /> if no inner
/// exception is specified.
/// </param>
public PropertyListException(string message, Exception inner) : base(message, inner) {}
/// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="inner">
/// The exception that is the cause of the current exception, or <see langword="null" /> if no inner
/// exception is specified.
/// </param>
public PropertyListException(string message, Exception inner) : base(message, inner) {}
protected PropertyListException(SerializationInfo info, StreamingContext context) : base(info, context) {}
}
protected PropertyListException(SerializationInfo info, StreamingContext context) : base(info, context) {}
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -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;
/// <summary>
/// A PropertyListFormatException is thrown by the various property list format parsers when an error in the
/// format of the given property list is encountered.
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class PropertyListFormatException : PropertyListException
{
/// <summary>
/// A PropertyListFormatException is thrown by the various property list format parsers when an error in the
/// format of the given property list is encountered.
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class PropertyListFormatException : PropertyListException
{
/// <summary>Creates a new exception with the given message.</summary>
/// <param name="message">A message containing information about the nature of the exception.</param>
public PropertyListFormatException(string message) : base(message) {}
}
/// <summary>Creates a new exception with the given message.</summary>
/// <param name="message">A message containing information about the nature of the exception.</param>
public PropertyListFormatException(string message) : base(message) {}
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,379 +27,367 @@ using System;
using System.IO;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>
/// This class provides methods to parse property lists. It can handle files, input streams and byte arrays. All
/// known property list formats are supported. This class also provides methods to save and convert property lists.
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public static class PropertyListParser
{
/// <summary>
/// This class provides methods to parse property lists. It can handle files, input streams and byte arrays. All
/// known property list formats are supported. This class also provides methods to save and convert property lists.
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public static class PropertyListParser
const int TYPE_XML = 0;
const int TYPE_BINARY = 1;
const int TYPE_ASCII = 2;
const int TYPE_ERROR_BLANK = 10;
const int TYPE_ERROR_UNKNOWN = 11;
/// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// <returns>The type of the property list</returns>
/// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param>
static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning)
{
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;
/// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// <returns>The type of the property list</returns>
/// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param>
static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning)
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;
}
/// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// <returns>The very first bytes of data of the property list (minus any whitespace)</returns>
/// <param name="bytes">The type of the property list</param>
static int DetermineType(ReadOnlySpan<byte> 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<byte> header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset));
return DetermineTypeExact(header);
}
/// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// <returns>The type of the property list</returns>
/// <param name="fs">
/// 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.
/// </param>
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;
}
/// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// <returns>The very first bytes of data of the property list (minus any whitespace)</returns>
/// <param name="bytes">The type of the property list</param>
static int DetermineType(ReadOnlySpan<byte> 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<byte> header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset));
return DetermineTypeExact(header);
}
/// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// <returns>The type of the property list</returns>
/// <param name="fs">
/// 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.
/// </param>
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;
}
/// <summary>Set up preprocessing functions for plist values.</summary>
/// <param name="preprocessor">A function that preprocesses the passed string and returns the adjusted value.</param>
/// <param name="type">The type of value preprocessor to use.</param>
public static void SetValuePreprocessor<T>(Func<T, T> preprocessor, ValuePreprocessor.Type type) =>
ValuePreprocessor.Set(preprocessor, type);
/// <summary>Reads all bytes from an Stream and stores them in an array, up to a maximum count.</summary>
/// <param name="fs">The Stream pointing to the data that should be stored in the array.</param>
internal static byte[] ReadAll(Stream fs)
{
using var outputStream = new MemoryStream();
fs.CopyTo(outputStream);
return outputStream.ToArray();
}
/// <summary>Parses a property list from a file.</summary>
/// <param name="filePath">Path to the property list file.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(string filePath) => Parse(new FileInfo(filePath));
/// <summary>Parses a property list from a file.</summary>
/// <param name="f">The property list file.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(FileInfo f)
{
using FileStream fis = f.OpenRead();
return Parse(fis);
}
/// <summary>Parses a property list from a byte array.</summary>
/// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
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);
}
}
/// <summary>Parses a property list from a byte array.</summary>
/// <param name="bytes">The property list data as a byte array.</param>
/// <param name="offset">The length of the property list.</param>
/// <param name="count">The offset at which to start reading the property list.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes, int offset, int length) => Parse(bytes.AsSpan(offset, length));
b = fs.ReadByte();
/// <summary>Parses a property list from a byte span.</summary>
/// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(ReadOnlySpan<byte> 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;
}
/// <summary>Set up preprocessing functions for plist values.</summary>
/// <param name="preprocessor">A function that preprocesses the passed string and returns the adjusted value.</param>
/// <param name="type">The type of value preprocessor to use.</param>
public static void SetValuePreprocessor<T>(Func<T, T> preprocessor, ValuePreprocessor.Type type) =>
ValuePreprocessor.Set(preprocessor, type);
/// <summary>Reads all bytes from an Stream and stores them in an array, up to a maximum count.</summary>
/// <param name="fs">The Stream pointing to the data that should be stored in the array.</param>
internal static byte[] ReadAll(Stream fs)
{
using var outputStream = new MemoryStream();
fs.CopyTo(outputStream);
return outputStream.ToArray();
}
/// <summary>Parses a property list from a file.</summary>
/// <param name="filePath">Path to the property list file.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(string filePath) => Parse(new FileInfo(filePath));
/// <summary>Parses a property list from a file.</summary>
/// <param name="f">The property list file.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(FileInfo f)
{
using FileStream fis = f.OpenRead();
return Parse(fis);
}
/// <summary>Parses a property list from a byte array.</summary>
/// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
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.");
}
}
/// <summary>Parses a property list from an Stream.</summary>
/// <param name="fs">The Stream delivering the property list data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(Stream fs) => Parse(ReadAll(fs));
/// <summary>Parses a property list from a byte array.</summary>
/// <param name="bytes">The property list data as a byte array.</param>
/// <param name="offset">The length of the property list.</param>
/// <param name="count">The offset at which to start reading the property list.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes, int offset, int length) => Parse(bytes.AsSpan(offset, length));
/// <summary>Saves a property list with the given object as root into a XML file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsXml(NSObject root, FileInfo outFile)
/// <summary>Parses a property list from a byte span.</summary>
/// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(ReadOnlySpan<byte> 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.");
}
}
/// <summary>Saves a property list with the given object as root in XML format into an output stream.</summary>
/// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsXml(NSObject root, Stream outStream)
/// <summary>Parses a property list from an Stream.</summary>
/// <param name="fs">The Stream delivering the property list data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(Stream fs) => Parse(ReadAll(fs));
/// <summary>Saves a property list with the given object as root into a XML file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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);
}
/// <summary>Saves a property list with the given object as root in XML format into an output stream.</summary>
/// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsXml(NSObject root, Stream outStream)
{
using var w = new StreamWriter(outStream, Encoding.UTF8, 1024, true);
w.Write(root.ToXmlPropertyList());
}
/// <summary>Converts a given property list file into the OS X and iOS XML format.</summary>
/// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param>
public static void ConvertToXml(FileInfo inFile, FileInfo outFile)
{
NSObject root = Parse(inFile);
SaveAsXml(root, outFile);
}
/// <summary>Saves a property list with the given object as root into a binary file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsBinary(NSObject root, FileInfo outFile)
{
string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
BinaryPropertyListWriter.Write(outFile, root);
}
/// <summary>Saves a property list with the given object as root in binary format into an output stream.</summary>
/// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsBinary(NSObject root, Stream outStream) => BinaryPropertyListWriter.Write(outStream, root);
/// <summary>Converts a given property list file into the OS X and iOS binary format.</summary>
/// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param>
public static void ConvertToBinary(FileInfo inFile, FileInfo outFile)
{
NSObject root = Parse(inFile);
SaveAsBinary(root, outFile);
}
/// <summary>Saves a property list with the given object as root into a ASCII file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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());
}
/// <summary>Saves a property list with the given object as root into a ASCII file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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());
}
/// <summary>Converts a given property list file into ASCII format.</summary>
/// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param>
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!");
}
}
/// <summary>Converts a given property list file into the OS X and iOS XML format.</summary>
/// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param>
public static void ConvertToXml(FileInfo inFile, FileInfo outFile)
/// <summary>Saves a property list with the given object as root into a GnuStep ASCII file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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());
}
/// <summary>Saves a property list with the given object as root into a GnuStep ASCII file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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());
}
/// <summary>Converts a given property list file into GnuStep ASCII format.</summary>
/// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param>
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);
/// <summary>Saves a property list with the given object as root into a binary file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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);
}
/// <summary>Saves a property list with the given object as root in binary format into an output stream.</summary>
/// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsBinary(NSObject root, Stream outStream) =>
BinaryPropertyListWriter.Write(outStream, root);
/// <summary>Converts a given property list file into the OS X and iOS binary format.</summary>
/// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param>
public static void ConvertToBinary(FileInfo inFile, FileInfo outFile)
{
NSObject root = Parse(inFile);
SaveAsBinary(root, outFile);
}
/// <summary>Saves a property list with the given object as root into a ASCII file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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());
}
/// <summary>Saves a property list with the given object as root into a ASCII file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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());
}
/// <summary>Converts a given property list file into ASCII format.</summary>
/// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param>
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!");
}
/// <summary>Saves a property list with the given object as root into a GnuStep ASCII file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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());
}
/// <summary>Saves a property list with the given object as root into a GnuStep ASCII file.</summary>
/// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception>
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());
}
/// <summary>Converts a given property list file into GnuStep ASCII format.</summary>
/// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param>
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!");
}
}
}
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -27,187 +27,182 @@ using System;
using System.Buffers.Binary;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>An UID. Only found in binary property lists that are keyed archives.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class UID : NSObject
{
/// <summary>An UID. Only found in binary property lists that are keyed archives.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public class UID : NSObject
readonly ulong value;
/// <summary>Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class.</summary>
/// <param name="bytes">Bytes.</param>
public UID(ReadOnlySpan<byte> 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.");
/// <summary>Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class.</summary>
/// <param name="bytes">Bytes.</param>
public UID(ReadOnlySpan<byte> 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);
}
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 8-bit
/// number.
/// </summary>
/// <param name="number">Unsigned 8-bit number.</param>
public UID(byte number) => value = number;
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 16-bit
/// number.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Unsigned 16-bit number.</param>
public UID(ushort number) => value = number;
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 32-bit
/// number.
/// </summary>
/// <param name="number">Unsigned 32-bit number.</param>
public UID(uint number) => value = number;
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 64-bit
/// number.
/// </summary>
/// <param name="number">Unsigned 64-bit number.</param>
public UID(ulong number) => value = number;
/// <summary>Gets the bytes.</summary>
/// <value>The bytes.</value>
public byte[] Bytes
{
get
{
byte[] bytes = new byte[ByteCount];
GetBytes(bytes);
return bytes;
}
}
/// <summary>Gets the number of bytes required to represent this <see cref="UID" />.</summary>
public int ByteCount => value switch
{
<= byte.MaxValue => 1,
<= ushort.MaxValue => 2,
<= uint.MaxValue => 4,
_ => 8
};
/// <summary>Writes the bytes required to represent this <see cref="UID" /> to a byte span.</summary>
/// <param name="bytes">The byte span to which to write the byte representation of this UID.</param>
public void GetBytes(Span<byte> 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();
}
}
/// <summary>
/// UIDs are represented as dictionaries in XML property lists, where the key is always <c>CF$UID</c> and the
/// value is the integer representation of the UID.
/// </summary>
/// <param name="xml">The xml StringBuilder</param>
/// <param name="level">The indentation level</param>
internal override void ToXml(StringBuilder xml, int level)
{
Indent(xml, level);
xml.Append("<dict>");
xml.AppendLine();
Indent(xml, level + 1);
xml.Append("<key>CF$UID</key>");
xml.AppendLine();
Indent(xml, level + 1);
xml.Append($"<integer>{value}</integer>");
xml.AppendLine();
Indent(xml, level);
xml.Append("</dict>");
}
internal override void ToBinary(BinaryPropertyListWriter outPlist)
{
outPlist.Write(0x80 + ByteCount - 1);
Span<byte> bytes = stackalloc byte[ByteCount];
GetBytes(bytes);
outPlist.Write(bytes);
}
internal override void ToASCII(StringBuilder ascii, int level)
{
Indent(ascii, level);
ascii.Append("\"");
Span<byte> 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);
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.UID" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.UID" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.UID" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) => Equals((object)obj);
/// <inheritdoc />
public override bool Equals(object obj)
{
if(obj is not UID uid)
return false;
return uid.value == value;
}
/// <inheritdoc />
public override int GetHashCode() => value.GetHashCode();
/// <inheritdoc />
public override string ToString() => $"{value} (UID)";
/// <summary>Gets a <see cref="ulong" /> which represents this <see cref="UID" />.</summary>
/// <returns>A <see cref="ulong" /> which represents this <see cref="UID" />.</returns>
public ulong ToUInt64() => value;
value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
}
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 8-bit
/// number.
/// </summary>
/// <param name="number">Unsigned 8-bit number.</param>
public UID(byte number) => value = number;
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 16-bit
/// number.
/// </summary>
/// <param name="name">Name.</param>
/// <param name="number">Unsigned 16-bit number.</param>
public UID(ushort number) => value = number;
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 32-bit
/// number.
/// </summary>
/// <param name="number">Unsigned 32-bit number.</param>
public UID(uint number) => value = number;
/// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 64-bit
/// number.
/// </summary>
/// <param name="number">Unsigned 64-bit number.</param>
public UID(ulong number) => value = number;
/// <summary>Gets the bytes.</summary>
/// <value>The bytes.</value>
public byte[] Bytes
{
get
{
byte[] bytes = new byte[ByteCount];
GetBytes(bytes);
return bytes;
}
}
/// <summary>Gets the number of bytes required to represent this <see cref="UID" />.</summary>
public int ByteCount => value switch
{
<= byte.MaxValue => 1,
<= ushort.MaxValue => 2,
<= uint.MaxValue => 4,
_ => 8
};
/// <summary>Writes the bytes required to represent this <see cref="UID" /> to a byte span.</summary>
/// <param name="bytes">The byte span to which to write the byte representation of this UID.</param>
public void GetBytes(Span<byte> 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();
}
}
/// <summary>
/// UIDs are represented as dictionaries in XML property lists, where the key is always <c>CF$UID</c> and the
/// value is the integer representation of the UID.
/// </summary>
/// <param name="xml">The xml StringBuilder</param>
/// <param name="level">The indentation level</param>
internal override void ToXml(StringBuilder xml, int level)
{
Indent(xml, level);
xml.Append("<dict>");
xml.AppendLine();
Indent(xml, level + 1);
xml.Append("<key>CF$UID</key>");
xml.AppendLine();
Indent(xml, level + 1);
xml.Append($"<integer>{value}</integer>");
xml.AppendLine();
Indent(xml, level);
xml.Append("</dict>");
}
internal override void ToBinary(BinaryPropertyListWriter outPlist)
{
outPlist.Write(0x80 + ByteCount - 1);
Span<byte> bytes = stackalloc byte[ByteCount];
GetBytes(bytes);
outPlist.Write(bytes);
}
internal override void ToASCII(StringBuilder ascii, int level)
{
Indent(ascii, level);
ascii.Append("\"");
Span<byte> 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);
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.UID" />.
/// </summary>
/// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.UID" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.UID" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) => Equals((object)obj);
/// <inheritdoc />
public override bool Equals(object obj)
{
if(obj is not UID uid) return false;
return uid.value == value;
}
/// <inheritdoc />
public override int GetHashCode() => value.GetHashCode();
/// <inheritdoc />
public override string ToString() => $"{value} (UID)";
/// <summary>Gets a <see cref="ulong" /> which represents this <see cref="UID" />.</summary>
/// <returns>A <see cref="ulong" /> which represents this <see cref="UID" />.</returns>
public ulong ToUInt64() => value;
}

View File

@@ -1,121 +1,180 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
// Copyright (C) 2014 Daniel Dreibrodt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Collections.Generic;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>
/// Allows you to override the default value class initialization for the values found
/// in the parsed plists by registering your own preprocessing implementations.
/// </summary>
public static class ValuePreprocessor
{
/// <summary>
/// Allows you to override the default value class initialization for the values found
/// in the parsed plists by registering your own preprocessing implementations.
/// Indicates the semantic type of content the preprocessor will work on--independent
/// from the underlying data type (which will be string in most cases anyway).
/// </summary>
public static class ValuePreprocessor
public enum Type
{
/// <summary>
/// Indicates the semantic type of content the preprocessor will work on--independent
/// from the underlying data type (which will be string in most cases anyway).
/// </summary>
public enum Type
{
BOOL, INTEGER, FLOATING_POINT,
UNDEFINED_NUMBER, STRING, DATA,
DATE
};
/// <summary>
/// A null-implementation of a preprocessor for registered, but passive, use cases.
/// </summary>
private static T NullPreprocessor<T>(T value) => value;
private record struct TypeIdentifier(Type ValueType, System.Type DataType);
/// <summary>
/// Default preprocessors for all the standard cases.
/// </summary>
private static readonly Dictionary<TypeIdentifier, Delegate> _preprocessors = new()
{
{ new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor<bool> },
{ new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Type.INTEGER, typeof(byte[])), NullPreprocessor<byte[]> },
{ new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor<byte[]> },
{ new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Type.STRING, typeof(byte[])), NullPreprocessor<byte[]> },
{ new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Type.DATA, typeof(byte[])), NullPreprocessor<byte[]> },
{ new TypeIdentifier(Type.DATE, typeof(string)), NullPreprocessor<string> },
{ new TypeIdentifier(Type.DATE, typeof(double)), NullPreprocessor<double> },
{ new TypeIdentifier(Type.DATE, typeof(byte[])), NullPreprocessor<byte[]> },
};
/// <summary>
/// Get a default preprocessor.
/// </summary>
public static Func<T, T> GetDefault<T>() => NullPreprocessor<T>;
/// <summary>
/// Set up a custom preprocessor.
/// </summary>
public static void Set<T>(Func<T, T> preprocessor, Type type) =>
_preprocessors[new(type, typeof(T))] = preprocessor;
/// <summary>
/// Unset a specific preprocessor--replaces it with a null-implementation
/// to prevent argument errors.
/// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was set up.</exception>
public static void Unset<T>(Type type) =>
_preprocessors[GetValidTypeIdentifier<T>(type)] = NullPreprocessor<T>;
/// <summary>
/// Completely unregister a specific preprocessor--remove it instead of
/// replacing it with a null-implementation.
/// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was registered.</exception>
public static void Remove<T>(Type type) =>
_preprocessors.Remove(GetValidTypeIdentifier<T>(type));
/// <summary>
/// Preprocess the supplied data using the appropriate registered implementation.
/// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was registered.</exception>
public static T Preprocess<T>(T value, Type type) => TryGetPreprocessor(type, out Func<T, T> preprocess)
? preprocess(value)
: throw new ArgumentException($"Failed to find a preprocessor for value '{value}'.");
/// <summary>
/// Gets the appropriate registered implementation--or null--and casts it back to
/// the required type.
/// </summary>
private static bool TryGetPreprocessor<T>(Type type, out Func<T, T> preprocess)
{
if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor))
{
preprocess = (Func<T, T>)preprocessor;
return true;
}
preprocess = default;
return false;
}
/// <summary>
/// Gets a type identifier if a preprocessor exists for it.
/// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was set up.</exception>
private static TypeIdentifier GetValidTypeIdentifier<T>(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
}
}
/// <summary>
/// Default preprocessors for all the standard cases.
/// </summary>
private static readonly Dictionary<TypeIdentifier, Delegate> _preprocessors = new()
{
{
new TypeIdentifier(Type.BOOL, typeof(bool)), NullPreprocessor<bool>
},
{
new TypeIdentifier(Type.BOOL, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.INTEGER, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.INTEGER, typeof(byte[])), NullPreprocessor<byte[]>
},
{
new TypeIdentifier(Type.FLOATING_POINT, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.FLOATING_POINT, typeof(byte[])), NullPreprocessor<byte[]>
},
{
new TypeIdentifier(Type.UNDEFINED_NUMBER, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.STRING, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.STRING, typeof(byte[])), NullPreprocessor<byte[]>
},
{
new TypeIdentifier(Type.DATA, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.DATA, typeof(byte[])), NullPreprocessor<byte[]>
},
{
new TypeIdentifier(Type.DATE, typeof(string)), NullPreprocessor<string>
},
{
new TypeIdentifier(Type.DATE, typeof(double)), NullPreprocessor<double>
},
{
new TypeIdentifier(Type.DATE, typeof(byte[])), NullPreprocessor<byte[]>
}
};
/// <summary>
/// A null-implementation of a preprocessor for registered, but passive, use cases.
/// </summary>
private static T NullPreprocessor<T>(T value) => value;
/// <summary>
/// Get a default preprocessor.
/// </summary>
public static Func<T, T> GetDefault<T>() => NullPreprocessor<T>;
/// <summary>
/// Set up a custom preprocessor.
/// </summary>
public static void Set<T>(Func<T, T> preprocessor, Type type) =>
_preprocessors[new TypeIdentifier(type, typeof(T))] = preprocessor;
/// <summary>
/// Unset a specific preprocessor--replaces it with a null-implementation
/// to prevent argument errors.
/// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was set up.</exception>
public static void Unset<T>(Type type) => _preprocessors[GetValidTypeIdentifier<T>(type)] = NullPreprocessor<T>;
/// <summary>
/// Completely unregister a specific preprocessor--remove it instead of
/// replacing it with a null-implementation.
/// </summary>
/// <exception cref="ArgumentException">
/// If no appropriate preprocessor--not even a default null-implementation--was
/// registered.
/// </exception>
public static void Remove<T>(Type type) => _preprocessors.Remove(GetValidTypeIdentifier<T>(type));
/// <summary>
/// Preprocess the supplied data using the appropriate registered implementation.
/// </summary>
/// <exception cref="ArgumentException">
/// If no appropriate preprocessor--not even a default null-implementation--was
/// registered.
/// </exception>
public static T Preprocess<T>(T value, Type type) => TryGetPreprocessor(type, out Func<T, T> preprocess)
? preprocess(value)
: throw new
ArgumentException($"Failed to find a preprocessor for value '{value}'.");
/// <summary>
/// Gets the appropriate registered implementation--or null--and casts it back to
/// the required type.
/// </summary>
private static bool TryGetPreprocessor<T>(Type type, out Func<T, T> preprocess)
{
if(_preprocessors.TryGetValue(new TypeIdentifier(type, typeof(T)), out Delegate preprocessor))
{
preprocess = (Func<T, T>)preprocessor;
return true;
}
preprocess = default(Func<T, T>);
return false;
}
/// <summary>
/// Gets a type identifier if a preprocessor exists for it.
/// </summary>
/// <exception cref="ArgumentException">If no appropriate preprocessor--not even a default null-implementation--was set up.</exception>
private static TypeIdentifier GetValidTypeIdentifier<T>(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);
}

View File

@@ -1,5 +1,5 @@
// plist-cil - An open source library to parse and generate property lists for .NET
// Copyright (C) 2015 Natalia Portillo
// Copyright (C) 2015-2025 Natalia Portillo
//
// This code is based on:
// plist - An open source library to parse and generate property lists
@@ -28,206 +28,230 @@ using System.IO;
using System.Linq;
using System.Xml;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>Parses XML property lists.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public static class XmlPropertyListParser
{
/// <summary>Parses XML property lists.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public static class XmlPropertyListParser
/// <summary>Parses a XML property list file.</summary>
/// <param name="f">The XML property list file.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(FileInfo f)
{
/// <summary>Parses a XML property list file.</summary>
/// <param name="f">The XML property list file.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
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);
}
/// <summary>Parses a XML property list from a byte array.</summary>
/// <param name="bytes">The byte array containing the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes)
{
var bis = new MemoryStream(bytes);
return Parse(bis);
}
/// <summary>Parses a XML property list from an input stream.</summary>
/// <param name="str">The input stream pointing to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
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);
}
/// <summary>Parses a XML property list from a string.</summary>
/// <param name="value">The string pointing to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject ParseString(string value)
{
var doc = new XmlDocument();
var settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore;
doc.LoadXml(value);
return ParseDocument(doc);
}
/// <summary>Parses the XML document by generating the appropriate NSObjects for each XML node.</summary>
/// <returns>The root NSObject of the property list contained in the XML document.</returns>
/// <param name="doc">The XML document.</param>
static NSObject ParseDocument(XmlDocument doc)
{
XmlNode docType = doc.ChildNodes.OfType<XmlNode>().
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);
}
/// <summary>Parses a XML property list from a byte array.</summary>
/// <param name="bytes">The byte array containing the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes)
{
var bis = new MemoryStream(bytes);
return Parse(bis);
}
/// <summary>Parses a XML property list from an input stream.</summary>
/// <param name="str">The input stream pointing to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
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);
}
/// <summary>Parses a XML property list from a string.</summary>
/// <param name="value">The string pointing to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject ParseString(string value)
{
var doc = new XmlDocument();
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Ignore
};
doc.LoadXml(value);
return ParseDocument(doc);
}
/// <summary>Parses the XML document by generating the appropriate NSObjects for each XML node.</summary>
/// <returns>The root NSObject of the property list contained in the XML document.</returns>
/// <param name="doc">The XML document.</param>
static NSObject ParseDocument(XmlDocument doc)
{
XmlNode docType = doc.ChildNodes.OfType<XmlNode>().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<XmlNode> 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.");
/// <summary>Parses a node in the XML structure and returns the corresponding NSObject</summary>
/// <returns>The corresponding NSObject.</returns>
/// <param name="n">The XML node.</param>
static NSObject ParseObject(XmlNode n)
XmlNode rootNode;
if(doc.DocumentElement is { Name: "plist" })
{
switch(n.Name)
//Root element wrapped in plist tag
List<XmlNode> 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);
}
/// <summary>Parses a node in the XML structure and returns the corresponding NSObject</summary>
/// <returns>The corresponding NSObject.</returns>
/// <param name="n">The XML node.</param>
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<XmlNode> children = FilterElementNodes(n.ChildNodes);
for(int i = 0; i < children.Count; i += 2)
{
var dict = new NSDictionary();
List<XmlNode> 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<XmlNode> 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;
}
}
/// <summary>Returns all element nodes that are contained in a list of nodes.</summary>
/// <returns>The sublist containing only nodes representing actual elements.</returns>
/// <param name="list">The list of nodes to search.</param>
static List<XmlNode> FilterElementNodes(XmlNodeList list)
{
List<XmlNode> result = new();
foreach(XmlNode child in list)
if(child.NodeType == XmlNodeType.Element)
result.Add(child);
return result;
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The node's text content.</returns>
/// <param name="n">The node.</param>
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<XmlNode> 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;
}
}
/// <summary>Returns all element nodes that are contained in a list of nodes.</summary>
/// <returns>The sublist containing only nodes representing actual elements.</returns>
/// <param name="list">The list of nodes to search.</param>
static List<XmlNode> FilterElementNodes(XmlNodeList list)
{
List<XmlNode> result = [];
foreach(XmlNode child in list)
if(child.NodeType == XmlNodeType.Element) result.Add(child);
return result;
}
/// <summary>
/// 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.
/// </summary>
/// <returns>The node's text content.</returns>
/// <param name="n">The node.</param>
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 "";
}
}

View File

@@ -1,18 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<VersionPrefix>2.2</VersionPrefix>
<TargetFrameworks>net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<VersionPrefix>2.3</VersionPrefix>
<Authors>Natalia Portillo</Authors>
<Company>Claunia.com</Company>
<Description>MIT licensed C#/.NET parser and writer for Apple and GnuStep Property Lists, supporting ASCII, Binary and Xml formats, based on Java's dd-plist.</Description>
<Copyright>© 2015-2020 Natalia Portillo</Copyright>
<Copyright>© 2015-2025 Natalia Portillo</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://www.github.com/claunia/plist-cil</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>apple propertylist property list gnustep plist</PackageTags>
<PackageReleaseNotes>Update XML representation of UIDs.
Add .NET 5.0 support.</PackageReleaseNotes>
<NeutralLanguage>en-US</NeutralLanguage>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>plist-cil.snk</AssemblyOriginatorKeyFile>
@@ -22,7 +20,7 @@ Add .NET 5.0 support.</PackageReleaseNotes>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<PackageVersion>2.2</PackageVersion>
<PackageVersion>2.3</PackageVersion>
</PropertyGroup>
<PropertyGroup>
@@ -41,18 +39,18 @@ Add .NET 5.0 support.</PackageReleaseNotes>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEBUG</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' != 'net462'">
<DefineConstants>$(DefineConstants);NATIVE_SPAN</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.112" PrivateAssets="all" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net462'">
<PackageReference Include="System.Memory" Version="4.6.0" />
<PackageReference Include="System.Memory" Version="4.6.0"/>
</ItemGroup>
<ItemGroup>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keyA</key>
<string>valueA</string>
<key>key&amp;B</key>
<string>value&amp;B</string>
<key>date</key>
<date>2011-11-28T09:21:30Z</date>
<key>data</key>
<data>AAAA BBBB CCCC</data>
<key>array</key>
<array>
<true/>
<false/>
<integer>87</integer>
<real>3.14159</real>
</array>
</dict>
<dict>
<key>keyA</key>
<string>valueA</string>
<key>key&amp;B</key>
<string>value&amp;B</string>
<key>date</key>
<date>2011-11-28T09:21:30Z</date>
<key>data</key>
<data>AAAA BBBB CCCC</data>
<key>array</key>
<array>
<true/>
<false/>
<integer>87</integer>
<real>3.14159</real>
</array>
</dict>
</plist>

Binary file not shown.

View File

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

View File

@@ -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.3",
"publicReleaseRefSpec": [
"^refs/heads/master$",
"^refs/heads/releases/*"
]
}