General refactor and clean-up.

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

View File

@@ -7,15 +7,16 @@ It is mostly based on [dd-plist for Java](https://github.com/3breadt/dd-plist).
And as it, this is licensed under the terms of the MIT license.
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

View File

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

View File

@@ -2,11 +2,12 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.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]
@@ -19,5 +20,4 @@ namespace Claunia.PropertyList.Benchmark
return nsObject;
}
}
}

View File

@@ -1,11 +1,12 @@
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;
[GlobalSetup]
@@ -13,5 +14,4 @@ namespace Claunia.PropertyList.Benchmark
[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)
{
BenchmarkRunner.Run<BinaryPropertyListParserBenchmarks>();
BenchmarkRunner.Run<BinaryPropertyListWriterBenchmarks>();
}
}
}

View File

@@ -7,11 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.2" />
<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

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

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

View File

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

View File

@@ -27,10 +27,10 @@ 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).
@@ -128,17 +128,17 @@ namespace plistcil.test
[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
#pragma warning disable 219
var arr = (NSArray)PropertyListParser.Parse(new FileInfo("test-files/issue30.plist"));
#pragma warning restore 219
#pragma warning restore 219
}
[Fact]
public static void TestIssue33()
{
#pragma warning disable 219
#pragma warning disable 219
var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj"));
#pragma warning restore 219
#pragma warning restore 219
}
[Fact]
@@ -188,5 +188,4 @@ namespace plistcil.test
Assert.IsType<double>(weight);
Assert.Equal(10d, (double)weight);
}
}
}

View File

@@ -2,10 +2,10 @@
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()
@@ -87,5 +87,4 @@ namespace plistcil.test
Assert.Empty(array);
}
}
}

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
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>";
@@ -25,5 +25,4 @@ namespace plistcil.test
Assert.Equal(content, actualContent);
}
}
}

View File

@@ -29,18 +29,16 @@ 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)
{
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;
if(arrayA[i] != arrayB[i]) return false;
return true;
}
@@ -85,10 +83,10 @@ namespace plistcil.test
Assert.Equal(actualDate.Date, expectedDate);
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[]
{
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
[
0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82
}));
]));
var a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4);
@@ -147,13 +145,13 @@ namespace plistcil.test
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(((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[]
{
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);
@@ -188,19 +186,19 @@ namespace plistcil.test
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);
@@ -209,15 +207,21 @@ namespace plistcil.test
map.Add("long", lng);
map.Add("date", date);
List<Dictionary<string,object>> listOfMaps = new()
{
List<Dictionary<string, object>> listOfMaps =
[
new Dictionary<string, object>
{
{ "int", i },
{ "long", lng },
{ "date", date }
{
"int", i
},
{
"long", lng
},
{
"date", date
}
};
}
];
var WrappedO = NSObject.Wrap((object)bl);
Assert.True(WrappedO is (NSNumber));
@@ -260,8 +264,7 @@ namespace plistcil.test
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]);
for(int x = 0; x < bytes.Length; x++) Assert.True(data[x] == bytes[x]);
WrappedO = NSObject.Wrap((object)array);
Assert.True(WrappedO is (NSArray));
@@ -284,7 +287,7 @@ namespace plistcil.test
Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng);
Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date));
WrappedO = NSObject.Wrap((object)listOfMaps);
WrappedO = NSObject.Wrap(listOfMaps);
Assert.True(WrappedO is (NSArray));
var arrayOfMaps = (NSArray)WrappedO;
Assert.True(arrayOfMaps.Count == 1);
@@ -317,15 +320,25 @@ namespace plistcil.test
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,
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,
((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011,
11,
28,
9,
21,
30,
DateTimeKind.Utc)));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[]
{
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
[
0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82
}));
]));
var a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4);
@@ -339,5 +352,4 @@ namespace plistcil.test
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testXml.plist"));
Assert.True(x.Equals(y));
}
}
}

View File

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

View File

@@ -2,20 +2,24 @@
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[]
})]
[InlineData(new byte[]
{
0xAB, 0xCD
}), InlineData(new byte[]
})]
[InlineData(new byte[]
{
0xAB, 0xCD, 0xEF, 0xFE
}), InlineData(new byte[]
})]
[InlineData(new byte[]
{
0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB
})]
@@ -46,7 +50,8 @@ namespace plistcil.test
Assert.Equal(new byte[]
{
0xAB
}, uid.Bytes);
},
uid.Bytes);
Assert.Equal(0xABu, uid.ToUInt64());
}
@@ -59,7 +64,8 @@ namespace plistcil.test
Assert.Equal(new byte[]
{
0xAB, 0xCD, 0xEF, 0x00
}, uid.Bytes);
},
uid.Bytes);
Assert.Equal(0xABCDEF00, uid.ToUInt64());
}
@@ -72,7 +78,8 @@ namespace plistcil.test
Assert.Equal(new byte[]
{
0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB
}, uid.Bytes);
},
uid.Bytes);
Assert.Equal(0xABCDEF0000EFCDAB, uid.ToUInt64());
}
@@ -85,7 +92,8 @@ namespace plistcil.test
Assert.Equal(new byte[]
{
0xAB, 0xCD, 0xEF, 0x00
}, uid.Bytes);
},
uid.Bytes);
Assert.Equal(0xABCDEF00u, uid.ToUInt64());
}
@@ -98,7 +106,8 @@ namespace plistcil.test
Assert.Equal(new byte[]
{
0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB
}, uid.Bytes);
},
uid.Bytes);
Assert.Equal(0xABCDEF0000EFCDABu, uid.ToUInt64());
}
@@ -111,7 +120,8 @@ namespace plistcil.test
Assert.Equal(new byte[]
{
0xAB, 0xCD
}, uid.Bytes);
},
uid.Bytes);
Assert.Equal(0xABCDu, uid.ToUInt64());
}
@@ -128,5 +138,4 @@ namespace plistcil.test
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,14 +9,14 @@ 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;
@@ -55,8 +55,7 @@ namespace plistcil.test
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) =>
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
throw new NotSupportedException();
/// <inheritdoc />
@@ -97,5 +96,4 @@ namespace plistcil.test
await output.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
}
}
}

