keystone
Version:
Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose
123 lines (115 loc) • 3.79 kB
JavaScript
var _ = require('lodash');
var listToArray = require('list-to-array');
var escapeValueForExcel = require('../security/escapeValueForExcel');
/**
* Applies option field transforms to get the CSV value for a field
*/
function transformFieldValue (field, item, options) {
var transform = typeof field.options.toCSV === 'string'
? listToArray(field.options.toCSV)
: field.options.toCSV;
if (typeof transform === 'function') {
return transform.call(item, field, options);
}
if (Array.isArray(transform)) {
var value = item.get(field.path);
if (transform.length === 1) {
return value[transform[0]];
} else {
return _.pick(value, transform);
}
}
return field.format(item);
}
/**
* Gets the data from an Item ready to be serialised to CSV for download
*/
function getCSVData (item, options) {
if (!options) {
options = {};
}
options.fields;
if (options.fields === undefined) {
options.fields = Object.keys(this.options.fields);
}
var data = {
id: String(item.id),
};
if (this.autokey) {
data[this.autokey.path] = item.get(this.autokey.path);
}
if (options.fields) {
if (typeof options.fields === 'string') {
options.fields = listToArray(options.fields);
}
if (!Array.isArray(options.fields)) {
throw new Error('List.getCSV: options.fields must be undefined, a string, or an array.');
}
options.fields.forEach(function (path) {
var field = this.fields[path];
if (!field) {
// if the path isn't actually a field, just add the value from
// that path in the mongoose document.
data[path] = item.get(path);
return;
}
if (field.type !== 'relationship' || !options.expandRelationshipFields) {
// use the transformFieldValue function to get the data
data[path] = transformFieldValue(field, item, options);
return;
}
// relationship values should be expanded into separate name and
// id pairs using the field's getExpandedData method.
var expanded = field.getExpandedData(item);
if (field.many) {
// for many-type relationships, ensure the value is an array,
// and turn it into a list of 'name (id)' values
data[path] = (Array.isArray(expanded) ? expanded : []).map(function (i) {
return i.name ? i.name + ' (' + i.id + ')' : i.id;
}).join(', ');
} else if (typeof expanded === 'object') {
// for single-type relationships, add two columns to the data
data[path] = expanded.name;
data[path + 'Id'] = expanded.id;
}
}, this);
}
if (typeof item.getCSVData === 'function') {
var ext = item.getCSVData(data, options);
if (typeof ext === 'object') {
_.forOwn(ext, function (value, key) {
if (value === undefined) {
delete data[key];
} else {
data[key] = value;
}
});
}
}
// Copy each value into the return structure, flattening arrays into lists and
// flattening objects into a column per property (one level only)
var rtn = {};
_.forOwn(data, function (value, prop) {
if (Array.isArray(value)) {
// Array values are serialised to JSON, this should be an edge-case catch
// for data coming raw from the item; array-type fields will already have
// been stringified by the field.format method.
rtn[prop] = JSON.stringify(value);
} else if (typeof value === 'object') {
// For object values, we loop through each key and add it to its own column
// in the csv. Complex values are serialised to JSON.
_.forOwn(value, function (v, i) {
var suffix = i.substr(0, 1).toUpperCase() + i.substr(1);
rtn[prop + suffix] = (typeof v === 'object') ? JSON.stringify(v) : v;
});
} else {
rtn[prop] = value;
}
});
// Prevent CSV macro injection
_.forOwn(rtn, (value, prop) => {
rtn[prop] = escapeValueForExcel(value);
});
return rtn;
}
module.exports = getCSVData;