Code cleanup and refactor.

This commit is contained in:
2018-07-09 19:57:08 +01:00
parent 9f65fa8d12
commit c109d2c76b
33 changed files with 2789 additions and 3256 deletions

View File

@@ -1,6 +1,6 @@
using BenchmarkDotNet.Attributes; using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Jobs; using BenchmarkDotNet.Attributes.Jobs;
using System.IO;
namespace Claunia.PropertyList.Benchmark namespace Claunia.PropertyList.Benchmark
{ {
@@ -8,7 +8,7 @@ namespace Claunia.PropertyList.Benchmark
[MemoryDiagnoser] [MemoryDiagnoser]
public class BinaryPropertyListParserBenchmarks public class BinaryPropertyListParserBenchmarks
{ {
private byte[] data = null; byte[] data;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
@@ -19,8 +19,8 @@ namespace Claunia.PropertyList.Benchmark
[Benchmark] [Benchmark]
public NSObject ReadLargePropertylistTest() public NSObject ReadLargePropertylistTest()
{ {
var nsObject = PropertyListParser.Parse(this.data); NSObject nsObject = PropertyListParser.Parse(data);
return nsObject; return nsObject;
} }
} }
} }

View File

@@ -7,7 +7,7 @@ namespace Claunia.PropertyList.Benchmark
[MemoryDiagnoser] [MemoryDiagnoser]
public class BinaryPropertyListWriterBenchmarks public class BinaryPropertyListWriterBenchmarks
{ {
private NSObject data = null; NSObject data;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
@@ -15,11 +15,10 @@ namespace Claunia.PropertyList.Benchmark
data = PropertyListParser.Parse("plist.bin"); data = PropertyListParser.Parse("plist.bin");
} }
[Benchmark] [Benchmark]
public byte[] WriteLargePropertylistTest() public byte[] WriteLargePropertylistTest()
{ {
return BinaryPropertyListWriter.WriteToArray(data); return BinaryPropertyListWriter.WriteToArray(data);
} }
} }
} }

View File

@@ -1,5 +1,5 @@
using BenchmarkDotNet.Running; using BenchmarkDotNet.Reports;
using System; using BenchmarkDotNet.Running;
namespace Claunia.PropertyList.Benchmark namespace Claunia.PropertyList.Benchmark
{ {
@@ -7,8 +7,8 @@ namespace Claunia.PropertyList.Benchmark
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
var summary = BenchmarkRunner.Run<BinaryPropertyListParserBenchmarks>(); Summary summary = BenchmarkRunner.Run<BinaryPropertyListParserBenchmarks>();
summary = BenchmarkRunner.Run<BinaryPropertyListWriterBenchmarks>(); summary = BenchmarkRunner.Run<BinaryPropertyListWriterBenchmarks>();
} }
} }
} }

View File

@@ -6,31 +6,31 @@ namespace plistcil.test
public class BinaryPropertyListParserTests public class BinaryPropertyListParserTests
{ {
[Theory] [Theory]
[InlineData(new byte[] { 0x08 }, 0x08)] [InlineData(new byte[] {0x08}, 0x08)]
[InlineData(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07 }, 7)] [InlineData(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, 7)]
[InlineData(new byte[] { 0x00, 0x0e, 0x47, 0x7b }, 0x00000000000e477b)] [InlineData(new byte[] {0x00, 0x0e, 0x47, 0x7b}, 0x00000000000e477b)]
public void ParseUnsignedIntTest(byte[] binaryValue, int expectedValue) public void ParseUnsignedIntTest(byte[] binaryValue, int expectedValue)
{ {
Assert.Equal(expectedValue, BinaryPropertyListParser.ParseUnsignedInt(binaryValue)); Assert.Equal(expectedValue, BinaryPropertyListParser.ParseUnsignedInt(binaryValue));
} }
[Theory] [Theory]
[InlineData(new byte[] { 0x57 }, 0x57)] [InlineData(new byte[] {0x57}, 0x57)]
[InlineData(new byte[] { 0x40, 0x2d, 0xf8, 0x4d }, 0x402df84d)] [InlineData(new byte[] {0x40, 0x2d, 0xf8, 0x4d}, 0x402df84d)]
[InlineData(new byte[] { 0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00 }, 0x41b483982a000000)] [InlineData(new byte[] {0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00}, 0x41b483982a000000)]
[InlineData(new byte[] { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19 }, unchecked((long)0xfffffffffffffc19))] [InlineData(new byte[] {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x19}, unchecked((long)0xfffffffffffffc19))]
public void ParseLongTest(byte[] binaryValue, long expectedValue) public void ParseLongTest(byte[] binaryValue, long expectedValue)
{ {
Assert.Equal(expectedValue, BinaryPropertyListParser.ParseLong(binaryValue)); Assert.Equal(expectedValue, BinaryPropertyListParser.ParseLong(binaryValue));
} }
[Theory] [Theory]
[InlineData(new byte[] { 0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00 }, 344168490)] [InlineData(new byte[] {0x41, 0xb4, 0x83, 0x98, 0x2a, 0x00, 0x00, 0x00}, 344168490)]
[InlineData(new byte[] { 0x40, 0x09, 0x21, 0xf9, 0xf0, 0x1b, 0x86, 0x6e }, 3.14159)] [InlineData(new byte[] {0x40, 0x09, 0x21, 0xf9, 0xf0, 0x1b, 0x86, 0x6e}, 3.14159)]
[InlineData(new byte[] { 0x40, 0x2d, 0xf8, 0x4d }, 2.71828007698059)] [InlineData(new byte[] {0x40, 0x2d, 0xf8, 0x4d}, 2.71828007698059)]
public void ParseDoubleTest(byte[] binaryValue, double expectedValue) public void ParseDoubleTest(byte[] binaryValue, double expectedValue)
{ {
Assert.Equal(expectedValue, BinaryPropertyListParser.ParseDouble(binaryValue), 14); Assert.Equal(expectedValue, BinaryPropertyListParser.ParseDouble(binaryValue), 14);
} }
} }
} }

View File

@@ -1,73 +1,73 @@
using Claunia.PropertyList; using System.IO;
using System.IO; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test
{ {
public class BinaryPropertyListWriterTests public class BinaryPropertyListWriterTests
{ {
[Fact]
public void RoundtripTest()
{
byte[] data = File.ReadAllBytes("test-files/plist.bin");
NSObject root = PropertyListParser.Parse(data);
using (MemoryStream actualOutput = new MemoryStream())
using (Stream expectedOutput = File.OpenRead("test-files/plist.bin"))
using (ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput))
{
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream);
writer.ReuseObjectIds = false;
writer.Write(root);
}
}
[Fact] [Fact]
public void Roundtrip2Test() public void Roundtrip2Test()
{ {
byte[] data = File.ReadAllBytes("test-files/plist2.bin"); byte[] data = File.ReadAllBytes("test-files/plist2.bin");
NSObject root = PropertyListParser.Parse(data); NSObject root = PropertyListParser.Parse(data);
using (MemoryStream actualOutput = new MemoryStream()) using(MemoryStream actualOutput = new MemoryStream())
using (Stream expectedOutput = File.OpenRead("test-files/plist2.bin")) using(Stream expectedOutput = File.OpenRead("test-files/plist2.bin"))
using (ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput)) using(ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput))
{ {
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream); BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream);
writer.ReuseObjectIds = false; writer.ReuseObjectIds = false;
writer.Write(root); writer.Write(root);
} }
} }
[Fact] [Fact]
public void Roundtrip3Test() public void Roundtrip3Test()
{ {
byte[] data = File.ReadAllBytes("test-files/plist3.bin"); byte[] data = File.ReadAllBytes("test-files/plist3.bin");
NSObject root = PropertyListParser.Parse(data); NSObject root = PropertyListParser.Parse(data);
using (MemoryStream actualOutput = new MemoryStream()) using(MemoryStream actualOutput = new MemoryStream())
using (Stream expectedOutput = File.OpenRead("test-files/plist3.bin")) using(Stream expectedOutput = File.OpenRead("test-files/plist3.bin"))
using (ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput)) using(ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput))
{ {
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream); BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream);
writer.ReuseObjectIds = false; writer.ReuseObjectIds = false;
writer.Write(root); writer.Write(root);
} }
} }
[Fact] [Fact]
public void Roundtrip4Test() public void Roundtrip4Test()
{ {
byte[] data = File.ReadAllBytes("test-files/plist4.bin"); byte[] data = File.ReadAllBytes("test-files/plist4.bin");
NSObject root = PropertyListParser.Parse(data); NSObject root = PropertyListParser.Parse(data);
using (MemoryStream actualOutput = new MemoryStream()) using(MemoryStream actualOutput = new MemoryStream())
using (Stream expectedOutput = File.OpenRead("test-files/plist4.bin")) using(Stream expectedOutput = File.OpenRead("test-files/plist4.bin"))
using (ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput)) using(ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput))
{ {
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream); BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream);
writer.ReuseObjectIds = false; writer.ReuseObjectIds = false;
writer.Write(root); writer.Write(root);
} }
}
[Fact]
public void RoundtripTest()
{
byte[] data = File.ReadAllBytes("test-files/plist.bin");
NSObject root = PropertyListParser.Parse(data);
using(MemoryStream actualOutput = new MemoryStream())
using(Stream expectedOutput = File.OpenRead("test-files/plist.bin"))
using(ValidatingStream validatingStream = new ValidatingStream(actualOutput, expectedOutput))
{
BinaryPropertyListWriter writer = new BinaryPropertyListWriter(validatingStream);
writer.ReuseObjectIds = false;
writer.Write(root);
}
} }
} }
} }

View File

@@ -22,31 +22,62 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System;
using System.IO; using System.IO;
using Xunit;
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit;
namespace plistcil.test namespace plistcil.test
{ {
public static class IssueTest public static class IssueTest
{ {
/// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario
/// where the binary data is not indented (no leading whitespace).
/// </summary>
[Fact] [Fact]
public static void TestIssue4() public static void RoundtripDataTest()
{ {
NSDictionary d = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue4.plist")); string expected = File.ReadAllText(@"test-files/RoundtripBinary.plist");
Assert.Equal("Kid\u2019s iPhone", ((NSString)d.ObjectForKey("Device Name")).ToString()); NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinary.plist"));
string actual = value.ToXmlPropertyList();
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
}
/// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario
/// where the binary data is indented.
/// </summary>
[Fact]
public static void RoundtripDataTest2()
{
string expected = File.ReadAllText(@"test-files/RoundtripBinaryIndentation.plist");
NSObject value =
XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinaryIndentation.plist"));
string actual = value.ToXmlPropertyList();
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
} }
[Fact] [Fact]
public static void TestIssue7() public static void RoundtripRealTest()
{ {
// also a test for issue 12 string expected = File.ReadAllText(@"test-files/RoundtripReal.plist");
// the issue4 test has a UTF-16-BE string in its binary representation NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripReal.plist"));
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/issue4.plist")); string actual = value.ToXmlPropertyList();
PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testIssue7.plist"));
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testIssue7.plist")); Assert.Equal(expected, actual, false, true);
Assert.True(x.Equals(y)); }
[Fact]
public static void RoundtripTest()
{
string expected = File.ReadAllText(@"test-files/Roundtrip.plist");
NSObject value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/Roundtrip.plist"));
string actual = value.ToXmlPropertyList();
Assert.Equal(expected, actual, false, true);
} }
[Fact] [Fact]
@@ -68,26 +99,28 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestIssue21() public static void TestIssue21()
{ {
String x = ((NSString)PropertyListParser.Parse(new FileInfo("test-files/issue21.plist"))).ToString(); string x = ((NSString)PropertyListParser.Parse(new FileInfo("test-files/issue21.plist"))).ToString();
Assert.Equal("Lot&s of &persand&s and other escapable \"\'<>€ characters", x); Assert.Equal("Lot&s of &persand&s and other escapable \"\'<>€ characters", x);
} }
[Fact] [Fact]
public static void TestIssue22() public static void TestIssue22()
{ {
NSDictionary x1 = ((NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji.plist"))); NSDictionary x1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji.plist"));
NSDictionary x2 = ((NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji-xml.plist"))); NSDictionary x2 =
(NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue22-emoji-xml.plist"));
PropertyListParser.SaveAsBinary(x1, new FileInfo("test-files/out-testIssue22.plist")); PropertyListParser.SaveAsBinary(x1, new FileInfo("test-files/out-testIssue22.plist"));
NSDictionary y1 = ((NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22.plist"))); NSDictionary y1 = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22.plist"));
PropertyListParser.SaveAsXml(x2, new FileInfo("test-files/out-testIssue22-xml.plist")); PropertyListParser.SaveAsXml(x2, new FileInfo("test-files/out-testIssue22-xml.plist"));
NSDictionary y2 = ((NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22-xml.plist"))); NSDictionary y2 =
(NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/out-testIssue22-xml.plist"));
Assert.True(x1.Equals(x2)); Assert.True(x1.Equals(x2));
Assert.True(x1.Equals(y1)); Assert.True(x1.Equals(y1));
Assert.True(x1.Equals(y2)); Assert.True(x1.Equals(y2));
Assert.True(x2.Equals(y1)); Assert.True(x2.Equals(y1));
Assert.True(x2.Equals(y2)); Assert.True(x2.Equals(y2));
String emojiString = "Test Test, \uD83D\uDE30\u2754\uD83D\uDC4D\uD83D\uDC4E\uD83D\uDD25"; string emojiString = "Test Test, \uD83D\uDE30\u2754\uD83D\uDC4D\uD83D\uDC4E\uD83D\uDD25";
Assert.Equal(emojiString, x1.ObjectForKey("emojiString").ToString()); Assert.Equal(emojiString, x1.ObjectForKey("emojiString").ToString());
Assert.Equal(emojiString, x2.ObjectForKey("emojiString").ToString()); Assert.Equal(emojiString, x2.ObjectForKey("emojiString").ToString());
@@ -115,10 +148,18 @@ namespace plistcil.test
public static void TestIssue38() public static void TestIssue38()
{ {
NSDictionary dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj")); NSDictionary dict = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue33.pbxproj"));
NSObject fileRef = ((NSDictionary)((NSDictionary)dict.Get("objects")).Get("65541A9C16D13B8C00A968D5")).Get("fileRef"); NSObject fileRef =
((NSDictionary)((NSDictionary)dict.Get("objects")).Get("65541A9C16D13B8C00A968D5")).Get("fileRef");
Assert.True(fileRef.Equals(new NSString("65541A9B16D13B8C00A968D5"))); Assert.True(fileRef.Equals(new NSString("65541A9B16D13B8C00A968D5")));
} }
[Fact]
public static void TestIssue4()
{
NSDictionary d = (NSDictionary)PropertyListParser.Parse(new FileInfo("test-files/issue4.plist"));
Assert.Equal("Kid\u2019s iPhone", ((NSString)d.ObjectForKey("Device Name")).ToString());
}
[Fact] [Fact]
public static void TestIssue49() public static void TestIssue49()
{ {
@@ -126,65 +167,28 @@ namespace plistcil.test
Assert.Empty(dict); Assert.Empty(dict);
} }
[Fact]
public static void TestIssue7()
{
// also a test for issue 12
// the issue4 test has a UTF-16-BE string in its binary representation
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/issue4.plist"));
PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testIssue7.plist"));
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testIssue7.plist"));
Assert.True(x.Equals(y));
}
[Fact] [Fact]
public static void TestRealInResourceRule() public static void TestRealInResourceRule()
{ {
NSDictionary dict = (NSDictionary)XmlPropertyListParser.Parse(new FileInfo("test-files/ResourceRules.plist")); NSDictionary dict =
(NSDictionary)XmlPropertyListParser.Parse(new FileInfo("test-files/ResourceRules.plist"));
Assert.Single(dict); Assert.Single(dict);
Assert.True(dict.ContainsKey("weight")); Assert.True(dict.ContainsKey("weight"));
var weight = dict["weight"].ToObject(); object weight = dict["weight"].ToObject();
Assert.IsType<double>(weight); Assert.IsType<double>(weight);
Assert.Equal(10d, (double)weight); Assert.Equal(10d, (double)weight);
} }
[Fact]
public static void RoundtripTest()
{
var expected = File.ReadAllText(@"test-files/Roundtrip.plist");
var value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/Roundtrip.plist"));
var actual = value.ToXmlPropertyList();
Assert.Equal(expected, actual, false, true);
}
[Fact]
public static void RoundtripRealTest()
{
var expected = File.ReadAllText(@"test-files/RoundtripReal.plist");
var value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripReal.plist"));
var actual = value.ToXmlPropertyList();
Assert.Equal(expected, actual, false, true);
}
/// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario
/// where the binary data is not indented (no leading whitespace).
/// </summary>
[Fact]
public static void RoundtripDataTest()
{
var expected = File.ReadAllText(@"test-files/RoundtripBinary.plist");
var value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinary.plist"));
var actual = value.ToXmlPropertyList();
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
}
/// <summary>
/// Makes sure that binary data is line-wrapped correctly when being serialized, in a scenario
/// where the binary data is indented.
/// </summary>
[Fact]
public static void RoundtripDataTest2()
{
var expected = File.ReadAllText(@"test-files/RoundtripBinaryIndentation.plist");
var value = XmlPropertyListParser.Parse(new FileInfo(@"test-files/RoundtripBinaryIndentation.plist"));
var actual = value.ToXmlPropertyList();
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
}
} }
} }

View File

@@ -1,17 +1,13 @@
using Claunia.PropertyList; using System.Collections.Generic;
using Claunia.PropertyList;
using Xunit; using Xunit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace plistcil.test namespace plistcil.test
{ {
public class NSArrayTests public class NSArrayTests
{ {
/// <summary> /// <summary>
/// Tests the addition of a .NET object to the NSArray /// Tests the addition of a .NET object to the NSArray
/// </summary> /// </summary>
[Fact] [Fact]
public void AddAndContainsObjectTest() public void AddAndContainsObjectTest()
@@ -24,7 +20,30 @@ namespace plistcil.test
} }
/// <summary> /// <summary>
/// Tests the <see cref="NSArray.IndexOf(object)"/> method for .NET objects. /// Tests the <see cref="NSArray.GetEnumerator" /> method.
/// </summary>
[Fact]
public void EnumeratorTest()
{
NSArray array = new NSArray();
array.Add(0);
array.Add(1);
IEnumerator<NSObject> enumerator = array.GetEnumerator();
Assert.Null(enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Equal(new NSNumber(0), enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Equal(new NSNumber(1), enumerator.Current);
Assert.False(enumerator.MoveNext());
}
/// <summary>
/// Tests the <see cref="NSArray.IndexOf(object)" /> method for .NET objects.
/// </summary> /// </summary>
[Fact] [Fact]
public void IndexOfTest() public void IndexOfTest()
@@ -38,8 +57,8 @@ namespace plistcil.test
} }
/// <summary> /// <summary>
/// Tests the <see cref="NSArray.Insert(int, object)"/> method for a /// Tests the <see cref="NSArray.Insert(int, object)" /> method for a
/// .NET object. /// .NET object.
/// </summary> /// </summary>
[Fact] [Fact]
public void InsertTest() public void InsertTest()
@@ -51,12 +70,12 @@ namespace plistcil.test
array.Insert(1, "test"); array.Insert(1, "test");
Assert.Equal(4, array.Count); Assert.Equal(4, array.Count);
Assert.Equal("test", array[1].ToObject()); Assert.Equal("test", array[1].ToObject());
} }
/// <summary> /// <summary>
/// Tests the <see cref="NSArray.Remove(object)"/> method for a .NET object. /// Tests the <see cref="NSArray.Remove(object)" /> method for a .NET object.
/// </summary> /// </summary>
[Fact] [Fact]
public void RemoveTest() public void RemoveTest()
@@ -68,28 +87,5 @@ namespace plistcil.test
Assert.Empty(array); Assert.Empty(array);
} }
/// <summary>
/// Tests the <see cref="NSArray.GetEnumerator"/> method.
/// </summary>
[Fact]
public void EnumeratorTest()
{
NSArray array = new NSArray();
array.Add(0);
array.Add(1);
var enumerator = array.GetEnumerator();
Assert.Null(enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Equal(new NSNumber(0), enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Equal(new NSNumber(1), enumerator.Current);
Assert.False(enumerator.MoveNext());
}
} }
} }

View File

@@ -1,10 +1,6 @@
using Claunia.PropertyList; using System;
using Claunia.PropertyList;
using Xunit; using Xunit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace plistcil.test namespace plistcil.test
{ {
@@ -13,19 +9,19 @@ namespace plistcil.test
[Fact] [Fact]
public static void ConstructorTest() public static void ConstructorTest()
{ {
var actual = new NSDate("2000-01-01T00:00:00Z"); NSDate actual = new NSDate("2000-01-01T00:00:00Z");
var expected = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); DateTime expected = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
Assert.Equal(expected, actual.Date.ToUniversalTime()); Assert.Equal(expected, actual.Date.ToUniversalTime());
} }
[Fact] [Fact]
public static void MakeDateStringTest() public static void MakeDateStringTest()
{ {
var date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); DateTime date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var expected = "2000-01-01T00:00:00Z"; string expected = "2000-01-01T00:00:00Z";
var actual = NSDate.MakeDateString(date); string actual = NSDate.MakeDateString(date);
Assert.Equal(expected, actual); Assert.Equal(expected, actual);
} }
} }
} }

View File

@@ -1,12 +1,5 @@
using Claunia.PropertyList; using Claunia.PropertyList;
using Xunit; using Xunit;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace plistcil.test namespace plistcil.test
{ {
@@ -15,15 +8,15 @@ namespace plistcil.test
[Fact] [Fact]
public static void NSNumberConstructorTest() public static void NSNumberConstructorTest()
{ {
var number = new NSNumber("10032936613", NSNumber.INTEGER); NSNumber number = new NSNumber("10032936613", NSNumber.INTEGER);
Assert.Equal(NSNumber.INTEGER, number.GetNSNumberType()); Assert.Equal(NSNumber.INTEGER, number.GetNSNumberType());
Assert.Equal(10032936613, number.ToObject()); Assert.Equal(10032936613, number.ToObject());
} }
[Fact] [Fact]
public static void NSNumberWithDecimalTest() public static void NSNumberWithDecimalTest()
{ {
var number = new NSNumber("1360155352.748765", NSNumber.REAL); NSNumber number = new NSNumber("1360155352.748765", NSNumber.REAL);
Assert.Equal("1360155352.748765", number.ToString()); Assert.Equal("1360155352.748765", number.ToString());
} }
@@ -33,12 +26,12 @@ namespace plistcil.test
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
#if !NETCORE #if !NETCORE
[Fact] [Fact]
[UseCulture("en-US")] [UseCulture("en-US")]
public static void ParseNumberEnTest() public static void ParseNumberEnTest()
{ {
var number = new NSNumber("7200.000001"); NSNumber number = new NSNumber("7200.000001");
Assert.True(number.isReal()); Assert.True(number.isReal());
Assert.Equal(7200.000001d, number.ToDouble()); Assert.Equal(7200.000001d, number.ToDouble());
} }
@@ -50,7 +43,7 @@ namespace plistcil.test
// As seen in a real property list: // As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
var number = new NSNumber("7200.000001"); NSNumber number = new NSNumber("7200.000001");
Assert.True(number.isReal()); Assert.True(number.isReal());
Assert.Equal(7200.000001d, number.ToDouble()); Assert.Equal(7200.000001d, number.ToDouble());
} }
@@ -62,7 +55,7 @@ namespace plistcil.test
// As seen in a real property list: // As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
var number = new NSNumber("7200.000000", NSNumber.REAL); NSNumber number = new NSNumber("7200.000000", NSNumber.REAL);
Assert.True(number.isReal()); Assert.True(number.isReal());
Assert.Equal(7200d, number.ToDouble()); Assert.Equal(7200d, number.ToDouble());
} }
@@ -74,10 +67,10 @@ namespace plistcil.test
// As seen in a real property list: // As seen in a real property list:
// <key>TimeZoneOffsetFromUTC</key> // <key>TimeZoneOffsetFromUTC</key>
// <real>7200.000000</real> // <real>7200.000000</real>
var number = new NSNumber("7200.000000", NSNumber.REAL); NSNumber number = new NSNumber("7200.000000", NSNumber.REAL);
Assert.True(number.isReal()); Assert.True(number.isReal());
Assert.Equal(7200d, number.ToDouble()); Assert.Equal(7200d, number.ToDouble());
} }
#endif #endif
} }
} }

View File

