arquero
Version:
Query processing and transformation of array-backed data tables.
121 lines (108 loc) • 4.43 kB
JavaScript
import { isFunction } from '../util/is-function.js';
import { mapObject } from '../util/map-object.js';
import { columns } from './util/columns.js';
import { formats } from './util/formats.js';
import { formatValue } from './util/format-value.js';
import { scan } from './util/scan.js';
/**
* Null format function.
* @callback NullFormat
* @param {null|undefined} [value] The value to format.
* @return {string} The formatted HTML string.
*/
/**
* CSS style function.
* @callback StyleFunction
* @param {string} name The column name.
* @param {number} row The table row index.
* @return {string} A CSS style string.
*/
/**
* CSS style options.
* @typedef {Object.<string, string | StyleFunction>} StyleOptions
*/
/**
* Options for HTML formatting.
* @typedef {object} HTMLFormatOptions
* @property {number} [limit=Infinity] The maximum number of rows to print.
* @property {number} [offset=0] The row offset indicating how many initial rows to skip.
* @property {import('./types.js').ColumnSelectOptions} [columns] Ordered list
* of column names to include. If function-valued, the function should
* accept a table as input and return an array of column name strings.
* @property {import('./types.js').ColumnAlignOptions} [align] Object of column
* alignment options. The object keys should be column names. The object
* values should be aligment strings, one of 'l' (left), 'c' (center), or
* 'r' (right). If specified, these override automatically inferred options.
* @property {import('./types.js').ColumnFormatOptions} [format] Object of column
* format options. The object keys should be column names. The object values
* should be formatting functions or specification objects. If specified,
* these override automatically inferred options.
* @property {NullFormat} [null] Format function for null or undefined values.
* If specified, this function will be invoked with the null or undefined
* value as the sole input, and the return value will be used as the HTML
* output for the value.
* @property {StyleOptions} [style] CSS styles to include in HTML output.
* The object keys should be HTML table tag names: 'table', 'thead',
* 'tbody', 'tr', 'th', or 'td'. The object values should be strings of
* valid CSS style directives (such as "font-weight: bold;") or functions
* that take a column name and row as inputs and return a CSS string.
* @property {number} [maxdigits=6] The maximum number of fractional digits
* to include when formatting numbers. This option is passed to the format
* inference method and is overridden by any explicit format options.
*/
/**
* Format a table as an HTML table string.
* @param {import('../table/Table.js').Table} table The table to format.
* @param {HTMLFormatOptions} options The formatting options.
* @return {string} An HTML table string.
*/
export function toHTML(table, options = {}) {
const names = columns(table, options.columns);
const { align, format } = formats(table, names, options);
const style = styles(options);
const nullish = options.null;
const alignValue = a => a === 'c' ? 'center' : a === 'r' ? 'right' : 'left';
const escape = s => s.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
const baseFormat = (value, opt) => escape(formatValue(value, opt));
const formatter = nullish
? (value, opt) => value == null ? nullish(value) : baseFormat(value, opt)
: baseFormat;
let r = -1;
let idx = -1;
const tag = (tag, name, shouldAlign) => {
const a = shouldAlign ? alignValue(align[name]) : '';
const s = style[tag] ? (style[tag](name, idx, r) || '') : '';
const css = (a ? (`text-align: ${a};` + (s ? ' ' : '')) : '') + s;
return `<${tag}${css ? ` style="${css}"` : ''}>`;
};
let text = tag('table')
+ tag('thead')
+ tag('tr', r)
+ names.map(name => `${tag('th', name, 1)}${name}</th>`).join('')
+ '</tr></thead>'
+ tag('tbody');
scan(table, names, options.limit, options.offset, {
start(row) {
r = row;
++idx;
text += tag('tr');
},
cell(value, name) {
text += tag('td', name, 1)
+ formatter(value, format[name])
+ '</td>';
},
end() {
text += '</tr>';
}
});
return text + '</tbody></table>';
}
function styles(options) {
return mapObject(
options.style,
value => isFunction(value) ? value : () => value
);
}