aboutsummaryrefslogtreecommitdiff
path: root/Src/nde/android/Query.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/nde/android/Query.cpp')
-rw-r--r--Src/nde/android/Query.cpp973
1 files changed, 973 insertions, 0 deletions
diff --git a/Src/nde/android/Query.cpp b/Src/nde/android/Query.cpp
new file mode 100644
index 00000000..46a9ad1a
--- /dev/null
+++ b/Src/nde/android/Query.cpp
@@ -0,0 +1,973 @@
+#include "../nde.h"
+#include "../NDEString.h"
+#include "Query.h"
+
+//---------------------------------------------------------------------------
+
+bool Scanner::Query(const char *query)
+{
+ if (!query) return false;
+ ndestring_release(last_query);
+ last_query = ndestring_wcsdup(query);
+ RemoveFilters();
+ in_query_parser = 1;
+ bool r = Query_Parse(query);
+
+ if (r == false)
+ {
+ if (!disable_date_resolution) RemoveFilters();
+ last_query_failed = true;
+ }
+ in_query_parser = 0;
+ Query_CleanUp();
+ return r & CheckFilters();
+}
+
+const char *Scanner::GetLastQuery()
+{
+ return last_query;
+}
+
+
+typedef struct
+{
+ const char *token;
+ int tid;
+} tokenstruct;
+
+tokenstruct Tokens[] = // Feel free to add more...
+{
+ {"AND", TOKEN_AND },
+ {"OR", TOKEN_OR },
+ {"HAS", TOKEN_CONTAINS },
+ {"NOTHAS",TOKEN_NOTCONTAINS},
+ {"BEGINS", TOKEN_BEGINS },
+ {"ENDS", TOKEN_ENDS },
+ {"ISEMPTY", TOKEN_ISEMPTY},
+ {"ISNOTEMPTY",TOKEN_ISNOTEMPTY},
+ {"LIKE", TOKEN_LIKE},
+ {"BEGINSLIKE", TOKEN_BEGINSLIKE},
+};
+
+
+typedef struct
+{
+ int Op;
+ int Level;
+} OpLevel;
+
+static int Query_ParseLength(const char *str)
+{
+ int i = atoi(str);
+
+ const char *p;
+ if ((p=strstr(str,":")))
+ {
+ i*=60;
+ i+=atoi(++p);
+ if ((p=strstr(p,":")))
+ {
+ i*=60;
+ i+=atoi(++p);
+ }
+ }
+ return i;
+}
+
+/*
+ our state machine
+
+ &, |
+ ----------<-------------------------<----------------------<-----------
+ | |
+ v ID (Col) =, >, <... ID / Data / [f] ) |
+ ---->(0) ----->-----> (1) ------>-----> (2) ------>------> (3) ------>-----> (4) <--
+ | |^ \isempty------------->------------/ |^ | |
+ | !( || ||---- | ) |
+ --<-- ---------<---------------------------<-------------<-| | -->--
+ &, | v [f] |
+ -->--
+
+*/
+
+//---------------------------------------------------------------------------
+bool Scanner::Query_Parse(const char *query)
+{
+ const char *p = query; // pointer on next token to read
+ int size;
+ int state = 0;
+ int pcount = 0;
+ VListEntry<OpLevel> *entry;
+
+ if (pstack.GetNElements() > 0)
+ Query_CleanUp();
+
+ while (1)
+ {
+ p = Query_EatSpace(p);
+ int t = Query_GetNextToken(p, &size, &token);
+ if (t == TOKEN_UNKNOWN)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ if (t == TOKEN_EOQ)
+ break;
+ switch (state)
+ {
+ case 0:
+ switch (t)
+ {
+ case TOKEN_PAROPEN:
+ state = 0;
+ // check too many parenthesis open
+ if (pcount == 255)
+ {
+ Query_SyntaxError((int)(p-query)); // should not be _syntax_ error
+ return false;
+ }
+ // up one level
+ pcount++;
+ break;
+ case TOKEN_NOT:
+ // push not in this level
+ OpLevel o;
+ o.Op = FILTER_NOT;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, true);
+ state = 0;
+ break;
+ case TOKEN_IDENTIFIER:
+ state = 1;
+ // create filter column
+
+ if (AddFilterByName(token, NULL, FILTER_NONE) == ADDFILTER_FAILED)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+
+ break;
+ default:
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ break;
+ case 1:
+ switch (t)
+ {
+ case TOKEN_EQUAL:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_EQUALS);
+ break;
+ }
+ case TOKEN_ABOVE:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_ABOVE);
+ break;
+ }
+ case TOKEN_BELOW:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_BELOW);
+ break;
+ }
+ case TOKEN_CONTAINS:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_CONTAINS);
+ break;
+ }
+ case TOKEN_NOTCONTAINS:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_NOTCONTAINS);
+ break;
+ }
+ case TOKEN_AOREQUAL:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_ABOVEOREQUAL);
+ break;
+ }
+ case TOKEN_BOREQUAL:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_BELOWOREQUAL);
+ break;
+ }
+ case TOKEN_NOTEQUAL:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_NOTEQUALS);
+ break;
+ }
+ case TOKEN_BEGINS:
+ {
+ state = 2;
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_BEGINS);
+ }
+ break;
+ case TOKEN_ENDS:
+ {
+ state = 2;
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_ENDS);
+ }
+ break;
+ case TOKEN_LIKE:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_LIKE);
+ break;
+ }
+ case TOKEN_BEGINSLIKE:
+ {
+ state = 2;
+ // set filter op
+ Filter *f = GetLastFilter();
+ f->SetOp(FILTER_BEGINSLIKE);
+ break;
+ }
+ case TOKEN_ISNOTEMPTY:
+ case TOKEN_ISEMPTY:
+ {
+ state = 3;
+ Filter *f = GetLastFilter();
+ f->SetOp(t==TOKEN_ISEMPTY ? FILTER_ISEMPTY : FILTER_ISNOTEMPTY);
+ // pop all operators in this level beginning by the last inserted
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ }
+ break;
+ default:
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ break;
+ case 2:
+ if (t == TOKEN_SQBRACKETOPEN)
+ {
+ state = 3;
+ const char *s = strchr(p, ']');
+ if (!s)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ p = Query_EatSpace(p);
+ if (*p == '[') p++;
+
+ char *format = ndestring_malloc((s-p+1)*sizeof(char));
+ strncpy(format, p, s-p);
+ format[s-p] = 0;
+
+ Filter *f = GetLastFilter();
+ int id = f->GetId();
+ ColumnField *c = GetColumnById(id);
+ int tt = c ? c->GetDataType() : -1;
+ if (disable_date_resolution || !c || (tt != FIELD_INTEGER && tt != FIELD_DATETIME && tt != FIELD_LENGTH && tt != FIELD_BOOLEAN))
+ {
+
+ if (disable_date_resolution)
+ {
+ StringField *field = (StringField *)f->Data();
+
+ if (!field)
+ {
+ // format was used without a value, assume value is 0
+ f->SetData(new StringField(""));
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ field = (StringField*)f->Data();
+ }
+
+ field->SetNDEString(format);
+ ndestring_release(format);
+ p = s+1;
+ continue;
+ }
+
+
+ ndestring_release(format);
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+
+ IntegerField *field = (IntegerField *)f->Data();
+
+ if (!field)
+ {
+ // format was used without a value, assume value is 0
+ f->SetData(new IntegerField(0));
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ field = (IntegerField *)f->Data();
+ }
+ int r = field->ApplyConversion(format);
+ ndestring_release(format);
+ if (!r)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ p = s+1;
+ continue;
+ }
+// switch (t) {
+ // case TOKEN_IDENTIFIER:
+ else // JF> we make this relaxed, so anything is valid as a value
+ {
+ state = 3;
+ // set filter data
+ Filter *f = GetLastFilter();
+ int id = f->GetId();
+ ColumnField *c = GetColumnById(id);
+ switch (c ? c->GetDataType() : -1)
+ {
+ case FIELD_DATETIME:
+ if (disable_date_resolution)
+ goto field_string_override;
+ case FIELD_LENGTH:
+ {
+ int i;
+ IntegerField *i_f = new IntegerField();
+ i = Query_ParseLength(token);
+ i_f->SetValue(i);
+ f->SetData(i_f);
+ }
+ break;
+
+ case FIELD_BOOLEAN:
+ case FIELD_INTEGER:
+ {
+ int i;
+ IntegerField *i_f = new IntegerField();
+ i = atoi(token);
+ i_f->SetValue(i);
+ f->SetData(i_f);
+ }
+ break;
+ case FIELD_INT64:
+ {
+ int64_t i;
+ Int64Field *i_f = new Int64Field();
+ i = strtoull(token, 0, 10); // todo: Replace with own conversion and error checking
+ i_f->SetValue(i);
+ f->SetData(i_f);
+ }
+ break;
+ case FIELD_FILENAME:
+ {
+ FilenameField *s_f = new FilenameField();
+ s_f->SetNDEString(token);
+ f->SetData(s_f);
+ }
+ break;
+ case FIELD_STRING:
+field_string_override:
+ {
+ StringField *s_f = new StringField();
+ s_f->SetNDEString(token);
+ f->SetData(s_f);
+ }
+ break;
+ default:
+ Query_SyntaxError((int)(p-query));
+ return false;
+ break;
+
+ }
+ // pop all operators in this level beginning by the last inserted
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ break;
+ }
+// default:
+ // Query_SyntaxError(p-query);
+ // return false;
+// }
+ break;
+ case 3:
+ switch (t)
+ {
+ case TOKEN_SQBRACKETOPEN:
+ {
+ const char *s = strchr(p, ']');
+ if (!s)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ p = Query_EatSpace(p);
+ if (*p == '[') p++;
+ char *format = ndestring_malloc((s-p+1)*sizeof(char));
+ strncpy(format, p, s-p);
+ format[s-p] = 0;
+ Filter *f = GetLastFilter();
+ int id = f->GetId();
+ ColumnField *c = GetColumnById(id);
+ int tt = c ? c->GetDataType() : -1;
+ if (disable_date_resolution || !c || (tt != FIELD_INTEGER && tt != FIELD_DATETIME && tt != FIELD_LENGTH && tt != FIELD_BOOLEAN))
+ {
+ if (disable_date_resolution)
+ {
+ StringField *field = (StringField *)f->Data();
+
+ if (!field)
+ {
+ // format was used without a value, assume value is 0
+ f->SetData(new StringField(""));
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ field = (StringField *)f->Data();
+ }
+
+ field->SetNDEString(format);
+ ndestring_release(format);
+ p = s+1;
+ continue;
+ }
+ ndestring_release(format);
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+
+ IntegerField *field = (IntegerField *)f->Data();
+
+ if (!field)
+ {
+ // format was used without a value, assume value is 0
+ f->SetData(new IntegerField(0));
+ entry = (VListEntry<OpLevel> *)pstack.GetFoot();
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ field = (IntegerField *)f->Data();
+ }
+ int r = field->ApplyConversion(format);
+ ndestring_release(format);
+ if (!r)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ p = s+1;
+ continue;
+ }
+ break;
+
+ case TOKEN_PARCLOSE:
+ state = 4;
+ // check parenthesis count
+ if (pcount == 0)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ // down one level
+ pcount--;
+ // pop all operators in this level, beginning by the last inserted
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ break;
+ case TOKEN_AND:
+ {
+ state = 0;
+ // push and
+ OpLevel o;
+ o.Op = FILTER_AND;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, true);
+ break;
+ }
+ case TOKEN_OR:
+ {
+ state = 0;
+ // push or
+ OpLevel o;
+ o.Op = FILTER_OR;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, true);
+ break;
+ }
+ default:
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ break;
+ case 4:
+ switch (t)
+ {
+ case TOKEN_AND:
+ {
+ state = 0;
+ // push and
+ OpLevel o;
+ o.Op = FILTER_AND;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, true);
+ break;
+ }
+ case TOKEN_OR:
+ {
+ state = 0;
+ // push or
+ OpLevel o;
+ o.Op = FILTER_OR;
+ o.Level = pcount;
+ entry = new VListEntry<OpLevel>;
+ entry->SetVal(o);
+ pstack.AddEntry(entry, true);
+ break;
+ }
+ case TOKEN_PARCLOSE:
+ state = 4;
+ // check parenthesis count
+ if (pcount == 0)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ // down one level
+ pcount--;
+ // pop all operators in this level, beginning by the last inserted
+ while (entry)
+ {
+ if (entry->GetVal().Level == pcount)
+ {
+ AddFilterOp(entry->GetVal().Op);
+ VListEntry<OpLevel> *_entry = (VListEntry<OpLevel> *)entry->GetPrevious();
+ pstack.RemoveEntry(entry);
+ entry = _entry;
+ }
+ else
+ break;
+ }
+ break;
+ default:
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ break;
+ default:
+ // Ahem... :/
+ break;
+ }
+ p += size;
+ }
+ if (pcount > 0)
+ {
+ Query_SyntaxError((int)(p-query));
+ return false;
+ }
+ return true;
+}
+
+//---------------------------------------------------------------------------
+
+void Scanner::Query_SyntaxError(int c)
+{
+
+}
+
+//---------------------------------------------------------------------------
+void Scanner::Query_CleanUp()
+{
+ while (pstack.GetNElements() > 0)
+ {
+ VListEntry<int> *e;
+ e = (VListEntry<int> *)pstack.GetHead();
+ pstack.RemoveEntry(e);
+ }
+}
+
+
+//---------------------------------------------------------------------------
+const char *Scanner::Query_EatSpace(const char *p)
+{
+ while (*p && *p == ' ') p++;
+ return p;
+}
+
+//---------------------------------------------------------------------------
+const char *Scanner::Query_ProbeNonAlphaNum(const char *p)
+{
+ int inquote=0;
+ while (*p && (!Query_isControlChar(*p) || (inquote)))
+ {
+ if (*p == '\"')
+ {
+ if (!inquote)
+ inquote = 1;
+ else
+ return p+1;
+ }
+ p++;
+ }
+ return p;
+}
+
+//---------------------------------------------------------------------------
+int Scanner::Query_isControlChar(char p)
+{
+ switch (p)
+ {
+ case '&':
+ case '|':
+ case '!':
+ case '(':
+ case '[':
+ case ')':
+ case ']':
+ case '>':
+ case '<':
+ case '=':
+ case ',':
+ case ' ':
+ return true;
+ }
+ return false;
+}
+
+//---------------------------------------------------------------------------
+char *Scanner::Query_ProbeAlphaNum(char *p)
+{
+ while (*p && Query_isControlChar(*p)) p++;
+ return p;
+}
+
+//---------------------------------------------------------------------------
+char *Scanner::Query_ProbeSpace(char *p)
+{
+ while (*p && *p != ' ') p++;
+ return p;
+}
+
+//---------------------------------------------------------------------------
+int Scanner::Query_LookupToken(const char *t)
+{
+ for (int i=0;i<sizeof(Tokens)/sizeof(tokenstruct);i++)
+ {
+ if (!_stricmp(Tokens[i].token, t))
+ return Tokens[i].tid;
+ }
+ return TOKEN_IDENTIFIER;
+}
+
+//---------------------------------------------------------------------------
+
+int Scanner::Query_GetNextToken(const char *p, int *size, char **_token, int tokentable)
+{
+
+ int t = TOKEN_EOQ;
+ const char *startptr = p;
+
+ if (!*p) return TOKEN_EOQ;
+
+ p = Query_EatSpace(p);
+
+ const char *e = Query_ProbeNonAlphaNum(p);
+
+ if (e != p) // We have a word
+ {
+ size_t token_length = e-p;
+ if (*_token) ndestring_release(*_token);
+ *_token = ndestring_wcsndup(p, token_length);
+ if (*(*_token) == '\"' && (*_token)[token_length-1] == '\"') // check for quoted string
+ {
+ size_t l=token_length-2;
+ if (l>0)
+ {
+ memcpy(*_token,(*_token)+1,l*sizeof(char));
+ (*_token)[l]=0;
+ Query_Unescape(*_token);
+ }
+ else
+ (*_token)[0]=0;// we have an empty string
+ }
+
+ switch (tokentable)
+ {
+ case -1:
+ t = TOKEN_IDENTIFIER;
+ break;
+ case 0:
+ t = Query_LookupToken(*_token);
+ break;
+ case 1:
+ t = IntegerField::LookupToken(*_token);
+ }
+ p = e;
+ }
+ else // We have a symbol
+ {
+ switch (*p)
+ {
+ case '&':
+ if (*(p+1) == '&') p++;
+ t = TOKEN_AND;
+ break;
+ case '|':
+ if (*(p+1) == '|') p++;
+ t = TOKEN_OR;
+ break;
+ case '!':
+ if (*(p+1) == '=')
+ {
+ p++;
+ t = TOKEN_NOTEQUAL;
+ break;
+ }
+ t = TOKEN_NOT;
+ break;
+ case '(':
+ t = TOKEN_PAROPEN;
+ break;
+ case ')':
+ t = TOKEN_PARCLOSE;
+ break;
+ case '[':
+ t = TOKEN_SQBRACKETOPEN;
+ break;
+ case ']':
+ t = TOKEN_SQBRACKETCLOSE;
+ break;
+ case ',':
+ t = TOKEN_COMMA;
+ break;
+ case '>':
+ if (*(p+1) == '=')
+ {
+ p++;
+ t = TOKEN_AOREQUAL;
+ break;
+ }
+ if (*(p+1) == '<')
+ {
+ p++;
+ t = TOKEN_NOTEQUAL;
+ break;
+ }
+ t = TOKEN_ABOVE;
+ break;
+ case '<':
+ if (*(p+1) == '=')
+ {
+ p++;
+ t = TOKEN_BOREQUAL;
+ break;
+ }
+ if (*(p+1) == '>')
+ {
+ p++;
+ t = TOKEN_NOTEQUAL;
+ break;
+ }
+ t = TOKEN_BELOW;
+ break;
+ case '=':
+ if (*(p+1) == '>')
+ {
+ p++;
+ t = TOKEN_AOREQUAL;
+ break;
+ }
+ if (*(p+1) == '<')
+ {
+ p++;
+ t = TOKEN_BOREQUAL;
+ break;
+ }
+ if (*(p+1) == '!')
+ {
+ p++;
+ t = TOKEN_NOTEQUAL;
+ break;
+ }
+ if (*(p+1) == '=') p++;
+ t = TOKEN_EQUAL;
+ break;
+ default:
+ t = TOKEN_UNKNOWN;
+ break;
+ }
+ p++;
+ }
+
+ *size = (int)(p - startptr);
+ return t;
+}
+
+static uint8_t quickhex(char c)
+{
+ int hexvalue = c;
+ if (hexvalue & 0x10)
+ hexvalue &= ~0x30;
+ else
+ {
+ hexvalue &= 0xF;
+ hexvalue += 9;
+ }
+ return hexvalue;
+}
+
+static uint8_t DecodeEscape(const char *&str)
+{
+ uint8_t a = quickhex(*++str);
+ uint8_t b = quickhex(*++str);
+ str++;
+ return a * 16 + b;
+}
+
+static void DecodeEscapedUTF8(char *&output, const char *&input)
+{
+ bool error=false;
+
+ while (*input == '%')
+ {
+ if (isxdigit(input[1]) && isxdigit(input[2]))
+ {
+ *output++=DecodeEscape(input);
+ }
+ else if (input[1] == '%')
+ {
+ input+=2;
+ *output++='%';
+ }
+ else
+ {
+ error = true;
+ break;
+ }
+ }
+
+ if (error)
+ {
+ *output++ = *input++;
+ }
+}
+
+// benski> We have the luxury of knowing that decoding will ALWAYS produce smaller strings
+// so we can do it in-place
+void Query_Unescape(char *str)
+{
+ const char *itr = str;
+ while (*itr)
+ {
+ switch (*itr)
+ {
+ case '%':
+ DecodeEscapedUTF8(str, itr);
+ break;
+ default:
+ *str++ = *itr++;
+ break;
+ }
+ }
+ *str = 0;
+}
+