Add DomainParser
This commit is contained in:
+718
@@ -238,4 +238,722 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Sao.common.udlex = Sao.class_(Object, {
|
||||
init: function(instream) {
|
||||
|
||||
var Stream = Sao.class_(Object, {
|
||||
init: function(stream) {
|
||||
this.stream = stream.split('');
|
||||
this.i = 0;
|
||||
},
|
||||
read: function(length) {
|
||||
if (length === undefined) {
|
||||
length = 1;
|
||||
}
|
||||
if (this.i >= this.stream.length) {
|
||||
return null;
|
||||
}
|
||||
var value = this.stream
|
||||
.slice(this.i, this.i + length).join();
|
||||
this.i += length;
|
||||
return value;
|
||||
}
|
||||
});
|
||||
this.instream = new Stream(instream);
|
||||
this.eof = null;
|
||||
this.commenters = '';
|
||||
this.nowordchars = [':', '>', '<', '=', '!', '"', ';', '(', ')'];
|
||||
this.whitespace = ' \t\r\n';
|
||||
this.whitespace_split = false;
|
||||
this.quotes = '"';
|
||||
this.escape = '\\';
|
||||
this.escapedquotes = '"';
|
||||
this.state = ' ';
|
||||
this.pushback = [];
|
||||
this.token = '';
|
||||
},
|
||||
get_token: function() {
|
||||
if (this.pushback.length > 0) {
|
||||
return this.pushback.shift();
|
||||
}
|
||||
var raw = this.read_token();
|
||||
return raw;
|
||||
},
|
||||
read_token: function() {
|
||||
var quoted = false;
|
||||
var escapedstate = ' ';
|
||||
while (true) {
|
||||
var nextchar = this.instream.read(1);
|
||||
if (this.state === null) {
|
||||
this.token = ''; // past en of file
|
||||
break;
|
||||
} else if (this.state == ' ') {
|
||||
if (!nextchar) {
|
||||
this.state = null; // end of file
|
||||
break;
|
||||
} else if (this.whitespace.contains(nextchar)) {
|
||||
if (this.token || quoted) {
|
||||
break; // emit current token
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (this.commenters.contains(nextchar)) {
|
||||
// TODO readline
|
||||
} else if (this.escape.contains(nextchar)) {
|
||||
escapedstate = 'a';
|
||||
this.state = nextchar;
|
||||
} else if (this.nowordchars.indexOf(nextchar) == -1) {
|
||||
this.token = nextchar;
|
||||
this.state = 'a';
|
||||
} else if (this.quotes.contains(nextchar)) {
|
||||
this.state = nextchar;
|
||||
} else if (this.whitespace_split) {
|
||||
this.token = nextchar;
|
||||
this.state = 'a';
|
||||
} else {
|
||||
this.token = nextchar;
|
||||
if (this.token || quoted) {
|
||||
break; // emit current token
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else if (this.quotes.contains(this.state)) {
|
||||
quoted = true;
|
||||
if (!nextchar) { // end of file
|
||||
throw 'no closing quotation';
|
||||
}
|
||||
if (nextchar == this.state) {
|
||||
this.state = 'a';
|
||||
} else if (this.escape.contains(nextchar) &&
|
||||
this.escapedquotes.contains(this.state)) {
|
||||
escapedstate = this.state;
|
||||
this.state = nextchar;
|
||||
} else {
|
||||
this.token = this.token + nextchar;
|
||||
}
|
||||
} else if (this.escape.contains(this.state)) {
|
||||
if (!nextchar) { // end of file
|
||||
throw 'no escaped character';
|
||||
}
|
||||
if (this.quotes.contains(escapedstate) &&
|
||||
(nextchar != this.state) &&
|
||||
(nextchar != escapedstate)) {
|
||||
this.token = this.token + this.state;
|
||||
}
|
||||
this.token = this.token + nextchar;
|
||||
this.state = escapedstate;
|
||||
} else if (this.state == 'a') {
|
||||
if (!nextchar) {
|
||||
this.state = null; // end of file
|
||||
break;
|
||||
} else if (this.whitespace.contains(nextchar)) {
|
||||
this.state = ' ';
|
||||
if (this.token || quoted) {
|
||||
break; // emit current token
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (this.commenters.contains(nextchar)) {
|
||||
// TODO
|
||||
} else if (this.quotes.contains(nextchar)) {
|
||||
this.state = nextchar;
|
||||
} else if (this.escape.contains(nextchar)) {
|
||||
escapedstate = 'a';
|
||||
this.state = nextchar;
|
||||
} else if ((this.nowordchars.indexOf(nextchar) == -1) ||
|
||||
this.quotes.contains(nextchar) ||
|
||||
this.whitespace_split) {
|
||||
this.token = this.token + nextchar;
|
||||
} else {
|
||||
this.pushback.unshift(nextchar);
|
||||
this.state = ' ';
|
||||
if (this.token) {
|
||||
break; // emit current token
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var result = this.token;
|
||||
this.token = '';
|
||||
if (!quoted && result === '') {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
next: function() {
|
||||
var token = this.get_token();
|
||||
if (token == this.eof) {
|
||||
return null;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
});
|
||||
|
||||
Sao.common.DomainParser = Sao.class_(Object, {
|
||||
OPERATORS: ['!=', '<=', '>=', '=', '!', '<', '>'],
|
||||
init: function(fields) {
|
||||
this.fields = {};
|
||||
this.strings = {};
|
||||
for (var name in fields) {
|
||||
var field = fields[name];
|
||||
if (field.searchable || (field.searchable === undefined)) {
|
||||
this.fields[name] = field;
|
||||
this.strings[field.string.toLowerCase()] = field;
|
||||
}
|
||||
}
|
||||
},
|
||||
parse: function(input) {
|
||||
try {
|
||||
var lex = new Sao.common.udlex(input);
|
||||
var tokens = [];
|
||||
while (true) {
|
||||
var token = lex.next();
|
||||
if (token === null) {
|
||||
break;
|
||||
}
|
||||
tokens.push(token);
|
||||
}
|
||||
tokens = this.group_operator(tokens);
|
||||
tokens = this.parenthesize(tokens);
|
||||
tokens = this.group(tokens);
|
||||
tokens = this.operatorize(tokens, 'or');
|
||||
tokens = this.operatorize(tokens, 'and');
|
||||
tokens = this.parse_clause(tokens);
|
||||
return this.simplify(tokens);
|
||||
} catch (e) {
|
||||
if (e == 'no closing quotation') {
|
||||
return this.parse(input + '"');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
string: function(domain) {
|
||||
|
||||
var string = function(clause) {
|
||||
if (jQuery.isEmptyObject(clause)) {
|
||||
return '';
|
||||
}
|
||||
var escaped;
|
||||
if ((typeof clause[0] == 'string') &&
|
||||
((clause[0] in this.fields) ||
|
||||
(clause[0] == 'rec_name'))) {
|
||||
var name = clause[0];
|
||||
var operator = clause[1];
|
||||
var value = clause[2];
|
||||
if (!(name in this.fields)) {
|
||||
escaped = value.replace('%%', '__');
|
||||
if (escaped.startsWith('%') && escaped.endsWith('%')) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
return this.quote(value);
|
||||
}
|
||||
var field = this.fields[name];
|
||||
if (operator.contains('ilike')) {
|
||||
escaped = value.replace('%%', '__');
|
||||
if (escaped.startsWith('%') && escaped.endsWith('%')) {
|
||||
value = value.slice(1, -1);
|
||||
} else if (!escaped.contains('%')) {
|
||||
if (operator == 'ilike') {
|
||||
operator = '=';
|
||||
} else {
|
||||
operator = '!';
|
||||
}
|
||||
value = value.replace('%%', '%');
|
||||
}
|
||||
}
|
||||
var def_operator = this.default_operator(field);
|
||||
if ((def_operator == operator.trim()) ||
|
||||
(operator.contains(def_operator) &&
|
||||
operator.contains('not'))) {
|
||||
operator = operator.replace(def_operator, '')
|
||||
.replace('not', '!').trim();
|
||||
}
|
||||
if (operator.endsWith('in')) {
|
||||
if (operator == 'not in') {
|
||||
operator = '!';
|
||||
} else {
|
||||
operator = '';
|
||||
}
|
||||
}
|
||||
var formatted_value = this.format_value(field, value);
|
||||
if ((this.OPERATORS.indexOf(operator) >= 0) &&
|
||||
(['char', 'text', 'sha', 'selection']
|
||||
.indexOf(field.type) >= 0) &&
|
||||
(value === '')) {
|
||||
formatted_value = '""';
|
||||
}
|
||||
return (this.quote(field.string) + ': ' +
|
||||
operator + formatted_value);
|
||||
} else {
|
||||
return '(' + this.string(clause) + ')';
|
||||
}
|
||||
};
|
||||
string = string.bind(this);
|
||||
|
||||
if (jQuery.isEmptyObject(domain)) {
|
||||
return '';
|
||||
}
|
||||
var nary = ' ';
|
||||
if ((domain[0] == 'AND') || (domain[0] == 'OR')) {
|
||||
if (domain[0] == 'OR') {
|
||||
nary = ' or ';
|
||||
}
|
||||
domain = domain.slice(1);
|
||||
}
|
||||
return domain.map(string).join(nary);
|
||||
},
|
||||
group_operator: function(tokens) {
|
||||
var cur = tokens[0];
|
||||
var nex = null;
|
||||
var result = [];
|
||||
tokens.slice(1).forEach(function(nex) {
|
||||
if ((nex == '=') && cur &&
|
||||
(this.OPERATORS.indexOf(cur + nex) >= 0)) {
|
||||
result.push(cur + nex);
|
||||
cur = null;
|
||||
} else {
|
||||
if (cur !== null) {
|
||||
result.push(cur);
|
||||
}
|
||||
cur = nex;
|
||||
}
|
||||
}.bind(this));
|
||||
if (cur !== null) {
|
||||
result.push(cur);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
parenthesize: function(tokens) {
|
||||
var result = [];
|
||||
var current = result;
|
||||
var parent = [];
|
||||
tokens.forEach(function(token, i) {
|
||||
if (current === undefined) {
|
||||
return;
|
||||
}
|
||||
if (token == '(') {
|
||||
parent.push(current);
|
||||
current = current[current.push([]) - 1];
|
||||
} else if (token == ')') {
|
||||
current = parent.pop();
|
||||
} else {
|
||||
current.push(token);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
group: function(tokens) {
|
||||
var result = [];
|
||||
|
||||
var _group = function(parts) {
|
||||
var result = [];
|
||||
var push_result = function(part) {
|
||||
result.push([part]);
|
||||
};
|
||||
var i = parts.indexOf(':');
|
||||
if (i == -1) {
|
||||
parts.forEach(push_result);
|
||||
return result;
|
||||
}
|
||||
var sub_group = function(name, lvalue) {
|
||||
return function(part) {
|
||||
if (!jQuery.isEmptyObject(name)) {
|
||||
if (!jQuery.isEmptyObject(lvalue)) {
|
||||
if (part[0] !== null) {
|
||||
lvalue.push(part[0]);
|
||||
}
|
||||
result.push(name.concat([lvalue]));
|
||||
} else {
|
||||
result.push(name.concat(part));
|
||||
}
|
||||
name.splice(0, name.length);
|
||||
} else {
|
||||
result.push(part);
|
||||
}
|
||||
};
|
||||
};
|
||||
for (var j = 0; j < i; j++) {
|
||||
var name = parts.slice(j, i).join(' ');
|
||||
if (name.toLowerCase() in this.strings) {
|
||||
if (!jQuery.isEmptyObject(parts.slice(0, j))) {
|
||||
parts.slice(0, j).forEach(push_result);
|
||||
} else {
|
||||
push_result(null);
|
||||
}
|
||||
name = [name];
|
||||
if (((i + 1) < parts.length) &&
|
||||
(this.OPERATORS.indexOf(parts[i + 1]) >= 0)) {
|
||||
name = name.concat([parts[i + 1]]);
|
||||
i += 1;
|
||||
} else {
|
||||
name = name.concat([null]);
|
||||
}
|
||||
var lvalue = [];
|
||||
while ((i + 2) < parts.length) {
|
||||
if (parts[i + 2] == ';') {
|
||||
lvalue.push(parts[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_group(parts.slice(i + 1)).forEach(
|
||||
sub_group(name, lvalue));
|
||||
if (!jQuery.isEmptyObject(name)) {
|
||||
if (!jQuery.isEmptyObject(lvalue)) {
|
||||
result.push(name.concat([lvalue]));
|
||||
} else {
|
||||
result.push(name.concat([null]));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
_group = _group.bind(this);
|
||||
|
||||
var parts = [];
|
||||
tokens.forEach(function(token) {
|
||||
if (token instanceof Array) {
|
||||
_group(parts).forEach(function(group) {
|
||||
if (!Sao.common.compare(group, [null])) {
|
||||
result.push(group);
|
||||
}
|
||||
});
|
||||
parts = [];
|
||||
result.push(this.group(token));
|
||||
} else {
|
||||
parts.push(token);
|
||||
}
|
||||
}.bind(this));
|
||||
_group(parts).forEach(function(group) {
|
||||
if (!Sao.common.compare(group, [null])) {
|
||||
result.push(group);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
operatorize: function(tokens, operator) {
|
||||
var result = [];
|
||||
operator = operator || 'or';
|
||||
tokens = jQuery.extend([], tokens);
|
||||
var test = function(value) {
|
||||
if (value instanceof Array) {
|
||||
return Sao.common.compare(value, [operator]);
|
||||
} else {
|
||||
return value == operator;
|
||||
}
|
||||
};
|
||||
var cur = tokens.shift();
|
||||
while (test(cur)) {
|
||||
cur = tokens.shift();
|
||||
}
|
||||
if (cur === undefined) {
|
||||
return result;
|
||||
}
|
||||
if (cur instanceof Array) {
|
||||
cur = this.operatorize(cur, operator);
|
||||
}
|
||||
var nex = null;
|
||||
while (!jQuery.isEmptyObject(tokens)) {
|
||||
nex = tokens.shift();
|
||||
if ((nex instanceof Array) && !test(nex)) {
|
||||
nex = this.operatorize(nex, operator);
|
||||
}
|
||||
if (test(nex)) {
|
||||
nex = tokens.shift();
|
||||
while (test(nex)) {
|
||||
nex = tokens.shift();
|
||||
}
|
||||
if (nex instanceof Array) {
|
||||
nex = this.operatorize(nex, operator);
|
||||
}
|
||||
if (nex !== undefined) {
|
||||
cur = [operator.toUpperCase(), cur, nex];
|
||||
} else {
|
||||
if (!test(cur)) {
|
||||
result.push([operator.toUpperCase(), cur]);
|
||||
cur = null;
|
||||
}
|
||||
}
|
||||
nex = null;
|
||||
} else {
|
||||
if (!test(cur)) {
|
||||
result.push(cur);
|
||||
}
|
||||
cur = nex;
|
||||
}
|
||||
}
|
||||
if (jQuery.isEmptyObject(tokens)) {
|
||||
if ((nex !== null) && !test(nex)) {
|
||||
result.push(nex);
|
||||
} else if ((cur !== null) && !test(nex)) {
|
||||
result.push(cur);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
parse_clause: function(tokens) {
|
||||
var result = [];
|
||||
tokens.forEach(function(clause) {
|
||||
if ((clause == 'OR') || (clause == 'AND')) {
|
||||
result.push(clause);
|
||||
} else if ((clause.length == 1) &&
|
||||
!(clause[0] instanceof Array)) {
|
||||
result.push(['rec_name', 'ilike', this.likify(clause[0])]);
|
||||
} else if ((clause.length == 3) &&
|
||||
(clause[0].toLowerCase() in this.strings)) {
|
||||
var name = clause[0];
|
||||
var operator = clause[1];
|
||||
var value = clause[2];
|
||||
var field = this.strings[clause[0].toLowerCase()];
|
||||
if (operator === null) {
|
||||
operator = this.default_operator(field);
|
||||
}
|
||||
if (value instanceof Array) {
|
||||
if (operator == '!') {
|
||||
operator = 'not in';
|
||||
} else {
|
||||
operator = 'in';
|
||||
}
|
||||
}
|
||||
if (operator == '!') {
|
||||
operator = this.negate_operator(
|
||||
this.default_operator(field));
|
||||
}
|
||||
if (operator.contains('like')) {
|
||||
value = this.likify(value);
|
||||
}
|
||||
if (['integer', 'float', 'numeric', 'datetime', 'date',
|
||||
'time'].indexOf(field.type) >= 0) {
|
||||
if (value && value.contains('..')) {
|
||||
var values = value.split('..', 2);
|
||||
var lvalue = this.convert_value(field, values[0]);
|
||||
var rvalue = this.convert_value(field, values[1]);
|
||||
result.push([
|
||||
[field.name, '>=', lvalue],
|
||||
[field.name, '<', rvalue]
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (value instanceof Array) {
|
||||
value = value.map(function(v) {
|
||||
return this.convert_value(field, v);
|
||||
}.bind(this));
|
||||
} else {
|
||||
value = this.convert_value(field, value);
|
||||
}
|
||||
result.push([field.name, operator, value]);
|
||||
} else {
|
||||
result.push(this.parse_clause(clause));
|
||||
}
|
||||
}.bind(this));
|
||||
return result;
|
||||
},
|
||||
likify: function(value) {
|
||||
if (!value) {
|
||||
return '%';
|
||||
}
|
||||
var escaped = value.replace('%%', '__');
|
||||
if (escaped.contains('%')) {
|
||||
return value;
|
||||
} else {
|
||||
return '%' + value + '%';
|
||||
}
|
||||
},
|
||||
quote: function(value) {
|
||||
if (typeof value != 'string') {
|
||||
return value;
|
||||
}
|
||||
var tests = [':', ' ', '(', ')'].concat(this.OPERATORS);
|
||||
for (var i = 0; i < tests.length; i++) {
|
||||
var test = tests[i];
|
||||
if (value.contains(test)) {
|
||||
return '"' + value + '"';
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
default_operator: function(field) {
|
||||
if (['char', 'text', 'many2one', 'many2many', 'one2many']
|
||||
.indexOf(field.type) >= 0) {
|
||||
return 'ilike';
|
||||
} else {
|
||||
return '=';
|
||||
}
|
||||
},
|
||||
negate_operator: function(operator) {
|
||||
switch (operator) {
|
||||
case 'ilike':
|
||||
return 'not ilike';
|
||||
case '=':
|
||||
return '!=';
|
||||
case 'in':
|
||||
return 'not in';
|
||||
}
|
||||
},
|
||||
convert_value: function(field, value) {
|
||||
var convert_selection = function() {
|
||||
if (typeof value == 'string') {
|
||||
for (var i = 0; i < field.selection.length; i++) {
|
||||
var selection = field.selection[i];
|
||||
var key = selection[0];
|
||||
var text = selection[1];
|
||||
if (value.toLowerCase() == text.toLowerCase()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
var converts = {
|
||||
'boolean': function() {
|
||||
if (typeof value == 'string') {
|
||||
return ['y', 'yes', 'true', 't', '1'].some(
|
||||
function(test) {
|
||||
return test.toLowerCase().startsWith(
|
||||
value.toLowerCase());
|
||||
});
|
||||
} else {
|
||||
return Boolean(value);
|
||||
}
|
||||
},
|
||||
'float': function() {
|
||||
var result = Number(value);
|
||||
if (isNaN(result) || value === '' || value === null) {
|
||||
return null;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
'integer': function() {
|
||||
var result = parseInt(value, 10);
|
||||
if (isNaN(result)) {
|
||||
return null;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
'numeric': function() {
|
||||
var result = new Sao.Decimal(value);
|
||||
if (isNaN(result.valueOf()) ||
|
||||
value === '' || value === null) {
|
||||
return null;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
'selection': convert_selection,
|
||||
'reference': convert_selection,
|
||||
// TODO datetime
|
||||
// TODO date
|
||||
// TODO time
|
||||
'many2one': function() {
|
||||
if (value === '') {
|
||||
return null;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
var func = converts[field.type];
|
||||
if (func) {
|
||||
return func();
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
format_value: function(field, value) {
|
||||
var format_float = function() {
|
||||
if (!value && value !== 0 && value !== new Sao.Decimal(0)) {
|
||||
return '';
|
||||
}
|
||||
var digit = parseInt((field.digits || [16, 2])[1], 10);
|
||||
if (isNaN(digit)) {
|
||||
digit = 2;
|
||||
}
|
||||
return value.toFixed(digit);
|
||||
};
|
||||
var format_selection = function() {
|
||||
for (var i = 0; i < field.selection.length; i++) {
|
||||
if (field.selection[i][0] == value) {
|
||||
return field.selection[i][1];
|
||||
}
|
||||
}
|
||||
return value || '';
|
||||
};
|
||||
|
||||
var converts = {
|
||||
'boolean': function() {
|
||||
if (value) {
|
||||
return 'True'; // TODO translate
|
||||
} else {
|
||||
return 'False';
|
||||
}
|
||||
},
|
||||
'integer': function() {
|
||||
if (value || value === 0) {
|
||||
return '' + parseInt(value, 10);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
'float': format_float,
|
||||
'numeric': format_float,
|
||||
'selection': format_selection,
|
||||
'reference': format_selection,
|
||||
// TODO datetime
|
||||
// TODO date
|
||||
// TODO time
|
||||
'many2one': function() {
|
||||
if (value === null) {
|
||||
return '';
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (value instanceof Array) {
|
||||
return value.map(function(v) {
|
||||
return this.format_value(field, v);
|
||||
}.bind(this)).join(';');
|
||||
} else {
|
||||
var func = converts[field.type];
|
||||
if (func) {
|
||||
return this.quote(func(value));
|
||||
} else if (value === null) {
|
||||
return '';
|
||||
} else {
|
||||
return this.quote(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
simplify: function(value) {
|
||||
if (value instanceof Array) {
|
||||
if ((value.length == 1) && (value[0] instanceof Array) &&
|
||||
((value[0][0] == 'AND') || (value[0][0] == 'OR') ||
|
||||
(value[0][0] instanceof Array))) {
|
||||
return this.simplify(value[0]);
|
||||
} else if ((value.length == 2) &&
|
||||
((value[0] == 'AND') || (value[0] == 'OR')) &&
|
||||
(value[1] instanceof Array)) {
|
||||
return this.simplify(value[1]);
|
||||
} else if ((value.length == 3) &&
|
||||
((value[0] == 'AND') || (value[0] == 'OR')) &&
|
||||
(value[1] instanceof Array) &&
|
||||
(value[0] == value[1][0])) {
|
||||
value = this.simplify(value[1]).concat([value[2]]);
|
||||
}
|
||||
return value.map(this.simplify.bind(this));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
});
|
||||
}());
|
||||
|
||||
+101
-5
@@ -66,6 +66,9 @@
|
||||
this.el.append(this.content_box);
|
||||
}
|
||||
},
|
||||
set_text: function(value) {
|
||||
this.search_entry.val(value);
|
||||
},
|
||||
search_prev: function() {
|
||||
this.screen.search_prev(this.search_entry.val());
|
||||
},
|
||||
@@ -136,6 +139,8 @@
|
||||
} else {
|
||||
this.row_activate = attributes.row_activate;
|
||||
}
|
||||
this.fields_view_tree = null;
|
||||
this.domain_parser = null;
|
||||
},
|
||||
load_next_view: function() {
|
||||
if (this.view_to_load) {
|
||||
@@ -158,7 +163,11 @@
|
||||
var arch = view.arch;
|
||||
var fields = view.fields;
|
||||
var xml_view = jQuery(jQuery.parseXML(arch));
|
||||
// TODO loading lazy/eager
|
||||
|
||||
if (xml_view.children().prop('tagName') == 'tree') {
|
||||
this.fields_view_tree = view;
|
||||
}
|
||||
|
||||
var loading = 'eager';
|
||||
if (xml_view.children().prop('tagName') == 'form') {
|
||||
loading = 'lazy';
|
||||
@@ -209,11 +218,23 @@
|
||||
},
|
||||
search_filter: function(search_string) {
|
||||
var domain = [];
|
||||
// TODO domain parser
|
||||
|
||||
if (domain.length && this.attributes.domain) {
|
||||
domain.unshift('AND');
|
||||
domain.push(this.attributes.domain);
|
||||
if (this.domain_parser && !this.parent) {
|
||||
if (search_string || search_string === '') {
|
||||
domain = this.domain_parser.parse(search_string);
|
||||
} else {
|
||||
domain = this.attributes.search_value;
|
||||
}
|
||||
this.screen_container.set_text(
|
||||
this.domain_parser.string(domain));
|
||||
} else {
|
||||
domain = [['id', 'in', this.group.map(function(r) {
|
||||
return r.id;
|
||||
})]];
|
||||
}
|
||||
|
||||
if (!jQuery.isEmptyObject(domain) && this.attributes.domain) {
|
||||
domain = ['AND', domain, this.attributes.domain];
|
||||
} else
|
||||
domain = this.attributes.domain || [];
|
||||
|
||||
@@ -423,12 +444,87 @@
|
||||
},
|
||||
search_active: function(active) {
|
||||
if (active && !this.parent) {
|
||||
if (!this.fields_view_tree) {
|
||||
this.model.execute('fields_view_get',
|
||||
[false, 'tree'], this.context)
|
||||
.then(function(view) {
|
||||
this.fields_view_tree = view;
|
||||
this.search_active(active);
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
if (!this.domain_parser) {
|
||||
var fields = jQuery.extend({},
|
||||
this.fields_view_tree.fields);
|
||||
|
||||
var set_selection = function(props) {
|
||||
return function(selection) {
|
||||
props.selection = selection;
|
||||
};
|
||||
};
|
||||
for (var name in fields) {
|
||||
if (!fields.hasOwnProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
var props = fields[name];
|
||||
if ((props.type != 'selection') &&
|
||||
(props.type != 'reference')) {
|
||||
continue;
|
||||
}
|
||||
if (props.selection instanceof Array) {
|
||||
continue;
|
||||
}
|
||||
this.get_selection(props).then(set_selection);
|
||||
}
|
||||
|
||||
// Filter only fields in XML view
|
||||
var xml_view = jQuery(jQuery.parseXML(
|
||||
this.fields_view_tree.arch));
|
||||
var xml_fields = xml_view.find('tree').children()
|
||||
.filter(function(node) {
|
||||
return node.tagName == 'field';
|
||||
}).map(function(node) {
|
||||
return node.getAttribute('name');
|
||||
});
|
||||
var dom_fields = {};
|
||||
xml_fields.each(function(name) {
|
||||
dom_fields[name] = fields[name];
|
||||
});
|
||||
if (!('id' in fields)) {
|
||||
fields.id = {
|
||||
'string': 'ID', // TODO translate
|
||||
'name': 'id',
|
||||
'type': 'integer'
|
||||
};
|
||||
}
|
||||
this.domain_parser = new Sao.common.DomainParser(fields);
|
||||
}
|
||||
this.screen_container.set_screen(this);
|
||||
this.screen_container.show_filter();
|
||||
} else {
|
||||
this.screen_container.hide_filter();
|
||||
}
|
||||
},
|
||||
get_selection: function(props) {
|
||||
var prm;
|
||||
var change_with = props.selection_change_with;
|
||||
if (change_with) {
|
||||
var values = {};
|
||||
change_with.forEach(function(p) {
|
||||
values[p] = null;
|
||||
});
|
||||
prm = this.model.execute(props.selection,
|
||||
[values]);
|
||||
} else {
|
||||
prm = this.model.execute(props.selection,
|
||||
[]);
|
||||
}
|
||||
return prm.then(function(selection) {
|
||||
return selection.sort(function(a, b) {
|
||||
return a[1].localeCompare(b[1]);
|
||||
});
|
||||
});
|
||||
},
|
||||
search_prev: function(search_string) {
|
||||
this.offset -= this.limit;
|
||||
this.search_filter(search_string);
|
||||
|
||||
+426
@@ -778,6 +778,432 @@
|
||||
new Date(2010, 1, 22, 10, 30, 20, 2).valueOf());
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.group_operator', function() {
|
||||
var parser = new Sao.common.DomainParser();
|
||||
QUnit.ok(Sao.common.compare(parser.group_operator(['a', '>', '=']),
|
||||
['a', '>=']), "group_operator(['a', '>', '='])");
|
||||
QUnit.ok(Sao.common.compare(parser.group_operator(['>', '=', 'b']),
|
||||
['>=', 'b']), "group_operator(['>', '=', 'b'])");
|
||||
QUnit.ok(Sao.common.compare(parser.group_operator(['a', '=', 'b']),
|
||||
['a', '=', 'b']), "group_operator(['a', '=', 'b'])");
|
||||
QUnit.ok(Sao.common.compare(parser.group_operator(['a', '>', '=', 'b']),
|
||||
['a', '>=', 'b']), "group_operator(['a', '>', '=', 'b'])");
|
||||
QUnit.ok(Sao.common.compare(parser.group_operator(['a', '>', '=', '=']),
|
||||
['a', '>=', '=']), "group_operator(['a', '>', '=', '='])");
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.parenthesize', function() {
|
||||
var parser = new Sao.common.DomainParser();
|
||||
[
|
||||
[['a'], ['a']],
|
||||
[['a', 'b'], ['a', 'b']],
|
||||
[['(', 'a', ')'], [['a']]],
|
||||
[['a', 'b', '(', 'c', '(', 'd', 'e', ')', 'f', ')', 'g'],
|
||||
['a', 'b', ['c', ['d', 'e'], 'f'], 'g']],
|
||||
[['a', 'b', '(', 'c'], ['a', 'b', ['c']]],
|
||||
[['a', 'b', '(', 'c', '(', 'd', 'e', ')', 'f'],
|
||||
['a', 'b', ['c', ['d', 'e'], 'f']]],
|
||||
[['a', 'b', ')'], ['a', 'b']],
|
||||
[['a', 'b', ')', 'c', ')', 'd)'], ['a', 'b']]
|
||||
].forEach(function(test) {
|
||||
var value = test[0];
|
||||
var result = test[1];
|
||||
QUnit.ok(Sao.common.compare(parser.parenthesize(value), result),
|
||||
'parenthesize(' + JSON.stringify(value) + ')');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.group', function() {
|
||||
var parser = new Sao.common.DomainParser({
|
||||
'name': {
|
||||
'string': 'Name'
|
||||
},
|
||||
'firstname': {
|
||||
'string': 'First Name'
|
||||
},
|
||||
'surname': {
|
||||
'string': '(Sur)Name'
|
||||
}
|
||||
});
|
||||
var udlex = function(input) {
|
||||
var lex = new Sao.common.udlex(input);
|
||||
var tokens = [];
|
||||
while (true) {
|
||||
var token = lex.next();
|
||||
if (token === null) {
|
||||
break;
|
||||
}
|
||||
tokens.push(token);
|
||||
}
|
||||
return tokens;
|
||||
};
|
||||
[
|
||||
['Name: Doe', [['Name', null, 'Doe']]],
|
||||
['"(Sur)Name": Doe', [['(Sur)Name', null, 'Doe']]],
|
||||
['Name: Doe Name: John', [
|
||||
['Name', null, 'Doe'],
|
||||
['Name', null, 'John']
|
||||
]],
|
||||
['Name: Name: John', [
|
||||
['Name', null, null],
|
||||
['Name', null, 'John']
|
||||
]],
|
||||
['First Name: John', [['First Name', null, 'John']]],
|
||||
['Name: Doe First Name: John', [
|
||||
['Name', null, 'Doe'],
|
||||
['First Name', null, 'John']
|
||||
]],
|
||||
['First Name: John Name: Doe', [
|
||||
['First Name', null, 'John'],
|
||||
['Name', null, 'Doe']
|
||||
]],
|
||||
['First Name: John First Name: Jane', [
|
||||
['First Name', null, 'John'],
|
||||
['First Name', null, 'Jane']
|
||||
]],
|
||||
['Name: John Doe', [
|
||||
['Name', null, 'John'],
|
||||
['Doe']
|
||||
]],
|
||||
['Name: "John Doe"', [['Name', null, 'John Doe']]],
|
||||
['Name: =Doe', [['Name', '=', 'Doe']]],
|
||||
['Name: =Doe Name: >John', [
|
||||
['Name', '=', 'Doe'],
|
||||
['Name', '>', 'John']
|
||||
]],
|
||||
['First Name: =John First Name: =Jane', [
|
||||
['First Name', '=', 'John'],
|
||||
['First Name', '=', 'Jane']
|
||||
]],
|
||||
['Name: John;Jane', [['Name', null, ['John', 'Jane']]]],
|
||||
['Name: John;', [['Name', null, ['John']]]],
|
||||
['Name: John;Jane Name: Doe', [
|
||||
['Name', null, ['John', 'Jane']],
|
||||
['Name', null, 'Doe']
|
||||
]],
|
||||
['Name: John; Name: Doe', [
|
||||
['Name', null, ['John']],
|
||||
['Name', null, 'Doe']
|
||||
]],
|
||||
['Name:', [['Name', null, null]]],
|
||||
['Name: =', [['Name', '=', null]]],
|
||||
['Name: =""', [['Name', '=', '']]],
|
||||
['Name: = ""', [['Name', '=', '']]],
|
||||
['Name: = Name: Doe', [
|
||||
['Name', '=', null],
|
||||
['Name', null, 'Doe']
|
||||
]]
|
||||
].forEach(function(test) {
|
||||
var value = test[0];
|
||||
var result = test[1];
|
||||
QUnit.ok(Sao.common.compare(parser.group(udlex(value)), result),
|
||||
'group(udlex(' + JSON.stringify(value) + ')');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.operatorize', function() {
|
||||
var parser = new Sao.common.DomainParser();
|
||||
var a = ['a', 'a', 'a'];
|
||||
var b = ['b', 'b', 'b'];
|
||||
var c = ['c', 'c', 'c'];
|
||||
[
|
||||
[['a'], ['a']],
|
||||
[['a', 'or', 'b'], [['OR', 'a', 'b']]],
|
||||
[['a', 'or', 'b', 'or', 'c'], [['OR', ['OR', 'a', 'b'], 'c']]],
|
||||
[['a', 'b', 'or', 'c'], ['a', ['OR', 'b', 'c']]],
|
||||
[['a', 'or', 'b', 'c'], [['OR', 'a', 'b'], 'c']],
|
||||
[['a', ['b', 'c']], ['a', ['b', 'c']]],
|
||||
[['a', ['b', 'c'], 'd'], ['a', ['b', 'c'], 'd']],
|
||||
[['a', 'or', ['b', 'c']], [['OR', 'a', ['b', 'c']]]],
|
||||
[['a', 'or', ['b', 'c'], 'd'],
|
||||
[['OR', 'a', ['b', 'c']], 'd']],
|
||||
[['a', ['b', 'c'], 'or', 'd'],
|
||||
['a', ['OR', ['b', 'c'], 'd']]],
|
||||
[['a', 'or', ['b', 'or', 'c']],
|
||||
[['OR', 'a', [['OR', 'b', 'c']]]]],
|
||||
[['or'], []],
|
||||
[['or', 'a'], ['a']],
|
||||
[['a', ['or', 'b']], ['a', ['b']]],
|
||||
[['a', 'or', 'or', 'b'], [['OR', 'a', 'b']]],
|
||||
[['or', 'or', 'a'], ['a']],
|
||||
[['or', 'or', 'a', 'b'], ['a', 'b']],
|
||||
[['or', 'or', 'a', 'or', 'b'], [['OR', 'a', 'b']]],
|
||||
[['a', ['b', 'or', 'c']], ['a', [['OR', 'b', 'c']]]],
|
||||
[[a, [b, ['or'], c]], [a, [['OR', b, c]]]],
|
||||
[['a', ['b', 'or']], ['a', [['OR', 'b']]]]
|
||||
].forEach(function(test) {
|
||||
var value = test[0];
|
||||
var result = test[1];
|
||||
QUnit.ok(Sao.common.compare(parser.operatorize(value), result),
|
||||
'operatorize(' + JSON.stringify(value) + ')');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.convert_value', function() {
|
||||
var parser = new Sao.common.DomainParser();
|
||||
|
||||
var test_func = function(test) {
|
||||
var value = test[0];
|
||||
var result = test[1];
|
||||
QUnit.strictEqual(parser.convert_value(this, value), result,
|
||||
'convert_value(' + JSON.stringify(this) + ', ' +
|
||||
JSON.stringify(value) + ')');
|
||||
};
|
||||
|
||||
var field = {
|
||||
'type': 'boolean'
|
||||
};
|
||||
[
|
||||
['Y', true],
|
||||
['yes', true],
|
||||
['t', true],
|
||||
['1', true],
|
||||
['N', false],
|
||||
['False', false],
|
||||
['no', false],
|
||||
['0', false],
|
||||
[null, false]
|
||||
].forEach(test_func, field);
|
||||
|
||||
field = {
|
||||
'type': 'float'
|
||||
};
|
||||
[
|
||||
['1', 1.0],
|
||||
['1.5', 1.5],
|
||||
['', null],
|
||||
['test', null],
|
||||
[null, null]
|
||||
].forEach(test_func, field);
|
||||
|
||||
field = {
|
||||
'type': 'integer'
|
||||
};
|
||||
[
|
||||
['1', 1],
|
||||
['1.5', 1],
|
||||
['', null],
|
||||
['test', null],
|
||||
[null, null]
|
||||
].forEach(test_func, field);
|
||||
|
||||
field = {
|
||||
'type': 'numeric'
|
||||
};
|
||||
[
|
||||
['1', new Sao.Decimal(1)],
|
||||
['1.5', new Sao.Decimal('1.5')],
|
||||
['', null],
|
||||
['test', null],
|
||||
[null, null]
|
||||
].forEach(function(test) {
|
||||
var value = test[0];
|
||||
var result = test[1];
|
||||
value = parser.convert_value(field, value);
|
||||
if (value !== null) {
|
||||
value = value.toString();
|
||||
}
|
||||
if (result !== null) {
|
||||
result = result.toString();
|
||||
}
|
||||
QUnit.strictEqual(value, result,
|
||||
'convert_value(' + JSON.stringify(field) + ', ' +
|
||||
JSON.stringify(test[0]) + ')');
|
||||
});
|
||||
|
||||
field = {
|
||||
'type': 'selection',
|
||||
'selection': [
|
||||
['male', 'Male'],
|
||||
['female', 'Female']
|
||||
]
|
||||
};
|
||||
[
|
||||
['Male', 'male'],
|
||||
['male', 'male'],
|
||||
['test', 'test']
|
||||
].forEach(test_func, field);
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.parse_clause', function() {
|
||||
var parser = new Sao.common.DomainParser({
|
||||
'name': {
|
||||
'string': 'Name',
|
||||
'name': 'name',
|
||||
'type': 'char'
|
||||
},
|
||||
'integer': {
|
||||
'string': 'Integer',
|
||||
'name': 'integer',
|
||||
'type': 'integer'
|
||||
},
|
||||
'selection': {
|
||||
'string': 'Selection',
|
||||
'name': 'selection',
|
||||
'type': 'selection',
|
||||
'selection': [
|
||||
['male', 'Male'],
|
||||
['female', 'Female']
|
||||
]
|
||||
}
|
||||
});
|
||||
[
|
||||
[[['John']], [['rec_name', 'ilike', '%John%']]],
|
||||
[[['Name', null, null]], [['name', 'ilike', '%']]],
|
||||
[[['Name', '=', null]], [['name', '=', null]]],
|
||||
[[['Name', '=', '']], [['name', '=', '']]],
|
||||
[[['Name', null, 'Doe']], [['name', 'ilike', '%Doe%']]],
|
||||
[[['Name', '!', 'Doe']], [['name', 'not ilike', '%Doe%']]],
|
||||
[[['Name', null, ['John', 'Jane']]],
|
||||
[['name', 'in', ['John', 'Jane']]]],
|
||||
[[['Name', '!', ['John', 'Jane']]],
|
||||
[['name', 'not in', ['John', 'Jane']]]],
|
||||
[[['Selection', null, ['Male', 'Female']]],
|
||||
[['selection', 'in', ['male', 'female']]]],
|
||||
[[['Integer', null, null]], [['integer', '=', null]]],
|
||||
[[['Integer', null, '3..5']], [[
|
||||
['integer', '>=', 3],
|
||||
['integer', '<', 5]
|
||||
]]]
|
||||
].forEach(function(test) {
|
||||
var value = test[0];
|
||||
var result = test[1];
|
||||
QUnit.ok(Sao.common.compare(parser.parse_clause(value), result),
|
||||
'parse_clause(' + JSON.stringify(value) + ')');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.format_value', function() {
|
||||
var parser = new Sao.common.DomainParser();
|
||||
|
||||
var test_func = function(test) {
|
||||
var value = test[0];
|
||||
var result = test[1];
|
||||
QUnit.strictEqual(parser.format_value(this, value), result,
|
||||
'format_value(' + JSON.stringify(this) + ', ' +
|
||||
JSON.stringify(value) + ')');
|
||||
};
|
||||
|
||||
var field = {
|
||||
'type': 'boolean'
|
||||
};
|
||||
[
|
||||
[true, 'True'],
|
||||
[false, 'False'],
|
||||
[null, 'False']
|
||||
].forEach(test_func, field);
|
||||
|
||||
field = {
|
||||
'type': 'integer'
|
||||
};
|
||||
[
|
||||
[1, '1'],
|
||||
[1.5, '1'],
|
||||
[0, '0'],
|
||||
[0.0, '0'],
|
||||
[false, ''],
|
||||
[null, '']
|
||||
].forEach(test_func, field);
|
||||
|
||||
field = {
|
||||
'type': 'float',
|
||||
'digits': [16, 2]
|
||||
};
|
||||
[
|
||||
[1, '1.00'],
|
||||
[1.5, '1.50'],
|
||||
[0, '0.00'],
|
||||
[0.0, '0.00'],
|
||||
[false, ''],
|
||||
[null, '']
|
||||
].forEach(test_func, field);
|
||||
|
||||
field = {
|
||||
'type': 'numeric',
|
||||
'digits': [16, 2]
|
||||
};
|
||||
[
|
||||
[new Sao.Decimal(1), '1.00'],
|
||||
[new Sao.Decimal('1.5'), '1.50'],
|
||||
[new Sao.Decimal(0), '0.00'],
|
||||
[new Sao.Decimal('0.0'), '0.00'],
|
||||
[false, ''],
|
||||
[null, '']
|
||||
].forEach(test_func, field);
|
||||
|
||||
field = {
|
||||
'type': 'selection',
|
||||
'selection': [
|
||||
['male', 'Male'],
|
||||
['female', 'Female']
|
||||
]
|
||||
};
|
||||
[
|
||||
['male', 'Male'],
|
||||
['test', 'test'],
|
||||
[false, ''],
|
||||
[null, '']
|
||||
].forEach(test_func, field);
|
||||
});
|
||||
|
||||
QUnit.test('DomainParser.string', function() {
|
||||
var parser = new Sao.common.DomainParser({
|
||||
'name': {
|
||||
'string': 'Name',
|
||||
'type': 'char'
|
||||
},
|
||||
'surname': {
|
||||
'string': '(Sur)Name',
|
||||
'type': 'char'
|
||||
},
|
||||
'date': {
|
||||
'string': 'Date',
|
||||
'type': 'date'
|
||||
}
|
||||
});
|
||||
|
||||
[
|
||||
[[['name', '=', 'Doe']], 'Name: =Doe'],
|
||||
[[['name', '=', null]], 'Name: ='],
|
||||
[[['name', '=', '']], 'Name: =""'],
|
||||
[[['name', 'ilike', '%']], 'Name: '],
|
||||
[[['name', 'ilike', '%Doe%']], 'Name: Doe'],
|
||||
[[['name', 'ilike', 'Doe']], 'Name: =Doe'],
|
||||
[[['name', 'ilike', 'Doe%']], 'Name: Doe%'],
|
||||
[[['name', 'ilike', 'Doe%%']], 'Name: =Doe%'],
|
||||
[[['name', 'not ilike', '%Doe%']], 'Name: !Doe'],
|
||||
[[['name', 'in', ['John', 'Jane']]], 'Name: John;Jane'],
|
||||
[[['name', 'not in', ['John', 'Jane']]], 'Name: !John;Jane'],
|
||||
[[
|
||||
['name', 'ilike', '%Doe%'],
|
||||
['name', 'ilike', '%Jane%']
|
||||
], 'Name: Doe Name: Jane'],
|
||||
[['AND',
|
||||
['name', 'ilike', '%Doe%'],
|
||||
['name', 'ilike', '%Jane%']
|
||||
], 'Name: Doe Name: Jane'],
|
||||
[['OR',
|
||||
['name', 'ilike', '%Doe%'],
|
||||
['name', 'ilike', '%Jane%']
|
||||
], 'Name: Doe or Name: Jane'],
|
||||
[[
|
||||
['name', 'ilike', '%Doe%'],
|
||||
['OR',
|
||||
['name', 'ilike', '%John%'],
|
||||
['name', 'ilike', '%Jane%']
|
||||
]
|
||||
], 'Name: Doe (Name: John or Name: Jane)'],
|
||||
[[], ''],
|
||||
[[['surname', 'ilike', '%Doe%']], '"(Sur)Name": Doe']
|
||||
//[[['date', '>=', new Date(2012, 10, 24)]], 'Date: >=10/24/2012']
|
||||
].forEach(function(test) {
|
||||
var value = test[0];
|
||||
var result = test[1];
|
||||
QUnit.strictEqual(parser.string(value), result,
|
||||
'string(' + JSON.stringify(value) + ')');
|
||||
});
|
||||
});
|
||||
|
||||
QUnit.test('CRUD', function() {
|
||||
var run_tests = function() {
|
||||
var User = new Sao.Model('res.user');
|
||||
|
||||
Reference in New Issue
Block a user