View File

@@ -3,10 +3,10 @@ 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();
@@ -91,7 +91,7 @@ namespace plistcil.test
lock(_testLock)
{
Func<string, string> examplePreprocessor = value => new string(value.Reverse().ToArray());
byte[] testByteArray = [0x42,];
byte[] testByteArray = [0x42];
string testString = "TestString";
var testType = (ValuePreprocessor.Type)42;
@@ -121,5 +121,4 @@ namespace plistcil.test
// there's no registered preprocessor for byte array arguments for STRING
Assert.Throws<ArgumentException>(() => ValuePreprocessor.Preprocess(testArray, ValuePreprocessor.Type.STRING));
}
}
}

View File

@@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<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>
@@ -14,7 +14,7 @@
</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,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">
<plist version="1.0">
<dict>
<dict>
<key>files</key>
<dict>
<key>PkgInfo</key>
@@ -76,5 +76,5 @@
<real>0.0</real>
</dict>
</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>
<dict>
<key>keyA</key>
<data>
MjAxMy0wMi0wMiAyMDoxNjo0MiBHTVQ6IGhhbmRsZV9tZXNzYWdlOiBBbmQgeW91IHdp
bGwga25vdyBteSBuYW1lIGlzIHRoZSBMb3JkIHdoZW4gSSBsYXkgbXkgdmVuZ2VhbmNl
IHVwb24gdGhlZS4=
</data>
</dict>
</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>

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
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.
@@ -13,25 +13,18 @@ namespace Claunia.PropertyList
{
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);
}
public override int GetHashCode(NSObject obj)
{
if(obj is NSString s &&
IsSerializationPrimitive(s))
return s.Content.GetHashCode();
if(obj is NSString s && IsSerializationPrimitive(s)) return s.Content.GetHashCode();
return obj.GetHashCode();
}
}
}
}

View File

@@ -1,9 +1,9 @@
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 retrieving objects in the
/// <see cref="BinaryPropertyListWriter.idMap" />. The logic is slightly different from
@@ -22,18 +22,22 @@ namespace Claunia.PropertyList
// The exceptions are UIDs, where we always compare by value, and "primitive" strings (a list of well-known
// 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),
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(),
NSNumber n when IsSerializationPrimitive(n) => n.ToObject()
.GetHashCode(),
NSString s when IsSerializationPrimitive(s) => s.Content
.GetHashCode(),
_ => obj.GetHashCode()
};
}
}
}

View File

