Skip to content

Regex parsing #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
64 changes: 50 additions & 14 deletions Src/Scripts/Include-GenerateUnitClassSourceCode.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function GenerateUnitClassSourceCode($unitClass)

using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Linq;
using JetBrains.Annotations;
using UnitsNet.Units;
Expand Down Expand Up @@ -278,29 +279,38 @@ namespace UnitsNet
public static $className Parse(string str, IFormatProvider formatProvider = null)
{
if (str == null) throw new ArgumentNullException("str");
string[] words = str.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
if (words.Length < 2)

var numFormat = formatProvider != null ?
(NumberFormatInfo) formatProvider.GetFormat(typeof (NumberFormatInfo)) :
NumberFormatInfo.CurrentInfo;

var numRegex = string.Format(@"[\d., {0}{1}]*\d", // allows digits, dots, commas, and spaces in the quantity (must end in digit)
numFormat.NumberGroupSeparator, // adds provided (or current) culture's group separator
numFormat.NumberDecimalSeparator); // adds provided (or current) culture's decimal separator
var regexString = string.Format("(?<value>[-+]?{0}{1}{2}{3}",
numRegex, // capture base (integral) Quantity value
@"(?:[eE][-+]?\d+)?)", // capture exponential (if any), end of Quantity capturing
@"\s?", // ignore whitespace (allows both "1kg", "1 kg")
@"(?<unit>\S+)"); // capture Unit (non-whitespace) input

var regex = new Regex(regexString);
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should consider caching this regex as a static field and construct it with the Compile flag for performance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this be affected by a change to the given formatProvider?
For example, running tests with different cultures.

GroupCollection groups = regex.Match(str.Trim()).Groups;

var valueString = groups["value"].Value;
var unitString = groups["unit"].Value;

if (valueString == "" || unitString == "")
{
var ex = new ArgumentException(
"Expected two or more words. Input string needs to be in the format \"<quantity> <unit>\".", "str");
"Expected valid quantity and unit. Input string needs to be in the format \"<quantity><unit> or <quantity> <unit>\".", "str");
ex.Data["input"] = str;
ex.Data["formatprovider"] = formatProvider == null ? null : formatProvider.ToString();
throw ex;
}

try
{
// Unit string is the last word, since units added so far don't contain spaces.
// Value string is everything else since number formatting can contain spaces.
string[] allWordsButLast = words.Take(words.Length - 1).ToArray();
string lastWord = words[words.Length - 1];

string unitString = lastWord;
string valueString = string.Join(" ", allWordsButLast);

var unitSystem = UnitSystem.GetCached(formatProvider);

$unitEnumName unit = unitSystem.Parse<$unitEnumName>(unitString);
$unitEnumName unit = ParseUnit(unitString, formatProvider);
double value = double.Parse(valueString, formatProvider);

return From(value, unit);
Expand All @@ -314,6 +324,32 @@ namespace UnitsNet
}
}

/// <summary>
/// Parse a unit string.
/// </summary>
/// <example>
/// Length.ParseUnit("m", new CultureInfo("en-US"));
/// </example>
/// <exception cref="ArgumentNullException">The value of 'str' cannot be null. </exception>
/// <exception cref="UnitsNetException">Error parsing string.</exception>
public static $unitEnumName ParseUnit(string str, IFormatProvider formatProvider = null)
{
if (str == null) throw new ArgumentNullException("str");
var unitSystem = UnitSystem.GetCached(formatProvider);

var unit = unitSystem.Parse<$unitEnumName>(str.Trim());

if (unit == $unitEnumName.Undefined)
{
var newEx = new UnitsNetException("Error parsing string. The unit is not a recognized $unitEnumName.");
newEx.Data["input"] = str;
newEx.Data["formatprovider"] = formatProvider == null ? null : formatProvider.ToString();
throw newEx;
}

return unit;
}

#endregion

/// <summary>
Expand Down
64 changes: 50 additions & 14 deletions Src/UnitsNet/GeneratedCode/UnitClasses/Acceleration.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

using System;
using System.Globalization;
using System.Text.RegularExpressions;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated code should be a separate commit.

using System.Linq;
using JetBrains.Annotations;
using UnitsNet.Units;
Expand Down Expand Up @@ -245,29 +246,38 @@ public double As(AccelerationUnit unit)
public static Acceleration Parse(string str, IFormatProvider formatProvider = null)
{
if (str == null) throw new ArgumentNullException("str");
string[] words = str.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
if (words.Length < 2)

var numFormat = formatProvider != null ?
(NumberFormatInfo) formatProvider.GetFormat(typeof (NumberFormatInfo)) :
NumberFormatInfo.CurrentInfo;

var numRegex = string.Format(@"[\d., {0}{1}]*\d", // allows digits, dots, commas, and spaces in the quantity (must end in digit)
numFormat.NumberGroupSeparator, // adds provided (or current) culture's group separator
numFormat.NumberDecimalSeparator); // adds provided (or current) culture's decimal separator
var regexString = string.Format("(?<value>[-+]?{0}{1}{2}{3}",
numRegex, // capture base (integral) Quantity value
@"(?:[eE][-+]?\d+)?)", // capture exponential (if any), end of Quantity capturing
@"\s?", // ignore whitespace (allows both "1kg", "1 kg")
@"(?<unit>\S+)"); // capture Unit (non-whitespace) input

var regex = new Regex(regexString);
GroupCollection groups = regex.Match(str.Trim()).Groups;

var valueString = groups["value"].Value;
var unitString = groups["unit"].Value;

if (valueString == "" || unitString == "")
{
var ex = new ArgumentException(
"Expected two or more words. Input string needs to be in the format \"<quantity> <unit>\".", "str");
"Expected valid quantity and unit. Input string needs to be in the format \"<quantity><unit> or <quantity> <unit>\".", "str");
ex.Data["input"] = str;
ex.Data["formatprovider"] = formatProvider == null ? null : formatProvider.ToString();
throw ex;
}

