Table Generator Implementation: From Data Editing to Multi-Format Output
Table Generator Implementation: From Data Editing to Multi-Format Output#
When writing technical documentation, I often need to insert tables. Hand-writing Markdown tables is manageable, but converting to HTML or CSV is tedious. Let’s build a tool that edits once and outputs in multiple formats.
The Essence of Three Table Formats#
Markdown Tables: Pipe-Separated#
| Name | Age | City |
| --- | --- | --- |
| John | 30 | NY |
| Jane | 25 | LA |
The core is using | to separate cells, with the second row as the separator line (alignment).
HTML Tables: Semantic Tags#
<table>
<thead>
<tr><th>Name</th><th>Age</th><th>City</th></tr>
</thead>
<tbody>
<tr><td>John</td><td>30</td><td>NY</td></tr>
<tr><td>Jane</td><td>25</td><td>LA</td></tr>
</tbody>
</table>
Clear structure, but verbose to write by hand.
CSV: Comma-Separated Values#
Name,Age,City
John,30,NY
Jane,25,LA
Simplest format, Excel opens it directly.
Core Data Structure Design#
Two arrays represent the table data:
const [headers, setHeaders] = useState<string[]>(["Name", "Age", "City"])
const [data, setData] = useState<string[][]>([
["John", "30", "NY"],
["Jane", "25", "LA"],
])
Why separate storage? Because headers and data have different rendering logic:
- Headers are editable with delete column buttons
- Data rows have delete row buttons at the end
- CSV first line is headers, followed by data
Format Conversion Algorithm Implementation#
Markdown Generator#
function generateMarkdown(headers: string[], data: string[][]): string {
// Header row
let md = "| " + headers.join(" | ") + " |\n"
// Separator line (default left-aligned)
md += "| " + headers.map(() => "---").join(" | ") + " |\n"
// Data rows
data.forEach(row => {
md += "| " + row.join(" | ") + " |\n"
})
return md
}
Note that join(" | ") automatically inserts separators between elements, avoiding manual concatenation.
HTML Generator#
function generateHTML(headers: string[], data: string[][]): string {
let html = "<table>\n <thead>\n <tr>\n"
// Headers
headers.forEach(h => {
html += ` <th>${h}</th>\n`
})
html += " </tr>\n </thead>\n <tbody>\n"
// Data rows
data.forEach(row => {
html += " <tr>\n"
row.forEach(cell => {
html += ` <td>${cell}</td>\n`
})
html += " </tr>\n"
})
html += " </tbody>\n</table>"
return html
}
Spacing controls indentation for formatted output.
CSV Generator#
function generateCSV(headers: string[], data: string[][]): string {
let csv = headers.join(",") + "\n"
data.forEach(row => {
csv += row.join(",") + "\n"
})
return csv
}
Simplest, but there’s a pitfall: comma and quote handling.
Edge Cases: Special Character Escaping#
Commas in CSV#
If a cell contains a comma, direct join(",") breaks the structure:
const row = ["Apple, Inc", "100"]
row.join(",") // "Apple, Inc,100" ❌ becomes 3 columns
The correct approach is to wrap with quotes:
function escapeCSV(cell: string): string {
if (cell.includes(",") || cell.includes('"') || cell.includes("\n")) {
return `"${cell.replace(/"/g, '""')}"` // Escape quotes as double quotes
}
return cell
}
function generateCSV(headers: string[], data: string[][]): string {
const escapedHeaders = headers.map(escapeCSV)
let csv = escapedHeaders.join(",") + "\n"
data.forEach(row => {
csv += row.map(escapeCSV).join(",") + "\n"
})
return csv
}
Special Characters in HTML#
<, >, & need escaping:
function escapeHTML(str: string): string {
return str
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
}
// When generating HTML
html += ` <td>${escapeHTML(cell)}</td>\n`
Newlines in Markdown Tables#
Markdown tables don’t support multiline content. Newlines break the structure:
function escapeMarkdown(cell: string): string {
return cell.replace(/\n/g, " ") // Replace newlines with spaces
}
Dynamic Table Editing Implementation#
Add Row#
const addRow = () => {
setData([...data, new Array(cols).fill("")])
setRows(rows + 1)
}
Adds an empty row with column count determined by current cols.
Delete Row#
const removeRow = (index: number) => {
setData(data.filter((_, i) => i !== index))
setRows(rows - 1)
}
filter removes the row at the specified index.
Add Column#
const addCol = () => {
setHeaders([...headers, `Col ${cols + 1}`])
setData(data.map(row => [...row, ""])) // Append empty cell to each row
setCols(cols + 1)
}
Note: both headers and data must be updated.
Delete Column#
const removeCol = (index: number) => {
setHeaders(headers.filter((_, i) => i !== index))
setData(data.map(row => row.filter((_, i) => i !== index)))
setCols(cols - 1)
}
Iterate each row and filter out the specified column.
Real-Time Preview and Performance Optimization#
Use useMemo to cache output during editing:
const output = useMemo(() => {
switch (format) {
case "markdown":
return generateMarkdown(headers, data)
case "html":
return generateHTML(headers, data)
case "csv":
return generateCSV(headers, data)
default:
return ""
}
}, [format, headers, data])
Dependencies are format, headers, data - recalculates only when these change.
One-Click Copy Functionality#
const copyTable = async () => {
try {
await navigator.clipboard.writeText(output)
// Show copy success feedback
} catch (err) {
// Fallback: use textarea
const textarea = document.createElement("textarea")
textarea.value = output
document.body.appendChild(textarea)
textarea.select()
document.execCommand("copy")
document.body.removeChild(textarea)
}
}
Clipboard API works in HTTPS environments. HTTP requires a fallback solution.
Real-World Use Cases#
1. Technical Documentation Tables#
API docs commonly need parameter tables:
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| id | string | Yes | User ID |
| name | string | No | Username |
Generate quickly with the table generator, copy to Markdown editor.
2. Data Analysis Reports#
Convert Excel data to Markdown tables for technical blogs.
3. Frontend Component Documentation#
Props tables, Events tables, Slots tables - unified format.
Based on These Principles#
I built an online tool: Table Generator
Key features:
- Three output formats: Markdown / HTML / CSV
- Dynamic add/remove rows and columns
- Real-time preview
- One-click copy
The core algorithms aren’t complex, but handling edge cases makes it practical. Hope this helps.
Related tools: Markdown Table Generator | CSV Viewer