h54s
Version:
HTML5 Data Adapter for SAS
178 lines (153 loc) • 7.53 kB
JavaScript
const h54sError = require('../error.js');
const logs = require('../logs.js');
/*
* Convert table object to Sas readable object
*
* @param {object} inObject - Object to convert
*
*/
module.exports.convertTableObject = function(inObject, chunkThreshold) {
const self = this;
if(chunkThreshold > 30000) {
console.warn('You should not set threshold larger than 30kb because of the SAS limitations');
}
// first check that the object is an array
if (typeof (inObject) !== 'object') {
throw new h54sError('argumentError', 'The parameter passed to checkAndGetTypeObject is not an object');
}
const arrayLength = inObject.length;
if (typeof (arrayLength) !== 'number') {
throw new h54sError('argumentError', 'The parameter passed to checkAndGetTypeObject does not have a valid length and is most likely not an array');
}
const existingCols = {}; // this is just to make lookup easier rather than traversing array each time. Will transform after
// function checkAndSetArray - this will check an inObject current key against the existing typeArray and either return -1 if there
// is a type mismatch or add an element and update/increment the length if needed
function checkAndIncrement(colSpec) {
if (typeof (existingCols[colSpec.colName]) === 'undefined') {
existingCols[colSpec.colName] = {};
existingCols[colSpec.colName].colName = colSpec.colName;
existingCols[colSpec.colName].colType = colSpec.colType;
existingCols[colSpec.colName].colLength = colSpec.colLength > 0 ? colSpec.colLength : 1;
return 0; // all ok
}
// check type match
if (existingCols[colSpec.colName].colType !== colSpec.colType) {
return -1; // there is a fudge in the typing
}
if (existingCols[colSpec.colName].colLength < colSpec.colLength) {
existingCols[colSpec.colName].colLength = colSpec.colLength > 0 ? colSpec.colLength : 1; // increment the max length of this column
return 0;
}
}
let chunkArrayCount = 0; // this is for keeping tabs on how long the current array string would be
const targetArray = []; // this is the array of target arrays
let currentTarget = 0;
targetArray[currentTarget] = [];
let j = 0;
for (let i = 0; i < inObject.length; i++) {
targetArray[currentTarget][j] = {};
let chunkRowCount = 0;
for (let key in inObject[i]) {
const thisSpec = {};
const thisValue = inObject[i][key];
//skip undefined values
if(thisValue === undefined || thisValue === null) {
continue;
}
//throw an error if there's NaN value
if(typeof thisValue === 'number' && isNaN(thisValue)) {
throw new h54sError('typeError', 'NaN value in one of the values (columns) is not allowed');
}
if(thisValue === -Infinity || thisValue === Infinity) {
throw new h54sError('typeError', thisValue.toString() + ' value in one of the values (columns) is not allowed');
}
if(thisValue === true || thisValue === false) {
throw new h54sError('typeError', 'Boolean value in one of the values (columns) is not allowed');
}
// get type... if it is an object then convert it to json and store as a string
const thisType = typeof (thisValue);
if (thisType === 'number') { // straightforward number
if(thisValue < Number.MIN_SAFE_INTEGER || thisValue > Number.MAX_SAFE_INTEGER) {
logs.addApplicationLog('Object[' + i + '].' + key + ' - This value exceeds expected numeric precision.');
}
thisSpec.colName = key;
thisSpec.colType = 'num';
thisSpec.colLength = 8;
thisSpec.encodedLength = thisValue.toString().length;
targetArray[currentTarget][j][key] = thisValue;
} else if (thisType === 'string') {
thisSpec.colName = key;
thisSpec.colType = 'string';
thisSpec.colLength = thisValue.length;
if (thisValue === "") {
targetArray[currentTarget][j][key] = " ";
} else {
targetArray[currentTarget][j][key] = encodeURIComponent(thisValue).replace(/'/g, '%27');
}
thisSpec.encodedLength = targetArray[currentTarget][j][key].length;
} else if(thisValue instanceof Date) {
console.log("ERROR VALUE ", thisValue)
console.log("TYPEOF VALUE ", typeof thisValue)
throw new h54sError('typeError', 'Date type not supported. Please use h54s.toSasDateTime function to convert it');
} else if (thisType == 'object') {
thisSpec.colName = key;
thisSpec.colType = 'json';
thisSpec.colLength = JSON.stringify(thisValue).length;
targetArray[currentTarget][j][key] = encodeURIComponent(JSON.stringify(thisValue)).replace(/'/g, '%27');
thisSpec.encodedLength = targetArray[currentTarget][j][key].length;
}
chunkRowCount = chunkRowCount + 6 + key.length + thisSpec.encodedLength;
if (checkAndIncrement(thisSpec) == -1) {
throw new h54sError('typeError', 'There is a type mismatch in the array between values (columns) of the same name.');
}
}
//remove last added row if it's empty
if(Object.keys(targetArray[currentTarget][j]).length === 0) {
targetArray[currentTarget].splice(j, 1);
continue;
}
if (chunkRowCount > chunkThreshold) {
throw new h54sError('argumentError', 'Row ' + j + ' exceeds size limit of 32kb');
} else if(chunkArrayCount + chunkRowCount > chunkThreshold) {
//create new array if this one is full and move the last item to the new array
const lastRow = targetArray[currentTarget].pop(); // get rid of that last row
currentTarget++; // move onto the next array
targetArray[currentTarget] = [lastRow]; // make it an array
j = 0; // initialise new row counter for new array - it will be incremented at the end of the function
chunkArrayCount = chunkRowCount; // this is the new chunk max size
} else {
chunkArrayCount = chunkArrayCount + chunkRowCount;
}
j++;
}
// reformat existingCols into an array so sas can parse it;
const specArray = [];
for (let k in existingCols) {
specArray.push(existingCols[k]);
}
return {
spec: specArray,
data: targetArray,
jsonLength: chunkArrayCount
}; // the spec will be the macro[0], with the data split into arrays of macro[1-n]
// means in terms of dojo xhr object at least they need to go into the same array
};
/*
* Convert javascript date to sas time
*
* @param {object} jsDate - javascript Date object
*
*/
module.exports.toSasDateTime = function (jsDate) {
const basedate = new Date("January 1, 1960 00:00:00");
const currdate = jsDate;
// offsets for UTC and timezones and BST
const baseOffset = basedate.getTimezoneOffset(); // in minutes
const currOffset = currdate.getTimezoneOffset(); // in minutes
// convert currdate to a sas datetime
const offsetSecs = (currOffset - baseOffset) * 60; // offsetDiff is in minutes to start with
const baseDateSecs = basedate.getTime() / 1000; // get rid of ms
const currdateSecs = currdate.getTime() / 1000; // get rid of ms
const sasDatetime = Math.round(currdateSecs - baseDateSecs - offsetSecs); // adjust
return sasDatetime;
};