Skip to content
Open
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
4 changes: 2 additions & 2 deletions SwiftCSV/Serializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ fileprivate extension String {
static let quote = "\""

func enquoted(whenContaining separator: String) -> String {
// If value contains a delimiter or quotes, double any embedded quotes and surround with quotes.
// Quote fields containing delimiters, quotes or line breaks, and double embedded quotes.
// For more information, see https://www.rfc-editor.org/rfc/rfc4180.html
if self.contains(separator) || self.contains(Self.quote) {
if self.contains(separator) || self.contains(Self.quote) || self.contains(where: { $0.isNewline }) {
return Self.quote + self.replacingOccurrences(of: Self.quote, with: Self.quote + Self.quote) + Self.quote
} else {
return self
Expand Down
28 changes: 28 additions & 0 deletions SwiftCSVTests/SerializationNewlineTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import XCTest
@testable import SwiftCSV

class SerializationNewlineTests: XCTestCase {
func testLineBreaksRemainQuotedForEnumeratedRoundTrip() throws {
for newline in ["\n", "\r", "\r\n"] {
for delimiter: CSVDelimiter in [.comma, .semicolon, .tab, .character("|")] {
let separator = String(delimiter.rawValue)
let source = "\"first\(newline)name\"\(separator)second\n\"value\(newline)continued\"\(separator)other"
let original = try EnumeratedCSV(string: source, delimiter: delimiter)
XCTAssertEqual(original.serialized, source)
let decoded = try EnumeratedCSV(string: original.serialized, delimiter: delimiter)
XCTAssertEqual(decoded.header, original.header)
XCTAssertEqual(decoded.rows, original.rows)
}
}
}

func testLineBreaksRemainQuotedForNamedRoundTrip() throws {
for newline in ["\n", "\r", "\r\n"] {
let source = "name,note\nAlice,\"first\(newline)second\""
let original = try NamedCSV(string: source, delimiter: .comma)
XCTAssertEqual(original.serialized, source)
let decoded = try NamedCSV(string: original.serialized, delimiter: .comma)
XCTAssertEqual(decoded.rows, original.rows)
}
}
}