diff --git a/src/EncDotNet.Iso8211/Iso8211DataDescriptiveRecordReader.cs b/src/EncDotNet.Iso8211/Iso8211DataDescriptiveRecordReader.cs index 49496da..64ee021 100644 --- a/src/EncDotNet.Iso8211/Iso8211DataDescriptiveRecordReader.cs +++ b/src/EncDotNet.Iso8211/Iso8211DataDescriptiveRecordReader.cs @@ -491,12 +491,50 @@ internal static ImmutableArray ParseFormatControls(string foreach (var part in parts) { - var parsed = ParseSingleFormat(part.Trim(), out int repeatCount); + var trimmedPart = part.Trim(); + + // Check for parenthesized sub-groups, e.g. "(3b24)" or "2(b14,I(10))" + int parenStart = trimmedPart.IndexOf('('); + if (parenStart >= 0 && trimmedPart.EndsWith(')')) + { + // Parse any leading repeat count before the opening paren + int outerRepeat = 0; + for (int i = 0; i < parenStart; i++) + { + if (char.IsDigit(trimmedPart[i])) + { + outerRepeat = outerRepeat * 10 + (trimmedPart[i] - '0'); + } + else + { + // Not a digit before '(' — not a sub-group, fall through to ParseSingleFormat + outerRepeat = -1; + break; + } + } + + if (outerRepeat >= 0) + { + int count = outerRepeat > 0 ? outerRepeat : 1; + // Recursively parse the parenthesized content + var innerContent = trimmedPart.Substring(parenStart + 1, trimmedPart.Length - parenStart - 2); + var innerFormats = ParseFormatControls(innerContent); + + for (int r = 0; r < count; r++) + { + formats.AddRange(innerFormats); + } + + continue; + } + } + + var parsed = ParseSingleFormat(trimmedPart, out int repeatCount); if (parsed.HasValue) { // Expand repeat counts: e.g., "2b24" produces two b24 entries - int count = repeatCount > 0 ? repeatCount : 1; - for (int r = 0; r < count; r++) + int repeatN = repeatCount > 0 ? repeatCount : 1; + for (int r = 0; r < repeatN; r++) { formats.Add(parsed.Value); } diff --git a/tests/EndDotNet.UnitTests/Iso8211DataDescriptiveRecordReaderTests.cs b/tests/EndDotNet.UnitTests/Iso8211DataDescriptiveRecordReaderTests.cs index 3027cdf..53ffed6 100644 --- a/tests/EndDotNet.UnitTests/Iso8211DataDescriptiveRecordReaderTests.cs +++ b/tests/EndDotNet.UnitTests/Iso8211DataDescriptiveRecordReaderTests.cs @@ -921,6 +921,66 @@ public void ParseFormatControls_MultipleRepeatCounts_ExpandAll() Assert.Equal(4, formats[4].Width); } + [Fact] + public void ParseFormatControls_NestedParentheses_InnerGroupExpanded() + { + // Act — "(b11,(3b24))" should produce: b11, b24, b24, b24 + var formats = Iso8211DataDescriptiveRecordReader.ParseFormatControls("(b11,(3b24))"); + + // Assert + Assert.Equal(4, formats.Length); + + Assert.Equal(Iso8211SubfieldFormatType.UnsignedInteger, formats[0].FormatType); + Assert.Equal(1, formats[0].Width); + + for (int i = 1; i < 4; i++) + { + Assert.Equal(Iso8211SubfieldFormatType.SignedInteger, formats[i].FormatType); + Assert.Equal(4, formats[i].Width); + } + } + + [Fact] + public void ParseFormatControls_NestedParentheses_RepeatBeforeParen() + { + // Act — "(b11,3(b24))" should produce: b11, b24, b24, b24 + var formats = Iso8211DataDescriptiveRecordReader.ParseFormatControls("(b11,3(b24))"); + + // Assert + Assert.Equal(4, formats.Length); + + Assert.Equal(Iso8211SubfieldFormatType.UnsignedInteger, formats[0].FormatType); + Assert.Equal(1, formats[0].Width); + + for (int i = 1; i < 4; i++) + { + Assert.Equal(Iso8211SubfieldFormatType.SignedInteger, formats[i].FormatType); + Assert.Equal(4, formats[i].Width); + } + } + + [Fact] + public void ParseFormatControls_NestedParentheses_MixedGroup() + { + // Act — "(A,2(I(10),b14))" should produce: A, I(10), b14, I(10), b14 + var formats = Iso8211DataDescriptiveRecordReader.ParseFormatControls("(A,2(I(10),b14))"); + + // Assert + Assert.Equal(5, formats.Length); + + Assert.Equal(Iso8211SubfieldFormatType.CharacterData, formats[0].FormatType); + + Assert.Equal(Iso8211SubfieldFormatType.Integer, formats[1].FormatType); + Assert.Equal(10, formats[1].Width); + Assert.Equal(Iso8211SubfieldFormatType.UnsignedInteger, formats[2].FormatType); + Assert.Equal(4, formats[2].Width); + + Assert.Equal(Iso8211SubfieldFormatType.Integer, formats[3].FormatType); + Assert.Equal(10, formats[3].Width); + Assert.Equal(Iso8211SubfieldFormatType.UnsignedInteger, formats[4].FormatType); + Assert.Equal(4, formats[4].Width); + } + #endregion #region Repeating Group Tests