From 7f1fd7b8102cfb578f797f93f53f500723f8dc04 Mon Sep 17 00:00:00 2001 From: Soar360 Date: Fri, 16 Jan 2026 13:19:52 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=B0=BA=E5=AF=B8=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BC=98=E5=8C=96=EF=BC=8C=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E5=8C=96=E7=B1=BB=E5=9E=8B=E4=B8=8E=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次提交对尺寸字符串解析进行了结构性优化:将 SizeHelper 拆为 partial 并引入多条正则与单位换算字典,提升解析能力和健壮性;新增 Dimension、DimensionKind、DimensionUnit 等结构化类型,为多维度尺寸表达提供基础;新增 Extract 接口(暂未实现),为后续结构化解析做准备。 --- src/LuYao.Common/Globalization/Dimension.cs | 12 ++++++++ .../Globalization/DimensionKind.cs | 30 +++++++++++++++++++ .../Globalization/DimensionUnit.cs | 16 ++++++++++ .../Globalization/SizeHelper.Dimension.cs | 12 ++++++++ src/LuYao.Common/Globalization/SizeHelper.cs | 26 ++++++++-------- 5 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 src/LuYao.Common/Globalization/Dimension.cs create mode 100644 src/LuYao.Common/Globalization/DimensionKind.cs create mode 100644 src/LuYao.Common/Globalization/DimensionUnit.cs create mode 100644 src/LuYao.Common/Globalization/SizeHelper.Dimension.cs diff --git a/src/LuYao.Common/Globalization/Dimension.cs b/src/LuYao.Common/Globalization/Dimension.cs new file mode 100644 index 0000000..c51620d --- /dev/null +++ b/src/LuYao.Common/Globalization/Dimension.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace LuYao.Globalization; + +public record struct DimensionItem(DimensionKind Kind, decimal Value); + +public class Dimension +{ + public DimensionUnit Unit { get; set; } = DimensionUnit.Centimeter; + public List Items { get; set; } = new List(); + public bool IsEmpty => Items.Count == 0; +} diff --git a/src/LuYao.Common/Globalization/DimensionKind.cs b/src/LuYao.Common/Globalization/DimensionKind.cs new file mode 100644 index 0000000..95b4414 --- /dev/null +++ b/src/LuYao.Common/Globalization/DimensionKind.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuYao.Globalization; + +/// +/// Specifies the type of dimension represented, such as length, width, or height. +/// +public enum DimensionKind +{ + /// + /// 未指定 + /// + Unspecified, + /// + /// 长度 + /// + Length, + /// + /// 宽度 + /// + Width, + /// + /// 高度 + /// + Height, +} diff --git a/src/LuYao.Common/Globalization/DimensionUnit.cs b/src/LuYao.Common/Globalization/DimensionUnit.cs new file mode 100644 index 0000000..e816d6e --- /dev/null +++ b/src/LuYao.Common/Globalization/DimensionUnit.cs @@ -0,0 +1,16 @@ +namespace LuYao.Globalization; + +/// +/// 长度单位 +/// +public enum DimensionUnit +{ + /// + /// 厘米 + /// + Centimeter, + /// + /// 英寸 + /// + Inch, +} diff --git a/src/LuYao.Common/Globalization/SizeHelper.Dimension.cs b/src/LuYao.Common/Globalization/SizeHelper.Dimension.cs new file mode 100644 index 0000000..34dd102 --- /dev/null +++ b/src/LuYao.Common/Globalization/SizeHelper.Dimension.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace LuYao.Globalization; + +partial class SizeHelper +{ + public static IReadOnlyList Extract(string size) + { + throw new NotImplementedException(); + } +} diff --git a/src/LuYao.Common/Globalization/SizeHelper.cs b/src/LuYao.Common/Globalization/SizeHelper.cs index 008fc08..7d645cd 100644 --- a/src/LuYao.Common/Globalization/SizeHelper.cs +++ b/src/LuYao.Common/Globalization/SizeHelper.cs @@ -51,7 +51,7 @@ namespace LuYao.Globalization; /// } /// /// -public static class SizeHelper +public static partial class SizeHelper { // Compiled regex patterns for better performance private static readonly Regex ParenthesesPattern = new Regex(@"\(([^)]+)\)", RegexOptions.Compiled); @@ -60,7 +60,7 @@ public static class SizeHelper private static readonly Regex UnitPattern = new Regex(@"(inch|in|mm|cm|dm|m)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex NumberPattern = new Regex(@"(\d+(?:\.\d+)?)", RegexOptions.Compiled); private static readonly Regex SingleValuePattern = new Regex(@"^\s*(\d+(?:\.\d+)?)\s*(inch|in|mm|cm|dm|m)?\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - + // Unit conversion factors (relative to centimeters) private static readonly Dictionary UnitConversions = new Dictionary(StringComparer.OrdinalIgnoreCase) { @@ -127,7 +127,7 @@ public static bool ExtractSize(string size, out decimal[] arr) arr = new decimal[] { singleValue.Value }; return true; } - + arr = new decimal[0]; return false; } @@ -166,7 +166,7 @@ private static List ExtractSizeGroups(string size) // First, extract groups in parentheses that contain separators var matches = ParenthesesPattern.Matches(size); - + foreach (Match match in matches) { if (match.Groups.Count > 1) @@ -200,7 +200,7 @@ private static List SplitIntoSubGroups(string text) { var result = new List(); var currentGroup = new System.Text.StringBuilder(); - + // Split by common separators: / , ; | using simple character iteration foreach (char c in text) { @@ -218,20 +218,20 @@ private static List SplitIntoSubGroups(string text) currentGroup.Append(c); } } - + // Add the last group if any var lastGroup = currentGroup.ToString().Trim(); if (!string.IsNullOrWhiteSpace(lastGroup) && ContainsIgnoreCase(lastGroup, "x", "*")) { result.Add(lastGroup); } - + // If no valid groups found, return the original text as a single group if (result.Count == 0 && ContainsIgnoreCase(text, "x", "*")) { result.Add(text); } - + return result; } @@ -271,7 +271,7 @@ private static List ExtractSizeFromGroup(string group) // 1. The entire input (after trimming) must be just number + unit // 2. A unit must be present (no bare numbers allowed) var match = SingleValuePattern.Match(input); - + if (match.Success && match.Groups[1].Success && !string.IsNullOrWhiteSpace(match.Groups[1].Value)) { // Require a unit to be present when there's no separator @@ -279,7 +279,7 @@ private static List ExtractSizeFromGroup(string group) { return null; } - + if (decimal.TryParse(match.Groups[1].Value, out decimal value)) { string unit = match.Groups[2].Value; @@ -317,8 +317,8 @@ private static List ExtractWithPerValueUnits(string group) { if (decimal.TryParse(match.Groups[1].Value, out decimal value)) { - string unit = match.Groups[2].Success && !string.IsNullOrEmpty(match.Groups[2].Value) - ? match.Groups[2].Value + string unit = match.Groups[2].Success && !string.IsNullOrEmpty(match.Groups[2].Value) + ? match.Groups[2].Value : "CM"; decimal converted = ConvertToCentimeters(value, unit); results.Add(converted); @@ -372,7 +372,7 @@ private static string DetermineUnit(string group) return "DM"; if (ContainsIgnoreCase(group, "M") && !ContainsIgnoreCase(group, "CM", "MM", "DM")) return "M"; - + return "CM"; // Default } From db3bada910ea03db73a80da146cb3b01a0e2b43a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:15:46 +0800 Subject: [PATCH 2/2] Implement SizeHelper.Extract with dimension label parsing (#52) * Initial plan * Implement SizeHelper.Extract with dimension parsing - 48/50 tests passing Co-authored-by: Soar360 <15421284+Soar360@users.noreply.github.com> * Fix PerValueUnitPattern to support dimension labels - all 50 tests passing Co-authored-by: Soar360 <15421284+Soar360@users.noreply.github.com> * Fix casing in HEIGHT to Height for consistency Co-authored-by: Soar360 <15421284+Soar360@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Soar360 <15421284+Soar360@users.noreply.github.com> --- .../Globalization/SizeHelper.Dimension.cs | 359 +++++++++++++++++- src/LuYao.Common/Globalization/SizeHelper.cs | 2 +- .../Globalization/SizeHelperTests.cs | 295 ++++++++++++++ 3 files changed, 654 insertions(+), 2 deletions(-) diff --git a/src/LuYao.Common/Globalization/SizeHelper.Dimension.cs b/src/LuYao.Common/Globalization/SizeHelper.Dimension.cs index 34dd102..0769092 100644 --- a/src/LuYao.Common/Globalization/SizeHelper.Dimension.cs +++ b/src/LuYao.Common/Globalization/SizeHelper.Dimension.cs @@ -1,12 +1,369 @@ using System; using System.Collections.Generic; +using System.Text.RegularExpressions; namespace LuYao.Globalization; partial class SizeHelper { + private static readonly Regex DimensionLabelPattern = new Regex(@"(\d+(?:\.\d+)?)\s*([''""]*)?\s*([WwHhLl]|Width|Height|Length)?", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + /// + /// 从字符串中提取尺寸信息,返回包含单位和维度信息的Dimension列表 + /// + /// 包含尺寸信息的字符串 + /// 解析后的Dimension列表 + /// + /// 提取规则: + /// + /// 支持厘米(cm)和英寸(in/'')两种单位 + /// 如果输入单位是cm或in,保持原单位;其他单位转换为cm + /// 无单位标记的输入(如1x2x3),DimensionKind为Unspecified + /// 支持维度标记: W/Width(宽), H/Height(高), L/Length(长) + /// 支持英寸的双引号标记: 10'' 或 10" + /// 支持括号内的多组尺寸,每组返回独立的Dimension对象 + /// + /// public static IReadOnlyList Extract(string size) { - throw new NotImplementedException(); + if (string.IsNullOrWhiteSpace(size)) + { + return new List(); + } + + var results = new List(); + + // Extract all groups (including those in parentheses) + var groups = ExtractSizeGroups(size); + + foreach (var group in groups) + { + if (string.IsNullOrWhiteSpace(group)) + continue; + + var dimension = ParseDimensionGroup(group); + if (dimension != null && !dimension.IsEmpty) + { + results.Add(dimension); + } + } + + return results; + } + + /// + /// 解析单个尺寸组,返回Dimension对象 + /// + private static Dimension? ParseDimensionGroup(string group) + { + if (string.IsNullOrWhiteSpace(group)) + return null; + + var dimension = new Dimension(); + + // 检测单位 + DimensionUnit detectedUnit = DetectDimensionUnit(group); + dimension.Unit = detectedUnit; + + // 检查是否有分隔符 + bool hasSeparator = ContainsIgnoreCase(group, "x", "*"); + + if (!hasSeparator) + { + // 单个值 + var item = ParseSingleDimensionValue(group); + if (item.HasValue) + { + dimension.Items.Add(item.Value); + } + return dimension; + } + + // 多个值的情况 + bool hasPerValueUnit = HasPerValueUnit(group); + + if (hasPerValueUnit) + { + // 每个值有自己的单位 + dimension.Items = ParseWithPerValueUnits(group, dimension.Unit); + } + else + { + // 所有值共享一个单位 + dimension.Items = ParseWithSingleUnit(group, dimension.Unit); + } + + return dimension; + } + + /// + /// 检测并返回尺寸组的单位类型 + /// + private static DimensionUnit DetectDimensionUnit(string group) + { + // 检查是否有英寸标记 ('' 或 " 或 in/inch) + if (group.Contains("''") || group.Contains("\"") || + ContainsIgnoreCase(group, "inch") || + System.Text.RegularExpressions.Regex.IsMatch(group, @"\d+\s*in\b", System.Text.RegularExpressions.RegexOptions.IgnoreCase)) + { + return DimensionUnit.Inch; + } + + // 检查是否有厘米标记 + if (ContainsIgnoreCase(group, "cm")) + { + return DimensionUnit.Centimeter; + } + + // 检查是否有其他单位(mm, dm, m)- 这些需要转换为cm + if (ContainsIgnoreCase(group, "mm", "dm") || + (ContainsIgnoreCase(group, "m") && !ContainsIgnoreCase(group, "cm", "mm", "dm"))) + { + return DimensionUnit.Centimeter; // 转换为厘米 + } + + // 默认无单位,使用厘米 + return DimensionUnit.Centimeter; + } + + /// + /// 解析单个尺寸值(不含分隔符) + /// + private static DimensionItem? ParseSingleDimensionValue(string input) + { + if (string.IsNullOrWhiteSpace(input)) + return null; + + // 提取数值和维度标签 + var match = DimensionLabelPattern.Match(input); + + if (!match.Success || !match.Groups[1].Success) + return null; + + if (!decimal.TryParse(match.Groups[1].Value, out decimal value)) + return null; + + // 检测维度类型 + DimensionKind kind = DimensionKind.Unspecified; + if (match.Groups[3].Success && !string.IsNullOrEmpty(match.Groups[3].Value)) + { + kind = ParseDimensionKind(match.Groups[3].Value); + } + + // 单位转换(如果需要) + decimal finalValue = ConvertToTargetUnit(value, input, DetectDimensionUnit(input)); + + return new DimensionItem(kind, finalValue); + } + + /// + /// 解析带有每个值独立单位的尺寸组 + /// + private static List ParseWithPerValueUnits(string group, DimensionUnit targetUnit) + { + var items = new List(); + + // 先按分隔符分割,然后逐个解析 + var parts = System.Text.RegularExpressions.Regex.Split(group, @"\s*[xX\*]\s*"); + + foreach (var part in parts) + { + if (string.IsNullOrWhiteSpace(part)) + continue; + + var trimmed = part.Trim(); + + // 解析单个部分: 数字 + 可选英寸标记 + 可选维度标签 + 可选单位 + 可选维度标签 + // 英寸标记可以是: '', ", ' + var match = System.Text.RegularExpressions.Regex.Match(trimmed, + @"^(\d+(?:\.\d+)?)\s*(''|[''""])?\s*([WwHhLl]|Width|Height|Length)?\s*(inch|in|mm|cm|dm|m)?\s*([WwHhLl]|Width|Height|Length)?$", + System.Text.RegularExpressions.RegexOptions.IgnoreCase); + + if (!match.Success || !match.Groups[1].Success) + continue; + + if (!decimal.TryParse(match.Groups[1].Value, out decimal value)) + continue; + + // 检测英寸标记 (group 2) + bool hasInchMarker = match.Groups[2].Success && !string.IsNullOrEmpty(match.Groups[2].Value); + + // 检测单位 (group 4) + string unitStr = match.Groups[4].Success ? match.Groups[4].Value : ""; + + // 检测维度标签 (group 3 在单位前, group 5 在单位后) + // 优先使用单位后的标签 + string labelStr = ""; + if (match.Groups[5].Success && !string.IsNullOrEmpty(match.Groups[5].Value)) + { + labelStr = match.Groups[5].Value; + } + else if (match.Groups[3].Success && !string.IsNullOrEmpty(match.Groups[3].Value)) + { + labelStr = match.Groups[3].Value; + } + + // 转换值到目标单位 + decimal convertedValue = ConvertValueToUnit(value, unitStr, hasInchMarker, targetUnit); + + // 解析维度类型 + DimensionKind kind = DimensionKind.Unspecified; + if (!string.IsNullOrEmpty(labelStr)) + { + kind = ParseDimensionKind(labelStr); + } + + items.Add(new DimensionItem(kind, convertedValue)); + } + + return items; + } + + /// + /// 解析带有单一单位的尺寸组 + /// + private static List ParseWithSingleUnit(string group, DimensionUnit targetUnit) + { + var items = new List(); + + // 确定组的单位 + string detectedUnitStr = DetermineUnit(group); + + // 移除单位标记 + var cleaned = UnitPattern.Replace(group, string.Empty); + // 移除英寸标记 + cleaned = cleaned.Replace("''", "").Replace("\"", ""); + + // 提取所有数值和可能的维度标签 + var pattern = @"(\d+(?:\.\d+)?)\s*([WwHhLl]|Width|Height|Length)?"; + var matches = System.Text.RegularExpressions.Regex.Matches(cleaned, pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase); + + foreach (System.Text.RegularExpressions.Match match in matches) + { + if (!match.Groups[1].Success || string.IsNullOrWhiteSpace(match.Groups[1].Value)) + continue; + + if (!decimal.TryParse(match.Groups[1].Value, out decimal value)) + continue; + + if (value == 0) + continue; + + // 转换值到目标单位 + bool hasInchMarker = group.Contains("''") || group.Contains("\""); + decimal convertedValue = ConvertValueToUnit(value, detectedUnitStr, hasInchMarker, targetUnit); + + // 检测维度类型 + DimensionKind kind = DimensionKind.Unspecified; + if (match.Groups[2].Success && !string.IsNullOrEmpty(match.Groups[2].Value)) + { + kind = ParseDimensionKind(match.Groups[2].Value); + } + + items.Add(new DimensionItem(kind, convertedValue)); + } + + return items; + } + + /// + /// 将值转换到目标单位 + /// + private static decimal ConvertValueToUnit(decimal value, string unitStr, bool hasInchMarker, DimensionUnit targetUnit) + { + // 如果有英寸标记 + if (hasInchMarker || ContainsIgnoreCase(unitStr, "inch", "in")) + { + if (targetUnit == DimensionUnit.Inch) + { + return value; // 保持英寸 + } + else + { + return value * 2.54m; // 转换为厘米 + } + } + + // 根据单位字符串转换 + if (ContainsIgnoreCase(unitStr, "cm")) + { + return value; // 已经是厘米 + } + else if (ContainsIgnoreCase(unitStr, "mm")) + { + return value * 0.1m; // 毫米转厘米 + } + else if (ContainsIgnoreCase(unitStr, "dm")) + { + return value * 10m; // 分米转厘米 + } + else if (ContainsIgnoreCase(unitStr, "m") && !ContainsIgnoreCase(unitStr, "cm", "mm", "dm")) + { + return value * 100m; // 米转厘米 + } + + // 无单位或无法识别,默认为厘米 + return value; + } + + /// + /// 转换值到目标单位(根据输入字符串检测单位) + /// + private static decimal ConvertToTargetUnit(decimal value, string input, DimensionUnit targetUnit) + { + // 检测输入中的单位 + bool hasInchMarker = input.Contains("''") || input.Contains("\""); + + if (hasInchMarker || ContainsIgnoreCase(input, "inch", "in")) + { + if (targetUnit == DimensionUnit.Inch) + { + return value; + } + else + { + return value * 2.54m; + } + } + + // 其他单位处理 + if (ContainsIgnoreCase(input, "cm")) + { + return value; + } + else if (ContainsIgnoreCase(input, "mm")) + { + return value * 0.1m; + } + else if (ContainsIgnoreCase(input, "dm")) + { + return value * 10m; + } + else if (ContainsIgnoreCase(input, "m") && !ContainsIgnoreCase(input, "cm", "mm", "dm")) + { + return value * 100m; + } + + return value; + } + + /// + /// 解析维度类型(宽、高、长) + /// + private static DimensionKind ParseDimensionKind(string label) + { + if (string.IsNullOrEmpty(label)) + return DimensionKind.Unspecified; + + label = label.ToUpperInvariant(); + + if (label == "W" || label == "WIDTH") + return DimensionKind.Width; + else if (label == "H" || label == "HEIGHT") + return DimensionKind.Height; + else if (label == "L" || label == "LENGTH") + return DimensionKind.Length; + + return DimensionKind.Unspecified; } } diff --git a/src/LuYao.Common/Globalization/SizeHelper.cs b/src/LuYao.Common/Globalization/SizeHelper.cs index 7d645cd..64aba52 100644 --- a/src/LuYao.Common/Globalization/SizeHelper.cs +++ b/src/LuYao.Common/Globalization/SizeHelper.cs @@ -55,7 +55,7 @@ public static partial class SizeHelper { // Compiled regex patterns for better performance private static readonly Regex ParenthesesPattern = new Regex(@"\(([^)]+)\)", RegexOptions.Compiled); - private static readonly Regex PerValueUnitPattern = new Regex(@"\d+(?:\.\d+)?\s*(inch|in|mm|cm|dm|m)\s*[x\*]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex PerValueUnitPattern = new Regex(@"\d+(?:\.\d+)?\s*(inch|in|mm|cm|dm|m)\s*([WwHhLl]|Width|Height|Length)?\s*[x\*]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex NumberWithOptionalUnitPattern = new Regex(@"(\d+(?:\.\d+)?)\s*(inch|in|mm|cm|dm|m)?", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex UnitPattern = new Regex(@"(inch|in|mm|cm|dm|m)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex NumberPattern = new Regex(@"(\d+(?:\.\d+)?)", RegexOptions.Compiled); diff --git a/tests/LuYao.Common.UnitTests/Globalization/SizeHelperTests.cs b/tests/LuYao.Common.UnitTests/Globalization/SizeHelperTests.cs index 642fb3d..e0de6d6 100644 --- a/tests/LuYao.Common.UnitTests/Globalization/SizeHelperTests.cs +++ b/tests/LuYao.Common.UnitTests/Globalization/SizeHelperTests.cs @@ -503,4 +503,299 @@ public void ExtractSize_MixedSlashAndParentheses_ReturnsCorrectArray() Assert.AreEqual(1m, arr[4]); // 10 mm = 1 cm Assert.AreEqual(1.5m, arr[5]); // 15 mm = 1.5 cm } + + #region Extract Method Tests + + /// + /// 测试无标记输入:1x2x3,期望 DimensionKind 为 Unspecified + /// + [TestMethod] + public void Extract_UnmarkedInput_ReturnsUnspecifiedKind() + { + var result = SizeHelper.Extract("1x2x3"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + Assert.AreEqual(DimensionKind.Unspecified, dimension.Items[0].Kind); + Assert.AreEqual(DimensionKind.Unspecified, dimension.Items[1].Kind); + Assert.AreEqual(DimensionKind.Unspecified, dimension.Items[2].Kind); + Assert.AreEqual(1m, dimension.Items[0].Value); + Assert.AreEqual(2m, dimension.Items[1].Value); + Assert.AreEqual(3m, dimension.Items[2].Value); + } + + /// + /// 测试英寸标记和维度标签:10''W X 36''H + /// + [TestMethod] + public void Extract_InchWithDimensionLabels_ParsesCorrectly() + { + var result = SizeHelper.Extract("10''W X 36''H"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Inch, dimension.Unit); + Assert.AreEqual(2, dimension.Items.Count); + + Assert.AreEqual(DimensionKind.Width, dimension.Items[0].Kind); + Assert.AreEqual(10m, dimension.Items[0].Value); + + Assert.AreEqual(DimensionKind.Height, dimension.Items[1].Kind); + Assert.AreEqual(36m, dimension.Items[1].Value); + } + + /// + /// 测试双组解析:10cmx10cmx10cm(3.94x3.94x3.94in),期望返回两个Dimension对象 + /// + [TestMethod] + public void Extract_TwoGroups_ReturnsTwoDimensions() + { + var result = SizeHelper.Extract("10cmx10cmx10cm(3.94x3.94x3.94in)"); + + Assert.AreEqual(2, result.Count); + + // 第一组:厘米 + var dim1 = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dim1.Unit); + Assert.AreEqual(3, dim1.Items.Count); + Assert.AreEqual(10m, dim1.Items[0].Value); + Assert.AreEqual(10m, dim1.Items[1].Value); + Assert.AreEqual(10m, dim1.Items[2].Value); + + // 第二组:英寸 + var dim2 = result[1]; + Assert.AreEqual(DimensionUnit.Inch, dim2.Unit); + Assert.AreEqual(3, dim2.Items.Count); + Assert.AreEqual(3.94m, dim2.Items[0].Value); + Assert.AreEqual(3.94m, dim2.Items[1].Value); + Assert.AreEqual(3.94m, dim2.Items[2].Value); + } + + /// + /// 测试厘米输入保持原单位 + /// + [TestMethod] + public void Extract_CentimeterInput_PreservesUnit() + { + var result = SizeHelper.Extract("10x20x30cm"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + Assert.AreEqual(10m, dimension.Items[0].Value); + Assert.AreEqual(20m, dimension.Items[1].Value); + Assert.AreEqual(30m, dimension.Items[2].Value); + } + + /// + /// 测试英寸输入保持原单位 + /// + [TestMethod] + public void Extract_InchInput_PreservesUnit() + { + var result = SizeHelper.Extract("10x20x30in"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Inch, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + Assert.AreEqual(10m, dimension.Items[0].Value); + Assert.AreEqual(20m, dimension.Items[1].Value); + Assert.AreEqual(30m, dimension.Items[2].Value); + } + + /// + /// 测试毫米转换为厘米 + /// + [TestMethod] + public void Extract_MillimeterInput_ConvertsToCentimeter() + { + var result = SizeHelper.Extract("100x200x300mm"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + Assert.AreEqual(10m, dimension.Items[0].Value); // 100mm = 10cm + Assert.AreEqual(20m, dimension.Items[1].Value); // 200mm = 20cm + Assert.AreEqual(30m, dimension.Items[2].Value); // 300mm = 30cm + } + + /// + /// 测试米转换为厘米 + /// + [TestMethod] + public void Extract_MeterInput_ConvertsToCentimeter() + { + var result = SizeHelper.Extract("1x2x3m"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + Assert.AreEqual(100m, dimension.Items[0].Value); // 1m = 100cm + Assert.AreEqual(200m, dimension.Items[1].Value); // 2m = 200cm + Assert.AreEqual(300m, dimension.Items[2].Value); // 3m = 300cm + } + + /// + /// 测试双引号标记的英寸 + /// + [TestMethod] + public void Extract_DoubleQuoteInch_ParsesCorrectly() + { + var result = SizeHelper.Extract("10\"x20\"x30\""); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Inch, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + Assert.AreEqual(10m, dimension.Items[0].Value); + Assert.AreEqual(20m, dimension.Items[1].Value); + Assert.AreEqual(30m, dimension.Items[2].Value); + } + + /// + /// 测试空字符串输入 + /// + [TestMethod] + public void Extract_EmptyString_ReturnsEmptyList() + { + var result = SizeHelper.Extract(""); + + Assert.IsNotNull(result); + Assert.AreEqual(0, result.Count); + } + + /// + /// 测试null输入 + /// + [TestMethod] + public void Extract_NullString_ReturnsEmptyList() + { + var result = SizeHelper.Extract(null); + + Assert.IsNotNull(result); + Assert.AreEqual(0, result.Count); + } + + /// + /// 测试维度标签:Width, Height, Length + /// + [TestMethod] + public void Extract_DimensionLabels_ParsesCorrectly() + { + var result = SizeHelper.Extract("10cmW x 20cmH x 30cmL"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + + Assert.AreEqual(DimensionKind.Width, dimension.Items[0].Kind); + Assert.AreEqual(10m, dimension.Items[0].Value); + + Assert.AreEqual(DimensionKind.Height, dimension.Items[1].Kind); + Assert.AreEqual(20m, dimension.Items[1].Value); + + Assert.AreEqual(DimensionKind.Length, dimension.Items[2].Kind); + Assert.AreEqual(30m, dimension.Items[2].Value); + } + + /// + /// 测试完整维度标签词:Width, Height, Length + /// + [TestMethod] + public void Extract_FullDimensionLabels_ParsesCorrectly() + { + var result = SizeHelper.Extract("10Width x 20Height x 30Length cm"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + + Assert.AreEqual(DimensionKind.Width, dimension.Items[0].Kind); + Assert.AreEqual(DimensionKind.Height, dimension.Items[1].Kind); + Assert.AreEqual(DimensionKind.Length, dimension.Items[2].Kind); + } + + /// + /// 测试混合单位和维度标签 + /// + [TestMethod] + public void Extract_MixedUnitsWithLabels_ParsesCorrectly() + { + var result = SizeHelper.Extract("10cmW x 20''H"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + // 当有多种单位时,优先检测到的单位作为主单位 + Assert.AreEqual(2, dimension.Items.Count); + + Assert.AreEqual(DimensionKind.Width, dimension.Items[0].Kind); + Assert.AreEqual(DimensionKind.Height, dimension.Items[1].Kind); + } + + /// + /// 测试小数值 + /// + [TestMethod] + public void Extract_DecimalValues_ParsesCorrectly() + { + var result = SizeHelper.Extract("10.5x20.3x30.7cm"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + Assert.AreEqual(10.5m, dimension.Items[0].Value); + Assert.AreEqual(20.3m, dimension.Items[1].Value); + Assert.AreEqual(30.7m, dimension.Items[2].Value); + } + + /// + /// 测试分米转换为厘米 + /// + [TestMethod] + public void Extract_DecimeterInput_ConvertsToCentimeter() + { + var result = SizeHelper.Extract("1x2x3dm"); + + Assert.AreEqual(1, result.Count); + var dimension = result[0]; + Assert.AreEqual(DimensionUnit.Centimeter, dimension.Unit); + Assert.AreEqual(3, dimension.Items.Count); + Assert.AreEqual(10m, dimension.Items[0].Value); // 1dm = 10cm + Assert.AreEqual(20m, dimension.Items[1].Value); // 2dm = 20cm + Assert.AreEqual(30m, dimension.Items[2].Value); // 3dm = 30cm + } + + /// + /// 测试多括号组 + /// + [TestMethod] + public void Extract_MultipleParenthesesGroups_ParsesCorrectly() + { + var result = SizeHelper.Extract("10x20cm(5x6in)(7x8mm)"); + + Assert.AreEqual(3, result.Count); + + // 第一组:厘米 + Assert.AreEqual(DimensionUnit.Centimeter, result[0].Unit); + Assert.AreEqual(2, result[0].Items.Count); + + // 第二组:英寸 + Assert.AreEqual(DimensionUnit.Inch, result[1].Unit); + Assert.AreEqual(2, result[1].Items.Count); + + // 第三组:毫米转厘米 + Assert.AreEqual(DimensionUnit.Centimeter, result[2].Unit); + Assert.AreEqual(2, result[2].Items.Count); + } + + #endregion }