mirror of
https://github.com/claunia/plist-cil.git
synced 2025-12-16 19:14:26 +00:00
649 lines
22 KiB
C#
649 lines
22 KiB
C#
// plist-cil - An open source library to parse and generate property lists for .NET
|
|
// Copyright (C) 2015 Natalia Portillo
|
|
//
|
|
// This code is based on:
|
|
// plist - An open source library to parse and generate property lists
|
|
// Copyright (C) 2014 Daniel Dreibrodt
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
using System;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
|
|
namespace Claunia.PropertyList
|
|
{
|
|
/// <summary>
|
|
/// A number whose value is either an integer, a real number or bool.
|
|
/// </summary>
|
|
/// @author Daniel Dreibrodt
|
|
/// @author Natalia Portillo
|
|
public class NSNumber : NSObject, IComparable
|
|
{
|
|
/// <summary>
|
|
/// Indicates that the number's value is an integer.
|
|
/// The number is stored as a .NET <see cref="long" />.
|
|
/// Its original value could have been char, short, int, long or even long long.
|
|
/// </summary>
|
|
public const int INTEGER = 0;
|
|
|
|
/// <summary>
|
|
/// Indicates that the number's value is a real number.
|
|
/// The number is stored as a .NET <see cref="double" />.
|
|
/// Its original value could have been float or double.
|
|
/// </summary>
|
|
public const int REAL = 1;
|
|
|
|
/// <summary>
|
|
/// Indicates that the number's value is bool.
|
|
/// </summary>
|
|
public const int BOOLEAN = 2;
|
|
readonly bool boolValue;
|
|
readonly double doubleValue;
|
|
|
|
readonly long longValue;
|
|
|
|
//Holds the current type of this number
|
|
readonly int type;
|
|
|
|
/// <summary>
|
|
/// Parses integers and real numbers from their binary representation.
|
|
/// <i>Note: real numbers are not yet supported.</i>
|
|
/// </summary>
|
|
/// <param name="bytes">The binary representation</param>
|
|
/// <param name="type">The type of number</param>
|
|
/// <seealso cref="INTEGER" />
|
|
/// <seealso cref="REAL" />
|
|
public NSNumber(ReadOnlySpan<byte> bytes, int type)
|
|
{
|
|
switch(type)
|
|
{
|
|
case INTEGER:
|
|
doubleValue = longValue = BinaryPropertyListParser.ParseLong(bytes);
|
|
break;
|
|
|
|
case REAL:
|
|
doubleValue = BinaryPropertyListParser.ParseDouble(bytes);
|
|
longValue = (long)Math.Round(doubleValue);
|
|
break;
|
|
|
|
default: throw new ArgumentException("Type argument is not valid.", nameof(type));
|
|
}
|
|
|
|
this.type = type;
|
|
}
|
|
|
|
public NSNumber(string text, int type)
|
|
{
|
|
switch(type)
|
|
{
|
|
case INTEGER:
|
|
{
|
|
doubleValue = longValue = long.Parse(text, CultureInfo.InvariantCulture);
|
|
break;
|
|
}
|
|
case REAL:
|
|
{
|
|
doubleValue = double.Parse(text, CultureInfo.InvariantCulture);
|
|
longValue = (long)Math.Round(doubleValue);
|
|
break;
|
|
}
|
|
default: { throw new ArgumentException("Type argument is not valid."); }
|
|
}
|
|
|
|
this.type = type;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a number from its textual representation.
|
|
/// </summary>
|
|
/// <param name="text">The textual representation of the number.</param>
|
|
/// <seealso cref="bool.Parse(string)" />
|
|
/// <seealso cref="long.Parse(string)" />
|
|
/// <seealso cref="double.Parse(string, IFormatProvider)" />
|
|
public NSNumber(string text)
|
|
{
|
|
if(text == null) throw new ArgumentException("The given string is null and cannot be parsed as number.");
|
|
|
|
long l;
|
|
double d;
|
|
|
|
if(text.StartsWith("0x") && long.TryParse(text.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out l))
|
|
{
|
|
doubleValue = longValue = l;
|
|
type = INTEGER;
|
|
}
|
|
else if(long.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out l))
|
|
{
|
|
doubleValue = longValue = l;
|
|
type = INTEGER;
|
|
}
|
|
else if(double.TryParse(text, NumberStyles.Number, CultureInfo.InvariantCulture, out d))
|
|
{
|
|
doubleValue = d;
|
|
longValue = (long)Math.Round(doubleValue);
|
|
type = REAL;
|
|
}
|
|
else
|
|
{
|
|
bool isTrue = string.Equals(text, "true", StringComparison.CurrentCultureIgnoreCase) ||
|
|
string.Equals(text, "yes", StringComparison.CurrentCultureIgnoreCase);
|
|
bool isFalse = string.Equals(text, "false", StringComparison.CurrentCultureIgnoreCase) ||
|
|
string.Equals(text, "no", StringComparison.CurrentCultureIgnoreCase);
|
|
|
|
if(isTrue || isFalse)
|
|
{
|
|
type = BOOLEAN;
|
|
boolValue = isTrue;
|
|
doubleValue = longValue = boolValue ? 1 : 0;
|
|
}
|
|
else
|
|
throw new
|
|
ArgumentException("The given string neither represents a double, an int nor a bool value.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an integer number.
|
|
/// </summary>
|
|
/// <param name="i">The integer value.</param>
|
|
public NSNumber(int i)
|
|
{
|
|
doubleValue = longValue = i;
|
|
type = INTEGER;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an integer number.
|
|
/// </summary>
|
|
/// <param name="l">The long integer value.</param>
|
|
public NSNumber(long l)
|
|
{
|
|
doubleValue = longValue = l;
|
|
type = INTEGER;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a real number.
|
|
/// </summary>
|
|
/// <param name="d">The real value.</param>
|
|
public NSNumber(double d)
|
|
{
|
|
longValue = (long)(doubleValue = d);
|
|
type = REAL;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a bool number.
|
|
/// </summary>
|
|
/// <param name="b">The bool value.</param>
|
|
public NSNumber(bool b)
|
|
{
|
|
boolValue = b;
|
|
doubleValue = longValue = b ? 1 : 0;
|
|
type = BOOLEAN;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compares the current <see cref="Claunia.PropertyList.NSNumber" /> to the specified object.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// 0 if the numbers are equal, 1 if the current <see cref="Claunia.PropertyList.NSNumber" /> is greater
|
|
/// than the argument and -1 if it is less, or the argument is not a number.
|
|
/// </returns>
|
|
/// <param name="o">Object to compare to the current <see cref="Claunia.PropertyList.NSNumber" />.</param>
|
|
public int CompareTo(object o)
|
|
{
|
|
double x = ToDouble();
|
|
double y;
|
|
if(o is NSNumber)
|
|
{
|
|
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>
|
|
/// <returns>The type flag.</returns>
|
|
/// <seealso cref="BOOLEAN" />
|
|
/// <seealso cref="INTEGER" />
|
|
/// <seealso cref="REAL" />
|
|
public int GetNSNumberType()
|
|
{
|
|
return type;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether the value of this NSNumber is a bool.
|
|
/// </summary>
|
|
/// <returns>Whether the number's value is a bool.</returns>
|
|
public bool isBoolean()
|
|
{
|
|
return type == BOOLEAN;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether the value of this NSNumber is an integer.
|
|
/// </summary>
|
|
/// <returns>Whether the number's value is an integer.</returns>
|
|
public bool isInteger()
|
|
{
|
|
return type == INTEGER;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether the value of this NSNumber is a real number.
|
|
/// </summary>
|
|
/// <returns>Whether the number's value is a real number.</returns>
|
|
public bool isReal()
|
|
{
|
|
return type == REAL;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number's bool value.
|
|
/// </summary>
|
|
/// <returns><c>true</c> if the value is true or non-zero, <c>false</c> otherwise.</returns>
|
|
public bool ToBool()
|
|
{
|
|
if(type == BOOLEAN) return boolValue;
|
|
|
|
return longValue != 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number's long value.
|
|
/// </summary>
|
|
/// <returns>The value of the number as long</returns>
|
|
public long ToLong()
|
|
{
|
|
return longValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number's int value.
|
|
/// <i>
|
|
/// Note: Even though the number's type might be INTEGER it can be larger than a Java int.
|
|
/// Use intValue() only if you are certain that it contains a number from the int range.
|
|
/// Otherwise the value might be innaccurate.
|
|
/// </i>
|
|
/// </summary>
|
|
/// <returns>The value of the number as int.</returns>
|
|
public int ToInt()
|
|
{
|
|
return (int)longValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number's double value.
|
|
/// </summary>
|
|
/// <returns>The value of the number as double.</returns>
|
|
public double ToDouble()
|
|
{
|
|
return doubleValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The number's float value.
|
|
/// WARNING: Possible loss of precision if the value is outside the float range.
|
|
/// </summary>
|
|
/// <returns>The value of the number as float.</returns>
|
|
public float floatValue()
|
|
{
|
|
return (float)doubleValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether the other object is a NSNumber of the same value.
|
|
/// </summary>
|
|
/// <param name="obj">The object to compare to.</param>
|
|
/// <returns>Whether the objects are equal in terms of numeric value and type.</returns>
|
|
public override bool Equals(object obj)
|
|
{
|
|
if(!(obj is NSNumber)) return false;
|
|
|
|
NSNumber n = (NSNumber)obj;
|
|
return type == n.type && longValue == n.longValue && doubleValue == n.doubleValue &&
|
|
boolValue == n.boolValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serves as a hash function for a <see cref="Claunia.PropertyList.NSNumber" /> object.
|
|
/// </summary>
|
|
/// <returns>
|
|
/// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a
|
|
/// hash table.
|
|
/// </returns>
|
|
public override int GetHashCode()
|
|
{
|
|
int hash = type;
|
|
hash = 37 * hash + (int)(longValue ^ ((uint)longValue >> 32));
|
|
hash = 37 * hash + (int)(BitConverter.DoubleToInt64Bits(doubleValue) ^
|
|
(uint)(BitConverter.DoubleToInt64Bits(doubleValue) >> 32));
|
|
hash = 37 * hash + (ToBool() ? 1 : 0);
|
|
return hash;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />.
|
|
/// </summary>
|
|
/// <returns>A <see cref="System.String" /> that represents the current <see cref="Claunia.PropertyList.NSNumber" />.</returns>
|
|
public override string ToString()
|
|
{
|
|
switch(type)
|
|
{
|
|
case INTEGER: { return ToLong().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)
|
|
{
|
|
Indent(xml, level);
|
|
switch(type)
|
|
{
|
|
case INTEGER:
|
|
{
|
|
xml.Append("<integer>");
|
|
xml.Append(ToLong());
|
|
xml.Append("</integer>");
|
|
break;
|
|
}
|
|
case REAL:
|
|
{
|
|
xml.Append("<real>");
|
|
|
|
if(doubleValue == 0) xml.Append("0.0");
|
|
else xml.Append(ToDouble().ToString("R", CultureInfo.InvariantCulture));
|
|
|
|
xml.Append("</real>");
|
|
break;
|
|
}
|
|
case BOOLEAN:
|
|
{
|
|
if(ToBool()) xml.Append("<true/>");
|
|
else xml.Append("<false/>");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal override void ToBinary(BinaryPropertyListWriter outPlist)
|
|
{
|
|
switch(GetNSNumberType())
|
|
{
|
|
case INTEGER:
|
|
{
|
|
if(ToLong() < 0)
|
|
{
|
|
outPlist.Write(0x13);
|
|
outPlist.WriteBytes(ToLong(), 8);
|
|
}
|
|
else if(ToLong() <= 0xff)
|
|
{
|
|
outPlist.Write(0x10);
|
|
outPlist.WriteBytes(ToLong(), 1);
|
|
}
|
|
else if(ToLong() <= 0xffff)
|
|
{
|
|
outPlist.Write(0x11);
|
|
outPlist.WriteBytes(ToLong(), 2);
|
|
}
|
|
else if(ToLong() <= 0xffffffffL)
|
|
{
|
|
outPlist.Write(0x12);
|
|
outPlist.WriteBytes(ToLong(), 4);
|
|
}
|
|
else
|
|
{
|
|
outPlist.Write(0x13);
|
|
outPlist.WriteBytes(ToLong(), 8);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case REAL:
|
|
{
|
|
outPlist.Write(0x23);
|
|
outPlist.WriteDouble(ToDouble());
|
|
break;
|
|
}
|
|
case BOOLEAN:
|
|
{
|
|
outPlist.Write(ToBool() ? 0x09 : 0x08);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal override void ToASCII(StringBuilder ascii, int level)
|
|
{
|
|
Indent(ascii, level);
|
|
if(type == BOOLEAN) ascii.Append(boolValue ? "YES" : "NO");
|
|
else ascii.Append(ToString());
|
|
}
|
|
|
|
internal override void ToASCIIGnuStep(StringBuilder ascii, int level)
|
|
{
|
|
Indent(ascii, level);
|
|
switch(type)
|
|
{
|
|
case INTEGER:
|
|
{
|
|
ascii.Append("<*I");
|
|
ascii.Append(ToString());
|
|
ascii.Append(">");
|
|
break;
|
|
}
|
|
case REAL:
|
|
{
|
|
ascii.Append("<*R");
|
|
ascii.Append(ToString());
|
|
ascii.Append(">");
|
|
break;
|
|
}
|
|
case BOOLEAN:
|
|
{
|
|
if(boolValue) ascii.Append("<*BY>");
|
|
else ascii.Append("<*BN>");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if an object is a number.
|
|
/// Substitutes .NET's Number class comparison
|
|
/// </summary>
|
|
/// <returns><c>true</c> if it is a number.</returns>
|
|
/// <param name="o">Object.</param>
|
|
static bool IsNumber(object o)
|
|
{
|
|
return o is sbyte || o is byte || o is short || o is ushort || o is int || o is uint || o is long ||
|
|
o is ulong || o is float || o is double || o is decimal;
|
|
}
|
|
|
|
static double GetDoubleFromObject(object o)
|
|
{
|
|
if(o is sbyte) return (sbyte)o;
|
|
if(o is byte) return (byte)o;
|
|
if(o is short) return (short)o;
|
|
if(o is ushort) return (ushort)o;
|
|
if(o is int) return (int)o;
|
|
if(o is uint) return (uint)o;
|
|
if(o is long) return (long)o;
|
|
if(o is ulong) return (ulong)o;
|
|
if(o is float) return (float)o;
|
|
if(o is double) return (double)o;
|
|
if(o is decimal) return (double)(decimal)o;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
|
|
/// <see cref="Claunia.PropertyList.NSNumber" />.
|
|
/// </summary>
|
|
/// <param name="obj">
|
|
/// The <see cref="Claunia.PropertyList.NSObject" /> to compare with the current
|
|
/// <see cref="Claunia.PropertyList.NSNumber" />.
|
|
/// </param>
|
|
/// <returns>
|
|
/// <c>true</c> if the specified <see cref="Claunia.PropertyList.NSObject" /> is equal to the current
|
|
/// <see cref="Claunia.PropertyList.NSNumber" />; otherwise, <c>false</c>.
|
|
/// </returns>
|
|
public override bool Equals(NSObject obj)
|
|
{
|
|
if(!(obj is NSNumber)) return false;
|
|
|
|
if(((NSNumber)obj).GetNSNumberType() != type) return false;
|
|
|
|
switch(type)
|
|
{
|
|
case INTEGER: return longValue == ((NSNumber)obj).ToLong();
|
|
case REAL: return doubleValue == ((NSNumber)obj).ToDouble();
|
|
case BOOLEAN: return boolValue == ((NSNumber)obj).ToBool();
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
public static explicit operator ulong(NSNumber value)
|
|
{
|
|
return (ulong)value.longValue;
|
|
}
|
|
|
|
public static explicit operator long(NSNumber value)
|
|
{
|
|
return value.longValue;
|
|
}
|
|
|
|
public static explicit operator uint(NSNumber value)
|
|
{
|
|
return (uint)value.longValue;
|
|
}
|
|
|
|
public static explicit operator int(NSNumber value)
|
|
{
|
|
return (int)value.longValue;
|
|
}
|
|
|
|
public static explicit operator ushort(NSNumber value)
|
|
{
|
|
return (ushort)value.longValue;
|
|
}
|
|
|
|
public static explicit operator short(NSNumber value)
|
|
{
|
|
return (short)value.longValue;
|
|
}
|
|
|
|
public static explicit operator byte(NSNumber value)
|
|
{
|
|
return (byte)value.longValue;
|
|
}
|
|
|
|
public static explicit operator sbyte(NSNumber value)
|
|
{
|
|
return (sbyte)value.longValue;
|
|
}
|
|
|
|
public static explicit operator double(NSNumber value)
|
|
{
|
|
return value.doubleValue;
|
|
}
|
|
|
|
public static explicit operator float(NSNumber value)
|
|
{
|
|
return (float)value.doubleValue;
|
|
}
|
|
|
|
public static explicit operator bool(NSNumber value)
|
|
{
|
|
return value.boolValue;
|
|
}
|
|
|
|
public static explicit operator NSNumber(ulong value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(long value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(uint value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(int value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(ushort value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(short value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(byte value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(sbyte value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(double value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(float value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
|
|
public static explicit operator NSNumber(bool value)
|
|
{
|
|
return new NSNumber(value);
|
|
}
|
|
}
|
|
} |