From e36dd6c757624b5797e9062677b445de15053fb3 Mon Sep 17 00:00:00 2001 From: Barracudapi <132427435+Barracudapi@users.noreply.github.com> Date: Sun, 27 Apr 2025 14:17:14 +0800 Subject: [PATCH] Add export functionality for XML, Markdown (MD), and TSV formats This update introduces the ability to export table data in multiple formats: XML, Markdown (MD), and TSV. The following changes were made: - **XML Export**: Added support for exporting data in XML format, where table headers and records are represented in standard XML tags. - **Markdown (MD) Export**: Added the option to export data as a Markdown table. This allows users to easily generate tables for use in documentation or README files. - **TSV Export**: Added support for exporting table data in Tab-Separated Values (TSV) format, which can be easily opened and manipulated in spreadsheet software. This update improves the overall export functionality and provides users with more options to download and share data. --- .../gui/export/export-result-button.tsx | 39 +++++++- src/lib/export-helper.ts | 97 ++++++++++++++++++- 2 files changed, 131 insertions(+), 5 deletions(-) diff --git a/src/components/gui/export/export-result-button.tsx b/src/components/gui/export/export-result-button.tsx index 023b674e..a77fc22a 100644 --- a/src/components/gui/export/export-result-button.tsx +++ b/src/components/gui/export/export-result-button.tsx @@ -17,7 +17,15 @@ import OptimizeTableState, { } from "../table-optimized/optimize-table-state"; export type ExportTarget = "clipboard" | "file"; -export type ExportFormat = "csv" | "delimited" | "json" | "sql" | "xlsx"; +export type ExportFormat = + | "csv" + | "delimited" + | "json" + | "sql" + | "xlsx" + | "xml" + | "md" + | "tsv"; export type ExportSelection = | "complete" | "selected_row" @@ -144,7 +152,7 @@ export default function ExportResultButton({ const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; - a.download = `export.${format === "delimited" ? "csv" : format}`; + a.download = `export.${format === "delimited" ? "csv" : format === "xml" ? "xml" : format === "md" ? "md" : format === "tsv" ? "tsv" : format}`; a.click(); URL.revokeObjectURL(url); }, [ @@ -327,6 +335,33 @@ export default function ExportResultButton({ Excel +
+ + +
+
+ + +
+
+ + +
diff --git a/src/lib/export-helper.ts b/src/lib/export-helper.ts index c20246a7..71935b9a 100644 --- a/src/lib/export-helper.ts +++ b/src/lib/export-helper.ts @@ -161,6 +161,81 @@ export function exportDataAsDelimitedText( return content; } +export function exportRowsToXml( + headers: string[], + records: unknown[][], + exportTarget?: ExportTarget +): string { + const escapeXml = (unsafe: string) => + unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + + let xml = '\n\n'; + + for (const record of records) { + xml += " \n"; + for (let i = 0; i < headers.length; i++) { + const header = escapeXml(headers[i]); + const value = record[i] != null ? escapeXml(String(record[i])) : ""; + xml += ` <${header}>${value}\n`; + } + xml += " \n"; + } + + xml += ""; + + if (exportTarget === "clipboard") { + copyToClipboard(xml); + return ""; + } + + return xml; +} + +export function exportToMarkdown( + headers: string[], + records: unknown[][], + exportTarget?: ExportTarget +) { + let result = `| ${headers.join(" | ")} | \n`; + result += `| ${headers.map(() => "---").join(" | ")} | \n`; + + for (const record of records) { + const row = record.map((value) => `| ${value} `).join(" ") + " |"; + result += row + "\n"; + } + + if (exportTarget === "clipboard") { + copyToClipboard(result); + return ""; + } + + return result; +} + +export function exportToTSV( + headers: string[], + records: unknown[][], + exportTarget?: ExportTarget +) { + let result = `${headers.join("\t")}\n`; + for (const record of records) { + const row = record.join("\t"); + result += row + "\n"; + } + + if (exportTarget === "clipboard") { + copyToClipboard(result); + return ""; + } + + return result; +} + export function getFormatHandlers( data: OptimizeTableState, exportTarget: ExportTarget, @@ -223,6 +298,9 @@ export function getFormatHandlers( parseUserInput(exportOptions?.encloser || "") || '"', exportTarget ), + xml: () => exportRowsToXml(headers, records, exportTarget), + md: () => exportToMarkdown(headers, records, exportTarget), + tsv: () => exportToTSV(headers, records, exportTarget), }; } @@ -255,7 +333,14 @@ export async function exportTableData( exportTarget: ExportTarget, options?: ExportOptions ): Promise { - console.log("Exporting", schemaName, tableName, format, exportTarget, options); + console.log( + "Exporting", + schemaName, + tableName, + format, + exportTarget, + options + ); const result = await databaseDriver.query( `SELECT * FROM ${databaseDriver.escapeId(schemaName)}.${databaseDriver.escapeId(tableName)}` ); @@ -265,10 +350,13 @@ export async function exportTableData( } const headers = Object.keys(result.rows[0]); - const records = result.rows.map((row: { [x: string]: string; }) => headers.map(header => row[header])); + const records = result.rows.map((row: { [x: string]: string }) => + headers.map((header) => row[header]) + ); const formatHandlers = { - csv: () => exportDataAsDelimitedText(headers, records, ",", "\n", '"', exportTarget), + csv: () => + exportDataAsDelimitedText(headers, records, ",", "\n", '"', exportTarget), json: () => exportRowsToJson(headers, records, exportTarget), sql: () => exportRowsToSqlInsert(tableName, headers, records, exportTarget), xlsx: () => exportToExcel(records, headers, tableName, exportTarget), @@ -281,6 +369,9 @@ export async function exportTableData( options?.encloser || '"', exportTarget ), + xml: () => exportRowsToXml(headers, records, exportTarget), + md: () => exportToMarkdown(headers, records, exportTarget), + tsv: () => exportToTSV(headers, records, exportTarget), }; const handler = formatHandlers[format];