// vim: et:ts=4:sw=4:sts=4:ft=javascript pandora.Query = (function() { function parseFind2(str) { // takes a find query string, returns useful information about the application's state // (selected lists, find input key/value, group queries and selection, query object) Ox.print('parseFind2', str) str = str || ''; var conditions, ret = { find: {key: '', value: ''}, groups: [], lists: [], query: {conditions: [], operator: ''} }, subconditions = str.match(/\[.*?\]/g) || []; // replace subconditions with placeholder, // so we can later split by main operator subconditions.forEach(function(subcondition, i) { subconditions[i] = subcondition.substr(1, subcondition.length - 2); str = str.replace(subconditions[i], i); }); if (str.indexOf(',') > -1) { ret.query.operator = '&'; } else if (str.indexOf('|') > -1) { ret.query.operator = '|'; } ret.query.conditions = ( ret.query.operator == '' ? [str] : str.split(ret.query.operator == '&' ? ',' : '|') ).map(function(condition, i) { var kv, ret; if (condition[0] == '[') { // re-insert subcondition ret = parseFind2(subconditions[parseInt(condition.substr(1, condition.length - 2))]).query; } else { kv = ((condition.indexOf(':') > -1 ? '' : ':') + condition).split(':'); ret = Ox.extend({key: kv[0]}, parseValue(kv[1])); } return ret; }); // lists are selected if exactly one condition in an & query // or every condition in an | query // has "list" as key and "" as operator ret.lists = ret.query.operator == '|' ? everyCondition(ret.query.conditions, 'list', '') : oneCondition(ret.query.conditions, 'list', ''); // find is populated if exactly one condition in an & query // has a findKey as key and "" as operator if (ret.query.operator == '|') { ret.find = {key: 'advanced', value: ''}; } else { conditions = Ox.map(pandora.site.findKeys, function(findKey) { var values = oneCondition(ret.query.conditions, findKey.id, ''); return values.length ? {key: findKey.id, values: values} : null; }); ret.find = conditions.length == 0 ? {key: '', value: ''} : conditions.length == 1 && conditions[0].values.length == 1 ? {key: conditions[0].key, value: conditions[0].values[0]} : {key: 'advanced', value: ''} } // a group is selected if exactly one condition in an & query // or every condition in an | query // has the group id as key and "=" as operator ret.groups = pandora.user.ui.groups.map(function(key) { var selected = ret.query.operator == '|' ? everyCondition(ret.query.conditions, key, '=') : oneCondition(ret.query.conditions, key, '='), query = ret.query; if (selected.length) { query = { conditions: Ox.map(ret.query.conditions, function(condition) { var ret; if (condition.conditions) { ret = condition.conditions[0].key != key ? condition : null; // fixme: correct? see below... } else { ret = condition.key != key ? condition : null; } return ret; }), operator: '' }; if (query.conditions.length == 1) { if (query.conditions[0].conditions) { // unwrap single remaining bracketed query query = { conditions: query.conditions[0].conditions, operator: query.conditions[0].operator } } else { query.operator = ''; } } } return { query: query, selected: selected }; }); function oneCondition(conditions, key, operator) { // if exactly one condition has the given key and operator // (including conditions where all subconditions match) // returns the corresponding value(s), otherwise returns [] var values = Ox.map(conditions, function(condition) { var ret, same; if (condition.conditions) { var every = everyCondition(condition.conditions, key, operator); // fixme: what if [key|key],[key|other]? ret = every.length ? every : null; } else { ret = condition.key == key && condition.operator == operator ? [condition.value] : null } return ret; }); return values.length == 1 ? values[0] : []; } function everyCondition(conditions, key, operator) { // if every condition has the given key and operator // (excluding conditions where all subconditions match) // returns the corresponding value(s), otherwise returns [] var values = Ox.map(conditions, function(condition) { return condition.key == key && condition.operator == operator ? condition.value : null }); return values.length == conditions.length ? values : []; } return ret; } function constructFind(query) { //Ox.print('cF', query) return /*encodeURI(*/$.map(query.conditions, function(v, i) { if (!Ox.isUndefined(v.conditions)) { return '[' + constructFind(v) + ']'; } else { return v.value !== '' ? v.key + (v.key ? ':' : '') + constructValue(v.value, v.operator) : null; } }).join(query.operator)/*)*/; } function constructValue(value, operator) { operator = operator.replace('=', '^$'); if (operator.indexOf('$') > -1) { value = operator.substr(0, operator.length - 1) + value + '$'; } else { value = operator + value; } return value; } function mergeFind() { } function parseFind(str) { str = str || ''; var find = { conditions: [], operator: '' }, subconditions = str.match(/\[.*?\]/g) || []; $.each(subconditions, function(i, v) { subconditions[i] = v.substr(1, v.length - 2); str = str.replace(v, '[' + i + ']'); }); if (str.indexOf(',') > -1) { find.operator = '&'; } else if (str.indexOf('|') > -1) { find.operator = '|'; } //Ox.print('pF', str, find.operator) find.conditions = $.map(find.operator === '' ? [str] : str.split(find.operator == '&' ? ',' : '|'), function(v, i) { //Ox.print('v', v) var ret, kv; if (v[0] == '[') { //Ox.print('recursion', subconditions) ret = parseFind(subconditions[parseInt(v.substr(1, v.length - 2))]); } else { kv = ((v.indexOf(':') > - 1 ? '' : ':') + v).split(':'); if (kv[0] == 'list') { // fixme: this is just a hack pandora.user.ui.listQuery = {conditions: [$.extend({ key: kv[0] }, parseValue(kv[1]))], operator: ''}; } else { ret = $.extend({ key: kv[0] }, parseValue(kv[1])); } } return ret; }); return find; } function parseValue(str) { var value = { value: decodeURI(str), operator: '' }; if (value.value[0] == '!') { value.operator = '!'; value.value = value.value.substr(1); } if ('^<>'.indexOf(value.value[0]) > -1) { value.operator += value.value[0]; value.value = value.value.substr(1); } if (value.value.substr(-1) == '$') { value.operator += '$'; value.value = value.value.substr(0, value.value.length - 1); } value.operator = value.operator.replace('^$', '='); return value; } return { fromString: function(str) { var list = '', query = Ox.unserialize(str.substr(1)), sort = []; if ('find' in query) { Ox.print(Ox.repeat('-', 80)); Ox.print('parseFind2', parseFind2(query.find)); Ox.print(Ox.repeat('-', 80)); pandora.user.ui.listQuery = {conditions: [], operator: ''}; // fixme: hackish pandora.user.ui.findQuery = parseFind(query.find); if (pandora.user.ui.listQuery.conditions.length) { list = pandora.user.ui.listQuery.conditions[0].value; !pandora.user.ui.lists[list] && pandora.UI.set( ['lists', list].join('|'), pandora.site.user.ui.lists[''] ); } pandora.UI.set({list: list}); //Ox.print('user.ui.findQuery', pandora.user.ui.findQuery) } if ('sort' in query) { sort = query.sort.split(','); pandora.UI.set(['lists', pandora.user.ui.list, 'sort'].join('|'), query.sort.split(',').map(function(v) { var hasOperator = '+-'.indexOf(v[0]) > -1, key = hasOperator ? v.substr(1) : v, operator = hasOperator ? v[0]/*.replace('+', '')*/ : pandora.getSortOperator(key); return { key: key, operator: operator }; })); } if ('view' in query) { pandora.UI.set(['lists', pandora.user.ui.list, 'listView'].join('|'), query.view); } }, toObject: function(groupId) { //Ox.print('tO', pandora.user.ui.findQuery.conditions) // the inner $.merge() creates a clone var conditions = $.merge( $.merge([], pandora.user.ui.listQuery.conditions), pandora.user.ui.findQuery.conditions ), operator; $.merge(conditions, pandora.user.queryGroups ? $.map(pandora.user.queryGroups, function(v, i) { if (v.id != groupId && v.query.conditions.length) { return v.query.conditions.length == 1 ? v.query.conditions : v.query; } }) : []); operator = conditions.length < 2 ? '' : ','; // fixme: should be & //Ox.print('>>', groupId, pandora.user.ui.find, conditions); return { conditions: conditions, operator: operator }; }, toString: function() { //Ox.print('tS', pandora.user.ui.find) var sort = pandora.user.ui.lists[pandora.user.ui.list].sort[0], key = sort.key, operator = sort.operator; return '?' + Ox.serialize({ find: constructFind(pandora.Query.toObject()), sort: (operator == pandora.getSortOperator(key) ? '' : operator) + key, view: pandora.user.ui.lists[pandora.user.ui.list].listView }); } }; })();