General refactor and clean-up.

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

View File

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

View File

@@ -2,9 +2,10 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
namespace Claunia.PropertyList.Benchmark
{
[SimpleJob(RuntimeMoniker.NetCoreApp50), MemoryDiagnoser]
namespace Claunia.PropertyList.Benchmark;
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]
public class BinaryPropertyListParserBenchmarks
{
byte[] data;
@@ -20,4 +21,3 @@ namespace Claunia.PropertyList.Benchmark
return nsObject;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,8 +27,8 @@ using System.IO;
using Claunia.PropertyList;
using Xunit;
namespace plistcil.test
{
namespace plistcil.test;
public static class IssueTest
{
/// <summary>
@@ -189,4 +189,3 @@ namespace plistcil.test
Assert.Equal(10d, (double)weight);
}
}
}

View File

@@ -2,8 +2,8 @@
using Claunia.PropertyList;
using Xunit;
namespace plistcil.test
{
namespace plistcil.test;
public class NSArrayTests
{
/// <summary>Tests the addition of a .NET object to the NSArray</summary>
@@ -88,4 +88,3 @@ namespace plistcil.test
Assert.Empty(array);
}
}
}

View File

@@ -2,8 +2,8 @@
using Claunia.PropertyList;
using Xunit;
namespace plistcil.test
{
namespace plistcil.test;
public class NSDateTests
{
[Fact]
@@ -24,4 +24,3 @@ namespace plistcil.test
Assert.Equal(expected, actual);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,8 @@
using Claunia.PropertyList;
using Xunit;
namespace plistcil.test
{
namespace plistcil.test;
public class PropertyListParserTests
{
static void ParseEmptyStreamTestDelegate()
@@ -17,4 +17,3 @@ namespace plistcil.test
public static void ParseEmptyStreamTest() =>
Assert.Throws<PropertyListFormatException>(ParseEmptyStreamTestDelegate);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
namespace Claunia.PropertyList
{
namespace Claunia.PropertyList;
public partial class BinaryPropertyListWriter
{
/// <summary>
@@ -13,25 +13,18 @@ namespace Claunia.PropertyList
{
public override bool Equals(NSObject x, NSObject y)
{
if(x is not NSString a ||
y is not NSString b)
return ReferenceEquals(x, y);
if(x is not NSString a || y is not NSString b) return ReferenceEquals(x, y);
if(!IsSerializationPrimitive(a) ||
!IsSerializationPrimitive(b))
return ReferenceEquals(x, y);
if(!IsSerializationPrimitive(a) || !IsSerializationPrimitive(b)) return ReferenceEquals(x, y);
return string.Equals(a.Content, b.Content, StringComparison.Ordinal);
}
public override int GetHashCode(NSObject obj)
{
if(obj is NSString s &&
IsSerializationPrimitive(s))
return s.Content.GetHashCode();
if(obj is NSString s && IsSerializationPrimitive(s)) return s.Content.GetHashCode();
return obj.GetHashCode();
}
}
}
}

View File

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

View File

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

View File

@@ -22,8 +22,8 @@
using System.Collections;
using System.Collections.Generic;
namespace Claunia.PropertyList
{
namespace Claunia.PropertyList;
partial class NSArray : IList<NSObject>
{
/// <inheritdoc />
@@ -77,4 +77,3 @@ namespace Claunia.PropertyList
public bool Remove(object item) => Remove(Wrap(item));
}
}

View File

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

View File

@@ -27,8 +27,8 @@ using System;
using System.IO;
using System.Text;
namespace Claunia.PropertyList
{
namespace Claunia.PropertyList;
/// <summary>NSData objects are wrappers for byte buffers</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
@@ -111,7 +111,7 @@ namespace Claunia.PropertyList
public override int GetHashCode()
{
int hash = 5;
hash = (67 * hash) + Bytes.GetHashCode();
hash = 67 * hash + Bytes.GetHashCode();
return hash;
}
@@ -124,12 +124,14 @@ namespace Claunia.PropertyList
string base64 = GetBase64EncodedData();
foreach(string line in base64.Split('\n'))
{
for(int offset = 0; offset < base64.Length; offset += DataLineLength)
{
Indent(xml, level);
xml.Append(line.Substring(offset, Math.Min(DataLineLength, line.Length - offset)));
xml.Append(NEWLINE);
}
}
Indent(xml, level);
xml.Append("</data>");
@@ -157,9 +159,7 @@ namespace Claunia.PropertyList
ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length;
}
else if((i + 1) % 2 == 0 &&
i != Bytes.Length - 1)
ascii.Append(" ");
else if((i + 1) % 2 == 0 && i != Bytes.Length - 1) ascii.Append(" ");
}
ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN);
@@ -185,4 +185,3 @@ namespace Claunia.PropertyList
public static explicit operator NSData(byte[] value) => new(value);
}
}