@@ -27,19 +27,19 @@ 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>
@@ -74,8 +74,7 @@ namespace Claunia.PropertyList
outStream = outStr;
}
public BinaryPropertyListWriter(Stream outStr, int version,
IEqualityComparer<NSObject> addObjectEqualityComparer,
public BinaryPropertyListWriter(Stream outStr, int version, IEqualityComparer<NSObject> addObjectEqualityComparer,
IEqualityComparer<NSObject> getObjectEqualityComparer)
{
this.version = version;
@@ -115,8 +114,7 @@ namespace Claunia.PropertyList
{
int v = GetMinimumRequiredVersion(o);
if(v > minVersion)
minVersion = v;
if(v > minVersion) minVersion = v;
}
break;
@@ -127,8 +125,7 @@ namespace Claunia.PropertyList
{
int v = GetMinimumRequiredVersion(o);
if(v > minVersion)
minVersion = v;
if(v > minVersion) minVersion = v;
}
break;
@@ -142,8 +139,7 @@ namespace Claunia.PropertyList
{
int v = GetMinimumRequiredVersion(o);
if(v > minVersion)
minVersion = v;
if(v > minVersion) minVersion = v;
}
break;
@@ -183,7 +179,8 @@ namespace Claunia.PropertyList
: "v0.0";
throw new IOException("The given property list structure cannot be saved. " +
"The required version of the binary format (" + versionString +
"The required version of the binary format (" +
versionString +
") is not yet supported.");
}
@@ -206,47 +203,32 @@ namespace Claunia.PropertyList
public void Write(NSObject root)
{
// magic bytes
Write(new[]
{
(byte)'b', (byte)'p', (byte)'l', (byte)'i', (byte)'s', (byte)'t'
});
Write("bplist"u8.ToArray());
//version
switch(version)
{
case VERSION_00:
{
Write(new[]
{
(byte)'0', (byte)'0'
});
Write("00"u8.ToArray());
break;
}
case VERSION_10:
{
Write(new[]
{
(byte)'1', (byte)'0'
});
Write("10"u8.ToArray());
break;
}
case VERSION_15:
{
Write(new[]
{
(byte)'1', (byte)'5'
});
Write("15"u8.ToArray());
break;
}
case VERSION_20:
{
Write(new[]
{
(byte)'2', (byte)'0'
});
Write("20"u8.ToArray());
break;
}
@@ -277,8 +259,7 @@ namespace Claunia.PropertyList
long offsetTableOffset = count;
int offsetSizeInBytes = ComputeOffsetSizeInBytes(count);
foreach(long offset in offsets)
WriteBytes(offset, offsetSizeInBytes);
foreach(long offset in offsets) WriteBytes(offset, offsetSizeInBytes);
if(version != VERSION_15)
{
@@ -310,16 +291,13 @@ namespace Claunia.PropertyList
{
if(ReuseObjectIds)
{
if(!idDict.ContainsKey(obj))
idDict.Add(obj, currentId++);
if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++);
}
else
{
if(!idDict2.ContainsKey(obj))
idDict2.Add(obj, currentId);
if(!idDict2.ContainsKey(obj)) idDict2.Add(obj, currentId);
if(!idDict.ContainsKey(obj))
idDict.Add(obj, currentId++);
if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++);
}
}
@@ -327,8 +305,7 @@ namespace Claunia.PropertyList
static int ComputeIdSizeInBytes(int numberOfIds)
{
if(numberOfIds < 256)
return 1;
if(numberOfIds < 256) return 1;
return numberOfIds < 65536 ? 2 : 4;
}
@@ -345,7 +322,8 @@ namespace Claunia.PropertyList
{
switch(value)
{
case < 0: throw new ArgumentException("value must be greater than or equal to 0", "value");
case < 0:
throw new ArgumentException("value must be greater than or equal to 0", "value");
case < 15:
Write((kind << 4) + value);
@@ -385,19 +363,18 @@ namespace Claunia.PropertyList
internal void Write(Span<byte> bytes)
{
#if NATIVE_SPAN
#if NATIVE_SPAN
outStream.Write(bytes);
count += bytes.Length;
#else
#else
Write(bytes.ToArray());
#endif
#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)));
for(int i = bytes - 1; i >= 0; i--) Write((int)(value >> 8 * i));
}
internal void WriteID(int id) => WriteBytes(id, idSizeInBytes);
@@ -412,11 +389,24 @@ namespace Claunia.PropertyList
// This is a list of "special" values which are only added once to a binary property
// 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";
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,10 +22,10 @@
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]
{
@@ -76,5 +76,4 @@ namespace Claunia.PropertyList
public void Insert(int index, object item) => Insert(index, Wrap(item));
public bool Remove(object item) => Remove(Wrap(item));
}
}

View File

@@ -27,13 +27,13 @@ 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>
@@ -42,7 +42,7 @@ namespace Claunia.PropertyList
/// <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);
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>
@@ -65,8 +65,7 @@ namespace Claunia.PropertyList
[Obsolete]
public void SetValue(int key, object value)
{
if(value == null)
throw new ArgumentNullException("value", "Cannot add null values to an NSArray!");
if(value == null) throw new ArgumentNullException("value", "Cannot add null values to an NSArray!");
array[key] = Wrap(value);
}
@@ -88,8 +87,7 @@ namespace Claunia.PropertyList
NSObject nso = Wrap(obj);
foreach(NSObject elem in array)
if(elem.Equals(nso))
return true;
if(elem.Equals(nso)) return true;
return false;
}
@@ -106,8 +104,7 @@ namespace Claunia.PropertyList
NSObject nso = Wrap(obj);
for(int i = 0; i < array.Count; i++)
if(array[i].Equals(nso))
return i;
if(array[i].Equals(nso)) return i;
return -1;
}
@@ -125,8 +122,7 @@ namespace Claunia.PropertyList
NSObject nso = Wrap(obj);
for(int i = 0; i < array.Count; i++)
if(array[i] == nso)
return i;
if(array[i] == nso) return i;
return -1;
}
@@ -143,11 +139,10 @@ namespace Claunia.PropertyList
/// <param name="indexes">The indices of the objects.</param>
public NSObject[] ObjectsAtIndexes(params int[] indexes)
{
NSObject[] result = new NSObject[indexes.Length];
var result = new NSObject[indexes.Length];
Array.Sort(indexes);
for(int i = 0; i < indexes.Length; i++)
result[i] = array[indexes[i]];
for(int i = 0; i < indexes.Length; i++) result[i] = array[indexes[i]];
return result;
}
@@ -166,13 +161,11 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(object obj)
{
if(obj is NSArray nsArray)
return ArrayEquals(nsArray, this);
if(obj is NSArray nsArray) return ArrayEquals(nsArray, this);
NSObject nso = Wrap(obj);
if(nso is NSArray nsoArray)
return ArrayEquals(nsoArray, this);
if(nso is NSArray nsoArray) return ArrayEquals(nsoArray, this);
return false;
}
@@ -185,7 +178,7 @@ namespace Claunia.PropertyList
public override int GetHashCode()
{
int hash = 7;
hash = (89 * hash) + array.GetHashCode();
hash = 89 * hash + array.GetHashCode();
return hash;
}
@@ -210,16 +203,14 @@ namespace Claunia.PropertyList
{
base.AssignIDs(outPlist);
foreach(NSObject obj in array)
obj.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));
foreach(NSObject obj in array) outPlist.WriteID(outPlist.GetID(obj));
}
/// <summary>
@@ -275,17 +266,14 @@ namespace Claunia.PropertyList
}
else
{
if(i != 0)
ascii.Append(" ");
if(i != 0) ascii.Append(" ");
array[i].ToASCII(ascii, 0);
}
if(i != array.Count - 1)
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(i != array.Count - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH)
continue;
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue;
ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length;
@@ -313,17 +301,14 @@ namespace Claunia.PropertyList
}
else
{
if(i != 0)
ascii.Append(" ");
if(i != 0) ascii.Append(" ");
array[i].ToASCIIGnuStep(ascii, 0);
}
if(i != array.Count - 1)
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(i != array.Count - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH)
continue;
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue;
ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length;
@@ -346,17 +331,13 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(NSObject obj)
{
if(obj is not NSArray nsArray)
return false;
if(obj is not NSArray nsArray) return false;
if(array.Count != nsArray.array.Count)
return false;
if(array.Count != nsArray.array.Count) return false;
for(int i = 0; i < array.Count; i++)
if(!array[i].Equals(nsArray[i]))
return false;
if(!array[i].Equals(nsArray[i])) return false;
return true;
}
}
}

View File

