diff --git a/SwiftCSV/EnumeratedView.swift b/SwiftCSV/EnumeratedView.swift index c17e71f..e71084e 100644 --- a/SwiftCSV/EnumeratedView.swift +++ b/SwiftCSV/EnumeratedView.swift @@ -9,7 +9,6 @@ import Foundation public struct EnumeratedView: View { - public struct Column { public let header: String public let rows: [String] @@ -21,7 +20,6 @@ public struct EnumeratedView: View { public private(set) var columns: [Column] public init(header: [String], text: String, delimiter: Character, loadColumns: Bool = false) throws { - var rows = [[String]]() var columns: [EnumeratedView.Column] = [] @@ -29,12 +27,14 @@ public struct EnumeratedView: View { rows.append(fields) } + // Fill in gaps at the end of rows that are too short. + rows = makingRectangular(rows: rows) + if loadColumns { columns = header.enumerated().map { (index: Int, header: String) -> EnumeratedView.Column in - return EnumeratedView.Column( header: header, - rows: rows.map { $0[index] }) + rows: rows.map { $0[safe: index] ?? "" }) } } @@ -43,7 +43,6 @@ public struct EnumeratedView: View { } public func serialize(header: [String], delimiter: Character) -> String { - let head = header .map(enquoteContentsIfNeeded(cell:)) .joined(separator: ",") + "\n" @@ -55,5 +54,19 @@ public struct EnumeratedView: View { return head + content } +} +extension Collection { + subscript (safe index: Self.Index) -> Self.Iterator.Element? { + return index < endIndex ? self[index] : nil + } +} + +fileprivate func makingRectangular(rows: [[String]]) -> [[String]] { + let cellsPerRow = rows.map { $0.count }.max() ?? 0 + return rows.map { row -> [String] in + let missingCellCount = cellsPerRow - row.count + let appendix = Array(repeating: "", count: missingCellCount) + return row + appendix + } } diff --git a/SwiftCSVTests/CSVTests.swift b/SwiftCSVTests/CSVTests.swift index 07e3b74..099bf43 100644 --- a/SwiftCSVTests/CSVTests.swift +++ b/SwiftCSVTests/CSVTests.swift @@ -31,8 +31,8 @@ class CSVTests: XCTestCase { } } - func testInit_whenThereAreIncompleteRows_makesRows() { - csv = try! CSV(string: "id,name,age\n1,Alice,18\n2,Bob,19\n3,Charlie") + func testInit_whenThereAreIncompleteRows_makesRows() throws { + csv = try CSV(string: "id,name,age\n1,Alice,18\n2,Bob,19\n3,Charlie") let expected = [ ["id": "1", "name": "Alice", "age": "18"], ["id": "2", "name": "Bob", "age": "19"], diff --git a/SwiftCSVTests/EnumeratedViewTests.swift b/SwiftCSVTests/EnumeratedViewTests.swift index 6ef23ca..4a985db 100644 --- a/SwiftCSVTests/EnumeratedViewTests.swift +++ b/SwiftCSVTests/EnumeratedViewTests.swift @@ -19,6 +19,18 @@ class EnumeratedViewTests: XCTestCase { csv = try! CSV(string: "id,name,age\n1,Alice,18\n2,Bob,19\n3,Charlie,20", delimiter: ",", loadColumns: true) } + func testInit_whenThereAreIncompleteRows_makesRows() throws { + csv = try CSV(string: "id,name,age\n1,Alice,18\n2\n3,Charlie", delimiter: ",", loadColumns: true) + let expected = [ + ["1", "Alice", "18"], + ["2", "", ""], + ["3", "Charlie", ""] + ] + for (index, row) in csv.rows.enumerated() { + XCTAssertEqual(expected[index], row) + } + } + func testExposesRows() { let expected: [[String]] = [ ["1", "Alice", "18"],