neo4jmapper src/helpers.js

    Source

    if (typeof window === 'object') {
      var _ = window._;
    } else {
      var _ = require('underscore');
    }
    
    var isConditionalOperator = /^\$(AND|OR|XOR|NOT|AND\$NOT|OR\$NOT)$/i;
    
    var sortStringAndOptionsArguments = function(string, options) {
      if (typeof string === 'object') {
        return { string: null, options: string };
      }
      return {
        string: string || null,
        options: options || {}
      }
    }
    
    var sortOptionsAndCallbackArguments = function(options, callback) {
      if (typeof options === 'function') {
        return { options: {}, callback: options };
      }
      return {
        options: options || {},
        callback: callback
      }
    }
    
    var sortStringAndCallbackArguments = function(string, callback) {
      if (typeof string === 'function') {
        callback = string;
        string = null;
      }
      return {
        callback: callback,
        string: string
      }
    }
    
    var getIdFromObject = function(o) {
      if ((typeof o === 'object') && (!isNaN(o._id_)))
        return o._id_;
      if (!isNaN(parseInt(o)))
        return parseInt(o);
      // else
      return o.id || null;
    }
    
    // source: https://gist.github.com/penguinboy/762197
    var flattenObject = function(ob, keepNullValues) {
      var toReturn = {};
      if (typeof keepNullValues !== 'boolean')
        keepNullValues = true;
      for (var i in ob) {
        if (!ob.hasOwnProperty(i))
          continue;
        if ((keepNullValues) && (ob[i] === null)) {
          toReturn[i] = ob[i];
        } else if ((typeof ob[i]) === 'object') {
          var flatObject = flattenObject(ob[i]);
          for (var x in flatObject) {
            if (!flatObject.hasOwnProperty(x)) continue;
    
            toReturn[i + '.' + x] = flatObject[x];
          }
        } else {
          toReturn[i] = ob[i];
        }
      }
      return toReturn;
    };
    
    // source: https://github.com/hughsk/flat/blob/master/index.js
    var unflattenObject = function (target, opts) {
      var opts = opts || {}
        , delimiter = opts.delimiter || '.'
        , result = {}
    
      if (Object.prototype.toString.call(target) !== '[object Object]') {
          return target
      }
    
      function getkey(key) {
          var parsedKey = parseInt(key)
          return (isNaN(parsedKey) ? key : parsedKey)
      };
    
      Object.keys(target).forEach(function(key) {
          var split = key.split(delimiter)
            , firstNibble
            , secondNibble
            , recipient = result
    
          firstNibble = getkey(split.shift())
          secondNibble = getkey(split[0])
    
          while (secondNibble !== undefined) {
              if (recipient[firstNibble] === undefined) {
                  recipient[firstNibble] = ((typeof secondNibble === 'number') ? [] : {})
              }
    
              recipient = recipient[firstNibble]
              if (split.length > 0) {
                  firstNibble = getkey(split.shift())
                  secondNibble = getkey(split[0])
              }
          }
    
          // unflatten again for 'messy objects'
          recipient[firstNibble] = unflattenObject(target[key])
      });
    
      return result
    };
    
    var escapeString = function(s) {
      if (typeof s !== 'string')
        return s;
      // trim quotes if exists
      if ( (/^".+"$/.test(s)) || (/^'.+'$/.test(s)) )
        s = s.substr(1,s.length-2);
      return s.replace(/^(['"]{1})/, '\\$1').replace(/([^\\]){1}(['"]{1})/g,'$1\\$2');
    }
    
    var escapeProperty = function(identifier, delimiter) {
      if (typeof delimiter !== 'string')
        delimiter = '`';
      // do we have s.th. like ?! appending
      var appending = identifier.match(/^(.*)([\?\!]{1})$/) || '';
      // console.log(appending)
      if ((appending)&&(appending[2])) {
        identifier = appending[1];
        appending = appending[2];
      }
      // no escaping if last char is a delimiter or ?, because we expect that the identifier is already escaped somehow
      if (new RegExp(''+delimiter+'{1}$').test(identifier))
        return identifier;
      // remove all delimiters `
      identifier = identifier.replace(new RegExp(delimiter, 'g'), '');
      if (/^(.+?)\..+$/.test(identifier))
        identifier = identifier.replace(/^(.+?)\.(.+)$/, '$1.'+delimiter+'$2'+delimiter);
      else
        identifier = delimiter+identifier+delimiter;
      return identifier + appending;
    }
    
    var valueToStringForCypherQuery = function(value, delimiter) {
      if (typeof delimiter !== 'string')
        delimiter = '';
      if ((value) && (value.constructor === RegExp)) {
        value = value.toString().replace(/^\/(\^)*(.+?)\/[ig]*$/, (value.ignoreCase) ? '$1(?i)$2' : '$1$2');
        // replace `\` with `\\` to keep compatibility with Java regex
        value = value.replace(/([^\\]{1})\\([^\\]{1})/g, '$1\\\\$2');
      } else {
        if ((typeof value === 'undefined') || (value === null))
          value = 'NULL';
        else if (value === NaN)
          // what would be a good representation of NaN?
          value = delimiter+'NaN'+delimiter;
        else if ((typeof value === 'boolean') || (typeof value === 'number'))
          value = String(value);
        else
          value = delimiter + escapeString(value) + delimiter;
      }
    
      return value;
    }
    
    var cypherKeyValueToString = function(key, originalValue, identifier, conditionalParametersObject) {
      var value = originalValue;
      var s = ''; // string that will be returned
      if (typeof conditionalParametersObject !== 'object')
        conditionalParametersObject = null;
      if (typeof identifier === 'string') {
        if (/^[nmr]\./.test(key))
          // we have already an identifier
          key = key;
        else if (/[\?\!]$/.test(key))
          // we have a default statement, escape without ! or ?
          key = identifier+'.'+key;
        else
          key = identifier+'.'+key;
      }
      key = escapeProperty(key)
      if (_.isRegExp(value)) {
        value = valueToStringForCypherQuery(value);
        value = ((conditionalParametersObject) && (conditionalParametersObject.valuesToParameters)) ? ((conditionalParametersObject.addValue) ? conditionalParametersObject.addValue(value) : value) : "'"+value+"'";
        s = key + " =~ " + value;
      }
      else {
        // convert to string
        if ((_.isNumber(value)) || (_.isBoolean(value)))
          value = ((conditionalParametersObject) && (conditionalParametersObject.valuesToParameters)) ? ((conditionalParametersObject.addValue) ? conditionalParametersObject.addValue(value) : value) : valueToStringForCypherQuery(value);
        // else escape
        else
          value = ((conditionalParametersObject) && (conditionalParametersObject.valuesToParameters)) ? ((conditionalParametersObject.addValue) ? conditionalParametersObject.addValue(value) : value) : "'"+escapeString(value)+"'";
        s = key + " = " + value;
      }
    
      return s;
    }
    
    var extractAttributesFromCondition = function(condition, attributes) {
      if (typeof attributes === 'undefined')
        attributes = [];
      _.each(condition, function(value, key) {
    
        if (_.isObject(value)) {
          extractAttributesFromCondition(condition[key], attributes);
        }
        if ( (!isConditionalOperator.test(key)) && (/^[a-zA-Z\_\-\.]+$/.test(key)) ) {
          // remove identifiers if exists
          attributes.push(key.replace(/^[nmr]{1}\./,''));
        }
      });
      return _.uniq(attributes);
    }
    
    var constructorNameOfFunction = function(func) {
      var name = func.constructor.toString().match(/^function\s(.+?)\(/)[1];
      if (name === 'Function') {
        name = func.toString().match(/^function\s(.+)\(/)[1]
      }
      return name;
    }
    
    var isObjectLiteral = function(data) {
      return Boolean((typeof data === 'object') && (data !== null) && (typeof Object.keys(data).length === 'number'));
    }
    
    var serializeObjectForCypher = function(o, options) {
      o = this.flattenObject(o);
      var result = [];
      if (typeof options !== 'object')
        options = {};
      options = _.defaults(options, {
        identifierDelimiter: '`',
        valueDelimiter: "'",
      });
      for (var attr in o) {
        var value = o[attr];
        result.push(escapeProperty(attr)+' : '+valueToStringForCypherQuery(value, options.valueDelimiter));
      }
      return '{ '+result.join(', ')+' }';
    }
    
    var helpers = {
      sortStringAndOptionsArguments: sortStringAndOptionsArguments,
      sortOptionsAndCallbackArguments: sortOptionsAndCallbackArguments,
      sortStringAndCallbackArguments: sortStringAndCallbackArguments,
      flattenObject: flattenObject,
      unflattenObject: unflattenObject,
      extractAttributesFromCondition: extractAttributesFromCondition,
      getIdFromObject: getIdFromObject,
      escapeString: escapeString,
      escapeProperty: escapeProperty,
      constructorNameOfFunction: constructorNameOfFunction,
      cypherKeyValueToString: cypherKeyValueToString,
      valueToStringForCypherQuery: valueToStringForCypherQuery,
      md5: (typeof window === 'object') ? window.Neo4jMapper.md5 : require('./lib/md5'),
      isConditionalOperator: isConditionalOperator,
      isObjectLiteral: isObjectLiteral,
      serializeObjectForCypher: serializeObjectForCypher,
    };
    
    if (typeof window !== 'object') {
      module.exports = exports = helpers;
    } else {
      window.Neo4jMapper.helpers = helpers;
    }