@@ -22,59 +22,29 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.IO;
using Xunit;
using Claunia.PropertyList;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Claunia.PropertyList;
using Xunit;
namespace plistcil.test namespace plistcil.test
{ {
public static class ParseTest public static class ParseTest
{ {
/** static bool ArrayEquals(byte[] arrayA, byte[] arrayB)
* Test the xml reader/writer
*/
[Fact]
public static void TestXml()
{ {
// Parse an example plist file if(arrayA.Length == arrayB.Length)
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist")); {
for(int i = 0; i < arrayA.Length; i++)
if(arrayA[i] != arrayB[i])
return false;
// check the data in it return true;
NSDictionary d = (NSDictionary)x; }
Assert.True(d.Count == 5);
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 10, 21, 30, DateTimeKind.Utc)) ||
((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30, DateTimeKind.Utc)));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
new byte[]{ 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, (byte)0x82 }));
NSArray a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4);
Assert.True(a[0].Equals(new NSNumber(true)));
Assert.True(a[1].Equals(new NSNumber(false)));
Assert.True(a[2].Equals(new NSNumber(87)));
Assert.True(a[3].Equals(new NSNumber(3.14159)));
// read/write it, make sure we get the same thing return false;
PropertyListParser.SaveAsXml(x, new FileInfo("test-files/out-testXml.plist"));
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testXml.plist"));
Assert.True(x.Equals(y));
}
/**
* Test the binary reader/writer.
*/
[Fact]
public static void TestBinary()
{
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist"));
// save and load as binary
PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testBinary.plist"));
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testBinary.plist"));
Assert.True(x.Equals(y));
} }
/** /**
@@ -107,18 +77,18 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestASCII() public static void TestASCII()
{ {
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii.plist")); NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii.plist"));
NSDictionary d = (NSDictionary)x; NSDictionary d = (NSDictionary)x;
Assert.True(d.Count == 5); Assert.True(d.Count == 5);
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
var actualDate = (NSDate)d.ObjectForKey("date"); NSDate actualDate = (NSDate)d.ObjectForKey("date");
var expectedDate = new DateTime(2011, 11, 28, 9, 21, 30, DateTimeKind.Utc).ToLocalTime(); DateTime expectedDate = new DateTime(2011, 11, 28, 9, 21, 30, DateTimeKind.Utc).ToLocalTime();
Assert.Equal(actualDate.Date, expectedDate); Assert.Equal(actualDate.Date, expectedDate);
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes, Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
new byte[]{ 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, (byte)0x82 })); new byte[] {0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82}));
NSArray a = (NSArray)d.ObjectForKey("array"); NSArray a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4); Assert.True(a.Count == 4);
Assert.True(a[0].Equals(new NSString("YES"))); Assert.True(a[0].Equals(new NSString("YES")));
@@ -128,31 +98,22 @@ namespace plistcil.test
} }
[Fact] [Fact]
public static void TestGnuStepASCII() public static void testAsciiUtf8CharactersInQuotedString()
{ {
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii-gnustep.plist")); NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test-ascii-utf8.plist"));
NSDictionary d = (NSDictionary)x; NSDictionary d = (NSDictionary)x;
Assert.True(d.Count == 5); Assert.Equal(2, d.Count);
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString()); Assert.Equal("JÔÖú@2x.jpg", d.ObjectForKey("path").ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString()); Assert.Equal("QÔÖú@2x 啕.jpg", d.ObjectForKey("Key QÔÖª@2x 䌡").ToString());
Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30, DateTimeKind.Utc).ToLocalTime()));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
new byte[]{ 0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, (byte)0x82 }));
NSArray a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4);
Assert.True(a[0].Equals(new NSNumber(true)));
Assert.True(a[1].Equals(new NSNumber(false)));
Assert.True(a[2].Equals(new NSNumber(87)));
Assert.True(a[3].Equals(new NSNumber(3.14159)));
} }
[Fact] [Fact]
public static void TestASCIIWriting() public static void TestASCIIWriting()
{ {
FileInfo inf = new FileInfo("test-files/test1.plist"); FileInfo inf = new FileInfo("test-files/test1.plist");
FileInfo outf = new FileInfo("test-files/out-test1-ascii.plist"); FileInfo outf = new FileInfo("test-files/out-test1-ascii.plist");
FileInfo in2 = new FileInfo("test-files/test1-ascii.plist"); FileInfo in2 = new FileInfo("test-files/test1-ascii.plist");
NSDictionary x = (NSDictionary)PropertyListParser.Parse(inf); NSDictionary x = (NSDictionary)PropertyListParser.Parse(inf);
PropertyListParser.SaveAsASCII(x, outf); PropertyListParser.SaveAsASCII(x, outf);
//Information gets lost when saving into the ASCII format (NSNumbers are converted to NSStrings) //Information gets lost when saving into the ASCII format (NSNumbers are converted to NSStrings)
@@ -162,12 +123,46 @@ namespace plistcil.test
Assert.True(y.Equals(z)); Assert.True(y.Equals(z));
} }
/**
* Test the binary reader/writer.
*/
[Fact]
public static void TestBinary()
{
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist"));
// save and load as binary
PropertyListParser.SaveAsBinary(x, new FileInfo("test-files/out-testBinary.plist"));
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testBinary.plist"));
Assert.True(x.Equals(y));
}
[Fact]
public static void TestGnuStepASCII()
{
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1-ascii-gnustep.plist"));
NSDictionary d = (NSDictionary)x;
Assert.True(d.Count == 5);
Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30,
DateTimeKind.Utc).ToLocalTime()));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
new byte[] {0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82}));
NSArray a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4);
Assert.True(a[0].Equals(new NSNumber(true)));
Assert.True(a[1].Equals(new NSNumber(false)));
Assert.True(a[2].Equals(new NSNumber(87)));
Assert.True(a[3].Equals(new NSNumber(3.14159)));
}
[Fact] [Fact]
public static void TestGnuStepASCIIWriting() public static void TestGnuStepASCIIWriting()
{ {
FileInfo inf = new FileInfo("test-files/test1.plist"); FileInfo inf = new FileInfo("test-files/test1.plist");
FileInfo outf = new FileInfo("test-files/out-test1-ascii-gnustep.plist"); FileInfo outf = new FileInfo("test-files/out-test1-ascii-gnustep.plist");
NSDictionary x = (NSDictionary)PropertyListParser.Parse(inf); NSDictionary x = (NSDictionary)PropertyListParser.Parse(inf);
PropertyListParser.SaveAsGnuStepASCII(x, outf); PropertyListParser.SaveAsGnuStepASCII(x, outf);
NSObject y = PropertyListParser.Parse(outf); NSObject y = PropertyListParser.Parse(outf);
Assert.True(x.Equals(y)); Assert.True(x.Equals(y));
@@ -176,86 +171,85 @@ namespace plistcil.test
[Fact] [Fact]
public static void TestWrap() public static void TestWrap()
{ {
bool bl = true; bool bl = true;
byte byt = 24; byte byt = 24;
short shrt = 12; short shrt = 12;
int i = 42; int i = 42;
long lng = 30000000000L; long lng = 30000000000L;
float flt = 124.3f; float flt = 124.3f;
double dbl = 32.0; double dbl = 32.0;
DateTime date = new DateTime(); DateTime date = new DateTime();
string strg = "Hello World"; string strg = "Hello World";
byte[] bytes = new byte[] { (byte)0x00, (byte)0xAF, (byte)0xAF }; byte[] bytes = {0x00, 0xAF, 0xAF};
Object[] array = new Object[] { bl, byt, shrt, i, lng, flt, dbl, date, strg, bytes }; object[] array = {bl, byt, shrt, i, lng, flt, dbl, date, strg, bytes};
int[] array2 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3000 }; int[] array2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3000};
List<Object> list = new List<Object>(array); List<object> list = new List<object>(array);
Dictionary<string, Object> map = new Dictionary<string, Object>(); Dictionary<string, object> map = new Dictionary<string, object>();
map.Add("int", i); map.Add("int", i);
map.Add("long", lng); map.Add("long", lng);
map.Add("date", date); map.Add("date", date);
NSObject WrappedO = NSObject.Wrap((Object)bl); NSObject WrappedO = NSObject.Wrap((object)bl);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO.GetType().Equals(typeof(NSNumber)));
Assert.True(WrappedO.ToObject().Equals(bl)); Assert.True(WrappedO.ToObject().Equals(bl));
WrappedO = NSObject.Wrap((Object)byt); WrappedO = NSObject.Wrap((object)byt);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO.GetType().Equals(typeof(NSNumber)));
Assert.True((int)WrappedO.ToObject() == byt); Assert.True((int)WrappedO.ToObject() == byt);
WrappedO = NSObject.Wrap((Object)shrt); WrappedO = NSObject.Wrap((object)shrt);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO.GetType().Equals(typeof(NSNumber)));
Assert.True((int)WrappedO.ToObject() == shrt); Assert.True((int)WrappedO.ToObject() == shrt);
WrappedO = NSObject.Wrap((Object)i); WrappedO = NSObject.Wrap((object)i);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO.GetType().Equals(typeof(NSNumber)));
Assert.True((int)WrappedO.ToObject() == i); Assert.True((int)WrappedO.ToObject() == i);
WrappedO = NSObject.Wrap((Object)lng); WrappedO = NSObject.Wrap((object)lng);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO.GetType().Equals(typeof(NSNumber)));
Assert.True((long)WrappedO.ToObject() == lng); Assert.True((long)WrappedO.ToObject() == lng);
WrappedO = NSObject.Wrap((Object)flt); WrappedO = NSObject.Wrap((object)flt);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO.GetType().Equals(typeof(NSNumber)));
Assert.True((double)WrappedO.ToObject() == flt); Assert.True((double)WrappedO.ToObject() == flt);
WrappedO = NSObject.Wrap((Object)dbl); WrappedO = NSObject.Wrap((object)dbl);
Assert.True(WrappedO.GetType().Equals(typeof(NSNumber))); Assert.True(WrappedO.GetType().Equals(typeof(NSNumber)));
Assert.True((double)WrappedO.ToObject() == dbl); Assert.True((double)WrappedO.ToObject() == dbl);
WrappedO = NSObject.Wrap((Object)date); WrappedO = NSObject.Wrap(date);
Assert.True(WrappedO.GetType().Equals(typeof(NSDate))); Assert.True(WrappedO.GetType().Equals(typeof(NSDate)));
Assert.True(((DateTime)WrappedO.ToObject()).Equals(date)); Assert.True(((DateTime)WrappedO.ToObject()).Equals(date));
WrappedO = NSObject.Wrap((Object)strg); WrappedO = NSObject.Wrap(strg);
Assert.True(WrappedO.GetType().Equals(typeof(NSString))); Assert.True(WrappedO.GetType().Equals(typeof(NSString)));
Assert.Equal(((string)WrappedO.ToObject()), strg); Assert.Equal((string)WrappedO.ToObject(), strg);
WrappedO = NSObject.Wrap((Object)bytes); WrappedO = NSObject.Wrap((object)bytes);
Assert.True(WrappedO.GetType().Equals(typeof(NSData))); Assert.True(WrappedO.GetType().Equals(typeof(NSData)));
byte[] data = (byte[])WrappedO.ToObject(); byte[] data = (byte[])WrappedO.ToObject();
Assert.True(data.Length == bytes.Length); Assert.True(data.Length == bytes.Length);
for (int x = 0; x < bytes.Length; x++) for(int x = 0; x < bytes.Length; x++) Assert.True(data[x] == bytes[x]);
Assert.True(data[x] == bytes[x]);
WrappedO = NSObject.Wrap((Object)array); WrappedO = NSObject.Wrap((object)array);
Assert.True(WrappedO.GetType().Equals(typeof(NSArray))); Assert.True(WrappedO.GetType().Equals(typeof(NSArray)));
Object[] objArray = (Object[])WrappedO.ToObject(); object[] objArray = (object[])WrappedO.ToObject();
Assert.True(objArray.Length == array.Length); Assert.True(objArray.Length == array.Length);
WrappedO = NSObject.Wrap((Object)array2); WrappedO = NSObject.Wrap(array2);
Assert.True(WrappedO.GetType().Equals(typeof(NSArray))); Assert.True(WrappedO.GetType().Equals(typeof(NSArray)));
Assert.True(((NSArray)WrappedO).Count == array2.Length); Assert.True(((NSArray)WrappedO).Count == array2.Length);
WrappedO = NSObject.Wrap((Object)list); WrappedO = NSObject.Wrap((object)list);
Assert.True(WrappedO.GetType().Equals(typeof(NSArray))); Assert.True(WrappedO.GetType().Equals(typeof(NSArray)));
objArray = (Object[])WrappedO.ToObject(); objArray = (object[])WrappedO.ToObject();
Assert.True(objArray.Length == array.Length); Assert.True(objArray.Length == array.Length);
WrappedO = NSObject.Wrap((Object)map); WrappedO = NSObject.Wrap((object)map);
Assert.True(WrappedO.GetType().Equals(typeof(NSDictionary))); Assert.True(WrappedO.GetType().Equals(typeof(NSDictionary)));
NSDictionary dict = (NSDictionary)WrappedO; NSDictionary dict = (NSDictionary)WrappedO;
Assert.True(((NSNumber)dict.ObjectForKey("int")).ToLong() == i); Assert.True(((NSNumber)dict.ObjectForKey("int")).ToLong() == i);
Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng); Assert.True(((NSNumber)dict.ObjectForKey("long")).ToLong() == lng);
Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date)); Assert.True(((NSDate)dict.ObjectForKey("date")).Date.Equals(date));
@@ -268,32 +262,37 @@ namespace plistcil.test
Assert.True(((DateTime)map.get("date")).Equals(date));*/ Assert.True(((DateTime)map.get("date")).Equals(date));*/
} }
static bool ArrayEquals(byte[] arrayA, byte[] arrayB) /**
{ * Test the xml reader/writer
if (arrayA.Length == arrayB.Length) */
{
for (int i = 0; i < arrayA.Length; i++)
{
if (arrayA[i] != arrayB[i])
{
return false;
}
}
return true;
}
return false;
}
[Fact] [Fact]
public static void testAsciiUtf8CharactersInQuotedString() public static void TestXml()
{ {
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test-ascii-utf8.plist")); // Parse an example plist file
NSObject x = PropertyListParser.Parse(new FileInfo("test-files/test1.plist"));
// check the data in it
NSDictionary d = (NSDictionary)x; NSDictionary d = (NSDictionary)x;
Assert.Equal(2, d.Count); Assert.True(d.Count == 5);
Assert.Equal("JÔÖú@2x.jpg", d.ObjectForKey("path").ToString()); Assert.Equal("valueA", ((NSString)d.ObjectForKey("keyA")).ToString());
Assert.Equal("QÔÖú@2x 啕.jpg", d.ObjectForKey("Key QÔÖª@2x 䌡").ToString()); Assert.Equal("value&B", ((NSString)d.ObjectForKey("key&B")).ToString());
Assert.True(((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 10, 21, 30,
DateTimeKind.Utc)) ||
((NSDate)d.ObjectForKey("date")).Date.Equals(new DateTime(2011, 11, 28, 9, 21, 30,
DateTimeKind.Utc)));
Assert.True(ArrayEquals(((NSData)d.ObjectForKey("data")).Bytes,
new byte[] {0x00, 0x00, 0x00, 0x04, 0x10, 0x41, 0x08, 0x20, 0x82}));
NSArray a = (NSArray)d.ObjectForKey("array");
Assert.True(a.Count == 4);
Assert.True(a[0].Equals(new NSNumber(true)));
Assert.True(a[1].Equals(new NSNumber(false)));
Assert.True(a[2].Equals(new NSNumber(87)));
Assert.True(a[3].Equals(new NSNumber(3.14159)));
// read/write it, make sure we get the same thing
PropertyListParser.SaveAsXml(x, new FileInfo("test-files/out-testXml.plist"));
NSObject y = PropertyListParser.Parse(new FileInfo("test-files/out-testXml.plist"));
Assert.True(x.Equals(y));
} }
}
} }
}

View File

@@ -1,23 +1,20 @@
using Claunia.PropertyList; using System.IO;
using Claunia.PropertyList;
using Xunit; using Xunit;
using System.IO;
namespace plistcil.test namespace plistcil.test
{ {
public class PropertyListParserTests public class PropertyListParserTests
{ {
static void ParseEmptyStreamTestDelegate()
{
using(MemoryStream stream = new MemoryStream()) PropertyListParser.Parse(stream);
}
[Fact] [Fact]
public static void ParseEmptyStreamTest() public static void ParseEmptyStreamTest()
{ {
Assert.Throws<PropertyListFormatException>(() => ParseEmptyStreamTestDelegate()); Assert.Throws<PropertyListFormatException>(() => ParseEmptyStreamTestDelegate());
} }
static void ParseEmptyStreamTestDelegate()
{
using (MemoryStream stream = new MemoryStream())
{
PropertyListParser.Parse(stream);
}
}
} }
} }

View File

@@ -1,103 +1,103 @@
using Claunia.PropertyList; using System.IO;
using System.IO; using Claunia.PropertyList;
using Xunit; using Xunit;
namespace plistcil.test namespace plistcil.test
{ {
public class UIDTests public class UIDTests
{ {
[Fact]
public void ByteUidTest()
{
var uid = new UID((byte)0xAB);
Assert.Equal(new byte[] { 0xAB }, uid.Bytes);
}
[Fact]
public void SByteUidTest()
{
var uid = new UID("test", unchecked((sbyte)0x0F));
Assert.Equal(new byte[] { 0x0F }, uid.Bytes);
}
[Fact]
public void ShortUidTest()
{
var uid = new UID("test", unchecked((short)0x0F0F));
Assert.Equal(new byte[] { 0x0F, 0x0F }, uid.Bytes);
}
[Fact]
public void UShortUidTest()
{
var uid = new UID(0xABCDu);
Assert.Equal(new byte[] { 0xAB, 0xCD }, uid.Bytes);
}
[Fact]
public void UIntUidTest()
{
var uid = new UID(0xABCDEF00u);
Assert.Equal(new byte[] { 0xAB, 0xCD, 0xEF, 0x00 }, uid.Bytes);
}
[Fact]
public void IntUidTest()
{
var uid = new UID(0xABCDEF00);
Assert.Equal(new byte[] { 0xAB, 0xCD, 0xEF, 0x00 }, uid.Bytes);
}
[Fact]
public void ULongUidTest()
{
var uid = new UID(0xABCDEF0000EFCDABu);
Assert.Equal(new byte[] { 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB }, uid.Bytes);
}
[Fact]
public void LongUidTest()
{
var uid = new UID(0xABCDEF0000EFCDAB);
Assert.Equal(new byte[] { 0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB }, uid.Bytes);
}
[Theory] [Theory]
[InlineData(new byte[] { 0xAB })] [InlineData(new byte[] {0xAB})]
[InlineData(new byte[] { 0xAB, 0xCD })] [InlineData(new byte[] {0xAB, 0xCD})]
[InlineData(new byte[] { 0xAB, 0xCD, 0xEF, 0xFE })] [InlineData(new byte[] {0xAB, 0xCD, 0xEF, 0xFE})]
[InlineData(new byte[] { 0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB })] [InlineData(new byte[] {0xAB, 0xCD, 0xEF, 0xFE, 0xFE, 0xEF, 0xCD, 0xAB})]
public void UidFromArrayTest(byte[] array) public void UidFromArrayTest(byte[] array)
{ {
var uid = new UID(array); UID uid = new UID(array);
Assert.Equal(array, uid.Bytes); Assert.Equal(array, uid.Bytes);
} }
[Fact] [Fact]
public void BinaryRoundTripTest() public void BinaryRoundTripTest()
{ {
var original = new UID(0xabcd); UID original = new UID(0xabcd);
using (MemoryStream stream = new MemoryStream()) using(MemoryStream stream = new MemoryStream())
{ {
BinaryPropertyListWriter.Write(stream, original); BinaryPropertyListWriter.Write(stream, original);
stream.Position = 0; stream.Position = 0;
var roundtrip = BinaryPropertyListParser.Parse(stream) as UID; UID roundtrip = BinaryPropertyListParser.Parse(stream) as UID;
Assert.Equal(original.Bytes, roundtrip.Bytes); Assert.Equal(original.Bytes, roundtrip.Bytes);
} }
} }
[Fact]
public void ByteUidTest()
{
UID uid = new UID(0xAB);
Assert.Equal(new byte[] {0xAB}, uid.Bytes);
}
[Fact]
public void IntUidTest()
{
UID uid = new UID(0xABCDEF00);
Assert.Equal(new byte[] {0xAB, 0xCD, 0xEF, 0x00}, uid.Bytes);
}
[Fact]
public void LongUidTest()
{
UID uid = new UID(0xABCDEF0000EFCDAB);
Assert.Equal(new byte[] {0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB}, uid.Bytes);
}
[Fact]
public void SByteUidTest()
{
UID uid = new UID("test", (sbyte)0x0F);
Assert.Equal(new byte[] {0x0F}, uid.Bytes);
}
[Fact]
public void ShortUidTest()
{
UID uid = new UID("test", (short)0x0F0F);
Assert.Equal(new byte[] {0x0F, 0x0F}, uid.Bytes);
}
[Fact]
public void UIntUidTest()
{
UID uid = new UID(0xABCDEF00u);
Assert.Equal(new byte[] {0xAB, 0xCD, 0xEF, 0x00}, uid.Bytes);
}
[Fact]
public void ULongUidTest()
{
UID uid = new UID(0xABCDEF0000EFCDABu);
Assert.Equal(new byte[] {0xAB, 0xCD, 0xEF, 0x00, 0x00, 0xEF, 0xCD, 0xAB}, uid.Bytes);
}
[Fact]
public void UShortUidTest()
{
UID uid = new UID(0xABCDu);
Assert.Equal(new byte[] {0xAB, 0xCD}, uid.Bytes);
}
[Fact] [Fact]
public void XmlRoundTripTest() public void XmlRoundTripTest()
{ {
var original = new UID(0xabcd); UID original = new UID(0xabcd);
var plist = original.ToXmlPropertyList(); string plist = original.ToXmlPropertyList();
// UIDs don't exist in XML property lists, but they are represented as strings // UIDs don't exist in XML property lists, but they are represented as strings
// for compability purposes // for compability purposes
var roundtrip = XmlPropertyListParser.ParseString(plist) as NSString; NSString roundtrip = XmlPropertyListParser.ParseString(plist) as NSString;
Assert.Equal("abcd", roundtrip.ToObject()); Assert.Equal("abcd", roundtrip.ToObject());
} }
} }
} }

View File

@@ -1,16 +1,15 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using Xunit.Sdk; using Xunit.Sdk;
/// <summary> /// <summary>
/// Apply this attribute to your test method to replace the /// Apply this attribute to your test method to replace the
/// <see cref="Thread.CurrentThread" /> <see cref="CultureInfo.CurrentCulture" /> and /// <see cref="Thread.CurrentThread" /> <see cref="CultureInfo.CurrentCulture" /> and
/// <see cref="CultureInfo.CurrentUICulture" /> with another culture. /// <see cref="CultureInfo.CurrentUICulture" /> with another culture.
/// </summary> /// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class UseCultureAttribute : BeforeAfterTestAttribute public class UseCultureAttribute : BeforeAfterTestAttribute
{ {
readonly Lazy<CultureInfo> culture; readonly Lazy<CultureInfo> culture;
@@ -20,77 +19,76 @@ public class UseCultureAttribute : BeforeAfterTestAttribute
CultureInfo originalUICulture; CultureInfo originalUICulture;
/// <summary> /// <summary>
/// Replaces the culture and UI culture of the current thread with /// Replaces the culture and UI culture of the current thread with
/// <paramref name="culture" /> /// <paramref name="culture" />
/// </summary> /// </summary>
/// <param name="culture">The name of the culture.</param> /// <param name="culture">The name of the culture.</param>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// This constructor overload uses <paramref name="culture" /> for both /// This constructor overload uses <paramref name="culture" /> for both
/// <see cref="Culture" /> and <see cref="UICulture" />. /// <see cref="Culture" /> and <see cref="UICulture" />.
/// </para> /// </para>
/// </remarks> /// </remarks>
public UseCultureAttribute(string culture) public UseCultureAttribute(string culture) : this(culture, culture) { }
: this(culture, culture) { }
/// <summary> /// <summary>
/// Replaces the culture and UI culture of the current thread with /// Replaces the culture and UI culture of the current thread with
/// <paramref name="culture" /> and <paramref name="uiCulture" /> /// <paramref name="culture" /> and <paramref name="uiCulture" />
/// </summary> /// </summary>
/// <param name="culture">The name of the culture.</param> /// <param name="culture">The name of the culture.</param>
/// <param name="uiCulture">The name of the UI culture.</param> /// <param name="uiCulture">The name of the UI culture.</param>
public UseCultureAttribute(string culture, string uiCulture) public UseCultureAttribute(string culture, string uiCulture)
{ {
this.culture = new Lazy<CultureInfo>(() => new CultureInfo(culture)); this.culture = new Lazy<CultureInfo>(() => new CultureInfo(culture));
this.uiCulture = new Lazy<CultureInfo>(() => new CultureInfo(uiCulture)); this.uiCulture = new Lazy<CultureInfo>(() => new CultureInfo(uiCulture));
} }
/// <summary> /// <summary>
/// Gets the culture. /// Gets the culture.
/// </summary> /// </summary>
public CultureInfo Culture { get { return culture.Value; } } public CultureInfo Culture => culture.Value;
/// <summary> /// <summary>
/// Gets the UI culture. /// Gets the UI culture.
/// </summary> /// </summary>
public CultureInfo UICulture { get { return uiCulture.Value; } } public CultureInfo UICulture => uiCulture.Value;
/// <summary> /// <summary>
/// Stores the current <see cref="Thread.CurrentPrincipal" /> /// Stores the current <see cref="Thread.CurrentPrincipal" />
/// <see cref="CultureInfo.CurrentCulture" /> and <see cref="CultureInfo.CurrentUICulture" /> /// <see cref="CultureInfo.CurrentCulture" /> and <see cref="CultureInfo.CurrentUICulture" />
/// and replaces them with the new cultures defined in the constructor. /// and replaces them with the new cultures defined in the constructor.
/// </summary> /// </summary>
/// <param name="methodUnderTest">The method under test</param> /// <param name="methodUnderTest">The method under test</param>
public override void Before(MethodInfo methodUnderTest) public override void Before(MethodInfo methodUnderTest)
{ {
#if NETCORE #if NETCORE
originalCulture = CultureInfo.CurrentCulture; originalCulture = CultureInfo.CurrentCulture;
originalUICulture = CultureInfo.CurrentUICulture; originalUICulture = CultureInfo.CurrentUICulture;
CultureInfo.CurrentCulture = Culture; CultureInfo.CurrentCulture = Culture;
CultureInfo.CurrentUICulture = Culture; CultureInfo.CurrentUICulture = Culture;
#else #else
originalCulture = Thread.CurrentThread.CurrentCulture; originalCulture = Thread.CurrentThread.CurrentCulture;
originalUICulture = Thread.CurrentThread.CurrentUICulture; originalUICulture = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentCulture = Culture; Thread.CurrentThread.CurrentCulture = Culture;
Thread.CurrentThread.CurrentUICulture = UICulture; Thread.CurrentThread.CurrentUICulture = UICulture;
#endif #endif
} }
/// <summary> /// <summary>
/// Restores the original <see cref="CultureInfo.CurrentCulture" /> and /// Restores the original <see cref="CultureInfo.CurrentCulture" /> and
/// <see cref="CultureInfo.CurrentUICulture" /> to <see cref="Thread.CurrentPrincipal" /> /// <see cref="CultureInfo.CurrentUICulture" /> to <see cref="Thread.CurrentPrincipal" />
/// </summary> /// </summary>
/// <param name="methodUnderTest">The method under test</param> /// <param name="methodUnderTest">The method under test</param>
public override void After(MethodInfo methodUnderTest) public override void After(MethodInfo methodUnderTest)
{ {
#if NETCORE #if NETCORE
CultureInfo.CurrentCulture = originalCulture; CultureInfo.CurrentCulture = originalCulture;
CultureInfo.CurrentUICulture = originalUICulture; CultureInfo.CurrentUICulture = originalUICulture;
#else #else
Thread.CurrentThread.CurrentCulture = originalCulture; Thread.CurrentThread.CurrentCulture = originalCulture;
Thread.CurrentThread.CurrentUICulture = originalUICulture; Thread.CurrentThread.CurrentUICulture = originalUICulture;
#endif #endif
} }
} }

View File