try
{
// Unit string is the last word, since units added so far don't contain spaces.
// Value string is everything else since number formatting can contain spaces.
string[] allWordsButLast = words.Take(words.Length - 1).ToArray();
string lastWord = words[words.Length - 1];

string unitString = lastWord;
string valueString = string.Join(" ", allWordsButLast);

var unitSystem = UnitSystem.GetCached(formatProvider);

AccelerationUnit unit = unitSystem.Parse<AccelerationUnit>(unitString);
AccelerationUnit unit = ParseUnit(unitString, formatProvider);
double value = double.Parse(valueString, formatProvider);

return From(value, unit);
Expand All @@ -281,6 +291,32 @@ public static Acceleration Parse(string str, IFormatProvider formatProvider = nu
}
}

/// <summary>
/// Parse a unit string.
/// </summary>
/// <example>
/// Length.ParseUnit("m", new CultureInfo("en-US"));
/// </example>
/// <exception cref="ArgumentNullException">The value of 'str' cannot be null. </exception>
/// <exception cref="UnitsNetException">Error parsing string.</exception>
public static AccelerationUnit ParseUnit(string str, IFormatProvider formatProvider = null)
{
if (str == null) throw new ArgumentNullException("str");
var unitSystem = UnitSystem.GetCached(formatProvider);

var unit = unitSystem.Parse<AccelerationUnit>(str.Trim());

if (unit == AccelerationUnit.Undefined)
{
var newEx = new UnitsNetException("Error parsing string. The unit is not a recognized AccelerationUnit.");
newEx.Data["input"] = str;
newEx.Data["formatprovider"] = formatProvider == null ? null : formatProvider.ToString();
throw newEx;
}

return unit;
}

#endregion

/// <summary>
Expand Down
64 changes: 50 additions & 14 deletions Src/UnitsNet/GeneratedCode/UnitClasses/AmplitudeRatio.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Linq;
using JetBrains.Annotations;
using UnitsNet.Units;
Expand Down Expand Up @@ -293,29 +294,38 @@ public double As(AmplitudeRatioUnit unit)
public static AmplitudeRatio Parse(string str, IFormatProvider formatProvider = null)
{
if (str == null) throw new ArgumentNullException("str");
string[] words = str.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
if (words.Length < 2)

var numFormat = formatProvider != null ?
(NumberFormatInfo) formatProvider.GetFormat(typeof (NumberFormatInfo)) :
NumberFormatInfo.CurrentInfo;

var numRegex = string.Format(@"[\d., {0}{1}]*\d", // allows digits, dots, commas, and spaces in the quantity (must end in digit)
numFormat.NumberGroupSeparator, // adds provided (or current) culture's group separator
numFormat.NumberDecimalSeparator); // adds provided (or current) culture's decimal separator
var regexString = string.Format("(?<value>[-+]?{0}{1}{2}{3}",
numRegex, // capture base (integral) Quantity value
@"(?:[eE][-+]?\d+)?)", // capture exponential (if any), end of Quantity capturing
@"\s?", // ignore whitespace (allows both "1kg", "1 kg")
@"(?<unit>\S+)"); // capture Unit (non-whitespace) input

var regex = new Regex(regexString);
GroupCollection groups = regex.Match(str.Trim()).Groups;

var valueString = groups["value"].Value;
var unitString = groups["unit"].Value;

if (valueString == "" || unitString == "")
{
var ex = new ArgumentException(
"Expected two or more words. Input string needs to be in the format \"<quantity> <unit>\".", "str");
"Expected valid quantity and unit. Input string needs to be in the format \"<quantity><unit> or <quantity> <unit>\".", "str");
ex.Data["input"] = str;
ex.Data["formatprovider"] = formatProvider == null ? null : formatProvider.ToString();
throw ex;
}

try
{
// Unit string is the last word, since units added so far don't contain spaces.
// Value string is everything else since number formatting can contain spaces.
string[] allWordsButLast = words.Take(words.Length - 1).ToArray();
string lastWord = words[words.Length - 1];

string unitString = lastWord;
string valueString = string.Join(" ", allWordsButLast);

var unitSystem = UnitSystem.GetCached(formatProvider);

AmplitudeRatioUnit unit = unitSystem.Parse<AmplitudeRatioUnit>(unitString);
AmplitudeRatioUnit unit = ParseUnit(unitString, formatProvider);
double value = double.Parse(valueString, formatProvider);

return From(value, unit);
Expand All @@ -329,6 +339,32 @@ public static AmplitudeRatio Parse(string str, IFormatProvider formatProvider =
}
}

