/*!
* Copyright (c) Jesse G. Donat and contributors.
* Licensed under the MIT License.
*
* This notice may not be removed or altered from any source distribution.
*/
import { allValues, enclosure, escape, parse, separator } from "csv-walker";
export interface CsvToMarkdownOptions {
/** Quoted-field enclosure passed to csv-walker when supplied. */
enclosure?: string;
/** CSV escape character; an empty string disables escaping. */
escape?: string;
/** Replacement for newlines within CSV fields. Defaults to "
"; null skips replacement. */
newlineReplacement: string | null;
/** Transforms each parsed field; indexes are zero-based and include headers. */
cellFilter: (value: string, rowIndex: number, columnIndex: number) => string;
/** Pads cells to align columns. Defaults to true. */
prettyPrint: boolean;
}
const CsvToMarkdownOptionsDefaults: CsvToMarkdownOptions = {
newlineReplacement: "
",
cellFilter: (value) => value,
prettyPrint: true,
};
/**
* Converts CSV to Markdown Table
*
* @param {string} csvContent - The string content of the CSV
* @param {string} delimiter - The character to use as the CSV column delimiter
* @param {boolean} hasHeader - Whether to use the first row of Data as headers
* @param {Partial} options - Optional conversion settings
* @returns {string}
*/
export default function csvToMarkdown(
csvContent: string,
delimiter: string = "\t",
hasHeader: boolean = false,
options: Partial = {},
): string {
const opt: CsvToMarkdownOptions = {
...CsvToMarkdownOptionsDefaults,
...options,
};
const parserOptions = [separator(delimiter)];
if (opt.enclosure !== undefined) {
parserOptions.push(enclosure(opt.enclosure));
}
if (opt.escape !== undefined) {
parserOptions.push(escape(opt.escape));
}
const tabularData = allValues(parse(csvContent, ...parserOptions));
const maxRowLen: number[] = [];
for (const [rowIndex, values] of tabularData.entries()) {
values.forEach((column, index) => {
let value = opt.cellFilter(column, rowIndex, index);
if (delimiter != "\t") {
value = value.replace(/\t/g, " ");
}
if (opt.newlineReplacement !== null) {
const replacement = opt.newlineReplacement;
value = value.replace(/\r\n?|\n/g, () => replacement);
}
value = value.replace(/(\||\\)/g, "\\$1");
maxRowLen[index] = opt.prettyPrint
? Math.max(maxRowLen[index] ?? 0, value.length)
: 0;
values[index] = value;
});
}
if (tabularData.length === 0) {
tabularData.push([""]);
maxRowLen.push(0);
}
let headerOutput = "";
let seperatorOutput = "";
const rowEnding = opt.prettyPrint ? "| \n" : "|\n";
maxRowLen.forEach((len) => {
const sizer = Array(len + 1 + 2);
seperatorOutput += "|" + (opt.prettyPrint ? sizer.join("-") : "---");
headerOutput += "|" + (opt.prettyPrint ? sizer.join(" ") : "");
});
headerOutput += rowEnding;
seperatorOutput += rowEnding;
if (hasHeader) {
headerOutput = "";
}
let rowOutput = "";
tabularData.forEach((col, i) => {
maxRowLen.forEach((len, y) => {
const row = typeof col[y] == "undefined" ? "" : col[y];
const spacing = opt.prettyPrint
? Array(len - row.length + 1).join(" ")
: "";
const out = opt.prettyPrint ? `| ${row}${spacing} ` : `|${row}`;
if (hasHeader && i === 0) {
headerOutput += out;
} else {
rowOutput += out;
}
});
if (hasHeader && i === 0) {
headerOutput += rowEnding;
} else {
rowOutput += rowEnding;
}
});
return `${headerOutput}${seperatorOutput}${rowOutput}`;
}