@@ -27,13 +27,13 @@ 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;
@@ -111,7 +111,7 @@ namespace Claunia.PropertyList
public override int GetHashCode()
{
int hash = 5;
hash = (67 * hash) + Bytes.GetHashCode();
hash = 67 * hash + Bytes.GetHashCode();
return hash;
}
@@ -124,12 +124,14 @@ namespace Claunia.PropertyList
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>");
@@ -157,9 +159,7 @@ namespace Claunia.PropertyList
ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length;
}
else if((i + 1) % 2 == 0 &&
i != Bytes.Length - 1)
ascii.Append(" ");
else if((i + 1) % 2 == 0 && i != Bytes.Length - 1) ascii.Append(" ");
}
ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN);
@@ -184,5 +184,4 @@ namespace Claunia.PropertyList
public static explicit operator byte[](NSData value) => value.Bytes;
public static explicit operator NSData(byte[] value) => new(value);
}
}

View File

@@ -27,13 +27,13 @@ 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
@@ -41,9 +41,9 @@ namespace Claunia.PropertyList
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;
@@ -162,8 +162,7 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(NSObject obj)
{
if(obj is not NSDate date)
return false;
if(obj is not NSDate date) return false;
int equality = DateTime.Compare(Date, date.Date);
@@ -173,5 +172,4 @@ namespace Claunia.PropertyList
public static explicit operator DateTime(NSDate value) => value.Date;
public static explicit operator NSDate(DateTime value) => new(value);
}
}

View File

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

View File

@@ -27,13 +27,13 @@ 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.
@@ -79,7 +79,8 @@ namespace Claunia.PropertyList
break;
default: throw new ArgumentException("Type argument is not valid.", nameof(type));
default:
throw new ArgumentException("Type argument is not valid.", nameof(type));
}
this.type = type;
@@ -118,12 +119,10 @@ namespace Claunia.PropertyList
/// <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 == 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))
long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out long l))
{
doubleValue = longValue = l;
type = INTEGER;
@@ -154,8 +153,7 @@ namespace Claunia.PropertyList
doubleValue = longValue = boolValue ? 1 : 0;
}
else
throw new
ArgumentException("The given string neither represents a double, an int nor a bool value.");
throw new ArgumentException("The given string neither represents a double, an int nor a bool value.");
}
}
@@ -214,8 +212,7 @@ namespace Claunia.PropertyList
: 1;
}
if(!IsNumber(o))
return -1;
if(!IsNumber(o)) return -1;
y = GetDoubleFromObject(o);
@@ -249,8 +246,7 @@ namespace Claunia.PropertyList
/// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns>
public bool ToBool()
{
if(type == BOOLEAN)
return boolValue;
if(type == BOOLEAN) return boolValue;
return longValue != 0;
}
@@ -282,10 +278,11 @@ namespace Claunia.PropertyList
/// <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;
if(obj is not NSNumber number) return false;
return type == number.type && longValue == number.longValue && doubleValue == number.doubleValue &&
return type == number.type &&
longValue == number.longValue &&
doubleValue == number.doubleValue &&
boolValue == number.boolValue;
}
@@ -297,12 +294,13 @@ namespace Claunia.PropertyList
public override int GetHashCode()
{
int hash = type;
hash = (37 * hash) + (int)(longValue ^ ((uint)longValue >> 32));
hash = 37 * hash + (int)(longValue ^ (uint)longValue >> 32);
hash = (37 * hash) + (int)(BitConverter.DoubleToInt64Bits(doubleValue) ^
hash = 37 * hash +
(int)(BitConverter.DoubleToInt64Bits(doubleValue) ^
(uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32));
hash = (37 * hash) + (ToBool() ? 1 : 0);
hash = 37 * hash + (ToBool() ? 1 : 0);
return hash;
}
@@ -483,11 +481,9 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(NSObject obj)
{
if(obj is not NSNumber number)
return false;
if(obj is not NSNumber number) return false;
if(number.GetNSNumberType() != type)
return false;
if(number.GetNSNumberType() != type) return false;
return type switch
{
@@ -541,5 +537,4 @@ namespace Claunia.PropertyList
public static explicit operator NSNumber(float value) => new(value);
public static explicit operator NSNumber(bool value) => new(value);
}
}

View File

@@ -27,16 +27,16 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace Claunia.PropertyList
namespace Claunia.PropertyList;
/// <summary>
/// <para>Abstract interface for any object contained in a property list.</para>
/// <para>The names and functions of the various objects orient themselves towards Apple's Cocoa API.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public abstract class NSObject
{
/// <summary>
/// <para>Abstract interface for any object contained in a property list.</para>
/// <para>The names and functions of the various objects orient themselves towards Apple's Cocoa API.</para>
/// </summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
public abstract class NSObject
{
/// <summary>
/// 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).
@@ -108,8 +108,7 @@ namespace Claunia.PropertyList
/// <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);
for(int i = 0; i < level; i++) xml.Append(INDENT);
}
/// <summary>Wraps the given value inside a NSObject.</summary>
@@ -140,8 +139,7 @@ namespace Claunia.PropertyList
{
var arr = new NSArray(value.Length);
for(int i = 0; i < value.Length; i++)
arr.Add(Wrap(value[i]));
for(int i = 0; i < value.Length; i++) arr.Add(Wrap(value[i]));
return arr;
}
@@ -154,8 +152,7 @@ namespace Claunia.PropertyList
{
var dict = new NSDictionary();
foreach(KeyValuePair<string, object> kvp in value)
dict.Add(kvp.Key, Wrap(kvp.Value));
foreach(KeyValuePair<string, object> kvp in value) dict.Add(kvp.Key, Wrap(kvp.Value));
return dict;
}
@@ -168,8 +165,7 @@ namespace Claunia.PropertyList
{
var set = new NSSet();
foreach(object o in value)
set.AddObject(Wrap(o));
foreach(object o in value) set.AddObject(Wrap(o));
return set;
}
@@ -196,55 +192,42 @@ namespace Claunia.PropertyList
/// <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 == null) throw new NullReferenceException("A null object cannot be wrapped as a NSObject");
if(o is NSObject nsObject)
return nsObject;
if(o is NSObject nsObject) return nsObject;
Type c = o.GetType();
if(typeof(bool).Equals(c))
return Wrap((bool)o);
if(typeof(bool).Equals(c)) return Wrap((bool)o);
if(typeof(byte).Equals(c))
return Wrap((byte)o);
if(typeof(byte).Equals(c)) return Wrap((byte)o);
if(typeof(short).Equals(c))
return Wrap((short)o);
if(typeof(short).Equals(c)) return Wrap((short)o);
if(typeof(int).Equals(c))
return Wrap((int)o);
if(typeof(int).Equals(c)) return Wrap((int)o);
if(typeof(long).IsAssignableFrom(c))
return Wrap((long)o);
if(typeof(long).IsAssignableFrom(c)) return Wrap((long)o);
if(typeof(float).Equals(c))
return Wrap((float)o);
if(typeof(float).Equals(c)) return Wrap((float)o);
if(typeof(double).IsAssignableFrom(c))
return Wrap((double)o);
if(typeof(double).IsAssignableFrom(c)) return Wrap((double)o);
if(typeof(string).Equals(c))
return new NSString((string)o);
if(typeof(string).Equals(c)) return new NSString((string)o);
if(typeof(DateTime).Equals(c))
return new NSDate((DateTime)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(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]));
for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
return nsa;
}
@@ -254,8 +237,7 @@ namespace Claunia.PropertyList
float[] array = (float[])o;
var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++)
nsa.Add(Wrap(array[i]));
for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
return nsa;
}
@@ -265,8 +247,7 @@ namespace Claunia.PropertyList
double[] array = (double[])o;
var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++)
nsa.Add(Wrap(array[i]));
for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
return nsa;
}
@@ -276,8 +257,7 @@ namespace Claunia.PropertyList
short[] array = (short[])o;
var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++)
nsa.Add(Wrap(array[i]));
for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
return nsa;
}
@@ -287,8 +267,7 @@ namespace Claunia.PropertyList
int[] array = (int[])o;
var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++)
nsa.Add(Wrap(array[i]));
for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
return nsa;
}
@@ -298,8 +277,7 @@ namespace Claunia.PropertyList
long[] array = (long[])o;
var nsa = new NSArray(array.Length);
for(int i = 0; i < array.Length; i++)
nsa.Add(Wrap(array[i]));
for(int i = 0; i < array.Length; i++) nsa.Add(Wrap(array[i]));
return nsa;
}
@@ -309,23 +287,21 @@ namespace Claunia.PropertyList
if(typeof(Dictionary<string, object>).IsAssignableFrom(c))
{
Dictionary<string, object> netDict = (Dictionary<string, object>)o;
var netDict = (Dictionary<string, object>)o;
var dict = new NSDictionary();
foreach(KeyValuePair<string, object> kvp in netDict)
dict.Add(kvp.Key, Wrap(kvp.Value));
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<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));
foreach(Dictionary<string, object> dict in (List<Dictionary<string, object>>)o) list.Add(Wrap(dict));
return list;
}
@@ -359,8 +335,7 @@ namespace Claunia.PropertyList
var nsArray = (NSArray)this;
object[] array = new object[nsArray.Count];
for(int i = 0; i < nsArray.Count; i++)
array[i] = nsArray[i].ToObject();
for(int i = 0; i < nsArray.Count; i++) array[i] = nsArray[i].ToObject();
return array;
}
@@ -369,8 +344,7 @@ namespace Claunia.PropertyList
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());
foreach(KeyValuePair<string, NSObject> kvp in dictA) dictB.Add(kvp.Key, kvp.Value.ToObject());
return dictB;
}
@@ -379,8 +353,7 @@ namespace Claunia.PropertyList
List<NSObject> setA = ((NSSet)this).GetSet();
List<object> setB = new();
foreach(NSObject o in setA)
setB.Add(o.ToObject());
foreach(NSObject o in setA) setB.Add(o.ToObject());
return setB;
}
@@ -394,46 +367,49 @@ namespace Claunia.PropertyList
{
long longVal = num.ToLong();
if(longVal is > int.MaxValue or < int.MinValue)
return longVal;
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();
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;
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;
if(arrayA.Length != arrayB.Length) return false;
for(int i = 0; i < arrayA.Length; i++)
if(arrayA[i] != arrayB[i])
return false;
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;
if(arrayA.Count != arrayB.Count) return false;
for(int i = 0; i < arrayA.Count; i++)
if(arrayA[i] != arrayB[i])
return false;
if(arrayA[i] != arrayB[i]) return false;
return true;
}
@@ -448,5 +424,4 @@ namespace Claunia.PropertyList
/// <see cref="Claunia.PropertyList.NSObject" />; otherwise, <c>false</c>.
/// </returns>
public abstract bool Equals(NSObject obj);
}
}

