Skip to content

Commit 4eb7083

Browse files
authored
feat: add support for multiple multi-line string properties with block scalar handling (#13)
1 parent b54dc4f commit 4eb7083

3 files changed

Lines changed: 72 additions & 11 deletions

File tree

src/Yamlify/Writer/Utf8YamlWriter.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,27 @@ public void WriteString(string? value)
434434
public void WriteString(ReadOnlySpan<char> value)
435435
{
436436
WriteScalarPrelude();
437+
438+
// Handle block scalar case specially
439+
if (!_inFlowContext && ContainsNewline(value) &&
440+
(_options.DefaultScalarStyle is ScalarStyle.Any or ScalarStyle.Literal))
441+
{
442+
WriteLiteralScalarValue(value);
443+
SetNotFirstInContainer();
444+
445+
// For block scalars:
446+
// - If string ends with newline (clip/keep), we already wrote the newline
447+
// as part of the content, so don't set _needsNewLine
448+
// - If string doesn't end with newline (strip), we need a newline before
449+
// the next property
450+
bool endsWithNewline = value.Length > 0 && value[^1] == '\n';
451+
if (!endsWithNewline)
452+
{
453+
_needsNewLine = true;
454+
}
455+
return;
456+
}
457+
437458
WriteScalarValue(value);
438459
WriteScalarPostlude();
439460
}
@@ -678,17 +699,9 @@ private void WriteScalarValue(ReadOnlySpan<char> value)
678699
return;
679700
}
680701

681-
// Check if we should use literal block style for multi-line strings
682-
// Use literal block style when:
683-
// 1. Not in flow context (block scalars not allowed in flow)
684-
// 2. String contains actual newlines (\n)
685-
// 3. DefaultScalarStyle is Any (auto-detect) or Literal
686-
if (!_inFlowContext && ContainsNewline(value) &&
687-
(_options.DefaultScalarStyle is ScalarStyle.Any or ScalarStyle.Literal))
688-
{
689-
WriteLiteralScalarValue(value);
690-
return;
691-
}
702+
// Note: Block scalar handling has been moved to WriteString to properly
703+
// skip the postlude (which sets _needsNewLine = true). Block scalars
704+
// already end with a newline, so we don't want an additional one.
692705

693706
// Determine if quoting is needed
694707
bool needsQuoting = NeedsQuoting(value);

test/Yamlify.Tests/Serialization/PrimitiveSerializationTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ public class AllPrimitivesClass
1616
public string? StringValue { get; set; }
1717
}
1818

19+
/// <summary>
20+
/// Class for testing multiple multi-line string properties.
21+
/// </summary>
22+
public class MultiStringClass
23+
{
24+
public string? FirstText { get; set; }
25+
public string? SecondText { get; set; }
26+
public string? ThirdText { get; set; }
27+
public int Value { get; set; }
28+
}
29+
1930
/// <summary>
2031
/// Class for testing special float values.
2132
/// </summary>
@@ -493,6 +504,42 @@ public void RoundtripMultilineString_PreservesContent()
493504
Assert.Equal(original.Name, deserialized.Name);
494505
}
495506

507+
[Fact]
508+
public void SerializeMultipleBlockScalars_NoExtraBlankLines()
509+
{
510+
// Arrange - Object with multiple multi-line string properties
511+
var original = new MultiStringClass
512+
{
513+
FirstText = "First line\nSecond line\n",
514+
SecondText = "Another first line\nAnother second line\n",
515+
ThirdText = "Third property line 1\nThird property line 2\n",
516+
Value = 42
517+
};
518+
519+
// Act
520+
var yaml = YamlSerializer.Serialize(original, TestSerializerContext.Default.MultiStringClass);
521+
522+
// Assert - Should use | (clip) chomping for strings ending with newline
523+
Assert.Contains("first-text: |", yaml);
524+
Assert.Contains("second-text: |", yaml);
525+
Assert.Contains("third-text: |", yaml);
526+
527+
// Verify no extra blank lines between properties
528+
// Each block scalar content ends with a newline, then the next property should start
529+
// immediately on the next line without an extra blank line
530+
Assert.DoesNotContain("\n\nsecond-text:", yaml);
531+
Assert.DoesNotContain("\n\nthird-text:", yaml);
532+
Assert.DoesNotContain("\n\nvalue:", yaml);
533+
534+
// Verify roundtrip
535+
var deserialized = YamlSerializer.Deserialize(yaml, TestSerializerContext.Default.MultiStringClass);
536+
Assert.NotNull(deserialized);
537+
Assert.Equal(original.FirstText, deserialized.FirstText);
538+
Assert.Equal(original.SecondText, deserialized.SecondText);
539+
Assert.Equal(original.ThirdText, deserialized.ThirdText);
540+
Assert.Equal(original.Value, deserialized.Value);
541+
}
542+
496543
[Fact]
497544
public void DeserializeMultilinePlainScalar_FoldsToSingleLine()
498545
{

test/Yamlify.Tests/Serialization/TestSerializerContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace Yamlify.Tests.Serialization;
2020
[YamlSerializable(typeof(Level3))]
2121
// Primitives
2222
[YamlSerializable(typeof(AllPrimitivesClass))]
23+
[YamlSerializable(typeof(MultiStringClass))]
2324
[YamlSerializable(typeof(SpecialNumbersClass))]
2425
// Enums
2526
[YamlSerializable(typeof(EnumClass))]

0 commit comments

Comments
 (0)