@@ -3,10 +3,8 @@
// </copyright> // </copyright>
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Xunit; using Xunit;
@@ -14,113 +12,101 @@ using Xunit;
namespace plistcil.test namespace plistcil.test
{ {
/// <summary> /// <summary>
/// A <see cref="Stream"/> which writes its output to a <see cref="Stream"/> and validates that the data which /// A <see cref="Stream" /> which writes its output to a <see cref="Stream" /> and validates that the data which
/// is being written to the output stream matches the data in a reference stream. /// is being written to the output stream matches the data in a reference stream.
/// </summary> /// </summary>
internal class ValidatingStream : Stream class ValidatingStream : Stream
{ {
private Stream output; Stream expectedOutput;
private Stream expectedOutput; Stream output;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ValidatingCompositeStream"/> class. /// Initializes a new instance of the <see cref="ValidatingCompositeStream" /> class.
/// </summary> /// </summary>
/// <param name="output"> /// <param name="output">
/// The <see cref="Stream"/> to which to write data. /// The <see cref="Stream" /> to which to write data.
/// </param> /// </param>
/// <param name="expectedOutput"> /// <param name="expectedOutput">
/// The reference stream for <paramref name="output"/>. /// The reference stream for <paramref name="output" />.
/// </param> /// </param>
public ValidatingStream(Stream output, Stream expectedOutput) public ValidatingStream(Stream output, Stream expectedOutput)
{ {
this.output = output ?? throw new ArgumentNullException(nameof(output)); this.output = output ?? throw new ArgumentNullException(nameof(output));
this.expectedOutput = expectedOutput ?? throw new ArgumentNullException(nameof(expectedOutput)); this.expectedOutput = expectedOutput ?? throw new ArgumentNullException(nameof(expectedOutput));
} }
/// <inheritdoc/> /// <inheritdoc />
public override bool CanRead public override bool CanRead => false;
{
get { return false; }
}
/// <inheritdoc/> /// <inheritdoc />
public override bool CanSeek public override bool CanSeek => false;
{
get { return false; }
}
/// <inheritdoc/> /// <inheritdoc />
public override bool CanWrite public override bool CanWrite => true;
{
get { return true; }
}
/// <inheritdoc/> /// <inheritdoc />
public override long Length public override long Length => output.Length;
{
get { return this.output.Length; }
}
/// <inheritdoc/> /// <inheritdoc />
public override long Position public override long Position
{ {
get { return this.output.Position; } get => output.Position;
set { throw new NotImplementedException(); } set => throw new NotImplementedException();
} }
/// <inheritdoc/> /// <inheritdoc />
public override void Flush() public override void Flush()
{ {
this.output.Flush(); output.Flush();
} }
/// <inheritdoc/> /// <inheritdoc />
public override int Read(byte[] buffer, int offset, int count) public override int Read(byte[] buffer, int offset, int count)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
/// <inheritdoc/> /// <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(); throw new NotSupportedException();
} }
/// <inheritdoc/> /// <inheritdoc />
public override long Seek(long offset, SeekOrigin origin) public override long Seek(long offset, SeekOrigin origin)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
/// <inheritdoc/> /// <inheritdoc />
public override void SetLength(long value) public override void SetLength(long value)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
/// <inheritdoc/> /// <inheritdoc />
public override void Write(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count)
{ {
byte[] expected = new byte[buffer.Length]; byte[] expected = new byte[buffer.Length];
this.expectedOutput.Read(expected, offset, count); expectedOutput.Read(expected, offset, count);
byte[] bufferChunk = buffer.Skip(offset).Take(count).ToArray(); byte[] bufferChunk = buffer.Skip(offset).Take(count).ToArray();
byte[] expectedChunk = expected.Skip(offset).Take(count).ToArray(); byte[] expectedChunk = expected.Skip(offset).Take(count).ToArray();
// Make sure the data being writen matches the data which was written to the expected stream. // Make sure the data being writen matches the data which was written to the expected stream.
// This will detect any errors as the invalid data is being written out - as opposed to post- // This will detect any errors as the invalid data is being written out - as opposed to post-
// test binary validation. // test binary validation.
Assert.Equal(expectedChunk, bufferChunk); Assert.Equal(expectedChunk, bufferChunk);
this.output.Write(buffer, offset, count); output.Write(buffer, offset, count);
} }
/// <inheritdoc/> /// <inheritdoc />
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{ {
byte[] expected = new byte[buffer.Length]; byte[] expected = new byte[buffer.Length];
await this.expectedOutput.ReadAsync(expected, offset, count, cancellationToken).ConfigureAwait(false); await expectedOutput.ReadAsync(expected, offset, count, cancellationToken).ConfigureAwait(false);
byte[] bufferChunk = buffer.Skip(offset).Take(count).ToArray(); byte[] bufferChunk = buffer.Skip(offset).Take(count).ToArray();
byte[] expectedChunk = expected.Skip(offset).Take(count).ToArray(); byte[] expectedChunk = expected.Skip(offset).Take(count).ToArray();
// Make sure the data being writen matches the data which was written to the expected stream. // Make sure the data being writen matches the data which was written to the expected stream.
@@ -128,7 +114,7 @@ namespace plistcil.test
// test binary validation. // test binary validation.
Assert.Equal(expectedChunk, bufferChunk); Assert.Equal(expectedChunk, bufferChunk);
await this.output.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); await output.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -22,61 +22,62 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using System.IO; using System.IO;
using System.Numerics;
using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>
/// Parses property lists that are in Apple's binary format. /// Parses property lists that are in Apple's binary format.
/// Use this class when you are sure about the format of the property list. /// Use this class when you are sure about the format of the property list.
/// Otherwise use the PropertyListParser class. /// Otherwise use the PropertyListParser class.
/// </para><para> /// </para>
/// Parsing is done by calling the static <see cref="Parse(byte[])"/>, /// <para>
/// <see cref="Parse(FileInfo)"/> and <see cref="Parse(Stream)"/> methods. /// Parsing is done by calling the static <see cref="Parse(byte[])" />,
/// </para> /// <see cref="Parse(FileInfo)" /> and <see cref="Parse(Stream)" /> methods.
/// </para>
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class BinaryPropertyListParser public class BinaryPropertyListParser
{ {
private readonly static Encoding utf16BigEndian = Encoding.GetEncoding("UTF-16BE"); static readonly Encoding utf16BigEndian = Encoding.GetEncoding("UTF-16BE");
/// <summary> /// <summary>
/// Major version of the property list format /// Major version of the property list format
/// </summary> /// </summary>
int majorVersion; int majorVersion;
/// <summary> /// <summary>
/// Minor version of the property list format /// Minor version of the property list format
/// </summary> /// </summary>
int minorVersion; int minorVersion;
/// <summary> /// <summary>
/// Length of an object reference in bytes /// Length of an object reference in bytes
/// </summary> /// </summary>
int objectRefSize; int objectRefSize;
/// <summary> /// <summary>
/// The table holding the information at which offset each object is found /// The table holding the information at which offset each object is found
/// </summary> /// </summary>
int[] offsetTable; int[] offsetTable;
/// <summary> /// <summary>
/// Protected constructor so that instantiation is fully controlled by the /// Protected constructor so that instantiation is fully controlled by the
/// static parse methods. /// static parse methods.
/// </summary> /// </summary>
/// <see cref="Parse(byte[])"/> /// <see cref="Parse(byte[])" />
protected BinaryPropertyListParser() protected BinaryPropertyListParser() { }
{
}
/// <summary> /// <summary>
/// Parses a binary property list from a byte array. /// Parses a binary property list from a byte array.
/// </summary> /// </summary>
/// <param name="data">The binary property list's data.</param> /// <param name="data">The binary property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -87,7 +88,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a binary property list from a byte array. /// Parses a binary property list from a byte array.
/// </summary> /// </summary>
/// <param name="data">The binary property list's data.</param> /// <param name="data">The binary property list's data.</param>
/// <param name="offset">The length of the property list.</param> /// <param name="offset">The length of the property list.</param>
@@ -100,7 +101,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a binary property list from a byte span. /// Parses a binary property list from a byte span.
/// </summary> /// </summary>
/// <param name="data">The binary property list's data.</param> /// <param name="data">The binary property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -112,23 +113,19 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a binary property list from a byte array. /// Parses a binary property list from a byte array.
/// </summary> /// </summary>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
/// <param name="bytes">The binary property list's data.</param> /// <param name="bytes">The binary property list's data.</param>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception> /// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
private NSObject DoParse(ReadOnlySpan<byte> bytes) NSObject DoParse(ReadOnlySpan<byte> bytes)
{ {
if(bytes.Length < 8 if(bytes.Length < 8 || bytes[0] != 'b' || bytes[1] != 'p' || bytes[2] != 'l' || bytes[3] != 'i' ||
|| bytes[0] != 'b' bytes[4] != 's' || bytes[5] != 't')
|| bytes[1] != 'p'
|| bytes[2] != 'l'
|| bytes[3] != 'i'
|| bytes[4] != 's'
|| bytes[5] != 't')
{ {
var magic = Encoding.ASCII.GetString(bytes.Slice(0, 8).ToArray()); string magic = Encoding.ASCII.GetString(bytes.Slice(0, 8).ToArray());
throw new PropertyListFormatException("The given data is no binary property list. Wrong magic bytes: " + magic); throw new PropertyListFormatException("The given data is no binary property list. Wrong magic bytes: " +
magic);
} }
majorVersion = bytes[6] - 0x30; //ASCII number majorVersion = bytes[6] - 0x30; //ASCII number
@@ -140,22 +137,21 @@ namespace Claunia.PropertyList
// 1.5 - Lion // 1.5 - Lion
// 2.0 - Snow Lion // 2.0 - Snow Lion
if (majorVersion > 0) if(majorVersion > 0)
{ throw new PropertyListFormatException("Unsupported binary property list format: v" + majorVersion +
// Version 1.0+ is not even supported by OS X's own parser "." + minorVersion +
throw new PropertyListFormatException("Unsupported binary property list format: v" + majorVersion + "." + minorVersion + ". " + ". " +
"Version 1.0 and later are not yet supported."); "Version 1.0 and later are not yet supported.");
}
/* /*
* Handle trailer, last 32 bytes of the file * Handle trailer, last 32 bytes of the file
*/ */
var trailer = bytes.Slice(bytes.Length - 32, 32); ReadOnlySpan<byte> trailer = bytes.Slice(bytes.Length - 32, 32);
//6 null bytes (index 0 to 5) //6 null bytes (index 0 to 5)
int offsetSize = trailer[6]; int offsetSize = trailer[6];
objectRefSize = trailer[7]; objectRefSize = trailer[7];
int numObjects = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(8, 8)); int numObjects = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(8, 8));
int topObject = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(16, 8)); int topObject = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(16, 8));
int offsetTableOffset = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(24, 8)); int offsetTableOffset = (int)BinaryPrimitives.ReadUInt64BigEndian(trailer.Slice(24, 8));
/* /*
@@ -163,9 +159,9 @@ namespace Claunia.PropertyList
*/ */
offsetTable = new int[numObjects]; offsetTable = new int[numObjects];
for (int i = 0; i < numObjects; i++) for(int i = 0; i < numObjects; i++)
{ {
var offsetBytes = bytes.Slice(offsetTableOffset + i * offsetSize, offsetSize); ReadOnlySpan<byte> offsetBytes = bytes.Slice(offsetTableOffset + i * offsetSize, offsetSize);
offsetTable[i] = (int)ParseUnsignedInt(offsetBytes); offsetTable[i] = (int)ParseUnsignedInt(offsetBytes);
} }
@@ -173,7 +169,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a binary property list from an input stream. /// Parses a binary property list from an input stream.
/// </summary> /// </summary>
/// <param name="fs">The input stream that points to the property list's data.</param> /// <param name="fs">The input stream that points to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -188,7 +184,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a binary property list file. /// Parses a binary property list file.
/// </summary> /// </summary>
/// <param name="f">The binary property list file</param> /// <param name="f">The binary property list file</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -199,218 +195,236 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses an object inside the currently parsed binary property list. /// Parses an object inside the currently parsed binary property list.
/// For the format specification check /// For the format specification check
/// <a href="http://www.opensource.apple.com/source/CF/CF-855.17/CFBinaryPList.c"> /// <a href="http://www.opensource.apple.com/source/CF/CF-855.17/CFBinaryPList.c">
/// Apple's binary property list parser implementation</a>. /// Apple's binary property list parser implementation
/// </a>
/// .
/// </summary> /// </summary>
/// <returns>The parsed object.</returns> /// <returns>The parsed object.</returns>
/// <param name="obj">The object ID.</param> /// <param name="obj">The object ID.</param>
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception> /// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
NSObject ParseObject(ReadOnlySpan<byte> bytes, int obj) NSObject ParseObject(ReadOnlySpan<byte> bytes, int obj)
{ {
int offset = offsetTable[obj]; int offset = offsetTable[obj];
byte type = bytes[offset]; byte type = bytes[offset];
int objType = (type & 0xF0) >> 4; //First 4 bits int objType = (type & 0xF0) >> 4; //First 4 bits
int objInfo = (type & 0x0F); //Second 4 bits int objInfo = type & 0x0F; //Second 4 bits
switch (objType) switch(objType)
{ {
case 0x0: case 0x0:
{
//Simple
switch(objInfo)
{ {
//Simple case 0x0:
switch (objInfo)
{ {
case 0x0: //null object (v1.0 and later)
{ return null;
//null object (v1.0 and later) }
return null; case 0x8:
} {
case 0x8: //false
{ return new NSNumber(false);
//false }
return new NSNumber(false); case 0x9:
} {
case 0x9: //true
{ return new NSNumber(true);
//true }
return new NSNumber(true); case 0xC:
} {
case 0xC: //URL with no base URL (v1.0 and later)
{ //TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17)
//URL with no base URL (v1.0 and later) break;
//TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17) }
break; case 0xD:
} {
case 0xD: //URL with base URL (v1.0 and later)
{ //TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17)
//URL with base URL (v1.0 and later) break;
//TODO Implement binary URL parsing (not yet even implemented in Core Foundation as of revision 855.17) }
break; case 0xE:
} {
case 0xE: //16-byte UUID (v1.0 and later)
{ //TODO Implement binary UUID parsing (not yet even implemented in Core Foundation as of revision 855.17)
//16-byte UUID (v1.0 and later) break;
//TODO Implement binary UUID parsing (not yet even implemented in Core Foundation as of revision 855.17) }
break; case 0xF:
} {
case 0xF: //filler byte
{ return null;
//filler byte
return null;
}
} }
break;
} }
break;
}
case 0x1: case 0x1:
{ {
//integer //integer
int length = (int)Math.Pow(2, objInfo); int length = (int)Math.Pow(2, objInfo);
return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.INTEGER); return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.INTEGER);
} }
case 0x2: case 0x2:
{ {
//real //real
int length = (int)Math.Pow(2, objInfo); int length = (int)Math.Pow(2, objInfo);
return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.REAL); return new NSNumber(bytes.Slice(offset + 1, length), NSNumber.REAL);
} }
case 0x3: case 0x3:
{ {
//Date //Date
if (objInfo != 0x3) if(objInfo != 0x3)
{ throw new
throw new PropertyListFormatException("The given binary property list contains a date object of an unknown type (" + objInfo + ")"); PropertyListFormatException("The given binary property list contains a date object of an unknown type (" +
} objInfo +
return new NSDate(bytes.Slice(offset + 1, 8)); ")");
}
return new NSDate(bytes.Slice(offset + 1, 8));
}
case 0x4: case 0x4:
{ {
//Data //Data
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int dataoffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int dataoffset);
return new NSData(CopyOfRange(bytes, offset + dataoffset, offset + dataoffset + length)); return new NSData(CopyOfRange(bytes, offset + dataoffset, offset + dataoffset + length));
} }
case 0x5: case 0x5:
{ {
//ASCII String, each character is 1 byte //ASCII String, each character is 1 byte
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset);
return new NSString(bytes.Slice(offset + stroffset, length), Encoding.ASCII); return new NSString(bytes.Slice(offset + stroffset, length), Encoding.ASCII);
} }
case 0x6: case 0x6:
{ {
//UTF-16-BE String //UTF-16-BE String
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int stroffset);
//UTF-16 characters can have variable length, but the Core Foundation reference implementation //UTF-16 characters can have variable length, but the Core Foundation reference implementation
//assumes 2 byte characters, thus only covering the Basic Multilingual Plane //assumes 2 byte characters, thus only covering the Basic Multilingual Plane
length *= 2; length *= 2;
return new NSString(bytes.Slice(offset + stroffset, length), utf16BigEndian); return new NSString(bytes.Slice(offset + stroffset, length), utf16BigEndian);
} }
case 0x7: case 0x7:
{ {
//UTF-8 string (v1.0 and later) //UTF-8 string (v1.0 and later)
ReadLengthAndOffset(bytes, objInfo, offset, out int strOffset, out int characters); ReadLengthAndOffset(bytes, objInfo, offset, out int strOffset, out int characters);
//UTF-8 characters can have variable length, so we need to calculate the byte length dynamically //UTF-8 characters can have variable length, so we need to calculate the byte length dynamically
//by reading the UTF-8 characters one by one //by reading the UTF-8 characters one by one
int length = CalculateUtf8StringLength(bytes, offset + strOffset, characters); int length = CalculateUtf8StringLength(bytes, offset + strOffset, characters);
return new NSString(bytes.Slice(offset + strOffset, length), Encoding.UTF8); return new NSString(bytes.Slice(offset + strOffset, length), Encoding.UTF8);
} }
case 0x8: case 0x8:
{ {
//UID (v1.0 and later) //UID (v1.0 and later)
int length = objInfo + 1; int length = objInfo + 1;
return new UID(bytes.Slice(offset + 1, length)); return new UID(bytes.Slice(offset + 1, length));
} }
case 0xA: case 0xA:
{
//Array
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int arrayOffset);
NSArray array = new NSArray(length);
for(int i = 0; i < length; i++)
{ {
//Array int objRef =
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int arrayOffset); (int)ParseUnsignedInt(bytes.Slice(offset + arrayOffset + i * objectRefSize, objectRefSize));
array.Add(ParseObject(bytes, objRef));
NSArray array = new NSArray(length);
for (int i = 0; i < length; i++)
{
int objRef = (int)ParseUnsignedInt(bytes.Slice(offset + arrayOffset + i * objectRefSize, objectRefSize));
array.Add(ParseObject(bytes, objRef));
}
return array;
} }
return array;
}
case 0xB: case 0xB:
{ {
//Ordered set (v1.0 and later) //Ordered set (v1.0 and later)
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset);
NSSet set = new NSSet(true); NSSet set = new NSSet(true);
for (int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int objRef = (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize)); int objRef =
set.AddObject(ParseObject(bytes, objRef)); (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize,
} objectRefSize));
return set; set.AddObject(ParseObject(bytes, objRef));
} }
return set;
}
case 0xC: case 0xC:
{ {
//Set (v1.0 and later) //Set (v1.0 and later)
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset);
NSSet set = new NSSet(); NSSet set = new NSSet();
for (int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{ {
int objRef = (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize)); int objRef =
set.AddObject(ParseObject(bytes, objRef)); (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize,
} objectRefSize));
return set; set.AddObject(ParseObject(bytes, objRef));
} }
return set;
}
case 0xD: case 0xD:
{ {
//Dictionary //Dictionary
ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset); ReadLengthAndOffset(bytes, objInfo, offset, out int length, out int contentOffset);
//System.out.println("Parsing dictionary #"+obj); //System.out.println("Parsing dictionary #"+obj);
NSDictionary dict = new NSDictionary(length); NSDictionary dict = new NSDictionary(length);
for (int i = 0; i < length; i++) for(int i = 0; i < length; i++)
{
int keyRef = (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize, objectRefSize));
int valRef = (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + (length * objectRefSize) + i * objectRefSize, objectRefSize));
NSObject key = ParseObject(bytes, keyRef);
NSObject val = ParseObject(bytes, valRef);
dict.Add(key.ToString(), val);
}
return dict;
}
default:
{ {
Debug.WriteLine("WARNING: The given binary property list contains an object of unknown type (" + objType + ")"); int keyRef =
break; (int)ParseUnsignedInt(bytes.Slice(offset + contentOffset + i * objectRefSize,
objectRefSize));
int valRef =
(int)
ParseUnsignedInt(bytes.Slice(offset + contentOffset + length * objectRefSize + i * objectRefSize,
objectRefSize));
NSObject key = ParseObject(bytes, keyRef);
NSObject val = ParseObject(bytes, valRef);
dict.Add(key.ToString(), val);
} }
return dict;
}
default:
{
Debug.WriteLine("WARNING: The given binary property list contains an object of unknown type (" +
objType +
")");
break;
}
} }
return null; return null;
} }
/// <summary> /// <summary>
/// Reads the length for arrays, sets and dictionaries. /// Reads the length for arrays, sets and dictionaries.
/// </summary> /// </summary>
/// <returns>An array with the length two. First entry is the length, second entry the offset at which the content starts.</returns> /// <returns>An array with the length two. First entry is the length, second entry the offset at which the content starts.</returns>
/// <param name="objInfo">Object information byte.</param> /// <param name="objInfo">Object information byte.</param>
/// <param name="offset">Offset in the byte array at which the object is located.</param> /// <param name="offset">Offset in the byte array at which the object is located.</param>
void ReadLengthAndOffset(ReadOnlySpan<byte> bytes, int objInfo, int offset, out int lengthValue, out int offsetValue) void ReadLengthAndOffset(ReadOnlySpan<byte> bytes, int objInfo, int offset, out int lengthValue,
out int offsetValue)
{ {
lengthValue = objInfo; lengthValue = objInfo;
offsetValue = 1; offsetValue = 1;
if (objInfo == 0xF) if(objInfo == 0xF)
{ {
int int_type = bytes[offset + 1]; int int_type = bytes[offset + 1];
int intType = (int_type & 0xF0) >> 4; int intType = (int_type & 0xF0) >> 4;
if (intType != 0x1) if(intType != 0x1)
{ Debug.WriteLine("BinaryPropertyListParser: Length integer has an unexpected type" + intType +
Debug.WriteLine("BinaryPropertyListParser: Length integer has an unexpected type" + intType + ". Attempting to parse anyway..."); ". Attempting to parse anyway...");
} int intInfo = int_type & 0x0F;
int intInfo = int_type & 0x0F;
int intLength = (int)Math.Pow(2, intInfo); int intLength = (int)Math.Pow(2, intInfo);
offsetValue = 2 + intLength; offsetValue = 2 + intLength;
if (intLength < 3) if(intLength < 3) lengthValue = (int)ParseUnsignedInt(bytes.Slice(offset + 2, intLength));
{
lengthValue = (int)ParseUnsignedInt(bytes.Slice(offset + 2, intLength));
}
else else
{ {
// BigInteger is Little-Endian in .NET, swap the thing. // BigInteger is Little-Endian in .NET, swap the thing.
@@ -419,13 +433,13 @@ namespace Claunia.PropertyList
Array.Reverse(bigEBigInteger); Array.Reverse(bigEBigInteger);
bigEBigInteger[bigEBigInteger.Length - 1] = 0x00; // Be sure to get unsigned BigInteger bigEBigInteger[bigEBigInteger.Length - 1] = 0x00; // Be sure to get unsigned BigInteger
lengthValue = (int)new System.Numerics.BigInteger(bigEBigInteger); lengthValue = (int)new BigInteger(bigEBigInteger);
} }
} }
} }
/// <summary> /// <summary>
/// Calculates the length in bytes of the UTF-8 string. /// Calculates the length in bytes of the UTF-8 string.
/// </summary> /// </summary>
/// <returns>The UTF-8 string length.</returns> /// <returns>The UTF-8 string length.</returns>
/// <param name="bytes">Array containing the UTF-8 string.</param> /// <param name="bytes">Array containing the UTF-8 string.</param>
@@ -434,76 +448,53 @@ namespace Claunia.PropertyList
int CalculateUtf8StringLength(ReadOnlySpan<byte> bytes, int offset, int numCharacters) int CalculateUtf8StringLength(ReadOnlySpan<byte> bytes, int offset, int numCharacters)
{ {
int length = 0; int length = 0;
for (int i = 0; i < numCharacters; i++) for(int i = 0; i < numCharacters; i++)
{ {
int tempOffset = offset + length; int tempOffset = offset + length;
if (bytes.Length <= tempOffset) if(bytes.Length <= tempOffset) return numCharacters;
if(bytes[tempOffset] < 0x80) length++;
if(bytes[tempOffset] < 0xC2) return numCharacters;
if(bytes[tempOffset] < 0xE0)
{ {
//WARNING: Invalid UTF-8 string, fall back to length = number of characters if((bytes[tempOffset + 1] & 0xC0) != 0x80) return numCharacters;
return numCharacters;
}
if (bytes[tempOffset] < 0x80)
{
length++;
}
if (bytes[tempOffset] < 0xC2)
{
//Invalid value (marks continuation byte), fall back to length = number of characters
return numCharacters;
}
else if (bytes[tempOffset] < 0xE0)
{
if ((bytes[tempOffset + 1] & 0xC0) != 0x80)
{
//Invalid continuation byte, fall back to length = number of characters
return numCharacters;
}
length += 2; length += 2;
} }
else if (bytes[tempOffset] < 0xF0) else if(bytes[tempOffset] < 0xF0)
{ {
if ((bytes[tempOffset + 1] & 0xC0) != 0x80 if((bytes[tempOffset + 1] & 0xC0) != 0x80 || (bytes[tempOffset + 2] & 0xC0) != 0x80)
|| (bytes[tempOffset + 2] & 0xC0) != 0x80)
{
//Invalid continuation byte, fall back to length = number of characters
return numCharacters; return numCharacters;
}
length += 3; length += 3;
} }
else if (bytes[tempOffset] < 0xF5) else if(bytes[tempOffset] < 0xF5)
{ {
if ((bytes[tempOffset + 1] & 0xC0) != 0x80 if((bytes[tempOffset + 1] & 0xC0) != 0x80 || (bytes[tempOffset + 2] & 0xC0) != 0x80 ||
|| (bytes[tempOffset + 2] & 0xC0) != 0x80 (bytes[tempOffset + 3] & 0xC0) != 0x80) return numCharacters;
|| (bytes[tempOffset + 3] & 0xC0) != 0x80)
{
//Invalid continuation byte, fall back to length = number of characters
return numCharacters;
}
length += 4; length += 4;
} }
} }
return length; return length;
} }
/// <summary> /// <summary>
/// Parses an unsigned integers from a span. /// Parses an unsigned integers from a span.
/// </summary> /// </summary>
/// <returns>The byte array containing the unsigned integer.</returns> /// <returns>The byte array containing the unsigned integer.</returns>
/// <param name="bytes">The unsigned integer represented by the given bytes.</param> /// <param name="bytes">The unsigned integer represented by the given bytes.</param>
public static long ParseUnsignedInt(ReadOnlySpan<byte> bytes) public static long ParseUnsignedInt(ReadOnlySpan<byte> bytes)
{ {
if (bytes.Length <= 4) if(bytes.Length <= 4) return ParseLong(bytes);
{
return ParseLong(bytes); return ParseLong(bytes) & 0xFFFFFFFFL;
}
else
{
return ParseLong(bytes) & 0xFFFFFFFFL;
}
} }
/// <summary> /// <summary>
/// Parses an unsigned integers from a byte array. /// Parses an unsigned integers from a byte array.
/// </summary> /// </summary>
/// <returns>The byte array containing the unsigned integer.</returns> /// <returns>The byte array containing the unsigned integer.</returns>
/// <param name="bytes">The unsigned integer represented by the given bytes.</param> /// <param name="bytes">The unsigned integer represented by the given bytes.</param>
@@ -513,50 +504,45 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a long from a (big-endian) byte array. /// Parses a long from a (big-endian) byte array.
/// </summary> /// </summary>
/// <returns>The long integer represented by the given bytes.</returns> /// <returns>The long integer represented by the given bytes.</returns>
/// <param name="bytes">The bytes representing the long integer.</param> /// <param name="bytes">The bytes representing the long integer.</param>
public static long ParseLong(ReadOnlySpan<byte> bytes) public static long ParseLong(ReadOnlySpan<byte> bytes)
{ {
switch (bytes.Length) switch(bytes.Length)
{ {
case 1: case 1: return bytes[0];
return bytes[0];
case 2: case 2: return BinaryPrimitives.ReadUInt16BigEndian(bytes);
return BinaryPrimitives.ReadUInt16BigEndian(bytes);
case 3: case 3: throw new NotSupportedException();
throw new NotSupportedException();
case 4: case 4: return BinaryPrimitives.ReadUInt32BigEndian(bytes);
return BinaryPrimitives.ReadUInt32BigEndian(bytes);
case 8: case 8: return (long)BinaryPrimitives.ReadUInt64BigEndian(bytes);
return (long)BinaryPrimitives.ReadUInt64BigEndian(bytes);
default: default:
throw new ArgumentOutOfRangeException(nameof(bytes), $"Cannot read a byte span of length {bytes.Length}"); throw new ArgumentOutOfRangeException(nameof(bytes),
$"Cannot read a byte span of length {bytes.Length}");
} }
} }
/// <summary> /// <summary>
/// Parses a double from a (big-endian) byte array. /// Parses a double from a (big-endian) byte array.
/// </summary> /// </summary>
/// <returns>The double represented by the given bytes.</returns> /// <returns>The double represented by the given bytes.</returns>
/// <param name="bytes">The bytes representing the double.</param> /// <param name="bytes">The bytes representing the double.</param>
public static double ParseDouble(ReadOnlySpan<byte> bytes) public static double ParseDouble(ReadOnlySpan<byte> bytes)
{ {
if (bytes.Length == 8) if(bytes.Length == 8) return BitConverter.Int64BitsToDouble(ParseLong(bytes));
return BitConverter.Int64BitsToDouble(ParseLong(bytes)); if(bytes.Length == 4) return BitConverter.ToSingle(BitConverter.GetBytes(ParseLong(bytes)), 0);
if (bytes.Length == 4)
return BitConverter.ToSingle(BitConverter.GetBytes(ParseLong(bytes)), 0);
throw new ArgumentException("bad byte array length " + bytes.Length); throw new ArgumentException("bad byte array length " + bytes.Length);
} }
/// <summary> /// <summary>
/// Copies a part of a byte array into a new array. /// Copies a part of a byte array into a new array.
/// </summary> /// </summary>
/// <returns>The copied array.</returns> /// <returns>The copied array.</returns>
/// <param name="src">The source array.</param> /// <param name="src">The source array.</param>
@@ -565,13 +551,11 @@ namespace Claunia.PropertyList
public static byte[] CopyOfRange(ReadOnlySpan<byte> src, int startIndex, int endIndex) public static byte[] CopyOfRange(ReadOnlySpan<byte> src, int startIndex, int endIndex)
{ {
int length = endIndex - startIndex; int length = endIndex - startIndex;
if (length < 0) if(length < 0)
{ throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex +
throw new ArgumentOutOfRangeException("startIndex (" + startIndex + ")" + " > endIndex (" + endIndex + ")"); ")");
}
return src.Slice(startIndex, endIndex - startIndex).ToArray(); return src.Slice(startIndex, endIndex - startIndex).ToArray();
} }
} }
} }

View File

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

View File

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

View File

