diff --git a/.gitignore b/.gitignore
index 25faa223..9bb491c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,4 @@ $tf*/
packages/*
Gu.Units.Generator/Templates/*.txt
publish/*
+Generator/*
diff --git a/Gu.Units.Fsharp.Tests/Gu.Units.Fsharp.Tests.fsproj b/Gu.Units.Fsharp.Tests/Gu.Units.Fsharp.Tests.fsproj
index fd03d4e3..31fd19e5 100644
--- a/Gu.Units.Fsharp.Tests/Gu.Units.Fsharp.Tests.fsproj
+++ b/Gu.Units.Fsharp.Tests/Gu.Units.Fsharp.Tests.fsproj
@@ -86,42 +86,11 @@
-->
-
-
-
-
- ..\packages\NUnit\lib\net20\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\net40\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\net45\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll
- True
- True
-
-
-
-
+
+
+ ..\packages\NUnit\lib\nunit.framework.dll
+ True
+ True
+
+
\ No newline at end of file
diff --git a/Gu.Units.Generator.Tests/Gu.Units.Generator.Tests.csproj b/Gu.Units.Generator.Tests/Gu.Units.Generator.Tests.csproj
index 8f243bd0..12762745 100644
--- a/Gu.Units.Generator.Tests/Gu.Units.Generator.Tests.csproj
+++ b/Gu.Units.Generator.Tests/Gu.Units.Generator.Tests.csproj
@@ -73,42 +73,11 @@
-->
-
-
-
-
- ..\packages\NUnit\lib\net20\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\net40\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\net45\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll
- True
- True
-
-
-
-
+
+
+ ..\packages\NUnit\lib\nunit.framework.dll
+ True
+ True
+
+
\ No newline at end of file
diff --git a/Gu.Units.Generator/Descriptors/Settings.cs b/Gu.Units.Generator/Descriptors/Settings.cs
index 9e342f8d..3032ac1a 100644
--- a/Gu.Units.Generator/Descriptors/Settings.cs
+++ b/Gu.Units.Generator/Descriptors/Settings.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
- using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -18,6 +17,7 @@ public class Settings
private readonly ParentCollection _derivedUnits;
private readonly ParentCollection _siUnits;
private readonly ObservableCollection _prefixes = new ObservableCollection();
+
protected Settings()
{
_derivedUnits = new ParentCollection(this, (unit, settings) => unit.Settings = settings);
@@ -115,18 +115,9 @@ public ObservableCollection Prefixes
get { return _prefixes; }
}
- public IEnumerable AllUnits
- {
- get { return SiUnits.Concat(DerivedUnits); }
- }
+ public IReadOnlyList AllUnits => SiUnits.Concat(DerivedUnits).ToList();
- public IEnumerable Quantities
- {
- get
- {
- return AllUnits.Select(x => x.Quantity).ToArray();
- }
- }
+ public IReadOnlyList Quantities => AllUnits.Select(x => x.Quantity).ToList();
public static Settings FromFile(string fullFileName)
{
diff --git a/Gu.Units.Generator/GeneratorSettings.xml b/Gu.Units.Generator/GeneratorSettings.xml
index 0763c0a3..da9e0a13 100644
--- a/Gu.Units.Generator/GeneratorSettings.xml
+++ b/Gu.Units.Generator/GeneratorSettings.xml
@@ -268,7 +268,7 @@
0
-
+
Bars
bar
@@ -287,6 +287,33 @@
+
+ NewtonsPerSquareMillimetre
+ N⋅mm⁻²
+
+ 1000000
+ 0
+
+
+
+
+ KilonewtonsPerSquareMillimetre
+ kN⋅mm⁻²
+
+ 1000000000
+ 0
+
+
+
+
+ NewtonsPerSquareMetre
+ N/m²
+
+ 1
+ 0
+
+
+
@@ -1243,6 +1270,147 @@
+
+ MetresPerUnitless
+ m/ul
+ LengthPerUnitless
+
+
+ MillimetresPerPercent
+ mm/%
+
+ 0.1
+ 0
+
+
+
+
+ MicrometresPerPercent
+ µm/%
+
+ 9.9999999999999991E-05
+ 0
+
+
+
+
+ NanometresPerPercent
+ nm/%
+
+ 1.0000000000000001E-07
+ 0
+
+
+
+
+ MetresPerPercent
+ m/%
+
+ 100
+ 0
+
+
+
+
+
+
+ Metres
+ 1
+
+
+ Scalar
+ -1
+
+
+
+
+ RadiansPerUnitless
+ rad/ul
+ AnglePerUnitless
+
+
+ DegreesPerPercent
+ °/%
+
+ 1.7453292519943295
+ 0
+
+
+
+
+ RadiansPerPercent
+ rad/%
+
+ 100
+ 0
+
+
+
+
+
+
+ Radians
+ 1
+
+
+ Scalar
+ -1
+
+
+
+
+ NewtonsPerUnitless
+ N/ul
+ ForcePerUnitless
+
+
+ NewtonsPerPercent
+ N/%
+
+ 100
+ 0
+
+
+
+
+ KilonewtonsPerPercent
+ kN/%
+
+ 100000
+ 0
+
+
+
+
+ MeganewtonsPerPercent
+ MN/%
+
+ 100000000
+ 0
+
+
+
+
+ GiganewtonsPerPercent
+ GN/%
+
+ 100000000000
+ 0
+
+
+
+
+
+
+ Newtons
+ 1
+
+
+ Scalar
+ -1
+
+
+
diff --git a/Gu.Units.Generator/Gu.Units.Generator.csproj b/Gu.Units.Generator/Gu.Units.Generator.csproj
index 6a319973..dc05b3b3 100644
--- a/Gu.Units.Generator/Gu.Units.Generator.csproj
+++ b/Gu.Units.Generator/Gu.Units.Generator.csproj
@@ -55,15 +55,14 @@
4
- false
+ true
-
-
+ Key.snk
-
+
true
- bin\Generator Only\
+ bin\GeneratorOnly\
DEBUG;TRACE
full
AnyCPU
@@ -146,25 +145,23 @@
TextTemplatingFileGenerator
UnitTypeConverter.txt
-
- True
- True
- UnitTypeConverterGenerator.tt
-
+
+
True
True
UnitTypeConverter.tt
-
+
TextTemplatingFileGenerator
- UnitTypeConverterGenerator.txt
+ UnitTypeConverters.txt
ResXFileCodeGenerator
Resources.Designer.cs
Designer
+
SettingsSingleFileGenerator
@@ -179,27 +176,22 @@
TextTemplatingFileGenerator
EnumerableGenerator.txt
-
+
TextTemplatingFileGenerator
- QuantityTypeConverterGenerator.txt
+ QuantityTypeConverters.txt
-
- True
- True
- QuantityTypeConverterGenerator.tt
-
-
+
Code
TextTemplatingFileGenerator
- UnitGenerator.txt
+ Units.txt
TextTemplatingFileGenerator
Quantity.txt
-
+
TextTemplatingFileGenerator
- QuantityGenerator.txt
+ Quantties.txt
TextTemplatingFileGenerator
@@ -213,11 +205,6 @@
-
- UnitGenerator.tt
- True
- True
-
True
True
@@ -242,21 +229,16 @@
Designer
-
- True
- True
- Enumerable.tt
-
True
True
EnumerableGenerator.tt
-
+
True
True
- QuantityGenerator.tt
-
+ Enumerable.tt
+
@@ -278,6 +260,65 @@
+
+
+ TextTemplatingFileGenerator
+ QuantityValueConverter.txt
+
+
+ QuantityValueConverter.tt
+ True
+ True
+
+
+ TextTemplatingFileGenerator
+ QuantityValueConverters.txt
+
+
+ TextTemplatingFileGenerator
+ QuantityJsonConverter.txt
+
+
+ QuantityJsonConverter.tt
+ True
+ True
+
+
+ TextTemplatingFileGenerator
+ QuantityJsonConverters.txt
+
+
+ QuantityJsonConverters.tt
+ True
+ True
+
+
+ True
+ True
+ QuantityValueConverters.tt
+
+
+ True
+ True
+ QuantityTypeConverters.tt
+
+
+ True
+ True
+ Quantties.tt
+
+
+
+ True
+ True
+ Units.tt
+
+
+ True
+ True
+ UnitTypeConverters.tt
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\net35\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\net20\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\portable-net45+wp80+win8+wpa81+dnxcore50\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\NUnit\lib\nunit.framework.dll
+ True
+ True
+
+
+
\ No newline at end of file
diff --git a/Gu.Units.Json.Tests/Properties/AssemblyInfo.cs b/Gu.Units.Json.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..cd139beb
--- /dev/null
+++ b/Gu.Units.Json.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Gu.Units.Json.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Gu.Units.Json.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("ddd84bca-0ff9-4854-bdb3-c69117b2978a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Gu.Units.Json.Tests/SerializationTests.cs b/Gu.Units.Json.Tests/SerializationTests.cs
new file mode 100644
index 00000000..ebbd3e32
--- /dev/null
+++ b/Gu.Units.Json.Tests/SerializationTests.cs
@@ -0,0 +1,64 @@
+namespace Gu.Units.Json.Tests
+{
+ using System.Globalization;
+ using Newtonsoft.Json;
+ using NUnit.Framework;
+
+ public class SerializationTests
+ {
+ [Test]
+ public void ToJsonDegrees()
+ {
+ var dummyClass = new DummyClass { Angle = Angle.FromDegrees(1.23) };
+ JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Degrees, "sv-SE");
+ var actual = JsonConvert.SerializeObject(dummyClass);
+ Assert.AreEqual("{\"Angle\":\"1,23\"}", actual);
+ }
+
+ [Test]
+ public void ToJsonRadians()
+ {
+ var dummyClass = new DummyClass { Angle = Angle.FromRadians(1.23) };
+ JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Radians, "en-US");
+ var actual = JsonConvert.SerializeObject(dummyClass);
+ Assert.AreEqual("{\"Angle\":\"1.23\u00A0rad\"}", actual);
+ }
+
+ [TestCase("{\"Angle\":\"1,23\"}", "sv-SE")]
+ [TestCase("{\"Angle\":\"1.23\"}", "en-US")]
+ public void FromJsonDefault(string json, string culture)
+ {
+ JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Default, culture);
+ var actual = JsonConvert.DeserializeObject(json);
+ Assert.AreEqual(Angle.FromDegrees(1.23), actual.Angle);
+ }
+
+ [TestCase("{\"Angle\":\"1,23\"}", "sv-SE")]
+ [TestCase("{\"Angle\":\"1.23\"}", "en-US")]
+ public void FromJsonDegrees(string json, string culture)
+ {
+ JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Degrees, culture);
+ var actual = JsonConvert.DeserializeObject(json);
+ Assert.AreEqual(Angle.FromDegrees(1.23), actual.Angle);
+ }
+
+ [TestCase("{\"Angle\":\"1,23\"}", "sv-SE")]
+ [TestCase("{\"Angle\":\"1.23\"}", "en-US")]
+ public void FromJsonRadians(string json, string culture)
+ {
+ JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Default, culture);
+ // yes radians converter can read degrees it only outputs radians
+ var actual = JsonConvert.DeserializeObject(json);
+ Assert.AreEqual(Angle.FromDegrees(1.23), actual.Angle);
+ }
+
+ private JsonSerializerSettings CreateSettings(AngleJsonConverter converter, string culture)
+ {
+ return new JsonSerializerSettings
+ {
+ Converters = new[] { converter },
+ Culture = CultureInfo.GetCultureInfo(culture)
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json.Tests/paket.references b/Gu.Units.Json.Tests/paket.references
new file mode 100644
index 00000000..91a7da98
--- /dev/null
+++ b/Gu.Units.Json.Tests/paket.references
@@ -0,0 +1,2 @@
+Newtonsoft.Json
+NUnit
\ No newline at end of file
diff --git a/Gu.Units.Json/AccelerationJsonConverter.generated.cs b/Gu.Units.Json/AccelerationJsonConverter.generated.cs
new file mode 100644
index 00000000..9910e4fd
--- /dev/null
+++ b/Gu.Units.Json/AccelerationJsonConverter.generated.cs
@@ -0,0 +1,41 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class AccelerationJsonConverter : JsonConverter
+ {
+ public static readonly AccelerationJsonConverter Default = new AccelerationJsonConverter(AccelerationUnit.MetresPerSecondSquared);
+ public static readonly AccelerationJsonConverter MetresPerSecondSquared = new AccelerationJsonConverter(AccelerationUnit.MetresPerSecondSquared);
+ public static readonly AccelerationJsonConverter MillimetresPerSecondSquared = new AccelerationJsonConverter(AccelerationUnit.MillimetresPerSecondSquared);
+ public static readonly AccelerationJsonConverter CentimetresPerSecondSquared = new AccelerationJsonConverter(AccelerationUnit.CentimetresPerSecondSquared);
+
+ private readonly AccelerationUnit unit;
+
+ private AccelerationJsonConverter(AccelerationUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var acceleration = (Acceleration)value;
+ serializer.Serialize(writer, acceleration.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Acceleration);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Acceleration.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/AngleJsonConverter.generated.cs b/Gu.Units.Json/AngleJsonConverter.generated.cs
new file mode 100644
index 00000000..c199ebf2
--- /dev/null
+++ b/Gu.Units.Json/AngleJsonConverter.generated.cs
@@ -0,0 +1,40 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class AngleJsonConverter : JsonConverter
+ {
+ public static readonly AngleJsonConverter Default = new AngleJsonConverter(AngleUnit.Radians);
+ public static readonly AngleJsonConverter Radians = new AngleJsonConverter(AngleUnit.Radians);
+ public static readonly AngleJsonConverter Degrees = new AngleJsonConverter(AngleUnit.Degrees);
+
+ private readonly AngleUnit unit;
+
+ private AngleJsonConverter(AngleUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var angle = (Angle)value;
+ serializer.Serialize(writer, angle.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Angle);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Angle.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/AnglePerUnitlessJsonConverter.generated.cs b/Gu.Units.Json/AnglePerUnitlessJsonConverter.generated.cs
new file mode 100644
index 00000000..68573263
--- /dev/null
+++ b/Gu.Units.Json/AnglePerUnitlessJsonConverter.generated.cs
@@ -0,0 +1,41 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class AnglePerUnitlessJsonConverter : JsonConverter
+ {
+ public static readonly AnglePerUnitlessJsonConverter Default = new AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit.RadiansPerUnitless);
+ public static readonly AnglePerUnitlessJsonConverter RadiansPerUnitless = new AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit.RadiansPerUnitless);
+ public static readonly AnglePerUnitlessJsonConverter DegreesPerPercent = new AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit.DegreesPerPercent);
+ public static readonly AnglePerUnitlessJsonConverter RadiansPerPercent = new AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit.RadiansPerPercent);
+
+ private readonly AnglePerUnitlessUnit unit;
+
+ private AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var anglePerUnitless = (AnglePerUnitless)value;
+ serializer.Serialize(writer, anglePerUnitless.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(AnglePerUnitless);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return AnglePerUnitless.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/AngularAccelerationJsonConverter.generated.cs b/Gu.Units.Json/AngularAccelerationJsonConverter.generated.cs
new file mode 100644
index 00000000..e076580b
--- /dev/null
+++ b/Gu.Units.Json/AngularAccelerationJsonConverter.generated.cs
@@ -0,0 +1,44 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class AngularAccelerationJsonConverter : JsonConverter
+ {
+ public static readonly AngularAccelerationJsonConverter Default = new AngularAccelerationJsonConverter(AngularAccelerationUnit.RadiansPerSecondSquared);
+ public static readonly AngularAccelerationJsonConverter RadiansPerSecondSquared = new AngularAccelerationJsonConverter(AngularAccelerationUnit.RadiansPerSecondSquared);
+ public static readonly AngularAccelerationJsonConverter DegreesPerSquareSecond = new AngularAccelerationJsonConverter(AngularAccelerationUnit.DegreesPerSquareSecond);
+ public static readonly AngularAccelerationJsonConverter RadiansPerSquareHour = new AngularAccelerationJsonConverter(AngularAccelerationUnit.RadiansPerSquareHour);
+ public static readonly AngularAccelerationJsonConverter DegreesPerSquareHour = new AngularAccelerationJsonConverter(AngularAccelerationUnit.DegreesPerSquareHour);
+ public static readonly AngularAccelerationJsonConverter DegreesPerSquareMinute = new AngularAccelerationJsonConverter(AngularAccelerationUnit.DegreesPerSquareMinute);
+ public static readonly AngularAccelerationJsonConverter RadiansPerSquareMinute = new AngularAccelerationJsonConverter(AngularAccelerationUnit.RadiansPerSquareMinute);
+
+ private readonly AngularAccelerationUnit unit;
+
+ private AngularAccelerationJsonConverter(AngularAccelerationUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var angularAcceleration = (AngularAcceleration)value;
+ serializer.Serialize(writer, angularAcceleration.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(AngularAcceleration);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return AngularAcceleration.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/AngularJerkJsonConverter.generated.cs b/Gu.Units.Json/AngularJerkJsonConverter.generated.cs
new file mode 100644
index 00000000..d7e3d811
--- /dev/null
+++ b/Gu.Units.Json/AngularJerkJsonConverter.generated.cs
@@ -0,0 +1,44 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class AngularJerkJsonConverter : JsonConverter
+ {
+ public static readonly AngularJerkJsonConverter Default = new AngularJerkJsonConverter(AngularJerkUnit.RadiansPerSecondCubed);
+ public static readonly AngularJerkJsonConverter RadiansPerSecondCubed = new AngularJerkJsonConverter(AngularJerkUnit.RadiansPerSecondCubed);
+ public static readonly AngularJerkJsonConverter DegreesPerSecondCubed = new AngularJerkJsonConverter(AngularJerkUnit.DegreesPerSecondCubed);
+ public static readonly AngularJerkJsonConverter RadiansPerHourCubed = new AngularJerkJsonConverter(AngularJerkUnit.RadiansPerHourCubed);
+ public static readonly AngularJerkJsonConverter DegreesPerHourCubed = new AngularJerkJsonConverter(AngularJerkUnit.DegreesPerHourCubed);
+ public static readonly AngularJerkJsonConverter RadiansPerMinuteCubed = new AngularJerkJsonConverter(AngularJerkUnit.RadiansPerMinuteCubed);
+ public static readonly AngularJerkJsonConverter DegreesPerMinuteCubed = new AngularJerkJsonConverter(AngularJerkUnit.DegreesPerMinuteCubed);
+
+ private readonly AngularJerkUnit unit;
+
+ private AngularJerkJsonConverter(AngularJerkUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var angularJerk = (AngularJerk)value;
+ serializer.Serialize(writer, angularJerk.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(AngularJerk);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return AngularJerk.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/AngularSpeedJsonConverter.generated.cs b/Gu.Units.Json/AngularSpeedJsonConverter.generated.cs
new file mode 100644
index 00000000..94a35190
--- /dev/null
+++ b/Gu.Units.Json/AngularSpeedJsonConverter.generated.cs
@@ -0,0 +1,45 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class AngularSpeedJsonConverter : JsonConverter
+ {
+ public static readonly AngularSpeedJsonConverter Default = new AngularSpeedJsonConverter(AngularSpeedUnit.RadiansPerSecond);
+ public static readonly AngularSpeedJsonConverter RadiansPerSecond = new AngularSpeedJsonConverter(AngularSpeedUnit.RadiansPerSecond);
+ public static readonly AngularSpeedJsonConverter RevolutionsPerMinute = new AngularSpeedJsonConverter(AngularSpeedUnit.RevolutionsPerMinute);
+ public static readonly AngularSpeedJsonConverter DegreesPerSecond = new AngularSpeedJsonConverter(AngularSpeedUnit.DegreesPerSecond);
+ public static readonly AngularSpeedJsonConverter DegreesPerMinute = new AngularSpeedJsonConverter(AngularSpeedUnit.DegreesPerMinute);
+ public static readonly AngularSpeedJsonConverter RadiansPerMinute = new AngularSpeedJsonConverter(AngularSpeedUnit.RadiansPerMinute);
+ public static readonly AngularSpeedJsonConverter DegreesPerHour = new AngularSpeedJsonConverter(AngularSpeedUnit.DegreesPerHour);
+ public static readonly AngularSpeedJsonConverter RadiansPerHour = new AngularSpeedJsonConverter(AngularSpeedUnit.RadiansPerHour);
+
+ private readonly AngularSpeedUnit unit;
+
+ private AngularSpeedJsonConverter(AngularSpeedUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var angularSpeed = (AngularSpeed)value;
+ serializer.Serialize(writer, angularSpeed.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(AngularSpeed);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return AngularSpeed.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/AreaJsonConverter.generated.cs b/Gu.Units.Json/AreaJsonConverter.generated.cs
new file mode 100644
index 00000000..5d4e1721
--- /dev/null
+++ b/Gu.Units.Json/AreaJsonConverter.generated.cs
@@ -0,0 +1,47 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class AreaJsonConverter : JsonConverter
+ {
+ public static readonly AreaJsonConverter Default = new AreaJsonConverter(AreaUnit.SquareMetres);
+ public static readonly AreaJsonConverter SquareMetres = new AreaJsonConverter(AreaUnit.SquareMetres);
+ public static readonly AreaJsonConverter SquareMillimetres = new AreaJsonConverter(AreaUnit.SquareMillimetres);
+ public static readonly AreaJsonConverter SquareCentimetres = new AreaJsonConverter(AreaUnit.SquareCentimetres);
+ public static readonly AreaJsonConverter SquareDecimetres = new AreaJsonConverter(AreaUnit.SquareDecimetres);
+ public static readonly AreaJsonConverter SquareKilometres = new AreaJsonConverter(AreaUnit.SquareKilometres);
+ public static readonly AreaJsonConverter SquareInches = new AreaJsonConverter(AreaUnit.SquareInches);
+ public static readonly AreaJsonConverter Hectare = new AreaJsonConverter(AreaUnit.Hectare);
+ public static readonly AreaJsonConverter SquareMile = new AreaJsonConverter(AreaUnit.SquareMile);
+ public static readonly AreaJsonConverter SquareYard = new AreaJsonConverter(AreaUnit.SquareYard);
+
+ private readonly AreaUnit unit;
+
+ private AreaJsonConverter(AreaUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var area = (Area)value;
+ serializer.Serialize(writer, area.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Area);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Area.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/CapacitanceJsonConverter.generated.cs b/Gu.Units.Json/CapacitanceJsonConverter.generated.cs
new file mode 100644
index 00000000..2463585e
--- /dev/null
+++ b/Gu.Units.Json/CapacitanceJsonConverter.generated.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class CapacitanceJsonConverter : JsonConverter
+ {
+ public static readonly CapacitanceJsonConverter Default = new CapacitanceJsonConverter(CapacitanceUnit.Farads);
+ public static readonly CapacitanceJsonConverter Farads = new CapacitanceJsonConverter(CapacitanceUnit.Farads);
+
+ private readonly CapacitanceUnit unit;
+
+ private CapacitanceJsonConverter(CapacitanceUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var capacitance = (Capacitance)value;
+ serializer.Serialize(writer, capacitance.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Capacitance);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Capacitance.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/CurrentJsonConverter.generated.cs b/Gu.Units.Json/CurrentJsonConverter.generated.cs
new file mode 100644
index 00000000..43fbe7a1
--- /dev/null
+++ b/Gu.Units.Json/CurrentJsonConverter.generated.cs
@@ -0,0 +1,43 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class CurrentJsonConverter : JsonConverter
+ {
+ public static readonly CurrentJsonConverter Default = new CurrentJsonConverter(CurrentUnit.Amperes);
+ public static readonly CurrentJsonConverter Amperes = new CurrentJsonConverter(CurrentUnit.Amperes);
+ public static readonly CurrentJsonConverter Milliamperes = new CurrentJsonConverter(CurrentUnit.Milliamperes);
+ public static readonly CurrentJsonConverter Kiloamperes = new CurrentJsonConverter(CurrentUnit.Kiloamperes);
+ public static readonly CurrentJsonConverter Megaamperes = new CurrentJsonConverter(CurrentUnit.Megaamperes);
+ public static readonly CurrentJsonConverter Microamperes = new CurrentJsonConverter(CurrentUnit.Microamperes);
+
+ private readonly CurrentUnit unit;
+
+ private CurrentJsonConverter(CurrentUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var current = (Current)value;
+ serializer.Serialize(writer, current.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Current);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Current.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/DensityJsonConverter.generated.cs b/Gu.Units.Json/DensityJsonConverter.generated.cs
new file mode 100644
index 00000000..8b74d6cb
--- /dev/null
+++ b/Gu.Units.Json/DensityJsonConverter.generated.cs
@@ -0,0 +1,41 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class DensityJsonConverter : JsonConverter
+ {
+ public static readonly DensityJsonConverter Default = new DensityJsonConverter(DensityUnit.KilogramsPerCubicMetre);
+ public static readonly DensityJsonConverter KilogramsPerCubicMetre = new DensityJsonConverter(DensityUnit.KilogramsPerCubicMetre);
+ public static readonly DensityJsonConverter GramsPerCubicMillimetre = new DensityJsonConverter(DensityUnit.GramsPerCubicMillimetre);
+ public static readonly DensityJsonConverter GramsPerCubicCentimetre = new DensityJsonConverter(DensityUnit.GramsPerCubicCentimetre);
+
+ private readonly DensityUnit unit;
+
+ private DensityJsonConverter(DensityUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var density = (Density)value;
+ serializer.Serialize(writer, density.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Density);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Density.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/ElectricChargeJsonConverter.generated.cs b/Gu.Units.Json/ElectricChargeJsonConverter.generated.cs
new file mode 100644
index 00000000..537dea09
--- /dev/null
+++ b/Gu.Units.Json/ElectricChargeJsonConverter.generated.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class ElectricChargeJsonConverter : JsonConverter
+ {
+ public static readonly ElectricChargeJsonConverter Default = new ElectricChargeJsonConverter(ElectricChargeUnit.Coulombs);
+ public static readonly ElectricChargeJsonConverter Coulombs = new ElectricChargeJsonConverter(ElectricChargeUnit.Coulombs);
+
+ private readonly ElectricChargeUnit unit;
+
+ private ElectricChargeJsonConverter(ElectricChargeUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var electricCharge = (ElectricCharge)value;
+ serializer.Serialize(writer, electricCharge.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(ElectricCharge);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return ElectricCharge.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/EnergyJsonConverter.generated.cs b/Gu.Units.Json/EnergyJsonConverter.generated.cs
new file mode 100644
index 00000000..cb379172
--- /dev/null
+++ b/Gu.Units.Json/EnergyJsonConverter.generated.cs
@@ -0,0 +1,46 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class EnergyJsonConverter : JsonConverter
+ {
+ public static readonly EnergyJsonConverter Default = new EnergyJsonConverter(EnergyUnit.Joules);
+ public static readonly EnergyJsonConverter Joules = new EnergyJsonConverter(EnergyUnit.Joules);
+ public static readonly EnergyJsonConverter Nanojoules = new EnergyJsonConverter(EnergyUnit.Nanojoules);
+ public static readonly EnergyJsonConverter Microjoules = new EnergyJsonConverter(EnergyUnit.Microjoules);
+ public static readonly EnergyJsonConverter Millijoules = new EnergyJsonConverter(EnergyUnit.Millijoules);
+ public static readonly EnergyJsonConverter Kilojoules = new EnergyJsonConverter(EnergyUnit.Kilojoules);
+ public static readonly EnergyJsonConverter Megajoules = new EnergyJsonConverter(EnergyUnit.Megajoules);
+ public static readonly EnergyJsonConverter Gigajoules = new EnergyJsonConverter(EnergyUnit.Gigajoules);
+ public static readonly EnergyJsonConverter KilowattHours = new EnergyJsonConverter(EnergyUnit.KilowattHours);
+
+ private readonly EnergyUnit unit;
+
+ private EnergyJsonConverter(EnergyUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var energy = (Energy)value;
+ serializer.Serialize(writer, energy.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Energy);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Energy.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/FlexibilityJsonConverter.generated.cs b/Gu.Units.Json/FlexibilityJsonConverter.generated.cs
new file mode 100644
index 00000000..2f5b06c3
--- /dev/null
+++ b/Gu.Units.Json/FlexibilityJsonConverter.generated.cs
@@ -0,0 +1,42 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class FlexibilityJsonConverter : JsonConverter
+ {
+ public static readonly FlexibilityJsonConverter Default = new FlexibilityJsonConverter(FlexibilityUnit.MetresPerNewton);
+ public static readonly FlexibilityJsonConverter MetresPerNewton = new FlexibilityJsonConverter(FlexibilityUnit.MetresPerNewton);
+ public static readonly FlexibilityJsonConverter MillimetresPerNewton = new FlexibilityJsonConverter(FlexibilityUnit.MillimetresPerNewton);
+ public static readonly FlexibilityJsonConverter MillimetresPerKilonewton = new FlexibilityJsonConverter(FlexibilityUnit.MillimetresPerKilonewton);
+ public static readonly FlexibilityJsonConverter MicrometresPerKilonewton = new FlexibilityJsonConverter(FlexibilityUnit.MicrometresPerKilonewton);
+
+ private readonly FlexibilityUnit unit;
+
+ private FlexibilityJsonConverter(FlexibilityUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var flexibility = (Flexibility)value;
+ serializer.Serialize(writer, flexibility.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Flexibility);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Flexibility.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/ForceJsonConverter.generated.cs b/Gu.Units.Json/ForceJsonConverter.generated.cs
new file mode 100644
index 00000000..38c401f9
--- /dev/null
+++ b/Gu.Units.Json/ForceJsonConverter.generated.cs
@@ -0,0 +1,45 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class ForceJsonConverter : JsonConverter
+ {
+ public static readonly ForceJsonConverter Default = new ForceJsonConverter(ForceUnit.Newtons);
+ public static readonly ForceJsonConverter Newtons = new ForceJsonConverter(ForceUnit.Newtons);
+ public static readonly ForceJsonConverter Nanonewtons = new ForceJsonConverter(ForceUnit.Nanonewtons);
+ public static readonly ForceJsonConverter Micronewtons = new ForceJsonConverter(ForceUnit.Micronewtons);
+ public static readonly ForceJsonConverter Millinewtons = new ForceJsonConverter(ForceUnit.Millinewtons);
+ public static readonly ForceJsonConverter Kilonewtons = new ForceJsonConverter(ForceUnit.Kilonewtons);
+ public static readonly ForceJsonConverter Meganewtons = new ForceJsonConverter(ForceUnit.Meganewtons);
+ public static readonly ForceJsonConverter Giganewtons = new ForceJsonConverter(ForceUnit.Giganewtons);
+
+ private readonly ForceUnit unit;
+
+ private ForceJsonConverter(ForceUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var force = (Force)value;
+ serializer.Serialize(writer, force.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Force);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Force.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/ForcePerUnitlessJsonConverter.generated.cs b/Gu.Units.Json/ForcePerUnitlessJsonConverter.generated.cs
new file mode 100644
index 00000000..931fda19
--- /dev/null
+++ b/Gu.Units.Json/ForcePerUnitlessJsonConverter.generated.cs
@@ -0,0 +1,43 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class ForcePerUnitlessJsonConverter : JsonConverter
+ {
+ public static readonly ForcePerUnitlessJsonConverter Default = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.NewtonsPerUnitless);
+ public static readonly ForcePerUnitlessJsonConverter NewtonsPerUnitless = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.NewtonsPerUnitless);
+ public static readonly ForcePerUnitlessJsonConverter NewtonsPerPercent = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.NewtonsPerPercent);
+ public static readonly ForcePerUnitlessJsonConverter KilonewtonsPerPercent = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.KilonewtonsPerPercent);
+ public static readonly ForcePerUnitlessJsonConverter MeganewtonsPerPercent = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.MeganewtonsPerPercent);
+ public static readonly ForcePerUnitlessJsonConverter GiganewtonsPerPercent = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.GiganewtonsPerPercent);
+
+ private readonly ForcePerUnitlessUnit unit;
+
+ private ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var forcePerUnitless = (ForcePerUnitless)value;
+ serializer.Serialize(writer, forcePerUnitless.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(ForcePerUnitless);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return ForcePerUnitless.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/FrequencyJsonConverter.generated.cs b/Gu.Units.Json/FrequencyJsonConverter.generated.cs
new file mode 100644
index 00000000..a2eb03d8
--- /dev/null
+++ b/Gu.Units.Json/FrequencyJsonConverter.generated.cs
@@ -0,0 +1,43 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class FrequencyJsonConverter : JsonConverter
+ {
+ public static readonly FrequencyJsonConverter Default = new FrequencyJsonConverter(FrequencyUnit.Hertz);
+ public static readonly FrequencyJsonConverter Hertz = new FrequencyJsonConverter(FrequencyUnit.Hertz);
+ public static readonly FrequencyJsonConverter Millihertz = new FrequencyJsonConverter(FrequencyUnit.Millihertz);
+ public static readonly FrequencyJsonConverter Kilohertz = new FrequencyJsonConverter(FrequencyUnit.Kilohertz);
+ public static readonly FrequencyJsonConverter Megahertz = new FrequencyJsonConverter(FrequencyUnit.Megahertz);
+ public static readonly FrequencyJsonConverter Gigahertz = new FrequencyJsonConverter(FrequencyUnit.Gigahertz);
+
+ private readonly FrequencyUnit unit;
+
+ private FrequencyJsonConverter(FrequencyUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var frequency = (Frequency)value;
+ serializer.Serialize(writer, frequency.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Frequency);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Frequency.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/Gu.Units.Json.csproj b/Gu.Units.Json/Gu.Units.Json.csproj
new file mode 100644
index 00000000..e87e7e14
--- /dev/null
+++ b/Gu.Units.Json/Gu.Units.Json.csproj
@@ -0,0 +1,218 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {C794758C-AFE9-489E-9525-81DF7AC98193}
+ Library
+ Properties
+ Gu.Units.Json
+ Gu.Units.Json
+ v4.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+ QuantityJsonConverters.txt4
+
+
+
+
+ {f1f8b138-becc-4475-a7ab-b3019338bc7b}
+ Gu.Units
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\net35\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\net20\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\portable-net45+wp80+win8+wpa81+dnxcore50\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
+
+ ..\packages\Newtonsoft.Json\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll
+ True
+ True
+
+
+
+
+
\ No newline at end of file
diff --git a/Gu.Units.Json/InductanceJsonConverter.generated.cs b/Gu.Units.Json/InductanceJsonConverter.generated.cs
new file mode 100644
index 00000000..1bae52df
--- /dev/null
+++ b/Gu.Units.Json/InductanceJsonConverter.generated.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class InductanceJsonConverter : JsonConverter
+ {
+ public static readonly InductanceJsonConverter Default = new InductanceJsonConverter(InductanceUnit.Henrys);
+ public static readonly InductanceJsonConverter Henrys = new InductanceJsonConverter(InductanceUnit.Henrys);
+
+ private readonly InductanceUnit unit;
+
+ private InductanceJsonConverter(InductanceUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var inductance = (Inductance)value;
+ serializer.Serialize(writer, inductance.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Inductance);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Inductance.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/JerkJsonConverter.generated.cs b/Gu.Units.Json/JerkJsonConverter.generated.cs
new file mode 100644
index 00000000..2061084a
--- /dev/null
+++ b/Gu.Units.Json/JerkJsonConverter.generated.cs
@@ -0,0 +1,49 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class JerkJsonConverter : JsonConverter
+ {
+ public static readonly JerkJsonConverter Default = new JerkJsonConverter(JerkUnit.MetresPerSecondCubed);
+ public static readonly JerkJsonConverter MetresPerSecondCubed = new JerkJsonConverter(JerkUnit.MetresPerSecondCubed);
+ public static readonly JerkJsonConverter MillimetresPerSecondCubed = new JerkJsonConverter(JerkUnit.MillimetresPerSecondCubed);
+ public static readonly JerkJsonConverter MillimetresPerHourCubed = new JerkJsonConverter(JerkUnit.MillimetresPerHourCubed);
+ public static readonly JerkJsonConverter MillimetresPerMinuteCubed = new JerkJsonConverter(JerkUnit.MillimetresPerMinuteCubed);
+ public static readonly JerkJsonConverter MetresPerHourCubed = new JerkJsonConverter(JerkUnit.MetresPerHourCubed);
+ public static readonly JerkJsonConverter MetresPerMinuteCubed = new JerkJsonConverter(JerkUnit.MetresPerMinuteCubed);
+ public static readonly JerkJsonConverter NanometresPerHourCubed = new JerkJsonConverter(JerkUnit.NanometresPerHourCubed);
+ public static readonly JerkJsonConverter NanometresPerMinuteCubed = new JerkJsonConverter(JerkUnit.NanometresPerMinuteCubed);
+ public static readonly JerkJsonConverter CentimetresPerSecondCubed = new JerkJsonConverter(JerkUnit.CentimetresPerSecondCubed);
+ public static readonly JerkJsonConverter CentimetresPerHourCubed = new JerkJsonConverter(JerkUnit.CentimetresPerHourCubed);
+ public static readonly JerkJsonConverter CentimetresPerMinuteCubed = new JerkJsonConverter(JerkUnit.CentimetresPerMinuteCubed);
+
+ private readonly JerkUnit unit;
+
+ private JerkJsonConverter(JerkUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var jerk = (Jerk)value;
+ serializer.Serialize(writer, jerk.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Jerk);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Jerk.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/LengthJsonConverter.generated.cs b/Gu.Units.Json/LengthJsonConverter.generated.cs
new file mode 100644
index 00000000..debd2c15
--- /dev/null
+++ b/Gu.Units.Json/LengthJsonConverter.generated.cs
@@ -0,0 +1,49 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class LengthJsonConverter : JsonConverter
+ {
+ public static readonly LengthJsonConverter Default = new LengthJsonConverter(LengthUnit.Metres);
+ public static readonly LengthJsonConverter Metres = new LengthJsonConverter(LengthUnit.Metres);
+ public static readonly LengthJsonConverter Nanometres = new LengthJsonConverter(LengthUnit.Nanometres);
+ public static readonly LengthJsonConverter Micrometres = new LengthJsonConverter(LengthUnit.Micrometres);
+ public static readonly LengthJsonConverter Millimetres = new LengthJsonConverter(LengthUnit.Millimetres);
+ public static readonly LengthJsonConverter Centimetres = new LengthJsonConverter(LengthUnit.Centimetres);
+ public static readonly LengthJsonConverter Decimetres = new LengthJsonConverter(LengthUnit.Decimetres);
+ public static readonly LengthJsonConverter Kilometres = new LengthJsonConverter(LengthUnit.Kilometres);
+ public static readonly LengthJsonConverter Inches = new LengthJsonConverter(LengthUnit.Inches);
+ public static readonly LengthJsonConverter Mile = new LengthJsonConverter(LengthUnit.Mile);
+ public static readonly LengthJsonConverter Yard = new LengthJsonConverter(LengthUnit.Yard);
+ public static readonly LengthJsonConverter NauticalMile = new LengthJsonConverter(LengthUnit.NauticalMile);
+
+ private readonly LengthUnit unit;
+
+ private LengthJsonConverter(LengthUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var length = (Length)value;
+ serializer.Serialize(writer, length.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Length);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Length.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/LengthPerUnitlessJsonConverter.generated.cs b/Gu.Units.Json/LengthPerUnitlessJsonConverter.generated.cs
new file mode 100644
index 00000000..b79391b2
--- /dev/null
+++ b/Gu.Units.Json/LengthPerUnitlessJsonConverter.generated.cs
@@ -0,0 +1,43 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class LengthPerUnitlessJsonConverter : JsonConverter
+ {
+ public static readonly LengthPerUnitlessJsonConverter Default = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MetresPerUnitless);
+ public static readonly LengthPerUnitlessJsonConverter MetresPerUnitless = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MetresPerUnitless);
+ public static readonly LengthPerUnitlessJsonConverter MillimetresPerPercent = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MillimetresPerPercent);
+ public static readonly LengthPerUnitlessJsonConverter MicrometresPerPercent = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MicrometresPerPercent);
+ public static readonly LengthPerUnitlessJsonConverter NanometresPerPercent = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.NanometresPerPercent);
+ public static readonly LengthPerUnitlessJsonConverter MetresPerPercent = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MetresPerPercent);
+
+ private readonly LengthPerUnitlessUnit unit;
+
+ private LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var lengthPerUnitless = (LengthPerUnitless)value;
+ serializer.Serialize(writer, lengthPerUnitless.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(LengthPerUnitless);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return LengthPerUnitless.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/LuminousIntensityJsonConverter.generated.cs b/Gu.Units.Json/LuminousIntensityJsonConverter.generated.cs
new file mode 100644
index 00000000..79df1df8
--- /dev/null
+++ b/Gu.Units.Json/LuminousIntensityJsonConverter.generated.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class LuminousIntensityJsonConverter : JsonConverter
+ {
+ public static readonly LuminousIntensityJsonConverter Default = new LuminousIntensityJsonConverter(LuminousIntensityUnit.Candelas);
+ public static readonly LuminousIntensityJsonConverter Candelas = new LuminousIntensityJsonConverter(LuminousIntensityUnit.Candelas);
+
+ private readonly LuminousIntensityUnit unit;
+
+ private LuminousIntensityJsonConverter(LuminousIntensityUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var luminousIntensity = (LuminousIntensity)value;
+ serializer.Serialize(writer, luminousIntensity.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(LuminousIntensity);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return LuminousIntensity.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/MassJsonConverter.generated.cs b/Gu.Units.Json/MassJsonConverter.generated.cs
new file mode 100644
index 00000000..7cd73e0d
--- /dev/null
+++ b/Gu.Units.Json/MassJsonConverter.generated.cs
@@ -0,0 +1,42 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class MassJsonConverter : JsonConverter
+ {
+ public static readonly MassJsonConverter Default = new MassJsonConverter(MassUnit.Kilograms);
+ public static readonly MassJsonConverter Kilograms = new MassJsonConverter(MassUnit.Kilograms);
+ public static readonly MassJsonConverter Grams = new MassJsonConverter(MassUnit.Grams);
+ public static readonly MassJsonConverter Milligrams = new MassJsonConverter(MassUnit.Milligrams);
+ public static readonly MassJsonConverter Micrograms = new MassJsonConverter(MassUnit.Micrograms);
+
+ private readonly MassUnit unit;
+
+ private MassJsonConverter(MassUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var mass = (Mass)value;
+ serializer.Serialize(writer, mass.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Mass);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Mass.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/PowerJsonConverter.generated.cs b/Gu.Units.Json/PowerJsonConverter.generated.cs
new file mode 100644
index 00000000..1cf8c264
--- /dev/null
+++ b/Gu.Units.Json/PowerJsonConverter.generated.cs
@@ -0,0 +1,45 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class PowerJsonConverter : JsonConverter
+ {
+ public static readonly PowerJsonConverter Default = new PowerJsonConverter(PowerUnit.Watts);
+ public static readonly PowerJsonConverter Watts = new PowerJsonConverter(PowerUnit.Watts);
+ public static readonly PowerJsonConverter Nanowatts = new PowerJsonConverter(PowerUnit.Nanowatts);
+ public static readonly PowerJsonConverter Microwatts = new PowerJsonConverter(PowerUnit.Microwatts);
+ public static readonly PowerJsonConverter Milliwatts = new PowerJsonConverter(PowerUnit.Milliwatts);
+ public static readonly PowerJsonConverter Kilowatts = new PowerJsonConverter(PowerUnit.Kilowatts);
+ public static readonly PowerJsonConverter Megawatts = new PowerJsonConverter(PowerUnit.Megawatts);
+ public static readonly PowerJsonConverter Gigawatts = new PowerJsonConverter(PowerUnit.Gigawatts);
+
+ private readonly PowerUnit unit;
+
+ private PowerJsonConverter(PowerUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var power = (Power)value;
+ serializer.Serialize(writer, power.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Power);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Power.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/PressureJsonConverter.generated.cs b/Gu.Units.Json/PressureJsonConverter.generated.cs
new file mode 100644
index 00000000..dec35977
--- /dev/null
+++ b/Gu.Units.Json/PressureJsonConverter.generated.cs
@@ -0,0 +1,50 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class PressureJsonConverter : JsonConverter
+ {
+ public static readonly PressureJsonConverter Default = new PressureJsonConverter(PressureUnit.Pascals);
+ public static readonly PressureJsonConverter Pascals = new PressureJsonConverter(PressureUnit.Pascals);
+ public static readonly PressureJsonConverter Nanopascals = new PressureJsonConverter(PressureUnit.Nanopascals);
+ public static readonly PressureJsonConverter Micropascals = new PressureJsonConverter(PressureUnit.Micropascals);
+ public static readonly PressureJsonConverter Millipascals = new PressureJsonConverter(PressureUnit.Millipascals);
+ public static readonly PressureJsonConverter Kilopascals = new PressureJsonConverter(PressureUnit.Kilopascals);
+ public static readonly PressureJsonConverter Megapascals = new PressureJsonConverter(PressureUnit.Megapascals);
+ public static readonly PressureJsonConverter Gigapascals = new PressureJsonConverter(PressureUnit.Gigapascals);
+ public static readonly PressureJsonConverter Bars = new PressureJsonConverter(PressureUnit.Bars);
+ public static readonly PressureJsonConverter Millibars = new PressureJsonConverter(PressureUnit.Millibars);
+ public static readonly PressureJsonConverter NewtonsPerSquareMillimetre = new PressureJsonConverter(PressureUnit.NewtonsPerSquareMillimetre);
+ public static readonly PressureJsonConverter KilonewtonsPerSquareMillimetre = new PressureJsonConverter(PressureUnit.KilonewtonsPerSquareMillimetre);
+ public static readonly PressureJsonConverter NewtonsPerSquareMetre = new PressureJsonConverter(PressureUnit.NewtonsPerSquareMetre);
+
+ private readonly PressureUnit unit;
+
+ private PressureJsonConverter(PressureUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var pressure = (Pressure)value;
+ serializer.Serialize(writer, pressure.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Pressure);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Pressure.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/Properties/AssemblyInfo.cs b/Gu.Units.Json/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..81278408
--- /dev/null
+++ b/Gu.Units.Json/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Gu.Units.Json")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Gu.Units.Json")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c794758c-afe9-489e-9525-81df7ac98193")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Gu.Units/QuantityGenerator.txt4 b/Gu.Units.Json/QuantityJsonConverters.txt4
similarity index 100%
rename from Gu.Units/QuantityGenerator.txt4
rename to Gu.Units.Json/QuantityJsonConverters.txt4
diff --git a/Gu.Units.Json/ResistanceJsonConverter.generated.cs b/Gu.Units.Json/ResistanceJsonConverter.generated.cs
new file mode 100644
index 00000000..63847214
--- /dev/null
+++ b/Gu.Units.Json/ResistanceJsonConverter.generated.cs
@@ -0,0 +1,43 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class ResistanceJsonConverter : JsonConverter
+ {
+ public static readonly ResistanceJsonConverter Default = new ResistanceJsonConverter(ResistanceUnit.Ohm);
+ public static readonly ResistanceJsonConverter Ohm = new ResistanceJsonConverter(ResistanceUnit.Ohm);
+ public static readonly ResistanceJsonConverter Microohm = new ResistanceJsonConverter(ResistanceUnit.Microohm);
+ public static readonly ResistanceJsonConverter Milliohm = new ResistanceJsonConverter(ResistanceUnit.Milliohm);
+ public static readonly ResistanceJsonConverter Kiloohm = new ResistanceJsonConverter(ResistanceUnit.Kiloohm);
+ public static readonly ResistanceJsonConverter Megaohm = new ResistanceJsonConverter(ResistanceUnit.Megaohm);
+
+ private readonly ResistanceUnit unit;
+
+ private ResistanceJsonConverter(ResistanceUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var resistance = (Resistance)value;
+ serializer.Serialize(writer, resistance.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Resistance);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Resistance.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/SpecificEnergyJsonConverter.generated.cs b/Gu.Units.Json/SpecificEnergyJsonConverter.generated.cs
new file mode 100644
index 00000000..6d09ba05
--- /dev/null
+++ b/Gu.Units.Json/SpecificEnergyJsonConverter.generated.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class SpecificEnergyJsonConverter : JsonConverter
+ {
+ public static readonly SpecificEnergyJsonConverter Default = new SpecificEnergyJsonConverter(SpecificEnergyUnit.JoulesPerKilogram);
+ public static readonly SpecificEnergyJsonConverter JoulesPerKilogram = new SpecificEnergyJsonConverter(SpecificEnergyUnit.JoulesPerKilogram);
+
+ private readonly SpecificEnergyUnit unit;
+
+ private SpecificEnergyJsonConverter(SpecificEnergyUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var specificEnergy = (SpecificEnergy)value;
+ serializer.Serialize(writer, specificEnergy.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(SpecificEnergy);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return SpecificEnergy.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/SpeedJsonConverter.generated.cs b/Gu.Units.Json/SpeedJsonConverter.generated.cs
new file mode 100644
index 00000000..0d6f0c84
--- /dev/null
+++ b/Gu.Units.Json/SpeedJsonConverter.generated.cs
@@ -0,0 +1,48 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class SpeedJsonConverter : JsonConverter
+ {
+ public static readonly SpeedJsonConverter Default = new SpeedJsonConverter(SpeedUnit.MetresPerSecond);
+ public static readonly SpeedJsonConverter MetresPerSecond = new SpeedJsonConverter(SpeedUnit.MetresPerSecond);
+ public static readonly SpeedJsonConverter MillimetresPerSecond = new SpeedJsonConverter(SpeedUnit.MillimetresPerSecond);
+ public static readonly SpeedJsonConverter CentimetresPerSecond = new SpeedJsonConverter(SpeedUnit.CentimetresPerSecond);
+ public static readonly SpeedJsonConverter KilometresPerHour = new SpeedJsonConverter(SpeedUnit.KilometresPerHour);
+ public static readonly SpeedJsonConverter CentimetresPerMinute = new SpeedJsonConverter(SpeedUnit.CentimetresPerMinute);
+ public static readonly SpeedJsonConverter MetresPerMinute = new SpeedJsonConverter(SpeedUnit.MetresPerMinute);
+ public static readonly SpeedJsonConverter MetresPerHour = new SpeedJsonConverter(SpeedUnit.MetresPerHour);
+ public static readonly SpeedJsonConverter MillimetresPerHour = new SpeedJsonConverter(SpeedUnit.MillimetresPerHour);
+ public static readonly SpeedJsonConverter CentimetresPerHour = new SpeedJsonConverter(SpeedUnit.CentimetresPerHour);
+ public static readonly SpeedJsonConverter MillimetresPerMinute = new SpeedJsonConverter(SpeedUnit.MillimetresPerMinute);
+
+ private readonly SpeedUnit unit;
+
+ private SpeedJsonConverter(SpeedUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var speed = (Speed)value;
+ serializer.Serialize(writer, speed.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Speed);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Speed.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/StiffnessJsonConverter.generated.cs b/Gu.Units.Json/StiffnessJsonConverter.generated.cs
new file mode 100644
index 00000000..7691ce6c
--- /dev/null
+++ b/Gu.Units.Json/StiffnessJsonConverter.generated.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class StiffnessJsonConverter : JsonConverter
+ {
+ public static readonly StiffnessJsonConverter Default = new StiffnessJsonConverter(StiffnessUnit.NewtonsPerMetre);
+ public static readonly StiffnessJsonConverter NewtonsPerMetre = new StiffnessJsonConverter(StiffnessUnit.NewtonsPerMetre);
+
+ private readonly StiffnessUnit unit;
+
+ private StiffnessJsonConverter(StiffnessUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var stiffness = (Stiffness)value;
+ serializer.Serialize(writer, stiffness.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Stiffness);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Stiffness.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/TemperatureJsonConverter.generated.cs b/Gu.Units.Json/TemperatureJsonConverter.generated.cs
new file mode 100644
index 00000000..fc44c4b3
--- /dev/null
+++ b/Gu.Units.Json/TemperatureJsonConverter.generated.cs
@@ -0,0 +1,41 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class TemperatureJsonConverter : JsonConverter
+ {
+ public static readonly TemperatureJsonConverter Default = new TemperatureJsonConverter(TemperatureUnit.Kelvin);
+ public static readonly TemperatureJsonConverter Kelvin = new TemperatureJsonConverter(TemperatureUnit.Kelvin);
+ public static readonly TemperatureJsonConverter Celsius = new TemperatureJsonConverter(TemperatureUnit.Celsius);
+ public static readonly TemperatureJsonConverter Fahrenheit = new TemperatureJsonConverter(TemperatureUnit.Fahrenheit);
+
+ private readonly TemperatureUnit unit;
+
+ private TemperatureJsonConverter(TemperatureUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var temperature = (Temperature)value;
+ serializer.Serialize(writer, temperature.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Temperature);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Temperature.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/TimeJsonConverter.generated.cs b/Gu.Units.Json/TimeJsonConverter.generated.cs
new file mode 100644
index 00000000..bda6f9a7
--- /dev/null
+++ b/Gu.Units.Json/TimeJsonConverter.generated.cs
@@ -0,0 +1,44 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class TimeJsonConverter : JsonConverter
+ {
+ public static readonly TimeJsonConverter Default = new TimeJsonConverter(TimeUnit.Seconds);
+ public static readonly TimeJsonConverter Seconds = new TimeJsonConverter(TimeUnit.Seconds);
+ public static readonly TimeJsonConverter Nanoseconds = new TimeJsonConverter(TimeUnit.Nanoseconds);
+ public static readonly TimeJsonConverter Microseconds = new TimeJsonConverter(TimeUnit.Microseconds);
+ public static readonly TimeJsonConverter Milliseconds = new TimeJsonConverter(TimeUnit.Milliseconds);
+ public static readonly TimeJsonConverter Hours = new TimeJsonConverter(TimeUnit.Hours);
+ public static readonly TimeJsonConverter Minutes = new TimeJsonConverter(TimeUnit.Minutes);
+
+ private readonly TimeUnit unit;
+
+ private TimeJsonConverter(TimeUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var time = (Time)value;
+ serializer.Serialize(writer, time.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Time);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Time.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/TorqueJsonConverter.generated.cs b/Gu.Units.Json/TorqueJsonConverter.generated.cs
new file mode 100644
index 00000000..d6304c28
--- /dev/null
+++ b/Gu.Units.Json/TorqueJsonConverter.generated.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class TorqueJsonConverter : JsonConverter
+ {
+ public static readonly TorqueJsonConverter Default = new TorqueJsonConverter(TorqueUnit.NewtonMetres);
+ public static readonly TorqueJsonConverter NewtonMetres = new TorqueJsonConverter(TorqueUnit.NewtonMetres);
+
+ private readonly TorqueUnit unit;
+
+ private TorqueJsonConverter(TorqueUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var torque = (Torque)value;
+ serializer.Serialize(writer, torque.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Torque);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Torque.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/UnitlessJsonConverter.generated.cs b/Gu.Units.Json/UnitlessJsonConverter.generated.cs
new file mode 100644
index 00000000..b47aaa2e
--- /dev/null
+++ b/Gu.Units.Json/UnitlessJsonConverter.generated.cs
@@ -0,0 +1,42 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class UnitlessJsonConverter : JsonConverter
+ {
+ public static readonly UnitlessJsonConverter Default = new UnitlessJsonConverter(UnitlessUnit.Scalar);
+ public static readonly UnitlessJsonConverter Scalar = new UnitlessJsonConverter(UnitlessUnit.Scalar);
+ public static readonly UnitlessJsonConverter PartsPerMillion = new UnitlessJsonConverter(UnitlessUnit.PartsPerMillion);
+ public static readonly UnitlessJsonConverter Promilles = new UnitlessJsonConverter(UnitlessUnit.Promilles);
+ public static readonly UnitlessJsonConverter Percents = new UnitlessJsonConverter(UnitlessUnit.Percents);
+
+ private readonly UnitlessUnit unit;
+
+ private UnitlessJsonConverter(UnitlessUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var unitless = (Unitless)value;
+ serializer.Serialize(writer, unitless.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Unitless);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Unitless.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/VoltageJsonConverter.generated.cs b/Gu.Units.Json/VoltageJsonConverter.generated.cs
new file mode 100644
index 00000000..7c2f2192
--- /dev/null
+++ b/Gu.Units.Json/VoltageJsonConverter.generated.cs
@@ -0,0 +1,43 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class VoltageJsonConverter : JsonConverter
+ {
+ public static readonly VoltageJsonConverter Default = new VoltageJsonConverter(VoltageUnit.Volts);
+ public static readonly VoltageJsonConverter Volts = new VoltageJsonConverter(VoltageUnit.Volts);
+ public static readonly VoltageJsonConverter Millivolts = new VoltageJsonConverter(VoltageUnit.Millivolts);
+ public static readonly VoltageJsonConverter Kilovolts = new VoltageJsonConverter(VoltageUnit.Kilovolts);
+ public static readonly VoltageJsonConverter Megavolts = new VoltageJsonConverter(VoltageUnit.Megavolts);
+ public static readonly VoltageJsonConverter Microvolts = new VoltageJsonConverter(VoltageUnit.Microvolts);
+
+ private readonly VoltageUnit unit;
+
+ private VoltageJsonConverter(VoltageUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var voltage = (Voltage)value;
+ serializer.Serialize(writer, voltage.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Voltage);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Voltage.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/VolumeJsonConverter.generated.cs b/Gu.Units.Json/VolumeJsonConverter.generated.cs
new file mode 100644
index 00000000..7e58add0
--- /dev/null
+++ b/Gu.Units.Json/VolumeJsonConverter.generated.cs
@@ -0,0 +1,43 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class VolumeJsonConverter : JsonConverter
+ {
+ public static readonly VolumeJsonConverter Default = new VolumeJsonConverter(VolumeUnit.CubicMetres);
+ public static readonly VolumeJsonConverter CubicMetres = new VolumeJsonConverter(VolumeUnit.CubicMetres);
+ public static readonly VolumeJsonConverter Litres = new VolumeJsonConverter(VolumeUnit.Litres);
+ public static readonly VolumeJsonConverter CubicCentimetres = new VolumeJsonConverter(VolumeUnit.CubicCentimetres);
+ public static readonly VolumeJsonConverter CubicMillimetres = new VolumeJsonConverter(VolumeUnit.CubicMillimetres);
+ public static readonly VolumeJsonConverter CubicInches = new VolumeJsonConverter(VolumeUnit.CubicInches);
+
+ private readonly VolumeUnit unit;
+
+ private VolumeJsonConverter(VolumeUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var volume = (Volume)value;
+ serializer.Serialize(writer, volume.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(Volume);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return Volume.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/VolumetricFlowJsonConverter.generated.cs b/Gu.Units.Json/VolumetricFlowJsonConverter.generated.cs
new file mode 100644
index 00000000..0323e748
--- /dev/null
+++ b/Gu.Units.Json/VolumetricFlowJsonConverter.generated.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Json
+{
+ using System;
+ using Newtonsoft.Json;
+
+ ///
+ /// for the quantity .
+ ///
+ [Serializable]
+ public class VolumetricFlowJsonConverter : JsonConverter
+ {
+ public static readonly VolumetricFlowJsonConverter Default = new VolumetricFlowJsonConverter(VolumetricFlowUnit.CubicMetresPerSecond);
+ public static readonly VolumetricFlowJsonConverter CubicMetresPerSecond = new VolumetricFlowJsonConverter(VolumetricFlowUnit.CubicMetresPerSecond);
+
+ private readonly VolumetricFlowUnit unit;
+
+ private VolumetricFlowJsonConverter(VolumetricFlowUnit unit)
+ {
+ this.unit = unit;
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var volumetricFlow = (VolumetricFlow)value;
+ serializer.Serialize(writer, volumetricFlow.ToString(this.unit, writer.Culture));
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(VolumetricFlow);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var stringValue = reader.Value as string;
+ return VolumetricFlow.Parse(stringValue, reader.Culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Json/paket.references b/Gu.Units.Json/paket.references
new file mode 100644
index 00000000..1063d003
--- /dev/null
+++ b/Gu.Units.Json/paket.references
@@ -0,0 +1 @@
+Newtonsoft.Json
\ No newline at end of file
diff --git a/Gu.Units.Json/paket.template b/Gu.Units.Json/paket.template
new file mode 100644
index 00000000..e9ef7135
--- /dev/null
+++ b/Gu.Units.Json/paket.template
@@ -0,0 +1,5 @@
+type project
+title Gu.Units.Json
+tags unit units quantity quantities unitsofmeasure
+projectUrl https://github.com/JohanLarsson/Gu.Units
+licenseUrl http://opensource.org/licenses/MIT
diff --git a/Gu.Units.Tests/BenchMarks/FormattingBenchmarks.cs b/Gu.Units.Tests/BenchMarks/FormattingBenchmarks.cs
new file mode 100644
index 00000000..e335202c
--- /dev/null
+++ b/Gu.Units.Tests/BenchMarks/FormattingBenchmarks.cs
@@ -0,0 +1,69 @@
+namespace Gu.Units.Tests
+{
+ using System;
+ using System.Diagnostics;
+ using Internals.Parsing;
+ using NUnit.Framework;
+
+ // run benchmarks in release build
+ [Explicit(Benchmarks.LongRunning)]
+ public class FormattingBenchmarks
+ {
+ // 2015-11-28| speed.ToString("F1 m/s") 1 000 000 times took: 995 ms
+ // 2015-11-28| $"{ speed.metresPerSecond:F1} {SpeedUnit.MetresPerSecond}" 1 000 000 times took: 742 ms
+ [Test]
+ public void Benchmark()
+ {
+ // this is not a fair comparison as .ToString parses the arguments. Still interesting as a base line.
+ var speed = Speed.FromMetresPerSecond(1.2);
+ var toString = speed.ToString("F1 m/s");
+ toString = speed.ToString("F0", "s⁻¹⋅mm");
+ toString = speed.ToString("F2 m⋅s⁻¹");
+ var string_Format = $"{speed.metresPerSecond:F1} {SpeedUnit.MetresPerSecond}";
+ // end warmup
+
+ var sw = Stopwatch.StartNew();
+ var n = 1000000;
+ for (int i = 0; i < n / 3; i++)
+ {
+ toString = speed.ToString("F1 m/s");
+ toString = speed.ToString("F0", "s⁻¹⋅mm");
+ toString = speed.ToString("F2 m⋅s⁻¹");
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| speed.ToString(\"F1 m/s\") {n:N0} times took: {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ for (int i = 0; i < n / 3; i++)
+ {
+ string_Format = $"{speed.metresPerSecond:F1} {SpeedUnit.MetresPerSecond}";
+ string_Format = $"{speed.metresPerSecond:F0} s⁻¹⋅mm";
+ string_Format = $"{speed.metresPerSecond:F2} m⋅s⁻¹";
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| $\"{{ speed.metresPerSecond:F1}} {{SpeedUnit.MetresPerSecond}}\" {n:N0} times took: {sw.ElapsedMilliseconds} ms");
+ }
+
+ [Test]
+ public void ForProfiling()
+ {
+ // this is not a fair comparison as .ToString parses the arguments. Still interesting as a base line.
+ var speed = Speed.FromMetresPerSecond(1.2);
+ var toString = speed.ToString("F1 m/s");
+ toString = speed.ToString("F0", "s⁻¹⋅mm");
+ toString = speed.ToString("F2 m⋅s⁻¹");
+ var string_Format = $"{speed.metresPerSecond:F1} {SpeedUnit.MetresPerSecond}";
+ // end warmup
+
+ var n = 1000000;
+ for (int i = 0; i < n/3; i++)
+ {
+ toString = speed.ToString("F1 m/s");
+ toString = speed.ToString("F0", "s⁻¹⋅mm");
+ toString = speed.ToString("F2 m⋅s⁻¹");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/BenchMarks/ParsingBenchMarks.cs b/Gu.Units.Tests/BenchMarks/ParsingBenchMarks.cs
new file mode 100644
index 00000000..adef490b
--- /dev/null
+++ b/Gu.Units.Tests/BenchMarks/ParsingBenchMarks.cs
@@ -0,0 +1,277 @@
+namespace Gu.Units.Tests
+{
+ using System;
+ using System.Collections.Concurrent;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Globalization;
+ using System.Linq;
+ using Internals.Parsing;
+ using NUnit.Framework;
+ using Units.Internals;
+
+ // run benchmarks in release build
+ [Explicit(Benchmarks.LongRunning)]
+ public class ParsingBenchmarks
+ {
+ // 2015-11-28| TryRead("#0.00#") 1 000 000 times with regex took: 355 ms
+ // 2015-11-28| TryRead("e5") 1 000 000 times with optimized took: 50 ms <- don't remember what was different.
+ // 2015-11-28| TryRead("e5") 1 000 000 times took: 118 ms
+ // 2015-11-28| TryRead("#0.00#") 1 000 000 times took: 95 ms
+ // 2015-11-28| TryRead("e5") 1 000 000 times took: 45 ms
+ // 2015-11-28| TryRead("#0.00#") 1 000 000 times took: 22 ms
+ [Test]
+ public void TryReadDoubleFormatSubstring()
+ {
+ int pos = 0;
+ PaddedFormat actual = DoubleFormatCache.GetOrCreate("e5", ref pos);
+ pos = 0;
+ actual = DoubleFormatCache.GetOrCreate("#0.00#", ref pos);
+ var sw = Stopwatch.StartNew();
+ var n = 1000000;
+ for (int i = 0; i < n; i++)
+ {
+ pos = 0;
+ DoubleFormatCache.GetOrCreate("#0.00#", ref pos);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| TryReadDoubleFormat(\"e5\") {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ for (int i = 0; i < n; i++)
+ {
+ pos = 0;
+ actual = DoubleFormatCache.GetOrCreate("#0.00#", ref pos);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| TryReadDoubleFormat(\"#0.00#\") {n:N0} times {sw.ElapsedMilliseconds} ms");
+ }
+
+ // 2015-12-02| TryRead("e5") 1 000 000 times 49 ms
+ // 2015-12-02| TryRead("#0.00#") 1 000 000 times 46 ms
+ [Test]
+ public void TryReadDoubleFormat()
+ {
+ var paddedFormat = DoubleFormatCache.GetOrCreate("e5");
+ paddedFormat = DoubleFormatCache.GetOrCreate("F5");
+ paddedFormat = DoubleFormatCache.GetOrCreate("#0.00#");
+ paddedFormat = DoubleFormatCache.GetOrCreate("#0.0#");
+ var sw = Stopwatch.StartNew();
+ var n = 1000000;
+ for (int i = 0; i < n; i++)
+ {
+ paddedFormat = DoubleFormatCache.GetOrCreate("e5");
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| TryRead(\"e5\") {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ for (int i = 0; i < n; i++)
+ {
+ paddedFormat = DoubleFormatCache.GetOrCreate("#0.00#");
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| TryRead(\"#0.00#\") {n:N0} times {sw.ElapsedMilliseconds} ms");
+ }
+
+ // 2015-12-02| DoubleReader.TryRead(" 123.45", 4, ...) 1 000 000 times 217 ms
+ // 2015-12-02| double.TryParse(substring, ...) 1 000 000 times 141 ms
+ // 2015-12-02| double.TryParse("123.45", ...) 1 000 000 times 124 ms
+ [Test]
+ public void TryReadDouble()
+ {
+ int pos = 4;
+ double actual;
+ const string text = "ab 123.45";
+ DoubleReader.TryRead(text, ref pos, NumberStyles.Float, CultureInfo.InvariantCulture, out actual);
+ var sw = Stopwatch.StartNew();
+ var n = 1000000;
+ for (int i = 0; i < n; i++)
+ {
+ pos = 4;
+ DoubleReader.TryRead(text, ref pos, NumberStyles.Float, CultureInfo.InvariantCulture, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| DoubleReader.TryRead(\" 123.45\", 4, ...) {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ string substring;
+ for (int i = 0; i < n; i++)
+ {
+ substring = text.Substring(4);
+ double.TryParse(substring, NumberStyles.Float, CultureInfo.InvariantCulture, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| double.TryParse(substring, ...) {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ substring = text.Substring(4);
+
+ for (int i = 0; i < n; i++)
+ {
+ double.TryParse(substring, NumberStyles.Float, CultureInfo.InvariantCulture, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| double.TryParse(\"{substring}\", ...) {n:N0} times {sw.ElapsedMilliseconds} ms");
+ }
+
+ // 2015-12-02| IntReader.TryReadInt32("ab 12345", 4, ...) 1 000 000 times 19 ms
+ // 2015-12-02| int.TryParse(substring: "12345", ...) 1 000 000 times 140 ms
+ // 2015-12-02| int.TryParse("12345", ...) 1 000 000 times 121 ms
+ [Test]
+ public void TryReadInt()
+ {
+ int pos = 4;
+ int actual;
+ const string text = "ab 12345";
+ IntReader.TryReadInt32(text, ref pos, out actual);
+ var sw = Stopwatch.StartNew();
+ var n = 1000000;
+ for (int i = 0; i < n; i++)
+ {
+ pos = 4;
+ IntReader.TryReadInt32(text, ref pos, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| IntReader.TryReadInt32(\"{text}\", 4, ...) {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ string substring = null;
+ for (int i = 0; i < n; i++)
+ {
+ substring = text.Substring(4);
+ int.TryParse(substring, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| int.TryParse(substring: \"{substring}\", ...) {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ for (int i = 0; i < n; i++)
+ {
+ int.TryParse(substring, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| int.TryParse(\"{substring}\", ...) {n:N0} times {sw.ElapsedMilliseconds} ms");
+ }
+
+ // 2015-11-28| IntReader.TryReadInt32(" 12", 4, ...) 1 000 000 times took: 40 ms
+ // 2015-11-28| int.TryParse(substring, ...) 1 000 000 times took: 134 ms
+ [Test]
+ public void TryGetValue()
+ {
+ string actual;
+ var dict = new Dictionary();
+ for (int i = 0; i < 10; i++)
+ {
+ dict[i] = i.ToString();
+ }
+ dict.TryGetValue(2, out actual);
+ var sw = Stopwatch.StartNew();
+ var n = 1000000;
+ for (int i = 0; i < n; i++)
+ {
+ dict.TryGetValue(i % 10, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| dict.TryGetValue(i % 10, out actual) {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ var cdict = new ConcurrentDictionary();
+ for (int i = 0; i < 10; i++)
+ {
+ cdict[i] = i.ToString();
+ }
+
+ cdict.TryGetValue(2, out actual);
+ sw.Restart();
+ for (int i = 0; i < n; i++)
+ {
+ cdict.TryGetValue(i % 10, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| cdict.TryGetValue(i % 10, out actual); {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ var slist = new SortedList();
+ for (int i = 0; i < 10; i++)
+ {
+ slist[i] = i.ToString();
+ }
+
+ slist.TryGetValue(2, out actual);
+ sw.Restart();
+ for (int i = 0; i < n; i++)
+ {
+ slist.TryGetValue(i % 10, out actual);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| slist.TryGetValue(i % 10, out actual); {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ var array = new KeyValuePair[10];
+ for (int i = 0; i < 10; i++)
+ {
+ array[i] = new KeyValuePair(i, i.ToString());
+ }
+
+ var kvp = Array.Find(array, x => x.Key == 2);
+ sw.Restart();
+ for (int i = 0; i < n; i++)
+ {
+ kvp = Array.Find(array, x => x.Key == i % 10);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Array.Find(array, x => x.Key == i % 10) {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ var kvpComparer = new KvpComparer();
+
+ for (int i = 0; i < n; i++)
+ {
+ var j = Array.BinarySearch(array, array[i % 10], kvpComparer);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Array.BinarySearch(array, array[i % 10]) {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+
+ for (int i = 0; i < n; i++)
+ {
+ var j = array.BinarySearchBy(x => x.Key, i % 10);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| array.BinarySearchBy(x => x.Key, i % 10 {n:N0} times {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+
+ for (int i = 0; i < n; i++)
+ {
+ kvp = array.First(x => x.Key == i % 10);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| array.First(x => x.Key == i % 10) {n:N0} times {sw.ElapsedMilliseconds} ms");
+ }
+
+ private class KvpComparer : IComparer>
+ {
+ public int Compare(KeyValuePair x, KeyValuePair y)
+ {
+ return x.Key.CompareTo(y.Key);
+ }
+ }
+ }
+}
diff --git a/Gu.Units.Tests/BenchMarks/QuantityBenchmark.cs b/Gu.Units.Tests/BenchMarks/QuantityBenchmark.cs
new file mode 100644
index 00000000..c46d62f3
--- /dev/null
+++ b/Gu.Units.Tests/BenchMarks/QuantityBenchmark.cs
@@ -0,0 +1,67 @@
+namespace Gu.Units.Tests
+{
+ using System;
+ using System.Diagnostics;
+ using Internals.Parsing;
+ using NUnit.Framework;
+
+ // run benchmarks in release build
+ [Explicit(Benchmarks.LongRunning)]
+ public class QuantityBenchmark
+ {
+ private const int n = 1000000;
+
+ // Summing 1E+007 Doubles took: 11 ms
+ // Summing 1E+007 Lengths took: 8 ms
+ // 2015-11-28| Summing 1 000 000 doubles took: 1 ms
+ // 2015-11-28| Summing 1 000 000 Lengths took: 6 ms
+ [Test]
+ public void CompareLengthAndDouble()
+ {
+ var sw = Stopwatch.StartNew();
+ double sum1 = 0;
+ for (double i = 0; i < n; i++)
+ {
+ sum1 += i;
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Summing {n:N0} doubles took: {sw.ElapsedMilliseconds} ms");
+ sw.Restart();
+ var sum2 = Length.FromMetres(1);
+ for (var i = 0; i < n; i++)
+ {
+ sum2 += Length.FromMetres(i);
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Summing {n:N0} Lengths took: {sw.ElapsedMilliseconds} ms");
+ }
+
+ // Summing 1E+007 Int32s took: 5 ms
+ // Summing 1E+007 Lengths took: 8 ms
+ // 2015-12-02| Summing 1 000 000 ints took: 0 ms
+ // 2015-12-02| Summing 1 000 000 Lengths took: 6 ms
+ [Test]
+ public void CompareLengthAndInt()
+ {
+ var sw = Stopwatch.StartNew();
+ int sum1 = 0;
+ for (int i = 0; i < n; i++)
+ {
+ sum1 += i;
+ }
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Summing {n:N0} ints took: {sw.ElapsedMilliseconds} ms");
+ sw.Restart();
+ var sum2 = Length.FromMetres(1);
+ for (var i = 0; i < n; i++)
+ {
+ sum2 += Length.FromMetres(i);
+ }
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Summing {n:N0} Lengths took: {sw.ElapsedMilliseconds} ms");
+
+ }
+ }
+}
diff --git a/Gu.Units.Tests/BenchMarks/SubstringCacheBenchmarks.cs b/Gu.Units.Tests/BenchMarks/SubstringCacheBenchmarks.cs
new file mode 100644
index 00000000..61a980a9
--- /dev/null
+++ b/Gu.Units.Tests/BenchMarks/SubstringCacheBenchmarks.cs
@@ -0,0 +1,82 @@
+namespace Gu.Units.Tests
+{
+ using System;
+ using System.Diagnostics;
+ using Internals.Parsing;
+ using NUnit.Framework;
+
+ // run benchmarks in release build
+ [Explicit(Benchmarks.LongRunning)]
+ public class SubstringCacheBenchmarks
+ {
+ // 2015-12-02| cache.TryFind(text, 4, out cached) 1 000 000 times with cache took: 29 ms
+ // 2015-12-02| text.Substring(3, 4) 1 000 000 times took: 16 ms
+ [Test]
+ public void TryFindSubStringWorstCase()
+ {
+ // this is not a really meaningful comparison as Find does much more work
+ // finding the end while substring gets the end for free.
+ // interesting as a base line.
+ var cache = new StringMap();
+ cache.Add("abc0", "d");
+ cache.Add("abc01", "e");
+ cache.Add("abc1", "e");
+ cache.Add("abc11", "e");
+ cache.Add("abc2", "f");
+ cache.Add("abc21", "f");
+
+ const string text0 = " abc0 ";
+ const string text1 = " abc1 ";
+ const string text2 = " abc2 ";
+ string cached;
+ cache.TryGetBySubString(text0, 4, out cached);
+
+ var subString = text0.Substring(3, 4);
+
+ var sw = Stopwatch.StartNew();
+ var n = 1000000;
+ for (int i = 0; i < n; i++)
+ {
+ switch (i % 3)
+ {
+ case 0:
+ cache.TryGetBySubString(text0, 4, out cached);
+ continue;
+ case 1:
+ cache.TryGetBySubString(text1, 4, out cached);
+ continue;
+ case 2:
+ cache.TryGetBySubString(text2, 4, out cached);
+ continue;
+ default:
+ throw new Exception();
+ }
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| cache.TryFind(text, 4, out cached) {n:N0} times with cache took: {sw.ElapsedMilliseconds} ms");
+
+ sw.Restart();
+ for (int i = 0; i < n; i++)
+ {
+ switch (i % 3)
+ {
+ case 0:
+ subString = text0.Substring(3, 4);
+ continue;
+ case 1:
+ subString = text1.Substring(3, 4);
+ continue;
+ case 2:
+ subString = text2.Substring(3, 4);
+ continue;
+ default:
+ throw new Exception();
+ }
+ }
+
+ sw.Stop();
+ Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| text.Substring(3, 4) {n:N0} times took: {sw.ElapsedMilliseconds} ms");
+ }
+ }
+}
diff --git a/Gu.Units.Tests/Constants/Benchmarks.cs b/Gu.Units.Tests/Constants/Benchmarks.cs
new file mode 100644
index 00000000..1ab64a38
--- /dev/null
+++ b/Gu.Units.Tests/Constants/Benchmarks.cs
@@ -0,0 +1,7 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ public static class Benchmarks
+ {
+ public const string LongRunning = "Long running benchmark";
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Constants/Reminder.cs b/Gu.Units.Tests/Constants/Reminder.cs
new file mode 100644
index 00000000..530d2162
--- /dev/null
+++ b/Gu.Units.Tests/Constants/Reminder.cs
@@ -0,0 +1,8 @@
+namespace Gu.Units.Tests
+{
+ public class Reminder
+ {
+ public const string ToDo = "Fix so this test passes!";
+ public const string CandidateForRemoval = "Maybe remove these";
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/DoubleParserTests.cs b/Gu.Units.Tests/DoubleParserTests.cs
deleted file mode 100644
index 33814c8e..00000000
--- a/Gu.Units.Tests/DoubleParserTests.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-namespace Gu.Units.Tests
-{
- using System;
- using System.Collections.Generic;
- using System.Globalization;
-
- using NUnit.Framework;
-
- public class DoubleParserTests
- {
- private static readonly string[] Formats = { "abc{0}def", "abcd{0}ef" };
-
- [TestCaseSource(typeof(DoubleParseHappyPathSource))]
- public void Parse(DoubleParseData data)
- {
- var culture = data.Culture;
- var style = data.Styles;
- var s = data.Text;
- foreach (var format in Formats)
- {
- var ns = string.Format(format, s);
- var start = format.IndexOf('{');
- int end;
- double expected = double.Parse(s, style, culture);
- var actual = DoubleParser.Parse(ns, start, style, culture, out end);
- Assert.AreEqual(expected, actual);
- var expectedEnd = start + s.Length;
- Assert.AreEqual(expectedEnd, end);
- }
- }
-
- [TestCaseSource(typeof(DoubleParseErrorSource))]
- public void ParseError(DoubleParseData data)
- {
- var culture = data.Culture;
- var style = data.Styles;
- var s = data.Text;
- foreach (var format in Formats)
- {
- var ns = string.Format(format, s);
- var start = format.IndexOf('{');
- int end = -1;
- Assert.Throws(() => double.Parse(s, style, culture));
- Assert.Throws(() => DoubleParser.Parse(ns, start, style, culture, out end));
- //Assert.AreEqual(start, end);
- }
- }
-
- [TestCaseSource(typeof(DoubleParseHappyPathSource))]
- public void TryParse(DoubleParseData data)
- {
- var culture = data.Culture;
- var style = data.Styles;
- var s = data.Text;
- foreach (var format in Formats)
- {
- var ns = string.Format(format, s);
- var start = format.IndexOf('{');
- int end;
- double expected;
- Assert.IsTrue(double.TryParse(s, style, culture, out expected));
- double actual;
- Assert.IsTrue(DoubleParser.TryParse(ns, start, style, culture, out actual, out end));
- Assert.AreEqual(expected, actual);
- var expectedEnd = start + s.Length;
- Assert.AreEqual(expectedEnd, end);
- }
- }
-
- [TestCaseSource(typeof(DoubleParseErrorSource))]
- public void TryParseError(DoubleParseData data)
- {
- var culture = data.Culture;
- var style = data.Styles;
- var s = data.Text;
- foreach (var format in Formats)
- {
- var ns = string.Format(format, s);
- var start = format.IndexOf('{');
- int end;
- double expected;
- Assert.IsFalse(double.TryParse(s, style, culture, out expected));
- double actual;
- Assert.IsFalse(DoubleParser.TryParse(ns, start, style, culture, out actual, out end));
- Assert.AreEqual(expected, actual);
- //Assert.AreEqual(start, end);
- }
- }
-
- public class DoubleParseHappyPathSource : List
- {
- public DoubleParseHappyPathSource()
- {
- var en = CultureInfo.GetCultureInfo("en-US");
- var sv = CultureInfo.GetCultureInfo("sv-SE");
- Add("1", NumberStyles.Float, en);
- Add(" 1", NumberStyles.Float, en);
- Add("-1", NumberStyles.Float, en);
- Add("+1", NumberStyles.Float, en);
- Add(".1", NumberStyles.Float, en);
- Add("1.", NumberStyles.Float, en);
- Add("+1.2", NumberStyles.Float, en);
- Add("+1,2", NumberStyles.Float, sv);
- Add("+1.2e3", NumberStyles.Float, en);
- Add("+1.2E3", NumberStyles.Float, en);
- Add("+1.2e-3", NumberStyles.Float, en);
- Add("+1.2E-3", NumberStyles.Float, en);
- Add("+1.2e+3", NumberStyles.Float, en);
- Add(sv.NumberFormat.NaNSymbol, NumberStyles.Float, sv);
- Add(sv.NumberFormat.PositiveInfinitySymbol, NumberStyles.Float, sv);
- Add(sv.NumberFormat.NegativeInfinitySymbol, NumberStyles.Float, sv);
- }
-
- public void Add(string text, NumberStyles styles, CultureInfo culture)
- {
- Add(new DoubleParseData(text, styles, culture));
- }
- }
-
- public class DoubleParseErrorSource : List
- {
- public DoubleParseErrorSource()
- {
- var en = CultureInfo.GetCultureInfo("en-US");
- var sv = CultureInfo.GetCultureInfo("sv-SE");
- Add("e1", NumberStyles.Float, en);
- Add(" 1", NumberStyles.None, en);
- Add("-1", NumberStyles.None, en);
- Add(".1", NumberStyles.None, en);
- //Add("1.", NumberStyles.Float | NumberStyles.AllowHexSpecifier, en);
- Add(".", NumberStyles.Float, en);
- //Add("+1,2", NumberStyles.Float, en);
- //Add("+1.2", NumberStyles.Float, sv);
- Add("+1.2e3", NumberStyles.None|NumberStyles.AllowDecimalPoint|NumberStyles.AllowLeadingSign, en);
- }
-
- public void Add(string text, NumberStyles styles, CultureInfo culture)
- {
- Add(new DoubleParseData(text, styles, culture));
- }
- }
-
- public class DoubleParseData
- {
- public readonly string Text;
- public readonly NumberStyles Styles;
- public readonly CultureInfo Culture;
-
- public DoubleParseData(string text, NumberStyles styles, CultureInfo culture)
- {
- Text = text;
- Styles = styles;
- Culture = culture;
- }
-
- public override string ToString()
- {
- return string.Format("Text: {0}, Styles: {1}, Culture: {2}", Text, Styles, Culture);
- }
- }
- }
-}
diff --git a/Gu.Units.Tests/FormatTests.cs b/Gu.Units.Tests/FormatTests.cs
new file mode 100644
index 00000000..f120e61c
--- /dev/null
+++ b/Gu.Units.Tests/FormatTests.cs
@@ -0,0 +1,121 @@
+namespace Gu.Units.Tests
+{
+ using System.Globalization;
+ using System.Threading;
+ using Internals.Parsing;
+ using NUnit.Framework;
+
+ public class FormatTests
+ {
+ private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹";
+ private const char MultiplyDot = '⋅';
+ const string UnknownFormat = "unknown format";
+
+ [Test]
+ public void FormatAngle()
+ {
+ var angle = Angle.FromDegrees(1.2);
+ using (Thread.CurrentThread.UsingTempCulture(CultureInfo.InvariantCulture))
+ {
+ Assert.AreEqual("0.020943951023932\u00A0rad", angle.ToString());
+ Assert.AreEqual("1.200°", angle.ToString("F3°"));
+ Assert.AreEqual("1.2°", angle.ToString(AngleUnit.Degrees));
+ Assert.AreEqual(" 1.2 ° ", angle.ToString(" F1 ° "));
+ Assert.AreEqual(" 0.02 rad ", angle.ToString(" F2 rad "));
+ Assert.AreEqual("1.200°", angle.ToString("F3", AngleUnit.Degrees));
+ Assert.AreEqual("0.02\u00A0rad", angle.ToString("F2", AngleUnit.Radians));
+ }
+
+ var sv = CultureInfo.GetCultureInfo("sv-SE");
+ Assert.AreEqual("0,020943951023932\u00A0rad", angle.ToString(sv));
+ Assert.AreEqual("1,200°", angle.ToString("F3°", sv));
+ Assert.AreEqual("1,2°", angle.ToString(AngleUnit.Degrees, sv));
+ Assert.AreEqual(" 1,2 ° ", angle.ToString(" F1 ° ", sv));
+ Assert.AreEqual(" 0,02 rad", angle.ToString(" F2 rad", sv));
+ Assert.AreEqual("1,200°", angle.ToString("F3", AngleUnit.Degrees, sv));
+ Assert.AreEqual("0,02\u00A0rad", angle.ToString("F2", AngleUnit.Radians, sv));
+ }
+
+ [Test]
+ public void FormatSpeed()
+ {
+ var speed = Speed.FromMetresPerSecond(1.2);
+ using (Thread.CurrentThread.UsingTempCulture(CultureInfo.InvariantCulture))
+ {
+ Assert.AreEqual("1.2\u00A0m/s", speed.ToString());
+ Assert.AreEqual("1.20 m/s", speed.ToString("F2 m/s"));
+ Assert.AreEqual(UnknownFormat, 1.2.ToString(UnknownFormat)); // for comparison
+ Assert.AreEqual(UnknownFormat, speed.ToString(UnknownFormat));
+ Assert.AreEqual("F1\u00A0{unit: invalid}", speed.ToString("F1 invalid"));
+ Assert.AreEqual("1.20 m⋅s⁻¹", speed.ToString("F2 m⋅s⁻¹"));
+ Assert.AreEqual("1.2\u00A0m⋅s⁻¹", speed.ToString("f1", "m⋅s⁻¹"));
+ Assert.AreEqual("1.2 m⋅s⁻¹", speed.ToString("f1 ", "m⋅s⁻¹"));
+ Assert.AreEqual("1.2 m⋅s⁻¹", speed.ToString("f1", " m⋅s⁻¹"));
+ Assert.AreEqual("1.2 m⋅s⁻¹", speed.ToString("f1 ", " m⋅s⁻¹"));
+ Assert.AreEqual("{value: null} mm⋅s⁻¹", speed.ToString("mm⋅s⁻¹"));
+ Assert.AreEqual("1200\u00A0s⁻¹⋅mm", speed.ToString("F0", "s⁻¹⋅mm"));
+ Assert.AreEqual("1200\u00A0s⁻¹⋅mm¹", speed.ToString("F0", "s⁻¹⋅mm¹"));
+ Assert.AreEqual("1.2\u00A0m*s^-1", speed.ToString("F1", "m*s^-1"));
+ Assert.AreEqual("1.2\u00A0s^-1*m", speed.ToString("F1", "s^-1*m"));
+ Assert.AreEqual("1.2\u00A0s^-1*m^1", speed.ToString("F1", "s^-1*m^1"));
+ Assert.AreEqual("4.32\u00A0km/h", speed.ToString(SpeedUnit.KilometresPerHour));
+ Assert.AreEqual("1.2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.Default));
+ Assert.AreEqual("1.2\u00A0m/s", speed.ToString( SpeedUnit.MetresPerSecond, SymbolFormat.FractionHatPowers));
+ Assert.AreEqual("1.2\u00A0m*s^-1", speed.ToString( SpeedUnit.MetresPerSecond, SymbolFormat.SignedHatPowers));
+ Assert.AreEqual("1.2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.FractionSuperScript));
+ Assert.AreEqual("1.2\u00A0m⋅s⁻¹", speed.ToString( SpeedUnit.MetresPerSecond, SymbolFormat.SignedSuperScript));
+ Assert.AreEqual("4.3\u00A0km/h", speed.ToString("F1", SpeedUnit.KilometresPerHour));
+ Assert.AreEqual("1.2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.Default));
+ Assert.AreEqual("1.2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.FractionHatPowers));
+ Assert.AreEqual("1.2\u00A0m*s^-1", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.SignedHatPowers));
+ Assert.AreEqual("1.2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.FractionSuperScript));
+ Assert.AreEqual("1.2\u00A0m⋅s⁻¹", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.SignedSuperScript));
+ Assert.AreEqual("1,200.00 mm⋅s⁻¹", speed.ToString("N mm⋅s⁻¹"));
+ }
+
+ var sv = CultureInfo.GetCultureInfo("sv-SE");
+
+ Assert.AreEqual("1,2\u00A0m/s", speed.ToString(sv));
+ Assert.AreEqual("1,20 m/s", speed.ToString("F2 m/s", sv));
+ Assert.AreEqual(UnknownFormat, 1.2.ToString(UnknownFormat, sv)); // for comparison
+ Assert.AreEqual(UnknownFormat, speed.ToString(UnknownFormat, sv));
+ Assert.AreEqual("F1\u00A0{unit: invalid}", speed.ToString("F1 invalid", sv));
+ Assert.AreEqual("1,20 m⋅s⁻¹", speed.ToString("F2 m⋅s⁻¹", sv));
+ Assert.AreEqual("1,2\u00A0m⋅s⁻¹", speed.ToString("f1", "m⋅s⁻¹", sv));
+ Assert.AreEqual("1,2 m⋅s⁻¹", speed.ToString("f1 ", "m⋅s⁻¹", sv));
+ Assert.AreEqual("1,2 m⋅s⁻¹", speed.ToString("f1", " m⋅s⁻¹", sv));
+ Assert.AreEqual("1,2 m⋅s⁻¹", speed.ToString("f1 ", " m⋅s⁻¹", sv));
+ Assert.AreEqual("{value: null} mm⋅s⁻¹", speed.ToString("mm⋅s⁻¹", sv));
+ Assert.AreEqual("1200\u00A0s⁻¹⋅mm", speed.ToString("F0", "s⁻¹⋅mm", sv));
+ Assert.AreEqual("1200\u00A0s⁻¹⋅mm¹", speed.ToString("F0", "s⁻¹⋅mm¹", sv));
+ Assert.AreEqual("1,2\u00A0m*s^-1", speed.ToString("F1", "m*s^-1", sv));
+ Assert.AreEqual("1,2\u00A0s^-1*m", speed.ToString("F1", "s^-1*m", sv));
+ Assert.AreEqual("1,2\u00A0s^-1*m^1", speed.ToString("F1", "s^-1*m^1", sv));
+ Assert.AreEqual("4,32\u00A0km/h", speed.ToString(SpeedUnit.KilometresPerHour, sv));
+ Assert.AreEqual("1,2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.Default, sv));
+ Assert.AreEqual("1,2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.FractionHatPowers, sv));
+ Assert.AreEqual("1,2\u00A0m*s^-1", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.SignedHatPowers, sv));
+ Assert.AreEqual("1,2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.FractionSuperScript, sv));
+ Assert.AreEqual("1,2\u00A0m⋅s⁻¹", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.SignedSuperScript, sv));
+ Assert.AreEqual("4,3\u00A0km/h", speed.ToString("F1", SpeedUnit.KilometresPerHour, sv));
+ Assert.AreEqual("1,2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.Default, sv));
+ Assert.AreEqual("1,2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.FractionHatPowers, sv));
+ Assert.AreEqual("1,2\u00A0m*s^-1", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.SignedHatPowers, sv));
+ Assert.AreEqual("1,2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.FractionSuperScript, sv));
+ Assert.AreEqual("1,2\u00A0m⋅s⁻¹", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.SignedSuperScript, sv));
+ Assert.AreEqual("1\u00A0200,00 mm⋅s⁻¹", speed.ToString("N mm⋅s⁻¹", sv));
+ }
+
+ [Explicit(Reminder.ToDo)]
+ [Test]
+ public void FormatPressure()
+ {
+ var pressure = Pressure.FromMegapascals(1.2);
+ Assert.AreEqual("1.20 N/m^2", pressure.ToString("F2 N/m^2"));
+ Assert.AreEqual("1.20 N/m^2", pressure.ToString("F2 N⋅m⁻²"));
+ Assert.AreEqual("1.20 N/m^2", pressure.ToString("F2 N⋅mm⁻²"));
+ Assert.AreEqual("1.20 MPa", pressure.ToString("F2 MPa"));
+ Assert.AreEqual("1.20E6 Pa", pressure.ToString("E Pa"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Gu.Units.Tests.csproj b/Gu.Units.Tests/Gu.Units.Tests.csproj
index 85b8d095..4e4fb0c6 100644
--- a/Gu.Units.Tests/Gu.Units.Tests.csproj
+++ b/Gu.Units.Tests/Gu.Units.Tests.csproj
@@ -44,23 +44,48 @@
+
+
+
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
+
+
-
@@ -82,6 +107,9 @@
-->
+
+
+
@@ -111,42 +139,11 @@
-
-
-
-
- ..\packages\NUnit\lib\net20\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\net40\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\net45\nunit.framework.dll
- True
- True
-
-
-
-
-
-
- ..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll
- True
- True
-
-
-
-
+
+
+ ..\packages\NUnit\lib\nunit.framework.dll
+ True
+ True
+
+
\ No newline at end of file
diff --git a/Gu.Units.Tests/Gu.Units.Tests.csproj.DotSettings b/Gu.Units.Tests/Gu.Units.Tests.csproj.DotSettings
index 5d56eba4..cd946618 100644
--- a/Gu.Units.Tests/Gu.Units.Tests.csproj.DotSettings
+++ b/Gu.Units.Tests/Gu.Units.Tests.csproj.DotSettings
@@ -1,2 +1,7 @@
- True
\ No newline at end of file
+ True
+ True
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Caching/UnitFormatCacheTests.cs b/Gu.Units.Tests/Internals/Caching/UnitFormatCacheTests.cs
new file mode 100644
index 00000000..e1ce5339
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Caching/UnitFormatCacheTests.cs
@@ -0,0 +1,49 @@
+namespace Gu.Units.Tests.Internals
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using NUnit.Framework;
+
+ public class UnitFormatCacheTests
+ {
+ private const string Unicodes = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹";
+
+ [TestCaseSource(nameof(SymbolFormatsSource))]
+ public void GetOrCreateSymbolFormatLength(SymbolFormat symbolFormat)
+ {
+ var format = UnitFormatCache.GetOrCreate(LengthUnit.Millimetres, symbolFormat);
+ Assert.AreEqual(null, format.PrePadding);
+ Assert.AreEqual("mm", format.Format);
+ Assert.AreEqual(null, format.PostPadding);
+ }
+
+ [TestCase(SymbolFormat.Default, "mm/s")]
+ [TestCase(SymbolFormat.SignedHatPowers, "mm*s^-1")]
+ [TestCase(SymbolFormat.FractionHatPowers, "mm/s")]
+ [TestCase(SymbolFormat.SignedSuperScript, "mm⋅s⁻¹")]
+ [TestCase(SymbolFormat.FractionSuperScript, "mm/s")]
+ public void GetOrCreateSymbolFormatSpeed(SymbolFormat symbolFormat, string expected)
+ {
+ var format = UnitFormatCache.GetOrCreate(SpeedUnit.MillimetresPerSecond, symbolFormat);
+ Assert.AreEqual(null, format.PrePadding);
+ Assert.AreEqual(expected, format.Format);
+ Assert.AreEqual(null, format.PostPadding);
+ }
+
+ [TestCase(SymbolFormat.Default, "Hz")]
+ [TestCase(SymbolFormat.SignedHatPowers, "Hz")]
+ [TestCase(SymbolFormat.FractionHatPowers, "Hz")]
+ [TestCase(SymbolFormat.SignedSuperScript, "Hz")]
+ [TestCase(SymbolFormat.FractionSuperScript, "Hz")]
+ public void GetOrCreateSymbolFormatFrequency(SymbolFormat symbolFormat, string expected)
+ {
+ var format = UnitFormatCache.GetOrCreate(FrequencyUnit.Hz, symbolFormat);
+ Assert.AreEqual(null, format.PrePadding);
+ Assert.AreEqual(expected, format.Format);
+ Assert.AreEqual(null, format.PostPadding);
+ }
+
+ private static IReadOnlyList SymbolFormatsSource = Enum.GetValues(typeof(SymbolFormat)).Cast().ToList();
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Helpers/MapTests.cs b/Gu.Units.Tests/Internals/Helpers/MapTests.cs
new file mode 100644
index 00000000..fcd6cbe8
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Helpers/MapTests.cs
@@ -0,0 +1,39 @@
+namespace Gu.Units.Tests.Internals.Helpers
+{
+ using NUnit.Framework;
+
+ public class MapTests
+ {
+ [Test]
+ public void TryGetSuccess()
+ {
+ var map = new Map();
+ map.TryAdd(1, "1");
+ map.TryAdd(2, "2");
+
+ string actualString;
+ Assert.True(map.TryGet(1, out actualString));
+ Assert.AreEqual("1", actualString);
+
+ int actualInt;
+ Assert.True(map.TryGet("1", out actualInt));
+ Assert.AreEqual(1, actualInt);
+ }
+
+ [Test]
+ public void TryGetFail()
+ {
+ var map = new Map();
+ map.TryAdd(1, "1");
+ map.TryAdd(2, "2");
+
+ string actualString;
+ Assert.False(map.TryGet(3, out actualString));
+ Assert.AreEqual(null, actualString);
+
+ int actualInt;
+ Assert.False(map.TryGet("3", out actualInt));
+ Assert.AreEqual(0, actualInt);
+ }
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Helpers/StringBuilderPoolTests.cs b/Gu.Units.Tests/Internals/Helpers/StringBuilderPoolTests.cs
new file mode 100644
index 00000000..0915800b
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Helpers/StringBuilderPoolTests.cs
@@ -0,0 +1,61 @@
+namespace Gu.Units.Tests.Internals.Helpers
+{
+ using System.Collections.Concurrent;
+ using System.Reflection;
+ using System.Text;
+ using NUnit.Framework;
+
+ public class StringBuilderPoolTests
+ {
+ [Test]
+ public void UseTwice()
+ {
+ Clear(); // testrunner may run in parallel creating more than one builder.
+ StringBuilder inner1;
+ using (var builder = StringBuilderPool.Borrow())
+ {
+ builder.Append("a");
+ inner1 = GetInner(builder);
+ Assert.AreEqual("a", builder.ToString());
+ }
+
+ using (var builder = StringBuilderPool.Borrow())
+ {
+ builder.Append("bc");
+ var inner2 = GetInner(builder);
+ Assert.AreSame(inner1, inner2);
+ Assert.AreEqual("bc", builder.ToString());
+ }
+ }
+
+ [Test]
+ public void Parallel()
+ {
+ using (var builder1 = StringBuilderPool.Borrow())
+ using (var builder2 = StringBuilderPool.Borrow())
+ {
+ builder1.Append("a");
+ builder2.Append("b");
+ Assert.AreNotSame(GetInner(builder1), GetInner(builder2));
+ Assert.AreEqual("a", builder1.ToString());
+ Assert.AreEqual("b", builder2.ToString());
+ }
+ }
+
+ private static StringBuilder GetInner(StringBuilderPool.Builder outer)
+ {
+ var fieldInfo = typeof(StringBuilderPool.Builder).GetField("builder", BindingFlags.Instance | BindingFlags.NonPublic);
+ return (StringBuilder)fieldInfo.GetValue(outer);
+ }
+
+ private static void Clear()
+ {
+ var buildersField = typeof (StringBuilderPool).GetField("Builders", BindingFlags.Static | BindingFlags.NonPublic);
+ var builders =(ConcurrentQueue) buildersField.GetValue(null);
+ StringBuilder temp;
+ while (builders.TryDequeue(out temp))
+ {
+ }
+ }
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Helpers/StringMapTests.cs b/Gu.Units.Tests/Internals/Helpers/StringMapTests.cs
new file mode 100644
index 00000000..4d56327a
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Helpers/StringMapTests.cs
@@ -0,0 +1,103 @@
+namespace Gu.Units.Tests.Internals.Helpers
+{
+ using System;
+ using System.Reflection;
+ using NUnit.Framework;
+
+ public class StringMapTests
+ {
+ [Test]
+ public void AddSameTwice()
+ {
+ var cache = new StringMap();
+ cache.Add("abc", "d");
+ cache.Add("abc", "d");
+ var cachedItems = GetInnerCache(cache);
+ Assert.AreEqual(1, cachedItems.Length);
+ }
+
+ [Test]
+ public void AddDifferentWithSameKeyTwice()
+ {
+ var cache = new StringMap();
+ cache.Add("abc", "d");
+ Assert.Throws(() => cache.Add("abc", "e"));
+ }
+
+ [Test]
+ public void Sorts()
+ {
+ var cache = new StringMap();
+ var item1 = cache.Add("abcde", "1");
+ var item2 = cache.Add("abc", "2");
+ var item3 = cache.Add("abcd", "3");
+ var item4 = cache.Add("bar", "4");
+ var actual = GetInnerCache(cache);
+ var expected = new[] { item2, item3, item1, item4 };
+ CollectionAssert.AreEqual(expected, actual);
+ }
+
+ [TestCase("abc", 0, "abc", "2")]
+ [TestCase("abcdef", 0, "abcde", "1")]
+ [TestCase(" abc", 1, "abc", "2")]
+ [TestCase(" abcd", 1, "abcd", "3")]
+ public void TryFindSubStringSuccess(string key, int pos, string expectedKey, string expectedValue)
+ {
+ var cache = new StringMap();
+ cache.Add("abcde", "1");
+ cache.Add("abc", "2");
+ cache.Add("abcd", "3");
+ cache.Add("bar", "4");
+ string actual;
+ string actualKey;
+ var success = cache.TryGetBySubString(key, pos, out actualKey, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expectedKey, actualKey);
+ Assert.AreEqual(expectedValue, actual);
+
+ success = cache.TryGetBySubString(key, pos, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expectedValue, actual);
+ }
+
+ [TestCase("abc", "2")]
+ [TestCase("abcde", "1")]
+ [TestCase("abcd", "3")]
+ public void TryGetSuccess(string key, string expected)
+ {
+ var cache = new StringMap();
+ cache.Add("abcde", "1");
+ cache.Add("abc", "2");
+ cache.Add("abcd", "3");
+ cache.Add("bar", "4");
+ string actual;
+ var success = cache.TryGet(key, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestCase(null, 0)]
+ [TestCase("", 0)]
+ [TestCase(" ggg", 1)]
+ [TestCase("ggg", 1)]
+ [TestCase("g", 1)]
+ public void AddThenGetFail(string key, int pos)
+ {
+ var cache = new StringMap();
+ cache.Add("abc", "d");
+ cache.Add("foo", "e");
+ cache.Add("bar", "f");
+ string actual;
+ var success = cache.TryGetBySubString(key, pos, out actual);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(null, actual);
+ Assert.AreEqual(null, actual);
+ }
+
+ private static StringMap.CachedItem[] GetInnerCache(StringMap cache)
+ {
+ var fieldInfo = typeof(StringMap).GetField("cache", BindingFlags.NonPublic | BindingFlags.Instance);
+ return (StringMap.CachedItem[])fieldInfo.GetValue(cache);
+ }
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/CompositeFormatParserTests.cs b/Gu.Units.Tests/Internals/Parsing/CompositeFormatParserTests.cs
new file mode 100644
index 00000000..eca67dfa
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/CompositeFormatParserTests.cs
@@ -0,0 +1,74 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System.Globalization;
+ using NUnit.Framework;
+
+ public class CompositeFormatParserTests
+ {
+ private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹";
+ private const char MultiplyDot = '⋅';
+
+ [TestCase("", "{0}\u00A0m", "1.2\u00A0m")]
+ [TestCase(null, "{0}\u00A0m", "1.2\u00A0m")]
+ [TestCase("E mm", "{0:E} mm", "1.200000E+003 mm")]
+ [TestCase("ecm", "{0:e}cm", "1.200000e+002cm")]
+ [TestCase("E5m", "{0:E5}m", "1.20000E+000m")]
+ [TestCase("e5 m", "{0:e5} m", "1.20000e+000 m")]
+ [TestCase("Fcm", "{0:F}cm", "120.00cm")]
+ [TestCase(" f mm ", " {0:f} mm ", " 1200.00 mm ")]
+ [TestCase("F5km", "{0:F5}km", "0.00120km")]
+ [TestCase("f5 m", "{0:f5} m", "1.20000 m")]
+ [TestCase("Gcm", "{0:G}cm", "120cm")]
+ [TestCase("g cm", "{0:g} cm", "120 cm")]
+ [TestCase("G5m", "{0:G5}m", "1.2m")]
+ [TestCase("g5 m", "{0:g5} m", "1.2 m")]
+ [TestCase("Nm", "{0:N}m", "1.20m")]
+ [TestCase("n m", "{0:n} m", "1.20 m")]
+ [TestCase("N5m", "{0:N5}m", "1.20000m")]
+ [TestCase("n5 m", "{0:n5} m", "1.20000 m")]
+ [TestCase("Rm", "{0:R}m", "1.2m")]
+ [TestCase("r m", "{0:r} m", "1.2 m")]
+ [TestCase("0m", "{0:0}m", "1m")]
+ [TestCase("0.00 m", "{0:0.00} m", "1.20 m")]
+ [TestCase("#m", "{0:#}m", "1m")]
+ [TestCase("#.# m", "{0:#.#} m", "1.2 m")]
+ [TestCase("#.0#m", "{0:#.0#}m", "1.2m")]
+ [TestCase("#0.00# m", "{0:#0.00#} m", "1.20 m")]
+ public void TryParseSuccess(string format, string expectedFormat, string expectedFormatted)
+ {
+ QuantityFormat actual;
+ var success = CompositeFormatParser.TryParse(format, out actual);
+
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expectedFormat, actual.CompositeFormat);
+
+ var length = Length.FromMetres(1.2);
+ var actualFormatted = length.ToString(format, CultureInfo.InvariantCulture);
+ Assert.AreEqual(expectedFormatted, actualFormatted);
+ }
+
+ [Explicit(Reminder.ToDo)]
+ [TestCase("1\u00A0200,00 mm⋅s⁻¹", "#_##0.00 mm⋅s⁻¹", "meh")]
+ [TestCase("F3 N/mm^2", "{0:F3} N/m^2", "MPa")]
+ [TestCase("F3 N⋅mm⁻²", "{0:F3} N⋅m⁻²", "MPa")]
+ [TestCase("E Pa", "{0:E} Pa", "Pa")]
+ public void TryParsePressure(string format, string expectedFormat, string expectedSymbol)
+ {
+ QuantityFormat actual;
+ var success = CompositeFormatParser.TryParse(format, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expectedFormat, actual.CompositeFormat);
+ Assert.AreEqual(expectedSymbol, actual.Unit.Symbol);
+ }
+
+ [TestCase("E", "E\u00A0{unit: null}")]
+ [TestCase("mm", "{value: null}\u00A0mm")]
+ public void TryParseError(string text, string expectedError)
+ {
+ QuantityFormat actual;
+ var success = CompositeFormatParser.TryParse(text, out actual);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(expectedError, actual.ErrorText);
+ }
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/DoubleFormatReaderTests.cs b/Gu.Units.Tests/Internals/Parsing/DoubleFormatReaderTests.cs
new file mode 100644
index 00000000..648d1a6e
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/DoubleFormatReaderTests.cs
@@ -0,0 +1,71 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System.Collections.Generic;
+ using NUnit.Framework;
+
+ public class DoubleFormatReaderTests
+ {
+ [TestCase(null, 0, null, 0)]
+ [TestCase("", 0, null, 0)]
+ [TestCase("E", 0, "E", 1)]
+ [TestCase("e", 0, "e", 1)]
+ [TestCase("E5", 0, "E5", 2)]
+ [TestCase("E20", 0, "E20", 3)] // double
+ [TestCase("e5", 0, "e5", 2)]
+ [TestCase("F", 0, "F", 1)]
+ [TestCase("f", 0, "f", 1)]
+ [TestCase("F5", 0, "F5", 2)]
+ [TestCase("f5", 0, "f5", 2)]
+ [TestCase("G", 0, "G", 1)]
+ [TestCase("g", 0, "g", 1)]
+ [TestCase("G5", 0, "G5", 2)]
+ [TestCase("g5", 0, "g5", 2)]
+ [TestCase("N", 0, "N", 1)]
+ [TestCase("n", 0, "n", 1)]
+ [TestCase("N5", 0, "N5", 2)]
+ [TestCase("n5", 0, "n5", 2)]
+ [TestCase("R", 0, "R", 1)]
+ [TestCase("r", 0, "r", 1)]
+ [TestCase("0", 0, "0", 1)]
+ [TestCase("0.00", 0, "0.00", 4)]
+ [TestCase("#", 0, "#", 1)]
+ [TestCase("#.#", 0, "#.#", 3)]
+ [TestCase("#.0#", 0, "#.0#", 4)]
+ [TestCase("#0.00#", 0, "#0.00#", 6)]
+ public void TryRead(string text, int pos, string expected, int expectedPos)
+ {
+ PaddedFormat actual = DoubleFormatCache.GetOrCreate(text, ref pos);
+ Assert.AreEqual(expected, actual.Format);
+ Assert.AreEqual(expectedPos, pos);
+ }
+
+ [TestCase("J", 0, null)]
+ [TestCase("J5", 0, null)]
+ //[TestCase("E100", 0, "E101")]
+ //[TestCase("E101", 0, "E111")]
+ //[TestCase("E102", 0, "E112")]
+ //[TestCase("E-1", 0, "E-1")]
+ [TestCase("abc", 0, "abc")]
+ public void TryReadError(string text, int pos, string expectedFormatted)
+ {
+ PaddedFormat actual = DoubleFormatCache.GetOrCreate(text, ref pos);
+ Assert.AreEqual(text, actual.Format);
+ Assert.AreEqual(0, pos);
+ string formatted = null;
+ try
+ {
+ formatted = 1.2.ToString(text);
+ }
+ catch
+ {
+ }
+
+ Assert.AreEqual(expectedFormatted, formatted);
+ }
+
+ private static readonly IReadOnlyList> HappyPaths = new[]
+ {
+ SuccessData.Create("e", 0, new PaddedFormat(null, "e", null), 1),
+ };
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/DoubleReaderTests.cs b/Gu.Units.Tests/Internals/Parsing/DoubleReaderTests.cs
new file mode 100644
index 00000000..40a5cbd4
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/DoubleReaderTests.cs
@@ -0,0 +1,318 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Text;
+ using NUnit.Framework;
+
+ public class DoubleReaderTests
+ {
+ private static readonly string[] PadFormats = { "abc{0}def", "abcd{0}ef", "{0}" };
+
+ [Explicit("Runs forever unless")]
+ [Test]
+ public void Fuzzer()
+ {
+ var rnd = new Random(DateTime.Now.Millisecond);
+ long count = 0;
+ var builder = new StringBuilder();
+ string text;
+
+ double parsed;
+ double read;
+
+ bool parseSuccess;
+ bool readSuccess;
+ bool success;
+ do
+ {
+ var pos = 0;
+ var length = rnd.Next(2, 15);
+ var decimalPlace = rnd.Next(length);
+ builder.Clear();
+ for (int i = 0; i < length; i++)
+ {
+ if (i == decimalPlace)
+ {
+ builder.Append('.');
+ continue;
+ }
+ builder.Append((char)rnd.Next('0', '9'));
+ }
+
+ text = builder.ToString();
+ parseSuccess = double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out parsed);
+ readSuccess = DoubleReader.TryRead(text, ref pos, NumberStyles.Float, CultureInfo.InvariantCulture, out read);
+ success = parseSuccess && readSuccess && parsed == read;
+ count++;
+ } while (success);
+
+ Console.WriteLine($"Count: {count}");
+ Console.WriteLine(text);
+ Console.WriteLine($"success: {parseSuccess} double.TryParse(text, out {parsed.ToString("R", CultureInfo.InvariantCulture)})");
+ Console.WriteLine($"success: {readSuccess} double.TryParse(text, out {read.ToString("R", CultureInfo.InvariantCulture)})");
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void ReadSuccess(DoubleData data)
+ {
+ var culture = data.Culture;
+ var style = data.Styles;
+ foreach (var format in PadFormats)
+ {
+ var text = string.Format(format, data.Text);
+ var pos = format.IndexOf('{');
+ var start = pos;
+ double expected = double.Parse(data.Text, style, culture);
+ var actual = DoubleReader.Read(text, ref pos, style, culture);
+ Assert.AreEqual(expected, actual);
+ var expectedEnd = start + data.Text.Length;
+ Assert.AreEqual(expectedEnd, pos);
+ }
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void ReadError(DoubleData data)
+ {
+ var culture = data.Culture;
+ var style = data.Styles;
+ var text = data.Text;
+ foreach (var format in PadFormats)
+ {
+ var ns = string.Format(format, text);
+ var pos = format.IndexOf('{');
+ var start = pos;
+ Exception parseException = null;
+ try
+ {
+ double.Parse(text, style, culture);
+ }
+ catch (Exception e)
+ {
+ parseException = e;
+ }
+
+ Exception readException = null;
+ try
+ {
+ DoubleReader.Read(ns, ref pos, style, culture);
+ }
+ catch (Exception e)
+ {
+ readException = e;
+ }
+
+ Assert.AreEqual(start, pos);
+ Assert.NotNull(parseException);
+ Assert.NotNull(readException);
+ }
+ }
+
+ [Test]
+ public void ReadException()
+ {
+ int endPos;
+ var text = "abcdef";
+ var culture = CultureInfo.InvariantCulture;
+ var pos = 3;
+ var e = Assert.Throws(() => DoubleReader.Read(text, ref pos, NumberStyles.Float, culture));
+ var expected = "Expected to find a double starting at index 3\r\n" +
+ "String: abcdef\r\n" +
+ " ^";
+ Assert.AreEqual(expected, e.Message);
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryReadSuccess(DoubleData data)
+ {
+ var culture = data.Culture;
+ var style = data.Styles;
+ var pos = 0;
+ double expected;
+ Assert.IsTrue(double.TryParse(data.Text, style, culture, out expected));
+ double actual;
+ Assert.IsTrue(DoubleReader.TryRead(data.Text, ref pos, style, culture, out actual));
+ Assert.AreEqual(expected, actual);
+ var expectedEnd = data.Text.Length;
+ Assert.AreEqual(expectedEnd, pos);
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryReadPaddedSuccess(DoubleData data)
+ {
+ var culture = data.Culture;
+ var style = data.Styles;
+ foreach (var format in PadFormats)
+ {
+ var text = string.Format(format, data.Text);
+ var pos = format.IndexOf('{');
+ var start = pos;
+ double expected;
+ Assert.IsTrue(double.TryParse(data.Text, style, culture, out expected));
+ double actual;
+ Assert.IsTrue(DoubleReader.TryRead(text, ref pos, style, culture, out actual));
+ Assert.AreEqual(expected, actual);
+ var expectedEnd = start + data.Text.Length;
+ Assert.AreEqual(expectedEnd, pos);
+ }
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void TryReadErrorPadded(DoubleData data)
+ {
+ var culture = data.Culture;
+ var style = data.Styles;
+ var s = data.Text;
+ foreach (var format in PadFormats)
+ {
+ var ns = string.Format(format, s);
+ var pos = format.IndexOf('{');
+ var start = pos;
+ double expected;
+ Assert.IsFalse(double.TryParse(s, style, culture, out expected));
+ double actual;
+ Assert.IsFalse(DoubleReader.TryRead(ns, ref pos, style, culture, out actual));
+ Assert.AreEqual(expected, actual);
+ Assert.AreEqual(start, pos);
+ }
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void TryReadError(DoubleData data)
+ {
+ var culture = data.Culture;
+ var style = data.Styles;
+ var pos = 0;
+ double expected;
+ Assert.IsFalse(double.TryParse(data.Text, style, culture, out expected));
+ double actual;
+ Assert.IsFalse(DoubleReader.TryRead(data.Text, ref pos, style, culture, out actual));
+ Assert.AreEqual(expected, actual);
+ Assert.AreEqual(0, pos);
+ }
+
+ #region TestData
+
+ private static readonly CultureInfo en = CultureInfo.GetCultureInfo("en-US");
+ private static readonly CultureInfo sv = CultureInfo.GetCultureInfo("sv-SE");
+
+ private static readonly IReadOnlyList HappyPaths = new[]
+ {
+ CreateParseData("0", NumberStyles.Float, en),
+ CreateParseData("0.", NumberStyles.Float, en),
+ CreateParseData(".0", NumberStyles.Float, en),
+ CreateParseData("0.0", NumberStyles.Float, en),
+ CreateParseData("1.2", NumberStyles.Float, en),
+ CreateParseData("0.012", NumberStyles.Float, en),
+ CreateParseData("0.0012", NumberStyles.Float, en),
+ CreateParseData("0.001", NumberStyles.Float, en),
+ CreateParseData("1", NumberStyles.Float, en),
+ CreateParseData(" 1", NumberStyles.Float, en),
+ CreateParseData("-1", NumberStyles.Float, en),
+ CreateParseData("+1", NumberStyles.Float, en),
+ CreateParseData(".1", NumberStyles.Float, en),
+ CreateParseData("-.1", NumberStyles.Float, en),
+ CreateParseData("1.", NumberStyles.Float, en),
+ CreateParseData("-1.", NumberStyles.Float, en),
+ CreateParseData("12,345.67", NumberStyles.Float | NumberStyles.AllowThousands, en),
+ CreateParseData("-12,345.67", NumberStyles.Float | NumberStyles.AllowThousands, en),
+ CreateParseData("+1.2", NumberStyles.Float, en),
+ CreateParseData("+1,2", NumberStyles.Float, sv),
+ CreateParseData("+1.2e3", NumberStyles.Float, en),
+ CreateParseData("-1.2E3", NumberStyles.Float, en),
+ CreateParseData("+1.2e-3", NumberStyles.Float, en),
+ CreateParseData("+1.2E-3", NumberStyles.Float, en),
+ CreateParseData("-1.2e+3", NumberStyles.Float, en),//1,,2,3,4,5,,,.00
+ CreateParseData("1,,2,3,4,5,,,.00", NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData("12345678910123456789", NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData("1.2345678910123456789", NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData("1234567891012345678.9", NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData(new string('1', 307), NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData(new string('1', 308), NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData(new string('1', 309), NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData("0." + new string('0', 15)+"1", NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData("0." + new string('0', 16)+"1", NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData("0." + new string('0', 299)+"1", NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData("0." + new string('0', 300)+"1", NumberStyles.Float|NumberStyles.AllowThousands, en),
+ CreateParseData(-12345.678910, NumberStyles.Float, en, "e"),
+ CreateParseData(12345.678910, NumberStyles.Float, en, "E"),
+ CreateParseData(12345.678910, NumberStyles.Float, en, "E5"),
+ CreateParseData(-12345.678910, NumberStyles.Float, en, "f"),
+ CreateParseData(12345.678910, NumberStyles.Float, en, "F"),
+ CreateParseData(12345.678912, NumberStyles.Float, en, "F20"),
+ CreateParseData(12345.678910, NumberStyles.Float, en, "G"),
+ CreateParseData(-12345.678910, NumberStyles.Float, en, "g"),
+ CreateParseData(12345.678910, NumberStyles.Float, en, "g5"),
+ CreateParseData(12345.678910, NumberStyles.Float |NumberStyles.AllowThousands, en, "n"),
+ CreateParseData(12345.678910, NumberStyles.Float |NumberStyles.AllowThousands, en, "N"),
+ CreateParseData(12345.678910, NumberStyles.Float |NumberStyles.AllowThousands, en, "N5"),
+ CreateParseData(12345.678910, NumberStyles.Float, en, "R"),
+ CreateParseData(-12345.678910, NumberStyles.Float, en, "r"),
+ CreateParseData(-Math.PI, NumberStyles.Float, en, "f15"),
+ CreateParseData(-Math.PI, NumberStyles.Float, en, "f16"),
+ CreateParseData(-Math.PI, NumberStyles.Float, en, "f17"),
+ CreateParseData(Math.PI, NumberStyles.Float, en, "f25"),
+ CreateParseData("3.141592653589793238", NumberStyles.Float, en),
+ CreateParseData("-3.141592653589793238", NumberStyles.Float, en),
+ CreateParseData("0.017453292519943295", NumberStyles.Float, en),
+ CreateParseData(-Math.PI, NumberStyles.Float, en, "r"),
+ CreateParseData(sv.NumberFormat.NaNSymbol, NumberStyles.Float, sv),
+ CreateParseData(sv.NumberFormat.PositiveInfinitySymbol, NumberStyles.Float, sv),
+ CreateParseData(sv.NumberFormat.NegativeInfinitySymbol, NumberStyles.Float, sv),
+ };
+
+ private static DoubleData CreateParseData(double value,
+ NumberStyles styles,
+ IFormatProvider culture,
+ string format)
+ {
+ return new DoubleData(value.ToString(format, culture), styles, culture);
+ }
+
+ private static readonly IReadOnlyList Errors = new[]
+ {
+ CreateParseData("e1", NumberStyles.Float, en),
+ CreateParseData(" 1", NumberStyles.None, en),
+ CreateParseData("-1", NumberStyles.None, en),
+ CreateParseData(".1", NumberStyles.None, en),
+ CreateParseData(",.1", NumberStyles.Float, en),
+ CreateParseData(new string('1', 311), NumberStyles.Float, en),
+ //Add("1.", NumberStyles.Float | NumberStyles.AllowHexSpecifier, en),
+ CreateParseData(".", NumberStyles.Float, en),
+ //Add("+1,2", NumberStyles.Float, en),
+ //Add("+1.2", NumberStyles.Float, sv),
+ CreateParseData("+1.2e3", NumberStyles.None | NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, en),
+ };
+
+ private static DoubleData CreateParseData(string text,
+ NumberStyles styles,
+ CultureInfo culture)
+ {
+ return new DoubleData(text, styles, culture);
+ }
+
+ public class DoubleData
+ {
+ public readonly string Text;
+ public readonly NumberStyles Styles;
+ public readonly IFormatProvider Culture;
+
+ public DoubleData(string text,
+ NumberStyles styles,
+ IFormatProvider culture)
+ {
+ this.Text = text;
+ this.Styles = styles;
+ this.Culture = culture;
+ }
+
+ public override string ToString()
+ {
+ return $"Text: {this.Text}, Styles: {this.Styles}, Culture: {this.Culture}";
+ }
+ }
+
+ #endregion TestData
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/Extensions/ParseExt.cs b/Gu.Units.Tests/Internals/Parsing/Extensions/ParseExt.cs
new file mode 100644
index 00000000..3b2e9138
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/Extensions/ParseExt.cs
@@ -0,0 +1,54 @@
+namespace Gu.Units.Tests.Internals.Parsing.Sources
+{
+ using System;
+ using System.Globalization;
+
+ public static class ParseExt
+ {
+ public static object Parse(this ISuccessData data, string text)
+ {
+ var parseMethod = data.Type.GetMethod(
+ nameof(Length.Parse),
+ new[] { typeof(string) });
+ return parseMethod.Invoke(null, new object[] { text });
+ }
+
+ public static object Parse(this ISuccessData data, string text,
+ CultureInfo cultureInfo)
+ {
+ var name = nameof(Length.Parse);
+ var parseMethod = data.Type.GetMethod(
+ name,
+ new[] { typeof(string), typeof(IFormatProvider) });
+ return parseMethod.Invoke(null, new object[] { text, cultureInfo });
+ }
+
+ public static bool TryParse(this ISuccessData data, string text,
+ out object actual)
+ {
+ var tryParseMethod = data.Type.GetMethod(
+ nameof(Length.TryParse),
+ new[] { typeof(string), data.Type.MakeByRefType() });
+ actual = null;
+ var parameters = new[] { text, actual };
+ var success = (bool)tryParseMethod.Invoke(null, parameters);
+ actual = parameters[1];
+ return success;
+ }
+
+ public static bool TryParse(this ISuccessData data, string text,
+ CultureInfo cultureInfo,
+ out object actual)
+ {
+ var parseMethod = data.Type.GetMethod(
+ nameof(Length.TryParse),
+ new[] { typeof(string), typeof(IFormatProvider), data.Type.MakeByRefType() });
+ actual = null;
+ var parameters = new object[] { text, cultureInfo, actual };
+ var success = (bool)parseMethod.Invoke(null, parameters);
+ actual = parameters[2];
+ return success;
+ }
+
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/Extensions/ThreadExt.cs b/Gu.Units.Tests/Internals/Parsing/Extensions/ThreadExt.cs
new file mode 100644
index 00000000..78b36e88
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/Extensions/ThreadExt.cs
@@ -0,0 +1,32 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+ using System.Globalization;
+ using System.Threading;
+
+ public static class ThreadExt
+ {
+ public static IDisposable UsingTempCulture(this Thread thread, CultureInfo culture)
+ {
+ return new TempCulture(thread, culture);
+ }
+
+ private class TempCulture : IDisposable
+ {
+ private readonly Thread _thread;
+ private readonly CultureInfo _current;
+
+ public TempCulture(Thread thread, CultureInfo culture)
+ {
+ this._thread = thread;
+ this._current = thread.CurrentCulture;
+ this._thread.CurrentCulture = culture;
+ }
+
+ public void Dispose()
+ {
+ this._thread.CurrentCulture = this._current;
+ }
+ }
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/IntReaderTests.cs b/Gu.Units.Tests/Internals/Parsing/IntReaderTests.cs
new file mode 100644
index 00000000..4f97fcd0
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/IntReaderTests.cs
@@ -0,0 +1,78 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+ using System.Collections.Generic;
+ using NUnit.Framework;
+
+ public class IntReaderTests
+ {
+ [TestCaseSource(nameof(HappyPaths))]
+ public void ParseSuccess(SuccessData data)
+ {
+ int pos = data.Start;
+ var actual = IntReader.ReadInt32(data.Text, ref pos);
+ Assert.AreEqual(actual, data.Expected);
+ Assert.AreEqual(pos, data.ExpectedEnd);
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void ParseError(ErrorData data)
+ {
+ int pos = data.Start;
+ Assert.Throws(() => IntReader.ReadInt32(data.Text, ref pos));
+ Assert.AreEqual(pos, data.ExpectedEnd);
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryParseSuccess(SuccessData data)
+ {
+ int pos = data.Start;
+ int actual;
+ var success = IntReader.TryReadInt32(data.Text, ref pos, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+ Assert.AreEqual(data.ExpectedEnd, pos);
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void TryParseError(ErrorData data)
+ {
+ int pos = data.Start;
+ int actual;
+ var success = IntReader.TryReadInt32(data.Text, ref pos, out actual);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(data.Expected, actual);
+ Assert.AreEqual(data.ExpectedEnd, pos);
+ }
+
+ private static readonly IReadOnlyList> HappyPaths = new[]
+ {
+ SuccessData.Create("1", 0, 1, 1),
+ SuccessData.Create("01", 0, 1, 2),
+ SuccessData.Create("12", 0, 12, 2),
+ SuccessData.Create(" 12", 1, 12, 3),
+ SuccessData.Create(" 12 ", 1, 12, 3),
+ SuccessData.Create("-1", 0, -1, 2),
+ SuccessData.Create("-12", 0, -12, 3),
+ SuccessData.Create("12345", 0, 12345, 5),
+ SuccessData.Create("67890", 0, 67890, 5),
+ SuccessData.Create(int.MaxValue.ToString(), 0, int.MaxValue, int.MaxValue.ToString().Length),
+ SuccessData.Create(" " + int.MaxValue, 1, int.MaxValue, int.MaxValue.ToString().Length + 1),
+ SuccessData.Create(" " + int.MaxValue + " ", 1, int.MaxValue, int.MaxValue.ToString().Length + 1),
+ SuccessData.Create(" " + int.MaxValue + "9", 1, int.MaxValue, int.MaxValue.ToString().Length + 1),
+ SuccessData.Create(int.MinValue.ToString(), 0, int.MinValue, int.MinValue.ToString().Length),
+ SuccessData.Create(" " + int.MinValue, 1, int.MinValue, int.MinValue.ToString().Length + 1),
+ SuccessData.Create(" " + int.MinValue + " ", 1, int.MinValue, int.MinValue.ToString().Length + 1),
+ SuccessData.Create(" " + int.MinValue + "9", 1, int.MinValue, int.MinValue.ToString().Length + 1),
+ };
+
+ private static readonly IReadOnlyList> Errors = new[]
+ {
+ ErrorData.Create("abc", 0),
+ ErrorData.Create("abc", 1),
+ ErrorData.Create("abc", 2),
+ ErrorData.Create(((long)int.MinValue-1).ToString(), 0), // less than int.min
+ ErrorData.Create(((long)int.MaxValue+1).ToString(), 0), // greater than int.max
+ };
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/ParseLengthTests.cs b/Gu.Units.Tests/Internals/Parsing/ParseLengthTests.cs
new file mode 100644
index 00000000..66190f93
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/ParseLengthTests.cs
@@ -0,0 +1,146 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Threading;
+ using NUnit.Framework;
+
+ public class ParseLengthTests
+ {
+ [TestCaseSource(nameof(HappyPaths))]
+ public void ParseLengthSuccess(SuccessData data)
+ {
+ var numberStyles = NumberStyles.Float;
+
+ var length = QuantityParser.Parse(data.Text, Length.From, numberStyles, data.CultureInfo);
+ Assert.AreEqual(data.Expected, length);
+
+ length = Length.Parse(data.Text, numberStyles, data.CultureInfo);
+ Assert.AreEqual(data.Expected, length);
+
+ length = Length.Parse(data.Text, data.CultureInfo);
+ Assert.AreEqual(data.Expected, length);
+
+ using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo))
+ {
+ length = Length.Parse(data.Text);
+ Assert.AreEqual(data.Expected, length);
+ }
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void ParseLengthThrows(ErrorData data)
+ {
+ var numberStyles = NumberStyles.Float;
+ var ex = Assert.Throws(() => QuantityParser.Parse(data.Text, Length.From, numberStyles, data.CultureInfo));
+ Assert.AreEqual(data.ExpectedMessage, ex.Message);
+
+ ex = Assert.Throws(() => Length.Parse(data.Text, numberStyles, data.CultureInfo));
+ Assert.AreEqual(data.ExpectedMessage, ex.Message);
+
+ ex = Assert.Throws(() => Length.Parse(data.Text, data.CultureInfo));
+ Assert.AreEqual(data.ExpectedMessage, ex.Message);
+
+ using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo))
+ {
+ ex = Assert.Throws(() => Length.Parse(data.Text));
+ Assert.AreEqual(data.ExpectedMessage, ex.Message);
+ }
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryParseLengthSuccess(SuccessData data)
+ {
+ Length actual;
+ var success = QuantityParser.TryParse(
+ data.Text,
+ Length.From,
+ NumberStyles.Float,
+ data.CultureInfo,
+ out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+
+ success = Length.TryParse(data.Text, NumberStyles.Float, data.CultureInfo, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+
+ success = Length.TryParse(data.Text, data.CultureInfo, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+
+ using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo))
+ {
+ success = Length.TryParse(data.Text, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+ }
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void TryParseLengthFails(ErrorData data)
+ {
+ Length length;
+ var success = QuantityParser.TryParse(
+ data.Text,
+ Length.From,
+ NumberStyles.Float,
+ data.CultureInfo,
+ out length);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(data.Expected, length);
+
+ success = Length.TryParse(data.Text, NumberStyles.Float, data.CultureInfo, out length);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(data.Expected, length);
+
+ success = Length.TryParse(data.Text, data.CultureInfo, out length);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(data.Expected, length);
+
+ using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo))
+ {
+ success = Length.TryParse(data.Text, out length);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(data.Expected, length);
+ }
+ }
+
+ private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; // keeping this here for copy pasting
+ private static readonly CultureInfo en = CultureInfo.GetCultureInfo("en-US");
+ private static readonly CultureInfo sv = CultureInfo.GetCultureInfo("sv-SE");
+
+ private static readonly IReadOnlyList> HappyPaths = new SuccessData[]
+ {
+ SuccessData.Create("1m", en, Length.FromMetres(1)),
+ SuccessData.Create("1m", sv, Length.FromMetres(1)),
+ SuccessData.Create("-1m", en, Length.FromMetres(-1)),
+ SuccessData.Create("-1m", sv, Length.FromMetres(-1)),
+ SuccessData.Create("1.2m", en,Length.FromMetres( 1.2)),
+ SuccessData.Create(" 1.2m", en,Length.FromMetres( 1.2)),
+ SuccessData.Create("1.2 m", en,Length.FromMetres( 1.2)),
+ SuccessData.Create("1.2m ", en,Length.FromMetres( 1.2)),
+ SuccessData.Create(" 1.2 m ", en,Length.FromMetres( 1.2)),
+ SuccessData.Create("1,2m", sv,Length.FromMetres( 1.2)),
+ SuccessData.Create("1e3m", en,Length.FromMetres( 1e3)),
+ SuccessData.Create("1E3m",en,Length.FromMetres( 1e3)),
+ SuccessData.Create("1e+3m",en,Length.FromMetres( 1e+3)),
+ SuccessData.Create("1E+3m",en,Length.FromMetres( 1E+3)),
+ SuccessData.Create("1.2e-3m", en,Length.FromMetres( 1.2e-3)),
+ SuccessData.Create("1.2E-3m", en, Length.FromMetres(1.2e-3)),
+ SuccessData.Create(" 1m",en, Length.FromMetres(1)),
+ SuccessData.Create("1 m",en,Length.FromMetres( 1)),
+ SuccessData.Create("1m ",en,Length.FromMetres( 1)),
+ SuccessData.Create("1.2mm",en, Length.FromMillimetres(1.2)),
+ SuccessData.Create("1.2cm",en,Length.FromCentimetres(1.2)),
+ };
+
+ private static readonly IReadOnlyList> Errors = new ErrorData[]
+ {
+ ErrorData.Create("1.2m", sv, "Could not parse the unit value from: 1.2m"),
+ ErrorData.Create("1.2", en, "Could not parse the unit value from: 1.2"),
+ ErrorData.Create("1,2m", en, "Could not parse the unit value from: 1,2m"),
+ };
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/ParseRoundtripTests.cs b/Gu.Units.Tests/Internals/Parsing/ParseRoundtripTests.cs
new file mode 100644
index 00000000..dfc5693d
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/ParseRoundtripTests.cs
@@ -0,0 +1,94 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Threading;
+ using NUnit.Framework;
+ using Sources;
+
+ public class ParseRoundtripTests
+ {
+ [TestCaseSource(nameof(HappyPaths))]
+ public void ParseRoundtrip(ISuccessData data)
+ {
+ using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo))
+ {
+ var actual = data.Parse(data.Text);
+ Assert.AreEqual(data.Expected, actual);
+ var toString = actual.ToString();
+ var roundtripped = data.Parse(toString);
+ Assert.AreEqual(data.Expected, roundtripped);
+ }
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void ParseRoundtripWithCulture(ISuccessData data)
+ {
+ var actual = data.Parse(data.Text, data.CultureInfo);
+ Assert.AreEqual(data.Expected, actual);
+ var toString = ((IFormattable)actual).ToString(null, data.CultureInfo);
+ var roundtripped = data.Parse(toString, data.CultureInfo);
+ Assert.AreEqual(data.Expected, roundtripped);
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryParseRoundtrip(ISuccessData data)
+ {
+ using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo))
+ {
+ object actual;
+ var success = data.TryParse(data.Text, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+ var toString = actual.ToString();
+ success = data.TryParse(toString, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+ }
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryParseRoundtripWithCulture(ISuccessData data)
+ {
+ object actual;
+ var success = data.TryParse(data.Text, data.CultureInfo, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+ var toString = ((IFormattable)actual).ToString(null, data.CultureInfo);
+ success = data.TryParse(data.Text, data.CultureInfo, out actual);
+ Assert.AreEqual(data.Expected, actual);
+ Assert.AreEqual(true, success);
+ }
+
+ private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; // keeping this here for copy pasting
+ private static readonly CultureInfo en = CultureInfo.GetCultureInfo("en-US");
+ private static readonly CultureInfo sv = CultureInfo.GetCultureInfo("sv-SE");
+
+ private static readonly IReadOnlyList HappyPaths = new ISuccessData[]
+ {
+ SuccessData.Create("1.2m^2", en, Area.FromSquareMetres(1.2)),
+ SuccessData.Create("1.2m²", en, Area.FromSquareMetres(1.2)),
+ SuccessData.Create("1,2m²", sv, Area.FromSquareMetres(1.2)),
+ SuccessData.Create("1.2s", en, Time.FromSeconds(1.2)),
+ SuccessData.Create("1.2h", en, Time.FromHours(1.2)),
+ SuccessData.Create("1.2ms", en, Time.FromMilliseconds(1.2)),
+ SuccessData.Create("1.2kg", en, Mass.FromKilograms(1.2)),
+ SuccessData.Create("1.2g", en, Mass.FromGrams(1.2)),
+ SuccessData.Create("1.2m³", en, Volume.FromCubicMetres(1.2)),
+ SuccessData.Create("1.2m^3", en, Volume.FromCubicMetres(1.2)),
+ SuccessData.Create("1.2m/s", en, Speed.FromMetresPerSecond(1.2)),
+ SuccessData.Create("1.2m⋅s⁻¹", en, Speed.FromMetresPerSecond(1.2)),
+ SuccessData.Create("1.2m*s⁻¹", en, Speed.FromMetresPerSecond(1.2)),
+ SuccessData.Create("1.2m¹⋅s⁻¹", en, Speed.FromMetresPerSecond(1.2)),
+ SuccessData.Create("1.2m^1⋅s⁻¹", en, Speed.FromMetresPerSecond(1.2)),
+ SuccessData.Create("1.2m^1⋅s^-1", en, Speed.FromMetresPerSecond(1.2)),
+ SuccessData.Create("1.2m^1/s^2", en, Acceleration.FromMetresPerSecondSquared(1.2)),
+ SuccessData.Create("1.2m/s^2", en, Acceleration.FromMetresPerSecondSquared(1.2)),
+ SuccessData.Create("1.2 m/s^2", en, Acceleration.FromMetresPerSecondSquared(1.2)),
+ SuccessData.Create("1.2 m / s^2", en, Acceleration.FromMetresPerSecondSquared(1.2)),
+ SuccessData.Create("1.2 m / s²", en, Acceleration.FromMetresPerSecondSquared(1.2)),
+ SuccessData.Create("1.2 mm / s²", en, Acceleration.FromMillimetresPerSecondSquared(1.2)),
+ };
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/PowerReaderTests.cs b/Gu.Units.Tests/Internals/Parsing/PowerReaderTests.cs
new file mode 100644
index 00000000..beb0328c
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/PowerReaderTests.cs
@@ -0,0 +1,48 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System.Collections.Generic;
+ using NUnit.Framework;
+
+ public class PowerReaderTests
+ {
+ [TestCaseSource(nameof(HappyPaths))]
+ public void ReadSuccess(SuccessData data)
+ {
+ var end = data.Start;
+ var actual = PowerReader.Read(data.Text, ref end);
+ Assert.AreEqual(data.Expected, actual);
+ Assert.AreEqual(data.ExpectedEnd, end);
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryReadSuccess(SuccessData data)
+ {
+ var end = data.Start;
+ int actual;
+ var success = PowerReader.TryRead(data.Text, ref end, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+ Assert.AreEqual(data.ExpectedEnd, end);
+ }
+
+ private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; // keeping this here for copy pasting
+ private static readonly IReadOnlyList> HappyPaths = new[]
+ {
+ SuccessData.Create("mm", 2, 1, 2),
+ SuccessData.Create("mm^2", 2, 2, 4),
+ SuccessData.Create("mm^-2", 2, -2, 5),
+ SuccessData.Create("mm⁰", 2, 0, 3),
+ SuccessData.Create("mm²", 2, 2, 3),
+ SuccessData.Create("mm⁺²", 2, 2, 4),
+ SuccessData.Create("mm⁺¹²", 2, 12, 5),
+ SuccessData.Create("mm¹²", 2, 12, 4),
+ SuccessData.Create("mm⁻¹²", 2, -12, 5),
+ SuccessData.Create("mm", 2, 1, 2),
+ };
+
+ private static readonly IReadOnlyList> ErrorSource = new[]
+ {
+ ErrorData.Create("mm^--2", 0),
+ };
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/SkipperTests.cs b/Gu.Units.Tests/Internals/Parsing/SkipperTests.cs
new file mode 100644
index 00000000..747a6572
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/SkipperTests.cs
@@ -0,0 +1,31 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using NUnit.Framework;
+
+ public class SkipperTests
+ {
+ [TestCase("abc", "abc", 0, 3)]
+ [TestCase("abc", "a", 0, 1)]
+ [TestCase(" abc", "a", 1, 2)]
+ [TestCase(" abc", "ab", 1, 3)]
+ [TestCase(" abc", "abc", 1, 4)]
+ public void TrySkipSuccess(string text, string toSkip, int pos, int expectedPos)
+ {
+ var success = Skipper.TrySkip(text, ref pos, toSkip);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expectedPos, pos);
+ }
+
+ [TestCase("abc", null, 0)]
+ [TestCase(" abc", "", 1)]
+ [TestCase("abc", "b", 0)]
+ [TestCase("abc", "bc", 0)]
+ public void TrySkipError(string text, string toSkip, int pos)
+ {
+ var expectedPos = pos;
+ var success = Skipper.TrySkip(text, ref pos, toSkip);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(expectedPos, pos);
+ }
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData.cs b/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData.cs
new file mode 100644
index 00000000..29c651c0
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData.cs
@@ -0,0 +1,49 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System.Collections.Generic;
+ using System.Globalization;
+
+ public static class ErrorData
+ {
+ public static ErrorData Create(string text,
+ int start,
+ T expected,
+ string expectedMessage)
+ {
+ return new ErrorData(text, start, expected, start, expectedMessage);
+ }
+
+ public static ErrorData Create(string text,
+ int start,
+ string expectedMessage)
+ {
+ return new ErrorData(text, start, default(T), start, expectedMessage);
+ }
+
+ public static ErrorData Create(string text, int start)
+ {
+ return new ErrorData(text, start, default(T), start, null);
+ }
+
+ public static ErrorData Create(string text, CultureInfo cultureInfo)
+ {
+ return new ErrorData(text, cultureInfo, 0, default(T), 0, null);
+ }
+
+ public static ErrorData Create(string text, CultureInfo cultureInfo, string expectedMessage)
+ {
+ return new ErrorData(text, cultureInfo, 0, default(T), 0, expectedMessage);
+ }
+
+ public static ErrorData Create(string text, CultureInfo cultureInfo, int start)
+ {
+ return new ErrorData(text, cultureInfo, start, default(T), start, null);
+ }
+
+ internal static ErrorData> CreateForSymbol(string text)
+ {
+ IReadOnlyList expected = new SymbolAndPower[0];
+ return new ErrorData>(text, 0, expected, 0, null);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData{T}.cs b/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData{T}.cs
new file mode 100644
index 00000000..cd140273
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData{T}.cs
@@ -0,0 +1,32 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System.Globalization;
+
+ public class ErrorData : SuccessData, IErrorData
+ {
+ public ErrorData(string text,
+ int start,
+ T expected,
+ int expectedEnd,
+ string expectedMessage)
+ : base(text, start, expected, expectedEnd)
+ {
+ ExpectedMessage = expectedMessage;
+ }
+
+ public ErrorData(string text,
+ CultureInfo cultureInfo,
+ int start,
+ T expected,
+ int expectedEnd,
+ string expectedMessage)
+ : base(text, cultureInfo, start, expected, expectedEnd)
+ {
+ ExpectedMessage = expectedMessage;
+ }
+
+ object IErrorData.Expected => Expected;
+
+ public string ExpectedMessage { get; }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/IErrorData.cs b/Gu.Units.Tests/Internals/Parsing/Sources/IErrorData.cs
new file mode 100644
index 00000000..d845ec8d
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/Sources/IErrorData.cs
@@ -0,0 +1,19 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+
+ public interface IErrorData
+ {
+ Type Type { get; }
+
+ object Expected { get; }
+
+ string ExpectedMessage { get; }
+
+ string Text { get; }
+
+ int Start { get; }
+
+ int ExpectedEnd { get; }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/ISuccessData.cs b/Gu.Units.Tests/Internals/Parsing/Sources/ISuccessData.cs
new file mode 100644
index 00000000..5c3d18ce
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/Sources/ISuccessData.cs
@@ -0,0 +1,20 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+ using System.Globalization;
+
+ public interface ISuccessData
+ {
+ Type Type { get; }
+
+ object Expected { get; }
+
+ int ExpectedEnd { get; }
+
+ int Start { get; }
+
+ string Text { get; }
+
+ CultureInfo CultureInfo { get; }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData.cs b/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData.cs
new file mode 100644
index 00000000..8fc1c88b
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData.cs
@@ -0,0 +1,40 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System.Collections.Generic;
+ using System.Globalization;
+
+ public static class SuccessData
+ {
+ public static SuccessData Create(string text,
+ int start,
+ T expected,
+ int expectedEnd)
+ {
+ return new SuccessData(text, start, expected, expectedEnd);
+ }
+
+ public static SuccessData Create(string text, T expected)
+ {
+ return new SuccessData(text, CultureInfo.InvariantCulture, 0, expected, text.Length);
+ }
+
+ public static SuccessData Create(string text, CultureInfo cultureInfo, T expected)
+ {
+ return new SuccessData(text, cultureInfo, 0, expected, text.Length);
+ }
+
+ public static SuccessData Create(string text,
+ CultureInfo cultureInfo,
+ int start,
+ T expected,
+ int expectedEnd)
+ {
+ return new SuccessData(text, cultureInfo, start, expected, expectedEnd);
+ }
+
+ internal static SuccessData> Create(string text, params SymbolAndPower[] expected)
+ {
+ return new SuccessData>(text, 0, expected, text.Length);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData{T}.cs b/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData{T}.cs
new file mode 100644
index 00000000..a3d4ef4e
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData{T}.cs
@@ -0,0 +1,77 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+
+ public class SuccessData : ISuccessData
+ {
+ public SuccessData(string text, int start, T expected, int expectedEnd)
+ : this(text, CultureInfo.InvariantCulture, start, expected, expectedEnd)
+ {
+ }
+
+ public SuccessData(string text,
+ CultureInfo cultureInfo,
+ int start,
+ T expected,
+ int expectedEnd)
+ {
+ Text = text;
+ CultureInfo = cultureInfo;
+ Start = start;
+ Expected = expected;
+ ExpectedEnd = expectedEnd;
+ }
+
+ public string Text { get; }
+
+ public CultureInfo CultureInfo { get; }
+
+ public int Start { get; }
+
+ public T Expected { get; }
+
+ public Type Type => typeof (T);
+
+ object ISuccessData.Expected => Expected;
+
+ public int ExpectedEnd { get; }
+
+ public override string ToString()
+ {
+ if (CultureInfo == null)
+ {
+ return $"Text: {Text}, Start: {Start}, Expected {ToString(typeof(T))}: {ToString(Expected)}, ExpectedEnd: {ExpectedEnd}";
+ }
+
+ return $"Text: {Text}, Culture: {CultureInfo.Name} Start: {Start}, Expected {ToString(typeof(T))}: {ToString(Expected)}, ExpectedEnd: {ExpectedEnd}";
+ }
+
+ private static string ToString(Type type)
+ {
+ if (type == typeof(IReadOnlyList))
+ {
+ return string.Empty;
+ }
+
+ return $"({type.Name})";
+ }
+
+ private static string ToString(T expected)
+ {
+ if (expected == null)
+ {
+ return "null";
+ }
+
+ var saps = expected as IEnumerable;
+ if (saps != null)
+ {
+ return $"{{{string.Join(", ", saps)}}}";
+ }
+
+ return expected.ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserSetTests.cs b/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserSetTests.cs
new file mode 100644
index 00000000..8d664826
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserSetTests.cs
@@ -0,0 +1,59 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System.Collections;
+ using System.Collections.Generic;
+ using NUnit.Framework;
+
+ public class SymbolAndPowerParserSetTests
+ {
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryReadSuccess(ISuccessData data)
+ {
+ var pos = data.Start;
+ IReadOnlyList actual;
+ var success = SymbolAndPowerReader.TryRead(data.Text, ref pos, out actual);
+ //Console.WriteLine("expected: {0}", data.ToString(data.Tokens));
+ //Console.WriteLine("actual: {0}", data.ToString(actual));
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.ExpectedEnd, pos);
+ CollectionAssert.AreEqual((IEnumerable)data.Expected, actual);
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void TryTokenizeError(IErrorData data)
+ {
+ var pos = data.Start;
+ IReadOnlyList actual;
+ var success = SymbolAndPowerReader.TryRead(data.Text, ref pos, out actual);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(data.Start, pos);
+ CollectionAssert.AreEqual(null, actual);
+ }
+
+ private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹";
+
+ internal static readonly IReadOnlyList>> HappyPaths = new[]
+ {
+ SuccessData.Create("m", new SymbolAndPower("m", 1)),
+ SuccessData.Create(" m ", new SymbolAndPower("m", 1)),
+ SuccessData.Create("m^2", new SymbolAndPower("m", 2)),
+ SuccessData.Create(" m ^ 2", new SymbolAndPower("m", 2)),
+ SuccessData.Create(" m ^ -2", new SymbolAndPower("m", -2)),
+ SuccessData.Create("m^1/s^2", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)),
+ SuccessData.Create("m¹/s²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)),
+ SuccessData.Create("m⁺¹/s²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)),
+ SuccessData.Create("m⁺¹/s²*g", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2), new SymbolAndPower("g", -1)),
+ SuccessData.Create("m¹⋅s⁻²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)),
+ SuccessData.Create("m⁻¹⋅s⁻²", new SymbolAndPower("m", -1), new SymbolAndPower("s", -2)),
+ };
+
+ internal static readonly IReadOnlyList>> Errors = new[]
+ {
+ ErrorData.CreateForSymbol("m⁻¹/s⁻²"),
+ ErrorData.CreateForSymbol("m⁻⁻¹"),
+ ErrorData.CreateForSymbol("m⁺⁻¹"),
+ ErrorData.CreateForSymbol("m+⁻¹"),
+ ErrorData.CreateForSymbol("m^¹/s⁻²"),
+ };
+ }
+}
diff --git a/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserTests.cs b/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserTests.cs
new file mode 100644
index 00000000..697f9404
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserTests.cs
@@ -0,0 +1,79 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using System;
+ using System.Collections.Generic;
+ using NUnit.Framework;
+
+ public class SymbolAndPowerParserTests
+ {
+ [TestCaseSource(nameof(HappyPaths))]
+ public void ParseSuccess(ISuccessData data)
+ {
+ var pos = data.Start;
+ var actual = SymbolAndPowerReader.Read(data.Text, ref pos);
+ Assert.AreEqual(data.Expected, actual);
+ Assert.AreEqual(data.ExpectedEnd, pos);
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void ParseError(IErrorData data)
+ {
+ var pos = data.Start;
+ Assert.Throws(() => SymbolAndPowerReader.Read(data.Text, ref pos));
+ Assert.AreEqual(data.ExpectedEnd, pos);
+ }
+
+ [TestCaseSource(nameof(HappyPaths))]
+ public void TryParseSuccess(ISuccessData data)
+ {
+ var pos = data.Start;
+ SymbolAndPower actual;
+ var success = SymbolAndPowerReader.TryRead(data.Text, ref pos, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(data.Expected, actual);
+ Assert.AreEqual(data.ExpectedEnd, pos);
+ }
+
+ [TestCaseSource(nameof(Errors))]
+ public void TryParseError(IErrorData data)
+ {
+ var pos = data.Start;
+ SymbolAndPower sap;
+ var success = SymbolAndPowerReader.TryRead(data.Text, ref pos, out sap);
+ Assert.AreEqual(false, success);
+ Assert.AreEqual(default(SymbolAndPower), sap);
+ Assert.AreEqual(data.ExpectedEnd, pos);
+ }
+
+ private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹";
+
+ private static readonly IReadOnlyList> HappyPaths = new[]
+ {
+ SuccessData.Create("m", 0, new SymbolAndPower("m", 1), 1),
+ SuccessData.Create(" m", 1, new SymbolAndPower("m", 1), 2),
+ SuccessData.Create("m^1", 0, new SymbolAndPower("m", 1), 3),
+ SuccessData.Create(" m ^ 1", 1, new SymbolAndPower("m", 1), 6),
+ SuccessData.Create("m⁻¹", 0, new SymbolAndPower("m", -1), 3),
+ SuccessData.Create("m^-1", 0, new SymbolAndPower("m", -1), 4),
+ SuccessData.Create("m¹", 0, new SymbolAndPower("m", 1), 2),
+ SuccessData.Create("m^2", 0, new SymbolAndPower("m", 2), 3),
+ SuccessData.Create("m^-2", 0, new SymbolAndPower("m", -2), 4),
+ SuccessData.Create("m²", 0, new SymbolAndPower("m", 2), 2),
+ SuccessData.Create("m⁻²", 0, new SymbolAndPower("m", -2), 3),
+ SuccessData.Create("m¹", 0, new SymbolAndPower("m", 1), 2),
+ SuccessData.Create("m³", 0, new SymbolAndPower("m", 3), 2),
+ SuccessData.Create("°", 0, new SymbolAndPower("°", 1), 1)
+ };
+
+ private static readonly IReadOnlyList> Errors = new[]
+ {
+ ErrorData.Create("m¹²", 0),
+ ErrorData.Create("m⁻¹²", 0),
+ ErrorData.Create("m⁻⁻2", 0),
+ ErrorData.Create("m^12", 0),
+ ErrorData.Create("m^-12", 0),
+ ErrorData.Create("m^--2", 0),
+ //ErrorData.Create("m-", 0),
+ };
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Internals/Parsing/UnitParserTests.cs b/Gu.Units.Tests/Internals/Parsing/UnitParserTests.cs
new file mode 100644
index 00000000..ee7fd34a
--- /dev/null
+++ b/Gu.Units.Tests/Internals/Parsing/UnitParserTests.cs
@@ -0,0 +1,70 @@
+namespace Gu.Units.Tests.Internals.Parsing
+{
+ using NUnit.Framework;
+
+ public class UnitParserTests
+ {
+ private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; // keeping this here for copy pasting
+
+ [TestCase("m")]
+ public void ParseMetres(string s)
+ {
+ var actual = LengthUnit.Parse(s);
+ var expected = LengthUnit.Metres;
+ Assert.AreEqual(expected, actual);
+
+ actual = UnitParser.Parse(s);
+ Assert.AreEqual(expected, actual);
+
+ var success = LengthUnit.TryParse(s, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expected, actual);
+
+ success = UnitParser.TryParse(s, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestCase("m^2")]
+ [TestCase("m²")]
+ [TestCase("m⁺²")]
+ public void ParseSquareMetres(string s)
+ {
+ var actual = AreaUnit.Parse(s);
+ var expected = AreaUnit.SquareMetres;
+ Assert.AreEqual(expected, actual);
+
+ actual = UnitParser.Parse(s);
+ Assert.AreEqual(expected, actual);
+
+ var success = AreaUnit.TryParse(s, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expected, actual);
+
+ success = UnitParser.TryParse(s, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestCase("mm^2")]
+ [TestCase("mm²")]
+ [TestCase("mm⁺²")]
+ public void ParseSquareMillimetres(string s)
+ {
+ var actual = AreaUnit.Parse(s);
+ var squareMillimetres = AreaUnit.SquareMillimetres;
+ Assert.AreEqual(squareMillimetres, actual);
+
+ actual = UnitParser.Parse(s);
+ Assert.AreEqual(squareMillimetres, actual);
+
+ var success = AreaUnit.TryParse(s, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(squareMillimetres, actual);
+
+ success = UnitParser.TryParse(s, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(squareMillimetres, actual);
+ }
+ }
+}
diff --git a/Gu.Units.Tests/QuantityTests.cs b/Gu.Units.Tests/LengthTests.cs
similarity index 67%
rename from Gu.Units.Tests/QuantityTests.cs
rename to Gu.Units.Tests/LengthTests.cs
index 0687e6b3..8be45df0 100644
--- a/Gu.Units.Tests/QuantityTests.cs
+++ b/Gu.Units.Tests/LengthTests.cs
@@ -1,11 +1,14 @@
namespace Gu.Units.Tests
{
+ using System.Globalization;
+ using System.Threading;
+ using Internals.Parsing;
using NUnit.Framework;
///
/// Just testing for length assuming the other generated units will work.
///
- public class QuantityTests
+ public class LengthTests
{
[Test]
public void Equality()
@@ -18,9 +21,9 @@ public void Equality()
Assert.IsFalse(metres1 != metres2);
}
- [TestCase(1, 2)]
- [TestCase(1, 1)]
- [TestCase(2, 1)]
+ [TestCase(1.0, 2.0)]
+ [TestCase(1.0, 1.0)]
+ [TestCase(2.0, 1.0)]
public void Compare(double d1, double d2)
{
var metres1 = Length.FromMetres(d1);
@@ -38,7 +41,7 @@ public void Addition()
var l2 = Length.FromCentimetres(1);
var sums = new[]
{
- l1 + l2,
+ l1 + l2,
l2 + l1
};
var expected = Length.FromCentimetres(101);
@@ -66,7 +69,7 @@ public void Multiplication()
var l = Length.FromMetres(1);
var prods = new[]
{
- l*2,
+ l*2,
2*l
};
var expected = Length.FromMetres(2);
@@ -86,5 +89,25 @@ public void Division()
Assert.IsInstanceOf(divided);
Assert.AreEqual(expected, divided);
}
+
+ [Test]
+ public new void ToString()
+ {
+ var length = Length.FromMetres(1.2);
+
+ using (Thread.CurrentThread.UsingTempCulture(CultureInfo.InvariantCulture))
+ {
+ Assert.AreEqual("1.2\u00A0m", length.ToString());
+ Assert.AreEqual("120\u00A0cm", length.ToString(LengthUnit.Centimetres));
+ Assert.AreEqual(" 1200.0 mm ", length.ToString(" F1 mm "));
+ Assert.AreEqual(" F1 {unit: null}", length.ToString(" F1 "));
+ Assert.AreEqual("12.0\u00A0dm", length.ToString("F1", LengthUnit.Decimetres));
+ }
+
+ var sv = CultureInfo.GetCultureInfo("sv-SE");
+ Assert.AreEqual("1,2\u00A0m", length.ToString(sv));
+ Assert.AreEqual(" 1200,0 mm ", length.ToString(" F1 mm ", sv));
+ Assert.AreEqual("1200,0\u00A0mm", length.ToString("F1", LengthUnit.Millimetres, sv));
+ }
}
}
diff --git a/Gu.Units.Tests/ParserTests.cs b/Gu.Units.Tests/ParserTests.cs
deleted file mode 100644
index df5c9012..00000000
--- a/Gu.Units.Tests/ParserTests.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-namespace Gu.Units.Tests
-{
- using System;
- using System.Globalization;
- using System.Text.RegularExpressions;
- using System.Threading;
-
- using Gu.Units.Tests.Sources;
-
- using NUnit.Framework;
-
- public class ParserTests
- {
- [TestCase("1m", new[] { "sv-se", "en-us" }, 1)]
- [TestCase("-1m", new[] { "sv-se", "en-us" }, -1)]
- [TestCase("1.2m", new[] { "en-us" }, 1.2)]
- [TestCase("1.2m", new[] { "en-us" }, 1.2)]
- [TestCase("1,2m", new[] { "sv-se" }, 1.2)]
- [TestCase("-1m", new[] { "sv-se", "en-us" }, -1)]
- [TestCase("1e3m", new[] { "sv-se", "en-us" }, 1e3)]
- [TestCase("1E3m", new[] { "sv-se", "en-us" }, 1e3)]
- [TestCase("1e+3m", new[] { "sv-se", "en-us" }, 1e+3)]
- [TestCase("1E+3m", new[] { "sv-se", "en-us" }, 1E+3)]
- [TestCase("1.2e-3m", new[] { "en-us" }, 1.2e-3)]
- [TestCase("1.2E-3m", new[] { "en-us" }, 1.2e-3)]
- [TestCase(" 1m", new[] { "sv-se", "en-us" }, 1)]
- [TestCase("1 m", new[] { "sv-se", "en-us" }, 1)]
- [TestCase("1m ", new[] { "sv-se", "en-us" }, 1)]
- [TestCase("1mm", new[] { "sv-se", "en-us" }, 1e-3)]
- [TestCase("1cm", new[] { "sv-se", "en-us" }, 1e-2)]
- public void ParseLength(string s, string[] cultures, double expected)
- {
- foreach (var culture in cultures)
- {
- var cultureInfo = CultureInfo.GetCultureInfo(culture);
- var length = Parser.Parse(s, Length.From, NumberStyles.Float, cultureInfo);
- Assert.AreEqual(expected, length.Metres);
- }
- }
-
- [TestCaseSource(typeof(ParseProvider))]
- public void Roundtrip(ParseProvider.ParseData data)
- {
- Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
- var actual = data.ParseMethod(data.StringValue);
- var expected = data.Quantity;
- Assert.AreEqual(expected, actual);
- var s = actual.ToString();
- var roundtripped = data.ParseMethod(s);
- Assert.AreEqual(expected, roundtripped);
- }
-
- [TestCase("mm^2")]
- [TestCase("mm²")]
- public void AreaUnit_Parse(string s)
- {
- var actual = AreaUnit.Parse(s);
- Assert.AreEqual(AreaUnit.SquareMillimetres, actual);
- }
-
- [TestCaseSource(typeof(TokenSource))]
- public void Tokenize(TokenSource.TokenData data)
- {
- var text = data.Text;
- if (data.Tokens == null)
- {
- Assert.Throws(() => Parser.TokenizeUnit(text));
- }
- else
- {
- var actual = Parser.TokenizeUnit(text);
- Console.WriteLine("expected: {0}", data.ToString(data.Tokens));
- Console.WriteLine("actual: {0}", data.ToString(actual));
- CollectionAssert.AreEqual(data.Tokens, actual);
- }
- }
-
- [TestCase("1.0cm", "sv-se")]
- [TestCase("1,0cm", "en-us")]
- public void Exceptions(string s, string culture)
- {
- var cultureInfo = CultureInfo.GetCultureInfo(culture);
- Assert.Throws(() => Parser.Parse(s, Length.From, NumberStyles.Float, cultureInfo));
- }
- }
-}
diff --git a/Gu.Units.Tests/ParserTests.cs.orig b/Gu.Units.Tests/ParserTests.cs.orig
deleted file mode 100644
index 3b3973df..00000000
--- a/Gu.Units.Tests/ParserTests.cs.orig
+++ /dev/null
@@ -1,129 +0,0 @@
-namespace Gu.Units.Tests
-{
- using System.Globalization;
- using System.Text.RegularExpressions;
- using NUnit.Framework;
-
- public class ParserTests
- {
-<<<<<<< HEAD
- [TestCase("1m", 1)]
- [TestCase("-1m", -1)]
- [TestCase("1.2m", 1.2)]
- [TestCase("1,2m", 1.2)]
- [TestCase("-1m", -1)]
- [TestCase("1e3m", 1e3)]
- [TestCase("-1e3m", -1e3)]
- [TestCase("1e+3m", 1e+3)]
- [TestCase("1E+3m", 1E+3)]
- [TestCase("-1e-3m", -1e-3)]
- [TestCase(" 1m", 1)]
- [TestCase("1m ", 1)]
- [TestCase("1 m", 1)]
- [TestCase(" 1 m", 1)]
- [TestCase("1 m ", 1)]
- [TestCase(" 1 m ", 1)]
- [TestCase(" 1 m ", 1)]
- [TestCase("1m ", 1)]
- [TestCase("1mm", 1e-3)]
- [TestCase("1cm", 1e-2)]
- public void ParseLength(string s, double expected)
-=======
- [TestCase("1m", new[] { "sv-se", "en-us" }, 1)]
- [TestCase("1.2m", new[] { "en-us" }, 1.2)]
- [TestCase("1,2m", new[] { "sv-se" }, 1.2)]
- [TestCase("-1m", new[] { "sv-se", "en-us" }, -1)]
- [TestCase("1e3m", new[] { "sv-se", "en-us" }, 1e3)]
- [TestCase("1E3m", new[] { "sv-se", "en-us" }, 1e3)]
- [TestCase("1e+3m", new[] { "sv-se", "en-us" }, 1e+3)]
- [TestCase("1E+3m", new[] { "sv-se", "en-us" }, 1E+3)]
- [TestCase("1.2e-3m", new[] { "en-us" }, 1.2e-3)]
- [TestCase("1.2E-3m", new[] { "en-us" }, 1.2e-3)]
- [TestCase(" 1m", new[] { "sv-se", "en-us" }, 1)]
- [TestCase("1 m", new[] { "sv-se", "en-us" }, 1)]
- [TestCase("1m ", new[] { "sv-se", "en-us" }, 1)]
- [TestCase("1mm", new[] { "sv-se", "en-us" }, 1e-3)]
- [TestCase("1cm", new[] { "sv-se", "en-us" }, 1e-2)]
- public void ParseLength(string s, string[] cultures, double expected)
->>>>>>> Added test for double pattern
- {
- foreach (var culture in cultures)
- {
- var cultureInfo = CultureInfo.GetCultureInfo(culture);
- var length = Parser.Parse(s, Length.From, cultureInfo);
- Assert.AreEqual(expected, length.Meters);
- }
- }
-
- [TestCase("1", 1)]
- [TestCase(".1", .1)]
- [TestCase("1.2", 1.2)]
- [TestCase("1.2E+3", 1.2E+3)]
- [TestCase("1.2e+3", 1.2E+3)]
- [TestCase("1.2E3", 1.2E3)]
- [TestCase("1.2e3", 1.2E3)]
- [TestCase("1.2E-3", 1.2E-3)]
- [TestCase("1.2e-3", 1.2E-3)]
- public void DoublePattern(string s, double expected)
- {
- Assert.IsTrue(Regex.IsMatch(s, Parser.DoublePattern));
- Assert.AreEqual(expected, double.Parse(s, CultureInfo.InvariantCulture));
- }
-
- [TestCase("1m", 1)]
- public void ParseMeters(string s, double expected)
- {
- var length = UnitParser.Parse(s, Length.From);
- Assert.AreEqual(expected, length.Meters);
- }
-
- [TestCase("1cm", 1)]
- public void ParseCentimeters(string s, double expected)
- {
- var length = UnitParser.Parse(s, Length.From);
- Assert.AreEqual(expected, length.Centimeters);
- }
-
- [TestCase("1mm", 1)]
- public void ParseMillimeters(string s, double expected)
- {
- var length = UnitParser.Parse(s, Length.From);
- Assert.AreEqual(expected, length.Millimeters);
- }
-
- [TestCase("1h", 1)]
- public void ParseHours(string s, double expected)
- {
- var time = UnitParser.Parse(s, Time.From);
- Assert.AreEqual(expected, time.Hours);
- }
-
- [TestCase("1s", 1)]
- public void ParseSeconds(string s, double expected)
- {
- var time = UnitParser.Parse(s, Time.From);
- Assert.AreEqual(expected, time.Seconds);
- }
-
- [TestCase("1ms", 1)]
- public void ParseMilliseconds(string s, double expected)
- {
- var time = UnitParser.Parse(s, Time.From);
- Assert.AreEqual(expected, time.MilliSeconds);
- }
-
- [TestCase("1kN", 1)]
- public void ParseKilonewtons(string s, double expected)
- {
- var force = UnitParser.Parse(s, Force.From);
- Assert.AreEqual(expected, force.KiloNewtons);
- }
-
- [TestCase("1N", 1)]
- public void ParseNewtons(string s, double expected)
- {
- var force = UnitParser.Parse(s, Force.From);
- Assert.AreEqual(expected, force.Newtons);
- }
- }
-}
diff --git a/Gu.Units.Tests/Samples/Samples.cs b/Gu.Units.Tests/Samples/Samples.cs
index 35d566c3..c957ae15 100644
--- a/Gu.Units.Tests/Samples/Samples.cs
+++ b/Gu.Units.Tests/Samples/Samples.cs
@@ -39,10 +39,11 @@ public void ConversionSample()
[TestCase("1.2m^1/s¹")]
public void ParsingSample(string s)
{
- var speed = Speed.Parse(s,CultureInfo.InvariantCulture);
- Assert.AreEqual(Speed.FromMetresPerSecond(1.2), Speed.Parse(s));
+ var speed = Speed.Parse(s, CultureInfo.InvariantCulture);
+ Assert.AreEqual(Speed.FromMetresPerSecond(1.2), speed);
+
Assert.IsTrue(Speed.TryParse(s, CultureInfo.InvariantCulture, out speed));
- Assert.AreEqual(Speed.FromMetresPerSecond(1.2), Speed.Parse(s));
+ Assert.AreEqual(Speed.FromMetresPerSecond(1.2), speed);
}
}
}
diff --git a/Gu.Units.Tests/Sandbox/SandboxTests.cs b/Gu.Units.Tests/Sandbox/SandboxTests.cs
deleted file mode 100644
index 8fd4b086..00000000
--- a/Gu.Units.Tests/Sandbox/SandboxTests.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-namespace Gu.Units.Tests.Sandbox
-{
- using System;
- using System.Diagnostics;
-
- using NUnit.Framework;
-
- public class SandboxTests
- {
- [Test, Explicit]
- public void PerformanceDoublesThenLengths()
- {
- var sw = Stopwatch.StartNew();
- var n = 10000000;
- double sum1 = 0;
- for (double i = 0; i < n; i++)
- {
- sum1 += i;
- }
- Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum1.GetType().Name, sw.ElapsedMilliseconds);
- sw.Restart();
- var sum2 = new Length(1, LengthUnit.Metres);
- for (var i = 0; i < n; i++)
- {
- sum2 += new Length(i, LengthUnit.Metres);
- }
- Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum2.GetType().Name, sw.ElapsedMilliseconds);
- //Summing 1E+007 Doubles took: 11 ms
- //Summing 1E+007 Lengths took: 8 ms
- }
-
- [Test, Explicit]
- public void PerformanceIntsThenLengths()
- {
- var sw = Stopwatch.StartNew();
- var n = 10000000;
- int sum1 = 0;
- for (int i = 0; i < n; i++)
- {
- sum1 += i;
- }
- Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum1.GetType().Name, sw.ElapsedMilliseconds);
- sw.Restart();
- var sum2 = new Length(1, LengthUnit.Metres);
- for (var i = 0; i < n; i++)
- {
- sum2 += new Length(i, LengthUnit.Metres);
- }
- Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum2.GetType().Name, sw.ElapsedMilliseconds);
- //Summing 1E+007 Int32s took: 5 ms
- //Summing 1E+007 Lengths took: 8 ms
- }
-
- [Test, Explicit]
- public void PerformanceLengthsThenDoubles()
- {
- var sw = Stopwatch.StartNew();
- var n = 10000000;
- var sum2 = new Length(1, LengthUnit.Metres);
- for (var i = 0; i < n; i++)
- {
- sum2 += new Length(i, LengthUnit.Metres);
- }
- Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum2.GetType().Name, sw.ElapsedMilliseconds);
- sw.Restart();
- double sum1 = 0;
- for (double i = 0; i < n; i++)
- {
- sum1 += i;
- }
- Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum1.GetType().Name, sw.ElapsedMilliseconds);
- //Summing 1E+007 Lengths took: 8 ms
- //Summing 1E+007 Doubles took: 11 ms
- }
- }
-}
diff --git a/Gu.Units.Tests/Sources/ConversionProvider.cs b/Gu.Units.Tests/Sources/ConversionProvider.cs
index bd31857e..34401f4b 100644
--- a/Gu.Units.Tests/Sources/ConversionProvider.cs
+++ b/Gu.Units.Tests/Sources/ConversionProvider.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Threading;
+ using Internals.Parsing;
public class ConversionProvider : List>
{
@@ -54,12 +55,14 @@ public class Conversion : IConversion
public Conversion(string from, string to, Func parser)
{
- Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
- this._parser = parser;
- this.From = @from;
- FromQuantity = Parse(from);
- this.To = to;
- ToQuantity = Parse(to);
+ using (Thread.CurrentThread.UsingTempCulture(CultureInfo.InvariantCulture))
+ {
+ this._parser = parser;
+ this.From = @from;
+ FromQuantity = Parse(from);
+ this.To = to;
+ ToQuantity = Parse(to);
+ }
}
public T Parse(string s)
diff --git a/Gu.Units.Tests/Sources/TokenSource.cs b/Gu.Units.Tests/Sources/TokenSource.cs
deleted file mode 100644
index d174996f..00000000
--- a/Gu.Units.Tests/Sources/TokenSource.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-namespace Gu.Units.Tests.Sources
-{
- using System.Collections.Generic;
-
- public class TokenSource : List
- {
- private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹";
- public TokenSource()
- {
- Add(new TokenData("m", new SymbolAndPower("m", 1)));
- Add(new TokenData(" m ", new SymbolAndPower("m", 1)));
- Add(new TokenData("m^2", new SymbolAndPower("m", 2)));
- Add(new TokenData(" m ^ 2", new SymbolAndPower("m", 2)));
- Add(new TokenData(" m ^ -2", new SymbolAndPower("m", -2)));
- Add(new TokenData("m^1/s^2", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)));
- Add(new TokenData("m¹/s²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)));
- Add(new TokenData("m⁺¹/s²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)));
- Add(new TokenData("m⁺¹/s²*g", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2), new SymbolAndPower("g", -1)));
- Add(new TokenData("m¹⋅s⁻²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)));
- Add(new TokenData("m⁻¹⋅s⁻²", new SymbolAndPower("m", -1), new SymbolAndPower("s", -2)));
- Add(new TokenData("m⁻¹/s⁻²", null));
- Add(new TokenData("m⁻⁻¹/s⁻²", null));
- Add(new TokenData("m⁺⁻¹/s⁻²", null));
- Add(new TokenData("m+⁻¹/s⁻²", null));
- Add(new TokenData("m^¹/s⁻²", null));
- }
-
- public class TokenData
- {
- public readonly string Text;
- public readonly IReadOnlyList Tokens;
-
- public TokenData(string text, params SymbolAndPower[] tokens)
- {
- Text = text;
- Tokens = tokens;
- }
-
- public override string ToString()
- {
- var tokens = "Exception";
- if (Tokens != null)
- {
- tokens = ToString(Tokens);
- }
- return string.Format("Text: {0}, Tokens: {1}", Text, Tokens);
- }
-
- public string ToString(IEnumerable tokens)
- {
- return string.Join(", ", tokens);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Gu.Units.Tests/SymbolAndPowerTests.cs b/Gu.Units.Tests/SymbolAndPowerTests.cs
deleted file mode 100644
index 1b19b803..00000000
--- a/Gu.Units.Tests/SymbolAndPowerTests.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-namespace Gu.Units.Tests
-{
- using System;
- using NUnit.Framework;
-
- public class SymbolAndPowerTests
- {
- private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹";
-
- internal static readonly Sign[] Signs = { Sign.Positive, Sign.Negative };
-
- [TestCase("m", "m", 1)]
- [TestCase(" m", "m", 1)]
- [TestCase("m^1", "m", 1)]
- [TestCase(" m ^ 1", "m", 1)]
- [TestCase("m⁻¹", "m", -1)]
- [TestCase("m^-1", "m", -1)]
- [TestCase("m¹", "m", 1)]
- [TestCase("m^2", "m", 2)]
- [TestCase("m^-2", "m", -2)]
- [TestCase("m²", "m", 2)]
- [TestCase("m¹", "m", 1)]
- [TestCase("m³", "m", 3)]
- [TestCase("m⁹", "m", 9)]
- [TestCase("kg⁹", "kg", 9)]
- [TestCase("°", "°", 1)]
- public void SimpleSymbol(string s, string symbol, int power)
- {
- foreach (var si in Signs)
- {
- Sign sign = si;
- int pos = 0;
- SymbolAndPower sap;
- if (power < 0 && sign == Sign.Negative)
- {
- Assert.Throws(() => SymbolAndPower.Read(s, ref pos, ref sign));
- return;
- }
- sap = SymbolAndPower.Read(s, ref pos, ref sign);
- Assert.AreEqual(symbol, sap.Symbol);
- if (si == Sign.Negative)
- {
- Assert.AreEqual(-1 * power, sap.Power);
- }
- else
- {
- Assert.AreEqual(power, sap.Power);
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Gu.Units.Tests/Helpers/DummyUnit.cs b/Gu.Units.Tests/TestHelpers/DummyUnit.cs
similarity index 89%
rename from Gu.Units.Tests/Helpers/DummyUnit.cs
rename to Gu.Units.Tests/TestHelpers/DummyUnit.cs
index eb03dfdd..bdf0883b 100644
--- a/Gu.Units.Tests/Helpers/DummyUnit.cs
+++ b/Gu.Units.Tests/TestHelpers/DummyUnit.cs
@@ -3,6 +3,9 @@ namespace Gu.Units.Tests
public class DummyUnit : IUnit
{
public string Symbol { get; private set; }
+
+ public IUnit SiUnit { get; }
+
public double ToSiUnit(double value)
{
return 10 * value;
diff --git a/Gu.Units.Tests/UnitParserTests.cs.orig b/Gu.Units.Tests/UnitParserTests.cs.orig
deleted file mode 100644
index 64c71cfe..00000000
--- a/Gu.Units.Tests/UnitParserTests.cs.orig
+++ /dev/null
@@ -1,98 +0,0 @@
-namespace Gu.Units.Tests
-{
- using NUnit.Framework;
-
- public class UnitParserTests
- {
- [TestCase("1m", 1)]
-<<<<<<< HEAD
- [TestCase("-1m", -1)]
- [TestCase("1e3m", 1e3)]
- [TestCase("-1e3m", -1e3)]
- [TestCase("1e+3m", 1e+3)]
- [TestCase("1e-3m", 1e-3)]
- [TestCase("-1e-3m", -1e-3)]
-=======
- [TestCase("1.2m", 1.2)]
- [TestCase("1,2m", 1.2)]
- [TestCase("-1m", -1)]
- [TestCase("1e3m", 1e3)]
- [TestCase("1E3m", 1e3)]
- [TestCase("1e+3m", 1e+3)]
- [TestCase("1E+3m", 1E+3)]
- [TestCase("1.2e-3m", 1.2e-3)]
- [TestCase("1.2E-3m", 1.2e-3)]
->>>>>>> Changed conversion signature in IUnit
- [TestCase(" 1m", 1)]
- [TestCase("1m ", 1)]
- [TestCase("1 m", 1)]
- [TestCase(" 1 m", 1)]
- [TestCase("1 m ", 1)]
- [TestCase(" 1 m ", 1)]
- [TestCase(" 1 m ", 1)]
- [TestCase("1m ", 1)]
- [TestCase("1mm", 1e-3)]
- [TestCase("1cm", 1e-2)]
- public void ParseLength(string s, double expected)
- {
- var length = UnitParser.Parse(s, Length.From);
- Assert.AreEqual(expected, length.Meters);
- }
-
- [TestCase("1m", 1)]
- public void ParseMeters(string s, double expected)
- {
- var length = UnitParser.Parse(s, Length.From);
- Assert.AreEqual(expected, length.Meters);
- }
-
- [TestCase("1cm", 1)]
- public void ParseCentimeters(string s, double expected)
- {
- var length = UnitParser.Parse(s, Length.From);
- Assert.AreEqual(expected, length.Centimeters);
- }
-
- [TestCase("1mm", 1)]
- public void ParseMillimeters(string s, double expected)
- {
- var length = UnitParser.Parse(s, Length.From);
- Assert.AreEqual(expected, length.Millimeters);
- }
-
- [TestCase("1h", 1)]
- public void ParseHours(string s, double expected)
- {
- var time = UnitParser.Parse(s, Time.From);
- Assert.AreEqual(expected, time.Hours);
- }
-
- [TestCase("1s", 1)]
- public void ParseSeconds(string s, double expected)
- {
- var time = UnitParser.Parse(s, Time.From);
- Assert.AreEqual(expected, time.Seconds);
- }
-
- [TestCase("1ms", 1)]
- public void ParseMilliseconds(string s, double expected)
- {
- var time = UnitParser.Parse(s, Time.From);
- Assert.AreEqual(expected, time.MilliSeconds);
- }
-
- [TestCase("1kN", 1)]
- public void ParseKilonewtons(string s, double expected)
- {
- var force = UnitParser.Parse(s, Force.From);
- Assert.AreEqual(expected, force.KiloNewtons);
- }
-
- [TestCase("1N", 1)]
- public void ParseNewtons(string s, double expected)
- {
- var force = UnitParser.Parse(s, Force.From);
- Assert.AreEqual(expected, force.Newtons);
- }
- }
-}
diff --git a/Gu.Units.Tests/UnitTests.cs b/Gu.Units.Tests/UnitTests.cs
index c6475b31..cd5af042 100644
--- a/Gu.Units.Tests/UnitTests.cs
+++ b/Gu.Units.Tests/UnitTests.cs
@@ -12,8 +12,7 @@ public class UnitTests
public void Roundtrip(IUnit unit)
{
var s = unit.ToString();
- var parseMethod = unit.GetType()
- .GetMethod("Parse", BindingFlags.Static | BindingFlags.Public);
+ var parseMethod = unit.GetType().GetMethod("Parse", BindingFlags.Static | BindingFlags.Public);
var roundtripped = (IUnit)parseMethod.Invoke(null, new object[] { s });
Assert.AreEqual(unit, roundtripped);
}
diff --git a/Gu.Units.Wpf.Demo/App.xaml b/Gu.Units.Wpf.Demo/App.xaml
index c74bc2cb..ab0bc63e 100644
--- a/Gu.Units.Wpf.Demo/App.xaml
+++ b/Gu.Units.Wpf.Demo/App.xaml
@@ -4,6 +4,29 @@
xmlns:local="clr-namespace:Gu.Units.Wpf.Demo"
StartupUri="MainWindow.xaml">
-
+
+
+
+
+
+
+
+
diff --git a/Gu.Units.Wpf.Demo/DoubleControl.cs b/Gu.Units.Wpf.Demo/DoubleControl.cs
new file mode 100644
index 00000000..3ac29653
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/DoubleControl.cs
@@ -0,0 +1,22 @@
+namespace Gu.Units.Wpf.Demo
+{
+ using System.Windows;
+ using System.Windows.Controls;
+ using System.Windows.Data;
+
+ // Dummy control for testing binding of doubles.
+ public class DoubleControl : Control
+ {
+ public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
+ "Value",
+ typeof(double),
+ typeof(DoubleControl),
+ new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault) {DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged});
+
+ public double Value
+ {
+ get { return (double)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj b/Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj
index 3d525787..193033af 100644
--- a/Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj
+++ b/Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj
@@ -48,7 +48,24 @@
MSBuild:Compile
Designer
+
+
+ InputOptionsView.xaml
+
+
+ StringFormatView.xaml
+
+
+ SymbolOptions.xaml
+
+
+ UnitsOnlyView.xaml
+
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
@@ -61,6 +78,18 @@
MainWindow.xaml
Code
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
diff --git a/Gu.Units.Wpf.Demo/InputOptionsView.xaml b/Gu.Units.Wpf.Demo/InputOptionsView.xaml
new file mode 100644
index 00000000..6ce651dd
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/InputOptionsView.xaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gu.Units.Wpf.Demo/InputOptionsView.xaml.cs b/Gu.Units.Wpf.Demo/InputOptionsView.xaml.cs
new file mode 100644
index 00000000..190f7120
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/InputOptionsView.xaml.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Gu.Units.Wpf.Demo
+{
+ ///
+ /// Interaction logic for InputOptionsView.xaml
+ ///
+ public partial class InputOptionsView : UserControl
+ {
+ public InputOptionsView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Demo/MainWindow.xaml b/Gu.Units.Wpf.Demo/MainWindow.xaml
index 8b1ab3a1..29bf8764 100644
--- a/Gu.Units.Wpf.Demo/MainWindow.xaml
+++ b/Gu.Units.Wpf.Demo/MainWindow.xaml
@@ -5,30 +5,27 @@
xmlns:local="clr-namespace:Gu.Units.Wpf.Demo"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:units="http://Gu.com/Units"
+ xmlns:units1="clr-namespace:Gu.Units;assembly=Gu.Units"
Title="MainWindow"
Width="525"
Height="350"
+ DataContext="{x:Static local:ViewModel.Instance}"
mc:Ignorable="d">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gu.Units.Wpf.Demo/StringFormatView.xaml b/Gu.Units.Wpf.Demo/StringFormatView.xaml
new file mode 100644
index 00000000..ddb5862a
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/StringFormatView.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gu.Units.Wpf.Demo/StringFormatView.xaml.cs b/Gu.Units.Wpf.Demo/StringFormatView.xaml.cs
new file mode 100644
index 00000000..9ce1a57d
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/StringFormatView.xaml.cs
@@ -0,0 +1,15 @@
+namespace Gu.Units.Wpf.Demo
+{
+ using System.Windows.Controls;
+
+ ///
+ /// Interaction logic for StringFormatView.xaml
+ ///
+ public partial class StringFormatView : UserControl
+ {
+ public StringFormatView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Demo/SymbolOptions.xaml b/Gu.Units.Wpf.Demo/SymbolOptions.xaml
new file mode 100644
index 00000000..7d44d8a9
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/SymbolOptions.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gu.Units.Wpf.Demo/SymbolOptions.xaml.cs b/Gu.Units.Wpf.Demo/SymbolOptions.xaml.cs
new file mode 100644
index 00000000..e56cd2cb
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/SymbolOptions.xaml.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Gu.Units.Wpf.Demo
+{
+ ///
+ /// Interaction logic for SymbolOptions.xaml
+ ///
+ public partial class SymbolOptions : UserControl
+ {
+ public SymbolOptions()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml b/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml
new file mode 100644
index 00000000..93dabcf8
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml.cs b/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml.cs
new file mode 100644
index 00000000..fd65ee07
--- /dev/null
+++ b/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml.cs
@@ -0,0 +1,15 @@
+namespace Gu.Units.Wpf.Demo
+{
+ using System.Windows.Controls;
+
+ ///
+ /// Interaction logic for UnitsOnlyView.xaml
+ ///
+ public partial class UnitsOnlyView : UserControl
+ {
+ public UnitsOnlyView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Demo/ViewModel.cs b/Gu.Units.Wpf.Demo/ViewModel.cs
index 7d169057..88391611 100644
--- a/Gu.Units.Wpf.Demo/ViewModel.cs
+++ b/Gu.Units.Wpf.Demo/ViewModel.cs
@@ -6,7 +6,15 @@ namespace Gu.Units.Wpf.Demo
public class ViewModel : INotifyPropertyChanged
{
- private Length _length;
+ private Length _length = Length.FromMillimetres(1234.567);
+ private Speed speed = Speed.FromMetresPerSecond(1.2);
+ private Pressure pressure = Pressure.FromMegapascals(1.23);
+
+ public static readonly ViewModel Instance = new ViewModel();
+
+ private ViewModel()
+ {
+ }
public event PropertyChangedEventHandler PropertyChanged;
@@ -22,6 +30,30 @@ public Length Length
}
}
+ public Speed Speed
+ {
+ get { return this.speed; }
+ set
+ {
+ if (value.Equals(this.speed))
+ return;
+ this.speed = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public Pressure Pressure
+ {
+ get { return this.pressure; }
+ set
+ {
+ if (value.Equals(this.pressure))
+ return;
+ this.pressure = value;
+ OnPropertyChanged();
+ }
+ }
+
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
diff --git a/Gu.Units.Wpf.Tests/Class1.cs b/Gu.Units.Wpf.Tests/Class1.cs
deleted file mode 100644
index 8e69b133..00000000
--- a/Gu.Units.Wpf.Tests/Class1.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Gu.Units.Wpf.Tests
-{
- public class Class1
- {
- }
-}
diff --git a/Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj b/Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj
index 40311725..d77186a3 100644
--- a/Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj
+++ b/Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj
@@ -30,11 +30,22 @@
4
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+ {ea4bce51-a58d-4fa7-bba4-6f0056d753ca}
+ Gu.Units.Wpf
+
+
+ {f1f8b138-becc-4475-a7ab-b3019338bc7b}
+ Gu.Units
+
+
-
-
-
- ..\packages\NUnit\lib\net20\nunit.framework.dll
- True
- True
-
-
-
-
+
-
- ..\packages\NUnit\lib\net40\nunit.framework.dll
+
+ ..\packages\Moq\lib\net35\Moq.dll
True
True
-
+
-
- ..\packages\NUnit\lib\net45\nunit.framework.dll
+
+ ..\packages\Moq\lib\net40\Moq.dll
True
True
-
+
-
- ..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll
+
+ ..\packages\Moq\lib\sl5\Moq.Silverlight.dll
True
True
+
+
+ ..\packages\NUnit\lib\nunit.framework.dll
+ True
+ True
+
+
\ No newline at end of file
diff --git a/Gu.Units.Wpf.Tests/LengthConverterTests.convert.cs b/Gu.Units.Wpf.Tests/LengthConverterTests.convert.cs
new file mode 100644
index 00000000..96c47cda
--- /dev/null
+++ b/Gu.Units.Wpf.Tests/LengthConverterTests.convert.cs
@@ -0,0 +1,137 @@
+namespace Gu.Units.Wpf.Tests
+{
+ using System;
+ using System.Globalization;
+ using NUnit.Framework;
+
+ public partial class LengthConverterTests
+ {
+ [RequiresSTA]
+ public class Convert
+ {
+ [TestCase(typeof(string), 1.2)]
+ [TestCase(typeof(object), 1.2)]
+ [TestCase(typeof(double), 1.2)]
+ public void WithExplicitUnit(Type targetType, object expected)
+ {
+ var converter = new LengthConverter
+ {
+ Unit = LengthUnit.Centimetres,
+ };
+
+ var length = Length.FromMillimetres(12);
+ var actual = converter.Convert(length, targetType, null, null);
+ Assert.AreEqual(expected, actual);
+ }
+
+ private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹";
+ private const char MultiplyDot = '⋅';
+
+ [TestCase(typeof(string), SymbolFormat.SignedHatPowers, "1.2\u00A0m*s^-1")]
+ [TestCase(typeof(object), SymbolFormat.FractionHatPowers, "1.2\u00A0m/s")]
+ [TestCase(typeof(object), SymbolFormat.SignedSuperScript, "1.2\u00A0m⋅s⁻¹")]
+ [TestCase(typeof(object), SymbolFormat.FractionSuperScript, "1.2\u00A0m/s")]
+ public void WithUnitAndSymbolFormat(Type targetType, SymbolFormat format, object expected)
+ {
+ var converter = new SpeedConverter
+ {
+ Unit = SpeedUnit.MetresPerSecond,
+ UnitInput = UnitInput.SymbolRequired,
+ SymbolFormat = format
+ };
+
+ var length = Speed.FromMetresPerSecond(1.2);
+ var actual = converter.Convert(length, targetType, null, CultureInfo.InvariantCulture);
+ Assert.AreEqual(expected, actual);
+ }
+
+
+ [TestCase(typeof(string), UnitInput.ScalarOnly, 1.2)]
+ [TestCase(typeof(string), UnitInput.SymbolAllowed, 1.2)]
+ [TestCase(typeof(string), UnitInput.SymbolRequired, "1.2\u00A0m/s")]
+ public void WithUnitAndUnitInput(Type targetType, UnitInput inputOptions, object expected)
+ {
+ var converter = new SpeedConverter
+ {
+ Unit = SpeedUnit.MetresPerSecond,
+ UnitInput = inputOptions
+ };
+
+ var length = Speed.FromMetresPerSecond(1.2);
+ var actual = converter.Convert(length, targetType, null, CultureInfo.InvariantCulture);
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestCase(typeof(string), "F1 m", "en-us", "1.2 m")]
+ [TestCase(typeof(object), "F1 m", "en-us", "1.2 m")]
+ [TestCase(typeof(double), "F1 m", "en-us", 1.234)]
+ public void WithStringFormat(Type targetType, string stringFormat, string culture, object expected)
+ {
+ var converter = new LengthConverter
+ {
+ StringFormat = stringFormat
+ };
+
+ var length = Length.FromMillimetres(1234);
+ var actual = converter.Convert(length, targetType, null, CultureInfo.GetCultureInfo(culture));
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestCase(typeof(string), "F1 m")]
+ [TestCase(typeof(object), "F1 m")]
+ public void WithBindingStringFormat(Type targetType, string stringFormat)
+ {
+ var converter = new LengthConverter();
+ var providerMock = new ServiceProviderMock
+ {
+ BindingStringFormat = stringFormat
+ };
+
+ converter.ProvideValue(providerMock.Object);
+ var length = Length.FromMillimetres(1234);
+ var actual = converter.Convert(length, targetType, null, null);
+ Assert.AreEqual(length, actual);
+ }
+
+ [Test]
+ public void WithBindingStringFormatAndExplicitStringFormat()
+ {
+ var converter = new LengthConverter
+ {
+ StringFormat = "F1 mm"
+ };
+
+ var providerMock = new ServiceProviderMock
+ {
+ BindingStringFormat = "F2 cm"
+ };
+
+ converter.ProvideValue(providerMock.Object);
+ var length = Length.FromMillimetres(1234);
+ Gu.Units.Wpf.Is.DesignMode = true;
+ var ex = Assert.Throws(()=> converter.Convert(length, typeof(string), null, null));
+ var expected = "Both Binding.StringFormat and StringFormat are set.";
+ Assert.AreEqual(expected, ex.Message);
+
+ Gu.Units.Wpf.Is.DesignMode = false;
+ var convert = converter.Convert(length, typeof (string), null, null);
+ Assert.AreEqual(expected, convert);
+ }
+
+ [TestCase(typeof(string), "")]
+ [TestCase(typeof(object), null)]
+ [TestCase(typeof(double), null)]
+ public void Null(Type targetType, object expected)
+ {
+ var converter = new LengthConverter
+ {
+ Unit = LengthUnit.Centimetres,
+ UnitInput = UnitInput.ScalarOnly
+ };
+
+ var actual = converter.Convert(null, targetType, null, null);
+ Assert.AreEqual(expected, actual);
+ }
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Tests/LengthConverterTests.convertback.cs b/Gu.Units.Wpf.Tests/LengthConverterTests.convertback.cs
new file mode 100644
index 00000000..fed7f8a9
--- /dev/null
+++ b/Gu.Units.Wpf.Tests/LengthConverterTests.convertback.cs
@@ -0,0 +1,53 @@
+namespace Gu.Units.Wpf.Tests
+{
+ using System.Globalization;
+ using NUnit.Framework;
+
+ [TestFixture]
+ public partial class LengthConverterTests
+ {
+ public class ConvertBack
+ {
+ [TestCase("1.2")]
+ [TestCase(1.2)]
+ public void WithExplicitUnitScalarOnly(object value)
+ {
+ var converter = new LengthConverter
+ {
+ Unit = LengthUnit.Centimetres,
+ UnitInput = UnitInput.ScalarOnly
+ };
+
+ var actual = converter.ConvertBack(value, typeof(Length), null, CultureInfo.InvariantCulture);
+ var expected = Length.FromCentimetres(1.2);
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestCase("1.2", UnitInput.ScalarOnly, true)]
+ [TestCase("1.2 mm", UnitInput.ScalarOnly, false)]
+ [TestCase("1.2", UnitInput.SymbolAllowed, true)]
+ [TestCase("12 mm", UnitInput.SymbolAllowed, true)]
+ [TestCase("1.2", UnitInput.SymbolRequired, false)]
+ [TestCase("12 mm", UnitInput.SymbolRequired, true)]
+ public void WithSymbolSettings(string value, UnitInput unitInput, bool expectSuccess)
+ {
+ var converter = new LengthConverter
+ {
+ Unit = LengthUnit.Centimetres,
+ UnitInput = unitInput
+ };
+
+ var actual = converter.ConvertBack(value, typeof(Length), null, CultureInfo.InvariantCulture);
+ if (expectSuccess)
+ {
+ var expected = Length.FromCentimetres(1.2);
+ Assert.AreEqual(expected, actual);
+ }
+ else
+ {
+ Assert.AreEqual(value, actual);
+ }
+ }
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Tests/LengthConverterTests.cs b/Gu.Units.Wpf.Tests/LengthConverterTests.cs
new file mode 100644
index 00000000..6e28b770
--- /dev/null
+++ b/Gu.Units.Wpf.Tests/LengthConverterTests.cs
@@ -0,0 +1,13 @@
+namespace Gu.Units.Wpf.Tests
+{
+ using NUnit.Framework;
+
+ public partial class LengthConverterTests
+ {
+ //[Test]
+ //public void Reminders()
+ //{
+ // Assert.Pass("Looks like R# needs this to render a proper tree.");
+ //}
+ }
+}
diff --git a/Gu.Units.Wpf.Tests/LengthConverterTests.setup.cs b/Gu.Units.Wpf.Tests/LengthConverterTests.setup.cs
new file mode 100644
index 00000000..9c8725be
--- /dev/null
+++ b/Gu.Units.Wpf.Tests/LengthConverterTests.setup.cs
@@ -0,0 +1,104 @@
+namespace Gu.Units.Wpf.Tests
+{
+ using System;
+ using System.Globalization;
+ using NUnit.Framework;
+
+ public partial class LengthConverterTests
+ {
+ [RequiresSTA]
+ public class Setup
+ {
+ [TestCase("F1 mm")]
+ [TestCase("{F1 mm}")]
+ [TestCase("{0:F1 mm}")]
+ public void SettingStringFormatHappyPath(string stringFormat)
+ {
+ var converter = new LengthConverter
+ {
+ StringFormat = stringFormat
+ };
+
+ var convert = converter.Convert(Length.FromMillimetres(12.34), typeof(string), null, CultureInfo.InvariantCulture);
+ Assert.AreEqual("12.3 mm", convert);
+ Assert.AreEqual(LengthUnit.Millimetres, converter.Unit);
+ Assert.AreEqual(UnitInput.SymbolRequired, converter.UnitInput);
+ }
+
+ [TestCase("unknown format")]
+ public void SettingStringFormatFailsIfBadFormat(string stringFormat)
+ {
+ var converter = new LengthConverter { Unit = LengthUnit.Centimetres };
+ Wpf.Is.DesignMode = true;
+ var ex = Assert.Throws(() => converter.StringFormat = stringFormat);
+ var expected = $"Error parsing: '{stringFormat}'";
+ Assert.AreEqual(expected, ex.Message);
+
+ Wpf.Is.DesignMode = false;
+
+ var convert = converter.Convert(Length.Zero, typeof(string), null, null);
+ Assert.AreEqual(stringFormat, convert);
+ }
+
+ [TestCase("F1 cm")]
+ [TestCase("{F1 cm}")]
+ [TestCase("{0:F1 cm}")]
+ public void BindingStringFormatHappyPath(string stringFormat)
+ {
+ var converter = new LengthConverter();
+ var providerMock = new ServiceProviderMock
+ {
+ BindingStringFormat = stringFormat
+ };
+
+ converter.ProvideValue(providerMock.Object);
+ var length = Length.FromMillimetres(12.3);
+ var convert = converter.Convert(length, typeof(string), null, CultureInfo.InvariantCulture);
+ Assert.AreEqual(length, convert);
+ Assert.AreEqual(LengthUnit.Centimetres, converter.Unit);
+ Assert.AreEqual(UnitInput.SymbolRequired, converter.UnitInput);
+ }
+
+ [Test]
+ public void SettingStringFormatWithUnitWhenUnitIsSet()
+ {
+ var converter = new LengthConverter
+ {
+ Unit = LengthUnit.Centimetres
+ };
+
+ Gu.Units.Wpf.Is.DesignMode = true;
+ var ex = Assert.Throws(() => converter.StringFormat = "F1 mm");
+ var expected = "Unit is set to 'cm' but StringFormat is 'F1 mm'";
+ Assert.AreEqual(expected, ex.Message);
+
+ Gu.Units.Wpf.Is.DesignMode = false;
+ var actual = converter.Convert(Length.FromMetres(1.2), typeof(string), null, CultureInfo.InvariantCulture);
+ Assert.AreEqual(expected, actual);
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void ThrowsInDesignModeIfMissingUnit(bool isDesignMode)
+ {
+ Gu.Units.Wpf.Is.DesignMode = isDesignMode;
+ var converter = new LengthConverter
+ {
+ UnitInput = UnitInput.ScalarOnly
+ };
+
+ var length = Length.FromMetres(1.2);
+ Assert.AreEqual(null, converter.Unit);
+ if (isDesignMode)
+ {
+ Assert.Throws(() => converter.Convert(length, typeof(string), null, null));
+ }
+ else
+ {
+ var actual = converter.Convert(length, typeof(string), null, null);
+ Assert.AreEqual("No unit set", actual);
+ }
+ }
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Tests/Reminder.cs b/Gu.Units.Wpf.Tests/Reminder.cs
new file mode 100644
index 00000000..4d1bb984
--- /dev/null
+++ b/Gu.Units.Wpf.Tests/Reminder.cs
@@ -0,0 +1,7 @@
+namespace Gu.Units.Wpf.Tests
+{
+ public static class Reminder
+ {
+ public const string ToDo = "Fix so this test passes!";
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf.Tests/ServiceProviderMock.cs b/Gu.Units.Wpf.Tests/ServiceProviderMock.cs
new file mode 100644
index 00000000..78c375c6
--- /dev/null
+++ b/Gu.Units.Wpf.Tests/ServiceProviderMock.cs
@@ -0,0 +1,36 @@
+namespace Gu.Units.Wpf.Tests
+{
+ using System;
+ using System.Windows.Controls;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+ using Moq;
+
+ public class ServiceProviderMock : Mock
+ {
+ internal readonly Mock ProvideValueTargetMock = new Mock(MockBehavior.Strict);
+ private readonly TextBox textBox = new TextBox();
+
+ public ServiceProviderMock()
+ :base(MockBehavior.Strict)
+ {
+ Setup(x => x.GetService(typeof(IProvideValueTarget)))
+ .Returns(this.ProvideValueTargetMock.Object);
+
+ this.ProvideValueTargetMock.SetupGet(x => x.TargetObject).Returns(this.textBox);
+ this.ProvideValueTargetMock.SetupGet(x => x.TargetProperty).Returns(TextBox.TextProperty);
+ }
+
+ public string BindingStringFormat
+ {
+ set
+ {
+ var binding = new Binding("Foo")
+ {
+ StringFormat = value
+ };
+ BindingOperations.SetBinding(this.textBox, TextBox.TextProperty, binding);
+ }
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Tests/StringFormatParserTests.cs b/Gu.Units.Wpf.Tests/StringFormatParserTests.cs
new file mode 100644
index 00000000..997b90de
--- /dev/null
+++ b/Gu.Units.Wpf.Tests/StringFormatParserTests.cs
@@ -0,0 +1,21 @@
+namespace Gu.Units.Wpf.Tests
+{
+ using NUnit.Framework;
+
+ public class StringFormatParserTests
+ {
+ [TestCase("{0:F1 mm}")]
+ [TestCase("F1 mm")]
+ public void TryParse(string format)
+ {
+ QuantityFormat actual;
+ var success = StringFormatParser.TryParse(format, out actual);
+ Assert.AreEqual(true, success);
+ Assert.AreEqual(null, actual.PrePadding);
+ Assert.AreEqual("F1", actual.ValueFormat);
+ Assert.AreEqual(" ", actual.Padding);
+ Assert.AreEqual("mm", actual.SymbolFormat);
+ Assert.AreEqual(null, actual.PostPadding);
+ }
+ }
+}
diff --git a/Gu.Units.Wpf.Tests/paket.references b/Gu.Units.Wpf.Tests/paket.references
index 77bef386..5694ee4c 100644
--- a/Gu.Units.Wpf.Tests/paket.references
+++ b/Gu.Units.Wpf.Tests/paket.references
@@ -1 +1,2 @@
-NUnit
\ No newline at end of file
+NUnit
+Moq
\ No newline at end of file
diff --git a/Gu.Units.Wpf/AccelerationConverter.generated.cs b/Gu.Units.Wpf/AccelerationConverter.generated.cs
new file mode 100644
index 00000000..22efbc30
--- /dev/null
+++ b/Gu.Units.Wpf/AccelerationConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Acceleration))]
+[assembly: TypeForwardedTo(typeof(AccelerationUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class AccelerationConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private AccelerationUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public AccelerationConverter()
+ {
+ }
+
+ public AccelerationConverter([TypeConverter(typeof(AccelerationUnitTypeConverter))]AccelerationUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(AccelerationUnitTypeConverter))]
+ public AccelerationUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Acceleration) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Acceleration)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var acceleration = (Acceleration)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return acceleration;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return acceleration.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return acceleration.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return acceleration.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Acceleration) || targetType == typeof(Acceleration?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Acceleration((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Acceleration(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Acceleration(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Acceleration result;
+ if (Acceleration.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/AngleConverter.generated.cs b/Gu.Units.Wpf/AngleConverter.generated.cs
new file mode 100644
index 00000000..526f3dee
--- /dev/null
+++ b/Gu.Units.Wpf/AngleConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Angle))]
+[assembly: TypeForwardedTo(typeof(AngleUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class AngleConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private AngleUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public AngleConverter()
+ {
+ }
+
+ public AngleConverter([TypeConverter(typeof(AngleUnitTypeConverter))]AngleUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(AngleUnitTypeConverter))]
+ public AngleUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Angle) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Angle)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var angle = (Angle)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return angle;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return angle.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return angle.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return angle.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Angle) || targetType == typeof(Angle?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Angle((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Angle(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Angle(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Angle result;
+ if (Angle.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/AnglePerUnitlessConverter.generated.cs b/Gu.Units.Wpf/AnglePerUnitlessConverter.generated.cs
new file mode 100644
index 00000000..f5474774
--- /dev/null
+++ b/Gu.Units.Wpf/AnglePerUnitlessConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(AnglePerUnitless))]
+[assembly: TypeForwardedTo(typeof(AnglePerUnitlessUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class AnglePerUnitlessConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private AnglePerUnitlessUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public AnglePerUnitlessConverter()
+ {
+ }
+
+ public AnglePerUnitlessConverter([TypeConverter(typeof(AnglePerUnitlessUnitTypeConverter))]AnglePerUnitlessUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(AnglePerUnitlessUnitTypeConverter))]
+ public AnglePerUnitlessUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is AnglePerUnitless) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(AnglePerUnitless)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var anglePerUnitless = (AnglePerUnitless)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return anglePerUnitless;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return anglePerUnitless.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return anglePerUnitless.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return anglePerUnitless.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(AnglePerUnitless) || targetType == typeof(AnglePerUnitless?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new AnglePerUnitless((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new AnglePerUnitless(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new AnglePerUnitless(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ AnglePerUnitless result;
+ if (AnglePerUnitless.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/AngularAccelerationConverter.generated.cs b/Gu.Units.Wpf/AngularAccelerationConverter.generated.cs
new file mode 100644
index 00000000..1cb553ba
--- /dev/null
+++ b/Gu.Units.Wpf/AngularAccelerationConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(AngularAcceleration))]
+[assembly: TypeForwardedTo(typeof(AngularAccelerationUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class AngularAccelerationConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private AngularAccelerationUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public AngularAccelerationConverter()
+ {
+ }
+
+ public AngularAccelerationConverter([TypeConverter(typeof(AngularAccelerationUnitTypeConverter))]AngularAccelerationUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(AngularAccelerationUnitTypeConverter))]
+ public AngularAccelerationUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is AngularAcceleration) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(AngularAcceleration)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var angularAcceleration = (AngularAcceleration)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return angularAcceleration;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return angularAcceleration.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return angularAcceleration.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return angularAcceleration.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(AngularAcceleration) || targetType == typeof(AngularAcceleration?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new AngularAcceleration((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new AngularAcceleration(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new AngularAcceleration(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ AngularAcceleration result;
+ if (AngularAcceleration.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/AngularJerkConverter.generated.cs b/Gu.Units.Wpf/AngularJerkConverter.generated.cs
new file mode 100644
index 00000000..9d6eded2
--- /dev/null
+++ b/Gu.Units.Wpf/AngularJerkConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(AngularJerk))]
+[assembly: TypeForwardedTo(typeof(AngularJerkUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class AngularJerkConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private AngularJerkUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public AngularJerkConverter()
+ {
+ }
+
+ public AngularJerkConverter([TypeConverter(typeof(AngularJerkUnitTypeConverter))]AngularJerkUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(AngularJerkUnitTypeConverter))]
+ public AngularJerkUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is AngularJerk) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(AngularJerk)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var angularJerk = (AngularJerk)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return angularJerk;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return angularJerk.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return angularJerk.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return angularJerk.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(AngularJerk) || targetType == typeof(AngularJerk?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new AngularJerk((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new AngularJerk(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new AngularJerk(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ AngularJerk result;
+ if (AngularJerk.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/AngularSpeedConverter.generated.cs b/Gu.Units.Wpf/AngularSpeedConverter.generated.cs
new file mode 100644
index 00000000..53a0d2c0
--- /dev/null
+++ b/Gu.Units.Wpf/AngularSpeedConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(AngularSpeed))]
+[assembly: TypeForwardedTo(typeof(AngularSpeedUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class AngularSpeedConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private AngularSpeedUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public AngularSpeedConverter()
+ {
+ }
+
+ public AngularSpeedConverter([TypeConverter(typeof(AngularSpeedUnitTypeConverter))]AngularSpeedUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(AngularSpeedUnitTypeConverter))]
+ public AngularSpeedUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is AngularSpeed) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(AngularSpeed)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var angularSpeed = (AngularSpeed)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return angularSpeed;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return angularSpeed.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return angularSpeed.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return angularSpeed.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(AngularSpeed) || targetType == typeof(AngularSpeed?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new AngularSpeed((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new AngularSpeed(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new AngularSpeed(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ AngularSpeed result;
+ if (AngularSpeed.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/AreaConverter.generated.cs b/Gu.Units.Wpf/AreaConverter.generated.cs
new file mode 100644
index 00000000..8b1009c6
--- /dev/null
+++ b/Gu.Units.Wpf/AreaConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Area))]
+[assembly: TypeForwardedTo(typeof(AreaUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class AreaConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private AreaUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public AreaConverter()
+ {
+ }
+
+ public AreaConverter([TypeConverter(typeof(AreaUnitTypeConverter))]AreaUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(AreaUnitTypeConverter))]
+ public AreaUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Area) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Area)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var area = (Area)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return area;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return area.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return area.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return area.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Area) || targetType == typeof(Area?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Area((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Area(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Area(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Area result;
+ if (Area.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/CapacitanceConverter.generated.cs b/Gu.Units.Wpf/CapacitanceConverter.generated.cs
new file mode 100644
index 00000000..5dff64f9
--- /dev/null
+++ b/Gu.Units.Wpf/CapacitanceConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Capacitance))]
+[assembly: TypeForwardedTo(typeof(CapacitanceUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class CapacitanceConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private CapacitanceUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public CapacitanceConverter()
+ {
+ }
+
+ public CapacitanceConverter([TypeConverter(typeof(CapacitanceUnitTypeConverter))]CapacitanceUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(CapacitanceUnitTypeConverter))]
+ public CapacitanceUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Capacitance) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Capacitance)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var capacitance = (Capacitance)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return capacitance;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return capacitance.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return capacitance.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return capacitance.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Capacitance) || targetType == typeof(Capacitance?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Capacitance((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Capacitance(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Capacitance(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Capacitance result;
+ if (Capacitance.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/CurrentConverter.generated.cs b/Gu.Units.Wpf/CurrentConverter.generated.cs
new file mode 100644
index 00000000..ca2586ba
--- /dev/null
+++ b/Gu.Units.Wpf/CurrentConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Current))]
+[assembly: TypeForwardedTo(typeof(CurrentUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class CurrentConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private CurrentUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public CurrentConverter()
+ {
+ }
+
+ public CurrentConverter([TypeConverter(typeof(CurrentUnitTypeConverter))]CurrentUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(CurrentUnitTypeConverter))]
+ public CurrentUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Current) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Current)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var current = (Current)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return current;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return current.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return current.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return current.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Current) || targetType == typeof(Current?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Current((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Current(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Current(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Current result;
+ if (Current.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/DensityConverter.generated.cs b/Gu.Units.Wpf/DensityConverter.generated.cs
new file mode 100644
index 00000000..f1d685cc
--- /dev/null
+++ b/Gu.Units.Wpf/DensityConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Density))]
+[assembly: TypeForwardedTo(typeof(DensityUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class DensityConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private DensityUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public DensityConverter()
+ {
+ }
+
+ public DensityConverter([TypeConverter(typeof(DensityUnitTypeConverter))]DensityUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(DensityUnitTypeConverter))]
+ public DensityUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Density) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Density)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var density = (Density)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return density;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return density.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return density.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return density.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Density) || targetType == typeof(Density?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Density((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Density(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Density(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Density result;
+ if (Density.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/ElectricChargeConverter.generated.cs b/Gu.Units.Wpf/ElectricChargeConverter.generated.cs
new file mode 100644
index 00000000..8dfe8f00
--- /dev/null
+++ b/Gu.Units.Wpf/ElectricChargeConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(ElectricCharge))]
+[assembly: TypeForwardedTo(typeof(ElectricChargeUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class ElectricChargeConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private ElectricChargeUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public ElectricChargeConverter()
+ {
+ }
+
+ public ElectricChargeConverter([TypeConverter(typeof(ElectricChargeUnitTypeConverter))]ElectricChargeUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(ElectricChargeUnitTypeConverter))]
+ public ElectricChargeUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is ElectricCharge) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(ElectricCharge)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var electricCharge = (ElectricCharge)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return electricCharge;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return electricCharge.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return electricCharge.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return electricCharge.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(ElectricCharge) || targetType == typeof(ElectricCharge?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new ElectricCharge((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new ElectricCharge(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new ElectricCharge(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ ElectricCharge result;
+ if (ElectricCharge.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/EnergyConverter.generated.cs b/Gu.Units.Wpf/EnergyConverter.generated.cs
new file mode 100644
index 00000000..7213e199
--- /dev/null
+++ b/Gu.Units.Wpf/EnergyConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Energy))]
+[assembly: TypeForwardedTo(typeof(EnergyUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class EnergyConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private EnergyUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public EnergyConverter()
+ {
+ }
+
+ public EnergyConverter([TypeConverter(typeof(EnergyUnitTypeConverter))]EnergyUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(EnergyUnitTypeConverter))]
+ public EnergyUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Energy) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Energy)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var energy = (Energy)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return energy;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return energy.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return energy.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return energy.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Energy) || targetType == typeof(Energy?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Energy((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Energy(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Energy(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Energy result;
+ if (Energy.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/FlexibilityConverter.generated.cs b/Gu.Units.Wpf/FlexibilityConverter.generated.cs
new file mode 100644
index 00000000..62c3ef7d
--- /dev/null
+++ b/Gu.Units.Wpf/FlexibilityConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Flexibility))]
+[assembly: TypeForwardedTo(typeof(FlexibilityUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class FlexibilityConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private FlexibilityUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public FlexibilityConverter()
+ {
+ }
+
+ public FlexibilityConverter([TypeConverter(typeof(FlexibilityUnitTypeConverter))]FlexibilityUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(FlexibilityUnitTypeConverter))]
+ public FlexibilityUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Flexibility) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Flexibility)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var flexibility = (Flexibility)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return flexibility;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return flexibility.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return flexibility.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return flexibility.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Flexibility) || targetType == typeof(Flexibility?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Flexibility((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Flexibility(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Flexibility(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Flexibility result;
+ if (Flexibility.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/ForceConverter.generated.cs b/Gu.Units.Wpf/ForceConverter.generated.cs
new file mode 100644
index 00000000..4a2d4122
--- /dev/null
+++ b/Gu.Units.Wpf/ForceConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Force))]
+[assembly: TypeForwardedTo(typeof(ForceUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class ForceConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private ForceUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public ForceConverter()
+ {
+ }
+
+ public ForceConverter([TypeConverter(typeof(ForceUnitTypeConverter))]ForceUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(ForceUnitTypeConverter))]
+ public ForceUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Force) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Force)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var force = (Force)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return force;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return force.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return force.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return force.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Force) || targetType == typeof(Force?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Force((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Force(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Force(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Force result;
+ if (Force.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/ForcePerUnitlessConverter.generated.cs b/Gu.Units.Wpf/ForcePerUnitlessConverter.generated.cs
new file mode 100644
index 00000000..70404d56
--- /dev/null
+++ b/Gu.Units.Wpf/ForcePerUnitlessConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(ForcePerUnitless))]
+[assembly: TypeForwardedTo(typeof(ForcePerUnitlessUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class ForcePerUnitlessConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private ForcePerUnitlessUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public ForcePerUnitlessConverter()
+ {
+ }
+
+ public ForcePerUnitlessConverter([TypeConverter(typeof(ForcePerUnitlessUnitTypeConverter))]ForcePerUnitlessUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(ForcePerUnitlessUnitTypeConverter))]
+ public ForcePerUnitlessUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is ForcePerUnitless) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(ForcePerUnitless)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var forcePerUnitless = (ForcePerUnitless)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return forcePerUnitless;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return forcePerUnitless.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return forcePerUnitless.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return forcePerUnitless.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(ForcePerUnitless) || targetType == typeof(ForcePerUnitless?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new ForcePerUnitless((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new ForcePerUnitless(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new ForcePerUnitless(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ ForcePerUnitless result;
+ if (ForcePerUnitless.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/FrequencyConverter.generated.cs b/Gu.Units.Wpf/FrequencyConverter.generated.cs
new file mode 100644
index 00000000..996bd6a6
--- /dev/null
+++ b/Gu.Units.Wpf/FrequencyConverter.generated.cs
@@ -0,0 +1,320 @@
+using System.Runtime.CompilerServices;
+using Gu.Units;
+
+[assembly: TypeForwardedTo(typeof(Frequency))]
+[assembly: TypeForwardedTo(typeof(FrequencyUnit))]
+
+namespace Gu.Units.Wpf
+{
+ using System;
+ using System.ComponentModel;
+ using System.Globalization;
+ using System.Windows;
+ using System.Windows.Data;
+ using System.Windows.Markup;
+
+ [MarkupExtensionReturnType(typeof(IValueConverter))]
+ public class FrequencyConverter : MarkupExtension, IValueConverter
+ {
+ private static readonly string StringFormatNotSet = "Not Set";
+ private FrequencyUnit? unit;
+ private IProvideValueTarget provideValueTarget;
+ private string stringFormat = StringFormatNotSet;
+ private QuantityFormat quantityFormat;
+ private string bindingStringFormat = StringFormatNotSet;
+ private string errorText;
+
+ public FrequencyConverter()
+ {
+ }
+
+ public FrequencyConverter([TypeConverter(typeof(FrequencyUnitTypeConverter))]FrequencyUnit unit)
+ {
+ Unit = unit;
+ }
+
+ [ConstructorArgument("unit"), TypeConverter(typeof(FrequencyUnitTypeConverter))]
+ public FrequencyUnit? Unit
+ {
+ get { return this.unit; }
+ set
+ {
+ if (value == null)
+ {
+ var message = $"{nameof(Unit)} cannot be null";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ this.unit = value.Value;
+ }
+ }
+
+ public SymbolFormat? SymbolFormat { get; set; }
+
+ public UnitInput? UnitInput { get; set; }
+
+ public string StringFormat
+ {
+ get { return this.stringFormat; }
+ set
+ {
+ this.stringFormat = value;
+ OnStringFormatChanged();
+ }
+ }
+
+ public override object ProvideValue(IServiceProvider serviceProvider)
+ {
+ // the binding does not have stringformat set at this point
+ // caching IProvideValueTarget to resolve later.
+ this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
+ return this;
+ }
+
+ public object Convert(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!IsValidConvertTargetType(targetType) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new ArgumentException(message, nameof(targetType));
+ }
+
+ if (value != null && !(value is Frequency) && Is.DesignMode)
+ {
+ var message = $"{GetType().Name} only supports converting from {typeof(Frequency)}";
+ throw new ArgumentException(message, nameof(value));
+ }
+
+ if (this.bindingStringFormat == StringFormatNotSet)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return "No unit set";
+ }
+
+ if (this.errorText != null)
+ {
+ return this.errorText;
+ }
+
+ if (value == null)
+ {
+ return targetType == typeof(string)
+ ? string.Empty
+ : null;
+ }
+
+ var frequency = (Frequency)value;
+
+ var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet
+ ? this.bindingStringFormat
+ : null;
+ if (format != null)
+ {
+ return frequency;
+ }
+
+ format = this.stringFormat != null && this.stringFormat != StringFormatNotSet
+ ? this.stringFormat
+ : null;
+ if (format != null &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return frequency.ToString(this.quantityFormat, culture);
+ }
+
+
+ if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) &&
+ (targetType == typeof(string) || targetType == typeof(object)))
+ {
+ return frequency.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture);
+ }
+
+ if (IsValidConvertTargetType(targetType))
+ {
+ return frequency.GetValue(this.unit.Value);
+ }
+
+ return value;
+ }
+
+ public object ConvertBack(object value,
+ Type targetType,
+ object parameter,
+ CultureInfo culture)
+ {
+ if (!(targetType == typeof(Frequency) || targetType == typeof(Frequency?)))
+ {
+ var message = $"{GetType().Name} does not support converting to {targetType.Name}";
+ throw new NotSupportedException(message);
+ }
+
+ if (value == null)
+ {
+ return null;
+ }
+
+ if (this.StringFormat == null)
+ {
+ TryGetStringFormatFromBinding();
+ }
+
+ if (this.unit == null)
+ {
+ if (Is.DesignMode)
+ {
+ var message = $"{nameof(Unit)} cannot be null\r\n" +
+ $"Must be specified Explicitly or in Binding.StringFormat";
+ throw new ArgumentException(message);
+ }
+
+ return value;
+ }
+
+ if (value is double)
+ {
+ return new Frequency((double)value, this.unit.Value);
+ }
+
+ var text = value as string;
+ if (string.IsNullOrEmpty(text))
+ {
+ return null;
+ }
+
+ var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly;
+ switch (unitInput)
+ {
+ case Wpf.UnitInput.ScalarOnly:
+ {
+ double d;
+ if (double.TryParse(text, NumberStyles.Float, culture, out d))
+ {
+ return new Frequency(d, this.unit.Value);
+ }
+
+ return value; // returning raw to trigger error
+ }
+ case Wpf.UnitInput.SymbolAllowed:
+ {
+ double d;
+ int pos = 0;
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d))
+ {
+ WhiteSpaceReader.TryRead(text, ref pos);
+ if (pos == text.Length)
+ {
+ return new Frequency(d, this.unit.Value);
+ }
+ }
+
+ goto case Wpf.UnitInput.SymbolRequired;
+ }
+ case Wpf.UnitInput.SymbolRequired:
+ {
+ Frequency result;
+ if (Frequency.TryParse(text, NumberStyles.Float, culture, out result))
+ {
+ return result;
+ }
+
+ return text;
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private void TryGetStringFormatFromBinding()
+ {
+ var target = this.provideValueTarget?.TargetObject as DependencyObject;
+ Binding binding = null;
+ if (target != null)
+ {
+ var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty;
+ if (targetProperty != null)
+ {
+ binding = BindingOperations.GetBinding(target, targetProperty);
+ }
+ }
+
+ binding = binding ?? this.provideValueTarget?.TargetObject as Binding;
+ this.bindingStringFormat = binding?.StringFormat;
+ if (this.bindingStringFormat != null)
+ {
+ OnStringFormatChanged();
+ }
+ }
+
+ private void OnStringFormatChanged()
+ {
+ if (this.bindingStringFormat != StringFormatNotSet &&
+ this.stringFormat != StringFormatNotSet &&
+ this.bindingStringFormat != this.stringFormat)
+ {
+ this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set.";
+
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ var format = this.stringFormat == StringFormatNotSet
+ ? this.bindingStringFormat
+ : this.stringFormat;
+ if (StringFormatParser.TryParse(format, out this.quantityFormat))
+ {
+ if (UnitInput == null && this.quantityFormat.SymbolFormat != null)
+ {
+ UnitInput = Wpf.UnitInput.SymbolRequired;
+ }
+
+ if (this.unit == null)
+ {
+ this.unit = this.quantityFormat.Unit;
+ }
+
+ else if (this.unit != this.quantityFormat.Unit)
+ {
+ this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'";
+ if (Is.DesignMode)
+ {
+ throw new InvalidOperationException(this.errorText);
+ }
+ }
+
+ return;
+ }
+
+ this.errorText = this.quantityFormat.ErrorText;
+ if (Is.DesignMode)
+ {
+ throw new ArgumentException($"Error parsing: '{this.errorText}'");
+ }
+
+ this.stringFormat = null;
+ }
+
+ private bool IsValidConvertTargetType(Type targetType)
+ {
+ return targetType == typeof(string) ||
+ targetType == typeof(double) ||
+ targetType == typeof(object);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Gu.Units.Wpf/Gu.Units.Wpf.csproj b/Gu.Units.Wpf/Gu.Units.Wpf.csproj
index 4513ce99..f2244771 100644
--- a/Gu.Units.Wpf/Gu.Units.Wpf.csproj
+++ b/Gu.Units.Wpf/Gu.Units.Wpf.csproj
@@ -35,10 +35,119 @@
+
+
-
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
+
+ QuantityValueConverters.txt4
+
@@ -46,6 +155,10 @@
Gu.Units
+
+
+
+