Code refactor and clean-up to follow new C# 9 conventions and features where applicable.

This commit is contained in:
2021-04-30 13:06:04 +01:00
parent a72e7ad0e4
commit 19756723a3
34 changed files with 2467 additions and 2691 deletions

View File

@@ -1,25 +1,22 @@
using BenchmarkDotNet.Attributes; using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using System.IO;
namespace Claunia.PropertyList.Benchmark namespace Claunia.PropertyList.Benchmark
{ {
[SimpleJob(RuntimeMoniker.NetCoreApp50)] [SimpleJob(RuntimeMoniker.NetCoreApp50), MemoryDiagnoser]
[MemoryDiagnoser]
public class BinaryPropertyListParserBenchmarks public class BinaryPropertyListParserBenchmarks
{ {
byte[] data; byte[] data;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup() => data = File.ReadAllBytes("plist.bin");
{
data = File.ReadAllBytes("plist.bin");
}
[Benchmark] [Benchmark]
public NSObject ReadLargePropertylistTest() public NSObject ReadLargePropertylistTest()
{ {
NSObject nsObject = PropertyListParser.Parse(data); NSObject nsObject = PropertyListParser.Parse(data);
return nsObject; return nsObject;
} }
} }

View File

@@ -3,22 +3,15 @@ using BenchmarkDotNet.Jobs;
namespace Claunia.PropertyList.Benchmark namespace Claunia.PropertyList.Benchmark
{ {
[SimpleJob(RuntimeMoniker.NetCoreApp50)] [SimpleJob(RuntimeMoniker.NetCoreApp50), MemoryDiagnoser]
[MemoryDiagnoser]
public class BinaryPropertyListWriterBenchmarks public class BinaryPropertyListWriterBenchmarks
{ {
NSObject data; NSObject data;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup() => data = PropertyListParser.Parse("plist.bin");
{
data = PropertyListParser.Parse("plist.bin");
}
[Benchmark] [Benchmark]
public byte[] WriteLargePropertylistTest() public byte[] WriteLargePropertylistTest() => BinaryPropertyListWriter.WriteToArray(data);
{
return BinaryPropertyListWriter.WriteToArray(data);
}
} }
} }

View File

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

View File

@@ -0,0 +1,3 @@
<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">
<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

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

View File

@@ -12,15 +12,19 @@ namespace plistcil.test
byte[] data = File.ReadAllBytes("test-files/plist2.bin"); byte[] data = File.ReadAllBytes("test-files/plist2.bin");
NSObject root = PropertyListParser.Parse(data); NSObject root = PropertyListParser.Parse(data);
using(MemoryStream actualOutput = new MemoryStream()) using var actualOutput = new MemoryStream();
using(Stream expectedOutput = File.OpenRead("test-files/plist2.bin"))
using(ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput)) using Stream expectedOutput = File.OpenRead("test-files/plist2.bin");
using var validatingStream = new ValidatingStream(actualOutput, expectedOutput);
var writer = new BinaryPropertyListWriter(validatingStream)
{ {
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream); ReuseObjectIds = false
writer.ReuseObjectIds = false; };
writer.Write(root); writer.Write(root);
} }
}
[Fact] [Fact]
public void Roundtrip3Test() public void Roundtrip3Test()
@@ -28,15 +32,16 @@ namespace plistcil.test
byte[] data = File.ReadAllBytes("test-files/plist3.bin"); byte[] data = File.ReadAllBytes("test-files/plist3.bin");
NSObject root = PropertyListParser.Parse(data); NSObject root = PropertyListParser.Parse(data);
using(MemoryStream actualOutput = new MemoryStream()) using var actualOutput = new MemoryStream();
using(Stream expectedOutput = File.OpenRead("test-files/plist3.bin"))
using(ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput)) using Stream expectedOutput = File.OpenRead("test-files/plist3.bin");
{
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream); using var validatingStream = new ValidatingStream(actualOutput, expectedOutput);
var writer = new BinaryPropertyListWriter(validatingStream);
writer.ReuseObjectIds = false; writer.ReuseObjectIds = false;
writer.Write(root); writer.Write(root);
} }
}
[Fact] [Fact]
public void Roundtrip4Test() public void Roundtrip4Test()
@@ -44,15 +49,19 @@ namespace plistcil.test
byte[] data = File.ReadAllBytes("test-files/plist4.bin"); byte[] data = File.ReadAllBytes("test-files/plist4.bin");
NSObject root = PropertyListParser.Parse(data); NSObject root = PropertyListParser.Parse(data);
using(MemoryStream actualOutput = new MemoryStream()) using var actualOutput = new MemoryStream();
using(Stream expectedOutput = File.OpenRead("test-files/plist4.bin"))
using(ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput)) using Stream expectedOutput = File.OpenRead("test-files/plist4.bin");
using var validatingStream = new ValidatingStream(actualOutput, expectedOutput);
var writer = new BinaryPropertyListWriter(validatingStream)
{ {
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream); ReuseObjectIds = false
writer.ReuseObjectIds = false; };
writer.Write(root); writer.Write(root);
} }
}
[Fact] [Fact]
public void RoundtripTest() public void RoundtripTest()
@@ -60,14 +69,18 @@ namespace plistcil.test
byte[] data = File.ReadAllBytes("test-files/plist.bin"); byte[] data = File.ReadAllBytes("test-files/plist.bin");
NSObject root = PropertyListParser.Parse(data); NSObject root = PropertyListParser.Parse(data);
using(MemoryStream actualOutput = new MemoryStream()) using var actualOutput = new MemoryStream();
using(Stream expectedOutput = File.OpenRead("test-files/plist.bin"))
using(ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput)) using Stream expectedOutput = File.OpenRead("test-files/plist.bin");
using var validatingStream = new ValidatingStream(actualOutput, expectedOutput);
var writer = new BinaryPropertyListWriter(validatingStream)
{ {
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream); ReuseObjectIds = false
writer.ReuseObjectIds = false; };
writer.Write(root); writer.Write(root);
} }
} }
} }
}

View File

@@ -32,8 +32,8 @@ namespace plistcil.test
public static class IssueTest public static class IssueTest
{ {
/// <summary> /// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario /// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario where the binary
/// where the binary data is not indented (no leading whitespace). /// data is not indented (no leading whitespace).
/// </summary> /// </summary>
[Fact] [Fact]
public static void RoundtripDataTest() public static void RoundtripDataTest()
@@ -46,15 +46,14 @@ namespace plistcil.test
} }
/// <summary> /// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario /// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario where the binary
/// where the binary data is indented. /// data is indented.
/// </summary> /// </summary>
[Fact] [Fact]
public static void RoundtripDataTest2() public static void RoundtripDataTest2()
{ {
string expected = File.ReadAllText(@"test-files/RoundtripBinaryIndentation.plist"); string expected = File.ReadAllText(@"test-files/RoundtripBinaryIndentation.plist");
NSObject value = NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinaryIndentation.plist"));
XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinaryIndentation.plist"));
string actual = value.ToXmlPropertyList(); string actual = value.ToXmlPropertyList();
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
@@ -90,7 +89,7 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestIssue18() public static void TestIssue18()
{ {
NSNumber x = new NSNumber(-999); var x = new NSNumber(-999);
PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testIssue18.plist")); PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testIssue18.plist"));
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testIssue18.plist")); NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testIssue18.plist"));
Assert.True(x.Equals(y)); Assert.True(x.Equals(y));
@@ -106,14 +105,12 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestIssue22() public static void TestIssue22()
{ {
NSDictionary x1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji.plist")); var x1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji.plist"));
NSDictionary x2 = var x2 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji-xml.plist"));
(NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji-xml.plist"));
PropertyListParser.SaveAsBinary(x1, new FileInfo("test-files/out-testIssue22.plist")); PropertyListParser.SaveAsBinary(x1, new FileInfo("test-files/out-testIssue22.plist"));
NSDictionary y1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22.plist")); var y1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22.plist"));
PropertyListParser.SaveAsXml(x2, new FileInfo("test-files/out-testIssue22-xml.plist")); PropertyListParser.SaveAsXml(x2, new FileInfo("test-files/out-testIssue22-xml.plist"));
NSDictionary y2 = var y2 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22-xml.plist"));
(NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22-xml.plist"));
Assert.True(x1.Equals(x2)); Assert.True(x1.Equals(x2));
Assert.True(x1.Equals(y1)); Assert.True(x1.Equals(y1));
Assert.True(x1.Equals(y2)); Assert.True(x1.Equals(y2));
@@ -132,7 +129,7 @@ namespace plistcil.test
public static void TestIssue30() public static void TestIssue30()
{ {
#pragma warning disable 219 #pragma warning disable 219
NSArray arr = (NSArray)PropertyListParser.Parse(new FileInfo("test-files/issue30.plist")); var arr = (NSArray)PropertyListParser.Parse(new FileInfo("test-files/issue30.plist"));
#pragma warning restore 219 #pragma warning restore 219
} }
@@ -140,30 +137,32 @@ namespace plistcil.test
public static void TestIssue33() public static void TestIssue33()
{ {
#pragma warning disable 219 #pragma warning disable 219
NSDictionary dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj")); var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj"));
#pragma warning restore 219 #pragma warning restore 219
} }
[Fact] [Fact]
public static void TestIssue38() public static void TestIssue38()
{ {
NSDictionary dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj")); var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj"));
NSObject fileRef = NSObject fileRef =
((NSDictionary)((NSDictionary)dict.Get("objects")).Get("65541A9C16D13B8C00A968D5")).Get("fileRef"); ((NSDictionary)((NSDictionary)dict.Get("objects")).Get("65541A9C16D13B8C00A968D5")).Get("fileRef");
Assert.True(fileRef.Equals(new NSString("65541A9B16D13B8C00A968D5"))); Assert.True(fileRef.Equals(new NSString("65541A9B16D13B8C00A968D5")));
} }
[Fact] [Fact]
public static void TestIssue4() public static void TestIssue4()
{ {
NSDictionary d = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue4.plist")); var d = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue4.plist"));
Assert.Equal("Kid\u2019s iPhone", ((NSString)d.ObjectForKey("Device Name")).ToString()); Assert.Equal("Kid\u2019s iPhone", ((NSString)d.ObjectForKey("Device Name")).ToString());
} }
[Fact] [Fact]
public static void TestIssue49() public static void TestIssue49()
{ {
NSDictionary dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue49.plist")); var dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue49.plist"));
Assert.Empty(dict); Assert.Empty(dict);
} }
@@ -181,8 +180,7 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestRealInResourceRule() public static void TestRealInResourceRule()
{ {
NSDictionary dict = var dict = (NSDictionary)XmlPropertyListParser.Parse(new FileInfo("test-files/ResourceRules.plist"));
(NSDictionary)XmlPropertyListParser.Parse(new FileInfo("test-files/ResourceRules.plist"));
Assert.Single(dict); Assert.Single(dict);
Assert.True(dict.ContainsKey("weight")); Assert.True(dict.ContainsKey("weight"));

View File

@@ -6,30 +6,30 @@ namespace plistcil.test
{ {
public class NSArrayTests public class NSArrayTests
{ {
/// <summary> /// <summary>Tests the addition of a .NET object to the NSArray</summary>
/// Tests the addition of a .NET object to the NSArray
/// </summary>
[Fact] [Fact]
public void AddAndContainsObjectTest() public void AddAndContainsObjectTest()
{ {
NSArray array = new NSArray(); var array = new NSArray
array.Add(1); {
1
};
Assert.True(array.Contains(1)); Assert.True(array.Contains(1));
Assert.False(array.Contains(2)); Assert.False(array.Contains(2));
} }
/// <summary> /// <summary>Tests the <see cref="NSArray.GetEnumerator" /> method.</summary>
/// Tests the <see cref="NSArray.GetEnumerator" /> method.
/// </summary>
[Fact] [Fact]
public void EnumeratorTest() public void EnumeratorTest()
{ {
NSArray array = new NSArray(); var array = new NSArray
array.Add(0); {
array.Add(1); 0,
1
};
IEnumerator<NSObject> enumerator = array.GetEnumerator(); using IEnumerator<NSObject> enumerator = array.GetEnumerator();
Assert.Null(enumerator.Current); Assert.Null(enumerator.Current);
@@ -42,31 +42,30 @@ namespace plistcil.test
Assert.False(enumerator.MoveNext()); Assert.False(enumerator.MoveNext());
} }
/// <summary> /// <summary>Tests the <see cref="NSArray.IndexOf(object)" /> method for .NET objects.</summary>
/// Tests the <see cref="NSArray.IndexOf(object)" /> method for .NET objects.
/// </summary>
[Fact] [Fact]
public void IndexOfTest() public void IndexOfTest()
{ {
NSArray array = new NSArray(); var array = new NSArray
array.Add(1); {
array.Add("test"); 1,
"test"
};
Assert.Equal(0, array.IndexOf(1)); Assert.Equal(0, array.IndexOf(1));
Assert.Equal(1, array.IndexOf("test")); Assert.Equal(1, array.IndexOf("test"));
} }
/// <summary> /// <summary>Tests the <see cref="NSArray.Insert(int, object)" /> method for a .NET object.</summary>
/// Tests the <see cref="NSArray.Insert(int, object)" /> method for a
/// .NET object.
/// </summary>
[Fact] [Fact]
public void InsertTest() public void InsertTest()
{ {
NSArray array = new NSArray(); var array = new NSArray
array.Add(0); {
array.Add(1); 0,
array.Add(2); 1,
2
};
array.Insert(1, "test"); array.Insert(1, "test");
@@ -74,14 +73,15 @@ namespace plistcil.test
Assert.Equal("test", array[1].ToObject()); Assert.Equal("test", array[1].ToObject());
} }
/// <summary> /// <summary>Tests the <see cref="NSArray.Remove(object)" /> method for a .NET object.</summary>
/// Tests the <see cref="NSArray.Remove(object)" /> method for a .NET object.
/// </summary>
[Fact] [Fact]
public void RemoveTest() public void RemoveTest()
{ {
NSArray array = new NSArray(); var array = new NSArray
array.Add(0); {
0
};
Assert.False(array.Remove((object)1)); Assert.False(array.Remove((object)1));
Assert.True(array.Remove((object)0)); Assert.True(array.Remove((object)0));

View File

@@ -9,15 +9,15 @@ namespace plistcil.test
[Fact] [Fact]
public static void ConstructorTest() public static void ConstructorTest()
{ {
NSDate actual = new NSDate("2000-01-01T00:00:00Z"); var actual = new NSDate("2000-01-01T00:00:00Z");
DateTime expected = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); var expected = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
Assert.Equal(expected, actual.Date.ToUniversalTime()); Assert.Equal(expected, actual.Date.ToUniversalTime());
} }
[Fact] [Fact]
public static void MakeDateStringTest() public static void MakeDateStringTest()
{ {
DateTime date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); var date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
string expected = "2000-01-01T00:00:00Z"; string expected = "2000-01-01T00:00:00Z";
string actual = NSDate.MakeDateString(date); string actual = NSDate.MakeDateString(date);

View File

@@ -1,72 +1,187 @@
using Claunia.PropertyList; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test
{ {
public class NSNumberTests public class NSNumberTests
{ {
public static IEnumerable<object[]> SpanConstructorTestData() public static IEnumerable<object[]> SpanConstructorTestData() => new List<object[]>
{
return new List<object[]>
{ {
// INTEGER values // INTEGER values
// 0 // 0
new object[] { new byte[] { 0x00 }, NSNumber.INTEGER, false, 0, 0.0 }, new object[]
{
new byte[]
{
0x00
},
NSNumber.INTEGER, false, 0, 0.0
},
// 1-byte value < sbyte.maxValue // 1-byte value < sbyte.maxValue
new object[] { new byte[] { 0x10 }, NSNumber.INTEGER, true, 16, 16.0 }, new object[]
{
new byte[]
{
0x10
},
NSNumber.INTEGER, true, 16, 16.0
},
// 1-byte value > sbyte.MaxValue // 1-byte value > sbyte.MaxValue
new object[] { new byte[] { 0xFF }, NSNumber.INTEGER, true, byte.MaxValue, (double)byte.MaxValue}, new object[]
{
new byte[]
{
0xFF
},
NSNumber.INTEGER, true, byte.MaxValue, (double)byte.MaxValue
},
// 2-byte value < short.maxValue // 2-byte value < short.maxValue
new object[] { new byte[] { 0x10, 0x00 }, NSNumber.INTEGER, true, 4096, 4096.0 }, new object[]
{
new byte[]
{
0x10, 0x00
},
NSNumber.INTEGER, true, 4096, 4096.0
},
// 2-byte value > short.maxValue // 2-byte value > short.maxValue
new object[] { new byte[] { 0xFF, 0xFF }, NSNumber.INTEGER, true, ushort.MaxValue, (double)ushort.MaxValue}, new object[]
{
new byte[]
{
0xFF, 0xFF
},
NSNumber.INTEGER, true, ushort.MaxValue, (double)ushort.MaxValue
},
// 4-byte value < int.maxValue // 4-byte value < int.maxValue
new object[] { new byte[] { 0x10, 0x00, 0x00, 0x00 }, NSNumber.INTEGER, true, 0x10000000, 1.0 * 0x10000000 }, new object[]
{
new byte[]
{
0x10, 0x00, 0x00, 0x00
},
NSNumber.INTEGER, true, 0x10000000, 1.0 * 0x10000000
},
// 4-bit value > int.MaxValue // 4-bit value > int.MaxValue
new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, NSNumber.INTEGER, true, uint.MaxValue, (double)uint.MaxValue }, new object[]
{
new byte[]
{
0xFF, 0xFF, 0xFF, 0xFF
},
NSNumber.INTEGER, true, uint.MaxValue, (double)uint.MaxValue
},
// 64-bit value < long.MaxValue // 64-bit value < long.MaxValue
new object[] { new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, NSNumber.INTEGER, true, 0x1000000000000000, 1.0 * 0x1000000000000000 }, new object[]
{
new byte[]
{
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
},
NSNumber.INTEGER, true, 0x1000000000000000, 1.0 * 0x1000000000000000
},
// 64-bit value > long.MaxValue // 64-bit value > long.MaxValue
new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, NSNumber.INTEGER, true, -1, -1.0 }, new object[]
{
new byte[]
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
},
NSNumber.INTEGER, true, -1, -1.0
},
// 128-bit positive value // 128-bit positive value
new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x00 }, NSNumber.INTEGER, true, unchecked((long)0xffffffffffffa000), 1.0 * unchecked((long)0xffffffffffffa000) }, new object[]
{
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa0, 0x00
},
NSNumber.INTEGER, true, unchecked((long)0xffffffffffffa000), 1.0 * unchecked((long)0xffffffffffffa000)
},
// 128-bit negative value // 128-bit negative value
new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, NSNumber.INTEGER, true, -1, -1.0 }, new object[]
{
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
},
NSNumber.INTEGER, true, -1, -1.0
},
// REAL values // REAL values
// 4-byte value (float) // 4-byte value (float)
new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00 }, NSNumber.REAL, false, 0, 0.0 }, new object[]
{
new byte[]
{
0x00, 0x00, 0x00, 0x00
},
NSNumber.REAL, false, 0, 0.0
},
new object[] { new byte[] { 0x41, 0x20, 0x00, 0x00 }, NSNumber.REAL, true, 10, 10.0 }, new object[]
{
new byte[]
{
0x41, 0x20, 0x00, 0x00
},
NSNumber.REAL, true, 10, 10.0
},
new object[] { new byte[] { 0x3d, 0xcc, 0xcc, 0xcd }, NSNumber.REAL, false, 0, 0.1 }, new object[]
{
new byte[]
{
0x3d, 0xcc, 0xcc, 0xcd
},
NSNumber.REAL, false, 0, 0.1
},
// 8-byte value (double) // 8-byte value (double)
new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, NSNumber.REAL, false, 0, 0.0 }, new object[]
{
new byte[]
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
},
NSNumber.REAL, false, 0, 0.0
},
new object[] { new byte[] { 0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, NSNumber.REAL, true, 10, 10.0 }, new object[]
{
new byte[]
{
0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
},
NSNumber.REAL, true, 10, 10.0
},
new object[] { new byte[] { 0x3f, 0xb9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a }, NSNumber.REAL, false, 0, 0.1 } new object[]
}; {
new byte[]
{
0x3f, 0xb9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a
},
NSNumber.REAL, false, 0, 0.1
} }
};
[Theory] [Theory, MemberData(nameof(SpanConstructorTestData))]
[MemberData(nameof(SpanConstructorTestData))]
public void SpanConstructorTest(byte[] data, int type, bool boolValue, long longValue, double doubleValue) public void SpanConstructorTest(byte[] data, int type, bool boolValue, long longValue, double doubleValue)
{ {
NSNumber number = new NSNumber((Span<byte>)data, type); var number = new NSNumber((Span<byte>)data, type);
Assert.Equal(boolValue, number.ToBool()); Assert.Equal(boolValue, number.ToBool());
Assert.Equal(longValue, number.ToLong()); Assert.Equal(longValue, number.ToLong());
Assert.Equal(doubleValue, number.ToDouble(), 5); Assert.Equal(doubleValue, number.ToDouble(), 5);
@@ -77,7 +192,10 @@ namespace plistcil.test
{ {
Assert.Throws<ArgumentNullException>(() => new NSNumber((Span<byte>)null, NSNumber.INTEGER)); Assert.Throws<ArgumentNullException>(() => new NSNumber((Span<byte>)null, NSNumber.INTEGER));
Assert.Throws<ArgumentNullException>(() => new NSNumber((Span<byte>)null, NSNumber.REAL)); Assert.Throws<ArgumentNullException>(() => new NSNumber((Span<byte>)null, NSNumber.REAL));
Assert.Throws<ArgumentOutOfRangeException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), NSNumber.INTEGER));
Assert.Throws<ArgumentOutOfRangeException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(),
NSNumber.INTEGER));
Assert.Throws<ArgumentException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), NSNumber.REAL)); Assert.Throws<ArgumentException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), NSNumber.REAL));
Assert.Throws<ArgumentException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), 9)); Assert.Throws<ArgumentException>(() => new NSNumber((Span<byte>)Array.Empty<byte>(), 9));
} }
@@ -93,7 +211,7 @@ namespace plistcil.test
[Fact] [Fact]
public static void NSNumberConstructorTest() public static void NSNumberConstructorTest()
{ {
NSNumber number = new NSNumber("10032936613", NSNumber.INTEGER); var number = new NSNumber("10032936613", NSNumber.INTEGER);
Assert.Equal(NSNumber.INTEGER, number.GetNSNumberType()); Assert.Equal(NSNumber.INTEGER, number.GetNSNumberType());
Assert.Equal(10032936613, number.ToObject()); Assert.Equal(10032936613, number.ToObject());
} }
@@ -101,7 +219,7 @@ namespace plistcil.test
[Fact] [Fact]
public static void NSNumberWithDecimalTest() public static void NSNumberWithDecimalTest()
{ {
NSNumber number = new NSNumber("1360155352.748765", NSNumber.REAL); var number = new NSNumber("1360155352.748765", NSNumber.REAL);
Assert.Equal("1360155352.748765", number.ToString()); Assert.Equal("1360155352.748765", number.ToString());
} }
@@ -110,92 +228,147 @@ namespace plistcil.test
// The value being used comes seen in a real property list: // The value being used comes seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
[Fact] [Fact, UseCulture("en-US")]
[UseCulture("en-US")]
public static void ParseNumberEnTest() public static void ParseNumberEnTest()
{ {
NSNumber number = new NSNumber("7200.000001"); var number = new NSNumber("7200.000001");
Assert.True(number.isReal()); Assert.True(number.isReal());
Assert.Equal(7200.000001d, number.ToDouble()); Assert.Equal(7200.000001d, number.ToDouble());
} }
[Fact] [Fact, UseCulture("nl-BE")]
[UseCulture("nl-BE")]
public static void ParseNumberNlTest() public static void ParseNumberNlTest()
{ {
// As seen in a real property list: // As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
NSNumber number = new NSNumber("7200.000001"); var number = new NSNumber("7200.000001");
Assert.True(number.isReal()); Assert.True(number.isReal());
Assert.Equal(7200.000001d, number.ToDouble()); Assert.Equal(7200.000001d, number.ToDouble());
} }
[Fact] [Fact, UseCulture("en-US")]
[UseCulture("en-US")]
public static void ParseNumberEnTest2() public static void ParseNumberEnTest2()
{ {
// As seen in a real property list: // As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
NSNumber number = new NSNumber("7200.000000", NSNumber.REAL); var number = new NSNumber("7200.000000", NSNumber.REAL);
Assert.True(number.isReal()); Assert.True(number.isReal());
Assert.Equal(7200d, number.ToDouble()); Assert.Equal(7200d, number.ToDouble());
} }
[Fact] [Fact, UseCulture("nl-BE")]
[UseCulture("nl-BE")]
public static void ParseNumberNlTest2() public static void ParseNumberNlTest2()
{ {
// As seen in a real property list: // As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
NSNumber number = new NSNumber("7200.000000", NSNumber.REAL); var number = new NSNumber("7200.000000", NSNumber.REAL);
Assert.True(number.isReal()); Assert.True(number.isReal());
Assert.Equal(7200d, number.ToDouble()); Assert.Equal(7200d, number.ToDouble());
} }
public static IEnumerable<object[]> StringConstructorTestData() public static IEnumerable<object[]> StringConstructorTestData() => new List<object[]>
{
return new List<object[]>
{ {
// Long values, formatted as hexadecimal values // Long values, formatted as hexadecimal values
new object[] { "0x00", false, 0, 0.0 }, new object[]
new object[] { "0x1000", true, 0x1000, 1.0 * 0x1000 }, {
new object[] { "0x00001000", true, 0x1000, 1.0 * 0x1000 }, "0x00", false, 0, 0.0
new object[] { "0x0000000000001000", true, 0x1000, 1.0 * 0x1000 }, },
new object[]
{
"0x1000", true, 0x1000, 1.0 * 0x1000
},
new object[]
{
"0x00001000", true, 0x1000, 1.0 * 0x1000
},
new object[]
{
"0x0000000000001000", true, 0x1000, 1.0 * 0x1000
},
// Long values, formatted as decimal values // Long values, formatted as decimal values
new object[] { "0", false, 0, 0.0 }, new object[]
new object[] { "10", true, 10, 10.0 }, {
"0", false, 0, 0.0
},
new object[]
{
"10", true, 10, 10.0
},
// Decimal values // Decimal values
new object[] { "0.0", false, 0, 0.0 }, new object[]
new object[] { "0.10", false, 0, 0.1 }, {
new object[] { "3.14", true, 3, 3.14 }, "0.0", false, 0, 0.0
},
new object[]
{
"0.10", false, 0, 0.1
},
new object[]
{
"3.14", true, 3, 3.14
},
// Boolean values // Boolean values
new object[] { "yes", true, 1, 1}, new object[]
new object[] { "true", true, 1, 1}, {
new object[] { "Yes", true, 1, 1}, "yes", true, 1, 1
new object[] { "True", true, 1, 1}, },
new object[] { "YES", true, 1, 1}, new object[]
new object[] { "TRUE", true, 1, 1}, {
"true", true, 1, 1
},
new object[]
{
"Yes", true, 1, 1
},
new object[]
{
"True", true, 1, 1
},
new object[]
{
"YES", true, 1, 1
},
new object[]
{
"TRUE", true, 1, 1
},
new object[] { "no", false, 0, 0}, new object[]
new object[] { "false", false, 0, 0}, {
new object[] { "No", false, 0, 0}, "no", false, 0, 0
new object[] { "False", false, 0, 0}, },
new object[] { "NO", false, 0, 0}, new object[]
new object[] { "FALSE", false, 0, 0}, {
}; "false", false, 0, 0
},
new object[]
{
"No", false, 0, 0
},
new object[]
{
"False", false, 0, 0
},
new object[]
{
"NO", false, 0, 0
},
new object[]
{
"FALSE", false, 0, 0
} }
};
[Theory] [Theory, MemberData(nameof(StringConstructorTestData))]
[MemberData(nameof(StringConstructorTestData))]
public void StringConstructorTest(string value, bool boolValue, long longValue, double doubleValue) public void StringConstructorTest(string value, bool boolValue, long longValue, double doubleValue)
{ {
NSNumber number = new NSNumber(value); var number = new NSNumber(value);
Assert.Equal(boolValue, number.ToBool()); Assert.Equal(boolValue, number.ToBool());
Assert.Equal(longValue, number.ToLong()); Assert.Equal(longValue, number.ToLong());
Assert.Equal(doubleValue, number.ToDouble(), 5); Assert.Equal(doubleValue, number.ToDouble(), 5);
@@ -208,90 +381,129 @@ namespace plistcil.test
Assert.Throws<ArgumentException>(() => new NSNumber("plist")); Assert.Throws<ArgumentException>(() => new NSNumber("plist"));
} }
public static IEnumerable<object[]> Int32ConstructorTestData() public static IEnumerable<object[]> Int32ConstructorTestData() => new List<object[]>
{
return new List<object[]>
{ {
// Long values, formatted as hexadecimal values // Long values, formatted as hexadecimal values
new object[] { 0, false, 0, 0.0 }, new object[]
new object[] { 1, true, 1, 1.0 }, {
new object[] { -1, true, -1, -1.0 }, 0, false, 0, 0.0
new object[] { int.MaxValue, true, int.MaxValue, int.MaxValue }, },
new object[] { int.MinValue, true, int.MinValue, int.MinValue }, new object[]
}; {
1, true, 1, 1.0
},
new object[]
{
-1, true, -1, -1.0
},
new object[]
{
int.MaxValue, true, int.MaxValue, int.MaxValue
},
new object[]
{
int.MinValue, true, int.MinValue, int.MinValue
} }
};
[Theory] [Theory, MemberData(nameof(Int32ConstructorTestData))]
[MemberData(nameof(Int32ConstructorTestData))]
public void Int32ConstructorTest(int value, bool boolValue, long longValue, double doubleValue) public void Int32ConstructorTest(int value, bool boolValue, long longValue, double doubleValue)
{ {
NSNumber number = new NSNumber(value); var number = new NSNumber(value);
Assert.Equal(boolValue, number.ToBool()); Assert.Equal(boolValue, number.ToBool());
Assert.Equal(longValue, number.ToLong()); Assert.Equal(longValue, number.ToLong());
Assert.Equal(doubleValue, number.ToDouble(), 5); Assert.Equal(doubleValue, number.ToDouble(), 5);
} }
public static IEnumerable<object[]> Int64ConstructorTestData() public static IEnumerable<object[]> Int64ConstructorTestData() => new List<object[]>
{
return new List<object[]>
{ {
// Long values, formatted as hexadecimal values // Long values, formatted as hexadecimal values
new object[] { 0, false, 0, 0.0 }, new object[]
new object[] { 1, true, 1, 1.0 }, {
new object[] { -1, true, -1, -1.0 }, 0, false, 0, 0.0
new object[] { long.MaxValue, true, long.MaxValue, long.MaxValue }, },
new object[] { long.MinValue, true, long.MinValue, long.MinValue }, new object[]
}; {
1, true, 1, 1.0
},
new object[]
{
-1, true, -1, -1.0
},
new object[]
{
long.MaxValue, true, long.MaxValue, long.MaxValue
},
new object[]
{
long.MinValue, true, long.MinValue, long.MinValue
} }
};
[Theory] [Theory, MemberData(nameof(Int64ConstructorTestData))]
[MemberData(nameof(Int64ConstructorTestData))]
public void Int64ConstructorTest(long value, bool boolValue, long longValue, double doubleValue) public void Int64ConstructorTest(long value, bool boolValue, long longValue, double doubleValue)
{ {
NSNumber number = new NSNumber(value); var number = new NSNumber(value);
Assert.Equal(boolValue, number.ToBool()); Assert.Equal(boolValue, number.ToBool());
Assert.Equal(longValue, number.ToLong()); Assert.Equal(longValue, number.ToLong());
Assert.Equal(doubleValue, number.ToDouble(), 5); Assert.Equal(doubleValue, number.ToDouble(), 5);
} }
public static IEnumerable<object[]> DoubleConstructorTestData() public static IEnumerable<object[]> DoubleConstructorTestData() => new List<object[]>
{
return new List<object[]>
{ {
// Long values, formatted as hexadecimal values // Long values, formatted as hexadecimal values
new object[] { 0.0, false, 0, 0.0 }, new object[]
new object[] { 1.0, true, 1, 1.0 }, {
new object[] { -1.0, true, -1, -1.0 }, 0.0, false, 0, 0.0
new object[] { double.Epsilon, false, 0, double.Epsilon }, },
new object[] { double.MaxValue, true, long.MinValue /* Overflow! */, double.MaxValue }, new object[]
new object[] { double.MinValue, true, long.MinValue, double.MinValue }, {
}; 1.0, true, 1, 1.0
},
new object[]
{
-1.0, true, -1, -1.0
},
new object[]
{
double.Epsilon, false, 0, double.Epsilon
},
new object[]
{
double.MaxValue, true, long.MinValue /* Overflow! */, double.MaxValue
},
new object[]
{
double.MinValue, true, long.MinValue, double.MinValue
} }
};
[Theory] [Theory, MemberData(nameof(DoubleConstructorTestData))]
[MemberData(nameof(DoubleConstructorTestData))]
public void DoubleConstructorTest(double value, bool boolValue, long longValue, double doubleValue) public void DoubleConstructorTest(double value, bool boolValue, long longValue, double doubleValue)
{ {
NSNumber number = new NSNumber(value); var number = new NSNumber(value);
Assert.Equal(boolValue, number.ToBool()); Assert.Equal(boolValue, number.ToBool());
Assert.Equal(longValue, number.ToLong()); Assert.Equal(longValue, number.ToLong());
Assert.Equal(doubleValue, number.ToDouble(), 5); Assert.Equal(doubleValue, number.ToDouble(), 5);
} }
public static IEnumerable<object[]> BoolConstructorTestData()
{ public static IEnumerable<object[]> BoolConstructorTestData() => new List<object[]>
return new List<object[]>
{ {
// Long values, formatted as hexadecimal values // Long values, formatted as hexadecimal values
new object[] { false, false, 0, 0.0 }, new object[]
new object[] { true, true, 1, 1.0 }, {
}; false, false, 0, 0.0
},
new object[]
{
true, true, 1, 1.0
} }
};
[Theory] [Theory, MemberData(nameof(BoolConstructorTestData))]
[MemberData(nameof(BoolConstructorTestData))]
public void BoolConstructorTest(bool value, bool boolValue, long longValue, double doubleValue) public void BoolConstructorTest(bool value, bool boolValue, long longValue, double doubleValue)
{ {
NSNumber number = new NSNumber(value); var number = new NSNumber(value);
Assert.Equal(boolValue, number.ToBool()); Assert.Equal(boolValue, number.ToBool());
Assert.Equal(longValue, number.ToLong()); Assert.Equal(longValue, number.ToLong());
Assert.Equal(doubleValue, number.ToDouble(), 5); Assert.Equal(doubleValue, number.ToDouble(), 5);
@@ -300,8 +512,8 @@ namespace plistcil.test
[Fact] [Fact]
public void EqualTest() public void EqualTest()
{ {
NSNumber a = new NSNumber(2); var a = new NSNumber(2);
NSNumber b = new NSNumber(2); var b = new NSNumber(2);
Assert.Equal(a.GetHashCode(), b.GetHashCode()); Assert.Equal(a.GetHashCode(), b.GetHashCode());
Assert.True(a.Equals(b)); Assert.True(a.Equals(b));

View File

@@ -35,8 +35,9 @@ namespace plistcil.test
{ {
static bool ArrayEquals(byte[] arrayA, byte[] arrayB) static bool ArrayEquals(byte[] arrayA, byte[] arrayB)
{ {
if(arrayA.Length == arrayB.Length) if(arrayA.Length != arrayB.Length)
{ return false;
for(int i = 0; i < arrayA.Length; i++) for(int i = 0; i < arrayA.Length; i++)
if(arrayA[i] != arrayB[i]) if(arrayA[i] != arrayB[i])
return false; return false;
@@ -44,9 +45,6 @@ namespace plistcil.test
return true; return true;
} }
return false;
}
/** /**
* NSSet only occurs in binary property lists, so we have to test it separately. * NSSet only occurs in binary property lists, so we have to test it separately.
* NSSets are not yet supported in reading/writing, as binary property list format v1+ is required. * NSSets are not yet supported in reading/writing, as binary property list format v1+ is required.
@@ -73,23 +71,26 @@ namespace plistcil.test
NSObject ParsedRoot = PropertyListParser.Parse(new FileInfo("test-files/out-testSet.plist")); NSObject ParsedRoot = PropertyListParser.Parse(new FileInfo("test-files/out-testSet.plist"));
Assert.True(ParsedRoot.Equals(dict)); Assert.True(ParsedRoot.Equals(dict));
}*/ }*/
[Fact] [Fact]
public static void TestASCII() public static void TestASCII()
{ {
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii.plist")); NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii.plist"));
NSDictionary d = (NSDictionary)x; var d = (NSDictionary)x;
Assert.True(d.Count == 5); Assert.True(d.Count == 5);
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
NSDate actualDate = (NSDate)d.ObjectForKey("date"); var actualDate = (NSDate)d.ObjectForKey("date");
DateTime expectedDate = new DateTime(2011, 11, 28, 9, 21, 30, DateTimeKind.Utc).ToLocalTime(); DateTime expectedDate = new DateTime(2011, 11, 28, 9, 21, 30, DateTimeKind.Utc).ToLocalTime();
Assert.Equal(actualDate.Date, expectedDate); Assert.Equal(actualDate.Date, expectedDate);
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
new byte[] {0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82})); Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[]
NSArray a = (NSArray)d.ObjectForKey("array"); {
0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82
}));
var a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4); Assert.True(a.Count == 4);
Assert.True(a[0].Equals(new NSString("YES"))); Assert.True(a[0].Equals(new NSString("YES")));
Assert.True(a[1].Equals(new NSString("NO"))); Assert.True(a[1].Equals(new NSString("NO")));
@@ -101,7 +102,7 @@ namespace plistcil.test
public static void testAsciiUtf8CharactersInQuotedString() public static void testAsciiUtf8CharactersInQuotedString()
{ {
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test-ascii-utf8.plist")); NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test-ascii-utf8.plist"));
NSDictionary d = (NSDictionary)x; var d = (NSDictionary)x;
Assert.Equal(2, d.Count); Assert.Equal(2, d.Count);
Assert.Equal("JÔÖú@2x.jpg", d.ObjectForKey("path").ToString()); Assert.Equal("JÔÖú@2x.jpg", d.ObjectForKey("path").ToString());
Assert.Equal("QÔÖú@2x 啕.jpg", d.ObjectForKey("Key QÔÖª@2x 䌡").ToString()); Assert.Equal("QÔÖú@2x 啕.jpg", d.ObjectForKey("Key QÔÖª@2x 䌡").ToString());
@@ -110,16 +111,16 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestASCIIWriting() public static void TestASCIIWriting()
{ {
FileInfo inf = new FileInfo("test-files/test1.plist"); var inf = new FileInfo("test-files/test1.plist");
FileInfo outf = new FileInfo("test-files/out-test1-ascii.plist"); var outf = new FileInfo("test-files/out-test1-ascii.plist");
FileInfo in2 = new FileInfo("test-files/test1-ascii.plist"); var in2 = new FileInfo("test-files/test1-ascii.plist");
NSDictionary x = (NSDictionary)PropertyListParser.Parse(inf); var x = (NSDictionary)PropertyListParser.Parse(inf);
PropertyListParser.SaveAsASCII(x, outf); PropertyListParser.SaveAsASCII(x, outf);
//Information gets lost when saving into the ASCII format (NSNumbers are converted to NSStrings) //Information gets lost when saving into the ASCII format (NSNumbers are converted to NSStrings)
NSDictionary y = (NSDictionary)PropertyListParser.Parse(outf); var y = (NSDictionary)PropertyListParser.Parse(outf);
NSDictionary z = (NSDictionary)PropertyListParser.Parse(in2); var z = (NSDictionary)PropertyListParser.Parse(in2);
Assert.True(y.Equals(z)); Assert.True(y.Equals(z));
} }
@@ -141,15 +142,20 @@ namespace plistcil.test
public static void TestGnuStepASCII() public static void TestGnuStepASCII()
{ {
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii-gnustep.plist")); NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii-gnustep.plist"));
NSDictionary d = (NSDictionary)x; var d = (NSDictionary)x;
Assert.True(d.Count == 5); Assert.True(d.Count == 5);
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30, Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30,
DateTimeKind.Utc).ToLocalTime())); DateTimeKind.Utc).ToLocalTime()));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
new byte[] {0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82})); Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[]
NSArray a = (NSArray)d.ObjectForKey("array"); {
0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82
}));
var a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4); Assert.True(a.Count == 4);
Assert.True(a[0].Equals(new NSNumber(true))); Assert.True(a[0].Equals(new NSNumber(true)));
Assert.True(a[1].Equals(new NSNumber(false))); Assert.True(a[1].Equals(new NSNumber(false)));
@@ -160,9 +166,9 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestGnuStepASCIIWriting() public static void TestGnuStepASCIIWriting()
{ {
FileInfo inf = new FileInfo("test-files/test1.plist"); var inf = new FileInfo("test-files/test1.plist");
FileInfo outf = new FileInfo("test-files/out-test1-ascii-gnustep.plist"); var outf = new FileInfo("test-files/out-test1-ascii-gnustep.plist");
NSDictionary x = (NSDictionary)PropertyListParser.Parse(inf); var x = (NSDictionary)PropertyListParser.Parse(inf);
PropertyListParser.SaveAsGnuStepASCII(x, outf); PropertyListParser.SaveAsGnuStepASCII(x, outf);
NSObject y = PropertyListParser.Parse(outf); NSObject y = PropertyListParser.Parse(outf);
Assert.True(x.Equals(y)); Assert.True(x.Equals(y));
@@ -178,77 +184,92 @@ namespace plistcil.test
long lng = 30000000000L; long lng = 30000000000L;
float flt = 124.3f; float flt = 124.3f;
double dbl = 32.0; double dbl = 32.0;
DateTime date = new DateTime(); var date = new DateTime();
string strg = "Hello World"; 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 List<object>(array);
Dictionary<string, object> map = new Dictionary<string, object>(); byte[] bytes =
{
0x00, 0xAF, 0xAF
};
object[] array =
{
bl, byt, shrt, i, lng, flt, dbl, date, strg, bytes
};
int[] array2 =
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3000
};
List<object> list = new(array);
Dictionary<string, object> map = new();
map.Add("int", i); map.Add("int", i);
map.Add("long", lng); map.Add("long", lng);
map.Add("date", date); map.Add("date", date);
NSObject WrappedO = NSObject.Wrap((object)bl); var WrappedO = NSObject.Wrap((object)bl);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO is (NSNumber));
Assert.True(WrappedO.ToObject().Equals(bl)); Assert.True(WrappedO.ToObject().Equals(bl));
WrappedO = NSObject.Wrap((object)byt); WrappedO = NSObject.Wrap((object)byt);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO is (NSNumber));
Assert.True((int)WrappedO.ToObject() == byt); Assert.True((int)WrappedO.ToObject() == byt);
WrappedO = NSObject.Wrap((object)shrt); WrappedO = NSObject.Wrap((object)shrt);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO is (NSNumber));
Assert.True((int)WrappedO.ToObject() == shrt); Assert.True((int)WrappedO.ToObject() == shrt);
WrappedO = NSObject.Wrap((object)i); WrappedO = NSObject.Wrap((object)i);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO is (NSNumber));
Assert.True((int)WrappedO.ToObject() == i); Assert.True((int)WrappedO.ToObject() == i);
WrappedO = NSObject.Wrap((object)lng); WrappedO = NSObject.Wrap((object)lng);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO is (NSNumber));
Assert.True((long)WrappedO.ToObject() == lng); Assert.True((long)WrappedO.ToObject() == lng);
WrappedO = NSObject.Wrap((object)flt); WrappedO = NSObject.Wrap((object)flt);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO is (NSNumber));
Assert.True((double)WrappedO.ToObject() == flt); Assert.True((double)WrappedO.ToObject() == flt);
WrappedO = NSObject.Wrap((object)dbl); WrappedO = NSObject.Wrap((object)dbl);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO is (NSNumber));
Assert.True((double)WrappedO.ToObject() == dbl); Assert.True((double)WrappedO.ToObject() == dbl);
WrappedO = NSObject.Wrap(date); WrappedO = NSObject.Wrap(date);
Assert.True(WrappedO.GetType().Equals(typeof(NSDate))); Assert.True(WrappedO is (NSDate));
Assert.True(((DateTime)WrappedO.ToObject()).Equals(date)); Assert.True(((DateTime)WrappedO.ToObject()).Equals(date));
WrappedO = NSObject.Wrap(strg); WrappedO = NSObject.Wrap(strg);
Assert.True(WrappedO.GetType().Equals(typeof(NSString))); Assert.True(WrappedO is (NSString));
Assert.Equal((string)WrappedO.ToObject(), strg); Assert.Equal((string)WrappedO.ToObject(), strg);
WrappedO = NSObject.Wrap((object)bytes); WrappedO = NSObject.Wrap((object)bytes);
Assert.True(WrappedO.GetType().Equals(typeof(NSData))); Assert.True(WrappedO is (NSData));
byte[] data = (byte[])WrappedO.ToObject(); byte[] data = (byte[])WrappedO.ToObject();
Assert.True(data.Length == bytes.Length); Assert.True(data.Length == bytes.Length);
for(int x = 0; x < bytes.Length; x++) 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); WrappedO = NSObject.Wrap((object)array);
Assert.True(WrappedO.GetType().Equals(typeof(NSArray))); Assert.True(WrappedO is (NSArray));
object[] objArray = (object[])WrappedO.ToObject(); object[] objArray = (object[])WrappedO.ToObject();
Assert.True(objArray.Length == array.Length); Assert.True(objArray.Length == array.Length);
WrappedO = NSObject.Wrap(array2); WrappedO = NSObject.Wrap(array2);
Assert.True(WrappedO.GetType().Equals(typeof(NSArray))); Assert.True(WrappedO is (NSArray));
Assert.True(((NSArray)WrappedO).Count == array2.Length); Assert.True(((NSArray)WrappedO).Count == array2.Length);
WrappedO = NSObject.Wrap((object)list); WrappedO = NSObject.Wrap((object)list);
Assert.True(WrappedO.GetType().Equals(typeof(NSArray))); Assert.True(WrappedO is (NSArray));
objArray = (object[])WrappedO.ToObject(); objArray = (object[])WrappedO.ToObject();
Assert.True(objArray.Length == array.Length); Assert.True(objArray.Length == array.Length);
WrappedO = NSObject.Wrap((object)map); WrappedO = NSObject.Wrap((object)map);
Assert.True(WrappedO.GetType().Equals(typeof(NSDictionary))); Assert.True(WrappedO is (NSDictionary));
NSDictionary dict = (NSDictionary)WrappedO; var dict = (NSDictionary)WrappedO;
Assert.True(((NSNumber)dict.ObjectForKey("int")).ToLong() == i); Assert.True(((NSNumber)dict.ObjectForKey("int")).ToLong() == i);
Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng); Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng);
Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date)); Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date));
@@ -272,17 +293,22 @@ namespace plistcil.test
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist")); NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist"));
// check the data in it // check the data in it
NSDictionary d = (NSDictionary)x; var d = (NSDictionary)x;
Assert.True(d.Count == 5); Assert.True(d.Count == 5);
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 10, 21, 30, Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 10, 21, 30,
DateTimeKind.Utc)) || DateTimeKind.Utc)) ||
((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30, ((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30,
DateTimeKind.Utc))); DateTimeKind.Utc)));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
new byte[] {0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82})); Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, new byte[]
NSArray a = (NSArray)d.ObjectForKey("array"); {
0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82
}));
var a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4); Assert.True(a.Count == 4);
Assert.True(a[0].Equals(new NSNumber(true))); Assert.True(a[0].Equals(new NSNumber(true)));
Assert.True(a[1].Equals(new NSNumber(false))); Assert.True(a[1].Equals(new NSNumber(false)));