View File

@@ -28,33 +28,33 @@ 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 = new List<NSObject>();
public NSSet() => set = [];
/// <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>();
set = [];
}
/// <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);
public NSSet(params NSObject[] objects) => set = [..objects];
/// <summary>Creates a set and fill it with the given objects.</summary>
/// <param name="objects">The objects to populate the set.</param>
@@ -64,8 +64,7 @@ namespace Claunia.PropertyList
this.ordered = ordered;
set = new List<NSObject>(objects);
if(ordered)
set.Sort();
if(ordered) set.Sort();
}
/// <summary>Gets the number of elements in the set.</summary>
@@ -75,9 +74,11 @@ namespace Claunia.PropertyList
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
@@ -87,8 +88,10 @@ namespace Claunia.PropertyList
public IEnumerator GetEnumerator()
{
lock(set)
{
return set.GetEnumerator();
}
}
/// <summary>Adds an object to the set.</summary>
/// <param name="obj">The object to add.</param>
@@ -98,8 +101,7 @@ namespace Claunia.PropertyList
{
set.Add(obj);
if(ordered)
set.Sort();
if(ordered) set.Sort();
}
}
@@ -111,8 +113,7 @@ namespace Claunia.PropertyList
{
set.Remove(obj);
if(ordered)
set.Sort();
if(ordered) set.Sort();
}
}
@@ -121,16 +122,20 @@ namespace Claunia.PropertyList
public NSObject[] AllObjects()
{
lock(set)
{
return set.ToArray();
}
}
/// <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)
{
return set.Count == 0 ? null : set[0];
}
}
/// <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>
@@ -148,8 +153,7 @@ namespace Claunia.PropertyList
lock(set)
{
foreach(NSObject o in set)
if(o.Equals(obj))
return o;
if(o.Equals(obj)) return o;
return null;
}
@@ -163,8 +167,7 @@ namespace Claunia.PropertyList
lock(set)
{
foreach(NSObject o in set)
if(otherSet.ContainsObject(o))
return true;
if(otherSet.ContainsObject(o)) return true;
return false;
}
@@ -178,8 +181,7 @@ namespace Claunia.PropertyList
lock(set)
{
foreach(NSObject o in set)
if(!otherSet.ContainsObject(o))
return false;
if(!otherSet.ContainsObject(o)) return false;
return true;
}
@@ -197,7 +199,7 @@ namespace Claunia.PropertyList
public override int GetHashCode()
{
int hash = 7;
hash = (29 * hash) + (set != null ? set.GetHashCode() : 0);
hash = 29 * hash + (set != null ? set.GetHashCode() : 0);
return hash;
}
@@ -216,11 +218,9 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(object obj)
{
if(obj == null)
return false;
if(obj == null) return false;
if(GetType() != obj.GetType())
return false;
if(GetType() != obj.GetType()) return false;
var other = (NSSet)obj;
@@ -239,8 +239,7 @@ namespace Claunia.PropertyList
xml.Append("<array>");
xml.Append(NEWLINE);
if(ordered)
set.Sort();
if(ordered) set.Sort();
foreach(NSObject o in set)
{
@@ -256,8 +255,7 @@ namespace Claunia.PropertyList
{
base.AssignIDs(outPlist);
foreach(NSObject obj in set)
obj.AssignIDs(outPlist);
foreach(NSObject obj in set) obj.AssignIDs(outPlist);
}
internal override void ToBinary(BinaryPropertyListWriter outPlist)
@@ -270,8 +268,7 @@ namespace Claunia.PropertyList
else
outPlist.WriteIntHeader(0xC, set.Count);
foreach(NSObject obj in set)
outPlist.WriteID(outPlist.GetID(obj));
foreach(NSObject obj in set) outPlist.WriteID(outPlist.GetID(obj));
}
/// <summary>
@@ -284,8 +281,7 @@ namespace Claunia.PropertyList
{
Indent(ascii, level);
if(ordered)
set.Sort();
if(ordered) set.Sort();
NSObject[] array = AllObjects();
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
@@ -295,7 +291,8 @@ namespace Claunia.PropertyList
{
Type objClass = array[i].GetType();
if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
if((objClass.Equals(typeof(NSDictionary)) ||
objClass.Equals(typeof(NSArray)) ||
objClass.Equals(typeof(NSData))) &&
indexOfLastNewLine != ascii.Length)
{
@@ -305,17 +302,14 @@ namespace Claunia.PropertyList
}
else
{
if(i != 0)
ascii.Append(" ");
if(i != 0) ascii.Append(" ");
array[i].ToASCII(ascii, 0);
}
if(i != array.Length - 1)
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(i != array.Length - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH)
continue;
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue;
ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length;
@@ -334,8 +328,7 @@ namespace Claunia.PropertyList
{
Indent(ascii, level);
if(ordered)
set.Sort();
if(ordered) set.Sort();
NSObject[] array = AllObjects();
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
@@ -343,8 +336,7 @@ namespace Claunia.PropertyList
for(int i = 0; i < array.Length; i++)
{
if(array[i] is NSDictionary or NSArray or NSData &&
indexOfLastNewLine != ascii.Length)
if(array[i] is NSDictionary or NSArray or NSData && indexOfLastNewLine != ascii.Length)
{
ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length;
@@ -352,17 +344,14 @@ namespace Claunia.PropertyList
}
else
{
if(i != 0)
ascii.Append(" ");
if(i != 0) ascii.Append(" ");
array[i].ToASCIIGnuStep(ascii, 0);
}
if(i != array.Length - 1)
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(i != array.Length - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH)
continue;
if(ascii.Length - indexOfLastNewLine <= ASCII_LINE_LENGTH) continue;
ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length;
@@ -385,17 +374,13 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(NSObject obj)
{
if(obj is not NSSet nsSet)
return false;
if(obj is not NSSet nsSet) return false;
if(set.Count != nsSet.Count)
return false;
if(set.Count != nsSet.Count) return false;
foreach(NSObject objS in nsSet)
if(!set.Contains(objS))
return false;
if(!set.Contains(objS)) return false;
return true;
}
}
}

View File

@@ -27,13 +27,13 @@ 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>
@@ -48,11 +48,11 @@ namespace Claunia.PropertyList
/// <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
#if NATIVE_SPAN
Content = encoding.GetString(bytes);
#else
#else
Content = encoding.GetString(bytes.ToArray());
#endif
#endif
}
/// <summary>Creates a NSString from a string.</summary>
@@ -68,7 +68,9 @@ namespace Claunia.PropertyList
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param>
public int CompareTo(object o) => o switch
{
NSString nsString => string.Compare(Content, nsString.Content, StringComparison.Ordinal),
NSString nsString => string.Compare(Content,
nsString.Content,
StringComparison.Ordinal),
string s => string.Compare(Content, s, StringComparison.Ordinal),
_ => -1
};
@@ -150,7 +152,8 @@ namespace Claunia.PropertyList
lock(typeof(NSString))
{
// Not much use, because some characters do not fallback to exception, even if not ASCII
asciiEncoder ??= Encoding.GetEncoding("ascii", EncoderFallback.ExceptionFallback,
asciiEncoder ??= Encoding.GetEncoding("ascii",
EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);
if(IsASCIIEncodable(Content))
@@ -201,18 +204,19 @@ namespace Claunia.PropertyList
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;
while(hex.Length < 4) hex = "0" + hex;
outString += hex;
}
else
{
outString += c switch
{
'\\' => "\\\\",
@@ -223,6 +227,8 @@ namespace Claunia.PropertyList
'\t' => "\\t",
_ => c
};
}
}
return outString;
}
@@ -241,8 +247,7 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(NSObject obj)
{
if(obj is not NSString nsString)
return false;
if(obj is not NSString nsString) return false;
return Content == nsString.Content;
}
@@ -250,8 +255,7 @@ namespace Claunia.PropertyList
internal static bool IsASCIIEncodable(string text)
{
foreach(char c in text)
if(c > 0x7F)
return false;
if(c > 0x7F) return false;
return true;
}
@@ -259,5 +263,4 @@ namespace Claunia.PropertyList
public static explicit operator string(NSString value) => value.Content;
public static explicit operator NSString(string value) => new(value);
}
}