@@ -22,171 +22,61 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.IO;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.IO;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>
/// A BinaryPropertyListWriter is a helper class for writing out /// A BinaryPropertyListWriter is a helper class for writing out
/// binary property list files. /// binary property list files.
/// </para><para> /// </para>
/// It contains an output stream and various structures for keeping track /// <para>
/// of which NSObjects have already been serialized, and where they were /// It contains an output stream and various structures for keeping track
/// put in the file. /// of which NSObjects have already been serialized, and where they were
/// </para> /// put in the file.
/// </para>
/// </summary> /// </summary>
/// @author Keith Randall /// @author Keith Randall
/// @author Natalia Portillo /// @author Natalia Portillo
public partial class BinaryPropertyListWriter public partial class BinaryPropertyListWriter
{ {
/// <summary> /// <summary>
/// Binary property list version 0.0 /// Binary property list version 0.0
/// </summary> /// </summary>
public const int VERSION_00 = 0; public const int VERSION_00 = 0;
/// <summary> /// <summary>
/// Binary property list version 1.0 /// Binary property list version 1.0
/// </summary> /// </summary>
public const int VERSION_10 = 10; public const int VERSION_10 = 10;
/// <summary> /// <summary>
/// Binary property list version 1.5 /// Binary property list version 1.5
/// </summary> /// </summary>
public const int VERSION_15 = 15; public const int VERSION_15 = 15;
/// <summary> /// <summary>
/// Binary property list version 2.0 /// Binary property list version 2.0
/// </summary> /// </summary>
public const int VERSION_20 = 20; public const int VERSION_20 = 20;
/// <summary> // map from object to its ID
/// Gets or sets a value indicating whether two equivalent objects should be serialized once in the binary property list file, or whether readonly Dictionary<NSObject, int> idDict = new Dictionary<NSObject, int>(new AddObjectEqualityComparer());
/// the value should be stored multiple times in the binary property list file. The default is <see langword="false"/>. readonly Dictionary<NSObject, int> idDict2 = new Dictionary<NSObject, int>(new GetObjectEqualityComparer());
/// </summary>
/// <remarks>
/// In most scenarios, you want this to be <see langword="true"/>, as it reduces the size of the binary proeprty list file. However,
/// by default, the Apple tools do not seem to implement this optimization, so set this value to <see langword="false"/> if you
/// want to maintain binary compatibility with the Apple tools.
/// </remarks>
public bool ReuseObjectIds
{
get;
set;
}
/// <summary> // # of bytes written so far
/// Finds out the minimum binary property list format version that long count;
/// can be used to save the given NSObject tree. int currentId;
/// </summary> int idSizeInBytes;
/// <returns>Version code</returns>
/// <param name="root">Object root.</param>
static int GetMinimumRequiredVersion(NSObject root)
{
int minVersion = VERSION_00;
if (root == null)
{
minVersion = VERSION_10;
}
if (root is NSDictionary)
{
NSDictionary dict = (NSDictionary)root;
foreach (NSObject o in dict.GetDictionary().Values)
{
int v = GetMinimumRequiredVersion(o);
if (v > minVersion)
minVersion = v;
}
}
else if (root is NSArray)
{
NSArray array = (NSArray)root;
foreach (NSObject o in array)
{
int v = GetMinimumRequiredVersion(o);
if (v > minVersion)
minVersion = v;
}
}
else if (root is NSSet)
{
//Sets are only allowed in property lists v1+
minVersion = VERSION_10;
NSSet set = (NSSet)root;
foreach (NSObject o in set.AllObjects())
{
int v = GetMinimumRequiredVersion(o);
if (v > minVersion)
minVersion = v;
}
}
return minVersion;
}
/// <summary>
/// Writes a binary plist file with the given object as the root.
/// </summary>
/// <param name="file">the file to write to</param>
/// <param name="root">the source of the data to write to the file</param>
/// <exception cref="IOException"></exception>
public static void Write(FileInfo file, NSObject root)
{
using (FileStream fous = file.OpenWrite())
{
Write(fous, root);
}
}
/// <summary>
/// Writes a binary plist serialization of the given object as the root.
/// </summary>
/// <param name="outStream">the stream to write to</param>
/// <param name="root">the source of the data to write to the stream</param>
/// <exception cref="IOException"></exception>
public static void Write(Stream outStream, NSObject root)
{
int minVersion = GetMinimumRequiredVersion(root);
if (minVersion > VERSION_00)
{
string versionString = ((minVersion == VERSION_10) ? "v1.0" : ((minVersion == VERSION_15) ? "v1.5" : ((minVersion == VERSION_20) ? "v2.0" : "v0.0")));
throw new IOException("The given property list structure cannot be saved. " +
"The required version of the binary format (" + versionString + ") is not yet supported.");
}
BinaryPropertyListWriter w = new BinaryPropertyListWriter(outStream, minVersion);
w.Write(root);
}
/// <summary>
/// Writes a binary plist serialization of the given object as the root
/// into a byte array.
/// </summary>
/// <returns>The byte array containing the serialized property list</returns>
/// <param name="root">The root object of the property list</param>
/// <exception cref="IOException"></exception>
public static byte[] WriteToArray(NSObject root)
{
MemoryStream bout = new MemoryStream();
Write(bout, root);
return bout.ToArray();
}
int version = VERSION_00;
// raw output stream to result file // raw output stream to result file
Stream outStream; Stream outStream;
// # of bytes written so far int version = VERSION_00;
long count;
// map from object to its ID
readonly Dictionary<NSObject, int> idDict = new Dictionary<NSObject, int>(new AddObjectEqualityComparer());
readonly Dictionary<NSObject, int> idDict2 = new Dictionary<NSObject, int>(new GetObjectEqualityComparer());
int currentId = 0;
int idSizeInBytes;
/// <summary> /// <summary>
/// Creates a new binary property list writer /// Creates a new binary property list writer
/// </summary> /// </summary>
/// <param name="outStr">The output stream into which the binary property list will be written</param> /// <param name="outStr">The output stream into which the binary property list will be written</param>
/// <exception cref="IOException">If an error occured while writing to the stream</exception> /// <exception cref="IOException">If an error occured while writing to the stream</exception>
@@ -198,37 +88,145 @@ namespace Claunia.PropertyList
public BinaryPropertyListWriter(Stream outStr, int version) public BinaryPropertyListWriter(Stream outStr, int version)
{ {
this.version = version; this.version = version;
outStream = outStr; outStream = outStr;
}
/// <summary>
/// Gets or sets a value indicating whether two equivalent objects should be serialized once in the binary property
/// list file, or whether
/// the value should be stored multiple times in the binary property list file. The default is <see langword="false" />
/// .
/// </summary>
/// <remarks>
/// In most scenarios, you want this to be <see langword="true" />, as it reduces the size of the binary proeprty list
/// file. However,
/// by default, the Apple tools do not seem to implement this optimization, so set this value to
/// <see langword="false" /> if you
/// want to maintain binary compatibility with the Apple tools.
/// </remarks>
public bool ReuseObjectIds { get; set; }
/// <summary>
/// Finds out the minimum binary property list format version that
/// can be used to save the given NSObject tree.
/// </summary>
/// <returns>Version code</returns>
/// <param name="root">Object root.</param>
static int GetMinimumRequiredVersion(NSObject root)
{
int minVersion = VERSION_00;
if(root == null) minVersion = VERSION_10;
if(root is NSDictionary)
{
NSDictionary dict = (NSDictionary)root;
foreach(NSObject o in dict.GetDictionary().Values)
{
int v = GetMinimumRequiredVersion(o);
if(v > minVersion) minVersion = v;
}
}
else if(root is NSArray)
{
NSArray array = (NSArray)root;
foreach(NSObject o in array)
{
int v = GetMinimumRequiredVersion(o);
if(v > minVersion) minVersion = v;
}
}
else if(root is NSSet)
{
//Sets are only allowed in property lists v1+
minVersion = VERSION_10;
NSSet set = (NSSet)root;
foreach(NSObject o in set.AllObjects())
{
int v = GetMinimumRequiredVersion(o);
if(v > minVersion) minVersion = v;
}
}
return minVersion;
}
/// <summary>
/// Writes a binary plist file with the given object as the root.
/// </summary>
/// <param name="file">the file to write to</param>
/// <param name="root">the source of the data to write to the file</param>
/// <exception cref="IOException"></exception>
public static void Write(FileInfo file, NSObject root)
{
using(FileStream fous = file.OpenWrite()) Write(fous, root);
}
/// <summary>
/// Writes a binary plist serialization of the given object as the root.
/// </summary>
/// <param name="outStream">the stream to write to</param>
/// <param name="root">the source of the data to write to the stream</param>
/// <exception cref="IOException"></exception>
public static void Write(Stream outStream, NSObject root)
{
int minVersion = GetMinimumRequiredVersion(root);
if(minVersion > VERSION_00)
{
string versionString = minVersion == VERSION_10
? "v1.0"
: (minVersion == VERSION_15
? "v1.5"
: (minVersion == VERSION_20 ? "v2.0" : "v0.0"));
throw new IOException("The given property list structure cannot be saved. " +
"The required version of the binary format (" + versionString +
") is not yet supported.");
}
BinaryPropertyListWriter w = new BinaryPropertyListWriter(outStream, minVersion);
w.Write(root);
}
/// <summary>
/// Writes a binary plist serialization of the given object as the root
/// into a byte array.
/// </summary>
/// <returns>The byte array containing the serialized property list</returns>
/// <param name="root">The root object of the property list</param>
/// <exception cref="IOException"></exception>
public static byte[] WriteToArray(NSObject root)
{
MemoryStream bout = new MemoryStream();
Write(bout, root);
return bout.ToArray();
} }
public void Write(NSObject root) public void Write(NSObject root)
{ {
// magic bytes // magic bytes
Write(new[] { (byte)'b', (byte)'p', (byte)'l', (byte)'i', (byte)'s', (byte)'t' }); Write(new[] {(byte)'b', (byte)'p', (byte)'l', (byte)'i', (byte)'s', (byte)'t'});
//version //version
switch (version) switch(version)
{ {
case VERSION_00: case VERSION_00:
{ {
Write(new[] { (byte)'0', (byte)'0' }); Write(new[] {(byte)'0', (byte)'0'});
break; break;
} }
case VERSION_10: case VERSION_10:
{ {
Write(new[] { (byte)'1', (byte)'0' }); Write(new[] {(byte)'1', (byte)'0'});
break; break;
} }
case VERSION_15: case VERSION_15:
{ {
Write(new[] { (byte)'1', (byte)'5' }); Write(new[] {(byte)'1', (byte)'5'});
break; break;
} }
case VERSION_20: case VERSION_20:
{ {
Write(new[] { (byte)'2', (byte)'0' }); Write(new[] {(byte)'2', (byte)'0'});
break; break;
} }
} }
// assign IDs to all the objects. // assign IDs to all the objects.
@@ -240,30 +238,21 @@ namespace Claunia.PropertyList
long[] offsets = new long[idDict.Count]; long[] offsets = new long[idDict.Count];
// write each object, save offset // write each object, save offset
foreach (var pair in idDict) foreach(KeyValuePair<NSObject, int> pair in idDict)
{ {
NSObject obj = pair.Key; NSObject obj = pair.Key;
int id = pair.Value; int id = pair.Value;
offsets[id] = count; offsets[id] = count;
if (obj == null) if(obj == null) Write(0x00);
{ else obj.ToBinary(this);
Write(0x00);
}
else
{
obj.ToBinary(this);
}
} }
// write offset table // write offset table
long offsetTableOffset = count; long offsetTableOffset = count;
int offsetSizeInBytes = ComputeOffsetSizeInBytes(count); int offsetSizeInBytes = ComputeOffsetSizeInBytes(count);
foreach (long offset in offsets) foreach(long offset in offsets) WriteBytes(offset, offsetSizeInBytes);
{
WriteBytes(offset, offsetSizeInBytes);
}
if (version != VERSION_15) if(version != VERSION_15)
{ {
// write trailer // write trailer
// 6 null bytes // 6 null bytes
@@ -286,70 +275,51 @@ namespace Claunia.PropertyList
internal void AssignID(NSObject obj) internal void AssignID(NSObject obj)
{ {
if (this.ReuseObjectIds) if(ReuseObjectIds)
{ {
if (!this.idDict.ContainsKey(obj)) if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++);
{
idDict.Add(obj, currentId++);
}
} }
else else
{ {
if (!this.idDict2.ContainsKey(obj)) if(!idDict2.ContainsKey(obj)) idDict2.Add(obj, currentId);
{ if(!idDict.ContainsKey(obj)) idDict.Add(obj, currentId++);
idDict2.Add(obj, currentId);
}
if (!this.idDict.ContainsKey(obj))
{
idDict.Add(obj, currentId++);
}
} }
} }
internal int GetID(NSObject obj) internal int GetID(NSObject obj)
{ {
if (this.ReuseObjectIds) if(ReuseObjectIds) return idDict[obj];
{
return idDict[obj]; return idDict2[obj];
}
else
{
return idDict2[obj];
}
} }
static int ComputeIdSizeInBytes(int numberOfIds) static int ComputeIdSizeInBytes(int numberOfIds)
{ {
if (numberOfIds < 256) if(numberOfIds < 256) return 1;
return 1;
return numberOfIds < 65536 ? 2 : 4; return numberOfIds < 65536 ? 2 : 4;
} }
static int ComputeOffsetSizeInBytes(long maxOffset) static int ComputeOffsetSizeInBytes(long maxOffset)
{ {
if (maxOffset < 256) if(maxOffset < 256) return 1;
return 1; if(maxOffset < 65536) return 2;
if (maxOffset < 65536)
return 2;
return maxOffset < 4294967296L ? 4 : 8; return maxOffset < 4294967296L ? 4 : 8;
} }
internal void WriteIntHeader(int kind, int value) internal void WriteIntHeader(int kind, int value)
{ {
if (value < 0) if(value < 0) throw new ArgumentException("value must be greater than or equal to 0", "value");
throw new ArgumentException("value must be greater than or equal to 0", "value");
if (value < 15) if(value < 15) Write((kind << 4) + value);
{ else if(value < 256)
Write((kind << 4) + value);
}
else if (value < 256)
{ {
Write((kind << 4) + 15); Write((kind << 4) + 15);
Write(0x10); Write(0x10);
WriteBytes(value, 1); WriteBytes(value, 1);
} }
else if (value < 65536) else if(value < 65536)
{ {
Write((kind << 4) + 15); Write((kind << 4) + 15);
Write(0x11); Write(0x11);
@@ -379,21 +349,18 @@ namespace Claunia.PropertyList
internal void Write(Span<byte> bytes) internal void Write(Span<byte> bytes)
{ {
#if SPAN_NATIVE #if SPAN_NATIVE
outStream.Write(bytes); outStream.Write(bytes);
count += bytes.Length; count += bytes.Length;
#else #else
this.Write(bytes.ToArray()); Write(bytes.ToArray());
#endif #endif
} }
internal void WriteBytes(long value, int bytes) internal void WriteBytes(long value, int bytes)
{ {
// write low-order bytes big-endian style // write low-order bytes big-endian style
for (int i = bytes - 1; i >= 0; i--) for(int i = bytes - 1; i >= 0; i--) Write((int)(value >> (8 * i)));
{
Write((int)(value >> (8 * i)));
}
} }
internal void WriteID(int id) internal void WriteID(int id)
@@ -413,27 +380,17 @@ namespace Claunia.PropertyList
internal static bool IsSerializationPrimitive(NSString obj) internal static bool IsSerializationPrimitive(NSString obj)
{ {
var content = obj.Content; string content = obj.Content;
// This is a list of "special" values which are only added once to a binary property // This is a list of "special" values which are only added once to a binary property
// list, and can be referenced multiple times. // list, and can be referenced multiple times.
return content == "$class" return content == "$class" || content == "$classes" || content == "$classname" ||
|| content == "$classes" content == "NS.objects" ||
|| content == "$classname" content == "NS.keys" || content == "NS.base" || content == "NS.relative" ||
|| content == "NS.objects" content == "NS.string" ||
|| content == "NS.keys" content == "NSURL" || content == "NSDictionary" || content == "NSObject" ||
|| content == "NS.base" content == "NSMutableDictionary" || content == "NSMutableArray" || content == "NSArray" ||
|| content == "NS.relative" content == "NSUUID" || content == "NSKeyedArchiver" || content == "NSMutableString";
|| content == "NS.string"
|| content == "NSURL"
|| content == "NSDictionary"
|| content == "NSObject"
|| content == "NSMutableDictionary"
|| content == "NSMutableArray"
|| content == "NSArray"
|| content == "NSUUID"
|| content == "NSKeyedArchiver"
|| content == "NSMutableString";
} }
internal static bool IsSerializationPrimitive(NSNumber n) internal static bool IsSerializationPrimitive(NSNumber n)
@@ -441,5 +398,4 @@ namespace Claunia.PropertyList
return n.isBoolean(); return n.isBoolean();
} }
} }
} }

View File

@@ -18,121 +18,108 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
partial class NSArray : IList<NSObject> partial class NSArray : IList<NSObject>
{ {
/// <inheritdoc/> /// <inheritdoc />
public NSObject this[int index] public NSObject this[int index]
{ {
get get => array[index];
{
return this.array[index];
}
set set => array[index] = value;
{
this.array[index] = value;
}
} }
/// <inheritdoc/> /// <inheritdoc />
public bool IsReadOnly public bool IsReadOnly => false;
{
get
{
return false;
}
}
public void Add(object item) /// <inheritdoc />
{
this.Add(NSObject.Wrap(item));
}
/// <inheritdoc/>
public void Add(NSObject item) public void Add(NSObject item)
{ {
this.array.Add(item); array.Add(item);
} }
/// <inheritdoc/> /// <inheritdoc />
public void Clear() public void Clear()
{ {
this.array.Clear(); array.Clear();
} }
public bool Contains(object item) /// <inheritdoc />
{
return this.Contains(NSObject.Wrap(item));
}
/// <inheritdoc/>
public bool Contains(NSObject item) public bool Contains(NSObject item)
{ {
return this.array.Contains(item); return array.Contains(item);
} }
/// <inheritdoc/> /// <inheritdoc />
public void CopyTo(NSObject[] array, int arrayIndex) public void CopyTo(NSObject[] array, int arrayIndex)
{ {
this.array.CopyTo(array, arrayIndex); this.array.CopyTo(array, arrayIndex);
} }
/// <inheritdoc/> /// <inheritdoc />
public IEnumerator<NSObject> GetEnumerator() public IEnumerator<NSObject> GetEnumerator()
{ {
return this.array.GetEnumerator(); return array.GetEnumerator();
}
/// <inheritdoc />
public int IndexOf(NSObject item)
{
return array.IndexOf(item);
}
/// <inheritdoc />
public void Insert(int index, NSObject item)
{
array.Insert(index, item);
}
/// <inheritdoc />
public bool Remove(NSObject item)
{
return array.Remove(item);
}
/// <inheritdoc />
public void RemoveAt(int index)
{
array.RemoveAt(index);
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return array.GetEnumerator();
}
public void Add(object item)
{
Add(Wrap(item));
}
public bool Contains(object item)
{
return Contains(Wrap(item));
} }
public int IndexOf(object item) public int IndexOf(object item)
{ {
return this.array.IndexOf(NSObject.Wrap(item)); return array.IndexOf(Wrap(item));
}
/// <inheritdoc/>
public int IndexOf(NSObject item)
{
return this.array.IndexOf(item);
} }
public void Insert(int index, object item) public void Insert(int index, object item)
{ {
this.Insert(index, NSObject.Wrap(item)); Insert(index, Wrap(item));
}
/// <inheritdoc/>
public void Insert(int index, NSObject item)
{
this.array.Insert(index, item);
} }
public bool Remove(object item) public bool Remove(object item)
{ {
return this.Remove(NSObject.Wrap(item)); return Remove(Wrap(item));
}
/// <inheritdoc/>
public bool Remove(NSObject item)
{
return this.array.Remove(item);
}
/// <inheritdoc/>
public void RemoveAt(int index)
{
this.array.RemoveAt(index);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return this.array.GetEnumerator();
} }
} }
} }

View File

@@ -22,15 +22,15 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// Represents an Array. /// Represents an Array.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
@@ -39,7 +39,7 @@ namespace Claunia.PropertyList
List<NSObject> array; List<NSObject> array;
/// <summary> /// <summary>
/// Creates an empty array of the given length. /// Creates an empty array of the given length.
/// </summary> /// </summary>
/// <param name="length">The number of elements this array will be able to hold.</param> /// <param name="length">The number of elements this array will be able to hold.</param>
public NSArray(int length) public NSArray(int length)
@@ -48,7 +48,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Creates a array from an existing one /// Creates a array from an existing one
/// </summary> /// </summary>
/// <param name="a">The array which should be wrapped by the NSArray.</param> /// <param name="a">The array which should be wrapped by the NSArray.</param>
public NSArray(params NSObject[] a) public NSArray(params NSObject[] a)
@@ -57,7 +57,13 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Returns the object stored at the given index. /// Returns the size of the array.
/// </summary>
/// <value>The number of elements that this array can store.</value>
public int Count => array.Count;
/// <summary>
/// Returns the object stored at the given index.
/// </summary> /// </summary>
/// <returns>The object at the given index.</returns> /// <returns>The object at the given index.</returns>
/// <param name="i">The index of the object.</param> /// <param name="i">The index of the object.</param>
@@ -68,33 +74,33 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Remove the i-th element from the array. /// Remove the i-th element from the array.
/// The array will be resized. /// The array will be resized.
/// </summary> /// </summary>
/// <param name="i">The index of the object</param> /// <param name="i">The index of the object</param>
[Obsolete] [Obsolete]
public void Remove(int i) public void Remove(int i)
{ {
this.array.RemoveAt(i); array.RemoveAt(i);
} }
/// <summary> /// <summary>
/// Stores an object at the specified index. /// Stores an object at the specified index.
/// If there was another object stored at that index it will be replaced. /// If there was another object stored at that index it will be replaced.
/// </summary> /// </summary>
/// <param name="key">The index where to store the object.</param> /// <param name="key">The index where to store the object.</param>
/// <param name="value">The object.</param> /// <param name="value">The object.</param>
[Obsolete] [Obsolete]
public void SetValue(int key, Object value) public void SetValue(int key, object value)
{ {
if (value == null) if(value == null) throw new ArgumentNullException("value", "Cannot add null values to an NSArray!");
throw new ArgumentNullException("value", "Cannot add null values to an NSArray!");
array[key] = NSObject.Wrap(value); array[key] = Wrap(value);
} }
/// <summary> /// <summary>
/// Returns the array of NSObjects represented by this NSArray. /// Returns the array of NSObjects represented by this NSArray.
/// Any changes to the values of this array will also affect the NSArray. /// Any changes to the values of this array will also affect the NSArray.
/// </summary> /// </summary>
/// <returns>The actual array represented by this NSArray.</returns> /// <returns>The actual array represented by this NSArray.</returns>
[Obsolete] [Obsolete]
@@ -104,82 +110,61 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Returns the size of the array. /// Checks whether an object is present in the array or whether it is equal
/// </summary> /// to any of the objects in the array.
/// <value>The number of elements that this array can store.</value>
public int Count
{
get
{
return array.Count;
}
}
/// <summary>
/// Checks whether an object is present in the array or whether it is equal
/// to any of the objects in the array.
/// </summary> /// </summary>
/// <returns><c>true</c>, when the object could be found. <c>false</c> otherwise.</returns> /// <returns><c>true</c>, when the object could be found. <c>false</c> otherwise.</returns>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
[Obsolete] [Obsolete]
public bool ContainsObject(Object obj) public bool ContainsObject(object obj)
{ {
NSObject nso = NSObject.Wrap(obj); NSObject nso = Wrap(obj);
foreach (NSObject elem in array) foreach(NSObject elem in array)
{ if(elem.Equals(nso))
if (elem.Equals(nso))
{
return true; return true;
}
}
return false; return false;
} }
/// <summary> /// <summary>
/// Searches for an object in the array. If it is found its index will be /// Searches for an object in the array. If it is found its index will be
/// returned. This method also returns an index if the object is not the same /// returned. This method also returns an index if the object is not the same
/// as the one stored in the array but has equal contents. /// as the one stored in the array but has equal contents.
/// </summary> /// </summary>
/// <returns>The index of the object, if it was found. -1 otherwise.</returns> /// <returns>The index of the object, if it was found. -1 otherwise.</returns>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
[Obsolete] [Obsolete]
public int IndexOfObject(Object obj) public int IndexOfObject(object obj)
{ {
NSObject nso = NSObject.Wrap(obj); NSObject nso = Wrap(obj);
for (int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
{ if(array[i].Equals(nso))
if (array[i].Equals(nso))
{
return i; return i;
}
}
return -1; return -1;
} }
/// <summary> /// <summary>
/// Searches for an object in the array. If it is found its index will be /// Searches for an object in the array. If it is found its index will be
/// returned. This method only returns the index of an object that is /// returned. This method only returns the index of an object that is
/// <b>identical</b> to the given one. Thus objects that might contain the /// <b>identical</b> to the given one. Thus objects that might contain the
/// same value as the given one will not be considered. /// same value as the given one will not be considered.
/// </summary> /// </summary>
/// <returns>The index of the object, if it was found. -1 otherwise.</returns> /// <returns>The index of the object, if it was found. -1 otherwise.</returns>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
[Obsolete] [Obsolete]
public int IndexOfIdenticalObject(Object obj) public int IndexOfIdenticalObject(object obj)
{ {
NSObject nso = NSObject.Wrap(obj); NSObject nso = Wrap(obj);
for (int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
{ if(array[i] == nso)
if (array[i] == nso)
{
return i; return i;
}
}
return -1; return -1;
} }
/// <summary> /// <summary>
/// Returns the last object contained in this array. /// Returns the last object contained in this array.
/// </summary> /// </summary>
/// <returns>The value of the highest index in the array.</returns> /// <returns>The value of the highest index in the array.</returns>
public NSObject LastObject() public NSObject LastObject()
@@ -188,8 +173,8 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Returns a new array containing only the values stored at the given /// Returns a new array containing only the values stored at the given
/// indices. The values are sorted by their index. /// indices. The values are sorted by their index.
/// </summary> /// </summary>
/// <returns>The new array containing the objects stored at the given indices.</returns> /// <returns>The new array containing the objects stored at the given indices.</returns>
/// <param name="indexes">The indices of the objects.</param> /// <param name="indexes">The indices of the objects.</param>
@@ -197,39 +182,39 @@ namespace Claunia.PropertyList
{ {
NSObject[] result = new NSObject[indexes.Length]; NSObject[] result = new NSObject[indexes.Length];
Array.Sort(indexes); Array.Sort(indexes);
for (int i = 0; i < indexes.Length; i++) for(int i = 0; i < indexes.Length; i++) result[i] = array[indexes[i]];
result[i] = array[indexes[i]];
return result; return result;
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="Claunia.PropertyList.NSArray"/>. /// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="Claunia.PropertyList.NSArray"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current /// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSArray"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSArray" />.
public override bool Equals(Object obj) /// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{ {
if (obj.GetType().Equals(typeof(NSArray))) if(obj.GetType().Equals(typeof(NSArray))) return ArrayEquals((NSArray)obj, this);
{
return ArrayEquals(((NSArray)obj), this); NSObject nso = Wrap(obj);
} if(nso.GetType().Equals(typeof(NSArray))) return ArrayEquals((NSArray)nso, this);
else
{
NSObject nso = NSObject.Wrap(obj);
if (nso.GetType().Equals(typeof(NSArray)))
{
return ArrayEquals(((NSArray)nso), this);
}
}
return false; return false;
} }
/// <summary> /// <summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSArray"/> object. /// Serves as a hash function for a <see cref="Claunia.PropertyList.NSArray" /> object.
/// </summary> /// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// <returns>
/// hash table.</returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
@@ -241,12 +226,13 @@ namespace Claunia.PropertyList
{ {
Indent(xml, level); Indent(xml, level);
xml.Append("<array>"); xml.Append("<array>");
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
foreach (NSObject o in array) foreach(NSObject o in array)
{ {
o.ToXml(xml, level + 1); o.ToXml(xml, level + 1);
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
} }
Indent(xml, level); Indent(xml, level);
xml.Append("</array>"); xml.Append("</array>");
} }
@@ -254,30 +240,26 @@ namespace Claunia.PropertyList
internal override void AssignIDs(BinaryPropertyListWriter outPlist) internal override void AssignIDs(BinaryPropertyListWriter outPlist)
{ {
base.AssignIDs(outPlist); base.AssignIDs(outPlist);
foreach (NSObject obj in array) foreach(NSObject obj in array) obj.AssignIDs(outPlist);
{
obj.AssignIDs(outPlist);
}
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.WriteIntHeader(0xA, array.Count); outPlist.WriteIntHeader(0xA, array.Count);
foreach (NSObject obj in array) foreach(NSObject obj in array) outPlist.WriteID(outPlist.GetID(obj));
{
outPlist.WriteID(outPlist.GetID(obj));
}
} }
/// <summary> /// <summary>
/// <para> /// <para>
/// Generates a valid ASCII property list which has this NSArray as its /// Generates a valid ASCII property list which has this NSArray as its
/// root object. /// root object.
/// </para><para> /// </para>
/// The generated property list complies with the format as /// <para>
/// described in https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html /// The generated property list complies with the format as
/// Property List Programming Guide - Old-Style ASCII Property Lists. /// described in
/// </para> /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
/// Property List Programming Guide - Old-Style ASCII Property Lists.
/// </para>
/// </summary> /// </summary>
/// <returns>ASCII representation of this object.</returns> /// <returns>ASCII representation of this object.</returns>
public string ToASCIIPropertyList() public string ToASCIIPropertyList()
@@ -289,14 +271,16 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// <para> /// <para>
/// Generates a valid ASCII property list in GnuStep format which has this /// Generates a valid ASCII property list in GnuStep format which has this
/// NSArray as its root object. /// NSArray as its root object.
/// </para><para> /// </para>
/// The generated property list complies with /// <para>
/// the format as described in http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html /// The generated property list complies with
/// GnuStep - NSPropertyListSerialization class documentation. /// the format as described in
/// </para> /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html
/// GnuStep - NSPropertyListSerialization class documentation.
/// </para>
/// </summary> /// </summary>
/// <returns>GnuStep ASCII representation of this object.</returns> /// <returns>GnuStep ASCII representation of this object.</returns>
public string ToGnuStepASCIIPropertyList() public string ToGnuStepASCIIPropertyList()
@@ -312,11 +296,11 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for (int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
{ {
Type objClass = array[i].GetType(); Type objClass = array[i].GetType();
if ((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || objClass.Equals(typeof(NSData))) if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
&& indexOfLastNewLine != ascii.Length) objClass.Equals(typeof(NSData))) && indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -324,20 +308,19 @@ namespace Claunia.PropertyList
} }
else else
{ {
if (i != 0) if(i != 0) ascii.Append(" ");
ascii.Append(" ");
array[i].ToASCII(ascii, 0); array[i].ToASCII(ascii, 0);
} }
if (i != array.Count - 1) if(i != array.Count - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if (ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
} }
ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
} }
@@ -346,11 +329,11 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for (int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
{ {
Type objClass = array[i].GetType(); Type objClass = array[i].GetType();
if ((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || objClass.Equals(typeof(NSData))) if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
&& indexOfLastNewLine != ascii.Length) objClass.Equals(typeof(NSData))) && indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -358,43 +341,45 @@ namespace Claunia.PropertyList
} }
else else
{ {
if (i != 0) if(i != 0) ascii.Append(" ");
ascii.Append(" ");
array[i].ToASCIIGnuStep(ascii, 0); array[i].ToASCIIGnuStep(ascii, 0);
} }
if (i != array.Count - 1) if(i != array.Count - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if (ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
} }
ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current <see cref="Claunia.PropertyList.NSArray"/>. /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="Claunia.PropertyList.NSObject"/> to compare with the current <see cref="Claunia.PropertyList.NSArray"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSArray"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSArray" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSArray" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if (!(obj is NSArray)) if(!(obj is NSArray)) return false;
return false;
if (array.Count != ((NSArray)obj).array.Count) if(array.Count != ((NSArray)obj).array.Count) return false;
return false;
for (int i = 0; i < array.Count; i++) for(int i = 0; i < array.Count; i++)
if (!array[i].Equals(((NSArray)obj)[i])) if(!array[i].Equals(((NSArray)obj)[i]))
return false; return false;
return true; return true;
} }
} }
} }

View File

@@ -22,6 +22,7 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.IO; using System.IO;
using System.Text; using System.Text;
@@ -29,7 +30,7 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// NSData objects are wrappers for byte buffers /// NSData objects are wrappers for byte buffers
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
@@ -37,117 +38,109 @@ namespace Claunia.PropertyList
{ {
// In the XML property list format, the base-64 encoded data is split across multiple lines. // In the XML property list format, the base-64 encoded data is split across multiple lines.
// Each line contains 68 characters. // Each line contains 68 characters.
private const int DataLineLength = 68; const int DataLineLength = 68;
readonly byte[] bytes;
/// <summary> /// <summary>
/// Creates the NSData object from the binary representation of it. /// Creates the NSData object from the binary representation of it.
/// </summary> /// </summary>
/// <param name="bytes">The raw data contained in the NSData object.</param> /// <param name="bytes">The raw data contained in the NSData object.</param>
public NSData(byte[] bytes) public NSData(byte[] bytes)
{ {
this.bytes = bytes; Bytes = bytes;
} }
/// <summary> /// <summary>
/// Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes. /// Creates a NSData object from its textual representation, which is a Base64 encoded amount of bytes.
/// </summary> /// </summary>
/// <param name="base64">The Base64 encoded contents of the NSData object.</param> /// <param name="base64">The Base64 encoded contents of the NSData object.</param>
/// <exception cref="FormatException">When the given string is not a proper Base64 formatted string.</exception> /// <exception cref="FormatException">When the given string is not a proper Base64 formatted string.</exception>
public NSData(string base64) public NSData(string base64)
{ {
bytes = Convert.FromBase64String(base64); Bytes = Convert.FromBase64String(base64);
} }
/// <summary> /// <summary>
/// Creates a NSData object from a file. Using the files contents as the contents of this NSData object. /// Creates a NSData object from a file. Using the files contents as the contents of this NSData object.
/// </summary> /// </summary>
/// <param name="file">The file containing the data.</param> /// <param name="file">The file containing the data.</param>
/// <exception cref="FileNotFoundException">If the file could not be found.</exception> /// <exception cref="FileNotFoundException">If the file could not be found.</exception>
/// <exception cref="IOException">If the file could not be read.</exception> /// <exception cref="IOException">If the file could not be read.</exception>
public NSData(FileInfo file) public NSData(FileInfo file)
{ {
bytes = new byte[(int)file.Length]; Bytes = new byte[(int)file.Length];
using (FileStream raf = file.OpenRead()) using(FileStream raf = file.OpenRead()) raf.Read(Bytes, 0, (int)file.Length);
{
raf.Read(bytes, 0, (int)file.Length);
}
} }
/// <summary> /// <summary>
/// The bytes contained in this NSData object. /// The bytes contained in this NSData object.
/// </summary> /// </summary>
/// <value>The data as bytes</value> /// <value>The data as bytes</value>
public byte[] Bytes public byte[] Bytes { get; }
{
get
{
return bytes;
}
}
/// <summary> /// <summary>
/// Gets the amount of data stored in this object. /// Gets the amount of data stored in this object.
/// </summary> /// </summary>
/// <value>The number of bytes contained in this object.</value> /// <value>The number of bytes contained in this object.</value>
public int Length public int Length => Bytes.Length;
{
get
{
return bytes.Length;
}
}
/// <summary> /// <summary>
/// Loads the bytes from this NSData object into a byte buffer. /// Loads the bytes from this NSData object into a byte buffer.
/// </summary> /// </summary>
/// <param name="buf">The byte buffer which will contain the data</param> /// <param name="buf">The byte buffer which will contain the data</param>
/// <param name="length">The amount of data to copy</param> /// <param name="length">The amount of data to copy</param>
public void GetBytes(MemoryStream buf, int length) public void GetBytes(MemoryStream buf, int length)
{ {
buf.Write(bytes, 0, Math.Min(bytes.Length, length)); buf.Write(Bytes, 0, Math.Min(Bytes.Length, length));
} }
/// <summary> /// <summary>
/// Loads the bytes from this NSData object into a byte buffer. /// Loads the bytes from this NSData object into a byte buffer.
/// </summary> /// </summary>
/// <param name="buf">The byte buffer which will contain the data</param> /// <param name="buf">The byte buffer which will contain the data</param>
/// <param name="rangeStart">The start index.</param> /// <param name="rangeStart">The start index.</param>
/// <param name="rangeStop">The stop index.</param> /// <param name="rangeStop">The stop index.</param>
public void GetBytes(MemoryStream buf, int rangeStart, int rangeStop) public void GetBytes(MemoryStream buf, int rangeStart, int rangeStop)
{ {
buf.Write(bytes, rangeStart, Math.Min(bytes.Length, rangeStop)); buf.Write(Bytes, rangeStart, Math.Min(Bytes.Length, rangeStop));
} }
/// <summary> /// <summary>
/// Gets the Base64 encoded data contained in this NSData object. /// Gets the Base64 encoded data contained in this NSData object.
/// </summary> /// </summary>
/// <returns>The Base64 encoded data as a <c>string</c>.</returns> /// <returns>The Base64 encoded data as a <c>string</c>.</returns>
public string GetBase64EncodedData() public string GetBase64EncodedData()
{ {
return Convert.ToBase64String(bytes); return Convert.ToBase64String(Bytes);
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="Claunia.PropertyList.NSData"/>. /// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="Claunia.PropertyList.NSData"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current /// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSData"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSData" />.
public override bool Equals(Object obj) /// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{ {
return obj.GetType().Equals(GetType()) && ArrayEquals(((NSData)obj).bytes, bytes); return obj.GetType().Equals(GetType()) && ArrayEquals(((NSData)obj).Bytes, Bytes);
} }
/// <summary> /// <summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSData"/> object. /// Serves as a hash function for a <see cref="Claunia.PropertyList.NSData" /> object.
/// </summary> /// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// <returns>
/// hash table.</returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 5; int hash = 5;
hash = 67 * hash + bytes.GetHashCode(); hash = 67 * hash + Bytes.GetHashCode();
return hash; return hash;
} }
@@ -155,25 +148,24 @@ namespace Claunia.PropertyList
{ {
Indent(xml, level); Indent(xml, level);
xml.Append("<data>"); xml.Append("<data>");
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
string base64 = GetBase64EncodedData(); string base64 = GetBase64EncodedData();
foreach (string line in base64.Split('\n')) foreach(string line in base64.Split('\n'))
{ for(int offset = 0; offset < base64.Length; offset += DataLineLength)
for (int offset = 0; offset < base64.Length; offset += DataLineLength)
{ {
Indent(xml, level); Indent(xml, level);
xml.Append(line.Substring(offset, Math.Min(DataLineLength, line.Length - offset))); xml.Append(line.Substring(offset, Math.Min(DataLineLength, line.Length - offset)));
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
} }
}
Indent(xml, level); Indent(xml, level);
xml.Append("</data>"); xml.Append("</data>");
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.WriteIntHeader(0x4, bytes.Length); outPlist.WriteIntHeader(0x4, Bytes.Length);
outPlist.Write(bytes); outPlist.Write(Bytes);
} }
internal override void ToASCII(StringBuilder ascii, int level) internal override void ToASCII(StringBuilder ascii, int level)
@@ -181,20 +173,18 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.DATA_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.DATA_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for (int i = 0; i < bytes.Length; i++) for(int i = 0; i < Bytes.Length; i++)
{ {
int b = bytes[i] & 0xFF; int b = Bytes[i] & 0xFF;
ascii.Append(String.Format("{0:x2}", b)); ascii.Append(string.Format("{0:x2}", b));
if (ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
else if ((i + 1) % 2 == 0 && i != bytes.Length - 1) else if((i + 1) % 2 == 0 && i != Bytes.Length - 1) ascii.Append(" ");
{
ascii.Append(" ");
}
} }
ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN); ascii.Append(ASCIIPropertyListParser.DATA_END_TOKEN);
} }
@@ -204,28 +194,32 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current <see cref="Claunia.PropertyList.NSData"/>. /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="Claunia.PropertyList.NSObject"/> to compare with the current <see cref="Claunia.PropertyList.NSData"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSData"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSData" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSData" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if (!(obj is NSData)) if(!(obj is NSData)) return false;
return false;
return ArrayEquals(bytes, ((NSData)obj).Bytes); return ArrayEquals(Bytes, ((NSData)obj).Bytes);
} }
static public explicit operator byte[](NSData value) public static explicit operator byte[](NSData value)
{ {
return value.bytes; return value.Bytes;
} }
static public explicit operator NSData(byte[] value) public static explicit operator NSData(byte[] value)
{ {
return new NSData(value); return new NSData(value);
} }
} }
} }

View File

