Skip to content

Commit 30d9969

Browse files
authored
Merge pull request #481 from foriequal0/literal-style
Add literal style support for OpenApiYamlWriter
2 parents f8e4201 + bdf10c4 commit 30d9969

File tree

3 files changed

+154
-4
lines changed

3 files changed

+154
-4
lines changed

src/Microsoft.OpenApi/Writers/OpenApiWriterBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public abstract class OpenApiWriterBase : IOpenApiWriter
2323
/// <summary>
2424
/// The indentation string to prepand to each line for each indentation level.
2525
/// </summary>
26-
private const string IndentationString = " ";
26+
protected const string IndentationString = " ";
2727

2828
/// <summary>
2929
/// Scope of the Open API element - object, array, property.

src/Microsoft.OpenApi/Writers/OpenApiYamlWriter.cs

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public OpenApiYamlWriter(TextWriter textWriter, OpenApiWriterSettings settings)
2727
{
2828
}
2929

30+
public bool UseLiteralStyle { get; set; }
31+
3032
/// <summary>
3133
/// Base Indentation Level.
3234
/// This denotes how many indentations are needed for the property in the base object.
@@ -163,11 +165,105 @@ public override void WritePropertyName(string name)
163165
/// <param name="value">The string value.</param>
164166
public override void WriteValue(string value)
165167
{
166-
WriteValueSeparator();
168+
if (!UseLiteralStyle || value.IndexOfAny(new [] { '\n', '\r' }) == -1)
169+
{
170+
WriteValueSeparator();
167171

168-
value = value.GetYamlCompatibleString();
172+
value = value.GetYamlCompatibleString();
169173

170-
Writer.Write(value);
174+
Writer.Write(value);
175+
}
176+
else
177+
{
178+
if (CurrentScope() != null)
179+
{
180+
WriteValueSeparator();
181+
}
182+
183+
Writer.Write("|");
184+
185+
WriteChompingIndicator(value);
186+
187+
// Write indentation indicator when it starts with spaces
188+
if (value.StartsWith(" "))
189+
{
190+
Writer.Write(IndentationString.Length);
191+
}
192+
193+
Writer.WriteLine();
194+
195+
IncreaseIndentation();
196+
197+
using (var reader = new StringReader(value))
198+
{
199+
bool firstLine = true;
200+
while (reader.ReadLine() is var line && line != null)
201+
{
202+
if (firstLine)
203+
firstLine = false;
204+
else
205+
Writer.WriteLine();
206+
207+
// Indentations for empty lines aren't needed.
208+
if (line.Length > 0)
209+
{
210+
WriteIndentation();
211+
}
212+
213+
Writer.Write(line);
214+
}
215+
}
216+
217+
DecreaseIndentation();
218+
}
219+
}
220+
221+
private void WriteChompingIndicator(string value)
222+
{
223+
var trailingNewlines = 0;
224+
var end = value.Length - 1;
225+
// We only need to know whether there are 0, 1, or more trailing newlines
226+
while (end >= 0 && trailingNewlines < 2)
227+
{
228+
var found = value.LastIndexOfAny(new[] { '\n', '\r' }, end, 2);
229+
if (found == -1 || found != end)
230+
{
231+
// does not ends with newline
232+
break;
233+
}
234+
235+
if (value[end] == '\r')
236+
{
237+
// ends with \r
238+
end--;
239+
}
240+
else if (end > 0 && value[end - 1] == '\r')
241+
{
242+
// ends with \r\n
243+
end -= 2;
244+
}
245+
else
246+
{
247+
// ends with \n
248+
end -= 1;
249+
}
250+
trailingNewlines++;
251+
}
252+
253+
switch (trailingNewlines)
254+
{
255+
case 0:
256+
// "strip" chomping indicator
257+
Writer.Write("-");
258+
break;
259+
case 1:
260+
// "clip"
261+
break;
262+
default:
263+
// "keep" chomping indicator
264+
Writer.Write("+");
265+
break;
266+
}
171267
}
172268

173269
/// <summary>

test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,59 @@ public void WriteStringWithSpecialCharactersAsYamlWorks(string input, string exp
7575
// Assert
7676
actual.Should().Be(expected);
7777
}
78+
79+
[Theory]
80+
[InlineData("multiline\r\nstring", "test: |-\n multiline\n string")]
81+
[InlineData("ends with\r\nline break\r\n", "test: |\n ends with\n line break")]
82+
[InlineData("ends with\r\n2 line breaks\r\n\r\n", "test: |+\n ends with\n 2 line breaks\n")]
83+
[InlineData("ends with\r\n3 line breaks\r\n\r\n\r\n", "test: |+\n ends with\n 3 line breaks\n\n")]
84+
[InlineData(" starts with\nspaces", "test: |-2\n starts with\n spaces")]
85+
[InlineData(" starts with\nspaces, and ends with line break\n", "test: |2\n starts with\n spaces, and ends with line break")]
86+
[InlineData("contains\n\n\nempty lines", "test: |-\n contains\n\n\n empty lines")]
87+
[InlineData("no line breaks fallback ", "test: 'no line breaks fallback '")]
88+
public void WriteStringWithNewlineCharactersInObjectAsYamlWorks(string input, string expected)
89+
{
90+
// Arrange
91+
var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture);
92+
var writer = new OpenApiYamlWriter(outputStringWriter) { UseLiteralStyle = true, };
93+
94+
// Act
95+
writer.WriteStartObject();
96+
writer.WritePropertyName("test");
97+
writer.WriteValue(input);
98+
writer.WriteEndObject();
99+
var actual = outputStringWriter.GetStringBuilder().ToString()
100+
// Normalize newline for cross platform
101+
.Replace("\r", "");
102+
103+
// Assert
104+
actual.Should().Be(expected);
105+
}
106+
107+
[Theory]
108+
[InlineData("multiline\r\nstring", "- |-\n multiline\n string")]
109+
[InlineData("ends with\r\nline break\r\n", "- |\n ends with\n line break")]
110+
[InlineData("ends with\r\n2 line breaks\r\n\r\n", "- |+\n ends with\n 2 line breaks\n")]
111+
[InlineData("ends with\r\n3 line breaks\r\n\r\n\r\n", "- |+\n ends with\n 3 line breaks\n\n")]
112+
[InlineData(" starts with\nspaces", "- |-2\n starts with\n spaces")]
113+
[InlineData(" starts with\nspaces, and ends with line break\n", "- |2\n starts with\n spaces, and ends with line break")]
114+
[InlineData("contains\n\n\nempty lines", "- |-\n contains\n\n\n empty lines")]
115+
[InlineData("no line breaks fallback ", "- 'no line breaks fallback '")]
116+
public void WriteStringWithNewlineCharactersInArrayAsYamlWorks(string input, string expected)
117+
{
118+
// Arrange
119+
var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture);
120+
var writer = new OpenApiYamlWriter(outputStringWriter) { UseLiteralStyle = true, };
121+
122+
// Act
123+
writer.WriteStartArray();
124+
writer.WriteValue(input);
125+
var actual = outputStringWriter.GetStringBuilder().ToString()
126+
// Normalize newline for cross platform
127+
.Replace("\r", "");
128+
129+
// Assert
130+
actual.Should().Be(expected);
131+
}
78132
}
79133
}

0 commit comments

Comments
 (0)