View File

@@ -27,8 +27,8 @@ using System;
using System.Globalization;
using System.Text;
namespace Claunia.PropertyList
{
namespace Claunia.PropertyList;
/// <summary>Represents a date</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
@@ -41,9 +41,9 @@ namespace Claunia.PropertyList
static readonly string sdfDefault = "yyyy-MM-dd'T'HH:mm:ssK";
static readonly string sdfGnuStep = "yyyy-MM-dd HH:mm:ss zzz";
static readonly string[] sdfAll =
{
[
sdfDefault, sdfGnuStep
};
];
static readonly CultureInfo provider = CultureInfo.InvariantCulture;
@@ -162,8 +162,7 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(NSObject obj)
{
if(obj is not NSDate date)
return false;
if(obj is not NSDate date) return false;
int equality = DateTime.Compare(Date, date.Date);
@@ -174,4 +173,3 @@ namespace Claunia.PropertyList
public static explicit operator NSDate(DateTime value) => new(value);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,8 +27,8 @@ using System;
using System.Security;
using System.Text;
namespace Claunia.PropertyList
{
namespace Claunia.PropertyList;
/// <summary>A NSString contains a string.</summary>
/// @author Daniel Dreibrodt
/// @author Natalia Portillo
@@ -68,7 +68,9 @@ namespace Claunia.PropertyList
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param>
public int CompareTo(object o) => o switch
{
NSString nsString => string.Compare(Content, nsString.Content, StringComparison.Ordinal),
NSString nsString => string.Compare(Content,
nsString.Content,
StringComparison.Ordinal),
string s => string.Compare(Content, s, StringComparison.Ordinal),
_ => -1
};
@@ -150,7 +152,8 @@ namespace Claunia.PropertyList
lock(typeof(NSString))
{
// Not much use, because some characters do not fallback to exception, even if not ASCII
asciiEncoder ??= Encoding.GetEncoding("ascii", EncoderFallback.ExceptionFallback,
asciiEncoder ??= Encoding.GetEncoding("ascii",
EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);
if(IsASCIIEncodable(Content))
@@ -201,18 +204,19 @@ namespace Claunia.PropertyList
char[] cArray = s.ToCharArray();
foreach(char c in cArray)
{
if(c > 127)
{
//non-ASCII Unicode
outString += "\\U";
string hex = $"{c:x}";
while(hex.Length < 4)
hex = "0" + hex;
while(hex.Length < 4) hex = "0" + hex;
outString += hex;
}
else
{
outString += c switch
{
'\\' => "\\\\",
@@ -223,6 +227,8 @@ namespace Claunia.PropertyList
'\t' => "\\t",
_ => c
};
}
}
return outString;
}
@@ -241,8 +247,7 @@ namespace Claunia.PropertyList
/// </returns>
public override bool Equals(NSObject obj)
{
if(obj is not NSString nsString)
return false;
if(obj is not NSString nsString) return false;
return Content == nsString.Content;
}
@@ -250,8 +255,7 @@ namespace Claunia.PropertyList
internal static bool IsASCIIEncodable(string text)
{
foreach(char c in text)
if(c > 0x7F)
return false;
if(c > 0x7F) return false;
return true;
}
@@ -260,4 +264,3 @@ namespace Claunia.PropertyList
public static explicit operator NSString(string value) => new(value);
}
}

View File

@@ -27,8 +27,8 @@
using System;
using System.Runtime.Serialization;
namespace Claunia.PropertyList
{
namespace Claunia.PropertyList;
/// <summary>The exception that is thrown when an property list file could not be processed correctly.</summary>
[Serializable]
public class PropertyListException : Exception
@@ -50,4 +50,3 @@ namespace Claunia.PropertyList
protected PropertyListException(SerializationInfo info, StreamingContext context) : base(info, context) {}
}
}

View File

@@ -23,8 +23,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
namespace Claunia.PropertyList
{
namespace Claunia.PropertyList;
/// <summary>
/// A PropertyListFormatException is thrown by the various property list format parsers when an error in the
/// format of the given property list is encountered.
@@ -37,4 +37,3 @@ namespace Claunia.PropertyList
/// <param name="message">A message containing information about the nature of the exception.</param>
public PropertyListFormatException(string message) : base(message) {}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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