@@ -22,20 +22,20 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Globalization;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// Represents a date /// Represents a date
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class NSDate : NSObject public class NSDate : NSObject
{ {
readonly DateTime date;
static readonly DateTime EPOCH = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc); static readonly DateTime EPOCH = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// The datetime ends with 'Z', which indicates UTC time. To make sure .NET // The datetime ends with 'Z', which indicates UTC time. To make sure .NET
@@ -43,29 +43,59 @@ namespace Claunia.PropertyList
static readonly string sdfDefault = "yyyy-MM-dd'T'HH:mm:ssK"; static readonly string sdfDefault = "yyyy-MM-dd'T'HH:mm:ssK";
static readonly string sdfGnuStep = "yyyy-MM-dd HH:mm:ss zzz"; static readonly string sdfGnuStep = "yyyy-MM-dd HH:mm:ss zzz";
static readonly System.Globalization.CultureInfo provider = System.Globalization.CultureInfo.InvariantCulture; static readonly CultureInfo provider = CultureInfo.InvariantCulture;
/// <summary> /// <summary>
/// Parses the XML date string and creates a .NET DateTime object from it. /// Creates a date from its binary representation.
/// </summary>
/// <param name="bytes">bytes The date bytes</param>
public NSDate(ReadOnlySpan<byte> bytes)
{
//dates are 8 byte big-endian double, seconds since the epoch
Date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes));
}
/// <summary>
/// Parses a date from its textual representation.
/// That representation has the following pattern: <code>yyyy-MM-dd'T'HH:mm:ss'Z'</code>
/// </summary>
/// <param name="textRepresentation">The textual representation of the date (ISO 8601 format)</param>
/// <exception cref="FormatException">When the date could not be parsed, i.e. it does not match the expected pattern.</exception>
public NSDate(string textRepresentation)
{
Date = ParseDateString(textRepresentation);
}
/// <summary>
/// Creates a NSDate from a .NET DateTime
/// </summary>
/// <param name="d">The date</param>
public NSDate(DateTime d)
{
Date = d;
}
/// <summary>
/// Gets the date.
/// </summary>
/// <returns>The date.</returns>
public DateTime Date { get; }
/// <summary>
/// Parses the XML date string and creates a .NET DateTime object from it.
/// </summary> /// </summary>
/// <returns>The parsed Date</returns> /// <returns>The parsed Date</returns>
/// <param name="textRepresentation">The date string as found in the XML property list</param> /// <param name="textRepresentation">The date string as found in the XML property list</param>
/// <exception cref="FormatException">Given string cannot be parsed</exception> /// <exception cref="FormatException">Given string cannot be parsed</exception>
static DateTime ParseDateString(string textRepresentation) static DateTime ParseDateString(string textRepresentation)
{ {
try try { return DateTime.ParseExact(textRepresentation, sdfDefault, provider); }
{ catch(FormatException) { return DateTime.ParseExact(textRepresentation, sdfGnuStep, provider); }
return DateTime.ParseExact(textRepresentation, sdfDefault, provider);
}
catch (FormatException)
{
return DateTime.ParseExact(textRepresentation, sdfGnuStep, provider);
}
} }
/// <summary> /// <summary>
/// Generates a String representation of a .NET DateTime object. The string /// Generates a String representation of a .NET DateTime object. The string
/// is formatted according to the specification for XML property list dates. /// is formatted according to the specification for XML property list dates.
/// </summary> /// </summary>
/// <param name="date">The date which should be represented.</param> /// <param name="date">The date which should be represented.</param>
/// <returns>The string representation of the date.</returns> /// <returns>The string representation of the date.</returns>
@@ -75,9 +105,9 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Generates a String representation of a .NET DateTime object. The string /// Generates a String representation of a .NET DateTime object. The string
/// is formatted according to the specification for GnuStep ASCII property /// is formatted according to the specification for GnuStep ASCII property
/// list dates. /// list dates.
/// </summary> /// </summary>
/// <param name="date">The date which should be represented.</param> /// <param name="date">The date which should be represented.</param>
/// <returns>The string representation of the date.</returns> /// <returns>The string representation of the date.</returns>
@@ -87,96 +117,62 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Creates a date from its binary representation. /// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </summary> /// </summary>
/// <param name="bytes">bytes The date bytes</param> /// <param name="obj">
public NSDate(ReadOnlySpan<byte> bytes) /// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{ {
//dates are 8 byte big-endian double, seconds since the epoch return obj.GetType().Equals(GetType()) && Date.Equals(((NSDate)obj).Date);
date = EPOCH.AddSeconds(BinaryPropertyListParser.ParseDouble(bytes));
} }
/// <summary> /// <summary>
/// Parses a date from its textual representation. /// Serves as a hash function for a <see cref="Claunia.PropertyList.NSDate" /> object.
/// That representation has the following pattern: <code>yyyy-MM-dd'T'HH:mm:ss'Z'</code>
/// </summary> /// </summary>
/// <param name="textRepresentation">The textual representation of the date (ISO 8601 format)</param> /// <returns>
/// <exception cref="FormatException">When the date could not be parsed, i.e. it does not match the expected pattern.</exception> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
public NSDate(String textRepresentation) /// hash table.
{ /// </returns>
date = ParseDateString(textRepresentation);
}
/// <summary>
/// Creates a NSDate from a .NET DateTime
/// </summary>
/// <param name="d">The date</param>
public NSDate(DateTime d)
{
date = d;
}
/// <summary>
/// Gets the date.
/// </summary>
/// <returns>The date.</returns>
public DateTime Date
{
get
{
return date;
}
}
/// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="Claunia.PropertyList.NSDate"/>.
/// </summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="Claunia.PropertyList.NSDate"/>.</param>
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate"/>; otherwise, <c>false</c>.</returns>
public override bool Equals(Object obj)
{
return obj.GetType().Equals(GetType()) && date.Equals(((NSDate)obj).Date);
}
/// <summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSDate"/> object.
/// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.</returns>
public override int GetHashCode() public override int GetHashCode()
{ {
return date.GetHashCode(); return Date.GetHashCode();
} }
internal override void ToXml(StringBuilder xml, int level) internal override void ToXml(StringBuilder xml, int level)
{ {
Indent(xml, level); Indent(xml, level);
xml.Append("<date>"); xml.Append("<date>");
xml.Append(MakeDateString(date)); xml.Append(MakeDateString(Date));
xml.Append("</date>"); xml.Append("</date>");
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.Write(0x33); outPlist.Write(0x33);
outPlist.WriteDouble((date - EPOCH).TotalSeconds); outPlist.WriteDouble((Date - EPOCH).TotalSeconds);
} }
/// <summary> /// <summary>
/// Generates a string representation of the date. /// Generates a string representation of the date.
/// </summary> /// </summary>
/// <returns>A string representation of the date.</returns> /// <returns>A string representation of the date.</returns>
public override String ToString() public override string ToString()
{ {
return date.ToString(); return Date.ToString();
} }
internal override void ToASCII(StringBuilder ascii, int level) internal override void ToASCII(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
ascii.Append("\""); ascii.Append("\"");
ascii.Append(MakeDateString(date)); ascii.Append(MakeDateString(Date));
ascii.Append("\""); ascii.Append("\"");
} }
@@ -184,35 +180,39 @@ namespace Claunia.PropertyList
{ {
Indent(ascii, level); Indent(ascii, level);
ascii.Append("<*D"); ascii.Append("<*D");
ascii.Append(MakeDateStringGnuStep(date)); ascii.Append(MakeDateStringGnuStep(Date));
ascii.Append(">"); ascii.Append(">");
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current <see cref="Claunia.PropertyList.NSDate"/>. /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="Claunia.PropertyList.NSObject"/> to compare with the current <see cref="Claunia.PropertyList.NSDate"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSDate"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSDate" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDate" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if (!(obj is NSDate)) if(!(obj is NSDate)) return false;
return false;
int equality = DateTime.Compare(date, ((NSDate)obj).Date); int equality = DateTime.Compare(Date, ((NSDate)obj).Date);
return equality == 0; return equality == 0;
} }
static public explicit operator DateTime (NSDate value) public static explicit operator DateTime(NSDate value)
{ {
return value.date; return value.Date;
} }
static public explicit operator NSDate(DateTime value) public static explicit operator NSDate(DateTime value)
{ {
return new NSDate(value); return new NSDate(value);
} }
} }
} }

View File

@@ -22,22 +22,26 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>
/// A NSDictionary is a collection of keys and values, essentially a Dictionary. /// A NSDictionary is a collection of keys and values, essentially a Dictionary.
/// The keys are simple Strings whereas the values can be any kind of NSObject. /// The keys are simple Strings whereas the values can be any kind of NSObject.
/// </para><para> /// </para>
/// You can access the keys through the function <see cref="Keys"/>. /// <para>
/// </para><para> /// You can access the keys through the function <see cref="Keys" />.
/// Access to the objects stored for each key is given through the function /// </para>
/// <see cref="ObjectForKey"/>. /// <para>
/// </para> /// Access to the objects stored for each key is given through the function
/// <see cref="ObjectForKey" />.
/// </para>
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
@@ -50,10 +54,10 @@ namespace Claunia.PropertyList
readonly Dictionary<string, NSString> keys; readonly Dictionary<string, NSString> keys;
/// <summary> /// <summary>
/// Creates a new empty NSDictionary with a specific capacity. /// Creates a new empty NSDictionary with a specific capacity.
/// </summary> /// </summary>
/// <param name="capacity"> /// <param name="capacity">
/// The capacity of the dictionary. /// The capacity of the dictionary.
/// </param> /// </param>
public NSDictionary(int capacity) public NSDictionary(int capacity)
{ {
@@ -62,17 +66,38 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Creates a new empty NSDictionary. /// Creates a new empty NSDictionary.
/// </summary> /// </summary>
public NSDictionary() public NSDictionary() : this(0) { }
: this(0)
{
}
/// <summary> /// <summary>
/// Gets the hashmap which stores the keys and values of this dictionary. /// Gets a value indicating whether this instance is empty.
/// Changes to the hashmap's contents are directly reflected in this /// </summary>
/// dictionary. /// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value>
public bool IsEmpty => dict.Count == 0;
#region IEnumerable implementation
/// <summary>
/// Gets the enumerator.
/// </summary>
/// <returns>The enumerator.</returns>
public IEnumerator<KeyValuePair<string, NSObject>> GetEnumerator()
{
return dict.GetEnumerator();
}
#endregion
#region IEnumerable implementation
IEnumerator IEnumerable.GetEnumerator()
{
return dict.GetEnumerator();
}
#endregion
/// <summary>
/// Gets the hashmap which stores the keys and values of this dictionary.
/// Changes to the hashmap's contents are directly reflected in this
/// dictionary.
/// </summary> /// </summary>
/// <returns>The hashmap which is used by this dictionary to store its contents.</returns> /// <returns>The hashmap which is used by this dictionary to store its contents.</returns>
public Dictionary<string, NSObject> GetDictionary() public Dictionary<string, NSObject> GetDictionary()
@@ -81,7 +106,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Gets the NSObject stored for the given key. /// Gets the NSObject stored for the given key.
/// </summary> /// </summary>
/// <returns>The object.</returns> /// <returns>The object.</returns>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
@@ -92,77 +117,68 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is empty. /// Checks if the specified object key is contained in the current instance.
/// </summary>
/// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value>
public bool IsEmpty
{
get
{
return dict.Count == 0;
}
}
/// <summary>
/// Checks if the specified object key is contained in the current instance.
/// </summary> /// </summary>
/// <returns><c>true</c>, if key is contained, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if key is contained, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
public bool ContainsKey(Object key) public bool ContainsKey(object key)
{ {
return key is string && dict.ContainsKey((string)key); return key is string && dict.ContainsKey((string)key);
} }
/// <summary> /// <summary>
/// Removes the item corresponding to the specified key from the current instance, if found. /// Removes the item corresponding to the specified key from the current instance, if found.
/// </summary> /// </summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
/// <returns><c>true</c>, if removed, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if removed, <c>false</c> otherwise.</returns>
public bool Remove(Object key) public bool Remove(object key)
{ {
return key is string && dict.Remove((string)key); return key is string && dict.Remove((string)key);
} }
/// <summary> /// <summary>
/// Gets the <see cref="NSObject"/> corresponding to the specified key from the current instance. /// Gets the <see cref="NSObject" /> corresponding to the specified key from the current instance.
/// </summary> /// </summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
/// <returns>The object corresponding to the specified key, null if not found in the current instance.</returns> /// <returns>The object corresponding to the specified key, null if not found in the current instance.</returns>
public NSObject Get(Object key) public NSObject Get(object key)
{ {
if (key is string) if(key is string) return ObjectForKey((string)key);
return ObjectForKey((string)key);
return null; return null;
} }
/// <summary> /// <summary>
/// Checks if the current instance contains the object corresponding to the specified key. /// Checks if the current instance contains the object corresponding to the specified key.
/// </summary> /// </summary>
/// <returns><c>true</c>, if value is contained, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if value is contained, <c>false</c> otherwise.</returns>
/// <param name="value">Object to search up in the current instance.</param> /// <param name="value">Object to search up in the current instance.</param>
public bool ContainsValue(Object value) public bool ContainsValue(object value)
{ {
if (value == null) if(value == null) return false;
return false;
NSObject wrap = NSObject.Wrap(value); NSObject wrap = Wrap(value);
return dict.ContainsValue(wrap); return dict.ContainsValue(wrap);
} }
/// <summary> /// <summary>
/// Puts a new key-value pair into this dictionary. /// Puts a new key-value pair into this dictionary.
/// If the value is null, no operation will be performed on the dictionary. /// If the value is null, no operation will be performed on the dictionary.
/// </summary> /// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="obj">The value. Supported object types are numbers, byte-arrays, dates, strings and arrays or sets of those.</param> /// <param name="obj">
public void Add(String key, Object obj) /// The value. Supported object types are numbers, byte-arrays, dates, strings and arrays or sets of
/// those.
/// </param>
public void Add(string key, object obj)
{ {
if (obj == null) if(obj == null) return;
return;
Add(key, NSObject.Wrap(obj)); Add(key, Wrap(obj));
} }
/// <summary> /// <summary>
/// Puts a new key-value pair into this dictionary. /// Puts a new key-value pair into this dictionary.
/// </summary> /// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="obj">The value.</param> /// <param name="obj">The value.</param>
@@ -172,7 +188,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Puts a new key-value pair into this dictionary. /// Puts a new key-value pair into this dictionary.
/// </summary> /// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="obj">The value.</param> /// <param name="obj">The value.</param>
@@ -182,7 +198,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Puts a new key-value pair into this dictionary. /// Puts a new key-value pair into this dictionary.
/// </summary> /// </summary>
/// <param name="key">The key.</param> /// <param name="key">The key.</param>
/// <param name="obj">The value.</param> /// <param name="obj">The value.</param>
@@ -192,153 +208,145 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Checks whether a given value is contained in this dictionary. /// Checks whether a given value is contained in this dictionary.
/// </summary> /// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(string val) public bool ContainsValue(string val)
{ {
foreach (NSObject o in dict.Values) foreach(NSObject o in dict.Values)
{ if(o.GetType().Equals(typeof(NSString)))
if (o.GetType().Equals(typeof(NSString)))
{ {
NSString str = (NSString)o; NSString str = (NSString)o;
if (str.Content.Equals(val)) if(str.Content.Equals(val)) return true;
return true;
} }
}
return false; return false;
} }
/// <summary> /// <summary>
/// Checks whether a given value is contained in this dictionary. /// Checks whether a given value is contained in this dictionary.
/// </summary> /// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(long val) public bool ContainsValue(long val)
{ {
foreach (NSObject o in dict.Values) foreach(NSObject o in dict.Values)
{ if(o.GetType().Equals(typeof(NSNumber)))
if (o.GetType().Equals(typeof(NSNumber)))
{ {
NSNumber num = (NSNumber)o; NSNumber num = (NSNumber)o;
if (num.isInteger() && num.ToInt() == val) if(num.isInteger() && num.ToInt() == val) return true;
return true;
} }
}
return false; return false;
} }
/// <summary> /// <summary>
/// Checks whether a given value is contained in this dictionary. /// Checks whether a given value is contained in this dictionary.
/// </summary> /// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(double val) public bool ContainsValue(double val)
{ {
foreach (NSObject o in dict.Values) foreach(NSObject o in dict.Values)
{ if(o.GetType().Equals(typeof(NSNumber)))
if (o.GetType().Equals(typeof(NSNumber)))
{ {
NSNumber num = (NSNumber)o; NSNumber num = (NSNumber)o;
if (num.isReal() && num.ToDouble() == val) if(num.isReal() && num.ToDouble() == val) return true;
return true;
} }
}
return false; return false;
} }
/// <summary> /// <summary>
/// Checks whether a given value is contained in this dictionary. /// Checks whether a given value is contained in this dictionary.
/// </summary> /// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(bool val) public bool ContainsValue(bool val)
{ {
foreach (NSObject o in dict.Values) foreach(NSObject o in dict.Values)
{ if(o.GetType().Equals(typeof(NSNumber)))
if (o.GetType().Equals(typeof(NSNumber)))
{ {
NSNumber num = (NSNumber)o; NSNumber num = (NSNumber)o;
if (num.isBoolean() && num.ToBool() == val) if(num.isBoolean() && num.ToBool() == val) return true;
return true;
} }
}
return false; return false;
} }
/// <summary> /// <summary>
/// Checks whether a given value is contained in this dictionary. /// Checks whether a given value is contained in this dictionary.
/// </summary> /// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(DateTime val) public bool ContainsValue(DateTime val)
{ {
foreach (NSObject o in dict.Values) foreach(NSObject o in dict.Values)
{ if(o.GetType().Equals(typeof(NSDate)))
if (o.GetType().Equals(typeof(NSDate)))
{ {
NSDate dat = (NSDate)o; NSDate dat = (NSDate)o;
if (dat.Date.Equals(val)) if(dat.Date.Equals(val)) return true;
return true;
} }
}
return false; return false;
} }
/// <summary> /// <summary>
/// Checks whether a given value is contained in this dictionary. /// Checks whether a given value is contained in this dictionary.
/// </summary> /// </summary>
/// <param name="val">The value that will be searched for.</param> /// <param name="val">The value that will be searched for.</param>
/// <returns>Whether the key is contained in this dictionary.</returns> /// <returns>Whether the key is contained in this dictionary.</returns>
public bool ContainsValue(byte[] val) public bool ContainsValue(byte[] val)
{ {
foreach (NSObject o in dict.Values) foreach(NSObject o in dict.Values)
{ if(o.GetType().Equals(typeof(NSData)))
if (o.GetType().Equals(typeof(NSData)))
{ {
NSData dat = (NSData)o; NSData dat = (NSData)o;
if (ArrayEquals(dat.Bytes, val)) if(ArrayEquals(dat.Bytes, val)) return true;
return true;
} }
}
return false; return false;
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current <see cref="Claunia.PropertyList.NSDictionary"/>. /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDictionary" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="Claunia.PropertyList.NSObject"/> to compare with the current <see cref="Claunia.PropertyList.NSDictionary"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSDictionary"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSDictionary" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSDictionary" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if (!(obj is NSDictionary)) if(!(obj is NSDictionary)) return false;
return false;
if (((NSDictionary)obj).dict.Count != dict.Count) if(((NSDictionary)obj).dict.Count != dict.Count) return false;
return false;
bool found; bool found;
foreach (KeyValuePair<string, NSObject> kvp in dict) foreach(KeyValuePair<string, NSObject> kvp in dict)
{ {
NSObject nsoB; NSObject nsoB;
found = ((NSDictionary)obj).dict.TryGetValue(kvp.Key, out nsoB); found = ((NSDictionary)obj).dict.TryGetValue(kvp.Key, out nsoB);
if (!found) if(!found) return false;
return false; if(!kvp.Value.Equals(nsoB)) return false;
if (!kvp.Value.Equals(nsoB))
return false;
} }
return true; return true;
} }
/// <summary> /// <summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSDictionary"/> object. /// Serves as a hash function for a <see cref="Claunia.PropertyList.NSDictionary" /> object.
/// </summary> /// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// <returns>
/// hash table.</returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
@@ -350,28 +358,27 @@ namespace Claunia.PropertyList
{ {
Indent(xml, level); Indent(xml, level);
xml.Append("<dict>"); xml.Append("<dict>");
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
foreach (KeyValuePair<string, NSObject> kvp in dict) foreach(KeyValuePair<string, NSObject> kvp in dict)
{ {
Indent(xml, level + 1); Indent(xml, level + 1);
xml.Append("<key>"); xml.Append("<key>");
//According to http://www.w3.org/TR/REC-xml/#syntax node values must not //According to http://www.w3.org/TR/REC-xml/#syntax node values must not
//contain the characters < or &. Also the > character should be escaped. //contain the characters < or &. Also the > character should be escaped.
if (kvp.Key.Contains("&") || kvp.Key.Contains("<") || kvp.Key.Contains(">")) if(kvp.Key.Contains("&") || kvp.Key.Contains("<") || kvp.Key.Contains(">"))
{ {
xml.Append("<![CDATA["); xml.Append("<![CDATA[");
xml.Append(kvp.Key.Replace("]]>", "]]]]><![CDATA[>")); xml.Append(kvp.Key.Replace("]]>", "]]]]><![CDATA[>"));
xml.Append("]]>"); xml.Append("]]>");
} }
else else xml.Append(kvp.Key);
{
xml.Append(kvp.Key);
}
xml.Append("</key>"); xml.Append("</key>");
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
kvp.Value.ToXml(xml, level + 1); kvp.Value.ToXml(xml, level + 1);
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
} }
Indent(xml, level); Indent(xml, level);
xml.Append("</dict>"); xml.Append("</dict>");
} }
@@ -380,35 +387,24 @@ namespace Claunia.PropertyList
{ {
base.AssignIDs(outPlist); base.AssignIDs(outPlist);
foreach (KeyValuePair<string, NSObject> entry in dict) foreach(KeyValuePair<string, NSObject> entry in dict) keys[entry.Key].AssignIDs(outPlist);
{
keys[entry.Key].AssignIDs(outPlist);
}
foreach (KeyValuePair<string, NSObject> entry in dict) foreach(KeyValuePair<string, NSObject> entry in dict) entry.Value.AssignIDs(outPlist);
{
entry.Value.AssignIDs(outPlist);
}
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.WriteIntHeader(0xD, dict.Count); outPlist.WriteIntHeader(0xD, dict.Count);
foreach (KeyValuePair<String, NSObject> entry in dict) 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));
outPlist.WriteID(outPlist.GetID(keys[entry.Key]));
}
foreach (KeyValuePair<String, NSObject> entry in dict)
{
outPlist.WriteID(outPlist.GetID(entry.Value));
}
} }
/// <summary> /// <summary>
/// Generates a valid ASCII property list which has this NSDictionary as its /// Generates a valid ASCII property list which has this NSDictionary as its
/// root object. The generated property list complies with the format as /// root object. The generated property list complies with the format as
/// described in https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html /// described in
/// Property List Programming Guide - Old-Style ASCII Property Lists. /// https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html
/// Property List Programming Guide - Old-Style ASCII Property Lists.
/// </summary> /// </summary>
/// <returns>ASCII representation of this object.</returns> /// <returns>ASCII representation of this object.</returns>
public string ToASCIIPropertyList() public string ToASCIIPropertyList()
@@ -420,10 +416,11 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Generates a valid ASCII property list in GnuStep format which has this /// Generates a valid ASCII property list in GnuStep format which has this
/// NSDictionary as its root object. The generated property list complies with /// NSDictionary as its root object. The generated property list complies with
/// the format as described in http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html /// the format as described in
/// GnuStep - NSPropertyListSerialization class documentation. /// http://www.gnustep.org/resources/documentation/Developer/Base/Reference/NSPropertyList.html
/// GnuStep - NSPropertyListSerialization class documentation.
/// </summary> /// </summary>
/// <returns>GnuStep ASCII representation of this object.</returns> /// <returns>GnuStep ASCII representation of this object.</returns>
public string ToGnuStepASCIIPropertyList() public string ToGnuStepASCIIPropertyList()
@@ -439,7 +436,7 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
foreach (string key in Keys) foreach(string key in Keys)
{ {
NSObject val = ObjectForKey(key); NSObject val = ObjectForKey(key);
Indent(ascii, level + 1); Indent(ascii, level + 1);
@@ -447,7 +444,8 @@ namespace Claunia.PropertyList
ascii.Append(NSString.EscapeStringForASCII(key)); ascii.Append(NSString.EscapeStringForASCII(key));
ascii.Append("\" ="); ascii.Append("\" =");
Type objClass = val.GetType(); Type objClass = val.GetType();
if (objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || objClass.Equals(typeof(NSData))) if(objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
objClass.Equals(typeof(NSData)))
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
val.ToASCII(ascii, level + 2); val.ToASCII(ascii, level + 2);
@@ -457,9 +455,11 @@ namespace Claunia.PropertyList
ascii.Append(" "); ascii.Append(" ");
val.ToASCII(ascii, 0); val.ToASCII(ascii, 0);
} }
ascii.Append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
} }
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN);
} }
@@ -469,7 +469,7 @@ namespace Claunia.PropertyList
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_BEGIN_TOKEN);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
foreach (string key in Keys) foreach(string key in Keys)
{ {
NSObject val = ObjectForKey(key); NSObject val = ObjectForKey(key);
Indent(ascii, level + 1); Indent(ascii, level + 1);
@@ -477,7 +477,8 @@ namespace Claunia.PropertyList
ascii.Append(NSString.EscapeStringForASCII(key)); ascii.Append(NSString.EscapeStringForASCII(key));
ascii.Append("\" ="); ascii.Append("\" =");
Type objClass = val.GetType(); Type objClass = val.GetType();
if (objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || objClass.Equals(typeof(NSData))) if(objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
objClass.Equals(typeof(NSData)))
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
val.ToASCIIGnuStep(ascii, level + 2); val.ToASCIIGnuStep(ascii, level + 2);
@@ -487,17 +488,18 @@ namespace Claunia.PropertyList
ascii.Append(" "); ascii.Append(" ");
val.ToASCIIGnuStep(ascii, 0); val.ToASCIIGnuStep(ascii, 0);
} }
ascii.Append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_ITEM_DELIMITER_TOKEN);
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
} }
Indent(ascii, level); Indent(ascii, level);
ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.DICTIONARY_END_TOKEN);
} }
#region IDictionary implementation #region IDictionary implementation
/// <summary> /// <summary>
/// Add the specified key and value. /// Add the specified key and value.
/// </summary> /// </summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
/// <param name="value">Value.</param> /// <param name="value">Value.</param>
@@ -508,7 +510,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Checks if there is any item contained in the current instance corresponding with the specified key. /// Checks if there is any item contained in the current instance corresponding with the specified key.
/// </summary> /// </summary>
/// <returns><c>true</c>, if key was contained, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if key was contained, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
@@ -518,7 +520,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Checks if there is any item contained in the current instance corresponding with the specified value. /// Checks if there is any item contained in the current instance corresponding with the specified value.
/// </summary> /// </summary>
/// <returns><c>true</c>, if value is contained, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if value is contained, <c>false</c> otherwise.</returns>
/// <param name="value">Key.</param> /// <param name="value">Key.</param>
@@ -528,7 +530,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Removes the item belonging to the specified key. /// Removes the item belonging to the specified key.
/// </summary> /// </summary>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
public bool Remove(string key) public bool Remove(string key)
@@ -538,7 +540,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Tries to get the item corresponding to the specified key /// Tries to get the item corresponding to the specified key
/// </summary> /// </summary>
/// <returns><c>true</c>, if get value was successfully found and retrieved, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, if get value was successfully found and retrieved, <c>false</c> otherwise.</returns>
/// <param name="key">Key.</param> /// <param name="key">Key.</param>
@@ -549,55 +551,36 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Gets or sets the <see cref="Claunia.PropertyList.NSObject"/> at the specified index. /// Gets or sets the <see cref="Claunia.PropertyList.NSObject" /> at the specified index.
/// </summary> /// </summary>
/// <param name="index">Index.</param> /// <param name="index">Index.</param>
public NSObject this[string index] public NSObject this[string index]
{ {
get get => dict[index];
{
return dict[index];
}
set set
{ {
if (!keys.ContainsKey(index)) if(!keys.ContainsKey(index)) keys.Add(index, new NSString(index));
{
keys.Add(index, new NSString(index));
}
dict[index] = value; dict[index] = value;
} }
} }
/// <summary> /// <summary>
/// Gets an array with all the keys contained in the current instance. /// Gets an array with all the keys contained in the current instance.
/// </summary> /// </summary>
/// <value>The keys.</value> /// <value>The keys.</value>
public ICollection<string> Keys public ICollection<string> Keys => dict.Keys;
{
get
{
return dict.Keys;
}
}
/// <summary> /// <summary>
/// Gets an array with all the objects contained in the current instance. /// Gets an array with all the objects contained in the current instance.
/// </summary> /// </summary>
/// <value>The objects.</value> /// <value>The objects.</value>
public ICollection<NSObject> Values public ICollection<NSObject> Values => dict.Values;
{
get
{
return dict.Values;
}
}
#endregion #endregion
#region ICollection implementation #region ICollection implementation
/// <summary> /// <summary>
/// Adds the specified item. /// Adds the specified item.
/// </summary> /// </summary>
/// <param name="item">Item.</param> /// <param name="item">Item.</param>
public void Add(KeyValuePair<string, NSObject> item) public void Add(KeyValuePair<string, NSObject> item)
@@ -607,7 +590,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Clears this instance. /// Clears this instance.
/// </summary> /// </summary>
public void Clear() public void Clear()
{ {
@@ -616,7 +599,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Checks if the current instance contains the specified item. /// Checks if the current instance contains the specified item.
/// </summary> /// </summary>
/// <param name="item">Item.</param> /// <param name="item">Item.</param>
/// <returns><c>true</c> if it is found, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if it is found, <c>false</c> otherwise.</returns>
@@ -626,9 +609,13 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Copies the <see cref="Dictionary{TKey, TValue}.ValueCollection"/> elements to an existing one-dimensional <see cref="Array"/>, starting at the specified array index. /// Copies the <see cref="Dictionary{TKey, TValue}.ValueCollection" /> elements to an existing one-dimensional
/// <see cref="Array" />, starting at the specified array index.
/// </summary> /// </summary>
/// <param name="array">The one-dimensional <see cref="Array"/> that is the destination of the elements copied from <see cref="Dictionary{TKey, TValue}.ValueCollection"/>. The <see cref="Array"/> must have zero-based indexing.</param> /// <param name="array">
/// The one-dimensional <see cref="Array" /> that is the destination of the elements copied from
/// <see cref="Dictionary{TKey, TValue}.ValueCollection" />. The <see cref="Array" /> must have zero-based indexing.
/// </param>
/// <param name="arrayIndex">The zero-based index in array at which copying begins.</param> /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
public void CopyTo(KeyValuePair<string, NSObject>[] array, int arrayIndex) public void CopyTo(KeyValuePair<string, NSObject>[] array, int arrayIndex)
{ {
@@ -637,7 +624,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Removes the specified item. /// Removes the specified item.
/// </summary> /// </summary>
/// <param name="item">Item to remove.</param> /// <param name="item">Item to remove.</param>
/// <returns><c>true</c> if successfully removed, <c>false</c> if not, or if item is not in current instance.</returns> /// <returns><c>true</c> if successfully removed, <c>false</c> if not, or if item is not in current instance.</returns>
@@ -648,51 +635,16 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Gets the count of items in the current instance. /// Gets the count of items in the current instance.
/// </summary> /// </summary>
/// <value>How many items are contained in the current instance.</value> /// <value>How many items are contained in the current instance.</value>
public int Count public int Count => dict.Count;
{
get
{
return dict.Count;
}
}
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is read only. /// Gets a value indicating whether this instance is read only.
/// </summary> /// </summary>
/// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value>
public bool IsReadOnly public bool IsReadOnly => false;
{
get
{
return false;
}
}
#endregion
#region IEnumerable implementation
/// <summary>
/// Gets the enumerator.
/// </summary>
/// <returns>The enumerator.</returns>
public IEnumerator<KeyValuePair<string, NSObject>> GetEnumerator()
{
return dict.GetEnumerator();
}
#endregion
#region IEnumerable implementation
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return dict.GetEnumerator();
}
#endregion #endregion
} }
} }

View File