View File

@@ -8,13 +8,13 @@ namespace plistcil.test
{ {
static void ParseEmptyStreamTestDelegate() static void ParseEmptyStreamTestDelegate()
{ {
using(MemoryStream stream = new MemoryStream()) PropertyListParser.Parse(stream); using var stream = new MemoryStream();
PropertyListParser.Parse(stream);
} }
[Fact] [Fact]
public static void ParseEmptyStreamTest() public static void ParseEmptyStreamTest() =>
{ Assert.Throws<PropertyListFormatException>(ParseEmptyStreamTestDelegate);
Assert.Throws<PropertyListFormatException>(() => ParseEmptyStreamTestDelegate());
}
} }
} }

View File

@@ -6,83 +6,120 @@ namespace plistcil.test
{ {
public class UIDTests public class UIDTests
{ {
[Theory] [Theory, InlineData(new byte[]
[InlineData(new byte[] {0xAB})] {
[InlineData(new byte[] {0xAB, 0xCD})] 0xAB
[InlineData(new byte[] {0xAB, 0xCD, 0xEF, 0xFE})] }), InlineData(new byte[]
[InlineData(new byte[] {0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB})] {
0xAB, 0xCD
}), InlineData(new byte[]
{
0xAB, 0xCD, 0xEF, 0xFE
}), InlineData(new byte[]
{
0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB
})]
public void UidFromArrayTest(byte[] array) public void UidFromArrayTest(byte[] array)
{ {
UID uid = new UID(array); var uid = new UID(array);
Assert.Equal(array, uid.Bytes); Assert.Equal(array, uid.Bytes);
} }
[Fact] [Fact]
public void BinaryRoundTripTest() public void BinaryRoundTripTest()
{ {
UID original = new UID(0xabcd); var original = new UID(0xabcd);
using var stream = new MemoryStream();
using(MemoryStream stream = new MemoryStream())
{
BinaryPropertyListWriter.Write(stream, original); BinaryPropertyListWriter.Write(stream, original);
stream.Position = 0; stream.Position = 0;
UID roundtrip = BinaryPropertyListParser.Parse(stream) as UID; var roundtrip = BinaryPropertyListParser.Parse(stream) as UID;
Assert.Equal(original.Bytes, roundtrip.Bytes); Assert.Equal(original.Bytes, roundtrip.Bytes);
} }
}
[Fact] [Fact]
public void ByteUidTest() public void ByteUidTest()
{ {
UID uid = new UID(0xAB); var uid = new UID(0xAB);
Assert.Equal(new byte[] {0xAB}, uid.Bytes);
Assert.Equal(new byte[]
{
0xAB
}, uid.Bytes);
Assert.Equal(0xABu, uid.ToUInt64()); Assert.Equal(0xABu, uid.ToUInt64());
} }
[Fact] [Fact]
public void IntUidTest() public void IntUidTest()
{ {
UID uid = new UID(0xABCDEF00); var uid = new UID(0xABCDEF00);
Assert.Equal(new byte[] {0xAB, 0xCD, 0xEF, 0x00}, uid.Bytes);
Assert.Equal(new byte[]
{
0xAB, 0xCD, 0xEF, 0x00
}, uid.Bytes);
Assert.Equal(0xABCDEF00, uid.ToUInt64()); Assert.Equal(0xABCDEF00, uid.ToUInt64());
} }
[Fact] [Fact]
public void LongUidTest() public void LongUidTest()
{ {
UID uid = new UID(0xABCDEF0000EFCDAB); var uid = new UID(0xABCDEF0000EFCDAB);
Assert.Equal(new byte[] {0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB}, uid.Bytes);
Assert.Equal(new byte[]
{
0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB
}, uid.Bytes);
Assert.Equal(0xABCDEF0000EFCDAB, uid.ToUInt64()); Assert.Equal(0xABCDEF0000EFCDAB, uid.ToUInt64());
} }
[Fact] [Fact]
public void UIntUidTest() public void UIntUidTest()
{ {
UID uid = new UID(0xABCDEF00u); var uid = new UID(0xABCDEF00u);
Assert.Equal(new byte[] {0xAB, 0xCD, 0xEF, 0x00}, uid.Bytes);
Assert.Equal(new byte[]
{
0xAB, 0xCD, 0xEF, 0x00
}, uid.Bytes);
Assert.Equal(0xABCDEF00u, uid.ToUInt64()); Assert.Equal(0xABCDEF00u, uid.ToUInt64());
} }
[Fact] [Fact]
public void ULongUidTest() public void ULongUidTest()
{ {
UID uid = new UID(0xABCDEF0000EFCDABu); var uid = new UID(0xABCDEF0000EFCDABu);
Assert.Equal(new byte[] {0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB}, uid.Bytes);
Assert.Equal(new byte[]
{
0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB
}, uid.Bytes);
Assert.Equal(0xABCDEF0000EFCDABu, uid.ToUInt64()); Assert.Equal(0xABCDEF0000EFCDABu, uid.ToUInt64());
} }
[Fact] [Fact]
public void UShortUidTest() public void UShortUidTest()
{ {
UID uid = new UID(0xABCDu); var uid = new UID(0xABCDu);
Assert.Equal(new byte[] {0xAB, 0xCD}, uid.Bytes);
Assert.Equal(new byte[]
{
0xAB, 0xCD
}, uid.Bytes);
Assert.Equal(0xABCDu, uid.ToUInt64()); Assert.Equal(0xABCDu, uid.ToUInt64());
} }
[Fact] [Fact]
public void XmlRoundTripTest() public void XmlRoundTripTest()
{ {
UID original = new UID(0xabcd); var original = new UID(0xabcd);
string plist = original.ToXmlPropertyList(); string plist = original.ToXmlPropertyList();

View File

@@ -5,9 +5,8 @@ using System.Threading;
using Xunit.Sdk; using Xunit.Sdk;
/// <summary> /// <summary>
/// Apply this attribute to your test method to replace the /// Apply this attribute to your test method to replace the <see cref="Thread.CurrentThread" />
/// <see cref="Thread.CurrentThread" /> <see cref="CultureInfo.CurrentCulture" /> and /// <see cref="CultureInfo.CurrentCulture" /> and <see cref="CultureInfo.CurrentUICulture" /> with another culture.
/// <see cref="CultureInfo.CurrentUICulture" /> with another culture.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class UseCultureAttribute : BeforeAfterTestAttribute public class UseCultureAttribute : BeforeAfterTestAttribute
@@ -18,22 +17,19 @@ public class UseCultureAttribute : BeforeAfterTestAttribute
CultureInfo originalCulture; CultureInfo originalCulture;
CultureInfo originalUICulture; CultureInfo originalUICulture;
/// <summary> /// <summary>Replaces the culture and UI culture of the current thread with <paramref name="culture" /></summary>
/// Replaces the culture and UI culture of the current thread with
/// <paramref name="culture" />
/// </summary>
/// <param name="culture">The name of the culture.</param> /// <param name="culture">The name of the culture.</param>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// This constructor overload uses <paramref name="culture" /> for both /// This constructor overload uses <paramref name="culture" /> for both <see cref="Culture" /> and
/// <see cref="Culture" /> and <see cref="UICulture" />. /// <see cref="UICulture" />.
/// </para> /// </para>
/// </remarks> /// </remarks>
public UseCultureAttribute(string culture) : this(culture, culture) {} public UseCultureAttribute(string culture) : this(culture, culture) {}
/// <summary> /// <summary>
/// Replaces the culture and UI culture of the current thread with /// Replaces the culture and UI culture of the current thread with <paramref name="culture" /> and
/// <paramref name="culture" /> and <paramref name="uiCulture" /> /// <paramref name="uiCulture" />
/// </summary> /// </summary>
/// <param name="culture">The name of the culture.</param> /// <param name="culture">The name of the culture.</param>
/// <param name="uiCulture">The name of the UI culture.</param> /// <param name="uiCulture">The name of the UI culture.</param>
@@ -43,20 +39,15 @@ public class UseCultureAttribute : BeforeAfterTestAttribute
this.uiCulture = new Lazy<CultureInfo>(() => new CultureInfo(uiCulture)); this.uiCulture = new Lazy<CultureInfo>(() => new CultureInfo(uiCulture));
} }
/// <summary> /// <summary>Gets the culture.</summary>
/// Gets the culture.
/// </summary>
public CultureInfo Culture => culture.Value; public CultureInfo Culture => culture.Value;
/// <summary> /// <summary>Gets the UI culture.</summary>
/// Gets the UI culture.
/// </summary>
public CultureInfo UICulture => uiCulture.Value; public CultureInfo UICulture => uiCulture.Value;
/// <summary> /// <summary>
/// Stores the current <see cref="Thread.CurrentPrincipal" /> /// Stores the current <see cref="Thread.CurrentPrincipal" /> <see cref="CultureInfo.CurrentCulture" /> and
/// <see cref="CultureInfo.CurrentCulture" /> and <see cref="CultureInfo.CurrentUICulture" /> /// <see cref="CultureInfo.CurrentUICulture" /> and replaces them with the new cultures defined in the constructor.
/// and replaces them with the new cultures defined in the constructor.
/// </summary> /// </summary>
/// <param name="methodUnderTest">The method under test</param> /// <param name="methodUnderTest">The method under test</param>
public override void Before(MethodInfo methodUnderTest) public override void Before(MethodInfo methodUnderTest)

View File

@@ -15,20 +15,14 @@ namespace plistcil.test
/// A <see cref="Stream" /> which writes its output to a <see cref="Stream" /> and validates that the data which /// 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. /// is being written to the output stream matches the data in a reference stream.
/// </summary> /// </summary>
class ValidatingStream : Stream internal class ValidatingStream : Stream
{ {
Stream expectedOutput; readonly Stream expectedOutput;
Stream output; readonly Stream output;
/// <summary> /// <summary>Initializes a new instance of the <see cref="ValidatingCompositeStream" /> class.</summary>
/// Initializes a new instance of the <see cref="ValidatingCompositeStream" /> class. /// <param name="output">The <see cref="Stream" /> to which to write data.</param>
/// </summary> /// <param name="expectedOutput">The reference stream for <paramref name="output" />.</param>
/// <param name="output">
/// The <see cref="Stream" /> to which to write data.
/// </param>
/// <param name="expectedOutput">
/// The reference stream for <paramref name="output" />.
/// </param>
public ValidatingStream(Stream output, Stream expectedOutput) public ValidatingStream(Stream output, Stream expectedOutput)
{ {
this.output = output ?? throw new ArgumentNullException(nameof(output)); this.output = output ?? throw new ArgumentNullException(nameof(output));
@@ -55,34 +49,21 @@ namespace plistcil.test
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Flush() public override void Flush() => output.Flush();
{
output.Flush();
}
/// <inheritdoc /> /// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count) public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
{
/// <inheritdoc />
public override Task<int>
ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
throw new NotSupportedException(); throw new NotSupportedException();
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
{
throw new NotSupportedException();
}
/// <inheritdoc /> /// <inheritdoc />
public override long Seek(long offset, SeekOrigin origin) public override void SetLength(long value) => throw new NotImplementedException();
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void SetLength(long value)
{
throw new NotImplementedException();
}
/// <inheritdoc /> /// <inheritdoc />
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)

View File

@@ -33,170 +33,95 @@ namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>
/// Parser for ASCII property lists. Supports Apple OS X/iOS and GnuStep/NeXTSTEP format. /// Parser for ASCII property lists. Supports Apple OS X/iOS and GnuStep/NeXTSTEP format. This parser is based on
/// This parser is based on the recursive descent paradigm, but the underlying grammar /// the recursive descent paradigm, but the underlying grammar is not explicitly defined.
/// is not explicitely 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> /// </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> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class ASCIIPropertyListParser public class ASCIIPropertyListParser
{ {
/// <summary> /// <summary>A space</summary>
/// A space
/// </summary>
public const char WHITESPACE_SPACE = ' '; public const char WHITESPACE_SPACE = ' ';
/// <summary> /// <summary>A tabulator</summary>
/// A tabulator
/// </summary>
public const char WHITESPACE_TAB = '\t'; public const char WHITESPACE_TAB = '\t';
/// <summary> /// <summary>A newline</summary>
/// A newline
/// </summary>
public const char WHITESPACE_NEWLINE = '\n'; public const char WHITESPACE_NEWLINE = '\n';
/// <summary> /// <summary>A carriage return</summary>
/// A carriage return
/// </summary>
public const char WHITESPACE_CARRIAGE_RETURN = '\r'; public const char WHITESPACE_CARRIAGE_RETURN = '\r';
/// <summary> /// <summary>Token of NSArray start</summary>
/// Token of NSArray start
/// </summary>
public const char ARRAY_BEGIN_TOKEN = '('; public const char ARRAY_BEGIN_TOKEN = '(';
/// <summary> /// <summary>Token of NSArray end</summary>
/// Token of NSArray end
/// </summary>
public const char ARRAY_END_TOKEN = ')'; public const char ARRAY_END_TOKEN = ')';
/// <summary> /// <summary>Token of NSArray item delimiter</summary>
/// Token of NSArray item delimiter
/// </summary>
public const char ARRAY_ITEM_DELIMITER_TOKEN = ','; public const char ARRAY_ITEM_DELIMITER_TOKEN = ',';
/// <summary> /// <summary>Token of NSDictionary start</summary>
/// Token of NSDictionary start
/// </summary>
public const char DICTIONARY_BEGIN_TOKEN = '{'; public const char DICTIONARY_BEGIN_TOKEN = '{';
/// <summary> /// <summary>Token of NSDictionary end</summary>
/// Token of NSDictionary end
/// </summary>
public const char DICTIONARY_END_TOKEN = '}'; public const char DICTIONARY_END_TOKEN = '}';
/// <summary> /// <summary>Token of NSDictionary assignment</summary>
/// Token of NSDictionary assignment
/// </summary>
public const char DICTIONARY_ASSIGN_TOKEN = '='; public const char DICTIONARY_ASSIGN_TOKEN = '=';
/// <summary> /// <summary>Token of NSDictionary item delimiter</summary>
/// Token of NSDictionary item delimiter
/// </summary>
public const char DICTIONARY_ITEM_DELIMITER_TOKEN = ';'; public const char DICTIONARY_ITEM_DELIMITER_TOKEN = ';';
/// <summary> /// <summary>Token of quoted NSString start</summary>
/// Token of quoted NSString start
/// </summary>
public const char QUOTEDSTRING_BEGIN_TOKEN = '"'; public const char QUOTEDSTRING_BEGIN_TOKEN = '"';
/// <summary> /// <summary>Token of quoted NSString end</summary>
/// Token of quoted NSString end
/// </summary>
public const char QUOTEDSTRING_END_TOKEN = '"'; public const char QUOTEDSTRING_END_TOKEN = '"';
/// <summary> /// <summary>Token of quoted NSString escaped character</summary>
/// Token of quoted NSString escaped character
/// </summary>
public const char QUOTEDSTRING_ESCAPE_TOKEN = '\\'; public const char QUOTEDSTRING_ESCAPE_TOKEN = '\\';
/// <summary> /// <summary>Token of NSData start</summary>
/// Token of NSData start
/// </summary>
public const char DATA_BEGIN_TOKEN = '<'; public const char DATA_BEGIN_TOKEN = '<';
/// <summary> /// <summary>Token of NSData end</summary>
/// Token of NSData end
/// </summary>
public const char DATA_END_TOKEN = '>'; public const char DATA_END_TOKEN = '>';
/// <summary> /// <summary>Token of GSObject start</summary>
/// Token of GSObject start
/// </summary>
public const char DATA_GSOBJECT_BEGIN_TOKEN = '*'; public const char DATA_GSOBJECT_BEGIN_TOKEN = '*';
/// <summary> /// <summary>Token of GSDate start</summary>
/// Token of GSDate start
/// </summary>
public const char DATA_GSDATE_BEGIN_TOKEN = 'D'; public const char DATA_GSDATE_BEGIN_TOKEN = 'D';
/// <summary> /// <summary>Token of GSBoolean start</summary>
/// Token of GSBoolean start
/// </summary>
public const char DATA_GSBOOL_BEGIN_TOKEN = 'B'; public const char DATA_GSBOOL_BEGIN_TOKEN = 'B';
/// <summary> /// <summary>Token for GSBoolen's <c>true</c></summary>
/// Token for GSBoolen's <c>true</c>
/// </summary>
public const char DATA_GSBOOL_TRUE_TOKEN = 'Y'; public const char DATA_GSBOOL_TRUE_TOKEN = 'Y';
/// <summary> /// <summary>Token for GSBoolen's <c>false</c></summary>
/// Token for GSBoolen's <c>false</c>
/// </summary>
public const char DATA_GSBOOL_FALSE_TOKEN = 'N'; public const char DATA_GSBOOL_FALSE_TOKEN = 'N';
/// <summary> /// <summary>Token for GSInteger</summary>
/// Token for GSInteger
/// </summary>
public const char DATA_GSINT_BEGIN_TOKEN = 'I'; public const char DATA_GSINT_BEGIN_TOKEN = 'I';
/// <summary> /// <summary>Token for GSReal</summary>
/// Token for GSReal
/// </summary>
public const char DATA_GSREAL_BEGIN_TOKEN = 'R'; public const char DATA_GSREAL_BEGIN_TOKEN = 'R';
/// <summary> /// <summary>Token for NSDate date field delimited</summary>
/// Token for NSDate date field delimited
/// </summary>
public const char DATE_DATE_FIELD_DELIMITER = '-'; public const char DATE_DATE_FIELD_DELIMITER = '-';
/// <summary> /// <summary>Token for NSDate time field delimiter</summary>
/// Token for NSDate time field delimiter
/// </summary>
public const char DATE_TIME_FIELD_DELIMITER = ':'; public const char DATE_TIME_FIELD_DELIMITER = ':';
/// <summary> /// <summary>Token for GSDate date and time delimiter</summary>
/// Token for GSDate date and time delimiter
/// </summary>
public const char DATE_GS_DATE_TIME_DELIMITER = ' '; public const char DATE_GS_DATE_TIME_DELIMITER = ' ';
/// <summary> /// <summary>Token for NSDate date and time delimiter</summary>
/// Token for NSDate date and time delimiter
/// </summary>
public const char DATE_APPLE_DATE_TIME_DELIMITER = 'T'; public const char DATE_APPLE_DATE_TIME_DELIMITER = 'T';
/// <summary> /// <summary>Token for NSDate end</summary>
/// Token for NSDate end
/// </summary>
public const char DATE_APPLE_END_TOKEN = 'Z'; public const char DATE_APPLE_END_TOKEN = 'Z';
/// <summary> /// <summary>Token for comment start</summary>
/// Token for comment start
/// </summary>
public const char COMMENT_BEGIN_TOKEN = '/'; public const char COMMENT_BEGIN_TOKEN = '/';
/// <summary> /// <summary>Second token for multiline comment</summary>
/// Second token for multiline comment
/// </summary>
public const char MULTILINE_COMMENT_SECOND_TOKEN = '*'; public const char MULTILINE_COMMENT_SECOND_TOKEN = '*';
/// <summary> /// <summary>Second token for singleline comment</summary>
/// Second token for singleline comment
/// </summary>
public const char SINGLELINE_COMMENT_SECOND_TOKEN = '/'; public const char SINGLELINE_COMMENT_SECOND_TOKEN = '/';
/// <summary> /// <summary>End token for multiline comment</summary>
/// End token for multiline comment
/// </summary>
public const char MULTILINE_COMMENT_END_TOKEN = '/'; public const char MULTILINE_COMMENT_END_TOKEN = '/';
/** /**
* Property list source data * Property list source data
*/ */
char[] data; readonly char[] data;
/** /**
* Current parsing index * Current parsing index
*/ */
@@ -207,30 +132,18 @@ namespace Claunia.PropertyList
*/ */
protected ASCIIPropertyListParser() {} protected ASCIIPropertyListParser() {}
/// <summary> /// <summary>Creates a new parser for the given property list content.</summary>
/// Creates a new parser for the given property list content.
/// </summary>
/// <param name="propertyListContent">The content of the property list that is to be parsed.</param> /// <param name="propertyListContent">The content of the property list that is to be parsed.</param>
ASCIIPropertyListParser(char[] propertyListContent) ASCIIPropertyListParser(char[] propertyListContent) => data = propertyListContent;
{
data = propertyListContent;
}
/// <summary> /// <summary>Parses an ASCII property list file.</summary>
/// Parses an ASCII property list file.
/// </summary>
/// <param name="f">The ASCII property list file..</param> /// <param name="f">The ASCII property list file..</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="FormatException">When an error occurs during parsing.</exception> /// <exception cref="FormatException">When an error occurs during parsing.</exception>
/// <exception cref="IOException">When an error occured while reading from the input stream.</exception> /// <exception cref="IOException">When an error occured while reading from the input stream.</exception>
public static NSObject Parse(FileInfo f) public static NSObject Parse(FileInfo f) => Parse(f.OpenRead());
{
return Parse(f.OpenRead());
}
/// <summary> /// <summary>Parses an ASCII property list from an input stream.</summary>
/// Parses an ASCII property list from an input stream.
/// </summary>
/// <param name="fs">The input stream that points to the property list's data.</param> /// <param name="fs">The input stream that points to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="FormatException">When an error occurs during parsing.</exception> /// <exception cref="FormatException">When an error occurs during parsing.</exception>
@@ -238,38 +151,27 @@ namespace Claunia.PropertyList
public static NSObject Parse(Stream fs) public static NSObject Parse(Stream fs)
{ {
byte[] buf = PropertyListParser.ReadAll(fs); byte[] buf = PropertyListParser.ReadAll(fs);
// Don't close the stream - that would be the responisibility of code that class
// Don't close the stream - that would be the responsibility of code that class
// Parse // Parse
return Parse(buf); return Parse(buf);
} }
/// <summary> /// <summary>Parses an ASCII property list from a byte array.</summary>
/// Parses an ASCII property list from a byte array.
/// </summary>
/// <param name="bytes">The ASCII property list data.</param> /// <param name="bytes">The ASCII property list data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="FormatException">When an error occurs during parsing.</exception> /// <exception cref="FormatException">When an error occurs during parsing.</exception>
public static NSObject Parse(byte[] bytes) public static NSObject Parse(byte[] bytes) => Parse(bytes.AsSpan());
{
return Parse(bytes.AsSpan());
}
/// <summary> /// <summary>Parses an ASCII property list from a byte array.</summary>
/// Parses an ASCII property list from a byte array.
/// </summary>
/// <param name="bytes">The ASCII property list data.</param> /// <param name="bytes">The ASCII property list data.</param>
/// <param name="count">The offset at which to start reading the property list.</param> /// <param name="count">The offset at which to start reading the property list.</param>
/// <param name="offset">The length of the property list.</param> /// <param name="offset">The length of the property list.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="FormatException">When an error occurs during parsing.</exception> /// <exception cref="FormatException">When an error occurs during parsing.</exception>
public static NSObject Parse(byte[] bytes, int offset, int count) public static NSObject Parse(byte[] bytes, int offset, int count) => Parse(bytes.AsSpan(offset, count));
{
return Parse(bytes.AsSpan(offset, count));
}
/// <summary> /// <summary>Parses an ASCII property list from a byte span.</summary>
/// Parses an ASCII property list from a byte span.
/// </summary>
/// <param name="bytes">The ASCII property list data.</param> /// <param name="bytes">The ASCII property list data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="FormatException">When an error occurs during parsing.</exception> /// <exception cref="FormatException">When an error occurs during parsing.</exception>
@@ -282,21 +184,18 @@ namespace Claunia.PropertyList
#endif #endif
} }
/// <summary> /// <summary>Parses an ASCII property list from a string.</summary>
/// Parses an ASCII property list from a string.
/// </summary>
/// <param name="value">The ASCII property list data.</param> /// <param name="value">The ASCII property list data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="FormatException">When an error occurs during parsing.</exception> /// <exception cref="FormatException">When an error occurs during parsing.</exception>
public static NSObject ParseString(string value) public static NSObject ParseString(string value)
{ {
ASCIIPropertyListParser parser = new ASCIIPropertyListParser(value.ToCharArray()); var parser = new ASCIIPropertyListParser(value.ToCharArray());
return parser.Parse(); return parser.Parse();
} }
/// <summary> /// <summary>Checks whether the given sequence of symbols can be accepted.</summary>
/// Checks whether the given sequence of symbols can be accepted.
/// </summary>
/// <returns>Whether the given tokens occur at the current parsing position.</returns> /// <returns>Whether the given tokens occur at the current parsing position.</returns>
/// <param name="sequence">The sequence of tokens to look for.</param> /// <param name="sequence">The sequence of tokens to look for.</param>
bool AcceptSequence(params char[] sequence) bool AcceptSequence(params char[] sequence)
@@ -309,61 +208,57 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Checks whether the given symbols can be accepted, that is, if one /// Checks whether the given symbols can be accepted, that is, if one of the given symbols is found at the current
/// of the given symbols is found at the current parsing position. /// parsing position.
/// </summary> /// </summary>
/// <param name="acceptableSymbols">The symbols to check.</param> /// <param name="acceptableSymbols">The symbols to check.</param>
/// <returns>Whether one of the symbols can be accepted or not.</returns> /// <returns>Whether one of the symbols can be accepted or not.</returns>
bool Accept(params char[] acceptableSymbols) bool Accept(params char[] acceptableSymbols)
{ {
bool symbolPresent = false; bool symbolPresent = false;
foreach(char c in acceptableSymbols) symbolPresent |= data[index] == c;
foreach(char c in acceptableSymbols)
symbolPresent |= data[index] == c;
return symbolPresent; return symbolPresent;
} }
/// <summary> /// <summary>
/// Checks whether the given symbol can be accepted, that is, if /// Checks whether the given symbol can be accepted, that is, if the given symbols is found at the current parsing
/// the given symbols is found at the current parsing position. /// position.
/// </summary> /// </summary>
/// <param name="acceptableSymbol">The symbol to check.</param> /// <param name="acceptableSymbol">The symbol to check.</param>
/// <returns>Whether the symbol can be accepted or not.</returns> /// <returns>Whether the symbol can be accepted or not.</returns>
bool Accept(char acceptableSymbol) bool Accept(char acceptableSymbol) => data[index] == acceptableSymbol;
{
return data[index] == acceptableSymbol;
}
/// <summary> /// <summary>Expects the input to have one of the given symbols at the current parsing position.</summary>
/// Expects the input to have one of the given symbols at the current parsing position.
/// </summary>
/// <param name="expectedSymbols">The expected symbols.</param> /// <param name="expectedSymbols">The expected symbols.</param>
/// <exception cref="FormatException">If none of the expected symbols could be found.</exception> /// <exception cref="FormatException">If none of the expected symbols could be found.</exception>
void Expect(params char[] expectedSymbols) void Expect(params char[] expectedSymbols)
{ {
if(!Accept(expectedSymbols)) if(Accept(expectedSymbols))
{ return;
string excString = "Expected '" + expectedSymbols[0] + "'"; 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] + "'"; excString += " but found '" + data[index] + "'";
throw new FormatException(string.Format("{0} at {1}", excString, index));
} throw new FormatException($"{excString} at {index}");
} }
/// <summary> /// <summary>Expects the input to have the given symbol at the current parsing position.</summary>
/// Expects the input to have the given symbol at the current parsing position.
/// </summary>
/// <param name="expectedSymbol">The expected symbol.</param> /// <param name="expectedSymbol">The expected symbol.</param>
/// <exception cref="FormatException">If the expected symbol could be found.</exception> /// <exception cref="FormatException">If the expected symbol could be found.</exception>
void Expect(char expectedSymbol) void Expect(char expectedSymbol)
{ {
if(!Accept(expectedSymbol)) if(!Accept(expectedSymbol))
throw new FormatException(string.Format("Expected '{0}' but found '{1}' at {2}", expectedSymbol, throw new FormatException($"Expected '{expectedSymbol}' but found '{data[index]}' at {index}");
data[index], index));
} }
/// <summary> /// <summary>Reads an expected symbol.</summary>
/// Reads an expected symbol.
/// </summary>
/// <param name="symbol">The symbol to read.</param> /// <param name="symbol">The symbol to read.</param>
/// <exception cref="FormatException">If the expected symbol could not be read.</exception> /// <exception cref="FormatException">If the expected symbol could not be read.</exception>
void Read(char symbol) void Read(char symbol)
@@ -375,19 +270,11 @@ namespace Claunia.PropertyList
/** /**
* Skips the current symbol. * Skips the current symbol.
*/ */
void Skip() void Skip() => index++;
{
index++;
}
/// <summary> /// <summary>Skips several symbols</summary>
/// Skips several symbols
/// </summary>
/// <param name="numSymbols">The amount of symbols to skip.</param> /// <param name="numSymbols">The amount of symbols to skip.</param>
void Skip(int numSymbols) void Skip(int numSymbols) => index += numSymbols;
{
index += numSymbols;
}
/** /**
* Skips all whitespaces and comments from the current parsing position onward. * Skips all whitespaces and comments from the current parsing position onward.
@@ -395,12 +282,14 @@ namespace Claunia.PropertyList
void SkipWhitespacesAndComments() void SkipWhitespacesAndComments()
{ {
bool commentSkipped; bool commentSkipped;
do do
{ {
commentSkipped = false; commentSkipped = false;
//Skip whitespaces //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 "//..." //Skip single line comments "//..."
if(AcceptSequence(COMMENT_BEGIN_TOKEN, SINGLELINE_COMMENT_SECOND_TOKEN)) if(AcceptSequence(COMMENT_BEGIN_TOKEN, SINGLELINE_COMMENT_SECOND_TOKEN))
@@ -409,15 +298,18 @@ namespace Claunia.PropertyList
ReadInputUntil(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE); ReadInputUntil(WHITESPACE_CARRIAGE_RETURN, WHITESPACE_NEWLINE);
commentSkipped = true; commentSkipped = true;
} }
//Skip multi line comments "/* ... */" //Skip multi line comments "/* ... */"
else if(AcceptSequence(COMMENT_BEGIN_TOKEN, MULTILINE_COMMENT_SECOND_TOKEN)) else if(AcceptSequence(COMMENT_BEGIN_TOKEN, MULTILINE_COMMENT_SECOND_TOKEN))
{ {
Skip(2); Skip(2);
while(true) while(true)
{ {
if(AcceptSequence(MULTILINE_COMMENT_SECOND_TOKEN, MULTILINE_COMMENT_END_TOKEN)) if(AcceptSequence(MULTILINE_COMMENT_SECOND_TOKEN, MULTILINE_COMMENT_END_TOKEN))
{ {
Skip(2); Skip(2);
break; break;
} }
@@ -426,19 +318,17 @@ namespace Claunia.PropertyList
commentSkipped = true; commentSkipped = true;
} }
} } while(
while(commentSkipped commentSkipped); //if a comment was skipped more whitespace or another comment can follow, so skip again
); //if a comment was skipped more whitespace or another comment can follow, so skip again
} }
/// <summary> /// <summary>Reads input until one of the given symbols is found.</summary>
/// Reads input until one of the given symbols is found.
/// </summary>
/// <returns>The input until one the given symbols.</returns> /// <returns>The input until one the given symbols.</returns>
/// <param name="symbols">The symbols that can occur after the string to read.</param> /// <param name="symbols">The symbols that can occur after the string to read.</param>
string ReadInputUntil(params char[] symbols) string ReadInputUntil(params char[] symbols)
{ {
string s = ""; string s = "";
while(!Accept(symbols)) while(!Accept(symbols))
{ {
s += data[index]; s += data[index];
@@ -448,14 +338,13 @@ namespace Claunia.PropertyList
return s; return s;
} }
/// <summary> /// <summary>Reads input until the given symbol is found.</summary>
/// Reads input until the given symbol is found.
/// </summary>
/// <returns>The input until the given symbol.</returns> /// <returns>The input until the given symbol.</returns>
/// <param name="symbol">The symbol that can occur after the string to read.</param> /// <param name="symbol">The symbol that can occur after the string to read.</param>
string ReadInputUntil(char symbol) string ReadInputUntil(char symbol)
{ {
string s = ""; string s = "";
while(!Accept(symbol)) while(!Accept(symbol))
{ {
s += data[index]; s += data[index];
@@ -465,46 +354,63 @@ namespace Claunia.PropertyList
return s; return s;
} }
/// <summary> /// <summary>Parses the property list from the beginning and returns the root object of the property list.</summary>
/// Parses the property list from the beginning and returns the root object
/// of the property list.
/// </summary>
/// <returns>The root object of the property list. This can either be a NSDictionary or a NSArray.</returns> /// <returns>The root object of the property list. This can either be a NSDictionary or a NSArray.</returns>
/// <exception cref="FormatException">When an error occured during parsing</exception> /// <exception cref="FormatException">When an error occured during parsing</exception>
public NSObject Parse() public NSObject Parse()
{ {
index = 0; index = 0;
//Skip Unicode byte order mark (BOM) //Skip Unicode byte order mark (BOM)
if(data.Length >= 3 && (data[0] & 0xFF) == 0xEF && (data[1] & 0xFF) == 0xBB && if(data.Length >= 3 &&
(data[2] & 0xFF) == 0xBF) Skip(3); (data[0] & 0xFF) == 0xEF &&
(data[1] & 0xFF) == 0xBB &&
(data[2] & 0xFF) == 0xBF)
Skip(3);
SkipWhitespacesAndComments(); SkipWhitespacesAndComments();
Expect(DICTIONARY_BEGIN_TOKEN, ARRAY_BEGIN_TOKEN, COMMENT_BEGIN_TOKEN); Expect(DICTIONARY_BEGIN_TOKEN, ARRAY_BEGIN_TOKEN, COMMENT_BEGIN_TOKEN);
try { return ParseObject(); }
try
{
return ParseObject();
}
catch(IndexOutOfRangeException) catch(IndexOutOfRangeException)
{ {
throw new FormatException(string.Format("Reached end of input unexpectedly at {0}.", index)); throw new FormatException($"Reached end of input unexpectedly at {index}.");
} }
} }
/// <summary> /// <summary>Parses the NSObject found at the current position in the property list data stream.</summary>
/// Parses the NSObject found at the current position in the property list
/// data stream.
/// </summary>
/// <returns>The parsed NSObject.</returns> /// <returns>The parsed NSObject.</returns>
/// <seealso cref="ASCIIPropertyListParser.index" /> /// <seealso cref="ASCIIPropertyListParser.index" />
NSObject ParseObject() NSObject ParseObject()
{ {
switch(data[index]) switch(data[index])
{ {
case ARRAY_BEGIN_TOKEN: { return ParseArray(); } case ARRAY_BEGIN_TOKEN:
case DICTIONARY_BEGIN_TOKEN: { return ParseDictionary(); } {
case DATA_BEGIN_TOKEN: { return ParseData(); } return ParseArray();
}
case DICTIONARY_BEGIN_TOKEN:
{
return ParseDictionary();
}
case DATA_BEGIN_TOKEN:
{
return ParseData();
}
case QUOTEDSTRING_BEGIN_TOKEN: case QUOTEDSTRING_BEGIN_TOKEN:
{ {
string quotedString = ParseQuotedString(); string quotedString = ParseQuotedString();
//apple dates are quoted strings of length 20 and after the 4 year digits a dash is found //apple dates are quoted strings of length 20 and after the 4 year digits a dash is found
if(quotedString.Length == 20 && quotedString[4] == DATE_DATE_FIELD_DELIMITER) if(quotedString.Length == 20 &&
try { return new NSDate(quotedString); } quotedString[4] == DATE_DATE_FIELD_DELIMITER)
try
{
return new NSDate(quotedString);
}
catch(Exception) catch(Exception)
{ {
//not a date? --> return string //not a date? --> return string
@@ -516,18 +422,21 @@ namespace Claunia.PropertyList
default: default:
{ {
//0-9 //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 //non-numerical -> string or boolean
string parsedString = ParseString(); string parsedString = ParseString();
return new NSString(parsedString); return new NSString(parsedString);
} }
} }
} }
/// <summary> /// <summary>
/// Parses an array from the current parsing position. /// Parses an array from the current parsing position. The prerequisite for calling this method is, that an array
/// The prerequisite for calling this method is, that an array begin token has been read. /// begin token has been read.
/// </summary> /// </summary>
/// <returns>The array found at the parsing position.</returns> /// <returns>The array found at the parsing position.</returns>
NSArray ParseArray() NSArray ParseArray()
@@ -535,25 +444,30 @@ namespace Claunia.PropertyList
//Skip begin token //Skip begin token
Skip(); Skip();
SkipWhitespacesAndComments(); SkipWhitespacesAndComments();
List<NSObject> objects = new List<NSObject>(); List<NSObject> objects = new();
while(!Accept(ARRAY_END_TOKEN)) while(!Accept(ARRAY_END_TOKEN))
{ {
objects.Add(ParseObject()); objects.Add(ParseObject());
SkipWhitespacesAndComments(); SkipWhitespacesAndComments();
if(Accept(ARRAY_ITEM_DELIMITER_TOKEN)) Skip();
else break; //must have reached end of array if(Accept(ARRAY_ITEM_DELIMITER_TOKEN))
Skip();
else
break; //must have reached end of array
SkipWhitespacesAndComments(); SkipWhitespacesAndComments();
} }
//parse end token //parse end token
Read(ARRAY_END_TOKEN); Read(ARRAY_END_TOKEN);
return new NSArray(objects.ToArray()); return new NSArray(objects.ToArray());
} }
/// <summary> /// <summary>
/// Parses a dictionary from the current parsing position. /// Parses a dictionary from the current parsing position. The prerequisite for calling this method is, that a
/// The prerequisite for calling this method is, that a dictionary begin token has been read. /// dictionary begin token has been read.
/// </summary> /// </summary>
/// <returns>The dictionary found at the parsing position.</returns> /// <returns>The dictionary found at the parsing position.</returns>
NSDictionary ParseDictionary() NSDictionary ParseDictionary()
@@ -561,13 +475,15 @@ namespace Claunia.PropertyList
//Skip begin token //Skip begin token
Skip(); Skip();
SkipWhitespacesAndComments(); SkipWhitespacesAndComments();
NSDictionary dict = new NSDictionary(); var dict = new NSDictionary();
while(!Accept(DICTIONARY_END_TOKEN)) while(!Accept(DICTIONARY_END_TOKEN))
{ {
//Parse key //Parse key
string keyString; string keyString;
if(Accept(QUOTEDSTRING_BEGIN_TOKEN)) keyString = ParseQuotedString();
else keyString = ParseString(); keyString = Accept(QUOTEDSTRING_BEGIN_TOKEN) ? ParseQuotedString() : ParseString();
SkipWhitespacesAndComments(); SkipWhitespacesAndComments();
//Parse assign token //Parse assign token
@@ -583,32 +499,40 @@ namespace Claunia.PropertyList
//skip end token //skip end token
Skip(); Skip();
return dict; return dict;
} }
/// <summary> /// <summary>
/// Parses a data object from the current parsing position. /// Parses a data object from the current parsing position. This can either be a NSData object or a GnuStep
/// This can either be a NSData object or a GnuStep NSNumber or NSDate. /// NSNumber or NSDate. The prerequisite for calling this method is, that a data begin token has been read.
/// The prerequisite for calling this method is, that a data begin token has been read.
/// </summary> /// </summary>
/// <returns>The data object found at the parsing position.</returns> /// <returns>The data object found at the parsing position.</returns>
NSObject ParseData() NSObject ParseData()
{ {
NSObject obj = null; NSObject obj = null;
//Skip begin token //Skip begin token
Skip(); Skip();
if(Accept(DATA_GSOBJECT_BEGIN_TOKEN)) if(Accept(DATA_GSOBJECT_BEGIN_TOKEN))
{ {
Skip(); Skip();
Expect(DATA_GSBOOL_BEGIN_TOKEN, DATA_GSDATE_BEGIN_TOKEN, DATA_GSINT_BEGIN_TOKEN, Expect(DATA_GSBOOL_BEGIN_TOKEN, DATA_GSDATE_BEGIN_TOKEN, DATA_GSINT_BEGIN_TOKEN,
DATA_GSREAL_BEGIN_TOKEN); DATA_GSREAL_BEGIN_TOKEN);
if(Accept(DATA_GSBOOL_BEGIN_TOKEN)) if(Accept(DATA_GSBOOL_BEGIN_TOKEN))
{ {
//Boolean //Boolean
Skip(); Skip();
Expect(DATA_GSBOOL_TRUE_TOKEN, DATA_GSBOOL_FALSE_TOKEN); Expect(DATA_GSBOOL_TRUE_TOKEN, DATA_GSBOOL_FALSE_TOKEN);
if(Accept(DATA_GSBOOL_TRUE_TOKEN)) obj = new NSNumber(true);
else obj = new NSNumber(false); if(Accept(DATA_GSBOOL_TRUE_TOKEN))
obj = new NSNumber(true);
else
obj = new NSNumber(false);
//Skip the parsed boolean token //Skip the parsed boolean token
Skip(); Skip();
} }
@@ -637,6 +561,7 @@ namespace Claunia.PropertyList
int numBytes = dataString.Length / 2; int numBytes = dataString.Length / 2;
byte[] bytes = new byte[numBytes]; byte[] bytes = new byte[numBytes];
for(int i = 0; i < bytes.Length; i++) for(int i = 0; i < bytes.Length; i++)
{ {
string byteString = dataString.Substring(i * 2, 2); string byteString = dataString.Substring(i * 2, 2);
@@ -653,15 +578,20 @@ namespace Claunia.PropertyList
return obj; return obj;
} }
/// <summary> /// <summary>Attempts to parse a plain string as a date if possible.</summary>
/// Attempts to parse a plain string as a date if possible.
/// </summary>
/// <returns>A NSDate if the string represents such an object. Otherwise a NSString is returned.</returns> /// <returns>A NSDate if the string represents such an object. Otherwise a NSString is returned.</returns>
NSObject ParseDateString() NSObject ParseDateString()
{ {
string numericalString = ParseString(); string numericalString = ParseString();
if(numericalString.Length > 4 && numericalString[4] == DATE_DATE_FIELD_DELIMITER)
try { return new NSDate(numericalString); } if(numericalString.Length <= 4 ||
numericalString[4] != DATE_DATE_FIELD_DELIMITER)
return new NSString(numericalString);
try
{
return new NSDate(numericalString);
}
catch(Exception) catch(Exception)
{ {
//An exception occurs if the string is not a date but just a string //An exception occurs if the string is not a date but just a string
@@ -671,21 +601,18 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a plain string from the current parsing position. /// Parses a plain string from the current parsing position. The string is made up of all characters to the next
/// The string is made up of all characters to the next whitespace, delimiter token or assignment token. /// whitespace, delimiter token or assignment token.
/// </summary> /// </summary>
/// <returns>The string found at the current parsing position.</returns> /// <returns>The string found at the current parsing position.</returns>
string ParseString() string ParseString() => ReadInputUntil(WHITESPACE_SPACE, WHITESPACE_TAB, WHITESPACE_NEWLINE,
{ WHITESPACE_CARRIAGE_RETURN, ARRAY_ITEM_DELIMITER_TOKEN,
return ReadInputUntil(WHITESPACE_SPACE, WHITESPACE_TAB, WHITESPACE_NEWLINE, DICTIONARY_ITEM_DELIMITER_TOKEN, DICTIONARY_ASSIGN_TOKEN,
WHITESPACE_CARRIAGE_RETURN,
ARRAY_ITEM_DELIMITER_TOKEN, DICTIONARY_ITEM_DELIMITER_TOKEN, DICTIONARY_ASSIGN_TOKEN,
ARRAY_END_TOKEN); ARRAY_END_TOKEN);
}
/// <summary> /// <summary>
/// Parses a quoted string from the current parsing position. /// Parses a quoted string from the current parsing position. The prerequisite for calling this method is, that a
/// The prerequisite for calling this method is, that a quoted string begin token has been read. /// quoted string begin token has been read.
/// </summary> /// </summary>
/// <returns>The quoted string found at the parsing method with all special characters unescaped.</returns> /// <returns>The quoted string found at the parsing method with all special characters unescaped.</returns>
/// <exception cref="FormatException">If an error occured during parsing.</exception> /// <exception cref="FormatException">If an error occured during parsing.</exception>
@@ -695,31 +622,39 @@ namespace Claunia.PropertyList
Skip(); Skip();
string quotedString = ""; string quotedString = "";
bool unescapedBackslash = true; bool unescapedBackslash = true;
//Read from opening quotation marks to closing quotation marks and skip escaped quotation marks //Read from opening quotation marks to closing quotation marks and skip escaped quotation marks
while(data[index] != QUOTEDSTRING_END_TOKEN || while(data[index] != QUOTEDSTRING_END_TOKEN ||
data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash) (data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash))
{ {
quotedString += data[index]; quotedString += data[index];
if(Accept(QUOTEDSTRING_ESCAPE_TOKEN)) if(Accept(QUOTEDSTRING_ESCAPE_TOKEN))
unescapedBackslash = !(data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash); unescapedBackslash = !(data[index - 1] == QUOTEDSTRING_ESCAPE_TOKEN && unescapedBackslash);
Skip(); Skip();
} }
string unescapedString; string unescapedString;
try { unescapedString = ParseQuotedString(quotedString); }
try
{
unescapedString = ParseQuotedString(quotedString);
}
catch(Exception) catch(Exception)
{ {
throw new FormatException(string.Format("The quoted string could not be parsed at {0}.", index)); throw new FormatException($"The quoted string could not be parsed at {index}.");
} }
//skip end token //skip end token
Skip(); Skip();
return unescapedString; return unescapedString;
} }
/// <summary> /// <summary>
/// Parses a string according to the format specified for ASCII property lists. /// Parses a string according to the format specified for ASCII property lists. Such strings can contain escape
/// Such strings can contain escape sequences which are unescaped in this method. /// sequences which are unescaped in this method.
/// </summary> /// </summary>
/// <returns>The unescaped string in UTF-8 or ASCII format, depending on the contained characters.</returns> /// <returns>The unescaped string in UTF-8 or ASCII format, depending on the contained characters.</returns>
/// <param name="s"> /// <param name="s">
@@ -730,7 +665,7 @@ namespace Claunia.PropertyList
/// <exception cref="EncoderFallbackException">If the string is encoded neither in ASCII nor in UTF-8</exception> /// <exception cref="EncoderFallbackException">If the string is encoded neither in ASCII nor in UTF-8</exception>
public static string ParseQuotedString(string s) public static string ParseQuotedString(string s)
{ {
List<byte> strBytes = new List<byte>(); List<byte> strBytes = new();
IEnumerable<char> characters = s.ToCharArray(); IEnumerable<char> characters = s.ToCharArray();
IEnumerator<char> c = characters.GetEnumerator(); IEnumerator<char> c = characters.GetEnumerator();
@@ -742,18 +677,27 @@ namespace Claunia.PropertyList
{ {
//An escaped sequence is following //An escaped sequence is following
byte[] bts = Encoding.UTF8.GetBytes(ParseEscapedSequence(c)); byte[] bts = Encoding.UTF8.GetBytes(ParseEscapedSequence(c));
foreach(byte b in bts) strBytes.Add(b);
foreach(byte b in bts)
strBytes.Add(b);
break; break;
} }
default: default:
{ {
//a normal ASCII char //a normal ASCII char
strBytes.AddRange(Encoding.BigEndianUnicode.GetBytes(new[] {c.Current})); strBytes.AddRange(Encoding.BigEndianUnicode.GetBytes(new[]
{
c.Current
}));
break; break;
} }
} }
byte[] bytArr = new byte[strBytes.Count]; byte[] bytArr = new byte[strBytes.Count];
int i = 0; int i = 0;
foreach(byte b in strBytes) foreach(byte b in strBytes)
{ {
bytArr[i] = b; bytArr[i] = b;
@@ -773,9 +717,7 @@ namespace Claunia.PropertyList
return result; return result;
} }
/// <summary> /// <summary>Unescapes an escaped character sequence, e.g. \\u00FC.</summary>
/// Unescapes an escaped character sequence, e.g. \\u00FC.
/// </summary>
/// <returns>The unescaped character as a string.</returns> /// <returns>The unescaped character as a string.</returns>
/// <param name="iterator">The string character iterator pointing to the first character after the backslash</param> /// <param name="iterator">The string character iterator pointing to the first character after the backslash</param>
/// <exception cref="EncoderFallbackException">If an invalid Unicode or ASCII escape sequence is found.</exception> /// <exception cref="EncoderFallbackException">If an invalid Unicode or ASCII escape sequence is found.</exception>
@@ -783,19 +725,41 @@ namespace Claunia.PropertyList
{ {
iterator.MoveNext(); iterator.MoveNext();
char c = iterator.Current; char c = iterator.Current;
if(c == '\\') return Encoding.UTF8.GetString(new byte[] {0, (byte)'\\'});
if(c == '"') return Encoding.UTF8.GetString(new byte[] {0, (byte)'\"'}); switch(c)
{
if(c == 'b') return Encoding.UTF8.GetString(new byte[] {0, (byte)'\b'}); case '\\':
return Encoding.UTF8.GetString(new byte[]
if(c == 'n') return Encoding.UTF8.GetString(new byte[] {0, (byte)'\n'}); {
0, (byte)'\\'
if(c == 'r') return Encoding.UTF8.GetString(new byte[] {0, (byte)'\r'}); });
case '"':
if(c == 't') return Encoding.UTF8.GetString(new byte[] {0, (byte)'\t'}); return Encoding.UTF8.GetString(new byte[]
{
if(c == 'U' || c == 'u') 0, (byte)'\"'
});
case 'b':
return Encoding.UTF8.GetString(new byte[]
{
0, (byte)'\b'
});
case 'n':
return Encoding.UTF8.GetString(new byte[]
{
0, (byte)'\n'
});
case 'r':
return Encoding.UTF8.GetString(new byte[]
{
0, (byte)'\r'
});
case 't':
return Encoding.UTF8.GetString(new byte[]
{
0, (byte)'\t'
});
case 'U':
case 'u':
{ {
//4 digit hex Unicode value //4 digit hex Unicode value
string byte1 = ""; string byte1 = "";
@@ -808,10 +772,15 @@ namespace Claunia.PropertyList
byte2 += iterator.Current; byte2 += iterator.Current;
iterator.MoveNext(); iterator.MoveNext();
byte2 += iterator.Current; byte2 += iterator.Current;
byte[] stringBytes = {(byte)Convert.ToInt32(byte1, 16), (byte)Convert.ToInt32(byte2, 16)};
byte[] stringBytes =
{
(byte)Convert.ToInt32(byte1, 16), (byte)Convert.ToInt32(byte2, 16)
};
return Encoding.UTF8.GetString(stringBytes); return Encoding.UTF8.GetString(stringBytes);
} }
else default:
{ {
//3 digit octal ASCII value //3 digit octal ASCII value
string num = ""; string num = "";
@@ -821,10 +790,16 @@ namespace Claunia.PropertyList
iterator.MoveNext(); iterator.MoveNext();
num += iterator.Current; num += iterator.Current;
int asciiCode = Convert.ToInt32(num, 8); int asciiCode = Convert.ToInt32(num, 8);
byte[] stringBytes = {0, (byte)asciiCode};
byte[] stringBytes =
{
0, (byte)asciiCode
};
return Encoding.UTF8.GetString(stringBytes); return Encoding.UTF8.GetString(stringBytes);
} }
} }
}
internal static bool IsASCIIEncodable(string text) internal static bool IsASCIIEncodable(string text)
{ {

View File

@@ -34,13 +34,12 @@ namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>
/// Parses property lists that are in Apple's binary format. /// Parses property lists that are in Apple's binary format. Use this class when you are sure about the format of
/// Use this class when you are sure about the format of the property list. /// the property list. Otherwise use the PropertyListParser class.
/// Otherwise use the PropertyListParser class.
/// </para> /// </para>
/// <para> /// <para>
/// Parsing is done by calling the static <see cref="Parse(byte[])" />, /// Parsing is done by calling the static <see cref="Parse(byte[])" />, <see cref="Parse(FileInfo)" /> and
/// <see cref="Parse(FileInfo)" /> and <see cref="Parse(Stream)" /> methods. /// <see cref="Parse(Stream)" /> methods.
/// </para> /// </para>
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
@@ -49,81 +48,63 @@ namespace Claunia.PropertyList
{ {
static readonly Encoding utf16BigEndian = Encoding.GetEncoding("UTF-16BE"); static readonly Encoding utf16BigEndian = Encoding.GetEncoding("UTF-16BE");
/// <summary> /// <summary>Major version of the property list format</summary>
/// Major version of the property list format
/// </summary>
int majorVersion; int majorVersion;
/// <summary> /// <summary>Minor version of the property list format</summary>
/// Minor version of the property list format
/// </summary>
int minorVersion; int minorVersion;
/// <summary> /// <summary>Length of an object reference in bytes</summary>
/// Length of an object reference in bytes
/// </summary>
int objectRefSize; int objectRefSize;
/// <summary> /// <summary>The table holding the information at which offset each object is found</summary>
/// The table holding the information at which offset each object is found
/// </summary>
int[] offsetTable; int[] offsetTable;
/// <summary> /// <summary>Protected constructor so that instantiation is fully controlled by the static parse methods.</summary>
/// Protected constructor so that instantiation is fully controlled by the
/// static parse methods.
/// </summary>
/// <see cref="Parse(byte[])" /> /// <see cref="Parse(byte[])" />
protected BinaryPropertyListParser() {} protected BinaryPropertyListParser() {}
/// <summary> /// <summary>Parses a binary property list from a byte array.</summary>
/// Parses a binary property list from a byte array.
/// </summary>
/// <param name="data">The binary property list's data.</param> /// <param name="data">The binary property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception> /// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
public static NSObject Parse(byte[] data) public static NSObject Parse(byte[] data) => Parse(data.AsSpan());
{
return Parse(data.AsSpan());
}
/// <summary> /// <summary>Parses a binary property list from a byte array.</summary>
/// Parses a binary property list from a byte array.
/// </summary>
/// <param name="data">The binary property list's data.</param> /// <param name="data">The binary property list's data.</param>
/// <param name="offset">The length of the property list.</param> /// <param name="offset">The length of the property list.</param>
/// <param name="count">The offset at which to start reading the property list.</param> /// <param name="count">The offset at which to start reading the property list.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception> /// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
public static NSObject Parse(byte[] data, int offset, int length) public static NSObject Parse(byte[] data, int offset, int length) => Parse(data.AsSpan(offset, length));
{
return Parse(data.AsSpan(offset, length));
}
/// <summary> /// <summary>Parses a binary property list from a byte span.</summary>
/// Parses a binary property list from a byte span.
/// </summary>
/// <param name="data">The binary property list's data.</param> /// <param name="data">The binary property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception> /// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
public static NSObject Parse(ReadOnlySpan<byte> data) public static NSObject Parse(ReadOnlySpan<byte> data)
{ {
BinaryPropertyListParser parser = new BinaryPropertyListParser(); var parser = new BinaryPropertyListParser();
return parser.DoParse(data); return parser.DoParse(data);
} }
/// <summary> /// <summary>Parses a binary property list from a byte array.</summary>
/// Parses a binary property list from a byte array.
/// </summary>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <param name="bytes">The binary property list's data.</param> /// <param name="bytes">The binary property list's data.</param>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception> /// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
protected NSObject DoParse(ReadOnlySpan<byte> bytes) protected NSObject DoParse(ReadOnlySpan<byte> bytes)
{ {
if(bytes.Length < 8 || bytes[0] != 'b' || bytes[1] != 'p' || bytes[2] != 'l' || bytes[3] != 'i' || if(bytes.Length < 8 ||
bytes[4] != 's' || bytes[5] != 't') bytes[0] != 'b' ||
bytes[1] != 'p' ||
bytes[2] != 'l' ||
bytes[3] != 'i' ||
bytes[4] != 's' ||
bytes[5] != 't')
{ {
string magic = Encoding.ASCII.GetString(bytes.Slice(0, 8).ToArray()); string magic = Encoding.ASCII.GetString(bytes.Slice(0, 8).ToArray());
throw new PropertyListFormatException("The given data is no binary property list. Wrong magic bytes: " + throw new PropertyListFormatException("The given data is no binary property list. Wrong magic bytes: " +
magic); magic);
} }
@@ -139,14 +120,14 @@ namespace Claunia.PropertyList
if(majorVersion > 0) if(majorVersion > 0)
throw new PropertyListFormatException("Unsupported binary property list format: v" + majorVersion + throw new PropertyListFormatException("Unsupported binary property list format: v" + majorVersion +
"." + minorVersion + "." + minorVersion + ". " +
". " +
"Version 1.0 and later are not yet supported."); "Version 1.0 and later are not yet supported.");
/* /*
* Handle trailer, last 32 bytes of the file * Handle trailer, last 32 bytes of the file
*/ */
ReadOnlySpan<byte> trailer = bytes.Slice(bytes.Length - 32, 32); ReadOnlySpan<byte> trailer = bytes.Slice(bytes.Length - 32, 32);
//6 null bytes (index 0 to 5) //6 null bytes (index 0 to 5)
int offsetSize = trailer[6]; int offsetSize = trailer[6];
objectRefSize = trailer[7]; objectRefSize = trailer[7];
@@ -161,16 +142,14 @@ namespace Claunia.PropertyList
for(int i = 0; i < numObjects; i++) for(int i = 0; i < numObjects; i++)
{ {
ReadOnlySpan<byte> offsetBytes = bytes.Slice(offsetTableOffset + i * offsetSize, offsetSize); ReadOnlySpan<byte> offsetBytes = bytes.Slice(offsetTableOffset + (i * offsetSize), offsetSize);
offsetTable[i] = (int)ParseUnsignedInt(offsetBytes); offsetTable[i] = (int)ParseUnsignedInt(offsetBytes);
} }
return ParseObject(bytes, topObject); return ParseObject(bytes, topObject);
} }
/// <summary> /// <summary>Parses a binary property list from an input stream.</summary>
/// Parses a binary property list from an input stream.
/// </summary>
/// <param name="fs">The input stream that points to the property list's data.</param> /// <param name="fs">The input stream that points to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception> /// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
@@ -178,32 +157,25 @@ namespace Claunia.PropertyList
{ {
//Read all bytes into a list //Read all bytes into a list
byte[] buf = PropertyListParser.ReadAll(fs); byte[] buf = PropertyListParser.ReadAll(fs);
// Don't close the stream - that would be the responisibility of code that class // Don't close the stream - that would be the responisibility of code that class
// Parse // Parse
return Parse(buf); return Parse(buf);
} }
/// <summary> /// <summary>Parses a binary property list file.</summary>
/// Parses a binary property list file.
/// </summary>
/// <param name="f">The binary property list file</param> /// <param name="f">The binary property list file</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception> /// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
public static NSObject Parse(FileInfo f) public static NSObject Parse(FileInfo f) => Parse(f.OpenRead());
{
return Parse(f.OpenRead());
}
protected int GetOffset(int obj) protected int GetOffset(int obj) => offsetTable[obj];
{
return this.offsetTable[obj];
}
/// <summary> /// <summary>
/// Parses an object inside the currently parsed binary property list. /// Parses an object inside the currently parsed binary property list. For the format specification check
/// For the format specification check
/// <a href="http://www.opensource.apple.com/source/CF/CF-855.17/CFBinaryPList.c"> /// <a href="http://www.opensource.apple.com/source/CF/CF-855.17/CFBinaryPList.c">
/// Apple's binary property list parser implementation /// Apple's binary property list parser
/// implementation
/// </a> /// </a>
/// . /// .
/// </summary> /// </summary>
@@ -216,6 +188,7 @@ namespace Claunia.PropertyList
byte type = bytes[offset]; byte type = bytes[offset];
int objType = (type & 0xF0) >> 4; //First 4 bits int objType = (type & 0xF0) >> 4; //First 4 bits
int objInfo = type & 0x0F; //Second 4 bits int objInfo = type & 0x0F; //Second 4 bits
switch(objType) switch(objType)
{ {
case 0x0: case 0x0:
@@ -269,12 +242,14 @@ namespace Claunia.PropertyList
{ {
//integer //integer
int length = 1 << objInfo; int length = 1 << objInfo;
return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.INTEGER); return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.INTEGER);
} }
case 0x2: case 0x2:
{ {
//real //real
int length = 1 << objInfo; int length = 1 << objInfo;
return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.REAL); return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.REAL);
} }
case 0x3: case 0x3:
@@ -283,8 +258,7 @@ namespace Claunia.PropertyList
if(objInfo != 0x3) if(objInfo != 0x3)
throw new throw new
PropertyListFormatException("The given binary property list contains a date object of an unknown type (" + PropertyListFormatException("The given binary property list contains a date object of an unknown type (" +
objInfo + objInfo + ")");
")");
return new NSDate(bytes.Slice(offset + 1, 8)); return new NSDate(bytes.Slice(offset + 1, 8));
} }
@@ -292,12 +266,14 @@ namespace Claunia.PropertyList
{ {
//Data //Data
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int dataoffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int dataoffset);
return new NSData(CopyOfRange(bytes, offset + dataoffset, offset + dataoffset + length)); return new NSData(CopyOfRange(bytes, offset + dataoffset, offset + dataoffset + length));
} }
case 0x5: case 0x5:
{ {
//ASCII String, each character is 1 byte //ASCII String, each character is 1 byte
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset);
return new NSString(bytes.Slice(offset + stroffset, length), Encoding.ASCII); return new NSString(bytes.Slice(offset + stroffset, length), Encoding.ASCII);
} }
case 0x6: case 0x6:
@@ -308,6 +284,7 @@ namespace Claunia.PropertyList
//UTF-16 characters can have variable length, but the Core Foundation reference implementation //UTF-16 characters can have variable length, but the Core Foundation reference implementation
//assumes 2 byte characters, thus only covering the Basic Multilingual Plane //assumes 2 byte characters, thus only covering the Basic Multilingual Plane
length *= 2; length *= 2;
return new NSString(bytes.Slice(offset + stroffset, length), utf16BigEndian); return new NSString(bytes.Slice(offset + stroffset, length), utf16BigEndian);
} }
case 0x7: case 0x7:
@@ -318,12 +295,14 @@ namespace Claunia.PropertyList
//UTF-8 characters can have variable length, so we need to calculate the byte length dynamically //UTF-8 characters can have variable length, so we need to calculate the byte length dynamically
//by reading the UTF-8 characters one by one //by reading the UTF-8 characters one by one
int length = CalculateUtf8StringLength(bytes, offset + strOffset, characters); int length = CalculateUtf8StringLength(bytes, offset + strOffset, characters);
return new NSString(bytes.Slice(offset + strOffset, length), Encoding.UTF8); return new NSString(bytes.Slice(offset + strOffset, length), Encoding.UTF8);
} }
case 0x8: case 0x8:
{ {
//UID (v1.0 and later) //UID (v1.0 and later)
int length = objInfo + 1; int length = objInfo + 1;
return new UID(bytes.Slice(offset + 1, length)); return new UID(bytes.Slice(offset + 1, length));
} }
case 0xA: case 0xA:
@@ -331,11 +310,14 @@ namespace Claunia.PropertyList
//Array //Array
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int arrayOffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int arrayOffset);
NSArray array = new NSArray(length); var array = new NSArray(length);
for(int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int objRef = int objRef =
(int)ParseUnsignedInt(bytes.Slice(offset + arrayOffset + i * objectRefSize, objectRefSize)); (int)ParseUnsignedInt(bytes.Slice(offset + arrayOffset + (i * objectRefSize),
objectRefSize));
array.Add(ParseObject(bytes, objRef)); array.Add(ParseObject(bytes, objRef));
} }
@@ -346,12 +328,14 @@ namespace Claunia.PropertyList
//Ordered set (v1.0 and later) //Ordered set (v1.0 and later)
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset);
NSSet set = new NSSet(true); var set = new NSSet(true);
for(int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int objRef = int objRef =
(int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize),
objectRefSize)); objectRefSize));
set.AddObject(ParseObject(bytes, objRef)); set.AddObject(ParseObject(bytes, objRef));
} }
@@ -362,12 +346,14 @@ namespace Claunia.PropertyList
//Set (v1.0 and later) //Set (v1.0 and later)
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset);
NSSet set = new NSSet(); var set = new NSSet();
for(int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int objRef = int objRef =
(int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize),
objectRefSize)); objectRefSize));
set.AddObject(ParseObject(bytes, objRef)); set.AddObject(ParseObject(bytes, objRef));
} }
@@ -379,16 +365,19 @@ namespace Claunia.PropertyList
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset);
//System.out.println("Parsing dictionary #"+obj); //System.out.println("Parsing dictionary #"+obj);
NSDictionary dict = new NSDictionary(length); var dict = new NSDictionary(length);
for(int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int keyRef = int keyRef =
(int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (i * objectRefSize),
objectRefSize)); objectRefSize));
int valRef = int valRef =
(int) (int)
ParseUnsignedInt(bytes.Slice(offset + contentOffset + length * objectRefSize + i * objectRefSize, ParseUnsignedInt(bytes.Slice(offset + contentOffset + (length * objectRefSize) + (i * objectRefSize),
objectRefSize)); objectRefSize));
NSObject key = ParseObject(bytes, keyRef); NSObject key = ParseObject(bytes, keyRef);
NSObject val = ParseObject(bytes, valRef); NSObject val = ParseObject(bytes, valRef);
dict.Add(key.ToString(), val); dict.Add(key.ToString(), val);
@@ -399,8 +388,8 @@ namespace Claunia.PropertyList
default: default:
{ {
Debug.WriteLine("WARNING: The given binary property list contains an object of unknown type (" + Debug.WriteLine("WARNING: The given binary property list contains an object of unknown type (" +
objType + objType + ")");
")");
break; break;
} }
} }
@@ -408,9 +397,7 @@ namespace Claunia.PropertyList
return null; return null;
} }
/// <summary> /// <summary>Reads the length for arrays, sets and dictionaries.</summary>
/// Reads the length for arrays, sets and dictionaries.
/// </summary>
/// <returns>An array with the length two. First entry is the length, second entry the offset at which the content starts.</returns> /// <returns>An array with the length two. First entry is the length, second entry the offset at which the content starts.</returns>
/// <param name="objInfo">Object information byte.</param> /// <param name="objInfo">Object information byte.</param>
/// <param name="offset">Offset in the byte array at which the object is located.</param> /// <param name="offset">Offset in the byte array at which the object is located.</param>
@@ -419,17 +406,22 @@ namespace Claunia.PropertyList
{ {
lengthValue = objInfo; lengthValue = objInfo;
offsetValue = 1; offsetValue = 1;
if(objInfo == 0xF) if(objInfo == 0xF)
{ {
int int_type = bytes[offset + 1]; int int_type = bytes[offset + 1];
int intType = (int_type & 0xF0) >> 4; int intType = (int_type & 0xF0) >> 4;
if(intType != 0x1) if(intType != 0x1)
Debug.WriteLine("BinaryPropertyListParser: Length integer has an unexpected type" + intType + Debug.WriteLine("BinaryPropertyListParser: Length integer has an unexpected type" + intType +
". Attempting to parse anyway..."); ". Attempting to parse anyway...");
int intInfo = int_type & 0x0F; int intInfo = int_type & 0x0F;
int intLength = 1 << intInfo; int intLength = 1 << intInfo;
offsetValue = 2 + intLength; offsetValue = 2 + intLength;
if(intLength < 3) lengthValue = (int)ParseUnsignedInt(bytes.Slice(offset + 2, intLength));
if(intLength < 3)
lengthValue = (int)ParseUnsignedInt(bytes.Slice(offset + 2, intLength));
else else
{ {
// BigInteger is Little-Endian in .NET, swap the thing. // BigInteger is Little-Endian in .NET, swap the thing.
@@ -443,9 +435,7 @@ namespace Claunia.PropertyList
} }
} }
/// <summary> /// <summary>Calculates the length in bytes of the UTF-8 string.</summary>
/// Calculates the length in bytes of the UTF-8 string.
/// </summary>
/// <returns>The UTF-8 string length.</returns> /// <returns>The UTF-8 string length.</returns>
/// <param name="bytes">Array containing the UTF-8 string.</param> /// <param name="bytes">Array containing the UTF-8 string.</param>
/// <param name="offset">Offset in the array where the UTF-8 string resides.</param> /// <param name="offset">Offset in the array where the UTF-8 string resides.</param>
@@ -453,31 +443,41 @@ namespace Claunia.PropertyList
int CalculateUtf8StringLength(ReadOnlySpan<byte> bytes, int offset, int numCharacters) int CalculateUtf8StringLength(ReadOnlySpan<byte> bytes, int offset, int numCharacters)
{ {
int length = 0; int length = 0;
for(int i = 0; i < numCharacters; i++) for(int i = 0; i < numCharacters; i++)
{ {
int tempOffset = offset + length; int tempOffset = offset + length;
if(bytes.Length <= tempOffset) return numCharacters;
if(bytes[tempOffset] < 0x80) length++; if(bytes.Length <= tempOffset)
if(bytes[tempOffset] < 0xC2) return numCharacters; return numCharacters;
if(bytes[tempOffset] < 0x80)
length++;
if(bytes[tempOffset] < 0xC2)
return numCharacters;
if(bytes[tempOffset] < 0xE0) if(bytes[tempOffset] < 0xE0)
{ {
if((bytes[tempOffset + 1] & 0xC0) != 0x80) return numCharacters; if((bytes[tempOffset + 1] & 0xC0) != 0x80)
return numCharacters;
length += 2; length += 2;
} }
else if(bytes[tempOffset] < 0xF0) 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; return numCharacters;
length += 3; length += 3;
} }
else if(bytes[tempOffset] < 0xF5) else if(bytes[tempOffset] < 0xF5)
{ {
if((bytes[tempOffset + 1] & 0xC0) != 0x80 || (bytes[tempOffset + 2] & 0xC0) != 0x80 || if((bytes[tempOffset + 1] & 0xC0) != 0x80 ||
(bytes[tempOffset + 3] & 0xC0) != 0x80) return numCharacters; (bytes[tempOffset + 2] & 0xC0) != 0x80 ||
(bytes[tempOffset + 3] & 0xC0) != 0x80)
return numCharacters;
length += 4; length += 4;
} }
@@ -486,31 +486,23 @@ namespace Claunia.PropertyList
return length; return length;
} }
/// <summary> /// <summary>Parses an unsigned integer from a span.</summary>
/// Parses an unsigned integer from a span.
/// </summary>
/// <returns>The byte array containing the unsigned integer.</returns> /// <returns>The byte array containing the unsigned integer.</returns>
/// <param name="bytes">The unsigned integer represented by the given bytes.</param> /// <param name="bytes">The unsigned integer represented by the given bytes.</param>
public static long ParseUnsignedInt(ReadOnlySpan<byte> bytes) public static long ParseUnsignedInt(ReadOnlySpan<byte> bytes)
{ {
if(bytes.Length <= 4) return ParseLong(bytes); if(bytes.Length <= 4)
return ParseLong(bytes);
return ParseLong(bytes) & 0xFFFFFFFFL; return ParseLong(bytes) & 0xFFFFFFFFL;
} }
/// <summary> /// <summary>Parses an unsigned integers from a byte array.</summary>
/// Parses an unsigned integers from a byte array.
/// </summary>
/// <returns>The byte array containing the unsigned integer.</returns> /// <returns>The byte array containing the unsigned integer.</returns>
/// <param name="bytes">The unsigned integer represented by the given bytes.</param> /// <param name="bytes">The unsigned integer represented by the given bytes.</param>
public static long ParseUnsignedInt(byte[] bytes) public static long ParseUnsignedInt(byte[] bytes) => ParseUnsignedInt(bytes.AsSpan());
{
return ParseUnsignedInt(bytes.AsSpan());
}
/// <summary> /// <summary>Parses a long from a (big-endian) byte array.</summary>
/// Parses a long from a (big-endian) byte array.
/// </summary>
/// <returns>The long integer represented by the given bytes.</returns> /// <returns>The long integer represented by the given bytes.</returns>
/// <param name="bytes">The bytes representing the long integer.</param> /// <param name="bytes">The bytes representing the long integer.</param>
public static long ParseLong(ReadOnlySpan<byte> bytes) public static long ParseLong(ReadOnlySpan<byte> bytes)
@@ -548,28 +540,27 @@ namespace Claunia.PropertyList
case 16: return BinaryPrimitives.ReadInt64BigEndian(bytes.Slice(8)); case 16: return BinaryPrimitives.ReadInt64BigEndian(bytes.Slice(8));
} }
if (bytes.Length < 8) if(bytes.Length >= 8)
{ throw new ArgumentOutOfRangeException(nameof(bytes),
// Compatability with existing archives, including anything with a non-power-of-2 $"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 // size and 16-byte values, and architectures that don't support unaligned access
long value = 0; long value = 0;
for(int i = 0; i < bytes.Length; i++) for(int i = 0; i < bytes.Length; i++)
{ {
value = (value << 8) + bytes[i]; value = (value << 8) + bytes[i];
} }
return value; return value;
}
// Theoretically we could handle non-power-of-2 byte arrays larger than 8, with the code // Theoretically we could handle non-power-of-2 byte arrays larger than 8, with the code
// above, and it appears the reference implementation does exactly that. But it seems to // above, and it appears the reference implementation does exactly that. But it seems to
// be an extreme edge case. // be an extreme edge case.
throw new ArgumentOutOfRangeException(nameof(bytes),
$"Cannot read a byte span of length {bytes.Length}");
} }
/// <summary> /// <summary>Parses a double from a (big-endian) byte array.</summary>
/// Parses a double from a (big-endian) byte array.
/// </summary>
/// <returns>The double represented by the given bytes.</returns> /// <returns>The double represented by the given bytes.</returns>
/// <param name="bytes">The bytes representing the double.</param> /// <param name="bytes">The bytes representing the double.</param>
public static double ParseDouble(ReadOnlySpan<byte> bytes) public static double ParseDouble(ReadOnlySpan<byte> bytes)
@@ -579,15 +570,15 @@ namespace Claunia.PropertyList
throw new ArgumentNullException(nameof(bytes)); throw new ArgumentNullException(nameof(bytes));
} }
if(bytes.Length == 8) return BitConverter.Int64BitsToDouble(ParseLong(bytes)); return bytes.Length switch
if(bytes.Length == 4) return BitConverter.ToSingle(BitConverter.GetBytes(ParseLong(bytes)), 0); {
8 => BitConverter.Int64BitsToDouble(ParseLong(bytes)),
throw new ArgumentException("bad byte array length " + bytes.Length); 4 => BitConverter.ToSingle(BitConverter.GetBytes(ParseLong(bytes)), 0),
_ => throw new ArgumentException("bad byte array length " + bytes.Length)
};
} }
/// <summary> /// <summary>Copies a part of a byte array into a new array.</summary>
/// Copies a part of a byte array into a new array.
/// </summary>
/// <returns>The copied array.</returns> /// <returns>The copied array.</returns>
/// <param name="src">The source array.</param> /// <param name="src">The source array.</param>
/// <param name="startIndex">The index from which to start copying.</param> /// <param name="startIndex">The index from which to start copying.</param>
@@ -595,6 +586,7 @@ namespace Claunia.PropertyList
public static byte[] CopyOfRange(ReadOnlySpan<byte> src, int startIndex, int endIndex) public static byte[] CopyOfRange(ReadOnlySpan<byte> src, int startIndex, int endIndex)
{ {
int length = endIndex - startIndex; int length = endIndex - startIndex;
if(length < 0) if(length < 0)
throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex + throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex +
")"); ")");

