blob: 7df2a0502423deb32856d61eef225836b291d17f [file]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Format data as TSV (tab-separated values).
* Replaces tabs and newlines in cell values with spaces to maintain format integrity.
* @param columns The list of column IDs to export.
* @param columnNames Mapping from column IDs to display names.
* @param rows The row data to format.
*/
export function formatAsTSV(
columns: ReadonlyArray<string>,
columnNames: Record<string, string>,
rows: Array<Record<string, string>>,
): string {
const lines: string[] = [];
// Helper to escape TSV special characters
const escapeTSV = (value: string): string => {
// Replace tabs and newlines with spaces to prevent breaking the format
// Handle Windows line endings (\r\n) as a single unit first
return value.replace(/\r\n/g, ' ').replace(/[\t\n\r]/g, ' ');
};
// Header row
const headerCells = columns.map((col) => escapeTSV(columnNames[col] ?? col));
lines.push(headerCells.join('\t'));
// Data rows
for (const row of rows) {
const cells = columns.map((col) => escapeTSV(row[col] ?? ''));
lines.push(cells.join('\t'));
}
return lines.join('\n');
}
/**
* Format data as JSON
* @param columns The list of column IDs to export.
* @param columnNames Mapping from column IDs to display names.
* @param rows The row data to format.
*/
export function formatAsJSON(
columns: ReadonlyArray<string>,
columnNames: Record<string, string>,
rows: Array<Record<string, string>>,
): string {
const namedRows = rows.map((row) => {
const namedRow: Record<string, string> = {};
for (const col of columns) {
const name = columnNames[col] ?? col;
namedRow[name] = row[col] ?? '';
}
return namedRow;
});
return JSON.stringify(namedRows, null, 2);
}
/**
* Format data as Markdown table.
* Escapes special characters to prevent breaking the table format.
* @param columns The list of column IDs to export.
* @param columnNames Mapping from column IDs to display names.
* @param rows The row data to format.
*/
export function formatAsMarkdown(
columns: ReadonlyArray<string>,
columnNames: Record<string, string>,
rows: Array<Record<string, string>>,
): string {
if (columns.length === 0) return '';
const lines: string[] = [];
// Helper to escape markdown special characters
const escapeMarkdown = (value: string): string => {
return (
value
// Backslashes must be escaped first to avoid double-escaping
.replace(/\\/g, '\\\\')
// Escape pipes (table delimiters)
.replace(/\|/g, '\\|')
// Replace newlines with spaces (can't have newlines in table cells)
// Handle Windows line endings (\r\n) as a single unit first
.replace(/\r\n/g, ' ')
.replace(/[\n\r]/g, ' ')
);
};
// Header row
const headerCells = columns.map((col) =>
escapeMarkdown(columnNames[col] ?? col),
);
lines.push(`| ${headerCells.join(' | ')} |`);
// Separator row
const separators = columns.map(() => '---');
lines.push(`| ${separators.join(' | ')} |`);
// Data rows
for (const row of rows) {
const cells = columns.map((col) => escapeMarkdown(row[col] ?? ''));
lines.push(`| ${cells.join(' | ')} |`);
}
return lines.join('\n');
}