refactor: Move formatting utilities to CutList.Core.Formatting namespace
Consolidates ArchUnits, FormatHelper, and Fraction classes into a dedicated Formatting namespace for better organization. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
74
CutList.Core/Formatting/ArchUnits.cs
Normal file
74
CutList.Core/Formatting/ArchUnits.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace CutList.Core.Formatting
|
||||
{
|
||||
public static class ArchUnits
|
||||
{
|
||||
public static double ParseToInches(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
return 0;
|
||||
|
||||
var sb = new StringBuilder(input.Trim().ToLower());
|
||||
|
||||
// replace all units with their equivelant symbols
|
||||
sb.Replace("ft", "'");
|
||||
sb.Replace("feet", "'");
|
||||
sb.Replace("foot", "'");
|
||||
sb.Replace("inches", "\"");
|
||||
sb.Replace("inch", "\"");
|
||||
sb.Replace("in", "\"");
|
||||
|
||||
var regex = new Regex("^(?<Feet>\\d+\\.?\\d*\\s*')?\\s*(?<Inches>\\d+\\.?\\d*\\s*\")?$");
|
||||
|
||||
// input manipulation is done, put the value back
|
||||
input = Fraction.ReplaceFractionsWithDecimals(sb.ToString());
|
||||
|
||||
var match2 = regex.Match(input);
|
||||
|
||||
if (!match2.Success)
|
||||
throw new Exception("Input is not in a valid format.");
|
||||
|
||||
var feet = match2.Groups["Feet"];
|
||||
var inches = match2.Groups["Inches"];
|
||||
|
||||
var totalInches = 0.0;
|
||||
|
||||
if (feet.Success)
|
||||
{
|
||||
var x = double.Parse(feet.Value.Remove(feet.Length - 1));
|
||||
totalInches += x * 12;
|
||||
}
|
||||
|
||||
if (inches.Success)
|
||||
{
|
||||
var x = double.Parse(inches.Value.Remove(inches.Length - 1));
|
||||
totalInches += x;
|
||||
}
|
||||
|
||||
return Math.Round(totalInches, 8);
|
||||
}
|
||||
|
||||
public static double ParseToFeet(string input)
|
||||
{
|
||||
var inches = ParseToInches(input);
|
||||
return Math.Round(inches / 12.0, 8);
|
||||
}
|
||||
|
||||
public static string FormatFromInches(double totalInches)
|
||||
{
|
||||
var feet = Math.Floor(totalInches / 12.0);
|
||||
var inches = FormatHelper.ConvertToMixedFraction(totalInches - (feet * 12.0));
|
||||
|
||||
if (feet > 0)
|
||||
{
|
||||
return $"{feet}' {inches}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{inches}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
CutList.Core/Formatting/FormatHelper.cs
Normal file
66
CutList.Core/Formatting/FormatHelper.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace CutList.Core.Formatting
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides formatting utilities for displaying measurements and values.
|
||||
/// </summary>
|
||||
public static class FormatHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a decimal measurement to a mixed fraction string representation.
|
||||
/// </summary>
|
||||
/// <param name="input">The decimal value to convert</param>
|
||||
/// <param name="precision">The denominator precision (default 16 for 1/16")</param>
|
||||
/// <returns>A string in the format "whole-numerator/denominator"</returns>
|
||||
public static string ConvertToMixedFraction(decimal input, int precision = 16)
|
||||
{
|
||||
// Get the whole number part
|
||||
int wholeNumber = (int)input;
|
||||
|
||||
// Get the fractional part
|
||||
decimal fractionalPart = Math.Abs(input - wholeNumber);
|
||||
|
||||
if (fractionalPart == 0)
|
||||
{
|
||||
return wholeNumber.ToString();
|
||||
}
|
||||
|
||||
// Convert the fractional part to a fraction
|
||||
int numerator = (int)(fractionalPart * precision);
|
||||
int denominator = precision;
|
||||
|
||||
// Simplify the fraction
|
||||
int gcd = GetGreatestCommonDivisor(numerator, denominator);
|
||||
numerator /= gcd;
|
||||
denominator /= gcd;
|
||||
|
||||
// If rounding wiped out the fraction → return whole number only
|
||||
if (numerator == 0)
|
||||
{
|
||||
return wholeNumber.ToString();
|
||||
}
|
||||
|
||||
return $"{wholeNumber}-{numerator}/{denominator}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a double measurement to a mixed fraction string representation.
|
||||
/// </summary>
|
||||
/// <param name="input">The double value to convert</param>
|
||||
/// <returns>A string in the format "whole-numerator/denominator"</returns>
|
||||
public static string ConvertToMixedFraction(double input)
|
||||
{
|
||||
return ConvertToMixedFraction((decimal)input);
|
||||
}
|
||||
|
||||
private static int GetGreatestCommonDivisor(int a, int b)
|
||||
{
|
||||
while (b != 0)
|
||||
{
|
||||
int temp = b;
|
||||
b = a % b;
|
||||
a = temp;
|
||||
}
|
||||
return Math.Abs(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
CutList.Core/Formatting/Fraction.cs
Normal file
83
CutList.Core/Formatting/Fraction.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace CutList.Core.Formatting
|
||||
{
|
||||
public static class Fraction
|
||||
{
|
||||
public static readonly Regex FractionRegex = new Regex(@"((?<WholeNum>\d+)(\ |-))?(?<Fraction>\d+\/\d+)");
|
||||
|
||||
public static bool IsValid(string s)
|
||||
{
|
||||
return FractionRegex.IsMatch(s);
|
||||
}
|
||||
|
||||
public static double Parse(string s)
|
||||
{
|
||||
var match = FractionRegex.Match(s);
|
||||
|
||||
if (!match.Success)
|
||||
throw new Exception("Invalid format.");
|
||||
|
||||
var value = 0.0;
|
||||
|
||||
var wholeNumGroup = match.Groups["WholeNum"];
|
||||
var fractionGroup = match.Groups["Fraction"];
|
||||
|
||||
if (wholeNumGroup.Success)
|
||||
{
|
||||
value = double.Parse(wholeNumGroup.Value);
|
||||
}
|
||||
|
||||
if (fractionGroup.Success)
|
||||
{
|
||||
var parts = fractionGroup.Value.Split('/');
|
||||
var numerator = double.Parse(parts[0]);
|
||||
var denominator = double.Parse(parts[1]);
|
||||
|
||||
value += Math.Round(numerator / denominator, 8);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static string ReplaceFractionsWithDecimals(string input)
|
||||
{
|
||||
var sb = new StringBuilder(input);
|
||||
|
||||
// find all matches and sort by descending index number to avoid
|
||||
// changing all previous index numbers when the fraction is replaced
|
||||
// with the decimal equivalent.
|
||||
var fractionMatches = FractionRegex.Matches(sb.ToString())
|
||||
.Cast<Match>()
|
||||
.OrderByDescending(m => m.Index);
|
||||
|
||||
foreach (var fractionMatch in fractionMatches)
|
||||
{
|
||||
// convert the fraction to a decimal value
|
||||
var decimalValue = Parse(fractionMatch.Value);
|
||||
|
||||
// remove the fraction and insert the decimal value in its place.
|
||||
sb.Remove(fractionMatch.Index, fractionMatch.Length);
|
||||
sb.Insert(fractionMatch.Index, decimalValue);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static bool TryParse(string s, out double fraction)
|
||||
{
|
||||
try
|
||||
{
|
||||
fraction = Parse(s);
|
||||
}
|
||||
catch
|
||||
{
|
||||
fraction = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user