273 lines
10 KiB
JavaScript
273 lines
10 KiB
JavaScript
flectra.define("web.Domain", function (require) {
|
|
"use strict";
|
|
|
|
var collections = require("web.collections");
|
|
var pyeval = require("web.pyeval");
|
|
|
|
/**
|
|
* The Domain Class allows to work with a domain as a tree and provides tools
|
|
* to manipulate array and string representations of domains.
|
|
*/
|
|
var Domain = collections.Tree.extend({
|
|
/**
|
|
* @constructor
|
|
* @param {string|Array|boolean|undefined} domain
|
|
* The given domain can be:
|
|
* * a string representation of the Python prefix-array
|
|
* representation of the domain.
|
|
* * a JS prefix-array representation of the domain.
|
|
* * a boolean where the "true" domain match all records and the
|
|
* "false" domain does not match any records.
|
|
* * undefined, considered as the false boolean.
|
|
* * a number, considered as true except 0 considered as false.
|
|
* @param {Object} [evalContext] - in case the given domain is a string, an
|
|
* evaluation context might be needed
|
|
*/
|
|
init: function (domain, evalContext) {
|
|
this._super.apply(this, arguments);
|
|
if (_.isArray(domain) || _.isString(domain)) {
|
|
this._parse(this.normalizeArray(_.clone(this.stringToArray(domain, evalContext))));
|
|
} else {
|
|
this._data = !!domain;
|
|
}
|
|
},
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Public
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Evaluates the domain with a set of values.
|
|
*
|
|
* @param {Object} values - a mapping {fieldName -> fieldValue} (note: all
|
|
* the fields used in the domain should be given a
|
|
* value otherwise the computation will break)
|
|
* @returns {boolean}
|
|
*/
|
|
compute: function (values) {
|
|
if (this._data === true || this._data === false) {
|
|
// The domain is a always-true or a always-false domain
|
|
return this._data;
|
|
} else if (_.isArray(this._data)) {
|
|
// The domain is a [name, operator, value] entity
|
|
// First check if we have the field value in the field values set
|
|
// and if the first part of the domain contains 'parent.field'
|
|
// get the value from the parent record.
|
|
var isParentField = false;
|
|
var fieldName = this._data[0];
|
|
// We split the domain first part and check if it's a match
|
|
// for the syntax 'parent.field'.
|
|
var parentField = this._data[0].split('.');
|
|
if ('parent' in values && parentField.length === 2) {
|
|
fieldName = parentField[1];
|
|
isParentField = parentField[0] === 'parent' &&
|
|
fieldName in values.parent;
|
|
}
|
|
if (!(this._data[0] in values) && !(isParentField)) {
|
|
throw new Error(_.str.sprintf(
|
|
"Unknown field %s in domain",
|
|
this._data[0]
|
|
));
|
|
}
|
|
var fieldValue;
|
|
if (!isParentField) {
|
|
fieldValue = values[fieldName];
|
|
} else {
|
|
fieldValue = values.parent[fieldName];
|
|
}
|
|
|
|
switch (this._data[1]) {
|
|
case "=":
|
|
case "==":
|
|
return _.isEqual(fieldValue, this._data[2]);
|
|
case "!=":
|
|
case "<>":
|
|
return !_.isEqual(fieldValue, this._data[2]);
|
|
case "<":
|
|
return (fieldValue < this._data[2]);
|
|
case ">":
|
|
return (fieldValue > this._data[2]);
|
|
case "<=":
|
|
return (fieldValue <= this._data[2]);
|
|
case ">=":
|
|
return (fieldValue >= this._data[2]);
|
|
case "in":
|
|
return _.contains(
|
|
_.isArray(this._data[2]) ? this._data[2] : [this._data[2]],
|
|
fieldValue
|
|
);
|
|
case "not in":
|
|
return !_.contains(
|
|
_.isArray(this._data[2]) ? this._data[2] : [this._data[2]],
|
|
fieldValue
|
|
);
|
|
case "like":
|
|
return (fieldValue.toLowerCase().indexOf(this._data[2].toLowerCase()) >= 0);
|
|
case "ilike":
|
|
return (fieldValue.indexOf(this._data[2]) >= 0);
|
|
default:
|
|
throw new Error(_.str.sprintf(
|
|
"Domain %s uses an unsupported operator",
|
|
this._data
|
|
));
|
|
}
|
|
} else { // The domain is a set of [name, operator, value] entitie(s)
|
|
switch (this._data) {
|
|
case "&":
|
|
return _.every(this._children, function (child) {
|
|
return child.compute(values);
|
|
});
|
|
case "|":
|
|
return _.some(this._children, function (child) {
|
|
return child.compute(values);
|
|
});
|
|
case "!":
|
|
return !this._children[0].compute(values);
|
|
}
|
|
}
|
|
},
|
|
/**
|
|
* Return the JS prefix-array representation of this domain. Note that all
|
|
* domains that use the "false" domain cannot be represented as such.
|
|
*
|
|
* @returns {Array} JS prefix-array representation of this domain
|
|
*/
|
|
toArray: function () {
|
|
if (this._data === false) {
|
|
throw new Error("'false' domain cannot be converted to array");
|
|
} else if (this._data === true) {
|
|
return [];
|
|
} else {
|
|
var arr = [this._data];
|
|
return arr.concat.apply(arr, _.map(this._children, function (child) {
|
|
return child.toArray();
|
|
}));
|
|
}
|
|
},
|
|
/**
|
|
* @returns {string} representation of the Python prefix-array
|
|
* representation of the domain
|
|
*/
|
|
toString: function () {
|
|
return Domain.prototype.arrayToString(this.toArray());
|
|
},
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Private
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Initializes the tree representation of the domain according to its given
|
|
* JS prefix-array representation. Note: the given array is considered
|
|
* already normalized.
|
|
*
|
|
* @private
|
|
* @param {Array} domain - normalized JS prefix-array representation of
|
|
* the domain
|
|
*/
|
|
_parse: function (domain) {
|
|
this._data = (domain.length === 0 ? true : domain[0]);
|
|
if (domain.length <= 1) return;
|
|
|
|
var expected = 1;
|
|
for (var i = 1 ; i < domain.length ; i++) {
|
|
if (domain[i] === "&" || domain[i] === "|") {
|
|
expected++;
|
|
} else if (domain[i] !== "!") {
|
|
expected--;
|
|
}
|
|
|
|
if (!expected) {
|
|
i++;
|
|
this._addSubdomain(domain.slice(1, i));
|
|
this._addSubdomain(domain.slice(i));
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Adds a domain as a child (e.g. if the current domain is ["|", A, B],
|
|
* using this method with a ["&", C, D] domain will result in a
|
|
* ["|", "|", A, B, "&", C, D]).
|
|
* Note: the internal tree representation is automatically simplified.
|
|
*
|
|
* @param {Array} domain - normalized JS prefix-array representation of a
|
|
* domain to add
|
|
*/
|
|
_addSubdomain: function (domain) {
|
|
if (!domain.length) return;
|
|
var subdomain = new Domain(domain);
|
|
|
|
if (!subdomain._children.length || subdomain._data !== this._data) {
|
|
this._children.push(subdomain);
|
|
} else {
|
|
var self = this;
|
|
_.each(subdomain._children, function (childDomain) {
|
|
self._children.push(childDomain);
|
|
});
|
|
}
|
|
},
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Static
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Converts JS prefix-array representation of a domain to a string
|
|
* representation of the Python prefix-array representation of this domain.
|
|
*
|
|
* @static
|
|
* @param {Array|string} domain
|
|
* @returns {string}
|
|
*/
|
|
arrayToString: function (domain) {
|
|
if (_.isString(domain)) return domain;
|
|
return JSON.stringify(domain || [])
|
|
.replace(/null/g, "None")
|
|
.replace(/false/g, "False")
|
|
.replace(/true/g, "True");
|
|
},
|
|
/**
|
|
* Converts a string representation of the Python prefix-array
|
|
* representation of a domain to a JS prefix-array representation of this
|
|
* domain.
|
|
*
|
|
* @static
|
|
* @param {string|Array} domain
|
|
* @param {Object} [evalContext]
|
|
* @returns {Array}
|
|
*/
|
|
stringToArray: function (domain, evalContext) {
|
|
if (!_.isString(domain)) return _.clone(domain);
|
|
return pyeval.eval("domain", domain || "[]", evalContext);
|
|
},
|
|
/**
|
|
* Makes implicit "&" operators explicit in the given JS prefix-array
|
|
* representation of domain (e.g [A, B] -> ["&", A, B])
|
|
*
|
|
* @static
|
|
* @param {Array} domain - the JS prefix-array representation of the domain
|
|
* to normalize (! will be normalized in-place)
|
|
* @returns {Array} the normalized JS prefix-array representation of the
|
|
* given domain
|
|
*/
|
|
normalizeArray: function (domain) {
|
|
var expected = 1;
|
|
_.each(domain, function (item) {
|
|
if (item === "&" || item === "|") {
|
|
expected++;
|
|
} else if (item !== "!") {
|
|
expected--;
|
|
}
|
|
});
|
|
if (expected < 0) {
|
|
domain.unshift.apply(domain, _.times(Math.abs(expected), _.constant("&")));
|
|
}
|
|
return domain;
|
|
},
|
|
});
|
|
|
|
return Domain;
|
|
});
|