View File

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

View File

@@ -5,45 +5,35 @@ namespace Claunia.PropertyList
public partial class BinaryPropertyListWriter public partial class BinaryPropertyListWriter
{ {
/// <summary> /// <summary>
/// The equality comparer which is used when retrieving objects in the <see cref="BinaryPropertyListWriter.idMap" />. /// The equality comparer which is used when retrieving objects in the
/// The logic is slightly different from <see cref="AddObjectEqualityComparer" />, results in two equivalent objects /// <see cref="BinaryPropertyListWriter.idMap" />. The logic is slightly different from
/// (UIDs mainly) being added to the <see cref="BinaryPropertyListWriter.idMap" />. Whenever the ID for one of /// <see cref="AddObjectEqualityComparer" />, results in two equivalent objects (UIDs mainly) being added to the
/// those equivalent objects is requested, the first ID is always returned. /// <see cref="BinaryPropertyListWriter.idMap" />. Whenever the ID for one of those equivalent objects is requested,
/// This means that there are "orphan" objects in binary property lists - duplicate objects which are never referenced /// the first ID is always returned. This means that there are "orphan" objects in binary property lists - duplicate
/// -; /// objects which are never referenced -; this logic exists purely to maintain binary compatibility with Apple's
/// this logic exists purely to maintain binary compatibility with Apple's format. /// format.
/// </summary> /// </summary>
class GetObjectEqualityComparer : EqualityComparer<NSObject> class GetObjectEqualityComparer : EqualityComparer<NSObject>
{ {
public override bool Equals(NSObject x, NSObject y) public override bool Equals(NSObject x, NSObject y) => x switch
{ {
// By default, use reference equality. Even if there are two objects - say a NSString - with the same // By default, use reference equality. Even if there are two objects - say a NSString - with the same
// value, do not consider them equal unless they are the same instance of NSString. // value, do not consider them equal unless they are the same instance of NSString.
// The exceptions are UIDs, where we always compare by value, and "primitive" strings (a list of well-known // The exceptions are UIDs, where we always compare by value, and "primitive" strings (a list of well-known
// strings), which are treaded specially and "recycled". // strings), which are treaded specially and "recycled".
if(x is UID) return x.Equals(y); UID => x.Equals(y),
NSNumber number when IsSerializationPrimitive(number) => number.Equals(y),
NSString nsString when IsSerializationPrimitive(nsString) => nsString.Equals(y),
_ => ReferenceEquals(x, y)
};
if(x is NSNumber && IsSerializationPrimitive((NSNumber)x)) return x.Equals(y); public override int GetHashCode(NSObject obj) => obj switch
if(x is NSString && IsSerializationPrimitive((NSString)x)) return x.Equals(y);
return ReferenceEquals(x, y);
}
public override int GetHashCode(NSObject obj)
{ {
if(obj == null) return 0; UID u => u.GetHashCode(),
NSNumber n when IsSerializationPrimitive(n) => n.ToObject().GetHashCode(),
UID u = obj as UID; NSString s when IsSerializationPrimitive(s) => s.Content.GetHashCode(),
if(u != null) return u.GetHashCode(); _ => obj.GetHashCode()
};
NSNumber n = obj as NSNumber;
if(n != null && IsSerializationPrimitive(n)) return n.ToObject().GetHashCode();
NSString s = obj as NSString;
if(s != null && IsSerializationPrimitive(s)) return s.Content.GetHashCode();
return obj.GetHashCode();
}
} }
} }
} }

