Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public abstract class OpenApiWriterBase : IOpenApiWriter
/// <summary>
/// The indentation string to prepand to each line for each indentation level.
/// </summary>
private const string IndentationString = " ";
protected const string IndentationString = " ";

/// <summary>
/// Scope of the Open API element - object, array, property.
Expand Down
102 changes: 99 additions & 3 deletions src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public OpenApiYamlWriter(TextWriter textWriter, OpenApiWriterSettings settings)
{
}

public bool UseLiteralStyle { get; set; }

/// <summary>
/// Base Indentation Level.
/// This denotes how many indentations are needed for the property in the base object.
Expand Down Expand Up @@ -163,11 +165,105 @@ public override void WritePropertyName(string name)
/// <param name="value">The string value.</param>
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;
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}