@@ -22,56 +22,57 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Text;
using System.Globalization; using System.Globalization;
using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// A number whose value is either an integer, a real number or bool. /// A number whose value is either an integer, a real number or bool.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class NSNumber : NSObject, IComparable public class NSNumber : NSObject, IComparable
{ {
/// <summary> /// <summary>
/// Indicates that the number's value is an integer. /// Indicates that the number's value is an integer.
/// The number is stored as a .NET <see cref="long"/>. /// The number is stored as a .NET <see cref="long" />.
/// Its original value could have been char, short, int, long or even long long. /// Its original value could have been char, short, int, long or even long long.
/// </summary> /// </summary>
public const int INTEGER = 0; public const int INTEGER = 0;
/// <summary> /// <summary>
/// Indicates that the number's value is a real number. /// Indicates that the number's value is a real number.
/// The number is stored as a .NET <see cref="double"/>. /// The number is stored as a .NET <see cref="double" />.
/// Its original value could have been float or double. /// Its original value could have been float or double.
/// </summary> /// </summary>
public const int REAL = 1; public const int REAL = 1;
/// <summary> /// <summary>
/// Indicates that the number's value is bool. /// Indicates that the number's value is bool.
/// </summary> /// </summary>
public const int BOOLEAN = 2; public const int BOOLEAN = 2;
readonly bool boolValue;
readonly double doubleValue;
readonly long longValue;
//Holds the current type of this number //Holds the current type of this number
readonly int type; readonly int type;
readonly long longValue;
readonly double doubleValue;
readonly bool boolValue;
/// <summary> /// <summary>
/// Parses integers and real numbers from their binary representation. /// Parses integers and real numbers from their binary representation.
/// <i>Note: real numbers are not yet supported.</i> /// <i>Note: real numbers are not yet supported.</i>
/// </summary> /// </summary>
/// <param name="bytes">The binary representation</param> /// <param name="bytes">The binary representation</param>
/// <param name="type">The type of number</param> /// <param name="type">The type of number</param>
/// <seealso cref="INTEGER"/> /// <seealso cref="INTEGER" />
/// <seealso cref="REAL"/> /// <seealso cref="REAL" />
public NSNumber(ReadOnlySpan<byte> bytes, int type) public NSNumber(ReadOnlySpan<byte> bytes, int type)
{ {
switch (type) switch(type)
{ {
case INTEGER: case INTEGER:
doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes); doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes);
@@ -79,141 +80,168 @@ namespace Claunia.PropertyList
case REAL: case REAL:
doubleValue = BinaryPropertyListParser.ParseDouble(bytes); doubleValue = BinaryPropertyListParser.ParseDouble(bytes);
longValue = (long)Math.Round(doubleValue); longValue = (long)Math.Round(doubleValue);
break; break;
default: default: throw new ArgumentException("Type argument is not valid.");
throw new ArgumentException("Type argument is not valid.");
} }
this.type = type; this.type = type;
} }
public NSNumber(string text, int type) public NSNumber(string text, int type)
{ {
switch (type) switch(type)
{ {
case INTEGER: case INTEGER:
{ {
doubleValue = longValue = long.Parse(text, CultureInfo.InvariantCulture); doubleValue = longValue = long.Parse(text, CultureInfo.InvariantCulture);
break; break;
} }
case REAL: case REAL:
{ {
doubleValue = double.Parse(text, CultureInfo.InvariantCulture); doubleValue = double.Parse(text, CultureInfo.InvariantCulture);
longValue = (long)Math.Round(doubleValue); longValue = (long)Math.Round(doubleValue);
break; break;
} }
default: default: { throw new ArgumentException("Type argument is not valid."); }
{
throw new ArgumentException("Type argument is not valid.");
}
} }
this.type = type; this.type = type;
} }
/// <summary> /// <summary>
/// Creates a number from its textual representation. /// Creates a number from its textual representation.
/// </summary> /// </summary>
/// <param name="text">The textual representation of the number.</param> /// <param name="text">The textual representation of the number.</param>
/// <seealso cref="bool.Parse(string)"/> /// <seealso cref="bool.Parse(string)" />
/// <seealso cref="long.Parse(string)"/> /// <seealso cref="long.Parse(string)" />
/// <seealso cref="double.Parse(string, IFormatProvider)"/> /// <seealso cref="double.Parse(string, IFormatProvider)" />
public NSNumber(string text) public NSNumber(string text)
{ {
if (text == null) if(text == null) throw new ArgumentException("The given string is null and cannot be parsed as number.");
throw new ArgumentException("The given string is null and cannot be parsed as number.");
long l; long l;
double d; double d;
if (text.StartsWith("0x") && long.TryParse("", NumberStyles.HexNumber, CultureInfo.InvariantCulture, out l)) if(text.StartsWith("0x") && long.TryParse("", NumberStyles.HexNumber, CultureInfo.InvariantCulture, out l))
{ {
doubleValue = longValue = l; doubleValue = longValue = l;
type = INTEGER; type = INTEGER;
} }
if (long.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out l))
if(long.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out l))
{ {
doubleValue = longValue = l; doubleValue = longValue = l;
type = INTEGER; type = INTEGER;
} }
else if (double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out d)) else if(double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out d))
{ {
doubleValue = d; doubleValue = d;
longValue = (long)Math.Round(doubleValue); longValue = (long)Math.Round(doubleValue);
type = REAL; type = REAL;
} }
else else
{ {
bool isTrue = string.Equals(text, "true", StringComparison.CurrentCultureIgnoreCase) || string.Equals(text, "yes", StringComparison.CurrentCultureIgnoreCase); bool isTrue = string.Equals(text, "true", StringComparison.CurrentCultureIgnoreCase) ||
bool isFalse = string.Equals(text, "false", StringComparison.CurrentCultureIgnoreCase) || string.Equals(text, "no", StringComparison.CurrentCultureIgnoreCase); string.Equals(text, "yes", StringComparison.CurrentCultureIgnoreCase);
bool isFalse = string.Equals(text, "false", StringComparison.CurrentCultureIgnoreCase) ||
string.Equals(text, "no", StringComparison.CurrentCultureIgnoreCase);
if (isTrue || isFalse) if(isTrue || isFalse)
{ {
type = BOOLEAN; type = BOOLEAN;
doubleValue = longValue = boolValue ? 1 : 0; doubleValue = longValue = boolValue ? 1 : 0;
} }
else else
{ throw new
throw new ArgumentException("The given string neither represents a double, an int nor a bool value."); ArgumentException("The given string neither represents a double, an int nor a bool value.");
}
} }
} }
/// <summary> /// <summary>
/// Creates an integer number. /// Creates an integer number.
/// </summary> /// </summary>
/// <param name="i">The integer value.</param> /// <param name="i">The integer value.</param>
public NSNumber(int i) public NSNumber(int i)
{ {
doubleValue = longValue = i; doubleValue = longValue = i;
type = INTEGER; type = INTEGER;
} }
/// <summary> /// <summary>
/// Creates an integer number. /// Creates an integer number.
/// </summary> /// </summary>
/// <param name="l">The long integer value.</param> /// <param name="l">The long integer value.</param>
public NSNumber(long l) public NSNumber(long l)
{ {
doubleValue = longValue = l; doubleValue = longValue = l;
type = INTEGER; type = INTEGER;
} }
/// <summary> /// <summary>
/// Creates a real number. /// Creates a real number.
/// </summary> /// </summary>
/// <param name="d">The real value.</param> /// <param name="d">The real value.</param>
public NSNumber(double d) public NSNumber(double d)
{ {
longValue = (long)(doubleValue = d); longValue = (long)(doubleValue = d);
type = REAL; type = REAL;
} }
/// <summary> /// <summary>
/// Creates a bool number. /// Creates a bool number.
/// </summary> /// </summary>
/// <param name="b">The bool value.</param> /// <param name="b">The bool value.</param>
public NSNumber(bool b) public NSNumber(bool b)
{ {
boolValue = b; boolValue = b;
doubleValue = longValue = b ? 1 : 0; doubleValue = longValue = b ? 1 : 0;
type = BOOLEAN; type = BOOLEAN;
} }
/// <summary> /// <summary>
/// Gets the type of this number's value. /// Compares the current <see cref="Claunia.PropertyList.NSNumber" /> to the specified object.
/// </summary>
/// <returns>
/// 0 if the numbers are equal, 1 if the current <see cref="Claunia.PropertyList.NSNumber" /> is greater
/// than the argument and -1 if it is less, or the argument is not a number.
/// </returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSNumber" />.</param>
public int CompareTo(object o)
{
double x = ToDouble();
double y;
if(o is NSNumber)
{
NSNumber num = (NSNumber)o;
y = num.ToDouble();
return x < y ? -1 : (x == y ? 0 : 1);
}
if(IsNumber(o))
{
y = GetDoubleFromObject(o);
return x < y ? -1 : (x == y ? 0 : 1);
}
return -1;
}
/// <summary>
/// Gets the type of this number's value.
/// </summary> /// </summary>
/// <returns>The type flag.</returns> /// <returns>The type flag.</returns>
/// <seealso cref="BOOLEAN"/> /// <seealso cref="BOOLEAN" />
/// <seealso cref="INTEGER"/> /// <seealso cref="INTEGER" />
/// <seealso cref="REAL"/> /// <seealso cref="REAL" />
public int GetNSNumberType() public int GetNSNumberType()
{ {
return type; return type;
} }
/// <summary> /// <summary>
/// Checks whether the value of this NSNumber is a bool. /// Checks whether the value of this NSNumber is a bool.
/// </summary> /// </summary>
/// <returns>Whether the number's value is a bool.</returns> /// <returns>Whether the number's value is a bool.</returns>
public bool isBoolean() public bool isBoolean()
@@ -222,7 +250,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Checks whether the value of this NSNumber is an integer. /// Checks whether the value of this NSNumber is an integer.
/// </summary> /// </summary>
/// <returns>Whether the number's value is an integer.</returns> /// <returns>Whether the number's value is an integer.</returns>
public bool isInteger() public bool isInteger()
@@ -231,7 +259,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Checks whether the value of this NSNumber is a real number. /// Checks whether the value of this NSNumber is a real number.
/// </summary> /// </summary>
/// <returns>Whether the number's value is a real number.</returns> /// <returns>Whether the number's value is a real number.</returns>
public bool isReal() public bool isReal()
@@ -240,18 +268,18 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// The number's bool value. /// The number's bool value.
/// </summary> /// </summary>
/// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns>
public bool ToBool() public bool ToBool()
{ {
if (type == BOOLEAN) if(type == BOOLEAN) return boolValue;
return boolValue;
return longValue != 0; return longValue != 0;
} }
/// <summary> /// <summary>
/// The number's long value. /// The number's long value.
/// </summary> /// </summary>
/// <returns>The value of the number as long</returns> /// <returns>The value of the number as long</returns>
public long ToLong() public long ToLong()
@@ -260,10 +288,12 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// The number's int value. /// The number's int value.
/// <i>Note: Even though the number's type might be INTEGER it can be larger than a Java int. /// <i>
/// Use intValue() only if you are certain that it contains a number from the int range. /// Note: Even though the number's type might be INTEGER it can be larger than a Java int.
/// Otherwise the value might be innaccurate.</i> /// Use intValue() only if you are certain that it contains a number from the int range.
/// Otherwise the value might be innaccurate.
/// </i>
/// </summary> /// </summary>
/// <returns>The value of the number as int.</returns> /// <returns>The value of the number as int.</returns>
public int ToInt() public int ToInt()
@@ -272,7 +302,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// The number's double value. /// The number's double value.
/// </summary> /// </summary>
/// <returns>The value of the number as double.</returns> /// <returns>The value of the number as double.</returns>
public double ToDouble() public double ToDouble()
@@ -281,8 +311,8 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// The number's float value. /// The number's float value.
/// WARNING: Possible loss of precision if the value is outside the float range. /// WARNING: Possible loss of precision if the value is outside the float range.
/// </summary> /// </summary>
/// <returns>The value of the number as float.</returns> /// <returns>The value of the number as float.</returns>
public float floatValue() public float floatValue()
@@ -291,406 +321,329 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Checks whether the other object is a NSNumber of the same value. /// Checks whether the other object is a NSNumber of the same value.
/// </summary> /// </summary>
/// <param name="obj">The object to compare to.</param> /// <param name="obj">The object to compare to.</param>
/// <returns>Whether the objects are equal in terms of numeric value and type.</returns> /// <returns>Whether the objects are equal in terms of numeric value and type.</returns>
public override bool Equals(Object obj) public override bool Equals(object obj)
{ {
if (!(obj is NSNumber)) if(!(obj is NSNumber)) return false;
return false;
NSNumber n = (NSNumber)obj; NSNumber n = (NSNumber)obj;
return type == n.type && longValue == n.longValue && doubleValue == n.doubleValue && boolValue == n.boolValue; return type == n.type && longValue == n.longValue && doubleValue == n.doubleValue &&
boolValue == n.boolValue;
} }
/// <summary> /// <summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSNumber"/> object. /// Serves as a hash function for a <see cref="Claunia.PropertyList.NSNumber" /> object.
/// </summary> /// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// <returns>
/// hash table.</returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = type; int hash = type;
hash = 37 * hash + (int)(longValue ^ ((uint)longValue >> 32)); hash = 37 * hash + (int)(longValue ^ ((uint)longValue >> 32));
hash = 37 * hash + (int)(BitConverter.DoubleToInt64Bits(doubleValue) ^ ((uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32))); 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; return hash;
} }
/// <summary> /// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="Claunia.PropertyList.NSNumber"/>. /// Returns a <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />.
/// </summary> /// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="Claunia.PropertyList.NSNumber"/>.</returns> /// <returns>A <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />.</returns>
public override string ToString() public override string ToString()
{ {
switch (type) switch(type)
{ {
case INTEGER: case INTEGER: { return ToLong().ToString(); }
{ case REAL: { return ToDouble().ToString("R", CultureInfo.InvariantCulture); }
return ToLong().ToString(); case BOOLEAN: { return ToBool().ToString(); }
} default: { return base.ToString(); }
case REAL:
{
return ToDouble().ToString("R", CultureInfo.InvariantCulture);
}
case BOOLEAN:
{
return ToBool().ToString();
}
default:
{
return base.ToString();
}
} }
} }
internal override void ToXml(StringBuilder xml, int level) internal override void ToXml(StringBuilder xml, int level)
{ {
Indent(xml, level); Indent(xml, level);
switch (type) switch(type)
{ {
case INTEGER: case INTEGER:
{ {
xml.Append("<integer>"); xml.Append("<integer>");
xml.Append(ToLong()); xml.Append(ToLong());
xml.Append("</integer>"); xml.Append("</integer>");
break; break;
} }
case REAL: case REAL:
{ {
xml.Append("<real>"); xml.Append("<real>");
if (doubleValue == 0) if(doubleValue == 0) xml.Append("0.0");
{ else xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture));
// 0 values appear to always roundtrip as 0.0,
// but non-zero values do not include decimals if
// not required (e.g. 10 -> "10")
xml.Append("0.0");
}
else
{
// ToString() can truncate the decimals, so use "R". See
// https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#the-round-trip-r-format-specifier
xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture));
}
xml.Append("</real>"); xml.Append("</real>");
break; break;
} }
case BOOLEAN: case BOOLEAN:
{ {
if (ToBool()) if(ToBool()) xml.Append("<true/>");
xml.Append("<true/>"); else xml.Append("<false/>");
else break;
xml.Append("<false/>"); }
break;
}
} }
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
switch (GetNSNumberType()) switch(GetNSNumberType())
{ {
case INTEGER: case INTEGER:
{
if(ToLong() < 0)
{ {
if (ToLong() < 0) outPlist.Write(0x13);
{ outPlist.WriteBytes(ToLong(), 8);
outPlist.Write(0x13);
outPlist.WriteBytes(ToLong(), 8);
}
else if (ToLong() <= 0xff)
{
outPlist.Write(0x10);
outPlist.WriteBytes(ToLong(), 1);
}
else if (ToLong() <= 0xffff)
{
outPlist.Write(0x11);
outPlist.WriteBytes(ToLong(), 2);
}
else if (ToLong() <= 0xffffffffL)
{
outPlist.Write(0x12);
outPlist.WriteBytes(ToLong(), 4);
}
else
{
outPlist.Write(0x13);
outPlist.WriteBytes(ToLong(), 8);
}
break;
} }
else if(ToLong() <= 0xff)
{
outPlist.Write(0x10);
outPlist.WriteBytes(ToLong(), 1);
}
else if(ToLong() <= 0xffff)
{
outPlist.Write(0x11);
outPlist.WriteBytes(ToLong(), 2);
}
else if(ToLong() <= 0xffffffffL)
{
outPlist.Write(0x12);
outPlist.WriteBytes(ToLong(), 4);
}
else
{
outPlist.Write(0x13);
outPlist.WriteBytes(ToLong(), 8);
}
break;
}
case REAL: case REAL:
{ {
outPlist.Write(0x23); outPlist.Write(0x23);
outPlist.WriteDouble(ToDouble()); outPlist.WriteDouble(ToDouble());
break; break;
} }
case BOOLEAN: case BOOLEAN:
{ {
outPlist.Write(ToBool() ? 0x09 : 0x08); outPlist.Write(ToBool() ? 0x09 : 0x08);
break; break;
} }
} }
} }
internal override void ToASCII(StringBuilder ascii, int level) internal override void ToASCII(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
if (type == BOOLEAN) if(type == BOOLEAN) ascii.Append(boolValue ? "YES" : "NO");
{ else ascii.Append(ToString());
ascii.Append(boolValue ? "YES" : "NO");
}
else
{
ascii.Append(ToString());
}
} }
internal override void ToASCIIGnuStep(StringBuilder ascii, int level) internal override void ToASCIIGnuStep(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
switch (type) switch(type)
{ {
case INTEGER: case INTEGER:
{ {
ascii.Append("<*I"); ascii.Append("<*I");
ascii.Append(ToString()); ascii.Append(ToString());
ascii.Append(">"); ascii.Append(">");
break; break;
} }
case REAL: case REAL:
{ {
ascii.Append("<*R"); ascii.Append("<*R");
ascii.Append(ToString()); ascii.Append(ToString());
ascii.Append(">"); ascii.Append(">");
break; break;
} }
case BOOLEAN: case BOOLEAN:
{ {
if (boolValue) if(boolValue) ascii.Append("<*BY>");
{ else ascii.Append("<*BN>");
ascii.Append("<*BY>"); break;
} }
else
{
ascii.Append("<*BN>");
}
break;
}
} }
} }
/// <summary> /// <summary>
/// Compares the current <see cref="Claunia.PropertyList.NSNumber"/> to the specified object. /// Determines if an object is a number.
/// </summary> /// Substitutes .NET's Number class comparison
/// <returns>0 if the numbers are equal, 1 if the current <see cref="Claunia.PropertyList.NSNumber"/> is greater
/// than the argument and -1 if it is less, or the argument is not a number.</returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSNumber"/>.</param>
public int CompareTo(Object o)
{
double x = ToDouble();
double y;
if (o is NSNumber)
{
NSNumber num = (NSNumber)o;
y = num.ToDouble();
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
if (IsNumber(o))
{
y = GetDoubleFromObject(o);
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
return -1;
}
/// <summary>
/// Determines if an object is a number.
/// Substitutes .NET's Number class comparison
/// </summary> /// </summary>
/// <returns><c>true</c> if it is a number.</returns> /// <returns><c>true</c> if it is a number.</returns>
/// <param name="o">Object.</param> /// <param name="o">Object.</param>
static bool IsNumber(Object o) static bool IsNumber(object o)
{ {
return o is sbyte return o is sbyte || o is byte || o is short || o is ushort || o is int || o is uint || o is long ||
|| o is byte o is ulong || o is float || o is double || o is decimal;
|| o is short
|| o is ushort
|| o is int
|| o is uint
|| o is long
|| o is ulong
|| o is float
|| o is double
|| o is decimal;
} }
static double GetDoubleFromObject(Object o) static double GetDoubleFromObject(object o)
{ {
if (o is sbyte) if(o is sbyte) return (sbyte)o;
return (double)((sbyte)o); if(o is byte) return (byte)o;
if (o is byte) if(o is short) return (short)o;
return (double)((byte)o); if(o is ushort) return (ushort)o;
if (o is short) if(o is int) return (int)o;
return (double)((short)o); if(o is uint) return (uint)o;
if (o is ushort) if(o is long) return (long)o;
return (double)((ushort)o); if(o is ulong) return (ulong)o;
if (o is int) if(o is float) return (float)o;
return (double)((int)o); if(o is double) return (double)o;
if (o is uint) if(o is decimal) return (double)(decimal)o;
return (double)((uint)o);
if (o is long)
return (double)((long)o);
if (o is ulong)
return (double)((ulong)o);
if (o is float)
return (double)((float)o);
if (o is double)
return (double)o;
if (o is decimal)
return (double)((decimal)o);
return (double)0; return 0;
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current <see cref="Claunia.PropertyList.NSNumber"/>. /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSNumber" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="Claunia.PropertyList.NSObject"/> to compare with the current <see cref="Claunia.PropertyList.NSNumber"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSNumber"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSNumber" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSNumber" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if (!(obj is NSNumber)) if(!(obj is NSNumber)) return false;
return false;
if (((NSNumber)obj).GetNSNumberType() != type) if(((NSNumber)obj).GetNSNumberType() != type) return false;
return false;
switch (type) switch(type)
{ {
case INTEGER: case INTEGER: return longValue == ((NSNumber)obj).ToLong();
return (longValue == ((NSNumber)obj).ToLong()); case REAL: return doubleValue == ((NSNumber)obj).ToDouble();
case REAL: case BOOLEAN: return boolValue == ((NSNumber)obj).ToBool();
return (doubleValue == ((NSNumber)obj).ToDouble()); default: return false;
case BOOLEAN:
return (boolValue == ((NSNumber)obj).ToBool());
default:
return false;
} }
} }
static public explicit operator ulong(NSNumber value) public static explicit operator ulong(NSNumber value)
{ {
return (ulong)value.longValue; return (ulong)value.longValue;
} }
static public explicit operator long(NSNumber value) public static explicit operator long(NSNumber value)
{ {
return value.longValue; return value.longValue;
} }
static public explicit operator uint(NSNumber value) public static explicit operator uint(NSNumber value)
{ {
return (uint)value.longValue; return (uint)value.longValue;
} }
static public explicit operator int(NSNumber value) public static explicit operator int(NSNumber value)
{ {
return (int)value.longValue; return (int)value.longValue;
} }
static public explicit operator ushort(NSNumber value) public static explicit operator ushort(NSNumber value)
{ {
return (ushort)value.longValue; return (ushort)value.longValue;
} }
static public explicit operator short(NSNumber value) public static explicit operator short(NSNumber value)
{ {
return (short)value.longValue; return (short)value.longValue;
} }
static public explicit operator byte(NSNumber value) public static explicit operator byte(NSNumber value)
{ {
return (byte)value.longValue; return (byte)value.longValue;
} }
static public explicit operator sbyte(NSNumber value) public static explicit operator sbyte(NSNumber value)
{ {
return (sbyte)value.longValue; return (sbyte)value.longValue;
} }
static public explicit operator double(NSNumber value) public static explicit operator double(NSNumber value)
{ {
return value.doubleValue; return value.doubleValue;
} }
static public explicit operator float(NSNumber value) public static explicit operator float(NSNumber value)
{ {
return (float)value.doubleValue; return (float)value.doubleValue;
} }
static public explicit operator bool(NSNumber value) public static explicit operator bool(NSNumber value)
{ {
return value.boolValue; return value.boolValue;
} }
static public explicit operator NSNumber(ulong value) public static explicit operator NSNumber(ulong value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(long value) public static explicit operator NSNumber(long value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(uint value) public static explicit operator NSNumber(uint value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(int value) public static explicit operator NSNumber(int value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(ushort value) public static explicit operator NSNumber(ushort value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(short value) public static explicit operator NSNumber(short value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(byte value) public static explicit operator NSNumber(byte value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(sbyte value) public static explicit operator NSNumber(sbyte value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(double value) public static explicit operator NSNumber(double value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(float value) public static explicit operator NSNumber(float value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
static public explicit operator NSNumber(bool value) public static explicit operator NSNumber(bool value)
{ {
return new NSNumber(value); return new NSNumber(value);
} }
} }
} }

View File

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

View File

@@ -22,20 +22,22 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// <para> /// <para>
/// A set is an interface to an unordered collection of objects. /// A set is an interface to an unordered collection of objects.
/// </para><para> /// </para>
/// This implementation uses a <see cref="List{NSObject}"/>as the underlying /// <para>
/// data structure. /// This implementation uses a <see cref="List{T}" />as the underlying
/// </para> /// data structure.
/// </para>
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
@@ -46,7 +48,7 @@ namespace Claunia.PropertyList
bool ordered; bool ordered;
/// <summary> /// <summary>
/// Creates an empty unordered set. /// Creates an empty unordered set.
/// </summary> /// </summary>
public NSSet() public NSSet()
{ {
@@ -54,18 +56,17 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Creates an empty set. /// Creates an empty set.
/// </summary> /// </summary>
/// <param name="ordered">Should the set be ordered on operations?</param> /// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered) public NSSet(bool ordered)
{ {
this.ordered = ordered; this.ordered = ordered;
set = new List<NSObject>(); set = new List<NSObject>();
} }
/// <summary> /// <summary>
/// Creates a set and fill it with the given objects. /// Creates a set and fill it with the given objects.
/// </summary> /// </summary>
/// <param name="objects">The objects to populate the set.</param> /// <param name="objects">The objects to populate the set.</param>
public NSSet(params NSObject[] objects) public NSSet(params NSObject[] objects)
@@ -74,73 +75,87 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Creates a set and fill it with the given objects. /// Creates a set and fill it with the given objects.
/// </summary> /// </summary>
/// <param name="objects">The objects to populate the set.</param> /// <param name="objects">The objects to populate the set.</param>
/// <param name="ordered">Should the set be ordered on operations?</param> /// <param name="ordered">Should the set be ordered on operations?</param>
public NSSet(bool ordered, params NSObject[] objects) public NSSet(bool ordered, params NSObject[] objects)
{ {
this.ordered = ordered; this.ordered = ordered;
set = new List<NSObject>(objects); set = new List<NSObject>(objects);
if (ordered) if(ordered) set.Sort();
set.Sort();
} }
/// <summary> /// <summary>
/// Adds an object to the set. /// Gets the number of elements in the set.
/// </summary>
/// <value>The number of elements in the set.</value>
public int Count
{
get
{
lock(set) return set.Count;
}
}
/// <summary>
/// Returns an enumerator object that lets you iterate over all elements of the set.
/// This is the equivalent to <c>objectEnumerator</c> in the Cocoa implementation
/// of NSSet.
/// </summary>
/// <returns>The iterator for the set.</returns>
public IEnumerator GetEnumerator()
{
lock(set) return set.GetEnumerator();
}
/// <summary>
/// Adds an object to the set.
/// </summary> /// </summary>
/// <param name="obj">The object to add.</param> /// <param name="obj">The object to add.</param>
public void AddObject(NSObject obj) public void AddObject(NSObject obj)
{ {
lock (set) lock(set)
{ {
set.Add(obj); set.Add(obj);
if (ordered) if(ordered) set.Sort();
set.Sort();
} }
} }
/// <summary> /// <summary>
/// Removes an object from the set. /// Removes an object from the set.
/// </summary> /// </summary>
/// <param name="obj">The object to remove.</param> /// <param name="obj">The object to remove.</param>
public void RemoveObject(NSObject obj) public void RemoveObject(NSObject obj)
{ {
lock (set) lock(set)
{ {
set.Remove(obj); set.Remove(obj);
if (ordered) if(ordered) set.Sort();
set.Sort();
} }
} }
/// <summary> /// <summary>
/// Returns all objects contained in the set. /// Returns all objects contained in the set.
/// </summary> /// </summary>
/// <returns>An array of all objects in the set.</returns> /// <returns>An array of all objects in the set.</returns>
public NSObject[] AllObjects() public NSObject[] AllObjects()
{ {
lock (set) lock(set) return set.ToArray();
{
return set.ToArray();
}
} }
/// <summary> /// <summary>
/// Returns one of the objects in the set, or <c>null</c> /// Returns one of the objects in the set, or <c>null</c>
/// if the set contains no objects. /// if the set contains no objects.
/// </summary> /// </summary>
/// <returns>The first object in the set, or <c>null</c> if the set is empty.</returns> /// <returns>The first object in the set, or <c>null</c> if the set is empty.</returns>
public NSObject AnyObject() public NSObject AnyObject()
{ {
lock (set) lock(set) return set.Count == 0 ? null : set[0];
{
return set.Count == 0 ? null : set[0];
}
} }
/// <summary> /// <summary>
/// Finds out whether a given object is contained in the set. /// Finds out whether a given object is contained in the set.
/// </summary> /// </summary>
/// <returns><c>true</c>, when the object was found, <c>false</c> otherwise.</returns> /// <returns><c>true</c>, when the object was found, <c>false</c> otherwise.</returns>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
@@ -150,76 +165,59 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Determines whether the set contains an object equal to a given object /// Determines whether the set contains an object equal to a given object
/// and returns that object if it is present. /// and returns that object if it is present.
/// </summary> /// </summary>
/// <param name="obj">The object to look for.</param> /// <param name="obj">The object to look for.</param>
/// <returns>The object if it is present, <c>null</c> otherwise.</returns> /// <returns>The object if it is present, <c>null</c> otherwise.</returns>
public NSObject Member(NSObject obj) public NSObject Member(NSObject obj)
{ {
lock (set) lock(set)
{ {
foreach (NSObject o in set) foreach(NSObject o in set)
{ if(o.Equals(obj))
if (o.Equals(obj))
return o; return o;
}
return null; return null;
} }
} }
/// <summary> /// <summary>
/// Finds out whether at least one object is present in both sets. /// Finds out whether at least one object is present in both sets.
/// </summary> /// </summary>
/// <returns><c>true</c> if the intersection of both sets is empty, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if the intersection of both sets is empty, <c>false</c> otherwise.</returns>
/// <param name="otherSet">The other set.</param> /// <param name="otherSet">The other set.</param>
public bool IntersectsSet(NSSet otherSet) public bool IntersectsSet(NSSet otherSet)
{ {
lock (set) lock(set)
{ {
foreach (NSObject o in set) foreach(NSObject o in set)
{ if(otherSet.ContainsObject(o))
if (otherSet.ContainsObject(o))
return true; return true;
}
return false; return false;
} }
} }
/// <summary> /// <summary>
/// Finds out if this set is a subset of the given set. /// Finds out if this set is a subset of the given set.
/// </summary> /// </summary>
/// <returns><c>true</c> if all elements in this set are also present in the other set, <c>false</c>otherwise.</returns> /// <returns><c>true</c> if all elements in this set are also present in the other set, <c>false</c>otherwise.</returns>
/// <param name="otherSet">The other set.</param> /// <param name="otherSet">The other set.</param>
public bool IsSubsetOfSet(NSSet otherSet) public bool IsSubsetOfSet(NSSet otherSet)
{ {
lock (set) lock(set)
{ {
foreach (NSObject o in set) foreach(NSObject o in set)
{ if(!otherSet.ContainsObject(o))
if (!otherSet.ContainsObject(o))
return false; return false;
}
return true; return true;
} }
} }
/// <summary> /// <summary>
/// Returns an enumerator object that lets you iterate over all elements of the set. /// Gets the underlying data structure in which this NSSets stores its content.
/// This is the equivalent to <c>objectEnumerator</c> in the Cocoa implementation
/// of NSSet.
/// </summary>
/// <returns>The iterator for the set.</returns>
public IEnumerator GetEnumerator()
{
lock (set)
{
return set.GetEnumerator();
}
}
/// <summary>
/// Gets the underlying data structure in which this NSSets stores its content.
/// </summary> /// </summary>
/// <returns>A Set object.</returns> /// <returns>A Set object.</returns>
internal List<NSObject> GetSet() internal List<NSObject> GetSet()
@@ -228,10 +226,12 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSSet"/> object. /// Serves as a hash function for a <see cref="Claunia.PropertyList.NSSet" /> object.
/// </summary> /// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// <returns>
/// hash table.</returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() public override int GetHashCode()
{ {
int hash = 7; int hash = 7;
@@ -240,44 +240,31 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="Claunia.PropertyList.NSSet"/>. /// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="Claunia.PropertyList.NSSet"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current /// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSSet"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSSet" />.
public override bool Equals(Object obj) /// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{ {
if (obj == null) if(obj == null) return false;
{
return false; if(GetType() != obj.GetType()) return false;
}
if (GetType() != obj.GetType())
{
return false;
}
NSSet other = (NSSet)obj; NSSet other = (NSSet)obj;
return !(set != other.set && (set == null || !set.Equals(other.set))); return !(set != other.set && (set == null || !set.Equals(other.set)));
} }
/// <summary> /// <summary>
/// Gets the number of elements in the set. /// Returns the XML representantion for this set.
/// </summary> /// There is no official XML representation specified for sets.
/// <value>The number of elements in the set.</value> /// In this implementation it is represented by an array.
public int Count
{
get
{
lock (set)
{
return set.Count;
}
}
}
/// <summary>
/// Returns the XML representantion for this set.
/// There is no official XML representation specified for sets.
/// In this implementation it is represented by an array.
/// </summary> /// </summary>
/// <param name="xml">The XML StringBuilder</param> /// <param name="xml">The XML StringBuilder</param>
/// <param name="level">The indentation level</param> /// <param name="level">The indentation level</param>
@@ -285,14 +272,14 @@ namespace Claunia.PropertyList
{ {
Indent(xml, level); Indent(xml, level);
xml.Append("<array>"); xml.Append("<array>");
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
if (ordered) if(ordered) set.Sort();
set.Sort(); foreach(NSObject o in set)
foreach (NSObject o in set)
{ {
o.ToXml(xml, level + 1); o.ToXml(xml, level + 1);
xml.Append(NSObject.NEWLINE); xml.Append(NEWLINE);
} }
Indent(xml, level); Indent(xml, level);
xml.Append("</array>"); xml.Append("</array>");
} }
@@ -300,49 +287,40 @@ namespace Claunia.PropertyList
internal override void AssignIDs(BinaryPropertyListWriter outPlist) internal override void AssignIDs(BinaryPropertyListWriter outPlist)
{ {
base.AssignIDs(outPlist); base.AssignIDs(outPlist);
foreach (NSObject obj in set) foreach(NSObject obj in set) obj.AssignIDs(outPlist);
{
obj.AssignIDs(outPlist);
}
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
if (ordered) if(ordered)
{ {
set.Sort(); set.Sort();
outPlist.WriteIntHeader(0xB, set.Count); outPlist.WriteIntHeader(0xB, set.Count);
} }
else else outPlist.WriteIntHeader(0xC, set.Count);
{
outPlist.WriteIntHeader(0xC, set.Count); foreach(NSObject obj in set) outPlist.WriteID(outPlist.GetID(obj));
}
foreach (NSObject obj in set)
{
outPlist.WriteID(outPlist.GetID(obj));
}
} }
/// <summary> /// <summary>
/// Returns the ASCII representation of this set. /// Returns the ASCII representation of this set.
/// There is no official ASCII representation for sets. /// There is no official ASCII representation for sets.
/// In this implementation sets are represented as arrays. /// In this implementation sets are represented as arrays.
/// </summary> /// </summary>
/// <param name="ascii">The ASCII file string builder</param> /// <param name="ascii">The ASCII file string builder</param>
/// <param name="level">The indentation level</param> /// <param name="level">The indentation level</param>
internal override void ToASCII(StringBuilder ascii, int level) internal override void ToASCII(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
if (ordered) if(ordered) set.Sort();
set.Sort();
NSObject[] array = AllObjects(); NSObject[] array = AllObjects();
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for (int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++)
{ {
Type objClass = array[i].GetType(); Type objClass = array[i].GetType();
if ((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || objClass.Equals(typeof(NSData))) if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
&& indexOfLastNewLine != ascii.Length) objClass.Equals(typeof(NSData))) && indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -350,43 +328,41 @@ namespace Claunia.PropertyList
} }
else else
{ {
if (i != 0) if(i != 0) ascii.Append(" ");
ascii.Append(" ");
array[i].ToASCII(ascii, 0); array[i].ToASCII(ascii, 0);
} }
if (i != array.Length - 1) if(i != array.Length - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if (ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
} }
ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
} }
/// <summary> /// <summary>
/// Returns the ASCII representation of this set according to the GnuStep format. /// Returns the ASCII representation of this set according to the GnuStep format.
/// There is no official ASCII representation for sets. /// There is no official ASCII representation for sets.
/// In this implementation sets are represented as arrays. /// In this implementation sets are represented as arrays.
/// </summary> /// </summary>
/// <param name="ascii">The ASCII file string builder</param> /// <param name="ascii">The ASCII file string builder</param>
/// <param name="level">The indentation level</param> /// <param name="level">The indentation level</param>
internal override void ToASCIIGnuStep(StringBuilder ascii, int level) internal override void ToASCIIGnuStep(StringBuilder ascii, int level)
{ {
Indent(ascii, level); Indent(ascii, level);
if (ordered) if(ordered) set.Sort();
set.Sort();
NSObject[] array = AllObjects(); NSObject[] array = AllObjects();
ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_BEGIN_TOKEN);
int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal); int indexOfLastNewLine = ascii.ToString().LastIndexOf(NEWLINE, StringComparison.Ordinal);
for (int i = 0; i < array.Length; i++) for(int i = 0; i < array.Length; i++)
{ {
Type objClass = array[i].GetType(); Type objClass = array[i].GetType();
if ((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) || objClass.Equals(typeof(NSData))) if((objClass.Equals(typeof(NSDictionary)) || objClass.Equals(typeof(NSArray)) ||
&& indexOfLastNewLine != ascii.Length) objClass.Equals(typeof(NSData))) && indexOfLastNewLine != ascii.Length)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
@@ -394,43 +370,45 @@ namespace Claunia.PropertyList
} }
else else
{ {
if (i != 0) if(i != 0) ascii.Append(" ");
ascii.Append(" ");
array[i].ToASCIIGnuStep(ascii, 0); array[i].ToASCIIGnuStep(ascii, 0);
} }
if (i != array.Length - 1) if(i != array.Length - 1) ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
ascii.Append(ASCIIPropertyListParser.ARRAY_ITEM_DELIMITER_TOKEN);
if (ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH) if(ascii.Length - indexOfLastNewLine > ASCII_LINE_LENGTH)
{ {
ascii.Append(NEWLINE); ascii.Append(NEWLINE);
indexOfLastNewLine = ascii.Length; indexOfLastNewLine = ascii.Length;
} }
} }
ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN); ascii.Append(ASCIIPropertyListParser.ARRAY_END_TOKEN);
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current <see cref="Claunia.PropertyList.NSSet"/>. /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="Claunia.PropertyList.NSObject"/> to compare with the current <see cref="Claunia.PropertyList.NSSet"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSSet"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSSet" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSSet" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if (!(obj is NSSet)) if(!(obj is NSSet)) return false;
return false;
if (set.Count != ((NSSet)obj).Count) if(set.Count != ((NSSet)obj).Count) return false;
return false;
foreach (NSObject objS in (NSSet)obj) foreach(NSObject objS in (NSSet)obj)
if (!set.Contains(objS)) if(!set.Contains(objS))
return false; return false;
return true; return true;
} }
} }
} }