/// <summary>
/// Parse a unit string.
/// </summary>
/// <example>
/// Length.ParseUnit("m", new CultureInfo("en-US"));
/// </example>
/// <exception cref="ArgumentNullException">The value of 'str' cannot be null. </exception>
/// <exception cref="UnitsNetException">Error parsing string.</exception>
public static AmplitudeRatioUnit ParseUnit(string str, IFormatProvider formatProvider = null)
{
if (str == null) throw new ArgumentNullException("str");
var unitSystem = UnitSystem.GetCached(formatProvider);

var unit = unitSystem.Parse<AmplitudeRatioUnit>(str.Trim());

if (unit == AmplitudeRatioUnit.Undefined)
{
var newEx = new UnitsNetException("Error parsing string. The unit is not a recognized AmplitudeRatioUnit.");
newEx.Data["input"] = str;
newEx.Data["formatprovider"] = formatProvider == null ? null : formatProvider.ToString();
throw newEx;
}

return unit;
}

#endregion

/// <summary>
Expand Down
64 changes: 50 additions & 14 deletions Src/UnitsNet/GeneratedCode/UnitClasses/Angle.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Linq;
using JetBrains.Annotations;
using UnitsNet.Units;
Expand Down Expand Up @@ -285,29 +286,38 @@ public double As(AngleUnit unit)
public static Angle Parse(string str, IFormatProvider formatProvider = null)
{
if (str == null) throw new ArgumentNullException("str");
string[] words = str.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
if (words.Length < 2)

var numFormat = formatProvider != null ?
(NumberFormatInfo) formatProvider.GetFormat(typeof (NumberFormatInfo)) :
NumberFormatInfo.CurrentInfo;

var numRegex = string.Format(@"[\d., {0}{1}]*\d", // allows digits, dots, commas, and spaces in the quantity (must end in digit)
numFormat.NumberGroupSeparator, // adds provided (or current) culture's group separator
numFormat.NumberDecimalSeparator); // adds provided (or current) culture's decimal separator
var regexString = string.Format("(?<value>[-+]?{0}{1}{2}{3}",
numRegex, // capture base (integral) Quantity value
@"(?:[eE][-+]?\d+)?)", // capture exponential (if any), end of Quantity capturing
@"\s?", // ignore whitespace (allows both "1kg", "1 kg")
@"(?<unit>\S+)"); // capture Unit (non-whitespace) input

var regex = new Regex(regexString);
GroupCollection groups = regex.Match(str.Trim()).Groups;

var valueString = groups["value"].Value;
var unitString = groups["unit"].Value;

if (valueString == "" || unitString == "")
{
var ex = new ArgumentException(
"Expected two or more words. Input string needs to be in the format \"<quantity> <unit>\".", "str");
"Expected valid quantity and unit. Input string needs to be in the format \"<quantity><unit> or <quantity> <unit>\".", "str");
ex.Data["input"] = str;
ex.Data["formatprovider"] = formatProvider == null ? null : formatProvider.ToString();
throw ex;
}

try
{
// Unit string is the last word, since units added so far don't contain spaces.
// Value string is everything else since number formatting can contain spaces.
string[] allWordsButLast = words.Take(words.Length - 1).ToArray();
string lastWord = words[words.Length - 1];

string unitString = lastWord;
string valueString = string.Join(" ", allWordsButLast);

var unitSystem = UnitSystem.GetCached(formatProvider);

AngleUnit unit = unitSystem.Parse<AngleUnit>(unitString);
AngleUnit unit = ParseUnit(unitString, formatProvider);
double value = double.Parse(valueString, formatProvider);

return From(value, unit);
Expand All @@ -321,6 +331,32 @@ public static Angle Parse(string str, IFormatProvider formatProvider = null)
}
}

/// <summary>
/// Parse a unit string.
/// </summary>
/// <example>
/// Length.ParseUnit("m", new CultureInfo("en-US"));
/// </example>
/// <exception cref="ArgumentNullException">The value of 'str' cannot be null. </exception>
/// <exception cref="UnitsNetException">Error parsing string.</exception>
public static AngleUnit ParseUnit(string str, IFormatProvider formatProvider = null)
{
if (str == null) throw new ArgumentNullException("str");
var unitSystem = UnitSystem.GetCached(formatProvider);

var unit = unitSystem.Parse<AngleUnit>(str.Trim());

if (unit == AngleUnit.Undefined)
{
var newEx = new UnitsNetException("Error parsing string. The unit is not a recognized AngleUnit.");
newEx.Data["input"] = str;
newEx.Data["formatprovider"] = formatProvider == null ? null : formatProvider.ToString();
throw newEx;
}

return unit;
}

#endregion

/// <summary>
Expand Down
Loading