View File

@@ -30,60 +30,43 @@ using System.IO;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para>A BinaryPropertyListWriter is a helper class for writing out binary property list files.</para>
/// <para> /// <para>
/// A BinaryPropertyListWriter is a helper class for writing out /// It contains an output stream and various structures for keeping track of which NSObjects have already been
/// binary property list files. /// serialized, and where they were put in the file.
/// </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> /// </para>
/// </summary> /// </summary>
/// @author Keith Randall /// @author Keith Randall
/// @author Natalia Portillo /// @author Natalia Portillo
public partial class BinaryPropertyListWriter public partial class BinaryPropertyListWriter
{ {
/// <summary> /// <summary>Binary property list version 0.0</summary>
/// Binary property list version 0.0
/// </summary>
public const int VERSION_00 = 0; public const int VERSION_00 = 0;
/// <summary> /// <summary>Binary property list version 1.0</summary>
/// Binary property list version 1.0
/// </summary>
public const int VERSION_10 = 10; public const int VERSION_10 = 10;
/// <summary> /// <summary>Binary property list version 1.5</summary>
/// Binary property list version 1.5
/// </summary>
public const int VERSION_15 = 15; public const int VERSION_15 = 15;
/// <summary> /// <summary>Binary property list version 2.0</summary>
/// Binary property list version 2.0
/// </summary>
public const int VERSION_20 = 20; public const int VERSION_20 = 20;
// map from object to its ID // map from object to its ID
protected readonly Dictionary<NSObject, int> idDict = new Dictionary<NSObject, int>(new AddObjectEqualityComparer()); protected readonly Dictionary<NSObject, int> idDict = new(new AddObjectEqualityComparer());
protected readonly Dictionary<NSObject, int> idDict2 = new Dictionary<NSObject, int>(new GetObjectEqualityComparer()); protected readonly Dictionary<NSObject, int> idDict2 = new(new GetObjectEqualityComparer());
// # of bytes written so far
private long count;
protected int currentId;
private int idSizeInBytes;
// raw output stream to result file // raw output stream to result file
Stream outStream; readonly Stream outStream;
int version = VERSION_00; readonly int version = VERSION_00;
/// <summary> // # of bytes written so far
/// Creates a new binary property list writer long count;
/// </summary> protected int currentId;
int idSizeInBytes;
/// <summary>Creates a new binary property list writer</summary>
/// <param name="outStr">The output stream into which the binary property list will be written</param> /// <param name="outStr">The output stream into which the binary property list will be written</param>
/// <exception cref="IOException">If an error occured while writing to the stream</exception> /// <exception cref="IOException">If an error occured while writing to the stream</exception>
public BinaryPropertyListWriter(Stream outStr) public BinaryPropertyListWriter(Stream outStr) => outStream = outStr;
{
outStream = outStr;
}
public BinaryPropertyListWriter(Stream outStr, int version) public BinaryPropertyListWriter(Stream outStr, int version)
{ {
@@ -91,7 +74,9 @@ namespace Claunia.PropertyList
outStream = outStr; outStream = outStr;
} }
public BinaryPropertyListWriter(Stream outStr, int version, IEqualityComparer<NSObject> addObjectEqualityComparer, IEqualityComparer<NSObject> getObjectEqualityComparer) public BinaryPropertyListWriter(Stream outStr, int version,
IEqualityComparer<NSObject> addObjectEqualityComparer,
IEqualityComparer<NSObject> getObjectEqualityComparer)
{ {
this.version = version; this.version = version;
outStream = outStr; outStream = outStr;
@@ -100,139 +85,169 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether two equivalent objects should be serialized once in the binary property /// Gets or sets a value indicating whether two equivalent objects should be serialized once in the binary
/// list file, or whether /// property list file, or whether the value should be stored multiple times in the binary property list file. The
/// the value should be stored multiple times in the binary property list file. The default is <see langword="false" /> /// default is <see langword="false" /> .
/// .
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// In most scenarios, you want this to be <see langword="true" />, as it reduces the size of the binary proeprty list /// In most scenarios, you want this to be <see langword="true" />, as it reduces the size of the binary proeprty
/// file. However, /// list file. However, by default, the Apple tools do not seem to implement this optimization, so set this value to
/// by default, the Apple tools do not seem to implement this optimization, so set this value to /// <see langword="false" /> if you want to maintain binary compatibility with the Apple tools.
/// <see langword="false" /> if you
/// want to maintain binary compatibility with the Apple tools.
/// </remarks> /// </remarks>
public bool ReuseObjectIds { get; set; } public bool ReuseObjectIds { get; set; }
/// <summary> /// <summary>Finds out the minimum binary property list format version that can be used to save the given NSObject tree.</summary>
/// Finds out the minimum binary property list format version that
/// can be used to save the given NSObject tree.
/// </summary>
/// <returns>Version code</returns> /// <returns>Version code</returns>
/// <param name="root">Object root.</param> /// <param name="root">Object root.</param>
static int GetMinimumRequiredVersion(NSObject root) static int GetMinimumRequiredVersion(NSObject root)
{ {
int minVersion = VERSION_00; int minVersion = VERSION_00;
if(root == null) minVersion = VERSION_10;
if(root is NSDictionary) switch(root)
{
case null:
minVersion = VERSION_10;
break;
case NSDictionary dict:
{ {
NSDictionary dict = (NSDictionary)root;
foreach(NSObject o in dict.GetDictionary().Values) foreach(NSObject o in dict.GetDictionary().Values)
{ {
int v = GetMinimumRequiredVersion(o); int v = GetMinimumRequiredVersion(o);
if(v > minVersion) minVersion = v;
if(v > minVersion)
minVersion = v;
} }
break;
} }
else if(root is NSArray) case NSArray array:
{ {
NSArray array = (NSArray)root;
foreach(NSObject o in array) foreach(NSObject o in array)
{ {
int v = GetMinimumRequiredVersion(o); int v = GetMinimumRequiredVersion(o);
if(v > minVersion) minVersion = v;
if(v > minVersion)
minVersion = v;
} }
break;
} }
else if(root is NSSet) case NSSet set:
{ {
//Sets are only allowed in property lists v1+ //Sets are only allowed in property lists v1+
minVersion = VERSION_10; minVersion = VERSION_10;
NSSet set = (NSSet)root;
foreach(NSObject o in set.AllObjects()) foreach(NSObject o in set.AllObjects())
{ {
int v = GetMinimumRequiredVersion(o); int v = GetMinimumRequiredVersion(o);
if(v > minVersion) minVersion = v;
if(v > minVersion)
minVersion = v;
}
break;
} }
} }
return minVersion; return minVersion;
} }
/// <summary> /// <summary>Writes a binary plist file with the given object as the root.</summary>
/// Writes a binary plist file with the given object as the root.
/// </summary>
/// <param name="file">the file to write to</param> /// <param name="file">the file to write to</param>
/// <param name="root">the source of the data to write to the file</param> /// <param name="root">the source of the data to write to the file</param>
/// <exception cref="IOException"></exception> /// <exception cref="IOException"></exception>
public static void Write(FileInfo file, NSObject root) public static void Write(FileInfo file, NSObject root)
{ {
using(FileStream fous = file.OpenWrite()) Write(fous, root); using FileStream fous = file.OpenWrite();
Write(fous, root);
} }
/// <summary> /// <summary>Writes a binary plist serialization of the given object as the root.</summary>
/// Writes a binary plist serialization of the given object as the root.
/// </summary>
/// <param name="outStream">the stream to write to</param> /// <param name="outStream">the stream to write to</param>
/// <param name="root">the source of the data to write to the stream</param> /// <param name="root">the source of the data to write to the stream</param>
/// <exception cref="IOException"></exception> /// <exception cref="IOException"></exception>
public static void Write(Stream outStream, NSObject root) public static void Write(Stream outStream, NSObject root)
{ {
int minVersion = GetMinimumRequiredVersion(root); int minVersion = GetMinimumRequiredVersion(root);
if(minVersion > VERSION_00) if(minVersion > VERSION_00)
{ {
string versionString = minVersion == VERSION_10 string versionString = minVersion == VERSION_10
? "v1.0" ? "v1.0"
: (minVersion == VERSION_15 : minVersion == VERSION_15
? "v1.5" ? "v1.5"
: (minVersion == VERSION_20 ? "v2.0" : "v0.0")); : minVersion == VERSION_20
? "v2.0"
: "v0.0";
throw new IOException("The given property list structure cannot be saved. " + throw new IOException("The given property list structure cannot be saved. " +
"The required version of the binary format (" + versionString + "The required version of the binary format (" + versionString +
") is not yet supported."); ") is not yet supported.");
} }
BinaryPropertyListWriter w = new BinaryPropertyListWriter(outStream, minVersion); var w = new BinaryPropertyListWriter(outStream, minVersion);
w.Write(root); w.Write(root);
} }
/// <summary> /// <summary>Writes a binary plist serialization of the given object as the root into a byte array.</summary>
/// Writes a binary plist serialization of the given object as the root
/// into a byte array.
/// </summary>
/// <returns>The byte array containing the serialized property list</returns> /// <returns>The byte array containing the serialized property list</returns>
/// <param name="root">The root object of the property list</param> /// <param name="root">The root object of the property list</param>
/// <exception cref="IOException"></exception> /// <exception cref="IOException"></exception>
public static byte[] WriteToArray(NSObject root) public static byte[] WriteToArray(NSObject root)
{ {
MemoryStream bout = new MemoryStream(); var bout = new MemoryStream();
Write(bout, root); Write(bout, root);
return bout.ToArray(); return bout.ToArray();
} }
public void Write(NSObject root) public void Write(NSObject root)
{ {
// magic bytes // magic bytes
Write(new[] {(byte)'b', (byte)'p', (byte)'l', (byte)'i', (byte)'s', (byte)'t'}); Write(new[]
{
(byte)'b', (byte)'p', (byte)'l', (byte)'i', (byte)'s', (byte)'t'
});
//version //version
switch(version) switch(version)
{ {
case VERSION_00: case VERSION_00:
{ {
Write(new[] {(byte)'0', (byte)'0'}); Write(new[]
{
(byte)'0', (byte)'0'
});
break; break;
} }
case VERSION_10: case VERSION_10:
{ {
Write(new[] {(byte)'1', (byte)'0'}); Write(new[]
{
(byte)'1', (byte)'0'
});
break; break;
} }
case VERSION_15: case VERSION_15:
{ {
Write(new[] {(byte)'1', (byte)'5'}); Write(new[]
{
(byte)'1', (byte)'5'
});
break; break;
} }
case VERSION_20: case VERSION_20:
{ {
Write(new[] {(byte)'2', (byte)'0'}); Write(new[]
{
(byte)'2', (byte)'0'
});
break; break;
} }
} }
@@ -251,29 +266,39 @@ namespace Claunia.PropertyList
NSObject obj = pair.Key; NSObject obj = pair.Key;
int id = pair.Value; int id = pair.Value;
offsets[id] = count; offsets[id] = count;
if(obj == null) Write(0x00);
else obj.ToBinary(this); if(obj == null)
Write(0x00);
else
obj.ToBinary(this);
} }
// write offset table // write offset table
long offsetTableOffset = count; long offsetTableOffset = count;
int offsetSizeInBytes = ComputeOffsetSizeInBytes(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) if(version != VERSION_15)
{ {
// write trailer // write trailer
// 6 null bytes // 6 null bytes
Write(new byte[6]); Write(new byte[6]);
// size of an offset // size of an offset
Write(offsetSizeInBytes); Write(offsetSizeInBytes);
// size of a ref // size of a ref
Write(idSizeInBytes); Write(idSizeInBytes);
// number of objects // number of objects
WriteLong(idDict.Count); WriteLong(idDict.Count);
// top object // top object
int rootID = idDict[root]; int rootID = idDict[root];
WriteLong(rootID); WriteLong(rootID);
// offset table offset // offset table offset
WriteLong(offsetTableOffset); WriteLong(offsetTableOffset);
} }
@@ -285,59 +310,64 @@ namespace Claunia.PropertyList
{ {
if(ReuseObjectIds) if(ReuseObjectIds)
{ {
if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++); if(!idDict.ContainsKey(obj))
idDict.Add(obj, currentId++);
} }
else else
{ {
if(!idDict2.ContainsKey(obj)) idDict2.Add(obj, currentId); if(!idDict2.ContainsKey(obj))
if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++); idDict2.Add(obj, currentId);
if(!idDict.ContainsKey(obj))
idDict.Add(obj, currentId++);
} }
} }
internal int GetID(NSObject obj) internal int GetID(NSObject obj) => ReuseObjectIds ? idDict[obj] : idDict2[obj];
{
if(ReuseObjectIds) return idDict[obj];
return idDict2[obj];
}
static int ComputeIdSizeInBytes(int numberOfIds) static int ComputeIdSizeInBytes(int numberOfIds)
{ {
if(numberOfIds < 256) return 1; if(numberOfIds < 256)
return 1;
return numberOfIds < 65536 ? 2 : 4; return numberOfIds < 65536 ? 2 : 4;
} }
static int ComputeOffsetSizeInBytes(long maxOffset) static int ComputeOffsetSizeInBytes(long maxOffset) => maxOffset switch
{ {
if(maxOffset < 256) return 1; < 256 => 1,
if(maxOffset < 65536) return 2; < 65536 => 2,
< 4294967296L => 4,
return maxOffset < 4294967296L ? 4 : 8; _ => 8
} };
internal void WriteIntHeader(int kind, int value) internal void WriteIntHeader(int kind, int value)
{ {
if(value < 0) throw new ArgumentException("value must be greater than or equal to 0", "value"); switch(value)
if(value < 15) Write((kind << 4) + value);
else if(value < 256)
{ {
case < 0: throw new ArgumentException("value must be greater than or equal to 0", "value");
case < 15:
Write((kind << 4) + value);
break;
case < 256:
Write((kind << 4) + 15); Write((kind << 4) + 15);
Write(0x10); Write(0x10);
WriteBytes(value, 1); WriteBytes(value, 1);
}
else if(value < 65536) break;
{ case < 65536:
Write((kind << 4) + 15); Write((kind << 4) + 15);
Write(0x11); Write(0x11);
WriteBytes(value, 2); WriteBytes(value, 2);
}
else break;
{ default:
Write((kind << 4) + 15); Write((kind << 4) + 15);
Write(0x12); Write(0x12);
WriteBytes(value, 4); WriteBytes(value, 4);
break;
} }
} }
@@ -366,23 +396,15 @@ namespace Claunia.PropertyList
internal void WriteBytes(long value, int bytes) internal void WriteBytes(long value, int bytes)
{ {
// write low-order bytes big-endian style // write low-order bytes big-endian style
for(int i = bytes - 1; i >= 0; i--) Write((int)(value >> (8 * i))); for(int i = bytes - 1; i >= 0; i--)
Write((int)(value >> (8 * i)));
} }
internal void WriteID(int id) internal void WriteID(int id) => WriteBytes(id, idSizeInBytes);
{
WriteBytes(id, idSizeInBytes);
}
internal void WriteLong(long value) internal void WriteLong(long value) => WriteBytes(value, 8);
{
WriteBytes(value, 8);
}
internal void WriteDouble(double value) internal void WriteDouble(double value) => WriteLong(BitConverter.DoubleToInt64Bits(value));
{
WriteLong(BitConverter.DoubleToInt64Bits(value));
}
internal static bool IsSerializationPrimitive(NSString obj) internal static bool IsSerializationPrimitive(NSString obj)
{ {
@@ -390,18 +412,11 @@ namespace Claunia.PropertyList
// This is a list of "special" values which are only added once to a binary property // This is a list of "special" values which are only added once to a binary property
// list, and can be referenced multiple times. // list, and can be referenced multiple times.
return content == "$class" || content == "$classes" || content == "$classname" || return content is "$class" or "$classes" or "$classname" or "NS.objects" or "NS.keys" or "NS.base" or
content == "NS.objects" || "NS.relative" or "NS.string" or "NSURL" or "NSDictionary" or "NSObject" or "NSMutableDictionary"
content == "NS.keys" || content == "NS.base" || content == "NS.relative" || or "NSMutableArray" or "NSArray" or "NSUUID" or "NSKeyedArchiver" or "NSMutableString";
content == "NS.string" ||
content == "NSURL" || content == "NSDictionary" || content == "NSObject" ||
content == "NSMutableDictionary" || content == "NSMutableArray" || content == "NSArray" ||
content == "NSUUID" || content == "NSKeyedArchiver" || content == "NSMutableString";
} }
internal static bool IsSerializationPrimitive(NSNumber n) internal static bool IsSerializationPrimitive(NSNumber n) => n.isBoolean();
{
return n.isBoolean();
}
} }
} }

View File

@@ -38,88 +38,43 @@ namespace Claunia.PropertyList
public bool IsReadOnly => false; public bool IsReadOnly => false;
/// <inheritdoc /> /// <inheritdoc />
public void Add(NSObject item) public void Add(NSObject item) => array.Add(item);
{
array.Add(item);
}
/// <inheritdoc /> /// <inheritdoc />
public void Clear() public void Clear() => array.Clear();
{
array.Clear();
}
/// <inheritdoc /> /// <inheritdoc />
public bool Contains(NSObject item) public bool Contains(NSObject item) => array.Contains(item);
{
return array.Contains(item);
}
/// <inheritdoc /> /// <inheritdoc />
public void CopyTo(NSObject[] array, int arrayIndex) public void CopyTo(NSObject[] array, int arrayIndex) => this.array.CopyTo(array, arrayIndex);
{
this.array.CopyTo(array, arrayIndex);
}
/// <inheritdoc /> /// <inheritdoc />
public IEnumerator<NSObject> GetEnumerator() public IEnumerator<NSObject> GetEnumerator() => array.GetEnumerator();
{
return array.GetEnumerator();
}
/// <inheritdoc /> /// <inheritdoc />
public int IndexOf(NSObject item) public int IndexOf(NSObject item) => array.IndexOf(item);
{
return array.IndexOf(item);
}
/// <inheritdoc /> /// <inheritdoc />
public void Insert(int index, NSObject item) public void Insert(int index, NSObject item) => array.Insert(index, item);
{
array.Insert(index, item);
}
/// <inheritdoc /> /// <inheritdoc />
public bool Remove(NSObject item) public bool Remove(NSObject item) => array.Remove(item);
{
return array.Remove(item);
}
/// <inheritdoc /> /// <inheritdoc />
public void RemoveAt(int index) public void RemoveAt(int index) => array.RemoveAt(index);
{
array.RemoveAt(index);
}
/// <inheritdoc /> /// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => array.GetEnumerator();
{
return array.GetEnumerator();
}
public void Add(object item) public void Add(object item) => Add(Wrap(item));
{
Add(Wrap(item));
}
public bool Contains(object item) public bool Contains(object item) => Contains(Wrap(item));
{
return Contains(Wrap(item));
}
public int IndexOf(object item) public int IndexOf(object item) => array.IndexOf(Wrap(item));
{
return array.IndexOf(Wrap(item));
}
public void Insert(int index, object item) public void Insert(int index, object item) => Insert(index, Wrap(item));
{
Insert(index, Wrap(item));
}
public bool Remove(object item) public bool Remove(object item) => Remove(Wrap(item));
{
return Remove(Wrap(item));
}
} }
} }

View File

