diff --git a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs
index 6d7f3f64d..05396a2f5 100755
--- a/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs
+++ b/src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs
@@ -23,7 +23,7 @@ public abstract class OpenApiWriterBase : IOpenApiWriter
///
/// The indentation string to prepand to each line for each indentation level.
///
- private const string IndentationString = " ";
+ protected const string IndentationString = " ";
///
/// Scope of the Open API element - object, array, property.
diff --git a/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs b/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs
index 2806d3d64..2eed59608 100755
--- a/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs
+++ b/src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs
@@ -27,6 +27,8 @@ public OpenApiYamlWriter(TextWriter textWriter, OpenApiWriterSettings settings)
{
}
+ public bool UseLiteralStyle { get; set; }
+
///
/// Base Indentation Level.
/// This denotes how many indentations are needed for the property in the base object.
@@ -163,11 +165,105 @@ public override void WritePropertyName(string name)
/// The string value.
public override void WriteValue(string value)
{
- WriteValueSeparator();
+ if (!UseLiteralStyle || value.IndexOfAny(new [] { '\n', '\r' }) == -1)
+ {
+ WriteValueSeparator();
- value = value.GetYamlCompatibleString();
+ value = value.GetYamlCompatibleString();
- Writer.Write(value);
+ Writer.Write(value);
+ }
+ else
+ {
+ if (CurrentScope() != null)
+ {
+ WriteValueSeparator();
+ }
+
+ Writer.Write("|");
+
+ WriteChompingIndicator(value);
+
+ // Write indentation indicator when it starts with spaces
+ if (value.StartsWith(" "))
+ {
+ Writer.Write(IndentationString.Length);
+ }
+
+ Writer.WriteLine();
+
+ IncreaseIndentation();
+
+ using (var reader = new StringReader(value))
+ {
+ bool firstLine = true;
+ while (reader.ReadLine() is var line && line != null)
+ {
+ if (firstLine)
+ firstLine = false;
+ else
+ Writer.WriteLine();
+
+ // Indentations for empty lines aren't needed.
+ if (line.Length > 0)
+ {
+ WriteIndentation();
+ }
+
+ Writer.Write(line);
+ }
+ }
+
+ DecreaseIndentation();
+ }
+ }
+
+ private void WriteChompingIndicator(string value)
+ {
+ var trailingNewlines = 0;
+ var end = value.Length - 1;
+ // We only need to know whether there are 0, 1, or more trailing newlines
+ while (end >= 0 && trailingNewlines < 2)
+ {
+ var found = value.LastIndexOfAny(new[] { '\n', '\r' }, end, 2);
+ if (found == -1 || found != end)
+ {
+ // does not ends with newline
+ break;
+ }
+
+ if (value[end] == '\r')
+ {
+ // ends with \r
+ end--;
+ }
+ else if (end > 0 && value[end - 1] == '\r')
+ {
+ // ends with \r\n
+ end -= 2;
+ }
+ else
+ {
+ // ends with \n
+ end -= 1;
+ }
+ trailingNewlines++;
+ }
+
+ switch (trailingNewlines)
+ {
+ case 0:
+ // "strip" chomping indicator
+ Writer.Write("-");
+ break;
+ case 1:
+ // "clip"
+ break;
+ default:
+ // "keep" chomping indicator
+ Writer.Write("+");
+ break;
+ }
}
///
diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs
index 60e598882..78a2c6678 100644
--- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs
@@ -75,5 +75,59 @@ public void WriteStringWithSpecialCharactersAsYamlWorks(string input, string exp
// Assert
actual.Should().Be(expected);
}
+
+ [Theory]
+ [InlineData("multiline\r\nstring", "test: |-\n multiline\n string")]
+ [InlineData("ends with\r\nline break\r\n", "test: |\n ends with\n line break")]
+ [InlineData("ends with\r\n2 line breaks\r\n\r\n", "test: |+\n ends with\n 2 line breaks\n")]
+ [InlineData("ends with\r\n3 line breaks\r\n\r\n\r\n", "test: |+\n ends with\n 3 line breaks\n\n")]
+ [InlineData(" starts with\nspaces", "test: |-2\n starts with\n spaces")]
+ [InlineData(" starts with\nspaces, and ends with line break\n", "test: |2\n starts with\n spaces, and ends with line break")]
+ [InlineData("contains\n\n\nempty lines", "test: |-\n contains\n\n\n empty lines")]
+ [InlineData("no line breaks fallback ", "test: 'no line breaks fallback '")]
+ public void WriteStringWithNewlineCharactersInObjectAsYamlWorks(string input, string expected)
+ {
+ // Arrange
+ var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture);
+ var writer = new OpenApiYamlWriter(outputStringWriter) { UseLiteralStyle = true, };
+
+ // Act
+ writer.WriteStartObject();
+ writer.WritePropertyName("test");
+ writer.WriteValue(input);
+ writer.WriteEndObject();
+ var actual = outputStringWriter.GetStringBuilder().ToString()
+ // Normalize newline for cross platform
+ .Replace("\r", "");
+
+ // Assert
+ actual.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("multiline\r\nstring", "- |-\n multiline\n string")]
+ [InlineData("ends with\r\nline break\r\n", "- |\n ends with\n line break")]
+ [InlineData("ends with\r\n2 line breaks\r\n\r\n", "- |+\n ends with\n 2 line breaks\n")]
+ [InlineData("ends with\r\n3 line breaks\r\n\r\n\r\n", "- |+\n ends with\n 3 line breaks\n\n")]
+ [InlineData(" starts with\nspaces", "- |-2\n starts with\n spaces")]
+ [InlineData(" starts with\nspaces, and ends with line break\n", "- |2\n starts with\n spaces, and ends with line break")]
+ [InlineData("contains\n\n\nempty lines", "- |-\n contains\n\n\n empty lines")]
+ [InlineData("no line breaks fallback ", "- 'no line breaks fallback '")]
+ public void WriteStringWithNewlineCharactersInArrayAsYamlWorks(string input, string expected)
+ {
+ // Arrange
+ var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture);
+ var writer = new OpenApiYamlWriter(outputStringWriter) { UseLiteralStyle = true, };
+
+ // Act
+ writer.WriteStartArray();
+ writer.WriteValue(input);
+ var actual = outputStringWriter.GetStringBuilder().ToString()
+ // Normalize newline for cross platform
+ .Replace("\r", "");
+
+ // Assert
+ actual.Should().Be(expected);
+ }
}
}