View File

@@ -27,12 +27,12 @@
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() {}
@@ -49,5 +49,4 @@ namespace Claunia.PropertyList
public PropertyListException(string message, Exception inner) : base(message, inner) {}
protected PropertyListException(SerializationInfo info, StreamingContext context) : base(info, context) {}
}
}

View File

@@ -23,18 +23,17 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// 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) {}
}
}

View File

@@ -27,16 +27,16 @@ 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;
@@ -48,16 +48,11 @@ namespace Claunia.PropertyList
/// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param>
static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning)
{
if(dataBeginning.Length == 0)
return TYPE_ERROR_BLANK;
if(dataBeginning.Length == 0) return TYPE_ERROR_BLANK;
if(dataBeginning[0] == '(' ||
dataBeginning[0] == '{' ||
dataBeginning[0] == '/')
return TYPE_ASCII;
if(dataBeginning[0] == '(' || dataBeginning[0] == '{' || dataBeginning[0] == '/') return TYPE_ASCII;
if(dataBeginning[0] == '<')
return TYPE_XML;
if(dataBeginning[0] == '<') return TYPE_XML;
if(dataBeginning.Length >= 6 &&
dataBeginning[0] == 'b' &&
@@ -76,20 +71,19 @@ namespace Claunia.PropertyList
/// <param name="bytes">The type of the property list</param>
static int DetermineType(ReadOnlySpan<byte> bytes)
{
if(bytes.Length == 0)
return TYPE_ERROR_BLANK;
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)
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] == ' ' ||
bytes[offset] == '\t' ||
bytes[offset] == '\r' ||
bytes[offset] == '\n' ||
bytes[offset] == '\f'))
offset++;
@@ -106,8 +100,7 @@ namespace Claunia.PropertyList
/// </param>
static int DetermineType(Stream fs, long offset = 0)
{
if(fs.Length == 0)
return TYPE_ERROR_BLANK;
if(fs.Length == 0) return TYPE_ERROR_BLANK;
long index = offset;
long readLimit = index + 1024;
@@ -129,13 +122,10 @@ namespace Claunia.PropertyList
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));
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;
if(b == -1) return TYPE_ERROR_BLANK;
byte[] magicBytes = new byte[8];
magicBytes[0] = (byte)b;
@@ -186,12 +176,14 @@ namespace Claunia.PropertyList
{
switch(DetermineType(bytes))
{
case TYPE_BINARY: return BinaryPropertyListParser.Parse(bytes);
case TYPE_XML: return XmlPropertyListParser.Parse(bytes);
case TYPE_ASCII: return ASCIIPropertyListParser.Parse(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.");
throw new PropertyListFormatException("The given data is not a property list of a supported format.");
}
}
@@ -209,12 +201,14 @@ namespace Claunia.PropertyList
{
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);
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.");
throw new PropertyListFormatException("The given data is not a property list of a supported format.");
}
}
@@ -231,8 +225,7 @@ namespace Claunia.PropertyList
{
string parent = outFile.DirectoryName;
if(!Directory.Exists(parent))
Directory.CreateDirectory(parent);
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.
@@ -269,8 +262,7 @@ namespace Claunia.PropertyList
{
string parent = outFile.DirectoryName;
if(!Directory.Exists(parent))
Directory.CreateDirectory(parent);
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
BinaryPropertyListWriter.Write(outFile, root);
}
@@ -279,8 +271,7 @@ namespace Claunia.PropertyList
/// <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);
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>
@@ -299,8 +290,7 @@ namespace Claunia.PropertyList
{
string parent = outFile.DirectoryName;
if(!Directory.Exists(parent))
Directory.CreateDirectory(parent);
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
@@ -317,8 +307,7 @@ namespace Claunia.PropertyList
{
string parent = outFile.DirectoryName;
if(!Directory.Exists(parent))
Directory.CreateDirectory(parent);
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
@@ -339,9 +328,11 @@ namespace Claunia.PropertyList
else if(root is NSArray array)
SaveAsASCII(array, outFile);
else
{
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>
@@ -351,8 +342,7 @@ namespace Claunia.PropertyList
{
string parent = outFile.DirectoryName;
if(!Directory.Exists(parent))
Directory.CreateDirectory(parent);
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
@@ -369,8 +359,7 @@ namespace Claunia.PropertyList
{
string parent = outFile.DirectoryName;
if(!Directory.Exists(parent))
Directory.CreateDirectory(parent);
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
@@ -401,5 +390,4 @@ namespace Claunia.PropertyList
"is neither a Dictionary nor an Array!");
}
}
}
}

View File

@@ -27,23 +27,20 @@ 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)
{
if(bytes.Length != 1 &&
bytes.Length != 2 &&
bytes.Length != 4 &&
bytes.Length != 8)
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);
@@ -126,7 +123,8 @@ namespace Claunia.PropertyList
break;
default: throw new InvalidOperationException();
default:
throw new InvalidOperationException();
}
}
@@ -169,8 +167,7 @@ namespace Claunia.PropertyList
Span<byte> bytes = stackalloc byte[ByteCount];
GetBytes(bytes);
foreach(byte b in bytes)
ascii.Append($"{b:x2}");
foreach(byte b in bytes) ascii.Append($"{b:x2}");
ascii.Append("\"");
}
@@ -194,8 +191,7 @@ namespace Claunia.PropertyList
/// <inheritdoc />
public override bool Equals(object obj)
{
if(obj is not UID uid)
return false;
if(obj is not UID uid) return false;
return uid.value == value;
}
@@ -209,5 +205,4 @@ namespace Claunia.PropertyList
/// <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,53 +1,83 @@
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.
/// </summary>
public static class ValuePreprocessor
{
/// <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,
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[]> },
{
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>
@@ -57,7 +87,7 @@ namespace Claunia.PropertyList
/// Set up a custom preprocessor.
/// </summary>
public static void Set<T>(Func<T, T> preprocessor, Type type) =>
_preprocessors[new(type, typeof(T))] = preprocessor;
_preprocessors[new TypeIdentifier(type, typeof(T))] = preprocessor;
/// <summary>
@@ -65,24 +95,29 @@ namespace Claunia.PropertyList
/// 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>;
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));
/// <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>
/// <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}'.");
: throw new
ArgumentException($"Failed to find a preprocessor for value '{value}'.");
/// <summary>
/// Gets the appropriate registered implementation--or null--and casts it back to
@@ -97,7 +132,7 @@ namespace Claunia.PropertyList
return true;
}
preprocess = default;
preprocess = default(Func<T, T>);
return false;
}
@@ -111,11 +146,10 @@ namespace Claunia.PropertyList
var identifier = new TypeIdentifier(type, typeof(T));
if(!_preprocessors.ContainsKey(identifier))
{
throw new ArgumentException($"Failed to find a valid preprocessor type identifier.");
}
throw new ArgumentException("Failed to find a valid preprocessor type identifier.");
return identifier;
}
}
private record struct TypeIdentifier(Type ValueType, System.Type DataType);
}

