Files
metabuilder/packages/data_table/seed/scripts/export.lua

236 lines
6.5 KiB
Lua

-- Export utilities for data tables
-- Provides CSV and JSON export functionality
---@class Export
local M = {}
---@class ExportOptions
---@field includeHeaders boolean Include column headers in export
---@field columns string[]|nil Specific columns to export (nil = all)
---@field delimiter string CSV delimiter (default: ",")
---@field lineEnding string Line ending (default: "\n")
---Escape a value for CSV
---@param value any Value to escape
---@return string Escaped CSV string
function M.escapeCsv(value)
if value == nil then
return ""
end
local str = tostring(value)
-- Check if escaping is needed
if string.find(str, '[,"\r\n]') then
-- Escape double quotes by doubling them
str = string.gsub(str, '"', '""')
-- Wrap in double quotes
str = '"' .. str .. '"'
end
return str
end
---Get column values from row data
---@param row table Row data object
---@param columns table[] Column definitions
---@param columnIds string[]|nil Specific column IDs (nil = all)
---@return string[] Array of values
function M.getRowValues(row, columns, columnIds)
local values = {}
if columnIds then
-- Specific columns only
for _, id in ipairs(columnIds) do
table.insert(values, row[id])
end
else
-- All columns
for _, col in ipairs(columns) do
if col.type ~= "actions" then
table.insert(values, row[col.id])
end
end
end
return values
end
---Get column labels for headers
---@param columns table[] Column definitions
---@param columnIds string[]|nil Specific column IDs (nil = all)
---@return string[] Array of labels
function M.getColumnLabels(columns, columnIds)
local labels = {}
if columnIds then
-- Specific columns only
for _, id in ipairs(columnIds) do
for _, col in ipairs(columns) do
if col.id == id then
table.insert(labels, col.label or col.id)
break
end
end
end
else
-- All columns (except actions)
for _, col in ipairs(columns) do
if col.type ~= "actions" then
table.insert(labels, col.label or col.id)
end
end
end
return labels
end
---Export data to CSV format
---@param data table[] Array of row data objects
---@param columns table[] Column definitions
---@param options? ExportOptions Export options
---@return string CSV formatted string
function M.exportToCsv(data, columns, options)
options = options or {}
local includeHeaders = options.includeHeaders ~= false
local columnIds = options.columns
local delimiter = options.delimiter or ","
local lineEnding = options.lineEnding or "\n"
local lines = {}
-- Add header row
if includeHeaders then
local headers = M.getColumnLabels(columns, columnIds)
local escapedHeaders = {}
for _, h in ipairs(headers) do
table.insert(escapedHeaders, M.escapeCsv(h))
end
table.insert(lines, table.concat(escapedHeaders, delimiter))
end
-- Add data rows
for _, row in ipairs(data) do
local values = M.getRowValues(row, columns, columnIds)
local escapedValues = {}
for _, v in ipairs(values) do
table.insert(escapedValues, M.escapeCsv(v))
end
table.insert(lines, table.concat(escapedValues, delimiter))
end
return table.concat(lines, lineEnding)
end
---Serialize a value to JSON
---@param value any Value to serialize
---@return string JSON string
function M.jsonEncode(value)
local t = type(value)
if value == nil then
return "null"
elseif t == "boolean" then
return value and "true" or "false"
elseif t == "number" then
return tostring(value)
elseif t == "string" then
-- Escape special characters
local escaped = value
escaped = string.gsub(escaped, '\\', '\\\\')
escaped = string.gsub(escaped, '"', '\\"')
escaped = string.gsub(escaped, '\n', '\\n')
escaped = string.gsub(escaped, '\r', '\\r')
escaped = string.gsub(escaped, '\t', '\\t')
return '"' .. escaped .. '"'
elseif t == "table" then
-- Check if array or object
local isArray = true
local maxIndex = 0
for k, _ in pairs(value) do
if type(k) ~= "number" or k < 1 or k ~= math.floor(k) then
isArray = false
break
end
if k > maxIndex then maxIndex = k end
end
isArray = isArray and maxIndex == #value
if isArray then
local items = {}
for _, v in ipairs(value) do
table.insert(items, M.jsonEncode(v))
end
return "[" .. table.concat(items, ",") .. "]"
else
local items = {}
for k, v in pairs(value) do
table.insert(items, M.jsonEncode(tostring(k)) .. ":" .. M.jsonEncode(v))
end
return "{" .. table.concat(items, ",") .. "}"
end
end
return "null"
end
---Export data to JSON format
---@param data table[] Array of row data objects
---@param columns? table[] Column definitions (optional, for column filtering)
---@param columnIds? string[] Specific columns to export (nil = all)
---@return string JSON formatted string
function M.exportToJson(data, columns, columnIds)
local result
if columns and columnIds then
-- Export only specified columns
result = {}
for _, row in ipairs(data) do
local filtered = {}
for _, id in ipairs(columnIds) do
filtered[id] = row[id]
end
table.insert(result, filtered)
end
else
result = data
end
return M.jsonEncode(result)
end
---Create a download-ready export object
---@param content string Export content
---@param filename string Suggested filename
---@param mimeType string MIME type
---@return table Export object with content, filename, mimeType
function M.createExport(content, filename, mimeType)
return {
content = content,
filename = filename,
mimeType = mimeType
}
end
---Export to CSV with download metadata
---@param data table[] Array of row data objects
---@param columns table[] Column definitions
---@param filename? string Suggested filename (default: "export.csv")
---@param options? ExportOptions Export options
---@return table Export object
function M.downloadCsv(data, columns, filename, options)
local csv = M.exportToCsv(data, columns, options)
return M.createExport(csv, filename or "export.csv", "text/csv")
end
---Export to JSON with download metadata
---@param data table[] Array of row data objects
---@param filename? string Suggested filename (default: "export.json")
---@return table Export object
function M.downloadJson(data, filename)
local json = M.exportToJson(data)
return M.createExport(json, filename or "export.json", "application/json")
end
return M