View File

@@ -22,67 +22,74 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Text; using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// A NSString contains a string. /// A NSString contains a string.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class NSString : NSObject, IComparable public class NSString : NSObject, IComparable
{ {
string content; static Encoding asciiEncoder, utf16beEncoder, utf8Encoder;
/// <summary> /// <summary>
/// Creates a NSString from its binary representation. /// Creates a NSString from its binary representation.
/// </summary> /// </summary>
/// <param name="bytes">The binary representation.</param> /// <param name="bytes">The binary representation.</param>
/// <param name="encoding">The encoding of the binary representation, the name of a supported charset.</param> /// <param name="encoding">The encoding of the binary representation, the name of a supported charset.</param>
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception> /// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
public NSString(ReadOnlySpan<byte> bytes, String encoding) public NSString(ReadOnlySpan<byte> bytes, string encoding) : this(bytes, Encoding.GetEncoding(encoding)) { }
: this(bytes, Encoding.GetEncoding(encoding))
{
}
/// <summary> /// <summary>
/// Creates a NSString from its binary representation. /// Creates a NSString from its binary representation.
/// </summary> /// </summary>
/// <param name="bytes">The binary representation.</param> /// <param name="bytes">The binary representation.</param>
/// <param name="encoding">The encoding of the binary representation.</param> /// <param name="encoding">The encoding of the binary representation.</param>
/// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception> /// <exception cref="ArgumentException">The encoding charset is invalid or not supported by the underlying platform.</exception>
public NSString(ReadOnlySpan<byte> bytes, Encoding encoding) public NSString(ReadOnlySpan<byte> bytes, Encoding encoding)
{ {
#if NATIVE_SPAN #if NATIVE_SPAN
content = encoding.GetString(bytes); Content = encoding.GetString(bytes);
#else #else
content = encoding.GetString(bytes.ToArray()); Content = encoding.GetString(bytes.ToArray());
#endif #endif
} }
/// <summary> /// <summary>
/// Creates a NSString from a string. /// Creates a NSString from a string.
/// </summary> /// </summary>
/// <param name="text">The string that will be contained in the NSString.</param> /// <param name="text">The string that will be contained in the NSString.</param>
public NSString(string text) public NSString(string text)
{ {
content = text; Content = text;
} }
/// <summary> /// <summary>
/// Gets this strings content. /// Gets this strings content.
/// </summary> /// </summary>
/// <returns>This NSString as .NET string object.</returns> /// <returns>This NSString as .NET string object.</returns>
public string Content public string Content { get; set; }
/// <summary>
/// Compares the current <see cref="Claunia.PropertyList.NSString" /> to the specified object.
/// </summary>
/// <returns>A 32-bit signed integer that indicates the lexical relationship between the two comparands.</returns>
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString" />.</param>
public int CompareTo(object o)
{ {
get { return this.content; } if(o is NSString) return string.Compare(Content, ((NSString)o).Content, StringComparison.Ordinal);
set { this.content = value; } if(o is string) return string.Compare(Content, (string)o, StringComparison.Ordinal);
return -1;
} }
/// <summary> /// <summary>
/// Appends a string to this string. /// Appends a string to this string.
/// </summary> /// </summary>
/// <param name="s">The string to append.</param> /// <param name="s">The string to append.</param>
public void Append(NSString s) public void Append(NSString s)
@@ -91,25 +98,25 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Appends a string to this string. /// Appends a string to this string.
/// </summary> /// </summary>
/// <param name="s">The string to append.</param> /// <param name="s">The string to append.</param>
public void Append(string s) public void Append(string s)
{ {
content += s; Content += s;
} }
/// <summary> /// <summary>
/// Prepends a string to this string. /// Prepends a string to this string.
/// </summary> /// </summary>
/// <param name="s">The string to prepend.</param> /// <param name="s">The string to prepend.</param>
public void Prepend(string s) public void Prepend(string s)
{ {
content = s + content; Content = s + Content;
} }
/// <summary> /// <summary>
/// Prepends a string to this string. /// Prepends a string to this string.
/// </summary> /// </summary>
/// <param name="s">The string to prepend.</param> /// <param name="s">The string to prepend.</param>
public void Prepend(NSString s) public void Prepend(NSString s)
@@ -118,56 +125,61 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="Claunia.PropertyList.NSString"/>. /// Determines whether the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="Claunia.PropertyList.NSString"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current /// The <see cref="System.Object" /> to compare with the current
/// <see cref="Claunia.PropertyList.NSString"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.NSString" />.
public override bool Equals(Object obj) /// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{ {
if (!(obj is NSString)) if(!(obj is NSString)) return false;
return false;
return content.Equals(((NSString)obj).content); return Content.Equals(((NSString)obj).Content);
} }
/// <summary> /// <summary>
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSString"/> object. /// Serves as a hash function for a <see cref="Claunia.PropertyList.NSString" /> object.
/// </summary> /// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a /// <returns>
/// hash table.</returns> /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
/// hash table.
/// </returns>
public override int GetHashCode() public override int GetHashCode()
{ {
return content.GetHashCode(); return Content.GetHashCode();
} }
/// <summary> /// <summary>
/// The textual representation of this NSString. /// The textual representation of this NSString.
/// </summary> /// </summary>
/// <returns>The NSString's contents.</returns> /// <returns>The NSString's contents.</returns>
public override string ToString() public override string ToString()
{ {
return content; return Content;
} }
static Encoding asciiEncoder, utf16beEncoder, utf8Encoder;
internal override void ToXml(StringBuilder xml, int level) internal override void ToXml(StringBuilder xml, int level)
{ {
Indent(xml, level); Indent(xml, level);
xml.Append("<string>"); xml.Append("<string>");
//Make sure that the string is encoded in UTF-8 for the XML output //Make sure that the string is encoded in UTF-8 for the XML output
lock (typeof(NSString)) lock(typeof(NSString))
{ {
if (utf8Encoder == null) if(utf8Encoder == null) utf8Encoder = Encoding.GetEncoding("UTF-8");
utf8Encoder = Encoding.GetEncoding("UTF-8");
try try
{ {
byte[] bytes = utf8Encoder.GetBytes(content); byte[] bytes = utf8Encoder.GetBytes(Content);
content = utf8Encoder.GetString(bytes); Content = utf8Encoder.GetString(bytes);
} }
catch (Exception ex) catch(Exception ex)
{ {
throw new PropertyListException("Could not encode the NSString into UTF-8: " + ex.Message); throw new PropertyListException("Could not encode the NSString into UTF-8: " + ex.Message);
} }
@@ -175,44 +187,43 @@ namespace Claunia.PropertyList
//According to http://www.w3.org/TR/REC-xml/#syntax node values must not //According to http://www.w3.org/TR/REC-xml/#syntax node values must not
//contain the characters < or &. Also the > character should be escaped. //contain the characters < or &. Also the > character should be escaped.
if (content.Contains("&") || content.Contains("<") || content.Contains(">")) if(Content.Contains("&") || Content.Contains("<") || Content.Contains(">"))
{ {
xml.Append("<![CDATA["); xml.Append("<![CDATA[");
xml.Append(content.Replace("]]>", "]]]]><![CDATA[>")); xml.Append(Content.Replace("]]>", "]]]]><![CDATA[>"));
xml.Append("]]>"); xml.Append("]]>");
} }
else else xml.Append(Content);
{
xml.Append(content);
}
xml.Append("</string>"); xml.Append("</string>");
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
int kind; int kind;
byte[] byteBuf; byte[] byteBuf;
lock (typeof(NSString)) lock(typeof(NSString))
{ {
if (asciiEncoder == null) if(asciiEncoder == null)
// Not much use, because some characters do not fallback to exception, even if not ASCII // Not much use, because some characters do not fallback to exception, even if not ASCII
asciiEncoder = Encoding.GetEncoding("ascii", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); asciiEncoder = Encoding.GetEncoding("ascii", EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);
if (IsASCIIEncodable(content)) if(IsASCIIEncodable(Content))
{ {
kind = 0x5; // standard ASCII kind = 0x5; // standard ASCII
byteBuf = asciiEncoder.GetBytes(content); byteBuf = asciiEncoder.GetBytes(Content);
} }
else else
{ {
if (utf16beEncoder == null) if(utf16beEncoder == null) utf16beEncoder = Encoding.BigEndianUnicode;
utf16beEncoder = Encoding.BigEndianUnicode;
kind = 0x6; // UTF-16-BE kind = 0x6; // UTF-16-BE
byteBuf = utf16beEncoder.GetBytes(content); byteBuf = utf16beEncoder.GetBytes(Content);
} }
} }
outPlist.WriteIntHeader(kind, content.Length);
outPlist.WriteIntHeader(kind, Content.Length);
outPlist.Write(byteBuf); outPlist.Write(byteBuf);
} }
@@ -224,7 +235,7 @@ namespace Claunia.PropertyList
//non-ASCII characters are not escaped but simply written into the //non-ASCII characters are not escaped but simply written into the
//file, thus actually violating the ASCII plain text format. //file, thus actually violating the ASCII plain text format.
//We will escape the string anyway because current Xcode project files (ASCII property lists) also escape their strings. //We will escape the string anyway because current Xcode project files (ASCII property lists) also escape their strings.
ascii.Append(EscapeStringForASCII(content)); ascii.Append(EscapeStringForASCII(Content));
ascii.Append("\""); ascii.Append("\"");
} }
@@ -232,107 +243,75 @@ namespace Claunia.PropertyList
{ {
Indent(ascii, level); Indent(ascii, level);
ascii.Append("\""); ascii.Append("\"");
ascii.Append(EscapeStringForASCII(content)); ascii.Append(EscapeStringForASCII(Content));
ascii.Append("\""); ascii.Append("\"");
} }
/// <summary> /// <summary>
/// Escapes a string for use in ASCII property lists. /// Escapes a string for use in ASCII property lists.
/// </summary> /// </summary>
/// <returns>The unescaped string.</returns> /// <returns>The unescaped string.</returns>
/// <param name="s">S.</param> /// <param name="s">S.</param>
internal static string EscapeStringForASCII(string s) internal static string EscapeStringForASCII(string s)
{ {
string outString = ""; string outString = "";
char[] cArray = s.ToCharArray(); char[] cArray = s.ToCharArray();
foreach (char c in cArray) foreach(char c in cArray)
{ if(c > 127)
if (c > 127)
{ {
//non-ASCII Unicode //non-ASCII Unicode
outString += "\\U"; outString += "\\U";
string hex = String.Format("{0:x}", c); string hex = string.Format("{0:x}", c);
while (hex.Length < 4) while(hex.Length < 4) hex = "0" + hex;
hex = "0" + hex;
outString += hex; outString += hex;
} }
else if (c == '\\') else if(c == '\\') outString += "\\\\";
{ else if(c == '\"') outString += "\\\"";
outString += "\\\\"; else if(c == '\b') outString += "\\b";
} else if(c == '\n') outString += "\\n";
else if (c == '\"') else if(c == '\r') outString += "\\r";
{ else if(c == '\t') outString += "\\t";
outString += "\\\""; else outString += c;
}
else if (c == '\b')
{
outString += "\\b";
}
else if (c == '\n')
{
outString += "\\n";
}
else if (c == '\r')
{
outString += "\\r";
}
else if (c == '\t')
{
outString += "\\t";
}
else
{
outString += c;
}
}
return outString; return outString;
} }
/// <summary> /// <summary>
/// Compares the current <see cref="Claunia.PropertyList.NSString"/> to the specified object. /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.NSString" />.
/// </summary> /// </summary>
/// <returns>A 32-bit signed integer that indicates the lexical relationship between the two comparands.</returns> /// <param name="obj">
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSString"/>.</param> /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
public int CompareTo(Object o) /// <see cref="Claunia.PropertyList.NSString" />.
{ /// </param>
if (o is NSString) /// <returns>
return string.Compare(Content, ((NSString)o).Content, StringComparison.Ordinal); /// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
if (o is String) /// <see cref="Claunia.PropertyList.NSString" />; otherwise, <c>false</c>.
return string.Compare(Content, ((String)o), StringComparison.Ordinal); /// </returns>
return -1;
}
/// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current <see cref="Claunia.PropertyList.NSString"/>.
/// </summary>
/// <param name="obj">The <see cref="Claunia.PropertyList.NSObject"/> to compare with the current <see cref="Claunia.PropertyList.NSString"/>.</param>
/// <returns><c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current
/// <see cref="Claunia.PropertyList.NSString"/>; otherwise, <c>false</c>.</returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
if (!(obj is NSString)) if(!(obj is NSString)) return false;
return false;
return content == ((NSString)obj).content; return Content == ((NSString)obj).Content;
} }
internal static bool IsASCIIEncodable(string text) internal static bool IsASCIIEncodable(string text)
{ {
foreach (char c in text) foreach(char c in text)
if ((int)c > 0x7F) if(c > 0x7F)
return false; return false;
return true; return true;
} }
static public explicit operator string(NSString value) public static explicit operator string(NSString value)
{ {
return value.content; return value.Content;
} }
static public explicit operator NSString(string value) public static explicit operator NSString(string value)
{ {
return new NSString(value); return new NSString(value);
} }
} }
} }

View File

@@ -23,55 +23,47 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Runtime.Serialization;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// The exception that is thrown when an property list file could not be processed correctly. /// The exception that is thrown when an property list file could not be processed correctly.
/// </summary> /// </summary>
[Serializable] [Serializable]
public class PropertyListException : Exception public class PropertyListException : Exception
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PropertyListException"/> class. /// Initializes a new instance of the <see cref="PropertyListException" /> class.
/// </summary> /// </summary>
public PropertyListException() public PropertyListException() { }
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PropertyListException"/> class. /// Initializes a new instance of the <see cref="PropertyListException" /> class.
/// </summary> /// </summary>
/// <param name="message"> /// <param name="message">
/// The error message that explains the reason for the exception. /// The error message that explains the reason for the exception.
/// </param> /// </param>
public PropertyListException(string message) public PropertyListException(string message) : base(message) { }
: base(message)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PropertyListException"/> class. /// Initializes a new instance of the <see cref="PropertyListException" /> class.
/// </summary> /// </summary>
/// <param name="message"> /// <param name="message">
/// The error message that explains the reason for the exception. /// The error message that explains the reason for the exception.
/// </param> /// </param>
/// <param name="inner"> /// <param name="inner">
/// The exception that is the cause of the current exception, or <see langword="null"/> /// The exception that is the cause of the current exception, or <see langword="null" />
/// if no inner exception is specified. /// if no inner exception is specified.
/// </param> /// </param>
public PropertyListException(string message, Exception inner) public PropertyListException(string message, Exception inner) : base(message, inner) { }
: base(message, inner)
{
}
#if !NETCORE #if !NETCORE
protected PropertyListException(SerializationInfo info, StreamingContext context) protected PropertyListException(SerializationInfo info, StreamingContext context)
: base(info, context) : base(info, context)
{ {
} }
#endif #endif
} }
} }

View File

@@ -22,25 +22,21 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// A PropertyListFormatException is thrown by the various property list format parsers /// A PropertyListFormatException is thrown by the various property list format parsers
/// when an error in the format of the given property list is encountered. /// when an error in the format of the given property list is encountered.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public class PropertyListFormatException : PropertyListException public class PropertyListFormatException : PropertyListException
{ {
/// <summary> /// <summary>
/// Creates a new exception with the given message. /// Creates a new exception with the given message.
/// </summary> /// </summary>
/// <param name="message">A message containing information about the nature of the exception.</param> /// <param name="message">A message containing information about the nature of the exception.</param>
public PropertyListFormatException(string message) : base(message) public PropertyListFormatException(string message) : base(message) { }
{
}
} }
} }

View File

