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
9 changes: 9 additions & 0 deletions SwiftCSV/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ extension CSV {
try Parser.enumerateAsArray(text: self.text, delimiter: self.delimiter, startAt: startAt, rowLimit: rowLimit, rowCallback: rowCallback)
}

/// Enumerate each row as a dictionary keyed by its header names.
/// - Throws: `CSVParseError.duplicateHeader` if headers repeat, or an error thrown by `block`.
public func enumerateAsDict(_ block: @escaping ([String : String]) throws -> ()) throws {

try Parser.enumerateAsDict(header: self.header, content: self.text, delimiter: self.delimiter, block: block)
Expand Down Expand Up @@ -125,8 +127,15 @@ enum Parser {
}
}

/// Parse rows as dictionaries. Duplicate header names are rejected because a dictionary cannot
/// preserve both columns.
static func enumerateAsDict(header: [String], content: String, delimiter: CSVDelimiter, rowLimit: Int? = nil, block: @escaping ([String : String]) throws -> ()) throws {

var seenHeaders = Set<String>()
for name in header where !seenHeaders.insert(name).inserted {
throw CSVParseError.duplicateHeader(name: name)
}

let enumeratedHeader = header.enumerated()

// Start after the header
Expand Down
2 changes: 2 additions & 0 deletions SwiftCSV/ParsingState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
public enum CSVParseError: Error {
case generic(message: String)
case quotation(message: String)
/// A duplicate header cannot be represented without losing a column in dictionary-based access.
case duplicateHeader(name: String)
}

/// State machine of parsing CSV contents character by character.
Expand Down
37 changes: 37 additions & 0 deletions SwiftCSVTests/DuplicateHeaderTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import XCTest
@testable import SwiftCSV

final class DuplicateHeaderTests: XCTestCase {
private let text = """
en,comment,de,en,en-GB,fr,es,it
,,,,,,,
Efficiency,,,,,,,
%@,,,,,,,
"""

func testNamedViewRejectsDuplicateHeaders() {
XCTAssertThrowsError(try CSV<Named>(string: text)) { error in
guard case CSVParseError.duplicateHeader(name: "en") = error else {
return XCTFail("Expected a duplicate-header error, got \(error)")
}
}
}

func testEnumeratedViewPreservesDuplicateHeaderColumns() throws {
let csv = try CSV<Enumerated>(string: text)

XCTAssertEqual(csv.header, ["en", "comment", "de", "en", "en-GB", "fr", "es", "it"])
XCTAssertEqual(csv.rows[1], ["Efficiency", "", "", "", "", "", "", ""])
XCTAssertEqual(csv.columns?.filter { $0.header == "en" }.count, 2)
}

func testDictionaryEnumerationRejectsDuplicateHeaders() throws {
let csv = try CSV<Enumerated>(string: text)

XCTAssertThrowsError(try csv.enumerateAsDict { _ in }) { error in
guard case CSVParseError.duplicateHeader(name: "en") = error else {
return XCTFail("Expected a duplicate-header error, got \(error)")
}
}
}
}