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
19 changes: 11 additions & 8 deletions src/Microsoft.Data.Analysis/DataFrame.IO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
Expand Down Expand Up @@ -499,11 +498,11 @@ public static void WriteCsv(DataFrame dataFrame, Stream csvStream,

if (t == typeof(string))
{
bool needsQuotes = ((string)cell).IndexOf(separator) != -1 || ((string)cell).IndexOf('\n') != -1;
if (needsQuotes)
string stringCell = (string)cell;
if (NeedsQuotes(stringCell, separator))
{
record.Append('\"');
record.Append(cell);
record.Append(stringCell.Replace("\"", "\"\"")); // Quotations in CSV data must be escaped with another quotation
record.Append('\"');
continue;
}
Expand All @@ -519,6 +518,7 @@ public static void WriteCsv(DataFrame dataFrame, Stream csvStream,
}
}
}

private static void WriteHeader(StreamWriter csvFile, IReadOnlyList<string> columnNames, char separator)
{
bool firstColumn = true;
Expand All @@ -533,20 +533,23 @@ private static void WriteHeader(StreamWriter csvFile, IReadOnlyList<string> colu
firstColumn = false;
}

bool needsQuotes = name.IndexOf(separator) != -1 || name.IndexOf('\n') != -1;
if (needsQuotes)
if (NeedsQuotes(name, separator))
{
csvFile.Write('\"');
csvFile.Write(name);
csvFile.Write(name.Replace("\"", "\"\"")); // Quotations in CSV data must be escaped with another quotation
csvFile.Write('\"');
}
else
{
csvFile.Write(name);
}
}

csvFile.WriteLine();
}

private static bool NeedsQuotes(string csvCell, char separator)
{
return csvCell.AsSpan().IndexOfAny(separator, '\n', '\"') != -1;
}
}
}
122 changes: 96 additions & 26 deletions test/Microsoft.Data.Analysis.Tests/DataFrame.IOTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1064,10 +1064,12 @@ public static IEnumerable<object[]> CsvWithTextQualifiers_TestData()
{
yield return new object[] // Comma Separators in Data
{
@"Name,Age,Description
Paul,34,""Paul lives in Vermont, VA.""
Victor,29,""Victor: Funny guy""
Maria,31,",
"""
Name,Age,Description
Paul,34,"Paul lives in Vermont, VA."
Victor,29,"Victor: Funny guy"
Maria,31,
""",
',',
new Type[] { typeof(string), typeof(int), typeof(string) },
new LoadCsvVerifyingHelper(
Expand All @@ -1085,10 +1087,12 @@ public static IEnumerable<object[]> CsvWithTextQualifiers_TestData()
};
yield return new object[] // Colon Separators in Data
{
@"Name:Age:Description
Paul:34:""Paul lives in Vermont, VA.""
Victor:29:""Victor: Funny guy""
Maria:31:",
"""
Name:Age:Description
Paul:34:"Paul lives in Vermont, VA."
Victor:29:"Victor: Funny guy"
Maria:31:
""",
':',
new Type[] { typeof(string), typeof(int), typeof(string) },
new LoadCsvVerifyingHelper(
Expand All @@ -1106,10 +1110,12 @@ public static IEnumerable<object[]> CsvWithTextQualifiers_TestData()
};
yield return new object[] // Comma Separators in Header
{
@"""Na,me"",Age,Description
Paul,34,""Paul lives in Vermont, VA.""
Victor,29,""Victor: Funny guy""
Maria,31,",
"""
"Na,me",Age,Description
Paul,34,"Paul lives in Vermont, VA."
Victor,29,"Victor: Funny guy"
Maria,31,
""",
',',
new Type[] { typeof(string), typeof(int), typeof(string) },
new LoadCsvVerifyingHelper(
Expand All @@ -1127,11 +1133,13 @@ public static IEnumerable<object[]> CsvWithTextQualifiers_TestData()
};
yield return new object[] // Newlines In Data
{
@"Name,Age,Description
Paul,34,""Paul lives in Vermont
VA.""
Victor,29,""Victor: Funny guy""
Maria,31,",
"""
Name,Age,Description
Paul,34,"Paul lives in Vermont
VA."
Victor,29,"Victor: Funny guy"
Maria,31,
""",
',',
new Type[] { typeof(string), typeof(int), typeof(string) },
new LoadCsvVerifyingHelper(
Expand All @@ -1141,27 +1149,89 @@ public static IEnumerable<object[]> CsvWithTextQualifiers_TestData()
new Type[] { typeof(string), typeof(int), typeof(string) },
new object[][]
{
new object[] { "Paul", 34, @"Paul lives in Vermont
VA." },
new object[]
{
"Paul",
34,
"""
Paul lives in Vermont
VA.
"""
},
new object[] { "Victor", 29, "Victor: Funny guy" },
new object[] { "Maria", 31, "" }
}
)
};
yield return new object[] // Newlines In Header
{
@"""Na
me"":Age:Description
Paul:34:""Paul lives in Vermont, VA.""
Victor:29:""Victor: Funny guy""
Maria:31:",
"""
"Na
me":Age:Description
Paul:34:"Paul lives in Vermont, VA."
Victor:29:"Victor: Funny guy"
Maria:31:
""",
':',
new Type[] { typeof(string), typeof(int), typeof(string) },
new LoadCsvVerifyingHelper(
3,
3,
new string[] { @"Na
me", "Age", "Description" },
new string[]
{
"""
Na
me
""",
"Age",
"Description"
},
new Type[] { typeof(string), typeof(int), typeof(string) },
new object[][]
{
new object[] { "Paul", 34, "Paul lives in Vermont, VA." },
new object[] { "Victor", 29, "Victor: Funny guy" },
new object[] { "Maria", 31, "" }
}
)
};
yield return new object[] // Quotations in Data
{
"""
Name,Age,Description
Paul,34,"Paul lives in ""Vermont VA""."
Victor,29,"Victor: Funny guy"
Maria,31,
""",
',',
new Type[] { typeof(string), typeof(int), typeof(string) },
new LoadCsvVerifyingHelper(
3,
3,
new string[] { "Name", "Age", "Description" },
new Type[] { typeof(string), typeof(int), typeof(string) },
new object[][]
{
new object[] { "Paul", 34, """Paul lives in "Vermont VA".""" },
new object[] { "Victor", 29, "Victor: Funny guy" },
new object[] { "Maria", 31, "" }
}
)
};
yield return new object[] // Quotations in Header
{
"""
Name,Age,"De""script""ion"
Paul,34,"Paul lives in Vermont, VA."
Victor,29,"Victor: Funny guy"
Maria,31,
""",
',',
new Type[] { typeof(string), typeof(int), typeof(string) },
new LoadCsvVerifyingHelper(
3,
3,
new string[] { "Name", "Age", """De"script"ion""" },
new Type[] { typeof(string), typeof(int), typeof(string) },
new object[][]
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<NoWarn>$(NoWarn);MSML_ParameterLocalVarName;MSML_PrivateFieldName;MSML_ExtendBaseTestClass;MSML_GeneralName</NoWarn>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down