@@ -29,96 +29,64 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>Represents an Array.</summary>
/// Represents an Array.
/// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public partial class NSArray : NSObject public partial class NSArray : NSObject
{ {
List<NSObject> array; readonly List<NSObject> array;
/// <summary> /// <summary>Creates an empty array of the given length.</summary>
/// Creates an empty array of the given length.
/// </summary>
/// <param name="length">The number of elements this array will be able to hold.</param> /// <param name="length">The number of elements this array will be able to hold.</param>
public NSArray(int length) public NSArray(int length) => array = new List<NSObject>(length);
{
array = new List<NSObject>(length);
}
/// <summary> /// <summary>Creates a array from an existing one</summary>
/// Creates a array from an existing one
/// </summary>
/// <param name="a">The array which should be wrapped by the NSArray.</param> /// <param name="a">The array which should be wrapped by the NSArray.</param>
public NSArray(params NSObject[] a) public NSArray(params NSObject[] a) => array = new List<NSObject>(a);
{
array = new List<NSObject>(a);
}
/// <summary> /// <summary>Returns the size of the array.</summary>
/// Returns the size of the array.
/// </summary>
/// <value>The number of elements that this array can store.</value> /// <value>The number of elements that this array can store.</value>
public int Count => array.Count; public int Count => array.Count;
/// <summary> /// <summary>Returns the object stored at the given index.</summary>
/// Returns the object stored at the given index.
/// </summary>
/// <returns>The object at the given index.</returns> /// <returns>The object at the given index.</returns>
/// <param name="i">The index of the object.</param> /// <param name="i">The index of the object.</param>
[Obsolete] [Obsolete]
public NSObject ObjectAtIndex(int i) public NSObject ObjectAtIndex(int i) => array[i];
{
return array[i];
}
/// <summary> /// <summary>Remove the i-th element from the array. The array will be resized.</summary>
/// Remove the i-th element from the array.
/// The array will be resized.
/// </summary>
/// <param name="i">The index of the object</param> /// <param name="i">The index of the object</param>
[Obsolete] [Obsolete]
public void Remove(int i) public void Remove(int i) => array.RemoveAt(i);
{
array.RemoveAt(i);
}
/// <summary> /// <summary>Stores an object at the specified index. If there was another object stored at that index it will be replaced.</summary>
/// Stores an object at the specified index.
/// If there was another object stored at that index it will be replaced.
/// </summary>
/// <param name="key">The index where to store the object.</param> /// <param name="key">The index where to store the object.</param>
/// <param name="value">The object.</param> /// <param name="value">The object.</param>
[Obsolete] [Obsolete]
public void SetValue(int key, object value) 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); array[key] = Wrap(value);
} }
/// <summary> /// <summary>
/// Returns the array of NSObjects represented by this NSArray. /// Returns the array of NSObjects represented by this NSArray. Any changes to the values of this array will also
/// Any changes to the values of this array will also affect the NSArray. /// affect the NSArray.
/// </summary> /// </summary>
/// <returns>The actual array represented by this NSArray.</returns> /// <returns>The actual array represented by this NSArray.</returns>
[Obsolete] [Obsolete]
public NSObject[] GetArray() public NSObject[] GetArray() => array.ToArray();
{
return array.ToArray();
}
/// <summary> /// <summary>Checks whether an object is present in the array or whether it is equal to any of the objects in the array.</summary>
/// Checks whether an object is present in the array or whether it is equal
/// to any of the objects in the array.
/// </summary>
/// <returns><c>true</c>, when the object could be found. <c>false</c> otherwise.</returns> /// <returns><c>true</c>, when the object could be found. <c>false</c> otherwise.</returns>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
[Obsolete] [Obsolete]
public bool ContainsObject(object obj) public bool ContainsObject(object obj)
{ {
NSObject nso = Wrap(obj); NSObject nso = Wrap(obj);
foreach(NSObject elem in array) foreach(NSObject elem in array)
if(elem.Equals(nso)) if(elem.Equals(nso))
return true; return true;
@@ -127,9 +95,8 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Searches for an object in the array. If it is found its index will be /// Searches for an object in the array. If it is found its index will be returned. This method also returns an
/// returned. This method also returns an index if the object is not the same /// index if the object is not the same as the one stored in the array but has equal contents.
/// as the one stored in the array but has equal contents.
/// </summary> /// </summary>
/// <returns>The index of the object, if it was found. -1 otherwise.</returns> /// <returns>The index of the object, if it was found. -1 otherwise.</returns>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
@@ -137,6 +104,7 @@ namespace Claunia.PropertyList
public int IndexOfObject(object obj) public int IndexOfObject(object obj)
{ {
NSObject nso = Wrap(obj); NSObject nso = Wrap(obj);
for(int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
if(array[i].Equals(nso)) if(array[i].Equals(nso))
return i; return i;
@@ -145,10 +113,9 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Searches for an object in the array. If it is found its index will be /// Searches for an object in the array. If it is found its index will be returned. This method only returns the
/// returned. This method only returns the index of an object that is /// index of an object that is <b>identical</b> to the given one. Thus objects that might contain the same value as the
/// <b>identical</b> to the given one. Thus objects that might contain the /// given one will not be considered.
/// same value as the given one will not be considered.
/// </summary> /// </summary>
/// <returns>The index of the object, if it was found. -1 otherwise.</returns> /// <returns>The index of the object, if it was found. -1 otherwise.</returns>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
@@ -156,6 +123,7 @@ namespace Claunia.PropertyList
public int IndexOfIdenticalObject(object obj) public int IndexOfIdenticalObject(object obj)
{ {
NSObject nso = Wrap(obj); NSObject nso = Wrap(obj);
for(int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
if(array[i] == nso) if(array[i] == nso)
return i; return i;
@@ -163,18 +131,13 @@ namespace Claunia.PropertyList
return -1; return -1;
} }
/// <summary> /// <summary>Returns the last object contained in this array.</summary>
/// Returns the last object contained in this array.
/// </summary>
/// <returns>The value of the highest index in the array.</returns> /// <returns>The value of the highest index in the array.</returns>
public NSObject LastObject() public NSObject LastObject() => array[array.Count - 1];
{
return array[array.Count - 1];
}
/// <summary> /// <summary>
/// Returns a new array containing only the values stored at the given /// Returns a new array containing only the values stored at the given indices. The values are sorted by their
/// indices. The values are sorted by their index. /// index.
/// </summary> /// </summary>
/// <returns>The new array containing the objects stored at the given indices.</returns> /// <returns>The new array containing the objects stored at the given indices.</returns>
/// <param name="indexes">The indices of the objects.</param> /// <param name="indexes">The indices of the objects.</param>
@@ -182,7 +145,10 @@ namespace Claunia.PropertyList
{ {
NSObject[] result = new NSObject[indexes.Length]; NSObject[] result = new NSObject[indexes.Length];
Array.Sort(indexes); 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; return result;
} }
@@ -200,17 +166,18 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if(obj.GetType().Equals(typeof(NSArray))) return ArrayEquals((NSArray)obj, this); if(obj is NSArray nsArray)
return ArrayEquals(nsArray, this);
NSObject nso = Wrap(obj); NSObject nso = Wrap(obj);
if(nso.GetType().Equals(typeof(NSArray))) return ArrayEquals((NSArray)nso, this);
if(nso is NSArray nsoArray)
return ArrayEquals(nsoArray, this);
return false; return false;
} }
/// <summary> /// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSArray" /> object.</summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSArray" /> object.
/// </summary>
/// <returns> /// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table. /// hash table.
@@ -218,7 +185,8 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
hash = 89 * hash + array.GetHashCode(); hash = (89 * hash) + array.GetHashCode();
return hash; return hash;
} }
@@ -227,6 +195,7 @@ namespace Claunia.PropertyList
Indent(xml, level); Indent(xml, level);
xml.Append("<array>"); xml.Append("<array>");
xml.Append(NEWLINE); xml.Append(NEWLINE);
foreach(NSObject o in array) foreach(NSObject o in array)
{ {
o.ToXml(xml, level + 1); o.ToXml(xml, level + 1);
@@ -240,23 +209,23 @@ namespace Claunia.PropertyList
internal override void AssignIDs(BinaryPropertyListWriter outPlist) internal override void AssignIDs(BinaryPropertyListWriter outPlist)
{ {
base.AssignIDs(outPlist); 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) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.WriteIntHeader(0xA, array.Count); 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> /// <summary>
/// <para>Generates a valid ASCII property list which has this NSArray as its root object.</para>
/// <para> /// <para>
/// Generates a valid ASCII property list which has this NSArray as its /// The generated property list complies with the format as described in
/// root object.
/// </para>
/// <para>
/// The generated property list complies with the format as
/// described in
/// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
/// Property List Programming Guide - Old-Style ASCII Property Lists. /// Property List Programming Guide - Old-Style ASCII Property Lists.
/// </para> /// </para>
@@ -264,30 +233,28 @@ namespace Claunia.PropertyList
/// <returns>ASCII representation of this object.</returns> /// <returns>ASCII representation of this object.</returns>
public string ToASCIIPropertyList() public string ToASCIIPropertyList()
{ {
StringBuilder ascii = new StringBuilder(); var ascii = new StringBuilder();
ToASCII(ascii, 0); ToASCII(ascii, 0);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
return ascii.ToString(); return ascii.ToString();
} }
/// <summary> /// <summary>
/// <para>Generates a valid ASCII property list in GnuStep format which has this NSArray as its root object.</para>
/// <para> /// <para>
/// Generates a valid ASCII property list in GnuStep format which has this /// The generated property list complies with the format as described in
/// NSArray as its root object. /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html GnuStep -
/// </para> /// NSPropertyListSerialization class documentation.
/// <para>
/// The generated property list complies with
/// the format as described in
/// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html
/// GnuStep - NSPropertyListSerialization class documentation.
/// </para> /// </para>
/// </summary> /// </summary>
/// <returns>GnuStep ASCII representation of this object.</returns> /// <returns>GnuStep ASCII representation of this object.</returns>
public string ToGnuStepASCIIPropertyList() public string ToGnuStepASCIIPropertyList()
{ {
StringBuilder ascii = new StringBuilder(); var ascii = new StringBuilder();
ToASCIIGnuStep(ascii, 0); ToASCIIGnuStep(ascii, 0);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
return ascii.ToString(); return ascii.ToString();
} }
@@ -296,11 +263,11 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for(int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
{ {
Type objClass = array[i].GetType(); if((array[i] is NSDictionary || array[i] is NSArray || array[i] is NSData) &&
if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || indexOfLastNewLine != ascii.Length)
objClass.Equals(typeof(NSData))) && indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -308,18 +275,21 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(i != 0) ascii.Append(" "); if(i != 0)
ascii.Append(" ");
array[i].ToASCII(ascii, 0); 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)
{
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
}
ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
} }
@@ -329,11 +299,13 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for(int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
{ {
Type objClass = array[i].GetType(); Type objClass = array[i].GetType();
if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
objClass.Equals(typeof(NSData))) && indexOfLastNewLine != ascii.Length) if((array[i] is NSDictionary || array[i] is NSArray || array[i] is NSData) &&
indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -341,18 +313,21 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(i != 0) ascii.Append(" "); if(i != 0)
ascii.Append(" ");
array[i].ToASCIIGnuStep(ascii, 0); 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)
{
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
}
ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
} }
@@ -371,12 +346,14 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(!(obj is NSArray)) return false; if(obj is not NSArray nsArray)
return false;
if(array.Count != ((NSArray)obj).array.Count) return false; if(array.Count != nsArray.array.Count)
return false;
for(int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
if(!array[i].Equals(((NSArray)obj)[i])) if(!array[i].Equals(nsArray[i]))
return false; return false;
return true; return true;

View File

@@ -29,9 +29,7 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>NSData objects are wrappers for byte buffers</summary>
/// NSData objects are wrappers for byte buffers
/// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class NSData : NSObject public class NSData : NSObject
@@ -40,78 +38,51 @@ namespace Claunia.PropertyList
// Each line contains 68 characters. // Each line contains 68 characters.
const int DataLineLength = 68; const int DataLineLength = 68;
/// <summary> /// <summary>Creates the NSData object from the binary representation of it.</summary>
/// Creates the NSData object from the binary representation of it.
/// </summary>
/// <param name="bytes">The raw data contained in the NSData object.</param> /// <param name="bytes">The raw data contained in the NSData object.</param>
public NSData(byte[] bytes) public NSData(byte[] bytes) => Bytes = bytes;
{
Bytes = bytes;
}
/// <summary> /// <summary>Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes.</summary>
/// Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes.
/// </summary>
/// <param name="base64">The Base64 encoded contents of the NSData object.</param> /// <param name="base64">The Base64 encoded contents of the NSData object.</param>
/// <exception cref="FormatException">When the given string is not a proper Base64 formatted string.</exception> /// <exception cref="FormatException">When the given string is not a proper Base64 formatted string.</exception>
public NSData(string base64) public NSData(string base64) => Bytes = Convert.FromBase64String(base64);
{
Bytes = Convert.FromBase64String(base64);
}
/// <summary> /// <summary>Creates a NSData object from a file. Using the files contents as the contents of this NSData object.</summary>
/// Creates a NSData object from a file. Using the files contents as the contents of this NSData object.
/// </summary>
/// <param name="file">The file containing the data.</param> /// <param name="file">The file containing the data.</param>
/// <exception cref="FileNotFoundException">If the file could not be found.</exception> /// <exception cref="FileNotFoundException">If the file could not be found.</exception>
/// <exception cref="IOException">If the file could not be read.</exception> /// <exception cref="IOException">If the file could not be read.</exception>
public NSData(FileInfo file) public NSData(FileInfo file)
{ {
Bytes = new byte[(int)file.Length]; Bytes = new byte[(int)file.Length];
using(FileStream raf = file.OpenRead()) raf.Read(Bytes, 0, (int)file.Length);
using FileStream raf = file.OpenRead();
raf.Read(Bytes, 0, (int)file.Length);
} }
/// <summary> /// <summary>The bytes contained in this NSData object.</summary>
/// The bytes contained in this NSData object.
/// </summary>
/// <value>The data as bytes</value> /// <value>The data as bytes</value>
public byte[] Bytes { get; } public byte[] Bytes { get; }
/// <summary> /// <summary>Gets the amount of data stored in this object.</summary>
/// Gets the amount of data stored in this object.
/// </summary>
/// <value>The number of bytes contained in this object.</value> /// <value>The number of bytes contained in this object.</value>
public int Length => Bytes.Length; public int Length => Bytes.Length;
/// <summary> /// <summary>Loads the bytes from this NSData object into a byte buffer.</summary>
/// Loads the bytes from this NSData object into a byte buffer.
/// </summary>
/// <param name="buf">The byte buffer which will contain the data</param> /// <param name="buf">The byte buffer which will contain the data</param>
/// <param name="length">The amount of data to copy</param> /// <param name="length">The amount of data to copy</param>
public void GetBytes(MemoryStream buf, int length) public void GetBytes(MemoryStream buf, int length) => buf.Write(Bytes, 0, Math.Min(Bytes.Length, length));
{
buf.Write(Bytes, 0, Math.Min(Bytes.Length, length));
}
/// <summary> /// <summary>Loads the bytes from this NSData object into a byte buffer.</summary>
/// Loads the bytes from this NSData object into a byte buffer.
/// </summary>
/// <param name="buf">The byte buffer which will contain the data</param> /// <param name="buf">The byte buffer which will contain the data</param>
/// <param name="rangeStart">The start index.</param> /// <param name="rangeStart">The start index.</param>
/// <param name="rangeStop">The stop index.</param> /// <param name="rangeStop">The stop index.</param>
public void GetBytes(MemoryStream buf, int rangeStart, int rangeStop) public void GetBytes(MemoryStream buf, int rangeStart, int rangeStop) =>
{
buf.Write(Bytes, rangeStart, Math.Min(Bytes.Length, rangeStop)); buf.Write(Bytes, rangeStart, Math.Min(Bytes.Length, rangeStop));
}
/// <summary> /// <summary>Gets the Base64 encoded data contained in this NSData object.</summary>
/// Gets the Base64 encoded data contained in this NSData object.
/// </summary>
/// <returns>The Base64 encoded data as a <c>string</c>.</returns> /// <returns>The Base64 encoded data as a <c>string</c>.</returns>
public string GetBase64EncodedData() public string GetBase64EncodedData() => Convert.ToBase64String(Bytes);
{
return Convert.ToBase64String(Bytes);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current /// Determines whether the specified <see cref="System.Object" /> is equal to the current
@@ -125,14 +96,10 @@ namespace Claunia.PropertyList
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current /// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>. /// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>.
/// </returns> /// </returns>
public override bool Equals(object obj) public override bool Equals(object obj) =>
{ obj.GetType().Equals(GetType()) && ArrayEquals(((NSData)obj).Bytes, Bytes);
return obj.GetType().Equals(GetType()) && ArrayEquals(((NSData)obj).Bytes, Bytes);
}
/// <summary> /// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSData" /> object.</summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSData" /> object.
/// </summary>
/// <returns> /// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table. /// hash table.
@@ -140,7 +107,8 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 5; int hash = 5;
hash = 67 * hash + Bytes.GetHashCode(); hash = (67 * hash) + Bytes.GetHashCode();
return hash; return hash;
} }
@@ -150,6 +118,7 @@ namespace Claunia.PropertyList
xml.Append("<data>"); xml.Append("<data>");
xml.Append(NEWLINE); xml.Append(NEWLINE);
string base64 = GetBase64EncodedData(); string base64 = GetBase64EncodedData();
foreach(string line in base64.Split('\n')) foreach(string line in base64.Split('\n'))
for(int offset = 0; offset < base64.Length; offset += DataLineLength) for(int offset = 0; offset < base64.Length; offset += DataLineLength)
{ {
@@ -173,25 +142,26 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.DATA_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.DATA_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for(int i = 0; i < Bytes.Length; i++) for(int i = 0; i < Bytes.Length; i++)
{ {
int b = Bytes[i] & 0xFF; int b = Bytes[i] & 0xFF;
ascii.Append(string.Format("{0:x2}", b)); ascii.Append($"{b:x2}");
if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; 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); ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN);
} }
internal override void ToASCIIGnuStep(StringBuilder ascii, int level) internal override void ToASCIIGnuStep(StringBuilder ascii, int level) => ToASCII(ascii, level);
{
ToASCII(ascii, level);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
@@ -205,21 +175,10 @@ namespace Claunia.PropertyList
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current /// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>. /// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>.
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj) => obj is NSData data && ArrayEquals(Bytes, data.Bytes);
{
if(!(obj is NSData)) return false;
return ArrayEquals(Bytes, ((NSData)obj).Bytes); public static explicit operator byte[](NSData value) => value.Bytes;
}
public static explicit operator byte[](NSData value) public static explicit operator NSData(byte[] value) => new(value);
{
return value.Bytes;
}
public static explicit operator NSData(byte[] value)
{
return new NSData(value);
}
} }
} }

View File

@@ -29,92 +29,69 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>Represents a date</summary>
/// Represents a date
/// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class NSDate : NSObject public class NSDate : NSObject
{ {
static readonly DateTime EPOCH = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc); static readonly DateTime EPOCH = new(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// The datetime ends with 'Z', which indicates UTC time. To make sure .NET // The datetime ends with 'Z', which indicates UTC time. To make sure .NET
// understands the 'Z' character as a timezone, specify the 'K' format string. // understands the 'Z' character as a timezone, specify the 'K' format string.
static readonly string sdfDefault = "yyyy-MM-dd'T'HH:mm:ssK"; static readonly string sdfDefault = "yyyy-MM-dd'T'HH:mm:ssK";
static readonly string sdfGnuStep = "yyyy-MM-dd HH:mm:ss zzz"; static readonly string sdfGnuStep = "yyyy-MM-dd HH:mm:ss zzz";
static readonly string[] sdfAll = { sdfDefault, sdfGnuStep }; static readonly string[] sdfAll =
{
sdfDefault, sdfGnuStep
};
static readonly CultureInfo provider = CultureInfo.InvariantCulture; static readonly CultureInfo provider = CultureInfo.InvariantCulture;
/// <summary> /// <summary>Creates a date from its binary representation.</summary>
/// Creates a date from its binary representation.
/// </summary>
/// <param name="bytes">bytes The date bytes</param> /// <param name="bytes">bytes The date bytes</param>
public NSDate(ReadOnlySpan<byte> bytes) public NSDate(ReadOnlySpan<byte> bytes) =>
{
//dates are 8 byte big-endian double, seconds since the epoch //dates are 8 byte big-endian double, seconds since the epoch
Date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes)); Date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes));
}
/// <summary> /// <summary>
/// Parses a date from its textual representation. /// Parses a date from its textual representation. That representation has the following pattern:
/// That representation has the following pattern: <code>yyyy-MM-dd'T'HH:mm:ss'Z'</code> /// <code>yyyy-MM-dd'T'HH:mm:ss'Z'</code>
/// </summary> /// </summary>
/// <param name="textRepresentation">The textual representation of the date (ISO 8601 format)</param> /// <param name="textRepresentation">The textual representation of the date (ISO 8601 format)</param>
/// <exception cref="FormatException">When the date could not be parsed, i.e. it does not match the expected pattern.</exception> /// <exception cref="FormatException">When the date could not be parsed, i.e. it does not match the expected pattern.</exception>
public NSDate(string textRepresentation) public NSDate(string textRepresentation) => Date = ParseDateString(textRepresentation);
{
Date = ParseDateString(textRepresentation);
}
/// <summary> /// <summary>Creates a NSDate from a .NET DateTime</summary>
/// Creates a NSDate from a .NET DateTime
/// </summary>
/// <param name="d">The date</param> /// <param name="d">The date</param>
public NSDate(DateTime d) public NSDate(DateTime d) => Date = d;
{
Date = d;
}
/// <summary> /// <summary>Gets the date.</summary>
/// Gets the date.
/// </summary>
/// <returns>The date.</returns> /// <returns>The date.</returns>
public DateTime Date { get; } public DateTime Date { get; }
/// <summary> /// <summary>Parses the XML date string and creates a .NET DateTime object from it.</summary>
/// Parses the XML date string and creates a .NET DateTime object from it.
/// </summary>
/// <returns>The parsed Date</returns> /// <returns>The parsed Date</returns>
/// <param name="textRepresentation">The date string as found in the XML property list</param> /// <param name="textRepresentation">The date string as found in the XML property list</param>
/// <exception cref="FormatException">Given string cannot be parsed</exception> /// <exception cref="FormatException">Given string cannot be parsed</exception>
static DateTime ParseDateString(string textRepresentation) static DateTime ParseDateString(string textRepresentation) =>
{ DateTime.ParseExact(textRepresentation, sdfAll, provider, DateTimeStyles.None);
return DateTime.ParseExact(textRepresentation, sdfAll, provider, DateTimeStyles.None);
}
/// <summary> /// <summary>
/// Generates a String representation of a .NET DateTime object. The string /// Generates a String representation of a .NET DateTime object. The string is formatted according to the
/// is formatted according to the specification for XML property list dates. /// specification for XML property list dates.
/// </summary> /// </summary>
/// <param name="date">The date which should be represented.</param> /// <param name="date">The date which should be represented.</param>
/// <returns>The string representation of the date.</returns> /// <returns>The string representation of the date.</returns>
public static string MakeDateString(DateTime date) public static string MakeDateString(DateTime date) => date.ToUniversalTime().ToString(sdfDefault, provider);
{
return date.ToUniversalTime().ToString(sdfDefault, provider);
}
/// <summary> /// <summary>
/// Generates a String representation of a .NET DateTime object. The string /// Generates a String representation of a .NET DateTime object. The string is formatted according to the
/// is formatted according to the specification for GnuStep ASCII property /// specification for GnuStep ASCII property list dates.
/// list dates.
/// </summary> /// </summary>
/// <param name="date">The date which should be represented.</param> /// <param name="date">The date which should be represented.</param>
/// <returns>The string representation of the date.</returns> /// <returns>The string representation of the date.</returns>
static string MakeDateStringGnuStep(DateTime date) static string MakeDateStringGnuStep(DateTime date) => date.ToString(sdfGnuStep, provider);
{
return date.ToString(sdfGnuStep, provider);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current /// Determines whether the specified <see cref="System.Object" /> is equal to the current
@@ -128,22 +105,14 @@ namespace Claunia.PropertyList
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current /// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />; otherwise, <c>false</c>. /// <see cref="Claunia.PropertyList.NSDate" />; otherwise, <c>false</c>.
/// </returns> /// </returns>
public override bool Equals(object obj) public override bool Equals(object obj) => obj.GetType().Equals(GetType()) && Date.Equals(((NSDate)obj).Date);
{
return obj.GetType().Equals(GetType()) && Date.Equals(((NSDate)obj).Date);
}
/// <summary> /// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSDate" /> object.</summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSDate" /> object.
/// </summary>
/// <returns> /// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table. /// hash table.
/// </returns> /// </returns>
public override int GetHashCode() public override int GetHashCode() => Date.GetHashCode();
{
return Date.GetHashCode();
}
internal override void ToXml(StringBuilder xml, int level) internal override void ToXml(StringBuilder xml, int level)
{ {
@@ -159,14 +128,9 @@ namespace Claunia.PropertyList
outPlist.WriteDouble((Date - EPOCH).TotalSeconds); outPlist.WriteDouble((Date - EPOCH).TotalSeconds);
} }
/// <summary> /// <summary>Generates a string representation of the date.</summary>
/// Generates a string representation of the date.
/// </summary>
/// <returns>A string representation of the date.</returns> /// <returns>A string representation of the date.</returns>
public override string ToString() public override string ToString() => Date.ToString();
{
return Date.ToString();
}
internal override void ToASCII(StringBuilder ascii, int level) internal override void ToASCII(StringBuilder ascii, int level)
{ {
@@ -198,21 +162,16 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(!(obj is NSDate)) return false; if(obj is not NSDate date)
return false;
int equality = DateTime.Compare(Date, ((NSDate)obj).Date); int equality = DateTime.Compare(Date, date.Date);
return equality == 0; return equality == 0;
} }
public static explicit operator DateTime(NSDate value) public static explicit operator DateTime(NSDate value) => value.Date;
{
return value.Date;
}
public static explicit operator NSDate(DateTime value) public static explicit operator NSDate(DateTime value) => new(value);
{
return new NSDate(value);
}
} }
} }

View File

