Skip to content

Markdown Table Generator — C++ 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 C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.

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

#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

enum class Align { None, Left, Center, Right };

// splitLine: literal-delimiter split (',', '\t', ';'), each cell trimmed.
std::vector<std::string> splitLine(const std::string& line, char delim) {
    std::vector<std::string> cells;
    size_t start = 0;
    for (size_t i = 0; i <= line.size(); i++) {
        if (i == line.size() || line[i] == delim) {
            size_t b = start, e = i;
            while (b < e && isspace(static_cast<unsigned char>(line[b]))) b++;
            while (e > b && isspace(static_cast<unsigned char>(line[e - 1]))) e--;
            cells.push_back(line.substr(b, e - b));
            start = i + 1;
        }
    }
    return cells;
}

// parseTable: non-empty (post-trim) lines, each split by the delimiter.
std::vector<std::vector<std::string>> parseTable(const std::string& input, char delim) {
    std::vector<std::vector<std::string>> rows;
    std::string line;
    std::istringstream stream(input);
    while (std::getline(stream, line)) {
        if (!line.empty() && line.back() == '\r') line.pop_back();
        size_t b = line.find_first_not_of(" \t");
        if (b == std::string::npos) continue; // blank line
        size_t e = line.find_last_not_of(" \t");
        rows.push_back(splitLine(line.substr(b, e - b + 1), delim));
    }
    return rows;
}

// escapeCell: newlines collapse to spaces, a literal '|' renders as '\|'.
std::string escapeCell(const std::string& cell) {
    std::string out;
    for (char c : cell) {
        if (c == '\n' || c == '\r') out += ' ';
        else {
            if (c == '|') out += '\\';
            out += c;
        }
    }
    return out;
}

// pad: fill to width honoring right/center/left alignment.
std::string pad(const std::string& cell, size_t width, Align a) {
    size_t diff = width > cell.size() ? width - cell.size() : 0;
    if (diff == 0) return cell;
    if (a == Align::Right) return std::string(diff, ' ') + cell;
    if (a == Align::Center) {
        size_t l = diff / 2;
        return std::string(l, ' ') + cell + std::string(diff - l, ' ');
    }
    return cell + std::string(diff, ' '); // Left, None -> left-aligned
}

// sepCell: ---, :--, --:, :-: — the alignment separator, min width 3.
std::string sepCell(Align a, size_t width) {
    size_t w = std::max<size_t>(3, width);
    switch (a) {
        case Align::Center: return ":" + std::string(w - 2, '-') + ":";
        case Align::Right: return std::string(w - 1, '-') + ":";
        case Align::Left: return ":" + std::string(w - 1, '-');
        default: return std::string(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.
std::string toMarkdown(std::vector<std::vector<std::string>> rows, bool header,
                       std::vector<Align> aligns) {
    if (rows.empty()) return "";
    size_t cols = 0;
    for (auto& r : rows) {
        for (auto& c : r) c = escapeCell(c);
        cols = std::max(cols, r.size());
        r.resize(cols, "");
    }
    std::vector<size_t> widths(cols, 3);
    std::vector<Align> align(cols, Align::None);
    for (size_t c = 0; c < cols; c++) {
        if (c < aligns.size()) align[c] = aligns[c];
        for (const auto& r : rows) widths[c] = std::max(widths[c], r[c].size());
    }

    auto line = [&](const std::vector<std::string>& cells) {
        std::string out = "| ";
        for (size_t c = 0; c < cols; c++) {
            if (c > 0) out += " | ";
            out += pad(cells[c], widths[c], align[c]);
        }
        return out + " |";
    };
    std::string separator = "| ";
    for (size_t c = 0; c < cols; c++) {
        if (c > 0) separator += " | ";
        separator += sepCell(align[c], widths[c]);
    }
    separator += " |";

    std::vector<std::string> out;
    out.push_back(header ? line(rows[0]) : line(std::vector<std::string>(cols, "")));
    out.push_back(separator);
    for (size_t i = header ? 1 : 0; i < rows.size(); i++) out.push_back(line(rows[i]));
    std::string result;
    for (size_t i = 0; i < out.size(); i++) {
        if (i > 0) result += '\n';
        result += out[i];
    }
    return result;
}

int main() {
    // Demo: a CSV-ish grid with a literal pipe in one cell (escaped on output).
    std::vector<std::vector<std::string>> rows =
        parseTable("Name,Price,Qty\nWire | cable,2 m,1\nLED,3 mm,25", ',');
    std::cout << toMarkdown(rows, true, {Align::None, Align::Right, Align::Center}) << "\n";
}

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 →