@@ -22,106 +22,85 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Text;
using System.IO; using System.IO;
using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// This class provides methods to parse property lists. It can handle files, /// This class provides methods to parse property lists. It can handle files,
/// input streams and byte arrays. All known property list formats are supported. /// input streams and byte arrays. All known property list formats are supported.
/// /// This class also provides methods to save and convert property lists.
/// This class also provides methods to save and convert property lists.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public static class PropertyListParser public static class PropertyListParser
{ {
const int TYPE_XML = 0; const int TYPE_XML = 0;
const int TYPE_BINARY = 1; const int TYPE_BINARY = 1;
const int TYPE_ASCII = 2; const int TYPE_ASCII = 2;
const int TYPE_ERROR_BLANK = 10; const int TYPE_ERROR_BLANK = 10;
const int TYPE_ERROR_UNKNOWN = 11; const int TYPE_ERROR_UNKNOWN = 11;
/// <summary> /// <summary>
/// Determines the type of a property list by means of the first bytes of its data /// Determines the type of a property list by means of the first bytes of its data
/// </summary> /// </summary>
/// <returns>The type of the property list</returns> /// <returns>The type of the property list</returns>
/// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param> /// <param name="dataBeginning">The very first bytes of data of the property list (minus any whitespace) as a string</param>
static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning) static int DetermineTypeExact(ReadOnlySpan<byte> dataBeginning)
{ {
if (dataBeginning.Length == 0) if(dataBeginning.Length == 0) return TYPE_ERROR_BLANK;
{
return TYPE_ERROR_BLANK; if(dataBeginning[0] == '(' || dataBeginning[0] == '{' || dataBeginning[0] == '/') return TYPE_ASCII;
}
else if (dataBeginning[0] == '(' || dataBeginning[0] == '{' || dataBeginning[0] == '/') if(dataBeginning[0] == '<') return TYPE_XML;
{ if(dataBeginning.Length >= 6 && dataBeginning[0] == 'b' && dataBeginning[1] == 'p' &&
return TYPE_ASCII; dataBeginning[2] == 'l' && dataBeginning[3] == 'i' && dataBeginning[4] == 's' &&
} dataBeginning[5] == 't') return TYPE_BINARY;
else if (dataBeginning[0] == '<')
{ return TYPE_ERROR_UNKNOWN;
return TYPE_XML;
}
else if (dataBeginning.Length >= 6
&& dataBeginning[0] == 'b'
&& dataBeginning[1] == 'p'
&& dataBeginning[2] == 'l'
&& dataBeginning[3] == 'i'
&& dataBeginning[4] == 's'
&& dataBeginning[5] == 't')
{
return TYPE_BINARY;
}
else
{
return TYPE_ERROR_UNKNOWN;
}
} }
/// <summary> /// <summary>
/// Determines the type of a property list by means of the first bytes of its data /// Determines the type of a property list by means of the first bytes of its data
/// </summary> /// </summary>
/// <returns>The very first bytes of data of the property list (minus any whitespace)</returns> /// <returns>The very first bytes of data of the property list (minus any whitespace)</returns>
/// <param name="bytes">The type of the property list</param> /// <param name="bytes">The type of the property list</param>
static int DetermineType(ReadOnlySpan<byte> bytes) static int DetermineType(ReadOnlySpan<byte> bytes)
{ {
if (bytes.Length == 0) if(bytes.Length == 0) return TYPE_ERROR_BLANK;
return TYPE_ERROR_BLANK;
//Skip any possible whitespace at the beginning of the file //Skip any possible whitespace at the beginning of the file
int offset = 0; int offset = 0;
if (bytes.Length >= 3 && (bytes[0] & 0xFF) == 0xEF && (bytes[1] & 0xFF) == 0xBB && (bytes[2] & 0xFF) == 0xBF) if(bytes.Length >= 3 && (bytes[0] & 0xFF) == 0xEF && (bytes[1] & 0xFF) == 0xBB &&
{ (bytes[2] & 0xFF) == 0xBF) offset += 3;
//Skip Unicode byte order mark (BOM) while(offset < bytes.Length && (bytes[offset] == ' ' || bytes[offset] == '\t' || bytes[offset] == '\r' ||
offset += 3; bytes[offset] == '\n' || bytes[offset] == '\f')) offset++;
}
while (offset < bytes.Length && (bytes[offset] == ' ' || bytes[offset] == '\t' || bytes[offset] == '\r' || bytes[offset] == '\n' || bytes[offset] == '\f'))
{
offset++;
}
var header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset)); ReadOnlySpan<byte> header = bytes.Slice(offset, Math.Min(8, bytes.Length - offset));
return DetermineTypeExact(header); return DetermineTypeExact(header);
} }
/// <summary> /// <summary>
/// Determines the type of a property list by means of the first bytes of its data /// Determines the type of a property list by means of the first bytes of its data
/// </summary> /// </summary>
/// <returns>The type of the property list</returns> /// <returns>The type of the property list</returns>
/// <param name="fs">An input stream pointing to the beginning of the property list data. /// <param name="fs">
/// The stream will be reset to the beginning of the property /// An input stream pointing to the beginning of the property list data.
/// list data after the type has been determined.</param> /// The stream will be reset to the beginning of the property
/// list data after the type has been determined.
/// </param>
static int DetermineType(Stream fs, long offset = 0) static int DetermineType(Stream fs, long offset = 0)
{ {
if (fs.Length == 0) if(fs.Length == 0) return TYPE_ERROR_BLANK;
return TYPE_ERROR_BLANK;
long index = offset; long index = offset;
long readLimit = index + 1024; long readLimit = index + 1024;
long mark = readLimit; long mark = readLimit;
fs.Seek(offset, SeekOrigin.Current); fs.Seek(offset, SeekOrigin.Current);
int b; int b;
bool bom = false; bool bom = false;
//Skip any possible whitespace at the beginning of the file //Skip any possible whitespace at the beginning of the file
@@ -132,15 +111,16 @@ namespace Claunia.PropertyList
fs.Seek(mark, SeekOrigin.Begin); fs.Seek(mark, SeekOrigin.Begin);
return DetermineType(fs, readLimit); return DetermineType(fs, readLimit);
} }
b = fs.ReadByte(); b = fs.ReadByte();
//Check if we are reading the Unicode byte order mark (BOM) and skip it //Check if we are reading the Unicode byte order mark (BOM) and skip it
bom = index < 3 && ((index == 0 && b == 0xEF) || (bom && ((index == 1 && b == 0xBB) || (index == 2 && b == 0xBF)))); bom = index < 3 && (index == 0 && b == 0xEF ||
bom && (index == 1 && b == 0xBB || index == 2 && b == 0xBF));
} }
while(b != -1 && (b == ' ' || b == '\t' || b == '\r' || b == '\n' || b == '\f' || bom)); while(b != -1 && (b == ' ' || b == '\t' || b == '\r' || b == '\n' || b == '\f' || bom));
if(b==-1) if(b == -1) return TYPE_ERROR_BLANK;
return TYPE_ERROR_BLANK;
byte[] magicBytes = new byte[8]; byte[] magicBytes = new byte[8];
magicBytes[0] = (byte)b; magicBytes[0] = (byte)b;
@@ -152,13 +132,13 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Reads all bytes from an Stream and stores them in an array, up to /// Reads all bytes from an Stream and stores them in an array, up to
/// a maximum count. /// a maximum count.
/// </summary> /// </summary>
/// <param name="fs">The Stream pointing to the data that should be stored in the array.</param> /// <param name="fs">The Stream pointing to the data that should be stored in the array.</param>
internal static byte[] ReadAll(Stream fs) internal static byte[] ReadAll(Stream fs)
{ {
using (MemoryStream outputStream = new MemoryStream()) using(MemoryStream outputStream = new MemoryStream())
{ {
fs.CopyTo(outputStream); fs.CopyTo(outputStream);
return outputStream.ToArray(); return outputStream.ToArray();
@@ -166,7 +146,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a property list from a file. /// Parses a property list from a file.
/// </summary> /// </summary>
/// <param name="filePath">Path to the property list file.</param> /// <param name="filePath">Path to the property list file.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -176,40 +156,35 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a property list from a file. /// Parses a property list from a file.
/// </summary> /// </summary>
/// <param name="f">The property list file.</param> /// <param name="f">The property list file.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(FileInfo f) public static NSObject Parse(FileInfo f)
{ {
using (FileStream fis = f.OpenRead()) using(FileStream fis = f.OpenRead()) return Parse(fis);
{
return Parse(fis);
}
} }
/// <summary> /// <summary>
/// Parses a property list from a byte array. /// Parses a property list from a byte array.
/// </summary> /// </summary>
/// <param name="bytes">The property list data as a byte array.</param> /// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(byte[] bytes) public static NSObject Parse(byte[] bytes)
{ {
switch (DetermineType(bytes)) switch(DetermineType(bytes))
{ {
case TYPE_BINARY: case TYPE_BINARY: return BinaryPropertyListParser.Parse(bytes);
return BinaryPropertyListParser.Parse(bytes); case TYPE_XML: return XmlPropertyListParser.Parse(bytes);
case TYPE_XML: case TYPE_ASCII: return ASCIIPropertyListParser.Parse(bytes);
return XmlPropertyListParser.Parse(bytes);
case TYPE_ASCII:
return ASCIIPropertyListParser.Parse(bytes);
default: 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.");
} }
} }
/// <summary> /// <summary>
/// Parses a property list from a byte array. /// Parses a property list from a byte array.
/// </summary> /// </summary>
/// <param name="bytes">The property list data as a byte array.</param> /// <param name="bytes">The property list data as a byte array.</param>
/// <param name="offset">The length of the property list.</param> /// <param name="offset">The length of the property list.</param>
@@ -221,27 +196,25 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a property list from a byte span. /// Parses a property list from a byte span.
/// </summary> /// </summary>
/// <param name="bytes">The property list data as a byte array.</param> /// <param name="bytes">The property list data as a byte array.</param>
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
public static NSObject Parse(ReadOnlySpan<byte> bytes) public static NSObject Parse(ReadOnlySpan<byte> bytes)
{ {
switch (DetermineType(bytes)) switch(DetermineType(bytes))
{ {
case TYPE_BINARY: case TYPE_BINARY: return BinaryPropertyListParser.Parse(bytes);
return BinaryPropertyListParser.Parse(bytes); case TYPE_XML: return XmlPropertyListParser.Parse(bytes.ToArray());
case TYPE_XML: case TYPE_ASCII: return ASCIIPropertyListParser.Parse(bytes);
return XmlPropertyListParser.Parse(bytes.ToArray());
case TYPE_ASCII:
return ASCIIPropertyListParser.Parse(bytes);
default: 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.");
} }
} }
/// <summary> /// <summary>
/// Parses a property list from an Stream. /// Parses a property list from an Stream.
/// </summary> /// </summary>
/// <param name="fs">The Stream delivering the property list data.</param> /// <param name="fs">The Stream delivering the property list data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -251,7 +224,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Saves a property list with the given object as root into a XML file. /// Saves a property list with the given object as root into a XML file.
/// </summary> /// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
@@ -259,41 +232,35 @@ namespace Claunia.PropertyList
public static void SaveAsXml(NSObject root, FileInfo outFile) public static void SaveAsXml(NSObject root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if (!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent);
// Use Create here -- to make sure that when the updated file is shorter than // Use Create here -- to make sure that when the updated file is shorter than
// the original file, no "obsolete" data is left at the end. // the original file, no "obsolete" data is left at the end.
using (Stream fous = outFile.Open(FileMode.Create, FileAccess.ReadWrite)) using(Stream fous = outFile.Open(FileMode.Create, FileAccess.ReadWrite)) SaveAsXml(root, fous);
{
SaveAsXml(root, fous);
}
} }
/// <summary> /// <summary>
/// Saves a property list with the given object as root in XML format into an output stream. /// Saves a property list with the given object as root in XML format into an output stream.
/// </summary> /// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param> /// <param name="outStream">The output stream.</param>
/// <exception cref="IOException">When an error occurs during the writing process.</exception> /// <exception cref="IOException">When an error occurs during the writing process.</exception>
public static void SaveAsXml(NSObject root, Stream outStream) public static void SaveAsXml(NSObject root, Stream outStream)
{ {
#if NET40 #if NET40
// The StreamWriter constructor which takes a "leaveOpen" parameter is // The StreamWriter constructor which takes a "leaveOpen" parameter is
// not available on .NET 4.0 // not available on .NET 4.0
StreamWriter w = new StreamWriter(outStream, Encoding.UTF8); StreamWriter w = new StreamWriter(outStream, Encoding.UTF8);
w.Write(root.ToXmlPropertyList()); w.Write(root.ToXmlPropertyList());
w.Close(); w.Close();
#else #else
using (StreamWriter w = new StreamWriter(outStream, Encoding.UTF8, bufferSize: 1024, leaveOpen: true)) using(StreamWriter w = new StreamWriter(outStream, Encoding.UTF8, 1024, true))
{
w.Write(root.ToXmlPropertyList()); w.Write(root.ToXmlPropertyList());
} #endif
#endif
} }
/// <summary> /// <summary>
/// Converts a given property list file into the OS X and iOS XML format. /// Converts a given property list file into the OS X and iOS XML format.
/// </summary> /// </summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param> /// <param name="outFile">The target file.</param>
@@ -304,7 +271,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Saves a property list with the given object as root into a binary file. /// Saves a property list with the given object as root into a binary file.
/// </summary> /// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
@@ -312,13 +279,12 @@ namespace Claunia.PropertyList
public static void SaveAsBinary(NSObject root, FileInfo outFile) public static void SaveAsBinary(NSObject root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if (!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent);
BinaryPropertyListWriter.Write(outFile, root); BinaryPropertyListWriter.Write(outFile, root);
} }
/// <summary> /// <summary>
/// Saves a property list with the given object as root in binary format into an output stream. /// Saves a property list with the given object as root in binary format into an output stream.
/// </summary> /// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outStream">The output stream.</param> /// <param name="outStream">The output stream.</param>
@@ -329,7 +295,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Converts a given property list file into the OS X and iOS binary format. /// Converts a given property list file into the OS X and iOS binary format.
/// </summary> /// </summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param> /// <param name="outFile">The target file.</param>
@@ -340,7 +306,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Saves a property list with the given object as root into a ASCII file. /// Saves a property list with the given object as root into a ASCII file.
/// </summary> /// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
@@ -348,17 +314,14 @@ namespace Claunia.PropertyList
public static void SaveAsASCII(NSDictionary root, FileInfo outFile) public static void SaveAsASCII(NSDictionary root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if (!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent); using(Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)) using(StreamWriter w = new StreamWriter(fous, Encoding.ASCII))
using (StreamWriter w = new StreamWriter(fous, Encoding.ASCII)) w.Write(root.ToASCIIPropertyList());
{
w.Write(root.ToASCIIPropertyList());
}
} }
/// <summary> /// <summary>
/// Saves a property list with the given object as root into a ASCII file. /// Saves a property list with the given object as root into a ASCII file.
/// </summary> /// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
@@ -366,40 +329,29 @@ namespace Claunia.PropertyList
public static void SaveAsASCII(NSArray root, FileInfo outFile) public static void SaveAsASCII(NSArray root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if (!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent); using(Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)) using(StreamWriter w = new StreamWriter(fous, Encoding.ASCII))
using (StreamWriter w = new StreamWriter(fous, Encoding.ASCII)) w.Write(root.ToASCIIPropertyList());
{
w.Write(root.ToASCIIPropertyList());
}
} }
/// <summary> /// <summary>
/// Converts a given property list file into ASCII format. /// Converts a given property list file into ASCII format.
/// </summary> /// </summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param> /// <param name="outFile">The target file.</param>
public static void ConvertToASCII(FileInfo inFile, FileInfo outFile) public static void ConvertToASCII(FileInfo inFile, FileInfo outFile)
{ {
NSObject root = Parse(inFile); NSObject root = Parse(inFile);
if (root is NSDictionary) if(root is NSDictionary) SaveAsASCII((NSDictionary)root, outFile);
{ else if(root is NSArray) SaveAsASCII((NSArray)root, outFile);
SaveAsASCII((NSDictionary)root, outFile);
}
else if (root is NSArray)
{
SaveAsASCII((NSArray)root, outFile);
}
else else
{ throw new PropertyListFormatException("The root of the given input property list " +
throw new PropertyListFormatException("The root of the given input property list " "is neither a Dictionary nor an Array!");
+ "is neither a Dictionary nor an Array!");
}
} }
/// <summary> /// <summary>
/// Saves a property list with the given object as root into a GnuStep ASCII file. /// Saves a property list with the given object as root into a GnuStep ASCII file.
/// </summary> /// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
@@ -407,17 +359,14 @@ namespace Claunia.PropertyList
public static void SaveAsGnuStepASCII(NSDictionary root, FileInfo outFile) public static void SaveAsGnuStepASCII(NSDictionary root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if (!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent); using(Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)) using(StreamWriter w = new StreamWriter(fous, Encoding.ASCII))
using (StreamWriter w = new StreamWriter(fous, Encoding.ASCII)) w.Write(root.ToGnuStepASCIIPropertyList());
{
w.Write(root.ToGnuStepASCIIPropertyList());
}
} }
/// <summary> /// <summary>
/// Saves a property list with the given object as root into a GnuStep ASCII file. /// Saves a property list with the given object as root into a GnuStep ASCII file.
/// </summary> /// </summary>
/// <param name="root">The root object.</param> /// <param name="root">The root object.</param>
/// <param name="outFile">The output file.</param> /// <param name="outFile">The output file.</param>
@@ -425,37 +374,25 @@ namespace Claunia.PropertyList
public static void SaveAsGnuStepASCII(NSArray root, FileInfo outFile) public static void SaveAsGnuStepASCII(NSArray root, FileInfo outFile)
{ {
string parent = outFile.DirectoryName; string parent = outFile.DirectoryName;
if (!Directory.Exists(parent)) if(!Directory.Exists(parent)) Directory.CreateDirectory(parent);
Directory.CreateDirectory(parent); using(Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite))
using (Stream fous = outFile.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)) using(StreamWriter w = new StreamWriter(fous, Encoding.ASCII))
using (StreamWriter w = new StreamWriter(fous, Encoding.ASCII)) w.Write(root.ToGnuStepASCIIPropertyList());
{
w.Write(root.ToGnuStepASCIIPropertyList());
}
} }
/// <summary> /// <summary>
/// Converts a given property list file into GnuStep ASCII format. /// Converts a given property list file into GnuStep ASCII format.
/// </summary> /// </summary>
/// <param name="inFile">The source file.</param> /// <param name="inFile">The source file.</param>
/// <param name="outFile">The target file.</param> /// <param name="outFile">The target file.</param>
public static void ConvertToGnuStepASCII(FileInfo inFile, FileInfo outFile) public static void ConvertToGnuStepASCII(FileInfo inFile, FileInfo outFile)
{ {
NSObject root = Parse(inFile); NSObject root = Parse(inFile);
if (root is NSDictionary) if(root is NSDictionary) SaveAsGnuStepASCII((NSDictionary)root, outFile);
{ else if(root is NSArray) SaveAsGnuStepASCII((NSArray)root, outFile);
SaveAsGnuStepASCII((NSDictionary)root, outFile);
}
else if (root is NSArray)
{
SaveAsGnuStepASCII((NSArray)root, outFile);
}
else else
{ throw new PropertyListFormatException("The root of the given input property list " +
throw new PropertyListFormatException("The root of the given input property list " "is neither a Dictionary nor an Array!");
+ "is neither a Dictionary nor an Array!");
}
} }
} }
} }

View File

@@ -22,6 +22,7 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text; using System.Text;
@@ -29,7 +30,7 @@ using System.Text;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// An UID. Only found in binary property lists that are keyed archives. /// An UID. Only found in binary property lists that are keyed archives.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
@@ -38,241 +39,226 @@ namespace Claunia.PropertyList
readonly ulong value; readonly ulong value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="bytes">Bytes.</param> /// <param name="bytes">Bytes.</param>
[Obsolete("UIDs have not meaningful names")] [Obsolete("UIDs have not meaningful names")]
public UID(String name, ReadOnlySpan<byte> bytes) public UID(string name, ReadOnlySpan<byte> bytes)
{ {
if (bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8) if(bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8)
throw new ArgumentException("Type argument is not valid."); throw new ArgumentException("Type argument is not valid.");
this.value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class.
/// </summary> /// </summary>
/// <param name="bytes">Bytes.</param> /// <param name="bytes">Bytes.</param>
public UID(ReadOnlySpan<byte> bytes) public UID(ReadOnlySpan<byte> bytes)
{ {
if (bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8) if(bytes.Length != 1 && bytes.Length != 2 && bytes.Length != 4 && bytes.Length != 8)
throw new ArgumentException("Type argument is not valid."); throw new ArgumentException("Type argument is not valid.");
this.value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
value = (ulong)BinaryPropertyListParser.ParseLong(bytes);
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 8-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 8-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Unsigned 8-bit number.</param> /// <param name="number">Unsigned 8-bit number.</param>
[Obsolete("UIDs have no meaningful names")] [Obsolete("UIDs have no meaningful names")]
public UID(String name, byte number) public UID(string name, byte number)
{ {
this.value = number; value = number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 8-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 8-bit number.
/// </summary> /// </summary>
/// <param name="number">Unsigned 8-bit number.</param> /// <param name="number">Unsigned 8-bit number.</param>
public UID(byte number) public UID(byte number)
{ {
this.value = number; value = number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using a signed 8-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using a signed 8-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Unsigned 8-bit number.</param> /// <param name="number">Unsigned 8-bit number.</param>
[Obsolete("UIDs must be unsigned values")] [Obsolete("UIDs must be unsigned values")]
public UID(String name, sbyte number) public UID(string name, sbyte number)
{ {
this.value = (ulong)number; value = (ulong)number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 16-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 16-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Unsigned 16-bit number.</param> /// <param name="number">Unsigned 16-bit number.</param>
[Obsolete("UIDs have no meaningful names")] [Obsolete("UIDs have no meaningful names")]
public UID(String name, ushort number) public UID(string name, ushort number)
{ {
this.value = number; value = number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 16-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 16-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Unsigned 16-bit number.</param> /// <param name="number">Unsigned 16-bit number.</param>
public UID(ushort number) public UID(ushort number)
{ {
this.value = number; value = number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using a signed 16-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using a signed 16-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Signed 16-bit number.</param> /// <param name="number">Signed 16-bit number.</param>
[Obsolete("UIDs must be unsigned values")] [Obsolete("UIDs must be unsigned values")]
public UID(String name, short number) public UID(string name, short number)
{ {
this.value = (ulong)number; value = (ulong)number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 32-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 32-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Unsigned 32-bit number.</param> /// <param name="number">Unsigned 32-bit number.</param>
[Obsolete("UIDs have no meaningful names")] [Obsolete("UIDs have no meaningful names")]
public UID(String name, uint number) public UID(string name, uint number)
{ {
this.value = number; value = number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 32-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 32-bit number.
/// </summary> /// </summary>
/// <param name="number">Unsigned 32-bit number.</param> /// <param name="number">Unsigned 32-bit number.</param>
public UID(uint number) public UID(uint number)
{ {
this.value = number; value = number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using a signed 32-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using a signed 32-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Signed 32-bit number.</param> /// <param name="number">Signed 32-bit number.</param>
[Obsolete("UIDs must be unsigned values")] [Obsolete("UIDs must be unsigned values")]
public UID(String name, int number) public UID(string name, int number)
{ {
this.value = (ulong)number; value = (ulong)number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 64-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 64-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Unsigned 64-bit number.</param> /// <param name="number">Unsigned 64-bit number.</param>
[Obsolete("UIDs have no meaningful names")] [Obsolete("UIDs have no meaningful names")]
public UID(String name, ulong number) public UID(string name, ulong number)
{ {
this.value = number; value = number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using an unsigned 64-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using an unsigned 64-bit number.
/// </summary> /// </summary>
/// <param name="number">Unsigned 64-bit number.</param> /// <param name="number">Unsigned 64-bit number.</param>
public UID(ulong number) public UID(ulong number)
{ {
this.value = number; value = number;
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Claunia.PropertyList.UID"/> class using a signed 64-bit number. /// Initializes a new instance of the <see cref="Claunia.PropertyList.UID" /> class using a signed 64-bit number.
/// </summary> /// </summary>
/// <param name="name">Name.</param> /// <param name="name">Name.</param>
/// <param name="number">Signed 64-bit number.</param> /// <param name="number">Signed 64-bit number.</param>
[Obsolete("UIDs must be unsigned values")] [Obsolete("UIDs must be unsigned values")]
public UID(String name, long number) public UID(string name, long number)
{ {
this.value = (ulong)number; value = (ulong)number;
} }
/// <summary> /// <summary>
/// Gets the bytes. /// Gets the bytes.
/// </summary> /// </summary>
/// <value>The bytes.</value> /// <value>The bytes.</value>
public byte[] Bytes public byte[] Bytes
{ {
get get
{ {
byte[] bytes = new byte[this.ByteCount]; byte[] bytes = new byte[ByteCount];
this.GetBytes(bytes); GetBytes(bytes);
return bytes; return bytes;
} }
} }
/// <summary> /// <summary>
/// Gets the number of bytes required to represent this <see cref="UID"/>. /// Gets the number of bytes required to represent this <see cref="UID" />.
/// </summary> /// </summary>
public int ByteCount public int ByteCount
{ {
get get
{ {
if (this.value <= byte.MaxValue) if(value <= byte.MaxValue) return 1;
{
return 1; if(value <= ushort.MaxValue) return 2;
} if(value <= uint.MaxValue) return 4;
else if (this.value <= ushort.MaxValue)
{ return 8;
return 2;
}
else if (this.value <= uint.MaxValue)
{
return 4;
}
else
{
return 8;
}
} }
} }
/// <summary> /// <summary>
/// Writes the bytes required to represent this <see cref="UID"/> to a byte span. /// Gets the name.
/// </summary>
/// <param name="bytes">
/// The byte span to which to write the byte representation of this UID.
/// </param>
public void GetBytes(Span<byte> bytes)
{
switch (this.ByteCount)
{
case 1:
bytes[0] = (byte)this.value;
break;
case 2:
BinaryPrimitives.WriteUInt16BigEndian(bytes, (ushort)this.value);
break;
case 4:
BinaryPrimitives.WriteUInt32BigEndian(bytes, (uint)this.value);
break;
case 8:
BinaryPrimitives.WriteUInt64BigEndian(bytes, this.value);
break;
default:
throw new InvalidOperationException();
}
}
/// <summary>
/// Gets the name.
/// </summary> /// </summary>
/// <value>The name.</value> /// <value>The name.</value>
[Obsolete("UIDs have no meaningful names")] [Obsolete("UIDs have no meaningful names")]
public string Name public string Name => value.ToString();
/// <summary>
/// Writes the bytes required to represent this <see cref="UID" /> to a byte span.
/// </summary>
/// <param name="bytes">
/// The byte span to which to write the byte representation of this UID.
/// </param>
public void GetBytes(Span<byte> bytes)
{ {
get switch(ByteCount)
{ {
return this.value.ToString(); case 1:
bytes[0] = (byte)value;
break;
case 2:
BinaryPrimitives.WriteUInt16BigEndian(bytes, (ushort)value);
break;
case 4:
BinaryPrimitives.WriteUInt32BigEndian(bytes, (uint)value);
break;
case 8:
BinaryPrimitives.WriteUInt64BigEndian(bytes, value);
break;
default: throw new InvalidOperationException();
} }
} }
/// <summary> /// <summary>
/// There is no XML representation specified for UIDs. /// There is no XML representation specified for UIDs.
/// In this implementation UIDs are represented as strings in the XML output. /// In this implementation UIDs are represented as strings in the XML output.
/// </summary> /// </summary>
/// <param name="xml">The xml StringBuilder</param> /// <param name="xml">The xml StringBuilder</param>
/// <param name="level">The indentation level</param> /// <param name="level">The indentation level</param>
@@ -280,18 +266,17 @@ namespace Claunia.PropertyList
{ {
Indent(xml, level); Indent(xml, level);
xml.Append("<string>"); xml.Append("<string>");
Span<byte> bytes = stackalloc byte[this.ByteCount]; Span<byte> bytes = stackalloc byte[ByteCount];
this.GetBytes(bytes); GetBytes(bytes);
foreach (byte b in bytes) foreach(byte b in bytes) xml.Append(string.Format("{0:x2}", b));
xml.Append(String.Format("{0:x2}", b));
xml.Append("</string>"); xml.Append("</string>");
} }
internal override void ToBinary(BinaryPropertyListWriter outPlist) internal override void ToBinary(BinaryPropertyListWriter outPlist)
{ {
outPlist.Write(0x80 + this.ByteCount - 1); outPlist.Write(0x80 + ByteCount - 1);
Span<byte> bytes = stackalloc byte[this.ByteCount]; Span<byte> bytes = stackalloc byte[ByteCount];
this.GetBytes(bytes); GetBytes(bytes);
outPlist.Write(bytes); outPlist.Write(bytes);
} }
@@ -299,10 +284,9 @@ namespace Claunia.PropertyList
{ {
Indent(ascii, level); Indent(ascii, level);
ascii.Append("\""); ascii.Append("\"");
Span<byte> bytes = stackalloc byte[this.ByteCount]; Span<byte> bytes = stackalloc byte[ByteCount];
this.GetBytes(bytes); GetBytes(bytes);
foreach (byte b in bytes) foreach(byte b in bytes) ascii.Append(string.Format("{0:x2}", b));
ascii.Append(String.Format("{0:x2}", b));
ascii.Append("\""); ascii.Append("\"");
} }
@@ -312,38 +296,42 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current <see cref="Claunia.PropertyList.UID"/>. /// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.UID" />.
/// </summary> /// </summary>
/// <param name="obj">The <see cref="Claunia.PropertyList.NSObject"/> to compare with the current <see cref="Claunia.PropertyList.UID"/>.</param> /// <param name="obj">
/// <returns><c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject"/> is equal to the current /// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
/// <see cref="Claunia.PropertyList.UID"/>; otherwise, <c>false</c>.</returns> /// <see cref="Claunia.PropertyList.UID" />.
/// </param>
/// <returns>
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
/// <see cref="Claunia.PropertyList.UID" />; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(NSObject obj) public override bool Equals(NSObject obj)
{ {
return Equals((object)obj); return Equals((object)obj);
} }
/// <inheritdoc/> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
var uid = obj as UID; UID uid = obj as UID;
if (uid == null) if(uid == null) return false;
return false;
return uid.value == value; return uid.value == value;
} }
/// <inheritdoc/> /// <inheritdoc />
public override int GetHashCode() public override int GetHashCode()
{ {
return this.value.GetHashCode(); return value.GetHashCode();
} }
/// <inheritdoc/> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return $"{this.value} (UID)"; return $"{value} (UID)";
} }
} }
} }

View File

@@ -22,22 +22,23 @@
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
using System.Xml;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Xml;
namespace Claunia.PropertyList namespace Claunia.PropertyList
{ {
/// <summary> /// <summary>
/// Parses XML property lists. /// Parses XML property lists.
/// </summary> /// </summary>
/// @author Daniel Dreibrodt /// @author Daniel Dreibrodt
/// @author Natalia Portillo /// @author Natalia Portillo
public static class XmlPropertyListParser public static class XmlPropertyListParser
{ {
/// <summary> /// <summary>
/// Parses a XML property list file. /// Parses a XML property list file.
/// </summary> /// </summary>
/// <param name="f">The XML property list file.</param> /// <param name="f">The XML property list file.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -45,19 +46,17 @@ namespace Claunia.PropertyList
{ {
XmlDocument doc = new XmlDocument(); XmlDocument doc = new XmlDocument();
var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }; XmlReaderSettings settings = new XmlReaderSettings {DtdProcessing = DtdProcessing.Ignore};
using (Stream stream = f.OpenRead()) using(Stream stream = f.OpenRead())
using (XmlReader reader = XmlReader.Create(stream, settings)) using(XmlReader reader = XmlReader.Create(stream, settings))
{ doc.Load(reader);
doc.Load(reader);
}
return ParseDocument(doc); return ParseDocument(doc);
} }
/// <summary> /// <summary>
/// Parses a XML property list from a byte array. /// Parses a XML property list from a byte array.
/// </summary> /// </summary>
/// <param name="bytes">The byte array containing the property list's data.</param> /// <param name="bytes">The byte array containing the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -68,7 +67,7 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a XML property list from an input stream. /// Parses a XML property list from an input stream.
/// </summary> /// </summary>
/// <param name="str">The input stream pointing to the property list's data.</param> /// <param name="str">The input stream pointing to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -79,16 +78,13 @@ namespace Claunia.PropertyList
XmlReaderSettings settings = new XmlReaderSettings(); XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Ignore; settings.DtdProcessing = DtdProcessing.Ignore;
using (XmlReader reader = XmlReader.Create(str, settings)) using(XmlReader reader = XmlReader.Create(str, settings)) doc.Load(reader);
{
doc.Load(reader);
}
return ParseDocument(doc); return ParseDocument(doc);
} }
/// <summary> /// <summary>
/// Parses a XML property list from a string. /// Parses a XML property list from a string.
/// </summary> /// </summary>
/// <param name="value">The string pointing to the property list's data.</param> /// <param name="value">The string pointing to the property list's data.</param>
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns> /// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
@@ -105,40 +101,35 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses the XML document by generating the appropriate NSObjects for each XML node. /// Parses the XML document by generating the appropriate NSObjects for each XML node.
/// </summary> /// </summary>
/// <returns>The root NSObject of the property list contained in the XML document.</returns> /// <returns>The root NSObject of the property list contained in the XML document.</returns>
/// <param name="doc">The XML document.</param> /// <param name="doc">The XML document.</param>
static NSObject ParseDocument(XmlDocument doc) static NSObject ParseDocument(XmlDocument doc)
{ {
var docType = doc.ChildNodes XmlNode docType = doc.ChildNodes.OfType<XmlNode>()
.OfType<XmlNode>() .SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType);
.SingleOrDefault(n => n.NodeType == XmlNodeType.DocumentType);
if (docType == null) if(docType == null)
{ {
if (!doc.DocumentElement.Name.Equals("plist")) if(!doc.DocumentElement.Name.Equals("plist"))
{
throw new XmlException("The given XML document is not a property list."); throw new XmlException("The given XML document is not a property list.");
}
} }
else if (!docType.Name.Equals("plist")) else if(!docType.Name.Equals("plist"))
{
throw new XmlException("The given XML document is not a property list."); throw new XmlException("The given XML document is not a property list.");
}
XmlNode rootNode; XmlNode rootNode;
if (doc.DocumentElement.Name.Equals("plist")) if(doc.DocumentElement.Name.Equals("plist"))
{ {
//Root element wrapped in plist tag //Root element wrapped in plist tag
List<XmlNode> rootNodes = FilterElementNodes(doc.DocumentElement.ChildNodes); List<XmlNode> rootNodes = FilterElementNodes(doc.DocumentElement.ChildNodes);
if (rootNodes.Count == 0) if(rootNodes.Count == 0)
throw new PropertyListFormatException("The given XML property list has no root element!"); throw new PropertyListFormatException("The given XML property list has no root element!");
if (rootNodes.Count == 1) if(rootNodes.Count == 1) rootNode = rootNodes[0];
rootNode = rootNodes[0];
else else
throw new PropertyListFormatException("The given XML property list has more than one root element!"); throw
new PropertyListFormatException("The given XML property list has more than one root element!");
} }
else else
//Root NSObject not wrapped in plist-tag //Root NSObject not wrapped in plist-tag
@@ -148,17 +139,17 @@ namespace Claunia.PropertyList
} }
/// <summary> /// <summary>
/// Parses a node in the XML structure and returns the corresponding NSObject /// Parses a node in the XML structure and returns the corresponding NSObject
/// </summary> /// </summary>
/// <returns>The corresponding NSObject.</returns> /// <returns>The corresponding NSObject.</returns>
/// <param name="n">The XML node.</param> /// <param name="n">The XML node.</param>
static NSObject ParseObject(XmlNode n) static NSObject ParseObject(XmlNode n)
{ {
if (n.Name.Equals("dict")) if(n.Name.Equals("dict"))
{ {
NSDictionary dict = new NSDictionary(); NSDictionary dict = new NSDictionary();
List<XmlNode> children = FilterElementNodes(n.ChildNodes); List<XmlNode> children = FilterElementNodes(n.ChildNodes);
for (int i = 0; i < children.Count; i += 2) for(int i = 0; i < children.Count; i += 2)
{ {
XmlNode key = children[i]; XmlNode key = children[i];
XmlNode val = children[i + 1]; XmlNode val = children[i + 1];
@@ -167,78 +158,73 @@ namespace Claunia.PropertyList
dict.Add(keyString, ParseObject(val)); dict.Add(keyString, ParseObject(val));
} }
return dict; return dict;
} }
if (n.Name.Equals("array"))
if(n.Name.Equals("array"))
{ {
List<XmlNode> children = FilterElementNodes(n.ChildNodes); List<XmlNode> children = FilterElementNodes(n.ChildNodes);
NSArray array = new NSArray(children.Count); NSArray array = new NSArray(children.Count);
for (int i = 0; i < children.Count; i++) for(int i = 0; i < children.Count; i++) array.Add(ParseObject(children[i]));
{
array.Add(ParseObject(children[i]));
}
return array; return array;
} }
if (n.Name.Equals("true"))
return new NSNumber(true); if(n.Name.Equals("true")) return new NSNumber(true);
if (n.Name.Equals("false")) if(n.Name.Equals("false")) return new NSNumber(false);
return new NSNumber(false); if(n.Name.Equals("integer")) return new NSNumber(GetNodeTextContents(n), NSNumber.INTEGER);
if (n.Name.Equals("integer")) if(n.Name.Equals("real")) return new NSNumber(GetNodeTextContents(n), NSNumber.REAL);
return new NSNumber(GetNodeTextContents(n), NSNumber.INTEGER); if(n.Name.Equals("string")) return new NSString(GetNodeTextContents(n));
if (n.Name.Equals("real")) if(n.Name.Equals("data")) return new NSData(GetNodeTextContents(n));
return new NSNumber(GetNodeTextContents(n), NSNumber.REAL);
if (n.Name.Equals("string"))
return new NSString(GetNodeTextContents(n));
if (n.Name.Equals("data"))
return new NSData(GetNodeTextContents(n));
return n.Name.Equals("date") ? new NSDate(GetNodeTextContents(n)) : null; return n.Name.Equals("date") ? new NSDate(GetNodeTextContents(n)) : null;
} }
/// <summary> /// <summary>
/// Returns all element nodes that are contained in a list of nodes. /// Returns all element nodes that are contained in a list of nodes.
/// </summary> /// </summary>
/// <returns>The sublist containing only nodes representing actual elements.</returns> /// <returns>The sublist containing only nodes representing actual elements.</returns>
/// <param name="list">The list of nodes to search.</param> /// <param name="list">The list of nodes to search.</param>
static List<XmlNode> FilterElementNodes(XmlNodeList list) static List<XmlNode> FilterElementNodes(XmlNodeList list)
{ {
List<XmlNode> result = new List<XmlNode>(); List<XmlNode> result = new List<XmlNode>();
foreach (XmlNode child in list) foreach(XmlNode child in list)
if (child.NodeType == XmlNodeType.Element) if(child.NodeType == XmlNodeType.Element)
result.Add(child); result.Add(child);
return result; return result;
} }
/// <summary> /// <summary>
/// Returns a node's text content. /// Returns a node's text content.
/// This method will return the text value represented by the node's direct children. /// This method will return the text value represented by the node's direct children.
/// If the given node is a TEXT or CDATA node, then its value is returned. /// If the given node is a TEXT or CDATA node, then its value is returned.
/// </summary> /// </summary>
/// <returns>The node's text content.</returns> /// <returns>The node's text content.</returns>
/// <param name="n">The node.</param> /// <param name="n">The node.</param>
static string GetNodeTextContents(XmlNode n) static string GetNodeTextContents(XmlNode n)
{ {
if (n.NodeType == XmlNodeType.Text || n.NodeType == XmlNodeType.CDATA) if(n.NodeType == XmlNodeType.Text || n.NodeType == XmlNodeType.CDATA)
{ {
string content = n.Value; //This concatenates any adjacent text/cdata/entity nodes string content = n.Value; //This concatenates any adjacent text/cdata/entity nodes
return content ?? ""; return content ?? "";
} }
if (n.HasChildNodes)
if(n.HasChildNodes)
{ {
XmlNodeList children = n.ChildNodes; XmlNodeList children = n.ChildNodes;
foreach (XmlNode child in children) foreach(XmlNode child in children)
{
//Skip any non-text nodes, like comments or entities //Skip any non-text nodes, like comments or entities
if (child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA) if(child.NodeType == XmlNodeType.Text || child.NodeType == XmlNodeType.CDATA)
{ {
string content = child.Value; //This concatenates any adjacent text/cdata/entity nodes string content = child.Value; //This concatenates any adjacent text/cdata/entity nodes
return content ?? ""; return content ?? "";
} }
}
return ""; return "";
} }
return ""; return "";
} }
} }
} }