Skip to content

Markdown Table Generator — Swift source

Turn pipe, CSV, tab, semicolon, or space-separated data into a clean GitHub-Flavored Markdown table. Auto-detects the delimiter, pads columns, escapes pipes, and supports per-column alignment - all in your browser.

This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// markdown-table — Swift port: split delimiter-separated rows and render a padded GFM table, ported from src/lib/markdown-table.ts.

import Foundation

enum Align: String { case none, left, center, right }

/// escapeCell: newlines collapse to spaces, a literal '|' renders as '\|'.
func escapeCell(_ cell: String) -> String {
    var out = ""
    for ch in cell {
        switch ch {
        case "\r", "\n": out.append(" ")
        case "|": out.append("\\|")
        default: out.append(ch)
        }
    }
    return out
}

/// splitLine: literal-delimiter split (',', '\t', ';'), each cell trimmed.
func splitLine(_ line: String, _ delim: Character) -> [String] {
    line.split(separator: delim, omittingEmptySubsequences: false)
        .map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
}

/// parseTable: non-empty lines, each split by the delimiter.
func parseTable(_ input: String, _ delim: Character) -> [[String]] {
    input.split(separator: "\n", omittingEmptySubsequences: false)
        .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
        .filter { !$0.isEmpty }
        .map { splitLine($0, delim) }
}

/// pad: fill to width honoring right/center/left alignment.
func pad(_ cell: String, _ width: Int, _ align: Align) -> String {
    let diff = width - cell.count
    guard diff > 0 else { return cell }
    switch align {
    case .right: return String(repeating: " ", count: diff) + cell
    case .center:
        return String(repeating: " ", count: diff / 2) + cell
            + String(repeating: " ", count: diff - diff / 2)
    case .left, .none: return cell + String(repeating: " ", count: diff)
    }
}

/// sepCell: ---, :--, --:, :-: — the alignment separator, min width 3.
func sepCell(_ align: Align, _ width: Int) -> String {
    let w = max(3, width)
    switch align {
    case .center: return ":" + String(repeating: "-", count: w - 2) + ":"
    case .right: return String(repeating: "-", count: w - 1) + ":"
    case .left: return ":" + String(repeating: "-", count: w - 1)
    case .none: return String(repeating: "-", count: w)
    }
}

/// toMarkdown: pad every column to its widest (escaped) cell and lay rows out
/// as '| a | b |', with the separator row under the header row.
func toMarkdown(_ rows: [[String]], header: Bool = true, aligns: [Align] = []) -> String {
    guard !rows.isEmpty else { return "" }
    let cols = rows.map { $0.count }.max()!
    var grid = rows.map { row -> [String] in
        var cells = row.map(escapeCell)
        while cells.count < cols { cells.append("") }
        return cells
    }
    let align = (0..<cols).map { aligns.indices.contains($0) ? aligns[$0] : .none }
    let widths = (0..<cols).map { c in max(3, grid.map { $0[c].count }.max()!) }

    func line(_ cells: [String]) -> String {
        "| " + cells.enumerated()
            .map { pad($0.element, widths[$0.offset], align[$0.offset]) }
            .joined(separator: " | ") + " |"
    }
    let separator = "| " + align.enumerated()
        .map { sepCell($0.element, widths[$0.offset]) }
        .joined(separator: " | ") + " |"

    let head = header ? grid.removeFirst() : Array(repeating: "", count: cols)
    let data = grid
    return ([line(head), separator] + data.map(line)).joined(separator: "\n")
}

// Demo: a CSV-ish grid with a literal pipe in one cell (escaped on output).
let rows = parseTable("Name,Price,Qty\nWire | cable,2 m,1\nLED,3 mm,25", ",")
print(toMarkdown(rows, aligns: [.none, .right, .center]))

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →