ARB
query_expr.cxx
Go to the documentation of this file.
1 // ============================================================= //
2 // //
3 // File : query_expr.cxx //
4 // Purpose : gui independent query functionality //
5 // //
6 // Coded by Ralf Westram (coder@reallysoft.de) in April 2017 //
7 // http://www.arb-home.de/ //
8 // //
9 // ============================================================= //
10 
11 #include "query_expr.h"
12 #include <arb_global_defs.h>
13 
14 using namespace std;
15 
16 QueryExpr::QueryExpr(query_operator aqo, QueryKeyPtr key, bool not_equal, const char *expression) :
17  op(aqo),
18  qkey(key),
19  Not(not_equal),
20  expr(strdup(expression)),
22  error(NULp),
23  lastACIresult(NULp),
24  next(NULp)
25 {
26  qe_assert(op == OR || op == AND);
27  detect_query_type();
28 }
29 
30 QueryExpr *QueryExpr::remove_tail() {
31  QueryExpr *tail = NULp;
32  if (next) {
33  QueryExpr *body_last = this;
34  while (body_last->next && body_last->next->next) {
35  body_last = body_last->next;
36  }
37  qe_assert(body_last->next);
38  qe_assert(!body_last->next->next);
39 
40  tail = body_last->next;
41  body_last->next = NULp;
42  }
43  return tail;
44 }
45 
47  qe_assert(this != tail);
48 
49  if (next) next->append(tail);
50  else {
51  next = tail;
52  tail = NULp;
53  }
54 }
55 
57  if (next) {
58  QueryExpr *tail = remove_tail();
59 
60  negate();
61  tail->negate();
62 
63  switch (tail->op) {
64  case AND: tail->op = OR; break;
65  case OR: tail->op = AND; break;
66  default: qe_assert(0); break;
67  }
68 
69  append(tail);
70  }
71  else {
72  Not = !Not;
73  qkey->negate();
74  }
75 }
76 
77 GB_ERROR QueryExpr::getError(int count) const {
78  GB_ERROR err = error;
79  error = NULp;
80 
81  if (err) {
82  err = GBS_global_string("%s (in %i. active query)", err, count+1);
83  }
84 
85  if (next) {
86  if (err) {
87  char *dup = strdup(err);
88 
89  err = next->getError(count+1);
90  if (err) err = GBS_global_string("%s\n%s", dup, err);
91  else err = GBS_static_string(dup);
92  free(dup);
93  }
94  else {
95  err = next->getError(count+1);
96  }
97  }
98 
99  return err;
100 }
101 
102 
103 inline bool containsWildcards(const char *str) { return strpbrk(str, "*?"); }
104 inline bool containsWildcards(const string& str) { return str.find_first_of("*?") != string::npos; }
105 
106 void QueryExpr::detect_query_type() {
107  char first = expr[0];
108  string& str = xquery.str;
109  str = expr;
110 
111  type = AQT_INVALID;
112 
113  if (!first) type = AQT_EMPTY;
114  else if (first == '/') {
115  GB_CASE case_flag;
116  GB_ERROR err = NULp;
117  const char *unwrapped = GBS_unwrap_regexpr(expr, &case_flag, &err);
118  if (unwrapped) {
119  xquery.regexp = GBS_compile_regexpr(unwrapped, case_flag, &err);
120  if (xquery.regexp) type = AQT_REGEXPR;
121  }
122  if (err) freedup(error, err);
123  }
124  else if (first == '|') type = AQT_ACI;
125  else if (first == '<' || first == '>') {
126  const char *rest = expr+1;
127  const char *end;
128  float f = strtof(rest, const_cast<char**>(&end));
129 
130  if (end != rest) { // did convert part or all of rest to float
131  if (end[0] == 0) { // all of rest has been converted
132  type = expr[0] == '<' ? AQT_LOWER : AQT_GREATER;
133  xquery.number = f;
134  }
135  else {
136  freeset(error, GBS_global_string_copy("Could not convert '%s' to number (unexpected content '%s')", rest, end));
137  }
138  }
139  // otherwise handle as non-special search string
140  }
141 
142  if (type == AQT_INVALID && !error) { // no type detected above
143  if (containsWildcards(expr)) {
144  size_t qlen = strlen(expr);
145  char last = expr[qlen-1];
146 
147  if (first == '*') {
148  if (last == '*') {
149  str = string(str, 1, str.length()-2); // cut off first and last
150  type = str.length() ? AQT_OCCURS : AQT_NON_EMPTY;
151  }
152  else {
153  str = string(str, 1); // cut of first
154  type = AQT_ENDS_WITH;
155  }
156  }
157  else {
158  if (last == '*') {
159  str = string(str, 0, str.length()-1); // cut of last
160  type = AQT_STARTS_WITH;
161  }
162  else type = AQT_WILDCARD;
163  }
164 
165  if (type != AQT_WILDCARD && containsWildcards(str)) { // still contains wildcards -> fallback
166  str = expr;
167  type = AQT_WILDCARD;
168  }
169  }
170  else type = AQT_EXACT_MATCH;
171  }
172 
173  // qe_assert(type != AQT_INVALID || error);
174  qe_assert(correlated(error, type == AQT_INVALID));
175 }
176 
177 bool QueryExpr::first_matches(const QueryTarget& target, char*& matched_data) const {
178  bool hit = false;
179  GB_ERROR retrieveError = NULp;
180  char *data = qkey->get_target_data(target, retrieveError);
181 
182  qe_assert(contradicted(data, retrieveError));
183  if (!data && !error) setError(retrieveError);
184 
185  if (error) {
186  hit = false; // as soon as an error has been set, the query will no longer match
187  }
188  else switch (type) {
189  case AQT_EMPTY: {
190  hit = (data[0] == 0);
191  break;
192  }
193  case AQT_NON_EMPTY: {
194  hit = (data[0] != 0);
195  break;
196  }
197  case AQT_EXACT_MATCH: { // exact match (but ignoring case)
198  hit = strcasecmp(data, expr) == 0;
199  break;
200  }
201  case AQT_OCCURS: { // query expression occurs in data (equiv to '*expr*')
202  hit = GBS_find_string(data, xquery.str.c_str(), 1);
203  break;
204  }
205  case AQT_STARTS_WITH: { // data starts with query expression (equiv to 'expr*')
206  hit = strncasecmp(data, xquery.str.c_str(), xquery.str.length()) == 0;
207  break;
208  }
209  case AQT_ENDS_WITH: { // data ends with query expression (equiv to '*expr')
210  int dlen = strlen(data);
211  hit = strcasecmp(data+dlen-xquery.str.length(), xquery.str.c_str()) == 0;
212  break;
213  }
214  case AQT_WILDCARD: { // expr contains wildcards (use GBS_string_matches for compare)
215  hit = GBS_string_matches(data, expr, GB_IGNORE_CASE);
216  break;
217  }
218  case AQT_GREATER: // data is greater than query
219  case AQT_LOWER: { // data is lower than query
220  const char *start = data;
221  while (start[0] == ' ') ++start;
222 
223  const char *end;
224  float f = strtof(start, const_cast<char**>(&end));
225 
226  if (end == start) { // nothing was converted
227  hit = false;
228  }
229  else {
230  bool is_numeric = (end[0] == 0);
231 
232  if (!is_numeric) {
233  while (end[0] == ' ') ++end;
234  is_numeric = (end[0] == 0);
235  }
236  if (is_numeric) {
237  hit = (type == AQT_GREATER)
238  ? f > xquery.number
239  : f < xquery.number;
240  }
241  else {
242  hit = false;
243  }
244  }
245  break;
246  }
247  case AQT_REGEXPR: { // expr is a regexpr ('/.../')
248  hit = GBS_regmatch_compiled(data, xquery.regexp, NULp);
249  break;
250  }
251  case AQT_ACI: { // expr is a ACI ('|...'); result = "0" -> no hit; otherwise hit
252  GBL_call_env callEnv(target.get_ACI_item(), target.get_env());
253 
254  char *aci_result = GB_command_interpreter_in_env(data, expr, callEnv);
255  if (!aci_result) {
256  freedup(error, GB_await_error());
257  hit = false;
258  }
259  else {
260  hit = strcmp(aci_result, "0") != 0;
261  }
262  freeset(lastACIresult, aci_result);
263  break;
264  }
265  case AQT_INVALID: { // invalid
266  qe_assert(0);
267  freedup(error, "Invalid search expression");
268  hit = false;
269  break;
270  }
271  }
272 
273  matched_data = data; // provide data used for query (transfers ownership to caller)
274  return Not ? !hit : hit;
275 }
276 
277 bool QueryExpr::matches(const QueryTarget& target, std::string& hit_reason) const {
278  bool hit = false;
279  int qindex = 0;
280 
281  qe_assert(hit_reason.empty());
282 
283  for (const QueryExpr *subexpr = this; // iterate over all single queries
284  subexpr;
285  ++qindex, subexpr = subexpr ? subexpr->next : NULp)
286  {
287  if ((subexpr->op == OR) == hit) {
288  continue; // skip query Q for '1 OR Q' and for '0 AND Q' (result can't change)
289  }
290 
291  string this_hit_reason;
292 
293  const QueryKey& query_key = subexpr->get_key();
294  query_key.reset();
295 
296  query_key_type key_type = subexpr->get_key_type();
297  bool this_hit = (key_type == QKEY_ALL || key_type == QKEY_ALL_REC);
298 
299  bool do_match = true;
300  while (do_match) { // loop over multi-target-keys
301  char *matched_data = NULp;
302  bool matched = subexpr->first_matches(target, matched_data); // includes not-op
303  bool sub_decided = // done?
304  key_type == QKEY_EXPLICIT ||
305  (matched == (key_type == QKEY_ANY || key_type == QKEY_ANY_REC));
306 
307  if (sub_decided) {
308  qe_assert(implicated(key_type != QKEY_EXPLICIT, contradicted(this_hit, matched)));
309  this_hit = matched;
310 
311  const char *reason_key = query_key.get_name();
312 
313  if (strlen(matched_data)>MAX_SHOWN_DATA_SIZE) {
314  size_t shortened_len = GBS_shorten_repeated_data(matched_data);
315  if (shortened_len>MAX_SHOWN_DATA_SIZE) {
316  strcpy(matched_data+MAX_SHOWN_DATA_SIZE-5, "[...]");
317  }
318  }
319  this_hit_reason = string(reason_key)+"="+matched_data;
320  const char *ACIresult = subexpr->get_last_ACI_result();
321  if (ACIresult) this_hit_reason = string("[ACI=")+ACIresult+"] "+this_hit_reason;
322 
323  do_match = false;
324  }
325  else {
326  do_match = do_match && query_key.next();
327  }
328  free(matched_data);
329  }
330 
331  if (this_hit && (key_type == QKEY_ALL || key_type == QKEY_ALL_REC)) {
332  qe_assert(this_hit_reason.empty());
333  this_hit_reason = subexpr->shallMatch() ? "<matched all>" : "<matched none>";
334  }
335 
336 
337  if (this_hit) {
338  qe_assert(!this_hit_reason.empty()); // if we got a hit, we also need a reason
339  const char *prefix = GBS_global_string("%c%c", '1'+qindex, subexpr->shallMatch() ? ' ' : '!');
340  this_hit_reason = string(prefix)+this_hit_reason;
341  }
342 
343  // calculate result
344  // (Note: the operator of the 1st query is always OR)
345  switch (subexpr->op) {
346  case AND: {
347  qe_assert(hit); // otherwise there was no need to run this sub-query
348  hit = this_hit;
349  hit_reason = hit_reason.empty() ? this_hit_reason : hit_reason+" & "+this_hit_reason;
350  break;
351  }
352  case OR: {
353  qe_assert(!hit); // otherwise there was no need to run this sub-query
354  hit = this_hit;
355  hit_reason = this_hit_reason;
356  break;
357  }
358  default:
359  qe_assert(0);
360  break;
361  }
362  qe_assert(!hit || !hit_reason.empty()); // if we got a hit, we also need a reason
363  }
364 
365  return hit;
366 }
367 
const char * GB_ERROR
Definition: arb_core.h:25
GB_TYPES type
virtual void reset() const =0
#define implicated(hypothesis, conclusion)
Definition: arb_assert.h:289
GBS_regex * GBS_compile_regexpr(const char *regexpr, GB_CASE case_flag, GB_ERROR *error)
Definition: arb_match.cxx:40
return string(buffer, length)
const GBL_env & get_env() const
Definition: query_expr.h:100
Definition: AP_filter.hxx:36
const char * GBS_unwrap_regexpr(const char *regexpr_in_slashes, GB_CASE *case_flag, GB_ERROR *error)
Definition: arb_match.cxx:69
virtual const char * get_name() const =0
size_t GBS_shorten_repeated_data(char *data)
Definition: adstring.cxx:357
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
STL namespace.
Definition: macke.h:201
bool next() const
Definition: query_expr.h:124
GB_CSTR GBS_find_string(GB_CSTR cont, GB_CSTR substr, int match_mode)
Definition: admatch.cxx:103
static HelixNrInfo * start
GB_ERROR GB_await_error()
Definition: arb_msg.cxx:342
void negate()
Definition: query_expr.h:130
static void error(const char *msg)
Definition: mkptypes.cxx:96
query_key_type
Definition: query_expr.h:81
virtual char * get_target_data(const QueryTarget &target, GB_ERROR &error) const =0
#define MAX_SHOWN_DATA_SIZE
Definition: query_expr.h:62
GB_CASE
Definition: arb_core.h:30
void append(QueryExpr *&tail)
Definition: query_expr.cxx:46
const char * GBS_regmatch_compiled(const char *str, GBS_regex *comreg, size_t *matchlen)
Definition: arb_match.cxx:120
#define qe_assert(cond)
Definition: query_expr.h:30
bool matches(const QueryTarget &target, std::string &hit_reason) const
Definition: query_expr.cxx:277
query_operator
Definition: query_expr.h:64
const char * GBS_static_string(const char *str)
Definition: arb_msg.cxx:212
void negate()
Definition: query_expr.cxx:56
#define NULp
Definition: cxxforward.h:116
GB_ERROR getError(int count=0) const
Definition: query_expr.cxx:77
NOT4PERL char * GB_command_interpreter_in_env(const char *str, const char *commands, const GBL_call_env &callEnv)
Definition: gb_aci.cxx:361
bool containsWildcards(const char *str)
Definition: query_expr.cxx:103
void setError(GB_ERROR error_) const
Definition: query_expr.h:210
QueryExpr(query_operator aqo, QueryKeyPtr key, bool not_equal, const char *expression)
Definition: query_expr.cxx:16
bool GBS_string_matches(const char *str, const char *expr, GB_CASE case_sens)
Definition: admatch.cxx:193
char * GBS_global_string_copy(const char *templat,...)
Definition: arb_msg.cxx:194
virtual GBDATA * get_ACI_item() const =0