UNPKG

papir

Version:
423 lines (415 loc) 17.4 kB
import encode from './encode' import moment from 'moment' class Util { constructor () { this.timestamp = (length = 30) => { let now = '' while (now.length < length) { now += '0' } now = (String(new Date().getTime()) + now).substring(0, length) return Number(now) } this.nonce = (length = 6) => { let nonce = '' let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' for (let i = 0; i < length; i++) { nonce += possible.charAt(Math.floor(Math.random() * possible.length)) } return nonce } this.stripUri = (url) => { return url.split('?')[0].split('#')[0] } this.getParams = (url, delimiter = '&', splitter = '=', divider = '?') => { let params = [] let split = url.split(divider) if (typeof split[1] !== 'undefined') { let queries = split[1].split(delimiter) queries.forEach(q => { q = q.split(splitter) if (q.length === 2) { params.push({ key: q[0], value: q[1] }) } else { params.push({ key: q[0], value: '' }) } }) } return params } this.querystring = { /** * * @param value: any Value to be appended to name. Takes also JSON strings * @param options: any Takes also JSON strings * @param options.name: String Name to be appended a value * @param options.protocol: String URL / Percentage encode protocol. options [ 'rfc3986', 'rfc1738' ] * @param options.dateFormat: String @see http://momentjs.com * @param options.keepEmpty: boolean Keep or remove keys with empty values * @param first: boolean * @returns querystring - Ex.: 'name=val1&name2[]=val2&name2[]=val3&name3[name4]=val4&name3[name5][]=val5' */ stringify: (value = null, options, first = true) => { options = Object.assign({ name: null, protocol: 'rfc3986', encodeNull: true, dateFormat: '', // Default ISO 8601 keepEmpty: true, keepEmptyInArrays: true, keepEmptyArray: true, keepNull: true, keepNullInArrays: true, delimiter: '&', splitter: '=', dotNotation: false, encodeNames: true, encodeValues: true, indexArrays: true, excludes: [], // At first level includes: [], // At first level. includes overrides excludes arrayIndexOpen: '[', arrayIndexClose: ']', emptyArrayToZero: false, keepArrayTags: true }, options) let querystring = '' let name = options.name let error = false if ( first && name !== null && typeof name === 'string' && options.excludes.indexOf(name) !== -1 && options.includes.indexOf(name) === -1 ) { options.name = null } if (first && options.encodeNames && name !== null) { name = encode.encode(name, options.protocol, options.encodeNull) } try { if (value !== '""') { value = JSON.parse(value) } } catch (e) {} finally { if (typeof value === 'undefined' && name !== null) { // undefined } else if (value === null && name !== null) { // null } else if (typeof value === 'string' && name !== null) { // string } else if (typeof value === 'number' && name !== null) { // number } else if (typeof value === 'boolean' && name !== null) { // boolean } else if (typeof value === 'function' && name !== null) { // function value = value.toString() } else if (value.constructor === Date && name !== null) { // date value = moment(value).format(options.dateFormat) } else if (value.constructor === Array && name !== null) { let i = 0 // Handle empty arrays @todo - Make customable values. Ex. null, '', '[]', 0 or delete it etc. if (value.length !== 0) { value.forEach(val => { let arrayIdentifier = (options.arrayIndexOpen + (options.indexArrays ? i : '') + options.arrayIndexClose) querystring += this.querystring.stringify(val, Object.assign(options, { name: name + (options.encodeNames ? encode.encode(arrayIdentifier, options.protocol, options.encodeNull) : arrayIdentifier) }), false) i++ }) } // Array } else if (value.constructor === Object) { Object.keys(value).forEach(key => { if ( options.excludes.indexOf(key) === -1 || options.includes.indexOf(key) !== -1 || !first ) { if (name === null) { querystring += this.querystring.stringify(value[key], Object.assign(options, { name: options.encodeNames ? encode.encode(key, options.protocol, options.encodeNull) : key }), false) } else { let keyConverted = options.dotNotation ? ('.' + key) : (options.arrayIndexOpen + key + options.arrayIndexClose) keyConverted = options.encodeNames ? encode.encode(keyConverted, options.protocol, options.encodeNull) : keyConverted querystring += this.querystring.stringify(value[key], Object.assign(options, { name: name + keyConverted }), false) } } }) // Object } else { /* console.error({ message: 'Unknown datatype. Could not stringify value to querystring', data: options }) */ error = true // Unknown } if (!error && (value === null || (value.constructor !== Array && value.constructor !== Object))) { if (name !== null && name !== '' && value !== '') { if (options.encodeValues && (value !== null || options.keepNull)) { value = encode.encode(value, options.protocol, options.encodeNull) } if (value === '' && (options.keepEmpty || (options.keepEmptyInArrays && !first))) { querystring += name + options.splitter + value + options.delimiter } else if (value !== '' && (options.keepNull || value !== null || (options.keepNullInArrays && !first))) { querystring += name + options.splitter + value + options.delimiter } } else if (name !== null && name !== '' && options.keepEmpty) { querystring += name + options.splitter + options.delimiter } else if (name !== null && name !== '' && options.keepEmptyInArrays && !first) { querystring += name + options.splitter + options.delimiter } } else if (!error && value.constructor === Array && value.length === 0) { if (options.keepEmptyArray) { value = null if (options.emptyArrayToZero) { value = 0 } else if (!options.keepNull) { value = '' } if (options.encodeValues) { value = encode.encode(value, options.protocol, options.encodeNull) } let arrayIdentifier = (options.arrayIndexOpen + (options.indexArrays ? 0 : '') + options.arrayIndexClose) let key = '' if (options.keepArrayTags) { key = options.encodeNames ? encode.encode(arrayIdentifier, options.protocol, options.encodeNull) : arrayIdentifier } querystring += name + key + options.splitter + value + options.delimiter } } } // Remove last delimiter if (first && querystring !== '') { querystring = querystring.slice(0, -1) } return querystring }, /** * * @see https://jsfiddle.net/b4su0jvs/48/ * @param querystring * @param delimiter * @param splitter * @param divider * @param indexEncodedArrays * @returns {*} */ indexArrays: (querystring, delimiter = '&', splitter = '=', divider = '?', indexEncodedArrays = true) => { let preserved = '' let qIndex = querystring.indexOf(divider) if (qIndex !== -1) { preserved = querystring.substr(0, (qIndex + 1)) querystring = querystring.substr((qIndex + 1)) } let params = querystring.split(delimiter) let doIndexing = (params, arrStart = '[', arrEnd = ']') => { let arrUnindexed = arrStart + arrEnd let indices = [] let prevKey = '' let parsed = [] params.forEach(param => { let key = param.split(splitter)[0] let value = param.split(splitter)[1] // Secure that we have indices for all arrays in param while (indices.length < (key.split(arrUnindexed).length - 1)) { indices.push(0) // Start indexing from 0 } // Secure that indices is not more than amount of arrays while (indices.length !== (key.split(arrUnindexed).length - 1)) { indices.pop() // Remove indices not used } // Iterate through arrays in param let count = 0 // Hold track for which array we are in indices.forEach(i => { let index = key.indexOf(arrUnindexed) // index position start of array in param let arraySpace = (arrStart + i + arrEnd).length // space used by array let endIndex = index + arraySpace // index position end of array in param if ( key.replace(arrUnindexed, arrStart + i + arrEnd).substring(0, endIndex) === prevKey.substring(0, endIndex) && key.replace(arrUnindexed, arrStart + i + arrEnd).indexOf(arrUnindexed) !== -1 ) { // param is equal to prev at this index and has more unindexed arrays key = key.replace(arrUnindexed, arrStart + i + arrEnd) } else if ( key.replace(arrUnindexed, arrStart + i + arrEnd).substring(0, endIndex) === prevKey.substring(0, endIndex) && key.replace(arrUnindexed, arrStart + i + arrEnd).indexOf(arrUnindexed) === -1 ) { if (key.length > (index + arrUnindexed.length)) { // param has more indexed arrays after this index let increment = false parsed.forEach(parse => { let parseKey = parse.split(splitter)[0] if (key.substring((endIndex - 1)) === parseKey.substring(endIndex)) { // keystring after this array index is equal to some prev if (key.substring(0, index) === parseKey.substring(0, index)) { // keystring before this array index is equal to some prev increment = true } } }) if (increment) { i++ } } else { // this is the last element on key i++ } key = key.replace(arrUnindexed, arrStart + i + arrEnd) } else { // param is not equal to prev param at this index i = 0 key = key.replace(arrUnindexed, arrStart + i + arrEnd) // if param matches other parsed params at this index, increment prev array +1 from match parsed.forEach(parse => { if (parse.substring(0, endIndex) === key.substring(0, endIndex)) { let subparse = parse.substring(0, parse.lastIndexOf(arrStart + i + arrEnd)) let start = subparse.lastIndexOf(arrStart) let end = subparse.lastIndexOf(arrEnd) let preIndex = Number(key.substring((start + 1), end)) // Find last array before current index of param where array is indexed by number while (subparse.lastIndexOf(arrStart) !== -1 && isNaN(preIndex) && end > start && start !== -1) { subparse = subparse.substring(0, subparse.lastIndexOf(arrStart)) start = subparse.lastIndexOf(arrStart) end = subparse.lastIndexOf(arrEnd) preIndex = Number(key.substring((start + 1), end)) } if (isNaN(preIndex)) { // No other arrays before this index is indexed by number, increment last '[' + i + ']' key = key.substring(0, key.lastIndexOf(arrStart + i + arrEnd)) + arrStart + (i + 1) + arrEnd i++ } else { // preIndex should increment key = key.substring(0, (start + 1)) + (preIndex + 1) + key.substring(end) i = 0 indices[(count - 1)] = (preIndex + 1) // update prev indice } } }) } indices[count] = i count++ }) prevKey = key parsed.push(key + (param.split(splitter).length > 1 ? (splitter + (typeof value !== 'undefined' ? value : '')) : '')) }) return parsed } querystring = preserved let parsed = doIndexing(params) if (indexEncodedArrays) { parsed = doIndexing(params, '%5B', '%5D') } parsed.forEach(param => { querystring += param + delimiter }) return parsed.length > 0 ? querystring.slice(0, -1) : querystring }, /** * Encode arguments after first divider until second divider * Ex.: ignored?encoded?ignored?ignored @todo - encode all after first ? * @param string * @param options * @returns {string} */ encode: (string, options) => { options = Object.assign({ protocol: 'rfc3986', divider: '?', delimiter: '&', splitter: '=', encodeNull: true, keepEmpty: true, encodeNames: true, encodeValues: true }, options) let split = [ string.substring(0, string.indexOf(options.divider)), string.substring(string.indexOf(options.divider) + 1) ] let encoded = '' if (string.indexOf(options.divider) !== -1) { let params = split[1].split(options.delimiter) params.forEach(param => { let query = param.split(options.splitter) let key = '' let value = '' if (query.length > 1) { let i = 0 query.forEach(q => { if (i === 0) { key = options.encodeNames ? encode.encode(q, options.protocol, options.encodeNull) : q } else if (i === 1) { value = options.encodeValues ? encode.encode(q, options.protocol, options.encodeNull) : q } else { value += (options.encodeValues ? encode.encode(options.splitter, options.protocol, options.encodeNull) : options.splitter) + (options.encodeValues ? encode.encode(q, options.protocol, options.encodeNull) : q) } i++ }) } else if (query.length === 1) { key = options.encodeNames ? encode.encode(query[0], options.protocol, options.encodeNull) : query[0] } if (key !== '' && value !== '') { encoded += key + options.splitter encoded += value + options.delimiter } else if (key !== '' && options.keepEmpty) { encoded += key + options.splitter + options.delimiter } }) if (encoded !== '') { encoded = encoded.slice(0, -1) } } else { split[0] = string delete split[1] } // Rebuild arguments string = '' let i = 0 split.forEach(part => { if (i === 1) { string += encoded + options.divider } else { string += part + options.divider } i++ }) if (string !== '') { string = string.slice(0, -1) } return string } } } } export default new Util() let clone = (target, obj) => { if (typeof obj === 'object' && obj !== null) { for (let i in obj) { try { target[i] = (typeof obj[i] === 'object' && obj[i] !== null) ? clone(obj[i].constructor(), obj[i]) : obj[i] } catch (e) { target[i] = obj[i] } } } else { return obj } return target } export { clone }