150 lines
6.7 KiB
C#
150 lines
6.7 KiB
C#
using System.Globalization;
|
||
using System.Text.RegularExpressions;
|
||
|
||
static class DimensionalToleranceParser
|
||
{
|
||
static readonly Regex FitDesignationRegex = new(
|
||
@"(?<![A-Za-z])(?<letters>[A-Za-z]{1,2})\s*(?<grade>01|0|1[0-8]|[1-9])(?!\d)",
|
||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||
|
||
static readonly Regex ExplicitDeviationRegex = new(
|
||
@"\(\s*(?<upper>[+\-−]?\s*\d+(?:[.,]\d+)?)\s*/\s*(?<lower>[+\-−]?\s*\d+(?:[.,]\d+)?)\s*\)",
|
||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||
|
||
static readonly Regex PlusMinusRegex = new(
|
||
@"[±]\s*(?<value>\d+(?:[.,]\d+)?)",
|
||
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
||
|
||
public static Dictionary<string, object?> Parse(
|
||
string text,
|
||
string rawText = "",
|
||
double? measurement = null,
|
||
string objectName = "")
|
||
{
|
||
var normalized = Normalize(text);
|
||
var dimensionKind = DetectDimensionKind(normalized, objectName);
|
||
var nominal = ExtractNominalSize(normalized, measurement, dimensionKind);
|
||
var designations = ExtractFitDesignations(normalized);
|
||
var deviations = ExtractDeviations(normalized);
|
||
var warnings = new List<string>();
|
||
|
||
if (dimensionKind == "radius" && designations.Count > 0)
|
||
warnings.Add("fit_designation_applied_to_radius_dimension");
|
||
if (designations.Count == 1)
|
||
warnings.Add("single_tolerance_designation_does_not_define_complete_hole_shaft_fit");
|
||
if (nominal == null)
|
||
warnings.Add("nominal_size_not_parsed");
|
||
|
||
var hole = designations.FirstOrDefault(item => Equals(item["role"], "hole"));
|
||
var shaft = designations.FirstOrDefault(item => Equals(item["role"], "shaft"));
|
||
var fitCode = string.Join("/", designations.Select(item => Convert.ToString(item["designation"], CultureInfo.InvariantCulture)));
|
||
|
||
return new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase)
|
||
{
|
||
["dimension_kind"] = dimensionKind,
|
||
["nominal_size_mm"] = nominal,
|
||
["interface_diameter_mm"] = nominal == null ? null : dimensionKind == "radius" ? nominal * 2 : nominal,
|
||
["fit_code"] = string.IsNullOrWhiteSpace(fitCode) ? null : fitCode,
|
||
["fit_grade"] = string.IsNullOrWhiteSpace(fitCode) ? null : fitCode,
|
||
["fit_designations"] = designations,
|
||
["hole_tolerance_designation"] = hole?["designation"],
|
||
["shaft_tolerance_designation"] = shaft?["designation"],
|
||
["fit_completeness"] = hole != null && shaft != null ? "hole_and_shaft_pair" : designations.Count > 0 ? "single_designation" : "none",
|
||
["upper_deviation_mm"] = deviations.Upper,
|
||
["lower_deviation_mm"] = deviations.Lower,
|
||
["deviation_source"] = deviations.Source,
|
||
["raw_text"] = rawText,
|
||
["text"] = normalized,
|
||
["parse_warnings"] = warnings
|
||
};
|
||
}
|
||
|
||
public static bool LooksLikeTolerance(string rawText, string text)
|
||
{
|
||
var merged = rawText + " " + text;
|
||
return merged.Contains(@"\S", StringComparison.OrdinalIgnoreCase) ||
|
||
FitDesignationRegex.IsMatch(Normalize(merged)) ||
|
||
Regex.IsMatch(merged, @"[+\-±]\s*\d+(?:\.\d+)?", RegexOptions.CultureInvariant);
|
||
}
|
||
|
||
static List<Dictionary<string, object?>> ExtractFitDesignations(string text)
|
||
{
|
||
var result = new List<Dictionary<string, object?>>();
|
||
foreach (Match match in FitDesignationRegex.Matches(text))
|
||
{
|
||
var letters = match.Groups["letters"].Value;
|
||
var grade = match.Groups["grade"].Value;
|
||
var role = letters == letters.ToUpperInvariant()
|
||
? "hole"
|
||
: letters == letters.ToLowerInvariant()
|
||
? "shaft"
|
||
: "unknown";
|
||
result.Add(new Dictionary<string, object?>
|
||
{
|
||
["designation"] = letters + grade,
|
||
["fundamental_deviation"] = letters,
|
||
["it_grade"] = grade,
|
||
["role"] = role
|
||
});
|
||
}
|
||
return result;
|
||
}
|
||
|
||
static string DetectDimensionKind(string text, string objectName)
|
||
{
|
||
if (Regex.IsMatch(text, @"^\s*R\s*\d", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) ||
|
||
objectName.Contains("RadialDimension", StringComparison.OrdinalIgnoreCase))
|
||
return "radius";
|
||
if (text.IndexOfAny(['Φ', 'φ', '⌀', 'Ø']) >= 0 ||
|
||
objectName.Contains("DiametricDimension", StringComparison.OrdinalIgnoreCase))
|
||
return "diameter";
|
||
return "linear_or_unspecified";
|
||
}
|
||
|
||
static double? ExtractNominalSize(string text, double? measurement, string dimensionKind)
|
||
{
|
||
var pattern = dimensionKind switch
|
||
{
|
||
"radius" => @"^\s*R\s*(?<value>\d+(?:[.,]\d+)?)",
|
||
"diameter" => @"[Φφ⌀Ø]\s*(?<value>\d+(?:[.,]\d+)?)",
|
||
_ => @"(?<value>\d+(?:[.,]\d+)?)"
|
||
};
|
||
var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||
if (match.Success && TryParseNumber(match.Groups["value"].Value, out var parsed))
|
||
return parsed;
|
||
return measurement is > 0 ? measurement : null;
|
||
}
|
||
|
||
static (double? Upper, double? Lower, string? Source) ExtractDeviations(string text)
|
||
{
|
||
var explicitMatch = ExplicitDeviationRegex.Match(text);
|
||
if (explicitMatch.Success &&
|
||
TryParseNumber(explicitMatch.Groups["upper"].Value, out var upper) &&
|
||
TryParseNumber(explicitMatch.Groups["lower"].Value, out var lower))
|
||
return (upper, lower, "explicit_upper_lower");
|
||
|
||
var plusMinusMatch = PlusMinusRegex.Match(text);
|
||
if (plusMinusMatch.Success && TryParseNumber(plusMinusMatch.Groups["value"].Value, out var value))
|
||
return (value, -value, "explicit_plus_minus");
|
||
|
||
return (null, null, null);
|
||
}
|
||
|
||
static bool TryParseNumber(string text, out double value) =>
|
||
double.TryParse(
|
||
text.Replace(" ", "", StringComparison.Ordinal).Replace('−', '-').Replace(',', '.'),
|
||
NumberStyles.Float,
|
||
CultureInfo.InvariantCulture,
|
||
out value);
|
||
|
||
static string Normalize(string text)
|
||
{
|
||
var value = text ?? "";
|
||
value = Regex.Replace(value, @"\\S([^;]+)\^([^;]*);", "$1/$2", RegexOptions.CultureInvariant);
|
||
value = Regex.Replace(value, @"\\S([^;]+);", "$1", RegexOptions.CultureInvariant);
|
||
value = Regex.Replace(value, @"\\[AH][^;]*;", "", RegexOptions.CultureInvariant);
|
||
value = value.Replace("{", "", StringComparison.Ordinal).Replace("}", "", StringComparison.Ordinal);
|
||
return Regex.Replace(value, @"\s+", " ").Trim();
|
||
}
|
||
}
|