@@ -32,16 +32,11 @@ namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>
/// A NSDictionary is a collection of keys and values, essentially a Dictionary. /// A NSDictionary is a collection of keys and values, essentially a Dictionary. The keys are simple Strings
/// The keys are simple Strings whereas the values can be any kind of NSObject. /// 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> /// </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> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
@@ -50,120 +45,88 @@ namespace Claunia.PropertyList
readonly Dictionary<string, NSObject> dict; readonly Dictionary<string, NSObject> dict;
// Maps the keys in this dictionary to their NSString equivalent. Makes sure the NSString // Maps the keys in this dictionary to their NSString equivalent. Makes sure the NSString
// object remains constant accross calls to AssignIDs and ToBinary // object remains constant across calls to AssignIDs and ToBinary
readonly Dictionary<string, NSString> keys; readonly Dictionary<string, NSString> keys;
/// <summary> /// <summary>Creates a new empty NSDictionary with a specific capacity.</summary>
/// Creates a new empty NSDictionary with a specific capacity. /// <param name="capacity">The capacity of the dictionary.</param>
/// </summary>
/// <param name="capacity">
/// The capacity of the dictionary.
/// </param>
public NSDictionary(int capacity) public NSDictionary(int capacity)
{ {
dict = new Dictionary<string, NSObject>(capacity); dict = new Dictionary<string, NSObject>(capacity);
keys = new Dictionary<string, NSString>(capacity); keys = new Dictionary<string, NSString>(capacity);
} }
/// <summary> /// <summary>Creates a new empty NSDictionary.</summary>
/// Creates a new empty NSDictionary.
/// </summary>
public NSDictionary() : this(0) {} public NSDictionary() : this(0) {}
/// <summary> /// <summary>Gets a value indicating whether this instance is empty.</summary>
/// Gets a value indicating whether this instance is empty.
/// </summary>
/// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value>
public bool IsEmpty => dict.Count == 0; public bool IsEmpty => dict.Count == 0;
#region IEnumerable implementation #region IEnumerable implementation
/// <summary> /// <summary>Gets the enumerator.</summary>
/// Gets the enumerator.
/// </summary>
/// <returns>The enumerator.</returns> /// <returns>The enumerator.</returns>
public IEnumerator<KeyValuePair<string, NSObject>> GetEnumerator() public IEnumerator<KeyValuePair<string, NSObject>> GetEnumerator() => dict.GetEnumerator();
{
return dict.GetEnumerator();
}
#endregion #endregion
#region IEnumerable implementation #region IEnumerable implementation
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator();
{
return dict.GetEnumerator();
}
#endregion #endregion
/// <summary> /// <summary>
/// Gets the hashmap which stores the keys and values of this dictionary. /// Gets the hashmap which stores the keys and values of this dictionary. Changes to the hashmap's contents are
/// Changes to the hashmap's contents are directly reflected in this /// directly reflected in this dictionary.
/// dictionary.
/// </summary> /// </summary>
/// <returns>The hashmap which is used by this dictionary to store its contents.</returns> /// <returns>The hashmap which is used by this dictionary to store its contents.</returns>
public Dictionary<string, NSObject> GetDictionary() public Dictionary<string, NSObject> GetDictionary() => dict;
{
return dict;
}
/// <summary> /// <summary>Gets the NSObject stored for the given key.</summary>
/// Gets the NSObject stored for the given key.
/// </summary>
/// <returns>The object.</returns> /// <returns>The object.</returns>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
public NSObject ObjectForKey(string key) public NSObject ObjectForKey(string key)
{ {
NSObject nso; NSObject nso;
return dict.TryGetValue(key, out nso) ? nso : null; return dict.TryGetValue(key, out nso) ? nso : null;
} }
/// <summary> /// <summary>Checks if the specified object key is contained in the current instance.</summary>
/// Checks if the specified object key is contained in the current instance.
/// </summary>
/// <returns><c>true</c>, if key is contained, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if key is contained, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
public bool ContainsKey(object key) public bool ContainsKey(object key) => key is string s && dict.ContainsKey(s);
{
return key is string && dict.ContainsKey((string)key);
}
/// <summary> /// <summary>Removes the item corresponding to the specified key from the current instance, if found.</summary>
/// Removes the item corresponding to the specified key from the current instance, if found.
/// </summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
/// <returns><c>true</c>, if removed, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if removed, <c>false</c> otherwise.</returns>
public bool Remove(object key) public bool Remove(object key) => key is string s && dict.Remove(s);
{
return key is string && dict.Remove((string)key);
}
/// <summary> /// <summary>Gets the <see cref="NSObject" /> corresponding to the specified key from the current instance.</summary>
/// Gets the <see cref="NSObject" /> corresponding to the specified key from the current instance.
/// </summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
/// <returns>The object corresponding to the specified key, null if not found in the current instance.</returns> /// <returns>The object corresponding to the specified key, null if not found in the current instance.</returns>
public NSObject Get(object key) public NSObject Get(object key)
{ {
if(key is string) return ObjectForKey((string)key); if(key is string s)
return ObjectForKey(s);
return null; return null;
} }
/// <summary> /// <summary>Checks if the current instance contains the object corresponding to the specified key.</summary>
/// Checks if the current instance contains the object corresponding to the specified key.
/// </summary>
/// <returns><c>true</c>, if value is contained, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if value is contained, <c>false</c> otherwise.</returns>
/// <param name="value">Object to search up in the current instance.</param> /// <param name="value">Object to search up in the current instance.</param>
public bool ContainsValue(object value) public bool ContainsValue(object value)
{ {
if(value == null) return false; if(value == null)
return false;
NSObject wrap = Wrap(value); NSObject wrap = Wrap(value);
return dict.ContainsValue(wrap); return dict.ContainsValue(wrap);
} }
/// <summary> /// <summary>
/// Puts a new key-value pair into this dictionary. /// Puts a new key-value pair into this dictionary. If the value is null, no operation will be performed on the
/// If the value is null, no operation will be performed on the dictionary. /// dictionary.
/// </summary> /// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="obj"> /// <param name="obj">
@@ -172,139 +135,104 @@ namespace Claunia.PropertyList
/// </param> /// </param>
public void Add(string key, object obj) public void Add(string key, object obj)
{ {
if(obj == null) return; if(obj == null)
return;
Add(key, Wrap(obj)); Add(key, Wrap(obj));
} }
/// <summary> /// <summary>Puts a new key-value pair into this dictionary.</summary>
/// Puts a new key-value pair into this dictionary.
/// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="obj">The value.</param> /// <param name="obj">The value.</param>
public void Add(string key, long obj) public void Add(string key, long obj) => Add(key, new NSNumber(obj));
{
Add(key, new NSNumber(obj));
}
/// <summary> /// <summary>Puts a new key-value pair into this dictionary.</summary>
/// Puts a new key-value pair into this dictionary.
/// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="obj">The value.</param> /// <param name="obj">The value.</param>
public void Add(string key, double obj) public void Add(string key, double obj) => Add(key, new NSNumber(obj));
{
Add(key, new NSNumber(obj));
}
/// <summary> /// <summary>Puts a new key-value pair into this dictionary.</summary>
/// Puts a new key-value pair into this dictionary.
/// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="obj">The value.</param> /// <param name="obj">The value.</param>
public void Add(string key, bool obj) public void Add(string key, bool obj) => Add(key, new NSNumber(obj));
{
Add(key, new NSNumber(obj));
}
/// <summary> /// <summary>Checks whether a given value is contained in this dictionary.</summary>
/// Checks whether a given value is contained in this dictionary.
/// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(string val) public bool ContainsValue(string val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o.GetType().Equals(typeof(NSString))) if(o is NSString str &&
{ str.Content.Equals(val))
NSString str = (NSString)o; return true;
if(str.Content.Equals(val)) return true;
}
return false; return false;
} }
/// <summary> /// <summary>Checks whether a given value is contained in this dictionary.</summary>
/// Checks whether a given value is contained in this dictionary.
/// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(long val) public bool ContainsValue(long val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o.GetType().Equals(typeof(NSNumber))) if(o is NSNumber num &&
{ num.isInteger() &&
NSNumber num = (NSNumber)o; num.ToInt() == val)
if(num.isInteger() && num.ToInt() == val) return true; return true;
}
return false; return false;
} }
/// <summary> /// <summary>Checks whether a given value is contained in this dictionary.</summary>
/// Checks whether a given value is contained in this dictionary.
/// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(double val) public bool ContainsValue(double val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o.GetType().Equals(typeof(NSNumber))) if(o is NSNumber num &&
{ num.isReal() &&
NSNumber num = (NSNumber)o; num.ToDouble() == val)
if(num.isReal() && num.ToDouble() == val) return true; return true;
}
return false; return false;
} }
/// <summary> /// <summary>Checks whether a given value is contained in this dictionary.</summary>
/// Checks whether a given value is contained in this dictionary.
/// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(bool val) public bool ContainsValue(bool val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o.GetType().Equals(typeof(NSNumber))) if(o is NSNumber num &&
{ num.isBoolean() &&
NSNumber num = (NSNumber)o; num.ToBool() == val)
if(num.isBoolean() && num.ToBool() == val) return true; return true;
}
return false; return false;
} }
/// <summary> /// <summary>Checks whether a given value is contained in this dictionary.</summary>
/// Checks whether a given value is contained in this dictionary.
/// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(DateTime val) public bool ContainsValue(DateTime val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o.GetType().Equals(typeof(NSDate))) if(o is NSDate dat &&
{ dat.Date.Equals(val))
NSDate dat = (NSDate)o; return true;
if(dat.Date.Equals(val)) return true;
}
return false; return false;
} }
/// <summary> /// <summary>Checks whether a given value is contained in this dictionary.</summary>
/// Checks whether a given value is contained in this dictionary.
/// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(byte[] val) public bool ContainsValue(byte[] val)
{ {
foreach(NSObject o in dict.Values) foreach(NSObject o in dict.Values)
if(o.GetType().Equals(typeof(NSData))) if(o is NSData dat &&
{ ArrayEquals(dat.Bytes, val))
NSData dat = (NSData)o; return true;
if(ArrayEquals(dat.Bytes, val)) return true;
}
return false; return false;
} }
@@ -323,26 +251,27 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(!(obj is NSDictionary)) return false; if(obj is not NSDictionary dictionary)
return false;
if(((NSDictionary)obj).dict.Count != dict.Count) return false; if(dictionary.dict.Count != dict.Count)
return false;
bool found;
foreach(KeyValuePair<string, NSObject> kvp in dict) foreach(KeyValuePair<string, NSObject> kvp in dict)
{ {
NSObject nsoB; bool found = dictionary.dict.TryGetValue(kvp.Key, out NSObject nsoB);
found = ((NSDictionary)obj).dict.TryGetValue(kvp.Key, out nsoB);
if(!found) return false; if(!found)
if(!kvp.Value.Equals(nsoB)) return false; return false;
if(!kvp.Value.Equals(nsoB))
return false;
} }
return true; return true;
} }
/// <summary> /// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSDictionary" /> object.</summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSDictionary" /> object.
/// </summary>
/// <returns> /// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table. /// hash table.
@@ -350,7 +279,8 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
hash = 83 * hash + (dict != null ? dict.GetHashCode() : 0); hash = (83 * hash) + (dict != null ? dict.GetHashCode() : 0);
return hash; return hash;
} }
@@ -359,19 +289,24 @@ namespace Claunia.PropertyList
Indent(xml, level); Indent(xml, level);
xml.Append("<dict>"); xml.Append("<dict>");
xml.Append(NEWLINE); xml.Append(NEWLINE);
foreach(KeyValuePair<string, NSObject> kvp in dict) foreach(KeyValuePair<string, NSObject> kvp in dict)
{ {
Indent(xml, level + 1); Indent(xml, level + 1);
xml.Append("<key>"); xml.Append("<key>");
//According to http://www.w3.org/TR/REC-xml/#syntax node values must not //According to http://www.w3.org/TR/REC-xml/#syntax node values must not
//contain the characters < or &. Also the > character should be escaped. //contain the characters < or &. Also the > character should be escaped.
if(kvp.Key.Contains("&") || kvp.Key.Contains("<") || kvp.Key.Contains(">")) if(kvp.Key.Contains("&") ||
kvp.Key.Contains("<") ||
kvp.Key.Contains(">"))
{ {
xml.Append("<![CDATA["); xml.Append("<![CDATA[");
xml.Append(kvp.Key.Replace("]]>", "]]]]><![CDATA[>")); xml.Append(kvp.Key.Replace("]]>", "]]]]><![CDATA[>"));
xml.Append("]]>"); xml.Append("]]>");
} }
else xml.Append(kvp.Key); else
xml.Append(kvp.Key);
xml.Append("</key>"); xml.Append("</key>");
xml.Append(NEWLINE); xml.Append(NEWLINE);
@@ -387,47 +322,53 @@ namespace Claunia.PropertyList
{ {
base.AssignIDs(outPlist); 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) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.WriteIntHeader(0xD, dict.Count); 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(entry.Value)); 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));
} }
/// <summary> /// <summary>
/// Generates a valid ASCII property list which has this NSDictionary as its /// Generates a valid ASCII property list which has this NSDictionary as its root object. The generated property
/// root object. The generated property list complies with the format as /// list complies with the format as described in
/// described in
/// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
/// Property List Programming Guide - Old-Style ASCII Property Lists. /// Property List Programming Guide - Old-Style ASCII Property Lists.
/// </summary> /// </summary>
/// <returns>ASCII representation of this object.</returns> /// <returns>ASCII representation of this object.</returns>
public string ToASCIIPropertyList() public string ToASCIIPropertyList()
{ {
StringBuilder ascii = new StringBuilder(); var ascii = new StringBuilder();
ToASCII(ascii, 0); ToASCII(ascii, 0);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
return ascii.ToString(); return ascii.ToString();
} }
/// <summary> /// <summary>
/// Generates a valid ASCII property list in GnuStep format which has this /// Generates a valid ASCII property list in GnuStep format which has this NSDictionary as its root object. The
/// NSDictionary as its root object. The generated property list complies with /// generated property list complies with the format as described in
/// the format as described in /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html GnuStep -
/// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html /// NSPropertyListSerialization class documentation.
/// GnuStep - NSPropertyListSerialization class documentation.
/// </summary> /// </summary>
/// <returns>GnuStep ASCII representation of this object.</returns> /// <returns>GnuStep ASCII representation of this object.</returns>
public string ToGnuStepASCIIPropertyList() public string ToGnuStepASCIIPropertyList()
{ {
StringBuilder ascii = new StringBuilder(); var ascii = new StringBuilder();
ToASCIIGnuStep(ascii, 0); ToASCIIGnuStep(ascii, 0);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
return ascii.ToString(); return ascii.ToString();
} }
@@ -436,6 +377,7 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
foreach(string key in Keys) foreach(string key in Keys)
{ {
NSObject val = ObjectForKey(key); NSObject val = ObjectForKey(key);
@@ -443,9 +385,8 @@ namespace Claunia.PropertyList
ascii.Append("\""); ascii.Append("\"");
ascii.Append(NSString.EscapeStringForASCII(key)); ascii.Append(NSString.EscapeStringForASCII(key));
ascii.Append("\" ="); ascii.Append("\" =");
Type objClass = val.GetType();
if(objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || if(val is NSDictionary or NSArray or NSData)
objClass.Equals(typeof(NSData)))
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
val.ToASCII(ascii, level + 2); val.ToASCII(ascii, level + 2);
@@ -469,6 +410,7 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
foreach(string key in Keys) foreach(string key in Keys)
{ {
NSObject val = ObjectForKey(key); NSObject val = ObjectForKey(key);
@@ -476,9 +418,8 @@ namespace Claunia.PropertyList
ascii.Append("\""); ascii.Append("\"");
ascii.Append(NSString.EscapeStringForASCII(key)); ascii.Append(NSString.EscapeStringForASCII(key));
ascii.Append("\" ="); ascii.Append("\" =");
Type objClass = val.GetType();
if(objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || if(val is NSDictionary or NSArray or NSData)
objClass.Equals(typeof(NSData)))
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
val.ToASCIIGnuStep(ascii, level + 2); val.ToASCIIGnuStep(ascii, level + 2);
@@ -498,9 +439,7 @@ namespace Claunia.PropertyList
} }
#region IDictionary implementation #region IDictionary implementation
/// <summary> /// <summary>Add the specified key and value.</summary>
/// Add the specified key and value.
/// </summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
/// <param name="value">Value.</param> /// <param name="value">Value.</param>
public void Add(string key, NSObject value) public void Add(string key, NSObject value)
@@ -509,79 +448,56 @@ namespace Claunia.PropertyList
keys.Add(key, new NSString(key)); keys.Add(key, new NSString(key));
} }
/// <summary> /// <summary>Checks if there is any item contained in the current instance corresponding with the specified key.</summary>
/// Checks if there is any item contained in the current instance corresponding with the specified key.
/// </summary>
/// <returns><c>true</c>, if key was contained, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if key was contained, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
public bool ContainsKey(string key) public bool ContainsKey(string key) => dict.ContainsKey(key);
{
return dict.ContainsKey(key);
}
/// <summary> /// <summary>Checks if there is any item contained in the current instance corresponding with the specified value.</summary>
/// Checks if there is any item contained in the current instance corresponding with the specified value.
/// </summary>
/// <returns><c>true</c>, if value is contained, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if value is contained, <c>false</c> otherwise.</returns>
/// <param name="value">Key.</param> /// <param name="value">Key.</param>
public bool ContainsValue(NSObject value) public bool ContainsValue(NSObject value) => dict.ContainsValue(value);
{
return dict.ContainsValue(value);
}
/// <summary> /// <summary>Removes the item belonging to the specified key.</summary>
/// Removes the item belonging to the specified key.
/// </summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
public bool Remove(string key) public bool Remove(string key)
{ {
keys.Remove(key); keys.Remove(key);
return dict.Remove(key); return dict.Remove(key);
} }
/// <summary> /// <summary>Tries to get the item corresponding to the specified key</summary>
/// Tries to get the item corresponding to the specified key
/// </summary>
/// <returns><c>true</c>, if get value was successfully found and retrieved, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if get value was successfully found and retrieved, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
/// <param name="value">Where to store the value.</param> /// <param name="value">Where to store the value.</param>
public bool TryGetValue(string key, out NSObject value) public bool TryGetValue(string key, out NSObject value) => dict.TryGetValue(key, out value);
{
return dict.TryGetValue(key, out value);
}
/// <summary> /// <summary>Gets or sets the <see cref="Claunia.PropertyList.NSObject" /> at the specified index.</summary>
/// Gets or sets the <see cref="Claunia.PropertyList.NSObject" /> at the specified index.
/// </summary>
/// <param name="index">Index.</param> /// <param name="index">Index.</param>
public NSObject this[string index] public NSObject this[string index]
{ {
get => dict[index]; get => dict[index];
set set
{ {
if(!keys.ContainsKey(index)) keys.Add(index, new NSString(index)); if(!keys.ContainsKey(index))
keys.Add(index, new NSString(index));
dict[index] = value; dict[index] = value;
} }
} }
/// <summary> /// <summary>Gets an array with all the keys contained in the current instance.</summary>
/// Gets an array with all the keys contained in the current instance.
/// </summary>
/// <value>The keys.</value> /// <value>The keys.</value>
public ICollection<string> Keys => dict.Keys; public ICollection<string> Keys => dict.Keys;
/// <summary> /// <summary>Gets an array with all the objects contained in the current instance.</summary>
/// Gets an array with all the objects contained in the current instance.
/// </summary>
/// <value>The objects.</value> /// <value>The objects.</value>
public ICollection<NSObject> Values => dict.Values; public ICollection<NSObject> Values => dict.Values;
#endregion #endregion
#region ICollection implementation #region ICollection implementation
/// <summary> /// <summary>Adds the specified item.</summary>
/// Adds the specified item.
/// </summary>
/// <param name="item">Item.</param> /// <param name="item">Item.</param>
public void Add(KeyValuePair<string, NSObject> item) public void Add(KeyValuePair<string, NSObject> item)
{ {
@@ -589,24 +505,17 @@ namespace Claunia.PropertyList
dict.Add(item.Key, item.Value); dict.Add(item.Key, item.Value);
} }
/// <summary> /// <summary>Clears this instance.</summary>
/// Clears this instance.
/// </summary>
public void Clear() public void Clear()
{ {
keys.Clear(); keys.Clear();
dict.Clear(); dict.Clear();
} }
/// <summary> /// <summary>Checks if the current instance contains the specified item.</summary>
/// Checks if the current instance contains the specified item.
/// </summary>
/// <param name="item">Item.</param> /// <param name="item">Item.</param>
/// <returns><c>true</c> if it is found, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if it is found, <c>false</c> otherwise.</returns>
public bool Contains(KeyValuePair<string, NSObject> item) public bool Contains(KeyValuePair<string, NSObject> item) => dict.ContainsKey(item.Key);
{
return dict.ContainsKey(item.Key);
}
/// <summary> /// <summary>
/// Copies the <see cref="Dictionary{TKey, TValue}.ValueCollection" /> elements to an existing one-dimensional /// Copies the <see cref="Dictionary{TKey, TValue}.ValueCollection" /> elements to an existing one-dimensional
@@ -623,26 +532,21 @@ namespace Claunia.PropertyList
coll.CopyTo(array, arrayIndex); coll.CopyTo(array, arrayIndex);
} }
/// <summary> /// <summary>Removes the specified item.</summary>
/// Removes the specified item.
/// </summary>
/// <param name="item">Item to remove.</param> /// <param name="item">Item to remove.</param>
/// <returns><c>true</c> if successfully removed, <c>false</c> if not, or if item is not in current instance.</returns> /// <returns><c>true</c> if successfully removed, <c>false</c> if not, or if item is not in current instance.</returns>
public bool Remove(KeyValuePair<string, NSObject> item) public bool Remove(KeyValuePair<string, NSObject> item)
{ {
keys.Remove(item.Key); keys.Remove(item.Key);
return dict.Remove(item.Key); return dict.Remove(item.Key);
} }
/// <summary> /// <summary>Gets the count of items in the current instance.</summary>
/// Gets the count of items in the current instance.
/// </summary>
/// <value>How many items are contained in the current instance.</value> /// <value>How many items are contained in the current instance.</value>
public int Count => dict.Count; public int Count => dict.Count;
/// <summary> /// <summary>Gets a value indicating whether this instance is read only.</summary>
/// Gets a value indicating whether this instance is read only.
/// </summary>
/// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value>
public bool IsReadOnly => false; public bool IsReadOnly => false;
#endregion #endregion

View File

