UNPKG

7.53 kBJavaScriptView Raw
1const h54sError = require('../error.js');
2const logs = require('../logs.js');
3
4/*
5* Convert table object to Sas readable object
6*
7* @param {object} inObject - Object to convert
8*
9*/
10module.exports.convertTableObject = function(inObject, chunkThreshold) {
11 const self = this;
12
13 if(chunkThreshold > 30000) {
14 console.warn('You should not set threshold larger than 30kb because of the SAS limitations');
15 }
16
17 // first check that the object is an array
18 if (typeof (inObject) !== 'object') {
19 throw new h54sError('argumentError', 'The parameter passed to checkAndGetTypeObject is not an object');
20 }
21
22 const arrayLength = inObject.length;
23 if (typeof (arrayLength) !== 'number') {
24 throw new h54sError('argumentError', 'The parameter passed to checkAndGetTypeObject does not have a valid length and is most likely not an array');
25 }
26
27 const existingCols = {}; // this is just to make lookup easier rather than traversing array each time. Will transform after
28
29 // function checkAndSetArray - this will check an inObject current key against the existing typeArray and either return -1 if there
30 // is a type mismatch or add an element and update/increment the length if needed
31
32 function checkAndIncrement(colSpec) {
33 if (typeof (existingCols[colSpec.colName]) === 'undefined') {
34 existingCols[colSpec.colName] = {};
35 existingCols[colSpec.colName].colName = colSpec.colName;
36 existingCols[colSpec.colName].colType = colSpec.colType;
37 existingCols[colSpec.colName].colLength = colSpec.colLength > 0 ? colSpec.colLength : 1;
38 return 0; // all ok
39 }
40 // check type match
41 if (existingCols[colSpec.colName].colType !== colSpec.colType) {
42 return -1; // there is a fudge in the typing
43 }
44 if (existingCols[colSpec.colName].colLength < colSpec.colLength) {
45 existingCols[colSpec.colName].colLength = colSpec.colLength > 0 ? colSpec.colLength : 1; // increment the max length of this column
46 return 0;
47 }
48 }
49 let chunkArrayCount = 0; // this is for keeping tabs on how long the current array string would be
50 const targetArray = []; // this is the array of target arrays
51 let currentTarget = 0;
52 targetArray[currentTarget] = [];
53 let j = 0;
54 for (let i = 0; i < inObject.length; i++) {
55 targetArray[currentTarget][j] = {};
56 let chunkRowCount = 0;
57
58 for (let key in inObject[i]) {
59 const thisSpec = {};
60 const thisValue = inObject[i][key];
61
62 //skip undefined values
63 if(thisValue === undefined || thisValue === null) {
64 continue;
65 }
66
67 //throw an error if there's NaN value
68 if(typeof thisValue === 'number' && isNaN(thisValue)) {
69 throw new h54sError('typeError', 'NaN value in one of the values (columns) is not allowed');
70 }
71
72 if(thisValue === -Infinity || thisValue === Infinity) {
73 throw new h54sError('typeError', thisValue.toString() + ' value in one of the values (columns) is not allowed');
74 }
75
76 if(thisValue === true || thisValue === false) {
77 throw new h54sError('typeError', 'Boolean value in one of the values (columns) is not allowed');
78 }
79
80 // get type... if it is an object then convert it to json and store as a string
81 const thisType = typeof (thisValue);
82
83 if (thisType === 'number') { // straightforward number
84 if(thisValue < Number.MIN_SAFE_INTEGER || thisValue > Number.MAX_SAFE_INTEGER) {
85 logs.addApplicationLog('Object[' + i + '].' + key + ' - This value exceeds expected numeric precision.');
86 }
87 thisSpec.colName = key;
88 thisSpec.colType = 'num';
89 thisSpec.colLength = 8;
90 thisSpec.encodedLength = thisValue.toString().length;
91 targetArray[currentTarget][j][key] = thisValue;
92 } else if (thisType === 'string') {
93 thisSpec.colName = key;
94 thisSpec.colType = 'string';
95 thisSpec.colLength = thisValue.length;
96
97 if (thisValue === "") {
98 targetArray[currentTarget][j][key] = " ";
99 } else {
100 targetArray[currentTarget][j][key] = encodeURIComponent(thisValue).replace(/'/g, '%27');
101 }
102 thisSpec.encodedLength = targetArray[currentTarget][j][key].length;
103 } else if(thisValue instanceof Date) {
104 console.log("ERROR VALUE ", thisValue)
105 console.log("TYPEOF VALUE ", typeof thisValue)
106 throw new h54sError('typeError', 'Date type not supported. Please use h54s.toSasDateTime function to convert it');
107 } else if (thisType == 'object') {
108 thisSpec.colName = key;
109 thisSpec.colType = 'json';
110 thisSpec.colLength = JSON.stringify(thisValue).length;
111 targetArray[currentTarget][j][key] = encodeURIComponent(JSON.stringify(thisValue)).replace(/'/g, '%27');
112 thisSpec.encodedLength = targetArray[currentTarget][j][key].length;
113 }
114
115 chunkRowCount = chunkRowCount + 6 + key.length + thisSpec.encodedLength;
116
117 if (checkAndIncrement(thisSpec) == -1) {
118 throw new h54sError('typeError', 'There is a type mismatch in the array between values (columns) of the same name.');
119 }
120 }
121
122 //remove last added row if it's empty
123 if(Object.keys(targetArray[currentTarget][j]).length === 0) {
124 targetArray[currentTarget].splice(j, 1);
125 continue;
126 }
127
128 if (chunkRowCount > chunkThreshold) {
129 throw new h54sError('argumentError', 'Row ' + j + ' exceeds size limit of 32kb');
130 } else if(chunkArrayCount + chunkRowCount > chunkThreshold) {
131 //create new array if this one is full and move the last item to the new array
132 const lastRow = targetArray[currentTarget].pop(); // get rid of that last row
133 currentTarget++; // move onto the next array
134 targetArray[currentTarget] = [lastRow]; // make it an array
135 j = 0; // initialise new row counter for new array - it will be incremented at the end of the function
136 chunkArrayCount = chunkRowCount; // this is the new chunk max size
137 } else {
138 chunkArrayCount = chunkArrayCount + chunkRowCount;
139 }
140 j++;
141 }
142
143 // reformat existingCols into an array so sas can parse it;
144 const specArray = [];
145 for (let k in existingCols) {
146 specArray.push(existingCols[k]);
147 }
148 return {
149 spec: specArray,
150 data: targetArray,
151 jsonLength: chunkArrayCount
152 }; // the spec will be the macro[0], with the data split into arrays of macro[1-n]
153 // means in terms of dojo xhr object at least they need to go into the same array
154};
155
156/*
157* Convert javascript date to sas time
158*
159* @param {object} jsDate - javascript Date object
160*
161*/
162module.exports.toSasDateTime = function (jsDate) {
163 const basedate = new Date("January 1, 1960 00:00:00");
164 const currdate = jsDate;
165
166 // offsets for UTC and timezones and BST
167 const baseOffset = basedate.getTimezoneOffset(); // in minutes
168 const currOffset = currdate.getTimezoneOffset(); // in minutes
169
170 // convert currdate to a sas datetime
171 const offsetSecs = (currOffset - baseOffset) * 60; // offsetDiff is in minutes to start with
172 const baseDateSecs = basedate.getTime() / 1000; // get rid of ms
173 const currdateSecs = currdate.getTime() / 1000; // get rid of ms
174 const sasDatetime = Math.round(currdateSecs - baseDateSecs - offsetSecs); // adjust
175
176 return sasDatetime;
177};