twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

parse.js (5817B)


      1 'use strict';
      2 
      3 var utils = require('./utils');
      4 
      5 var has = Object.prototype.hasOwnProperty;
      6 
      7 var defaults = {
      8     allowDots: false,
      9     allowPrototypes: false,
     10     arrayLimit: 20,
     11     decoder: utils.decode,
     12     delimiter: '&',
     13     depth: 5,
     14     parameterLimit: 1000,
     15     plainObjects: false,
     16     strictNullHandling: false
     17 };
     18 
     19 var parseValues = function parseQueryStringValues(str, options) {
     20     var obj = {};
     21     var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
     22     var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
     23     var parts = cleanStr.split(options.delimiter, limit);
     24 
     25     for (var i = 0; i < parts.length; ++i) {
     26         var part = parts[i];
     27 
     28         var bracketEqualsPos = part.indexOf(']=');
     29         var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
     30 
     31         var key, val;
     32         if (pos === -1) {
     33             key = options.decoder(part, defaults.decoder);
     34             val = options.strictNullHandling ? null : '';
     35         } else {
     36             key = options.decoder(part.slice(0, pos), defaults.decoder);
     37             val = options.decoder(part.slice(pos + 1), defaults.decoder);
     38         }
     39         if (has.call(obj, key)) {
     40             obj[key] = [].concat(obj[key]).concat(val);
     41         } else {
     42             obj[key] = val;
     43         }
     44     }
     45 
     46     return obj;
     47 };
     48 
     49 var parseObject = function (chain, val, options) {
     50     var leaf = val;
     51 
     52     for (var i = chain.length - 1; i >= 0; --i) {
     53         var obj;
     54         var root = chain[i];
     55 
     56         if (root === '[]') {
     57             obj = [];
     58             obj = obj.concat(leaf);
     59         } else {
     60             obj = options.plainObjects ? Object.create(null) : {};
     61             var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
     62             var index = parseInt(cleanRoot, 10);
     63             if (
     64                 !isNaN(index)
     65                 && root !== cleanRoot
     66                 && String(index) === cleanRoot
     67                 && index >= 0
     68                 && (options.parseArrays && index <= options.arrayLimit)
     69             ) {
     70                 obj = [];
     71                 obj[index] = leaf;
     72             } else {
     73                 obj[cleanRoot] = leaf;
     74             }
     75         }
     76 
     77         leaf = obj;
     78     }
     79 
     80     return leaf;
     81 };
     82 
     83 var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
     84     if (!givenKey) {
     85         return;
     86     }
     87 
     88     // Transform dot notation to bracket notation
     89     var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey;
     90 
     91     // The regex chunks
     92 
     93     var brackets = /(\[[^[\]]*])/;
     94     var child = /(\[[^[\]]*])/g;
     95 
     96     // Get the parent
     97 
     98     var segment = brackets.exec(key);
     99     var parent = segment ? key.slice(0, segment.index) : key;
    100 
    101     // Stash the parent if it exists
    102 
    103     var keys = [];
    104     if (parent) {
    105         // If we aren't using plain objects, optionally prefix keys
    106         // that would overwrite object prototype properties
    107         if (!options.plainObjects && has.call(Object.prototype, parent)) {
    108             if (!options.allowPrototypes) {
    109                 return;
    110             }
    111         }
    112 
    113         keys.push(parent);
    114     }
    115 
    116     // Loop through children appending to the array until we hit depth
    117 
    118     var i = 0;
    119     while ((segment = child.exec(key)) !== null && i < options.depth) {
    120         i += 1;
    121         if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
    122             if (!options.allowPrototypes) {
    123                 return;
    124             }
    125         }
    126         keys.push(segment[1]);
    127     }
    128 
    129     // If there's a remainder, just add whatever is left
    130 
    131     if (segment) {
    132         keys.push('[' + key.slice(segment.index) + ']');
    133     }
    134 
    135     return parseObject(keys, val, options);
    136 };
    137 
    138 module.exports = function (str, opts) {
    139     var options = opts ? utils.assign({}, opts) : {};
    140 
    141     if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') {
    142         throw new TypeError('Decoder has to be a function.');
    143     }
    144 
    145     options.ignoreQueryPrefix = options.ignoreQueryPrefix === true;
    146     options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter;
    147     options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth;
    148     options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
    149     options.parseArrays = options.parseArrays !== false;
    150     options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder;
    151     options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots;
    152     options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects;
    153     options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes;
    154     options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit;
    155     options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
    156 
    157     if (str === '' || str === null || typeof str === 'undefined') {
    158         return options.plainObjects ? Object.create(null) : {};
    159     }
    160 
    161     var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
    162     var obj = options.plainObjects ? Object.create(null) : {};
    163 
    164     // Iterate over the keys and setup the new object
    165 
    166     var keys = Object.keys(tempObj);
    167     for (var i = 0; i < keys.length; ++i) {
    168         var key = keys[i];
    169         var newObj = parseKeys(key, tempObj[key], options);
    170         obj = utils.merge(obj, newObj, options);
    171     }
    172 
    173     return utils.compact(obj);
    174 };