diff --git a/Sources/Ink/Internal/Table.swift b/Sources/Ink/Internal/Table.swift index ac98f89..eebd71c 100644 --- a/Sources/Ink/Internal/Table.swift +++ b/Sources/Ink/Internal/Table.swift @@ -9,7 +9,7 @@ import Foundation struct Table: Fragment { var modifierTarget: Modifier.Target { .tables } - private var header: Row? + private var headers: [Row]? // There can be multiple header rows in a table. private var rows = [Row]() private var columnCount = 0 private var columnAlignments = [ColumnAlignment]() @@ -37,15 +37,16 @@ struct Table: Fragment { var html = "" let render: () -> String = { "\(html)
" } - if let header = header { - let rowHTML = self.html( - forRow: header, - cellElementName: "th", - urls: urls, - modifiers: modifiers - ) - - html.append("\(rowHTML)") + if let headers = headers { + for row in headers { + let rowHTML = self.html( + forRow: row, + cellElementName: "th", + urls: urls, + modifiers: modifiers + ) + html.append("\(rowHTML)") + } } guard !rows.isEmpty else { @@ -70,7 +71,14 @@ struct Table: Fragment { } func plainText() -> String { - var text = header.map(plainText) ?? "" + var text = "" + + if let headers = headers { + for row in headers { + if !text.isEmpty { text.append("\n") } + text.append(plainText(forRow: row)) + } + } for row in rows { if !text.isEmpty { text.append("\n") } @@ -107,15 +115,43 @@ private extension Table { } } } + + /// This method is to determine where the format row is located in the Table. + /// - Returns: Int? If a value is returned it should be the index for the formatting row. + func findHeaderAlignmentRow() -> Int? { + var result : Int? = nil + + let textPredicate = Self.allowedHeaderCharacters.contains + for (index, row) in rows.enumerated() { + if index > 0 { + // see if the current row is all column alignment cells + var goodRow = true + for cell in row { + let text = cell.plainText() + + if !text.allSatisfy(textPredicate) { + goodRow = false + break + } + } + if goodRow { + result = index + } + } + } + return result + } + mutating func formHeaderAndColumnAlignmentsIfNeeded() { guard rows.count > 1 else { return } - guard rows[0].count == rows[1].count else { return } + + guard let alignmentRow = findHeaderAlignmentRow(), rows[0].count == rows[alignmentRow].count else { return } let textPredicate = Self.allowedHeaderCharacters.contains var alignments = [ColumnAlignment]() - for cell in rows[1] { + for cell in rows[alignmentRow] { let text = cell.plainText() guard text.allSatisfy(textPredicate) else { @@ -124,10 +160,12 @@ private extension Table { alignments.append(parseColumnAlignment(from: text)) } - - header = rows[0] + + var headers = [Row]() + headers.append(contentsOf: rows[0.. ColumnAlignment { diff --git a/Tests/InkTests/TableTests.swift b/Tests/InkTests/TableTests.swift index bfe06e6..b251799 100644 --- a/Tests/InkTests/TableTests.swift +++ b/Tests/InkTests/TableTests.swift @@ -41,6 +41,28 @@ final class TableTests: XCTestCase { """) } + func testTableWithMultipleHeaderRows() { + let html = MarkdownParser().html(from: """ + | HeaderA | HeaderB | HeaderC | + | HeaderD | HeaderE | HeaderF | + | ------- | ------- | ------- | + | CellA1 | CellB1 | CellC1 | + | CellA2 | CellB2 | CellC2 | + """) + + XCTAssertEqual(html, """ + \ + \ + \ + \ + \ + \ + \ +
HeaderAHeaderBHeaderC
HeaderDHeaderEHeaderF
CellA1CellB1CellC1
CellA2CellB2CellC2
+ """) + } + + func testTableWithUnalignedColumns() { let html = MarkdownParser().html(from: """ | HeaderA | HeaderB | HeaderC | @@ -210,6 +232,7 @@ extension TableTests { return [ ("testTableWithoutHeader", testTableWithoutHeader), ("testTableWithHeader", testTableWithHeader), + ("testTableWithMultipleHeaderRows", testTableWithMultipleHeaderRows), ("testTableWithUnalignedColumns", testTableWithUnalignedColumns), ("testTableWithOnlyHeader", testTableWithOnlyHeader), ("testIncompleteTable", testIncompleteTable),