@@ -29,30 +29,24 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>A number whose value is either an integer, a real number or bool.</summary>
/// A number whose value is either an integer, a real number or bool.
/// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class NSNumber : NSObject, IComparable public class NSNumber : NSObject, IComparable
{ {
/// <summary> /// <summary>
/// Indicates that the number's value is an integer. /// Indicates that the number's value is an integer. The number is stored as a .NET <see cref="long" />. Its
/// The number is stored as a .NET <see cref="long" />. /// original value could have been char, short, int, long or even long long.
/// Its original value could have been char, short, int, long or even long long.
/// </summary> /// </summary>
public const int INTEGER = 0; public const int INTEGER = 0;
/// <summary> /// <summary>
/// Indicates that the number's value is a real number. /// Indicates that the number's value is a real number. The number is stored as a .NET <see cref="double" />. Its
/// The number is stored as a .NET <see cref="double" />. /// original value could have been float or double.
/// Its original value could have been float or double.
/// </summary> /// </summary>
public const int REAL = 1; public const int REAL = 1;
/// <summary> /// <summary>Indicates that the number's value is bool.</summary>
/// Indicates that the number's value is bool.
/// </summary>
public const int BOOLEAN = 2; public const int BOOLEAN = 2;
readonly bool boolValue; readonly bool boolValue;
readonly double doubleValue; readonly double doubleValue;
@@ -76,11 +70,13 @@ namespace Claunia.PropertyList
{ {
case INTEGER: case INTEGER:
doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes); doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes);
break; break;
case REAL: case REAL:
doubleValue = BinaryPropertyListParser.ParseDouble(bytes); doubleValue = BinaryPropertyListParser.ParseDouble(bytes);
longValue = (long)Math.Round(doubleValue); longValue = (long)Math.Round(doubleValue);
break; break;
default: throw new ArgumentException("Type argument is not valid.", nameof(type)); default: throw new ArgumentException("Type argument is not valid.", nameof(type));
@@ -96,35 +92,38 @@ namespace Claunia.PropertyList
case INTEGER: case INTEGER:
{ {
doubleValue = longValue = long.Parse(text, CultureInfo.InvariantCulture); doubleValue = longValue = long.Parse(text, CultureInfo.InvariantCulture);
break; break;
} }
case REAL: case REAL:
{ {
doubleValue = double.Parse(text, CultureInfo.InvariantCulture); doubleValue = double.Parse(text, CultureInfo.InvariantCulture);
longValue = (long)Math.Round(doubleValue); longValue = (long)Math.Round(doubleValue);
break; break;
} }
default: { throw new ArgumentException("Type argument is not valid."); } default:
{
throw new ArgumentException("Type argument is not valid.");
}
} }
this.type = type; this.type = type;
} }
/// <summary> /// <summary>Creates a number from its textual representation.</summary>
/// Creates a number from its textual representation.
/// </summary>
/// <param name="text">The textual representation of the number.</param> /// <param name="text">The textual representation of the number.</param>
/// <seealso cref="bool.Parse(string)" /> /// <seealso cref="bool.Parse(string)" />
/// <seealso cref="long.Parse(string)" /> /// <seealso cref="long.Parse(string)" />
/// <seealso cref="double.Parse(string, IFormatProvider)" /> /// <seealso cref="double.Parse(string, IFormatProvider)" />
public NSNumber(string text) 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.");
long l; if(text.StartsWith("0x") &&
double d; long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture,
out long l))
if(text.StartsWith("0x") && long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out l))
{ {
doubleValue = longValue = l; doubleValue = longValue = l;
type = INTEGER; type = INTEGER;
@@ -134,7 +133,7 @@ namespace Claunia.PropertyList
doubleValue = longValue = l; doubleValue = longValue = l;
type = INTEGER; type = INTEGER;
} }
else if(double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out d)) else if(double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out double d))
{ {
doubleValue = d; doubleValue = d;
longValue = (long)Math.Round(doubleValue); longValue = (long)Math.Round(doubleValue);
@@ -144,6 +143,7 @@ namespace Claunia.PropertyList
{ {
bool isTrue = string.Equals(text, "true", StringComparison.CurrentCultureIgnoreCase) || bool isTrue = string.Equals(text, "true", StringComparison.CurrentCultureIgnoreCase) ||
string.Equals(text, "yes", StringComparison.CurrentCultureIgnoreCase); string.Equals(text, "yes", StringComparison.CurrentCultureIgnoreCase);
bool isFalse = string.Equals(text, "false", StringComparison.CurrentCultureIgnoreCase) || bool isFalse = string.Equals(text, "false", StringComparison.CurrentCultureIgnoreCase) ||
string.Equals(text, "no", StringComparison.CurrentCultureIgnoreCase); string.Equals(text, "no", StringComparison.CurrentCultureIgnoreCase);
@@ -159,9 +159,7 @@ namespace Claunia.PropertyList
} }
} }
/// <summary> /// <summary>Creates an integer number.</summary>
/// Creates an integer number.
/// </summary>
/// <param name="i">The integer value.</param> /// <param name="i">The integer value.</param>
public NSNumber(int i) public NSNumber(int i)
{ {
@@ -169,9 +167,7 @@ namespace Claunia.PropertyList
type = INTEGER; type = INTEGER;
} }
/// <summary> /// <summary>Creates an integer number.</summary>
/// Creates an integer number.
/// </summary>
/// <param name="l">The long integer value.</param> /// <param name="l">The long integer value.</param>
public NSNumber(long l) public NSNumber(long l)
{ {
@@ -179,9 +175,7 @@ namespace Claunia.PropertyList
type = INTEGER; type = INTEGER;
} }
/// <summary> /// <summary>Creates a real number.</summary>
/// Creates a real number.
/// </summary>
/// <param name="d">The real value.</param> /// <param name="d">The real value.</param>
public NSNumber(double d) public NSNumber(double d)
{ {
@@ -189,9 +183,7 @@ namespace Claunia.PropertyList
type = REAL; type = REAL;
} }
/// <summary> /// <summary>Creates a bool number.</summary>
/// Creates a bool number.
/// </summary>
/// <param name="b">The bool value.</param> /// <param name="b">The bool value.</param>
public NSNumber(bool b) public NSNumber(bool b)
{ {
@@ -200,143 +192,104 @@ namespace Claunia.PropertyList
type = BOOLEAN; type = BOOLEAN;
} }
/// <summary> /// <summary>Compares the current <see cref="Claunia.PropertyList.NSNumber" /> to the specified object.</summary>
/// Compares the current <see cref="Claunia.PropertyList.NSNumber" /> to the specified object.
/// </summary>
/// <returns> /// <returns>
/// 0 if the numbers are equal, 1 if the current <see cref="Claunia.PropertyList.NSNumber" /> is greater /// 0 if the numbers are equal, 1 if the current <see cref="Claunia.PropertyList.NSNumber" /> is greater than the
/// than the argument and -1 if it is less, or the argument is not a number. /// argument and -1 if it is less, or the argument is not a number.
/// </returns> /// </returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSNumber" />.</param> /// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSNumber" />.</param>
public int CompareTo(object o) public int CompareTo(object o)
{ {
double x = ToDouble(); double x = ToDouble();
double y; double y;
if(o is NSNumber)
if(o is NSNumber num)
{ {
NSNumber num = (NSNumber)o;
y = num.ToDouble(); y = num.ToDouble();
return x < y ? -1 : (x == y ? 0 : 1);
} return x < y
? -1
if(IsNumber(o)) : x == y
{ ? 0
y = GetDoubleFromObject(o); : 1;
return x < y ? -1 : (x == y ? 0 : 1);
} }
if(!IsNumber(o))
return -1; return -1;
y = GetDoubleFromObject(o);
return x < y
? -1
: x == y
? 0
: 1;
} }
/// <summary> /// <summary>Gets the type of this number's value.</summary>
/// Gets the type of this number's value.
/// </summary>
/// <returns>The type flag.</returns> /// <returns>The type flag.</returns>
/// <seealso cref="BOOLEAN" /> /// <seealso cref="BOOLEAN" />
/// <seealso cref="INTEGER" /> /// <seealso cref="INTEGER" />
/// <seealso cref="REAL" /> /// <seealso cref="REAL" />
public int GetNSNumberType() public int GetNSNumberType() => type;
{
return type;
}
/// <summary> /// <summary>Checks whether the value of this NSNumber is a bool.</summary>
/// Checks whether the value of this NSNumber is a bool.
/// </summary>
/// <returns>Whether the number's value is a bool.</returns> /// <returns>Whether the number's value is a bool.</returns>
public bool isBoolean() public bool isBoolean() => type == BOOLEAN;
{
return type == BOOLEAN;
}
/// <summary> /// <summary>Checks whether the value of this NSNumber is an integer.</summary>
/// Checks whether the value of this NSNumber is an integer.
/// </summary>
/// <returns>Whether the number's value is an integer.</returns> /// <returns>Whether the number's value is an integer.</returns>
public bool isInteger() public bool isInteger() => type == INTEGER;
{
return type == INTEGER;
}
/// <summary> /// <summary>Checks whether the value of this NSNumber is a real number.</summary>
/// Checks whether the value of this NSNumber is a real number.
/// </summary>
/// <returns>Whether the number's value is a real number.</returns> /// <returns>Whether the number's value is a real number.</returns>
public bool isReal() public bool isReal() => type == REAL;
{
return type == REAL;
}
/// <summary> /// <summary>The number's bool value.</summary>
/// The number's bool value.
/// </summary>
/// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns>
public bool ToBool() public bool ToBool()
{ {
if(type == BOOLEAN) return boolValue; if(type == BOOLEAN)
return boolValue;
return longValue != 0; return longValue != 0;
} }
/// <summary> /// <summary>The number's long value.</summary>
/// The number's long value.
/// </summary>
/// <returns>The value of the number as long</returns> /// <returns>The value of the number as long</returns>
public long ToLong() public long ToLong() => longValue;
{
return longValue;
}
/// <summary> /// <summary>
/// The number's int value. /// The number's int value.
/// <i> /// <i>
/// Note: Even though the number's type might be INTEGER it can be larger than a Java int. /// Note: Even though the number's type might be INTEGER it can be larger than a Java int. Use intValue() only if
/// Use intValue() only if you are certain that it contains a number from the int range. /// you are certain that it contains a number from the int range. Otherwise the value might be inaccurate.
/// Otherwise the value might be innaccurate.
/// </i> /// </i>
/// </summary> /// </summary>
/// <returns>The value of the number as int.</returns> /// <returns>The value of the number as int.</returns>
public int ToInt() public int ToInt() => (int)longValue;
{
return (int)longValue;
}
/// <summary> /// <summary>The number's double value.</summary>
/// The number's double value.
/// </summary>
/// <returns>The value of the number as double.</returns> /// <returns>The value of the number as double.</returns>
public double ToDouble() public double ToDouble() => doubleValue;
{
return doubleValue;
}
/// <summary> /// <summary>The number's float value. WARNING: Possible loss of precision if the value is outside the float range.</summary>
/// The number's float value.
/// WARNING: Possible loss of precision if the value is outside the float range.
/// </summary>
/// <returns>The value of the number as float.</returns> /// <returns>The value of the number as float.</returns>
public float floatValue() public float floatValue() => (float)doubleValue;
{
return (float)doubleValue;
}
/// <summary> /// <summary>Checks whether the other object is a NSNumber of the same value.</summary>
/// Checks whether the other object is a NSNumber of the same value.
/// </summary>
/// <param name="obj">The object to compare to.</param> /// <param name="obj">The object to compare to.</param>
/// <returns>Whether the objects are equal in terms of numeric value and type.</returns> /// <returns>Whether the objects are equal in terms of numeric value and type.</returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if(!(obj is NSNumber)) return false; if(obj is not NSNumber number)
return false;
NSNumber n = (NSNumber)obj; return type == number.type && longValue == number.longValue && doubleValue == number.doubleValue &&
return type == n.type && longValue == n.longValue && doubleValue == n.doubleValue && boolValue == number.boolValue;
boolValue == n.boolValue;
} }
/// <summary> /// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSNumber" /> object.</summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSNumber" /> object.
/// </summary>
/// <returns> /// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table. /// hash table.
@@ -344,31 +297,33 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = type; int hash = type;
hash = 37 * hash + (int)(longValue ^ ((uint)longValue >> 32)); hash = (37 * hash) + (int)(longValue ^ ((uint)longValue >> 32));
hash = 37 * hash + (int)(BitConverter.DoubleToInt64Bits(doubleValue) ^
hash = (37 * hash) + (int)(BitConverter.DoubleToInt64Bits(doubleValue) ^
(uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32)); (uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32));
hash = 37 * hash + (ToBool() ? 1 : 0);
hash = (37 * hash) + (ToBool() ? 1 : 0);
return hash; return hash;
} }
/// <summary> /// <summary>
/// Returns a <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />. /// Returns a <see cref="System.String" /> that represents the current
/// <see cref="Claunia.PropertyList.NSNumber" />.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />.</returns> /// <returns>A <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />.</returns>
public override string ToString() public override string ToString() => type switch
{ {
switch(type) INTEGER => ToLong().ToString(),
{ REAL => ToDouble().ToString("R", CultureInfo.InvariantCulture),
case INTEGER: { return ToLong().ToString(); } BOOLEAN => ToBool().ToString(),
case REAL: { return ToDouble().ToString("R", CultureInfo.InvariantCulture); } _ => base.ToString()
case BOOLEAN: { return ToBool().ToString(); } };
default: { return base.ToString(); }
}
}
internal override void ToXml(StringBuilder xml, int level) internal override void ToXml(StringBuilder xml, int level)
{ {
Indent(xml, level); Indent(xml, level);
switch(type) switch(type)
{ {
case INTEGER: case INTEGER:
@@ -376,22 +331,26 @@ namespace Claunia.PropertyList
xml.Append("<integer>"); xml.Append("<integer>");
xml.Append(ToLong()); xml.Append(ToLong());
xml.Append("</integer>"); xml.Append("</integer>");
break; break;
} }
case REAL: case REAL:
{ {
xml.Append("<real>"); xml.Append("<real>");
if(doubleValue == 0) xml.Append("0.0"); if(doubleValue == 0)
else xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture)); xml.Append("0.0");
else
xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture));
xml.Append("</real>"); xml.Append("</real>");
break; break;
} }
case BOOLEAN: case BOOLEAN:
{ {
if(ToBool()) xml.Append("<true/>"); xml.Append(ToBool() ? "<true/>" : "<false/>");
else xml.Append("<false/>");
break; break;
} }
} }
@@ -435,11 +394,13 @@ namespace Claunia.PropertyList
{ {
outPlist.Write(0x23); outPlist.Write(0x23);
outPlist.WriteDouble(ToDouble()); outPlist.WriteDouble(ToDouble());
break; break;
} }
case BOOLEAN: case BOOLEAN:
{ {
outPlist.Write(ToBool() ? 0x09 : 0x08); outPlist.Write(ToBool() ? 0x09 : 0x08);
break; break;
} }
} }
@@ -448,13 +409,17 @@ namespace Claunia.PropertyList
internal override void ToASCII(StringBuilder ascii, int level) internal override void ToASCII(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
if(type == BOOLEAN) ascii.Append(boolValue ? "YES" : "NO");
else ascii.Append(ToString()); if(type == BOOLEAN)
ascii.Append(boolValue ? "YES" : "NO");
else
ascii.Append(ToString());
} }
internal override void ToASCIIGnuStep(StringBuilder ascii, int level) internal override void ToASCIIGnuStep(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
switch(type) switch(type)
{ {
case INTEGER: case INTEGER:
@@ -462,6 +427,7 @@ namespace Claunia.PropertyList
ascii.Append("<*I"); ascii.Append("<*I");
ascii.Append(ToString()); ascii.Append(ToString());
ascii.Append(">"); ascii.Append(">");
break; break;
} }
case REAL: case REAL:
@@ -469,45 +435,39 @@ namespace Claunia.PropertyList
ascii.Append("<*R"); ascii.Append("<*R");
ascii.Append(ToString()); ascii.Append(ToString());
ascii.Append(">"); ascii.Append(">");
break; break;
} }
case BOOLEAN: case BOOLEAN:
{ {
if(boolValue) ascii.Append("<*BY>"); ascii.Append(boolValue ? "<*BY>" : "<*BN>");
else ascii.Append("<*BN>");
break; break;
} }
} }
} }
/// <summary> /// <summary>Determines if an object is a number. Substitutes .NET's Number class comparison</summary>
/// Determines if an object is a number.
/// Substitutes .NET's Number class comparison
/// </summary>
/// <returns><c>true</c> if it is a number.</returns> /// <returns><c>true</c> if it is a number.</returns>
/// <param name="o">Object.</param> /// <param name="o">Object.</param>
static bool IsNumber(object o) static bool IsNumber(object o) =>
{ o is sbyte or byte or short or ushort or int or uint or long or ulong or float or double or decimal;
return o is sbyte || o is byte || o is short || o is ushort || o is int || o is uint || o is long ||
o is ulong || o is float || o is double || o is decimal;
}
static double GetDoubleFromObject(object o) static double GetDoubleFromObject(object o) => o switch
{ {
if(o is sbyte) return (sbyte)o; sbyte @sbyte => @sbyte,
if(o is byte) return (byte)o; byte b => b,
if(o is short) return (short)o; short s => s,
if(o is ushort) return (ushort)o; ushort @ushort => @ushort,
if(o is int) return (int)o; int i => i,
if(o is uint) return (uint)o; uint u => u,
if(o is long) return (long)o; long l => l,
if(o is ulong) return (ulong)o; ulong @ulong => @ulong,
if(o is float) return (float)o; float f => f,
if(o is double) return (double)o; double d => d,
if(o is decimal) return (double)(decimal)o; decimal @decimal => (double)@decimal,
_ => 0
return 0; };
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
@@ -523,127 +483,63 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(!(obj is NSNumber)) return false; if(obj is not NSNumber number)
return false;
if(((NSNumber)obj).GetNSNumberType() != type) return false; if(number.GetNSNumberType() != type)
return false;
switch(type) return type switch
{ {
case INTEGER: return longValue == ((NSNumber)obj).ToLong(); INTEGER => longValue == number.ToLong(),
case REAL: return doubleValue == ((NSNumber)obj).ToDouble(); REAL => doubleValue == number.ToDouble(),
case BOOLEAN: return boolValue == ((NSNumber)obj).ToBool(); BOOLEAN => boolValue == number.ToBool(),
default: return false; _ => false
} };
} }
public static explicit operator ulong(NSNumber value) public static explicit operator ulong(NSNumber value) => (ulong)value.longValue;
{
return (ulong)value.longValue;
}
public static explicit operator long(NSNumber value) public static explicit operator long(NSNumber value) => value.longValue;
{
return value.longValue;
}
public static explicit operator uint(NSNumber value) public static explicit operator uint(NSNumber value) => (uint)value.longValue;
{
return (uint)value.longValue;
}
public static explicit operator int(NSNumber value) public static explicit operator int(NSNumber value) => (int)value.longValue;
{
return (int)value.longValue;
}
public static explicit operator ushort(NSNumber value) public static explicit operator ushort(NSNumber value) => (ushort)value.longValue;
{
return (ushort)value.longValue;
}
public static explicit operator short(NSNumber value) public static explicit operator short(NSNumber value) => (short)value.longValue;
{
return (short)value.longValue;
}
public static explicit operator byte(NSNumber value) public static explicit operator byte(NSNumber value) => (byte)value.longValue;
{
return (byte)value.longValue;
}
public static explicit operator sbyte(NSNumber value) public static explicit operator sbyte(NSNumber value) => (sbyte)value.longValue;
{
return (sbyte)value.longValue;
}
public static explicit operator double(NSNumber value) public static explicit operator double(NSNumber value) => value.doubleValue;
{
return value.doubleValue;
}
public static explicit operator float(NSNumber value) public static explicit operator float(NSNumber value) => (float)value.doubleValue;
{
return (float)value.doubleValue;
}
public static explicit operator bool(NSNumber value) public static explicit operator bool(NSNumber value) => value.boolValue;
{
return value.boolValue;
}
public static explicit operator NSNumber(ulong value) public static explicit operator NSNumber(ulong value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(long value) public static explicit operator NSNumber(long value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(uint value) public static explicit operator NSNumber(uint value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(int value) public static explicit operator NSNumber(int value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(ushort value) public static explicit operator NSNumber(ushort value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(short value) public static explicit operator NSNumber(short value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(byte value) public static explicit operator NSNumber(byte value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(sbyte value) public static explicit operator NSNumber(sbyte value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(double value) public static explicit operator NSNumber(double value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(float value) public static explicit operator NSNumber(float value) => new(value);
{
return new NSNumber(value);
}
public static explicit operator NSNumber(bool value) public static explicit operator NSNumber(bool value) => new(value);
{
return new NSNumber(value);
}
} }
} }

View File

@@ -25,73 +25,51 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>Abstract interface for any object contained in a property list.</para>
/// Abstract interface for any object contained in a property list. /// <para>The names and functions of the various objects orient themselves towards Apple's Cocoa API.</para>
/// </para>
/// <para>
/// The names and functions of the various objects orient themselves
/// towards Apple's Cocoa API.
/// </para>
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public abstract class NSObject public abstract class NSObject
{ {
/// <summary> /// <summary>
/// The newline character used for generating the XML output. /// The newline character used for generating the XML output. To maintain compatibility with the Apple format,
/// To maintain compatibility with the Apple format, only a newline character /// only a newline character is used (as opposed to cr+lf which is normally used on Windows).
/// is used (as opposed to cr+lf which is normally used on Windows).
/// </summary> /// </summary>
internal static readonly string NEWLINE = "\n"; internal static readonly string NEWLINE = "\n";
/// <summary> /// <summary>The indentation character used for generating the XML output. This is the tabulator character.</summary>
/// The identation character used for generating the XML output. This is the
/// tabulator character.
/// </summary>
static readonly string INDENT = "\t"; static readonly string INDENT = "\t";
/// <summary> /// <summary>
/// The maximum length of the text lines to be used when generating /// The maximum length of the text lines to be used when generating ASCII property lists. But this number is only
/// ASCII property lists. But this number is only a guideline it is not /// a guideline it is not guaranteed that it will not be overstepped.
/// guaranteed that it will not be overstepped.
/// </summary> /// </summary>
internal static readonly int ASCII_LINE_LENGTH = 80; internal static readonly int ASCII_LINE_LENGTH = 80;
/// <summary> /// <summary>Generates the XML representation of the object (without XML headers or enclosing plist-tags).</summary>
/// Generates the XML representation of the object (without XML headers or enclosing plist-tags).
/// </summary>
/// <param name="xml">The StringBuilder onto which the XML representation is appended.</param> /// <param name="xml">The StringBuilder onto which the XML representation is appended.</param>
/// <param name="level">The indentation level of the object.</param> /// <param name="level">The indentation level of the object.</param>
internal abstract void ToXml(StringBuilder xml, int level); internal abstract void ToXml(StringBuilder xml, int level);
/// <summary> /// <summary>Assigns IDs to all the objects in this NSObject subtree.</summary>
/// Assigns IDs to all the objects in this NSObject subtree.
/// </summary>
/// <param name="outPlist">The writer object that handles the binary serialization.</param> /// <param name="outPlist">The writer object that handles the binary serialization.</param>
internal virtual void AssignIDs(BinaryPropertyListWriter outPlist) internal virtual void AssignIDs(BinaryPropertyListWriter outPlist) => outPlist.AssignID(this);
{
outPlist.AssignID(this);
}
/// <summary> /// <summary>Generates the binary representation of the object.</summary>
/// Generates the binary representation of the object.
/// </summary>
/// <param name="outPlist">The output stream to serialize the object to.</param> /// <param name="outPlist">The output stream to serialize the object to.</param>
internal abstract void ToBinary(BinaryPropertyListWriter outPlist); internal abstract void ToBinary(BinaryPropertyListWriter outPlist);
/// <summary> /// <summary>Generates a valid XML property list including headers using this object as root.</summary>
/// Generates a valid XML property list including headers using this object as root.
/// </summary>
/// <returns>The XML representation of the property list including XML header and doctype information.</returns> /// <returns>The XML representation of the property list including XML header and doctype information.</returns>
public string ToXmlPropertyList() public string ToXmlPropertyList()
{ {
StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); var xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
xml.Append(NEWLINE); xml.Append(NEWLINE);
xml.Append("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); xml.Append("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
xml.Append(NEWLINE); xml.Append(NEWLINE);
@@ -101,13 +79,13 @@ namespace Claunia.PropertyList
xml.Append(NEWLINE); xml.Append(NEWLINE);
xml.Append("</plist>"); xml.Append("</plist>");
xml.Append(NEWLINE); xml.Append(NEWLINE);
return xml.ToString(); return xml.ToString();
} }
/// <summary> /// <summary>
/// Generates the ASCII representation of this object. /// Generates the ASCII representation of this object. The generated ASCII representation does not end with a
/// The generated ASCII representation does not end with a newline. /// newline. Complies with
/// Complies with
/// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
/// </summary> /// </summary>
/// <param name="ascii">The StringBuilder onto which the ASCII representation is appended.</param> /// <param name="ascii">The StringBuilder onto which the ASCII representation is appended.</param>
@@ -115,124 +93,99 @@ namespace Claunia.PropertyList
internal abstract void ToASCII(StringBuilder ascii, int level); internal abstract void ToASCII(StringBuilder ascii, int level);
/// <summary> /// <summary>
/// Generates the ASCII representation of this object in the GnuStep format. /// Generates the ASCII representation of this object in the GnuStep format. The generated ASCII representation
/// The generated ASCII representation does not end with a newline. /// does not end with a newline.
/// </summary> /// </summary>
/// <param name="ascii">The StringBuilder onto which the ASCII representation is appended.</param> /// <param name="ascii">The StringBuilder onto which the ASCII representation is appended.</param>
/// <param name="level">The indentation level of the object.</param> /// <param name="level">The indentation level of the object.</param>
internal abstract void ToASCIIGnuStep(StringBuilder ascii, int level); internal abstract void ToASCIIGnuStep(StringBuilder ascii, int level);
/// <summary> /// <summary>
/// Helper method that adds correct identation to the xml output. /// Helper method that adds correct indentation to the xml output. Calling this method will add <c>level</c>
/// Calling this method will add <c>level</c> number of tab characters /// number of tab characters to the <c>xml</c> string.
/// to the <c>xml</c> string.
/// </summary> /// </summary>
/// <param name="xml">The string builder for the XML document.</param> /// <param name="xml">The string builder for the XML document.</param>
/// <param name="level">The level of identation.</param> /// <param name="level">The level of indentation.</param>
internal static void Indent(StringBuilder xml, int level) internal static void Indent(StringBuilder xml, int level)
{ {
for(int i = 0; i < level; i++) xml.Append(INDENT); for(int i = 0; i < level; i++)
xml.Append(INDENT);
} }
/// <summary> /// <summary>Wraps the given value inside a NSObject.</summary>
/// Wraps the given value inside a NSObject.
/// </summary>
/// <param name="value">The value to represent as a NSObject.</param> /// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns> /// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(long value) public static NSNumber Wrap(long value) => new(value);
{
return new NSNumber(value);
}
/// <summary> /// <summary>Wraps the given value inside a NSObject.</summary>
/// Wraps the given value inside a NSObject.
/// </summary>
/// <param name="value">The value to represent as a NSObject.</param> /// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns> /// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(double value) public static NSNumber Wrap(double value) => new(value);
{
return new NSNumber(value);
}
/// <summary> /// <summary>Wraps the given value inside a NSObject.</summary>
/// Wraps the given value inside a NSObject.
/// </summary>
/// <param name="value">The value to represent as a NSObject.</param> /// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns> /// <returns>A NSObject representing the given value.</returns>
public static NSNumber Wrap(bool value) public static NSNumber Wrap(bool value) => new(value);
{
return new NSNumber(value);
}
/// <summary> /// <summary>Wraps the given value inside a NSObject.</summary>
/// Wraps the given value inside a NSObject.
/// </summary>
/// <param name="value">The value to represent as a NSObject.</param> /// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns> /// <returns>A NSObject representing the given value.</returns>
public static NSData Wrap(byte[] value) public static NSData Wrap(byte[] value) => new(value);
{
return new NSData(value);
}
/// <summary> /// <summary>Creates a NSArray with the contents of the given array.</summary>
/// Creates a NSArray with the contents of the given array.
/// </summary>
/// <param name="value">The value to represent as a NSObject.</param> /// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns> /// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the objects contained in the array cannot be represented by a NSObject.</exception> /// <exception cref="SystemException">When one of the objects contained in the array cannot be represented by a NSObject.</exception>
public static NSArray Wrap(object[] value) public static NSArray Wrap(object[] value)
{ {
NSArray arr = new NSArray(value.Length); 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; return arr;
} }
/// <summary> /// <summary>Creates a NSDictionary with the contents of the given map.</summary>
/// Creates a NSDictionary with the contents of the given map.
/// </summary>
/// <param name="value">The value to represent as a NSObject.</param> /// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns> /// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the values contained in the map cannot be represented by a NSObject.</exception> /// <exception cref="SystemException">When one of the values contained in the map cannot be represented by a NSObject.</exception>
public static NSDictionary Wrap(Dictionary<string, object> value) public static NSDictionary Wrap(Dictionary<string, object> value)
{ {
NSDictionary dict = new NSDictionary(); 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; return dict;
} }
/// <summary> /// <summary>Creates a NSSet with the contents of this set.</summary>
/// Creates a NSSet with the contents of this set.
/// </summary>
/// <param name="value">The value to represent as a NSObject.</param> /// <param name="value">The value to represent as a NSObject.</param>
/// <returns>A NSObject representing the given value.</returns> /// <returns>A NSObject representing the given value.</returns>
/// <exception cref="SystemException">When one of the values contained in the map cannot be represented by a NSObject.</exception> /// <exception cref="SystemException">When one of the values contained in the map cannot be represented by a NSObject.</exception>
public static NSSet Wrap(List<object> value) public static NSSet Wrap(List<object> value)
{ {
NSSet set = new NSSet(); var set = new NSSet();
foreach(object o in value) set.AddObject(Wrap(o));
foreach(object o in value)
set.AddObject(Wrap(o));
return set; return set;
} }
/// <summary> /// <summary>
/// <para> /// <para>Creates a NSObject representing the given .NET Object.</para>
/// Creates a NSObject representing the given .NET Object.
/// </para>
/// <para> /// <para>
/// Numerics of type <see cref="bool" />, <see cref="int" />, <see cref="long" />, <see cref="short" />, /// Numerics of type <see cref="bool" />, <see cref="int" />, <see cref="long" />, <see cref="short" />,
/// <see cref="byte" />, <see cref="float" /> or <see cref="double" /> are wrapped as NSNumber objects. /// <see cref="byte" />, <see cref="float" /> or <see cref="double" /> are wrapped as NSNumber objects.
/// </para> /// </para>
/// <para>Strings are wrapped as <see cref="NSString" /> objects and byte arrays as <see cref="NSData" /> objects.</para>
/// <para>DateTime objects are wrapped as <see cref="NSDate" /> objects.</para>
/// <para>Serializable classes are serialized and their data is stored in <see cref="NSData" /> objects.</para>
/// <para> /// <para>
/// Strings are wrapped as <see cref="NSString" /> objects and byte arrays as <see cref="NSData" /> objects. /// Arrays and Collection objects are converted to <see cref="NSArray" /> where each array member is wrapped into
/// </para> /// a <see cref="NSObject" />.
/// <para>
/// DateTime objects are wrapped as <see cref="NSDate" /> objects.
/// </para>
/// <para>
/// Serializable classes are serialized and their data is stored in <see cref="NSData" /> objects.
/// </para>
/// <para>
/// Arrays and Collection objects are converted to <see cref="NSArray" /> where each array member is wrapped into a
/// <see cref="NSObject" />.
/// </para> /// </para>
/// <para> /// <para>
/// Dictionaries are converted to <see cref="NSDictionary" />. Each key is converted to a string and each value /// Dictionaries are converted to <see cref="NSDictionary" />. Each key is converted to a string and each value
@@ -243,79 +196,111 @@ namespace Claunia.PropertyList
/// <returns>A NSObject equivalent to the given object.</returns> /// <returns>A NSObject equivalent to the given object.</returns>
public static NSObject Wrap(object o) public static NSObject Wrap(object o)
{ {
if(o == null) 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) return (NSObject)o; if(o is NSObject nsObject)
return nsObject;
Type c = o.GetType(); Type c = o.GetType();
if(typeof(bool).Equals(c)) return Wrap((bool)o);
if(typeof(byte).Equals(c)) return Wrap((byte)o); if(typeof(bool).Equals(c))
return Wrap((bool)o);
if(typeof(short).Equals(c)) return Wrap((short)o); if(typeof(byte).Equals(c))
return Wrap((byte)o);
if(typeof(int).Equals(c)) return Wrap((int)o); if(typeof(short).Equals(c))
return Wrap((short)o);
if(typeof(long).IsAssignableFrom(c)) return Wrap((long)o); if(typeof(int).Equals(c))
return Wrap((int)o);
if(typeof(float).Equals(c)) return Wrap((float)o); if(typeof(long).IsAssignableFrom(c))
return Wrap((long)o);
if(typeof(double).IsAssignableFrom(c)) return Wrap((double)o); if(typeof(float).Equals(c))
return Wrap((float)o);
if(typeof(string).Equals(c)) return new NSString((string)o); if(typeof(double).IsAssignableFrom(c))
return Wrap((double)o);
if(typeof(DateTime).Equals(c)) return new NSDate((DateTime)o); if(typeof(string).Equals(c))
return new NSString((string)o);
if(typeof(DateTime).Equals(c))
return new NSDate((DateTime)o);
if(c.IsArray) if(c.IsArray)
{ {
Type cc = c.GetElementType(); 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))) if(cc.Equals(typeof(bool)))
{ {
bool[] array = (bool[])o; bool[] array = (bool[])o;
NSArray nsa = new NSArray(array.Length); 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; return nsa;
} }
if(cc.Equals(typeof(float))) if(cc.Equals(typeof(float)))
{ {
float[] array = (float[])o; float[] array = (float[])o;
NSArray nsa = new NSArray(array.Length); 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; return nsa;
} }
if(cc.Equals(typeof(double))) if(cc.Equals(typeof(double)))
{ {
double[] array = (double[])o; double[] array = (double[])o;
NSArray nsa = new NSArray(array.Length); 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; return nsa;
} }
if(cc.Equals(typeof(short))) if(cc.Equals(typeof(short)))
{ {
short[] array = (short[])o; short[] array = (short[])o;
NSArray nsa = new NSArray(array.Length); 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; return nsa;
} }
if(cc.Equals(typeof(int))) if(cc.Equals(typeof(int)))
{ {
int[] array = (int[])o; int[] array = (int[])o;
NSArray nsa = new NSArray(array.Length); 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; return nsa;
} }
if(cc.Equals(typeof(long))) if(cc.Equals(typeof(long)))
{ {
long[] array = (long[])o; long[] array = (long[])o;
NSArray nsa = new NSArray(array.Length); 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; return nsa;
} }
@@ -325,19 +310,22 @@ namespace Claunia.PropertyList
if(typeof(Dictionary<string, object>).IsAssignableFrom(c)) if(typeof(Dictionary<string, object>).IsAssignableFrom(c))
{ {
Dictionary<string, object> netDict = (Dictionary<string, object>)o; Dictionary<string, object> netDict = (Dictionary<string, object>)o;
NSDictionary dict = new NSDictionary(); 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; 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());
throw new PropertyListException(string.Format("Cannot wrap an object of type {0}.", o.GetType().Name)); throw new PropertyListException($"Cannot wrap an object of type {o.GetType().Name}.");
} }
/// <summary> /// <summary>
/// Converts this NSObject into an equivalent object /// Converts this NSObject into an equivalent object of the .NET Runtime Environment.
/// of the .NET Runtime Environment.
/// <para><see cref="NSArray" /> objects are converted to arrays.</para> /// <para><see cref="NSArray" /> objects are converted to arrays.</para>
/// <para> /// <para>
/// <see cref="NSDictionary" /> objects are converted to objects extending the /// <see cref="NSDictionary" /> objects are converted to objects extending the
@@ -356,39 +344,50 @@ namespace Claunia.PropertyList
/// <returns>A native .NET object representing this NSObject's value.</returns> /// <returns>A native .NET object representing this NSObject's value.</returns>
public object ToObject() public object ToObject()
{ {
if(this is NSArray) switch(this)
{ {
NSArray nsArray = (NSArray)this; case NSArray:
{
var nsArray = (NSArray)this;
object[] array = new object[nsArray.Count]; 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; return array;
} }
case NSDictionary:
if(this is NSDictionary)
{ {
Dictionary<string, NSObject> dictA = ((NSDictionary)this).GetDictionary(); Dictionary<string, NSObject> dictA = ((NSDictionary)this).GetDictionary();
Dictionary<string, object> dictB = new Dictionary<string, object>(dictA.Count); 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; return dictB;
} }
case NSSet:
if(this is NSSet)
{ {
List<NSObject> setA = ((NSSet)this).GetSet(); List<NSObject> setA = ((NSSet)this).GetSet();
List<object> setB = new List<object>(); List<object> setB = new();
foreach(NSObject o in setA) setB.Add(o.ToObject());
foreach(NSObject o in setA)
setB.Add(o.ToObject());
return setB; return setB;
} }
case NSNumber:
if(this is NSNumber)
{ {
NSNumber num = (NSNumber)this; var num = (NSNumber)this;
switch(num.GetNSNumberType()) switch(num.GetNSNumberType())
{ {
case NSNumber.INTEGER: case NSNumber.INTEGER:
{ {
long longVal = num.ToLong(); long longVal = num.ToLong();
if(longVal > int.MaxValue || longVal < int.MinValue) return longVal;
if(longVal is > int.MaxValue or < int.MinValue)
return longVal;
return num.ToInt(); return num.ToInt();
} }
@@ -396,23 +395,22 @@ namespace Claunia.PropertyList
case NSNumber.BOOLEAN: return num.ToBool(); case NSNumber.BOOLEAN: return num.ToBool();
default: return num.ToDouble(); 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;
} }
if(this is NSString) return ((NSString)this).Content;
if(this is NSData) return ((NSData)this).Bytes;
if(this is NSDate) return ((NSDate)this).Date;
if(this is UID) return ((UID)this).Bytes;
return this;
} }
internal static bool ArrayEquals(byte[] arrayA, byte[] arrayB) internal static bool ArrayEquals(byte[] arrayA, byte[] arrayB)
{ {
if(arrayA.Length == arrayB.Length) if(arrayA.Length != arrayB.Length)
{ return false;
for(int i = 0; i < arrayA.Length; i++) for(int i = 0; i < arrayA.Length; i++)
if(arrayA[i] != arrayB[i]) if(arrayA[i] != arrayB[i])
return false; return false;
@@ -420,13 +418,11 @@ namespace Claunia.PropertyList
return true; return true;
} }
return false;
}
internal static bool ArrayEquals(IList<NSObject> arrayA, IList<NSObject> arrayB) internal static bool ArrayEquals(IList<NSObject> arrayA, IList<NSObject> arrayB)
{ {
if(arrayA.Count == arrayB.Count) if(arrayA.Count != arrayB.Count)
{ return false;
for(int i = 0; i < arrayA.Count; i++) for(int i = 0; i < arrayA.Count; i++)
if(arrayA[i] != arrayB[i]) if(arrayA[i] != arrayB[i])
return false; return false;
@@ -434,12 +430,7 @@ namespace Claunia.PropertyList
return true; return true;
} }
return false; /// <summary>Determines if the specific NSObject is the same as the NSObject overriding this method.</summary>
}
/// <summary>
/// Determines if the specific NSObject is the same as the NSObject overriding this method.
/// </summary>
/// <param name="obj"> /// <param name="obj">
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSObject" />. /// <see cref="Claunia.PropertyList.NSObject" />.

View File

@@ -31,33 +31,20 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>A set is an interface to an unordered collection of objects.</para>
/// A set is an interface to an unordered collection of objects. /// <para>This implementation uses a <see cref="List{T}" />as the underlying data structure.</para>
/// </para>
/// <para>
/// This implementation uses a <see cref="List{T}" />as the underlying
/// data structure.
/// </para>
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class NSSet : NSObject, IEnumerable public class NSSet : NSObject, IEnumerable
{ {
readonly bool ordered;
readonly List<NSObject> set; readonly List<NSObject> set;
bool ordered; /// <summary>Creates an empty unordered set.</summary>
public NSSet() => set = new List<NSObject>();
/// <summary> /// <summary>Creates an empty set.</summary>
/// Creates an empty unordered set.
/// </summary>
public NSSet()
{
set = new List<NSObject>();
}
/// <summary>
/// Creates an empty set.
/// </summary>
/// <param name="ordered">Should the set be ordered on operations?</param> /// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered) public NSSet(bool ordered)
{ {
@@ -65,108 +52,94 @@ namespace Claunia.PropertyList
set = new List<NSObject>(); set = new List<NSObject>();
} }
/// <summary> /// <summary>Creates a set and fill it with the given objects.</summary>
/// Creates a set and fill it with the given objects.
/// </summary>
/// <param name="objects">The objects to populate the set.</param> /// <param name="objects">The objects to populate the set.</param>
public NSSet(params NSObject[] objects) public NSSet(params NSObject[] objects) => set = new List<NSObject>(objects);
{
set = new List<NSObject>(objects);
}
/// <summary> /// <summary>Creates a set and fill it with the given objects.</summary>
/// Creates a set and fill it with the given objects.
/// </summary>
/// <param name="objects">The objects to populate the set.</param> /// <param name="objects">The objects to populate the set.</param>
/// <param name="ordered">Should the set be ordered on operations?</param> /// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered, params NSObject[] objects) public NSSet(bool ordered, params NSObject[] objects)
{ {
this.ordered = ordered; this.ordered = ordered;
set = new List<NSObject>(objects); set = new List<NSObject>(objects);
if(ordered) set.Sort();
if(ordered)
set.Sort();
} }
/// <summary> /// <summary>Gets the number of elements in the set.</summary>
/// Gets the number of elements in the set.
/// </summary>
/// <value>The number of elements in the set.</value> /// <value>The number of elements in the set.</value>
public int Count public int Count
{ {
get get
{ {
lock(set) return set.Count; lock(set)
return set.Count;
} }
} }
/// <summary> /// <summary>
/// Returns an enumerator object that lets you iterate over all elements of the set. /// Returns an enumerator object that lets you iterate over all elements of the set. This is the equivalent to
/// This is the equivalent to <c>objectEnumerator</c> in the Cocoa implementation /// <c>objectEnumerator</c> in the Cocoa implementation of NSSet.
/// of NSSet.
/// </summary> /// </summary>
/// <returns>The iterator for the set.</returns> /// <returns>The iterator for the set.</returns>
public IEnumerator GetEnumerator() public IEnumerator GetEnumerator()
{ {
lock(set) return set.GetEnumerator(); lock(set)
return set.GetEnumerator();
} }
/// <summary> /// <summary>Adds an object to the set.</summary>
/// Adds an object to the set.
/// </summary>
/// <param name="obj">The object to add.</param> /// <param name="obj">The object to add.</param>
public void AddObject(NSObject obj) public void AddObject(NSObject obj)
{ {
lock(set) lock(set)
{ {
set.Add(obj); set.Add(obj);
if(ordered) set.Sort();
if(ordered)
set.Sort();
} }
} }
/// <summary> /// <summary>Removes an object from the set.</summary>
/// Removes an object from the set.
/// </summary>
/// <param name="obj">The object to remove.</param> /// <param name="obj">The object to remove.</param>
public void RemoveObject(NSObject obj) public void RemoveObject(NSObject obj)
{ {
lock(set) lock(set)
{ {
set.Remove(obj); set.Remove(obj);
if(ordered) set.Sort();
if(ordered)
set.Sort();
} }
} }
/// <summary> /// <summary>Returns all objects contained in the set.</summary>
/// Returns all objects contained in the set.
/// </summary>
/// <returns>An array of all objects in the set.</returns> /// <returns>An array of all objects in the set.</returns>
public NSObject[] AllObjects() public NSObject[] AllObjects()
{ {
lock(set) return set.ToArray(); lock(set)
return set.ToArray();
} }
/// <summary> /// <summary>Returns one of the objects in the set, or <c>null</c> if the set contains no objects.</summary>
/// Returns one of the objects in the set, or <c>null</c>
/// if the set contains no objects.
/// </summary>
/// <returns>The first object in the set, or <c>null</c> if the set is empty.</returns> /// <returns>The first object in the set, or <c>null</c> if the set is empty.</returns>
public NSObject AnyObject() public NSObject AnyObject()
{ {
lock(set) return set.Count == 0 ? null : set[0]; lock(set)
return set.Count == 0 ? null : set[0];
} }
/// <summary> /// <summary>Finds out whether a given object is contained in the set.</summary>
/// Finds out whether a given object is contained in the set.
/// </summary>
/// <returns><c>true</c>, when the object was found, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, when the object was found, <c>false</c> otherwise.</returns>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
public bool ContainsObject(NSObject obj) public bool ContainsObject(NSObject obj) => set.Contains(obj);
{
return set.Contains(obj);
}
/// <summary> /// <summary>
/// Determines whether the set contains an object equal to a given object /// Determines whether the set contains an object equal to a given object and returns that object if it is
/// and returns that object if it is present. /// present.
/// </summary> /// </summary>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
/// <returns>The object if it is present, <c>null</c> otherwise.</returns> /// <returns>The object if it is present, <c>null</c> otherwise.</returns>
@@ -182,9 +155,7 @@ namespace Claunia.PropertyList
} }
} }
/// <summary> /// <summary>Finds out whether at least one object is present in both sets.</summary>
/// Finds out whether at least one object is present in both sets.
/// </summary>
/// <returns><c>true</c> if the intersection of both sets is empty, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the intersection of both sets is empty, <c>false</c> otherwise.</returns>
/// <param name="otherSet">The other set.</param> /// <param name="otherSet">The other set.</param>
public bool IntersectsSet(NSSet otherSet) public bool IntersectsSet(NSSet otherSet)
@@ -199,9 +170,7 @@ namespace Claunia.PropertyList
} }
} }
/// <summary> /// <summary>Finds out if this set is a subset of the given set.</summary>
/// Finds out if this set is a subset of the given set.
/// </summary>
/// <returns><c>true</c> if all elements in this set are also present in the other set, <c>false</c>otherwise.</returns> /// <returns><c>true</c> if all elements in this set are also present in the other set, <c>false</c>otherwise.</returns>
/// <param name="otherSet">The other set.</param> /// <param name="otherSet">The other set.</param>
public bool IsSubsetOfSet(NSSet otherSet) public bool IsSubsetOfSet(NSSet otherSet)
@@ -216,18 +185,11 @@ namespace Claunia.PropertyList
} }
} }
/// <summary> /// <summary>Gets the underlying data structure in which this NSSets stores its content.</summary>
/// Gets the underlying data structure in which this NSSets stores its content.
/// </summary>
/// <returns>A Set object.</returns> /// <returns>A Set object.</returns>
internal List<NSObject> GetSet() internal List<NSObject> GetSet() => set;
{
return set;
}
/// <summary> /// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSSet" /> object.</summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSSet" /> object.
/// </summary>
/// <returns> /// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table. /// hash table.
@@ -235,7 +197,8 @@ namespace Claunia.PropertyList
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
hash = 29 * hash + (set != null ? set.GetHashCode() : 0); hash = (29 * hash) + (set != null ? set.GetHashCode() : 0);
return hash; return hash;
} }
@@ -253,18 +216,20 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(object obj) 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;
NSSet other = (NSSet)obj;
return !(set != other.set && (set == null || !set.Equals(other.set))); return !(set != other.set && (set == null || !set.Equals(other.set)));
} }
/// <summary> /// <summary>
/// Returns the XML representantion for this set. /// Returns the XML representation for this set. There is no official XML representation specified for sets. In
/// There is no official XML representation specified for sets. /// this implementation it is represented by an array.
/// In this implementation it is represented by an array.
/// </summary> /// </summary>
/// <param name="xml">The XML StringBuilder</param> /// <param name="xml">The XML StringBuilder</param>
/// <param name="level">The indentation level</param> /// <param name="level">The indentation level</param>
@@ -273,7 +238,10 @@ namespace Claunia.PropertyList
Indent(xml, level); Indent(xml, level);
xml.Append("<array>"); xml.Append("<array>");
xml.Append(NEWLINE); xml.Append(NEWLINE);
if(ordered) set.Sort();
if(ordered)
set.Sort();
foreach(NSObject o in set) foreach(NSObject o in set)
{ {
o.ToXml(xml, level + 1); o.ToXml(xml, level + 1);
@@ -287,7 +255,9 @@ namespace Claunia.PropertyList
internal override void AssignIDs(BinaryPropertyListWriter outPlist) internal override void AssignIDs(BinaryPropertyListWriter outPlist)
{ {
base.AssignIDs(outPlist); 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) internal override void ToBinary(BinaryPropertyListWriter outPlist)
@@ -297,30 +267,37 @@ namespace Claunia.PropertyList
set.Sort(); set.Sort();
outPlist.WriteIntHeader(0xB, set.Count); outPlist.WriteIntHeader(0xB, set.Count);
} }
else outPlist.WriteIntHeader(0xC, set.Count); 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> /// <summary>
/// Returns the ASCII representation of this set. /// Returns the ASCII representation of this set. There is no official ASCII representation for sets. In this
/// There is no official ASCII representation for sets. /// implementation sets are represented as arrays.
/// In this implementation sets are represented as arrays.
/// </summary> /// </summary>
/// <param name="ascii">The ASCII file string builder</param> /// <param name="ascii">The ASCII file string builder</param>
/// <param name="level">The indentation level</param> /// <param name="level">The indentation level</param>
internal override void ToASCII(StringBuilder ascii, int level) internal override void ToASCII(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
if(ordered) set.Sort();
if(ordered)
set.Sort();
NSObject[] array = AllObjects(); NSObject[] array = AllObjects();
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++)
{ {
Type objClass = array[i].GetType(); Type objClass = array[i].GetType();
if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
objClass.Equals(typeof(NSData))) && indexOfLastNewLine != ascii.Length) objClass.Equals(typeof(NSData))) &&
indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -328,41 +305,46 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(i != 0) ascii.Append(" "); if(i != 0)
ascii.Append(" ");
array[i].ToASCII(ascii, 0); 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)
{
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
}
ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
} }
/// <summary> /// <summary>
/// Returns the ASCII representation of this set according to the GnuStep format. /// Returns the ASCII representation of this set according to the GnuStep format. There is no official ASCII
/// There is no official ASCII representation for sets. /// representation for sets. In this implementation sets are represented as arrays.
/// In this implementation sets are represented as arrays.
/// </summary> /// </summary>
/// <param name="ascii">The ASCII file string builder</param> /// <param name="ascii">The ASCII file string builder</param>
/// <param name="level">The indentation level</param> /// <param name="level">The indentation level</param>
internal override void ToASCIIGnuStep(StringBuilder ascii, int level) internal override void ToASCIIGnuStep(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
if(ordered) set.Sort();
if(ordered)
set.Sort();
NSObject[] array = AllObjects(); NSObject[] array = AllObjects();
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for(int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++)
{ {
Type objClass = array[i].GetType(); if(array[i] is NSDictionary or NSArray or NSData &&
if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || indexOfLastNewLine != ascii.Length)
objClass.Equals(typeof(NSData))) && indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -370,18 +352,21 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(i != 0) ascii.Append(" "); if(i != 0)
ascii.Append(" ");
array[i].ToASCIIGnuStep(ascii, 0); 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)
{
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
}
ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
} }
@@ -400,11 +385,13 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(!(obj is NSSet)) return false; if(obj is not NSSet nsSet)
return false;
if(set.Count != ((NSSet)obj).Count) return false; if(set.Count != nsSet.Count)
return false;
foreach(NSObject objS in (NSSet)obj) foreach(NSObject objS in nsSet)
if(!set.Contains(objS)) if(!set.Contains(objS))
return false; return false;

View File

@@ -28,26 +28,20 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>A NSString contains a string.</summary>
/// A NSString contains a string.
/// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class NSString : NSObject, IComparable public class NSString : NSObject, IComparable
{ {
static Encoding asciiEncoder, utf16beEncoder, utf8Encoder; static Encoding asciiEncoder, utf16beEncoder, utf8Encoder;
/// <summary> /// <summary>Creates a NSString from its binary representation.</summary>
/// Creates a NSString from its binary representation.
/// </summary>
/// <param name="bytes">The binary representation.</param> /// <param name="bytes">The binary representation.</param>
/// <param name="encoding">The encoding of the binary representation, the name of a supported charset.</param> /// <param name="encoding">The encoding of the binary representation, the name of a supported charset.</param>
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception> /// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
public NSString(ReadOnlySpan<byte> bytes, string encoding) : this(bytes, Encoding.GetEncoding(encoding)) {} public NSString(ReadOnlySpan<byte> bytes, string encoding) : this(bytes, Encoding.GetEncoding(encoding)) {}
/// <summary> /// <summary>Creates a NSString from its binary representation.</summary>
/// Creates a NSString from its binary representation.
/// </summary>
/// <param name="bytes">The binary representation.</param> /// <param name="bytes">The binary representation.</param>
/// <param name="encoding">The encoding of the binary representation.</param> /// <param name="encoding">The encoding of the binary representation.</param>
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception> /// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
@@ -60,69 +54,39 @@ namespace Claunia.PropertyList
#endif #endif
} }
/// <summary> /// <summary>Creates a NSString from a string.</summary>
/// Creates a NSString from a string.
/// </summary>
/// <param name="text">The string that will be contained in the NSString.</param> /// <param name="text">The string that will be contained in the NSString.</param>
public NSString(string text) public NSString(string text) => Content = text;
{
Content = text;
}
/// <summary> /// <summary>Gets this strings content.</summary>
/// Gets this strings content.
/// </summary>
/// <returns>This NSString as .NET string object.</returns> /// <returns>This NSString as .NET string object.</returns>
public string Content { get; set; } public string Content { get; set; }
/// <summary> /// <summary>Compares the current <see cref="Claunia.PropertyList.NSString" /> to the specified object.</summary>
/// Compares the current <see cref="Claunia.PropertyList.NSString" /> to the specified object.
/// </summary>
/// <returns>A 32-bit signed integer that indicates the lexical relationship between the two comparands.</returns> /// <returns>A 32-bit signed integer that indicates the lexical relationship between the two comparands.</returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param> /// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param>
public int CompareTo(object o) public int CompareTo(object o) => o switch
{ {
if(o is NSString) return string.Compare(Content, ((NSString)o).Content, StringComparison.Ordinal); NSString nsString => string.Compare(Content, nsString.Content, StringComparison.Ordinal),
if(o is string) return string.Compare(Content, (string)o, StringComparison.Ordinal); string s => string.Compare(Content, s, StringComparison.Ordinal),
_ => -1
};
return -1; /// <summary>Appends a string to this string.</summary>
}
/// <summary>
/// Appends a string to this string.
/// </summary>
/// <param name="s">The string to append.</param> /// <param name="s">The string to append.</param>
public void Append(NSString s) public void Append(NSString s) => Append(s.Content);
{
Append(s.Content);
}
/// <summary> /// <summary>Appends a string to this string.</summary>
/// Appends a string to this string.
/// </summary>
/// <param name="s">The string to append.</param> /// <param name="s">The string to append.</param>
public void Append(string s) public void Append(string s) => Content += s;
{
Content += s;
}
/// <summary> /// <summary>Prepends a string to this string.</summary>
/// Prepends a string to this string.
/// </summary>
/// <param name="s">The string to prepend.</param> /// <param name="s">The string to prepend.</param>
public void Prepend(string s) public void Prepend(string s) => Content = s + Content;
{
Content = s + Content;
}
/// <summary> /// <summary>Prepends a string to this string.</summary>
/// Prepends a string to this string.
/// </summary>
/// <param name="s">The string to prepend.</param> /// <param name="s">The string to prepend.</param>
public void Prepend(NSString s) public void Prepend(NSString s) => Prepend(s.Content);
{
Prepend(s.Content);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to the current /// Determines whether the specified <see cref="System.Object" /> is equal to the current
@@ -136,33 +100,18 @@ namespace Claunia.PropertyList
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current /// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />; otherwise, <c>false</c>. /// <see cref="Claunia.PropertyList.NSString" />; otherwise, <c>false</c>.
/// </returns> /// </returns>
public override bool Equals(object obj) public override bool Equals(object obj) => obj is NSString nsString && Content.Equals(nsString.Content);
{
if(!(obj is NSString)) return false;
return Content.Equals(((NSString)obj).Content); /// <summary>Serves as a hash function for a <see cref="Claunia.PropertyList.NSString" /> object.</summary>
}
/// <summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSString" /> object.
/// </summary>
/// <returns> /// <returns>
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table. /// hash table.
/// </returns> /// </returns>
public override int GetHashCode() public override int GetHashCode() => Content.GetHashCode();
{
return Content.GetHashCode();
}
/// <summary> /// <summary>The textual representation of this NSString.</summary>
/// The textual representation of this NSString.
/// </summary>
/// <returns>The NSString's contents.</returns> /// <returns>The NSString's contents.</returns>
public override string ToString() public override string ToString() => Content;
{
return Content;
}
internal override void ToXml(StringBuilder xml, int level) internal override void ToXml(StringBuilder xml, int level)
{ {
@@ -172,7 +121,7 @@ namespace Claunia.PropertyList
//Make sure that the string is encoded in UTF-8 for the XML output //Make sure that the string is encoded in UTF-8 for the XML output
lock(typeof(NSString)) lock(typeof(NSString))
{ {
if(utf8Encoder == null) utf8Encoder = Encoding.GetEncoding("UTF-8"); utf8Encoder ??= Encoding.GetEncoding("UTF-8");
try try
{ {
@@ -187,13 +136,16 @@ namespace Claunia.PropertyList
//According to http://www.w3.org/TR/REC-xml/#syntax node values must not //According to http://www.w3.org/TR/REC-xml/#syntax node values must not
//contain the characters < or &. Also the > character should be escaped. //contain the characters < or &. Also the > character should be escaped.
if(Content.Contains("&") || Content.Contains("<") || Content.Contains(">")) if(Content.Contains("&") ||
Content.Contains("<") ||
Content.Contains(">"))
{ {
xml.Append("<![CDATA["); xml.Append("<![CDATA[");
xml.Append(Content.Replace("]]>", "]]]]><![CDATA[>")); xml.Append(Content.Replace("]]>", "]]]]><![CDATA[>"));
xml.Append("]]>"); xml.Append("]]>");
} }
else xml.Append(Content); else
xml.Append(Content);
xml.Append("</string>"); xml.Append("</string>");
} }
@@ -202,11 +154,11 @@ namespace Claunia.PropertyList
{ {
int kind; int kind;
byte[] byteBuf; byte[] byteBuf;
lock(typeof(NSString)) lock(typeof(NSString))
{ {
if(asciiEncoder == null)
// Not much use, because some characters do not fallback to exception, even if not ASCII // Not much use, because some characters do not fallback to exception, even if not ASCII
asciiEncoder = Encoding.GetEncoding("ascii", EncoderFallback.ExceptionFallback, asciiEncoder ??= Encoding.GetEncoding("ascii", EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback); DecoderFallback.ExceptionFallback);
if(IsASCIIEncodable(Content)) if(IsASCIIEncodable(Content))
@@ -216,7 +168,7 @@ namespace Claunia.PropertyList
} }
else else
{ {
if(utf16beEncoder == null) utf16beEncoder = Encoding.BigEndianUnicode; utf16beEncoder ??= Encoding.BigEndianUnicode;
kind = 0x6; // UTF-16-BE kind = 0x6; // UTF-16-BE
byteBuf = utf16beEncoder.GetBytes(Content); byteBuf = utf16beEncoder.GetBytes(Content);
@@ -231,6 +183,7 @@ namespace Claunia.PropertyList
{ {
Indent(ascii, level); Indent(ascii, level);
ascii.Append("\""); ascii.Append("\"");
//According to https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html //According to https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
//non-ASCII characters are not escaped but simply written into the //non-ASCII characters are not escaped but simply written into the
//file, thus actually violating the ASCII plain text format. //file, thus actually violating the ASCII plain text format.
@@ -247,31 +200,37 @@ namespace Claunia.PropertyList
ascii.Append("\""); ascii.Append("\"");
} }
/// <summary> /// <summary>Escapes a string for use in ASCII property lists.</summary>
/// Escapes a string for use in ASCII property lists.
/// </summary>
/// <returns>The unescaped string.</returns> /// <returns>The unescaped string.</returns>
/// <param name="s">S.</param> /// <param name="s">S.</param>
internal static string EscapeStringForASCII(string s) internal static string EscapeStringForASCII(string s)
{ {
string outString = ""; string outString = "";
char[] cArray = s.ToCharArray(); char[] cArray = s.ToCharArray();
foreach(char c in cArray) foreach(char c in cArray)
if(c > 127) if(c > 127)
{ {
//non-ASCII Unicode //non-ASCII Unicode
outString += "\\U"; outString += "\\U";
string hex = string.Format("{0:x}", c); string hex = $"{c:x}";
while(hex.Length < 4) hex = "0" + hex;
while(hex.Length < 4)
hex = "0" + hex;
outString += hex; outString += hex;
} }
else if(c == '\\') outString += "\\\\"; else
else if(c == '\"') outString += "\\\""; outString += c switch
else if(c == '\b') outString += "\\b"; {
else if(c == '\n') outString += "\\n"; '\\' => "\\\\",
else if(c == '\r') outString += "\\r"; '\"' => "\\\"",
else if(c == '\t') outString += "\\t"; '\b' => "\\b",
else outString += c; '\n' => "\\n",
'\r' => "\\r",
'\t' => "\\t",
_ => c
};
return outString; return outString;
} }
@@ -290,9 +249,10 @@ namespace Claunia.PropertyList
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if(!(obj is NSString)) return false; if(obj is not NSString nsString)
return false;
return Content == ((NSString)obj).Content; return Content == nsString.Content;
} }
internal static bool IsASCIIEncodable(string text) internal static bool IsASCIIEncodable(string text)
@@ -304,14 +264,8 @@ namespace Claunia.PropertyList
return true; return true;
} }
public static explicit operator string(NSString value) public static explicit operator string(NSString value) => value.Content;
{
return value.Content;
}
public static explicit operator NSString(string value) public static explicit operator NSString(string value) => new(value);
{
return new NSString(value);
}
} }
} }

View File

@@ -29,40 +29,25 @@ using System.Runtime.Serialization;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>The exception that is thrown when an property list file could not be processed correctly.</summary>
/// The exception that is thrown when an property list file could not be processed correctly.
/// </summary>
[Serializable] [Serializable]
public class PropertyListException : Exception public class PropertyListException : Exception
{ {
/// <summary> /// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
/// Initializes a new instance of the <see cref="PropertyListException" /> class.
/// </summary>
public PropertyListException() {} public PropertyListException() {}
/// <summary> /// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
/// Initializes a new instance of the <see cref="PropertyListException" /> class. /// <param name="message">The error message that explains the reason for the exception.</param>
/// </summary>
/// <param name="message">
/// The error message that explains the reason for the exception.
/// </param>
public PropertyListException(string message) : base(message) {} public PropertyListException(string message) : base(message) {}
/// <summary> /// <summary>Initializes a new instance of the <see cref="PropertyListException" /> class.</summary>
/// Initializes a new instance of the <see cref="PropertyListException" /> class. /// <param name="message">The error message that explains the reason for the exception.</param>
/// </summary>
/// <param name="message">
/// The error message that explains the reason for the exception.
/// </param>
/// <param name="inner"> /// <param name="inner">
/// The exception that is the cause of the current exception, or <see langword="null" /> /// The exception that is the cause of the current exception, or <see langword="null" /> if no inner
/// if no inner exception is specified. /// exception is specified.
/// </param> /// </param>
public PropertyListException(string message, Exception inner) : base(message, inner) {} public PropertyListException(string message, Exception inner) : base(message, inner) {}
protected PropertyListException(SerializationInfo info, StreamingContext context) protected PropertyListException(SerializationInfo info, StreamingContext context) : base(info, context) {}
: base(info, context)
{
}
} }
} }