View File

@@ -28,13 +28,13 @@ 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>
@@ -48,8 +48,12 @@ namespace Claunia.PropertyList
};
using(Stream stream = f.OpenRead())
{
using(var reader = XmlReader.Create(stream, settings))
{
doc.Load(reader);
}
}
return ParseDocument(doc);
}
@@ -71,11 +75,15 @@ namespace Claunia.PropertyList
{
var doc = new XmlDocument();
var settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore;
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Ignore
};
using(var reader = XmlReader.Create(str, settings))
{
doc.Load(reader);
}
return ParseDocument(doc);
}
@@ -87,8 +95,10 @@ namespace Claunia.PropertyList
{
var doc = new XmlDocument();
var settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore;
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Ignore
};
doc.LoadXml(value);
@@ -100,17 +110,14 @@ namespace Claunia.PropertyList
/// <param name="doc">The XML document.</param>
static NSObject ParseDocument(XmlDocument doc)
{
XmlNode docType = doc.ChildNodes.OfType<XmlNode>().
SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType);
XmlNode docType = doc.ChildNodes.OfType<XmlNode>().SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType);
if(docType == null)
{
if(doc.DocumentElement != null &&
!doc.DocumentElement.Name.Equals("plist"))
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"))
throw new XmlException("The given XML document is not a property list.");
else if(!docType.Name.Equals("plist")) throw new XmlException("The given XML document is not a property list.");
XmlNode rootNode;
@@ -121,7 +128,8 @@ namespace Claunia.PropertyList
rootNode = rootNodes.Count switch
{
0 => throw new PropertyListFormatException("The given XML property list has no root element!"),
0 => throw new
PropertyListFormatException("The given XML property list has no root element!"),
1 => rootNodes[0],
_ => throw new
PropertyListFormatException("The given XML property list has more than one root element!")
@@ -143,9 +151,12 @@ namespace Claunia.PropertyList
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" 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();
@@ -168,18 +179,32 @@ namespace Claunia.PropertyList
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]));
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;
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;
}
}
@@ -188,11 +213,10 @@ namespace Claunia.PropertyList
/// <param name="list">The list of nodes to search.</param>
static List<XmlNode> FilterElementNodes(XmlNodeList list)
{
List<XmlNode> result = new();
List<XmlNode> result = [];
foreach(XmlNode child in list)
if(child.NodeType == XmlNodeType.Element)
result.Add(child);
if(child.NodeType == XmlNodeType.Element) result.Add(child);
return result;
}
@@ -212,22 +236,22 @@ namespace Claunia.PropertyList
return content ?? "";
}
if(!n.HasChildNodes)
return "";
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 "";
}
}
}

View File

@@ -45,12 +45,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<PackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" 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>
<dict>
<key>emojiString</key>
<string>Test Test, 😰❔👍👎🔥</string>
</dict>
</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">
<array>
<dict/>
<integer>0</integer>

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>
<dict>
<key>Device Name</key>
<string>Kids iPhone</string>
</dict>
</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,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">
<plist version="1.0">
<dict>
<dict>
<key>keyA</key>
<string>valueA</string>
<key>key&amp;B</key>
@@ -17,5 +17,5 @@
<integer>87</integer>
<real>3.14159</real>
</array>
</dict>
</dict>
</plist>

Binary file not shown.

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">
<plist version="1.0">
<dict>
<dict>
<key>number</key>
<integer>-1234</integer>
<key>number2</key>
@@ -14,5 +14,5 @@
<real>2.352535353543534e+19</real>
<key>number6</key>
<real>-999992312312312.2</real>
</dict>
</dict>
</plist>