View File

@@ -26,16 +26,14 @@
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// A PropertyListFormatException is thrown by the various property list format parsers /// A PropertyListFormatException is thrown by the various property list format parsers when an error in the
/// when an error in the format of the given property list is encountered. /// format of the given property list is encountered.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class PropertyListFormatException : PropertyListException public class PropertyListFormatException : PropertyListException
{ {
/// <summary> /// <summary>Creates a new exception with the given message.</summary>
/// Creates a new exception with the given message.
/// </summary>
/// <param name="message">A message containing information about the nature of the exception.</param> /// <param name="message">A message containing information about the nature of the exception.</param>
public PropertyListFormatException(string message) : base(message) {} public PropertyListFormatException(string message) : base(message) {}
} }

View File

@@ -30,9 +30,8 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// This class provides methods to parse property lists. It can handle files, /// This class provides methods to parse property lists. It can handle files, input streams and byte arrays. All
/// input streams and byte arrays. All known property list formats are supported. /// known property list formats are supported. This class also provides methods to save and convert property lists.
/// This class also provides methods to save and convert property lists.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
@@ -44,57 +43,71 @@ namespace Claunia.PropertyList
const int TYPE_ERROR_BLANK = 10; const int TYPE_ERROR_BLANK = 10;
const int TYPE_ERROR_UNKNOWN = 11; const int TYPE_ERROR_UNKNOWN = 11;
/// <summary> /// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// Determines the type of a property list by means of the first bytes of its data
/// </summary>
/// <returns>The type of the property list</returns> /// <returns>The type of the property list</returns>
/// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param> /// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param>
static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning) static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning)
{ {
if(dataBeginning.Length == 0) 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] == '<')
if(dataBeginning.Length >= 6 && dataBeginning[0] == 'b' && dataBeginning[1] == 'p' && return TYPE_XML;
dataBeginning[2] == 'l' && dataBeginning[3] == 'i' && dataBeginning[4] == 's' &&
dataBeginning[5] == 't') return TYPE_BINARY; if(dataBeginning.Length >= 6 &&
dataBeginning[0] == 'b' &&
dataBeginning[1] == 'p' &&
dataBeginning[2] == 'l' &&
dataBeginning[3] == 'i' &&
dataBeginning[4] == 's' &&
dataBeginning[5] == 't')
return TYPE_BINARY;
return TYPE_ERROR_UNKNOWN; return TYPE_ERROR_UNKNOWN;
} }
/// <summary> /// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// Determines the type of a property list by means of the first bytes of its data
/// </summary>
/// <returns>The very first bytes of data of the property list (minus any whitespace)</returns> /// <returns>The very first bytes of data of the property list (minus any whitespace)</returns>
/// <param name="bytes">The type of the property list</param> /// <param name="bytes">The type of the property list</param>
static int DetermineType(ReadOnlySpan<byte> bytes) static int DetermineType(ReadOnlySpan<byte> bytes)
{ {
if(bytes.Length == 0) return TYPE_ERROR_BLANK; if(bytes.Length == 0)
return TYPE_ERROR_BLANK;
//Skip any possible whitespace at the beginning of the file //Skip any possible whitespace at the beginning of the file
int offset = 0; int offset = 0;
if(bytes.Length >= 3 && (bytes[0] & 0xFF) == 0xEF && (bytes[1] & 0xFF) == 0xBB &&
(bytes[2] & 0xFF) == 0xBF) offset += 3; if(bytes.Length >= 3 &&
while(offset < bytes.Length && (bytes[offset] == ' ' || bytes[offset] == '\t' || bytes[offset] == '\r' || (bytes[0] & 0xFF) == 0xEF &&
bytes[offset] == '\n' || bytes[offset] == '\f')) offset++; (bytes[1] & 0xFF) == 0xBB &&
(bytes[2] & 0xFF) == 0xBF)
offset += 3;
while(offset < bytes.Length &&
(bytes[offset] == ' ' || bytes[offset] == '\t' || bytes[offset] == '\r' || bytes[offset] == '\n' ||
bytes[offset] == '\f'))
offset++;
ReadOnlySpan<byte> header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset)); ReadOnlySpan<byte> header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset));
return DetermineTypeExact(header); return DetermineTypeExact(header);
} }
/// <summary> /// <summary>Determines the type of a property list by means of the first bytes of its data</summary>
/// Determines the type of a property list by means of the first bytes of its data
/// </summary>
/// <returns>The type of the property list</returns> /// <returns>The type of the property list</returns>
/// <param name="fs"> /// <param name="fs">
/// An input stream pointing to the beginning of the property list data. /// An input stream pointing to the beginning of the property list data. The stream will be reset to the
/// The stream will be reset to the beginning of the property /// beginning of the property list data after the type has been determined.
/// list data after the type has been determined.
/// </param> /// </param>
static int DetermineType(Stream fs, long offset = 0) 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 index = offset;
long readLimit = index + 1024; long readLimit = index + 1024;
@@ -109,18 +122,20 @@ namespace Claunia.PropertyList
if(++index > readLimit) if(++index > readLimit)
{ {
fs.Seek(mark, SeekOrigin.Begin); fs.Seek(mark, SeekOrigin.Begin);
return DetermineType(fs, readLimit); return DetermineType(fs, readLimit);
} }
b = fs.ReadByte(); b = fs.ReadByte();
//Check if we are reading the Unicode byte order mark (BOM) and skip it //Check if we are reading the Unicode byte order mark (BOM) and skip it
bom = index < 3 && (index == 0 && b == 0xEF || bom = index < 3 && ((index == 0 && b == 0xEF) ||
bom && (index == 1 && b == 0xBB || index == 2 && b == 0xBF)); (bom && ((index == 1 && b == 0xBB) || (index == 2 && b == 0xBF))));
} } while(b != -1 &&
while(b != -1 && (b == ' ' || b == '\t' || b == '\r' || b == '\n' || b == '\f' || bom)); (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]; byte[] magicBytes = new byte[8];
magicBytes[0] = (byte)b; magicBytes[0] = (byte)b;
@@ -128,46 +143,37 @@ namespace Claunia.PropertyList
int type = DetermineTypeExact(magicBytes.AsSpan(0, read)); int type = DetermineTypeExact(magicBytes.AsSpan(0, read));
fs.Seek(mark, SeekOrigin.Begin); fs.Seek(mark, SeekOrigin.Begin);
return type; return type;
} }
/// <summary> /// <summary>Reads all bytes from an Stream and stores them in an array, up to a maximum count.</summary>
/// Reads all bytes from an Stream and stores them in an array, up to
/// a maximum count.
/// </summary>
/// <param name="fs">The Stream pointing to the data that should be stored in the array.</param> /// <param name="fs">The Stream pointing to the data that should be stored in the array.</param>
internal static byte[] ReadAll(Stream fs) internal static byte[] ReadAll(Stream fs)
{ {
using(MemoryStream outputStream = new MemoryStream()) using var outputStream = new MemoryStream();
{
fs.CopyTo(outputStream); fs.CopyTo(outputStream);
return outputStream.ToArray(); return outputStream.ToArray();
} }
}
/// <summary> /// <summary>Parses a property list from a file.</summary>
/// Parses a property list from a file.
/// </summary>
/// <param name="filePath">Path to the property list file.</param> /// <param name="filePath">Path to the property list file.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(string filePath) public static NSObject Parse(string filePath) => Parse(new FileInfo(filePath));
{
return Parse(new FileInfo(filePath));
}
/// <summary> /// <summary>Parses a property list from a file.</summary>
/// Parses a property list from a file.
/// </summary>
/// <param name="f">The property list file.</param> /// <param name="f">The property list file.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(FileInfo f) public static NSObject Parse(FileInfo f)
{ {
using(FileStream fis = f.OpenRead()) return Parse(fis); using FileStream fis = f.OpenRead();
return Parse(fis);
} }
/// <summary> /// <summary>Parses a property list from a byte array.</summary>
/// Parses a property list from a byte array.
/// </summary>
/// <param name="bytes">The property list data as a byte array.</param> /// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes) public static NSObject Parse(byte[] bytes)
@@ -183,21 +189,14 @@ namespace Claunia.PropertyList
} }
} }
/// <summary> /// <summary>Parses a property list from a byte array.</summary>
/// Parses a property list from a byte array.
/// </summary>
/// <param name="bytes">The property list data as a byte array.</param> /// <param name="bytes">The property list data as a byte array.</param>
/// <param name="offset">The length of the property list.</param> /// <param name="offset">The length of the property list.</param>
/// <param name="count">The offset at which to start reading the property list.</param> /// <param name="count">The offset at which to start reading the property list.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes, int offset, int length) public static NSObject Parse(byte[] bytes, int offset, int length) => Parse(bytes.AsSpan(offset, length));
{
return Parse(bytes.AsSpan(offset, length));
}
/// <summary> /// <summary>Parses a property list from a byte span.</summary>
/// Parses a property list from a byte span.
/// </summary>
/// <param name="bytes">The property list data as a byte array.</param> /// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(ReadOnlySpan<byte> bytes) public static NSObject Parse(ReadOnlySpan<byte> bytes)
@@ -213,47 +212,41 @@ namespace Claunia.PropertyList
} }
} }
/// <summary> /// <summary>Parses a property list from an Stream.</summary>
/// Parses a property list from an Stream.
/// </summary>
/// <param name="fs">The Stream delivering the property list data.</param> /// <param name="fs">The Stream delivering the property list data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(Stream fs) public static NSObject Parse(Stream fs) => Parse(ReadAll(fs));
{
return Parse(ReadAll(fs));
}
/// <summary> /// <summary>Saves a property list with the given object as root into a XML file.</summary>
/// Saves a property list with the given object as root into a XML file.
/// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsXml(NSObject root, FileInfo outFile) public static void SaveAsXml(NSObject root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; 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 // Use Create here -- to make sure that when the updated file is shorter than
// the original file, no "obsolete" data is left at the end. // the original file, no "obsolete" data is left at the end.
using(Stream fous = outFile.Open(FileMode.Create, FileAccess.ReadWrite)) SaveAsXml(root, fous); using Stream fous = outFile.Open(FileMode.Create, FileAccess.ReadWrite);
SaveAsXml(root, fous);
} }
/// <summary> /// <summary>Saves a property list with the given object as root in XML format into an output stream.</summary>
/// Saves a property list with the given object as root in XML format into an output stream.
/// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param> /// <param name="outStream">The output stream.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsXml(NSObject root, Stream outStream) public static void SaveAsXml(NSObject root, Stream outStream)
{ {
using(StreamWriter w = new StreamWriter(outStream, Encoding.UTF8, 1024, true)) using var w = new StreamWriter(outStream, Encoding.UTF8, 1024, true);
w.Write(root.ToXmlPropertyList()); w.Write(root.ToXmlPropertyList());
} }
/// <summary> /// <summary>Converts a given property list file into the OS X and iOS XML format.</summary>
/// Converts a given property list file into the OS X and iOS XML format.
/// </summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param> /// <param name="outFile">The target file.</param>
public static void ConvertToXml(FileInfo inFile, FileInfo outFile) public static void ConvertToXml(FileInfo inFile, FileInfo outFile)
@@ -262,33 +255,28 @@ namespace Claunia.PropertyList
SaveAsXml(root, outFile); SaveAsXml(root, outFile);
} }
/// <summary> /// <summary>Saves a property list with the given object as root into a binary file.</summary>
/// Saves a property list with the given object as root into a binary file.
/// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsBinary(NSObject root, FileInfo outFile) public static void SaveAsBinary(NSObject root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
if(!Directory.Exists(parent))
Directory.CreateDirectory(parent);
BinaryPropertyListWriter.Write(outFile, root); BinaryPropertyListWriter.Write(outFile, root);
} }
/// <summary> /// <summary>Saves a property list with the given object as root in binary format into an output stream.</summary>
/// Saves a property list with the given object as root in binary format into an output stream.
/// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param> /// <param name="outStream">The output stream.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsBinary(NSObject root, Stream outStream) public static void SaveAsBinary(NSObject root, Stream outStream) =>
{
BinaryPropertyListWriter.Write(outStream, root); BinaryPropertyListWriter.Write(outStream, root);
}
/// <summary> /// <summary>Converts a given property list file into the OS X and iOS binary format.</summary>
/// Converts a given property list file into the OS X and iOS binary format.
/// </summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param> /// <param name="outFile">The target file.</param>
public static void ConvertToBinary(FileInfo inFile, FileInfo outFile) public static void ConvertToBinary(FileInfo inFile, FileInfo outFile)
@@ -297,94 +285,115 @@ namespace Claunia.PropertyList
SaveAsBinary(root, outFile); SaveAsBinary(root, outFile);
} }
/// <summary> /// <summary>Saves a property list with the given object as root into a ASCII file.</summary>
/// Saves a property list with the given object as root into a ASCII file.
/// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsASCII(NSDictionary root, FileInfo outFile) public static void SaveAsASCII(NSDictionary root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
using(Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)) if(!Directory.Exists(parent))
using(StreamWriter w = new StreamWriter(fous, Encoding.ASCII)) Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
using var w = new StreamWriter(fous, Encoding.ASCII);
w.Write(root.ToASCIIPropertyList()); w.Write(root.ToASCIIPropertyList());
} }
/// <summary> /// <summary>Saves a property list with the given object as root into a ASCII file.</summary>
/// Saves a property list with the given object as root into a ASCII file.
/// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsASCII(NSArray root, FileInfo outFile) public static void SaveAsASCII(NSArray root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
using(Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)) if(!Directory.Exists(parent))
using(StreamWriter w = new StreamWriter(fous, Encoding.ASCII)) Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
using var w = new StreamWriter(fous, Encoding.ASCII);
w.Write(root.ToASCIIPropertyList()); w.Write(root.ToASCIIPropertyList());
} }
/// <summary> /// <summary>Converts a given property list file into ASCII format.</summary>
/// Converts a given property list file into ASCII format.
/// </summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param> /// <param name="outFile">The target file.</param>
public static void ConvertToASCII(FileInfo inFile, FileInfo outFile) public static void ConvertToASCII(FileInfo inFile, FileInfo outFile)
{ {
NSObject root = Parse(inFile); NSObject root = Parse(inFile);
if(root is NSDictionary) SaveAsASCII((NSDictionary)root, outFile);
else if(root is NSArray) SaveAsASCII((NSArray)root, outFile); if(root is NSDictionary dictionary)
SaveAsASCII(dictionary, outFile);
else if(root is NSArray array)
SaveAsASCII(array, outFile);
else else
throw new PropertyListFormatException("The root of the given input property list " + throw new PropertyListFormatException("The root of the given input property list " +
"is neither a Dictionary nor an Array!"); "is neither a Dictionary nor an Array!");
} }
/// <summary> /// <summary>Saves a property list with the given object as root into a GnuStep ASCII file.</summary>
/// Saves a property list with the given object as root into a GnuStep ASCII file.
/// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsGnuStepASCII(NSDictionary root, FileInfo outFile) public static void SaveAsGnuStepASCII(NSDictionary root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
using(Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)) if(!Directory.Exists(parent))
using(StreamWriter w = new StreamWriter(fous, Encoding.ASCII)) Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
using var w = new StreamWriter(fous, Encoding.ASCII);
w.Write(root.ToGnuStepASCIIPropertyList()); w.Write(root.ToGnuStepASCIIPropertyList());
} }
/// <summary> /// <summary>Saves a property list with the given object as root into a GnuStep ASCII file.</summary>
/// Saves a property list with the given object as root into a GnuStep ASCII file.
/// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsGnuStepASCII(NSArray root, FileInfo outFile) public static void SaveAsGnuStepASCII(NSArray root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
using(Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)) if(!Directory.Exists(parent))
using(StreamWriter w = new StreamWriter(fous, Encoding.ASCII)) Directory.CreateDirectory(parent);
using Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite);
using var w = new StreamWriter(fous, Encoding.ASCII);
w.Write(root.ToGnuStepASCIIPropertyList()); w.Write(root.ToGnuStepASCIIPropertyList());
} }
/// <summary> /// <summary>Converts a given property list file into GnuStep ASCII format.</summary>
/// Converts a given property list file into GnuStep ASCII format.
/// </summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param> /// <param name="outFile">The target file.</param>
public static void ConvertToGnuStepASCII(FileInfo inFile, FileInfo outFile) public static void ConvertToGnuStepASCII(FileInfo inFile, FileInfo outFile)
{ {
NSObject root = Parse(inFile); NSObject root = Parse(inFile);
if(root is NSDictionary) SaveAsGnuStepASCII((NSDictionary)root, outFile);
else if(root is NSArray) SaveAsGnuStepASCII((NSArray)root, outFile); switch(root)
else {
case NSDictionary dictionary:
SaveAsGnuStepASCII(dictionary, outFile);
break;
case NSArray array:
SaveAsGnuStepASCII(array, outFile);
break;
default:
throw new PropertyListFormatException("The root of the given input property list " + throw new PropertyListFormatException("The root of the given input property list " +
"is neither a Dictionary nor an Array!"); "is neither a Dictionary nor an Array!");
} }
} }
} }
}

View File

@@ -29,67 +29,56 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>An UID. Only found in binary property lists that are keyed archives.</summary>
/// An UID. Only found in binary property lists that are keyed archives.
/// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class UID : NSObject public class UID : NSObject
{ {
readonly ulong value; readonly ulong value;
/// <summary> /// <summary>Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class.</summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class.
/// </summary>
/// <param name="bytes">Bytes.</param> /// <param name="bytes">Bytes.</param>
public UID(ReadOnlySpan<byte> bytes) public UID(ReadOnlySpan<byte> bytes)
{ {
if(bytes.Length != 1 && 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."); throw new ArgumentException("Type argument is not valid.");
value = (ulong)BinaryPropertyListParser.ParseLong(bytes); value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 8-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 8-bit
/// number.
/// </summary> /// </summary>
/// <param name="number">Unsigned 8-bit number.</param> /// <param name="number">Unsigned 8-bit number.</param>
public UID(byte number) public UID(byte number) => value = number;
{
value = number;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 16-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 16-bit
/// number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Unsigned 16-bit number.</param> /// <param name="number">Unsigned 16-bit number.</param>
public UID(ushort number) public UID(ushort number) => value = number;
{
value = number;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 32-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 32-bit
/// number.
/// </summary> /// </summary>
/// <param name="number">Unsigned 32-bit number.</param> /// <param name="number">Unsigned 32-bit number.</param>
public UID(uint number) public UID(uint number) => value = number;
{
value = number;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 64-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 64-bit
/// number.
/// </summary> /// </summary>
/// <param name="number">Unsigned 64-bit number.</param> /// <param name="number">Unsigned 64-bit number.</param>
public UID(ulong number) public UID(ulong number) => value = number;
{
value = number;
}
/// <summary> /// <summary>Gets the bytes.</summary>
/// Gets the bytes.
/// </summary>
/// <value>The bytes.</value> /// <value>The bytes.</value>
public byte[] Bytes public byte[] Bytes
{ {
@@ -97,50 +86,44 @@ namespace Claunia.PropertyList
{ {
byte[] bytes = new byte[ByteCount]; byte[] bytes = new byte[ByteCount];
GetBytes(bytes); GetBytes(bytes);
return bytes; return bytes;
} }
} }
/// <summary> /// <summary>Gets the number of bytes required to represent this <see cref="UID" />.</summary>
/// Gets the number of bytes required to represent this <see cref="UID" />. public int ByteCount => value switch
/// </summary>
public int ByteCount
{ {
get <= byte.MaxValue => 1,
{ <= ushort.MaxValue => 2,
if(value <= byte.MaxValue) return 1; <= uint.MaxValue => 4,
_ => 8
};
if(value <= ushort.MaxValue) return 2; /// <summary>Writes the bytes required to represent this <see cref="UID" /> to a byte span.</summary>
if(value <= uint.MaxValue) return 4; /// <param name="bytes">The byte span to which to write the byte representation of this UID.</param>
return 8;
}
}
/// <summary>
/// Writes the bytes required to represent this <see cref="UID" /> to a byte span.
/// </summary>
/// <param name="bytes">
/// The byte span to which to write the byte representation of this UID.
/// </param>
public void GetBytes(Span<byte> bytes) public void GetBytes(Span<byte> bytes)
{ {
switch(ByteCount) switch(ByteCount)
{ {
case 1: case 1:
bytes[0] = (byte)value; bytes[0] = (byte)value;
break; break;
case 2: case 2:
BinaryPrimitives.WriteUInt16BigEndian(bytes, (ushort)value); BinaryPrimitives.WriteUInt16BigEndian(bytes, (ushort)value);
break; break;
case 4: case 4:
BinaryPrimitives.WriteUInt32BigEndian(bytes, (uint)value); BinaryPrimitives.WriteUInt32BigEndian(bytes, (uint)value);
break; break;
case 8: case 8:
BinaryPrimitives.WriteUInt64BigEndian(bytes, value); BinaryPrimitives.WriteUInt64BigEndian(bytes, value);
break; break;
default: throw new InvalidOperationException(); default: throw new InvalidOperationException();
@@ -148,9 +131,8 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// UIDs are represented as dictionaries in XML property lists, /// UIDs are represented as dictionaries in XML property lists, where the key is always <c>CF$UID</c> and the
/// where the key is always <c>CF$UID</c> and the value is the integer /// value is the integer representation of the UID.
/// representation of the UID.
/// </summary> /// </summary>
/// <param name="xml">The xml StringBuilder</param> /// <param name="xml">The xml StringBuilder</param>
/// <param name="level">The indentation level</param> /// <param name="level">The indentation level</param>
@@ -165,7 +147,7 @@ namespace Claunia.PropertyList
xml.AppendLine(); xml.AppendLine();
Indent(xml, level + 1); Indent(xml, level + 1);
xml.Append($"<integer>{this.value}</integer>"); xml.Append($"<integer>{value}</integer>");
xml.AppendLine(); xml.AppendLine();
Indent(xml, level); Indent(xml, level);
@@ -186,14 +168,14 @@ namespace Claunia.PropertyList
ascii.Append("\""); ascii.Append("\"");
Span<byte> bytes = stackalloc byte[ByteCount]; Span<byte> bytes = stackalloc byte[ByteCount];
GetBytes(bytes); GetBytes(bytes);
foreach(byte b in bytes) ascii.Append(string.Format("{0:x2}", b));
foreach(byte b in bytes)
ascii.Append($"{b:x2}");
ascii.Append("\""); ascii.Append("\"");
} }
internal override void ToASCIIGnuStep(StringBuilder ascii, int level) internal override void ToASCIIGnuStep(StringBuilder ascii, int level) => ToASCII(ascii, level);
{
ToASCII(ascii, level);
}
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
@@ -207,42 +189,25 @@ namespace Claunia.PropertyList
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current /// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.UID" />; otherwise, <c>false</c>. /// <see cref="Claunia.PropertyList.UID" />; otherwise, <c>false</c>.
/// </returns> /// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj) => Equals((object)obj);
{
return Equals((object)obj);
}
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
UID uid = obj as UID; if(obj is not UID uid)
return false;
if(uid == null) return false;
return uid.value == value; return uid.value == value;
} }
/// <inheritdoc /> /// <inheritdoc />
public override int GetHashCode() public override int GetHashCode() => value.GetHashCode();
{
return value.GetHashCode();
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString() => $"{value} (UID)";
{
return $"{value} (UID)";
}
/// <summary> /// <summary>Gets a <see cref="ulong" /> which represents this <see cref="UID" />.</summary>
/// Gets a <see cref="ulong"/> which represents this <see cref="UID"/>. /// <returns>A <see cref="ulong" /> which represents this <see cref="UID" />.</returns>
/// </summary> public ulong ToUInt64() => value;
/// <returns>
/// A <see cref="ulong"/> which represents this <see cref="UID"/>.
/// </returns>
public ulong ToUInt64()
{
return this.value;
}
} }
} }

View File

@@ -30,69 +30,64 @@ using System.Xml;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>Parses XML property lists.</summary>
/// Parses XML property lists.
/// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public static class XmlPropertyListParser public static class XmlPropertyListParser
{ {
/// <summary> /// <summary>Parses a XML property list file.</summary>
/// Parses a XML property list file.
/// </summary>
/// <param name="f">The XML property list file.</param> /// <param name="f">The XML property list file.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(FileInfo f) public static NSObject Parse(FileInfo f)
{ {
XmlDocument doc = new XmlDocument(); var doc = new XmlDocument();
XmlReaderSettings settings = new XmlReaderSettings {DtdProcessing = DtdProcessing.Ignore}; var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Ignore
};
using(Stream stream = f.OpenRead()) using(Stream stream = f.OpenRead())
using(XmlReader reader = XmlReader.Create(stream, settings)) using(var reader = XmlReader.Create(stream, settings))
doc.Load(reader); doc.Load(reader);
return ParseDocument(doc); return ParseDocument(doc);
} }
/// <summary> /// <summary>Parses a XML property list from a byte array.</summary>
/// Parses a XML property list from a byte array.
/// </summary>
/// <param name="bytes">The byte array containing the property list's data.</param> /// <param name="bytes">The byte array containing the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes) public static NSObject Parse(byte[] bytes)
{ {
MemoryStream bis = new MemoryStream(bytes); var bis = new MemoryStream(bytes);
return Parse(bis); return Parse(bis);
} }
/// <summary> /// <summary>Parses a XML property list from an input stream.</summary>
/// Parses a XML property list from an input stream.
/// </summary>
/// <param name="str">The input stream pointing to the property list's data.</param> /// <param name="str">The input stream pointing to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(Stream str) public static NSObject Parse(Stream str)
{ {
XmlDocument doc = new XmlDocument(); var doc = new XmlDocument();
XmlReaderSettings settings = new XmlReaderSettings(); var settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore; settings.DtdProcessing = DtdProcessing.Ignore;
using(XmlReader reader = XmlReader.Create(str, settings)) doc.Load(reader); using(var reader = XmlReader.Create(str, settings))
doc.Load(reader);
return ParseDocument(doc); return ParseDocument(doc);
} }
/// <summary> /// <summary>Parses a XML property list from a string.</summary>
/// Parses a XML property list from a string.
/// </summary>
/// <param name="value">The string pointing to the property list's data.</param> /// <param name="value">The string pointing to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject ParseString(string value) public static NSObject ParseString(string value)
{ {
XmlDocument doc = new XmlDocument(); var doc = new XmlDocument();
XmlReaderSettings settings = new XmlReaderSettings(); var settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore; settings.DtdProcessing = DtdProcessing.Ignore;
doc.LoadXml(value); doc.LoadXml(value);
@@ -100,19 +95,18 @@ namespace Claunia.PropertyList
return ParseDocument(doc); return ParseDocument(doc);
} }
/// <summary> /// <summary>Parses the XML document by generating the appropriate NSObjects for each XML node.</summary>
/// Parses the XML document by generating the appropriate NSObjects for each XML node.
/// </summary>
/// <returns>The root NSObject of the property list contained in the XML document.</returns> /// <returns>The root NSObject of the property list contained in the XML document.</returns>
/// <param name="doc">The XML document.</param> /// <param name="doc">The XML document.</param>
static NSObject ParseDocument(XmlDocument doc) static NSObject ParseDocument(XmlDocument doc)
{ {
XmlNode docType = doc.ChildNodes.OfType<XmlNode>() XmlNode docType = doc.ChildNodes.OfType<XmlNode>().
.SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType); SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType);
if(docType == null) if(docType == null)
{ {
if(!doc.DocumentElement.Name.Equals("plist")) if(doc.DocumentElement != null &&
!doc.DocumentElement.Name.Equals("plist"))
throw new XmlException("The given XML document is not a property list."); throw new XmlException("The given XML document is not a property list.");
} }
else if(!docType.Name.Equals("plist")) else if(!docType.Name.Equals("plist"))
@@ -120,46 +114,43 @@ namespace Claunia.PropertyList
XmlNode rootNode; XmlNode rootNode;
if(doc.DocumentElement.Name.Equals("plist")) if(doc.DocumentElement is { Name: "plist" })
{ {
//Root element wrapped in plist tag //Root element wrapped in plist tag
List<XmlNode> rootNodes = FilterElementNodes(doc.DocumentElement.ChildNodes); List<XmlNode> rootNodes = FilterElementNodes(doc.DocumentElement.ChildNodes);
if(rootNodes.Count == 0)
throw new PropertyListFormatException("The given XML property list has no root element!"); rootNode = rootNodes.Count switch
if(rootNodes.Count == 1) rootNode = rootNodes[0]; {
else 0 => throw new PropertyListFormatException("The given XML property list has no root element!"),
throw 1 => rootNodes[0],
new PropertyListFormatException("The given XML property list has more than one root element!"); _ => throw new
PropertyListFormatException("The given XML property list has more than one root element!")
};
} }
else else
//Root NSObject not wrapped in plist-tag //Root NSObject not wrapped in plist-tag
rootNode = doc.DocumentElement; rootNode = doc.DocumentElement;
return ParseObject(rootNode); return ParseObject(rootNode);
} }
/// <summary> /// <summary>Parses a node in the XML structure and returns the corresponding NSObject</summary>
/// Parses a node in the XML structure and returns the corresponding NSObject
/// </summary>
/// <returns>The corresponding NSObject.</returns> /// <returns>The corresponding NSObject.</returns>
/// <param name="n">The XML node.</param> /// <param name="n">The XML node.</param>
static NSObject ParseObject(XmlNode n) static NSObject ParseObject(XmlNode n)
{ {
if(n.Name.Equals("dict")) switch(n.Name)
{ {
// Special case for UID values // Special case for UID values
if(n.ChildNodes.Count == 2 case "dict" when n.ChildNodes.Count == 2 && n.ChildNodes[0].Name == "key" &&
&& n.ChildNodes[0].Name == "key" n.ChildNodes[0].InnerText == "CF$UID" && n.ChildNodes[1].Name == "integer" &&
&& n.ChildNodes[0].InnerText == "CF$UID" uint.TryParse(n.ChildNodes[1].InnerText, out uint uidValue): return new UID(uidValue);
&& n.ChildNodes[1].Name == "integer" case "dict":
&& uint.TryParse(n.ChildNodes[1].InnerText, out uint uidValue))
{ {
return new UID(uidValue); var dict = new NSDictionary();
}
else
{
NSDictionary dict = new NSDictionary();
List<XmlNode> children = FilterElementNodes(n.ChildNodes); List<XmlNode> children = FilterElementNodes(n.ChildNodes);
for(int i = 0; i < children.Count; i += 2) for(int i = 0; i < children.Count; i += 2)
{ {
XmlNode key = children[i]; XmlNode key = children[i];
@@ -172,71 +163,71 @@ namespace Claunia.PropertyList
return dict; return dict;
} }
} case "array":
if(n.Name.Equals("array"))
{ {
List<XmlNode> children = FilterElementNodes(n.ChildNodes); List<XmlNode> children = FilterElementNodes(n.ChildNodes);
NSArray array = new NSArray(children.Count); 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; return array;
} }
case "true": return new NSNumber(true);
if(n.Name.Equals("true")) return new NSNumber(true); case "false": return new NSNumber(false);
if(n.Name.Equals("false")) return new NSNumber(false); case "integer": return new NSNumber(GetNodeTextContents(n), NSNumber.INTEGER);
if(n.Name.Equals("integer")) return new NSNumber(GetNodeTextContents(n), NSNumber.INTEGER); case "real": return new NSNumber(GetNodeTextContents(n), NSNumber.REAL);
if(n.Name.Equals("real")) return new NSNumber(GetNodeTextContents(n), NSNumber.REAL); case "string": return new NSString(GetNodeTextContents(n));
if(n.Name.Equals("string")) return new NSString(GetNodeTextContents(n)); case "data": return new NSData(GetNodeTextContents(n));
if(n.Name.Equals("data")) return new NSData(GetNodeTextContents(n)); default: return n.Name.Equals("date") ? new NSDate(GetNodeTextContents(n)) : null;
}
return n.Name.Equals("date") ? new NSDate(GetNodeTextContents(n)) : null;
} }
/// <summary> /// <summary>Returns all element nodes that are contained in a list of nodes.</summary>
/// Returns all element nodes that are contained in a list of nodes.
/// </summary>
/// <returns>The sublist containing only nodes representing actual elements.</returns> /// <returns>The sublist containing only nodes representing actual elements.</returns>
/// <param name="list">The list of nodes to search.</param> /// <param name="list">The list of nodes to search.</param>
static List<XmlNode> FilterElementNodes(XmlNodeList list) static List<XmlNode> FilterElementNodes(XmlNodeList list)
{ {
List<XmlNode> result = new List<XmlNode>(); List<XmlNode> result = new();
foreach(XmlNode child in list) foreach(XmlNode child in list)
if(child.NodeType == XmlNodeType.Element) if(child.NodeType == XmlNodeType.Element)
result.Add(child); result.Add(child);
return result; return result;
} }
/// <summary> /// <summary>
/// Returns a node's text content. /// Returns a node's text content. This method will return the text value represented by the node's direct
/// This method will return the text value represented by the node's direct children. /// children. If the given node is a TEXT or CDATA node, then its value is returned.
/// If the given node is a TEXT or CDATA node, then its value is returned.
/// </summary> /// </summary>
/// <returns>The node's text content.</returns> /// <returns>The node's text content.</returns>
/// <param name="n">The node.</param> /// <param name="n">The node.</param>
static string GetNodeTextContents(XmlNode n) static string GetNodeTextContents(XmlNode n)
{ {
if(n.NodeType == XmlNodeType.Text || n.NodeType == XmlNodeType.CDATA) if(n.NodeType is XmlNodeType.Text or XmlNodeType.CDATA)
{ {
string content = n.Value; //This concatenates any adjacent text/cdata/entity nodes string content = n.Value; //This concatenates any adjacent text/cdata/entity nodes
return content ?? ""; return content ?? "";
} }
if(n.HasChildNodes) if(!n.HasChildNodes)
{ return "";
XmlNodeList children = n.ChildNodes; XmlNodeList children = n.ChildNodes;
foreach(XmlNode child in children) foreach(XmlNode child in children)
//Skip any non-text nodes, like comments or entities //Skip any non-text nodes, like comments or entities
if(child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA) if(child.NodeType is XmlNodeType.Text or XmlNodeType.CDATA)
{ {
string content = child.Value; //This concatenates any adjacent text/cdata/entity nodes string content = child.Value; //This concatenates any adjacent text/cdata/entity nodes
return content ?? ""; return content ?? "";
} }
return ""; return "";
} }
return "";
}
} }
} }