// SciTE - Scintilla based Text Editor
-// PropSet.cxx - a java style properties file module
-// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org>
+/** @file PropSet.cxx
+ ** A Java style properties file module.
+ **/
+// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
// The License.txt file describes the conditions under which this software may be distributed.
// Maintain a dictionary of properties
#include <stdlib.h>
#include <string.h>
-#include <ctype.h>
#include <stdio.h>
#include "Platform.h"
#include "PropSet.h"
+// The comparison and case changing functions here assume ASCII
+// or extended ASCII such as the normal Windows code page.
+
+static inline char MakeUpperCase(char ch) {
+ if (ch < 'a' || ch > 'z')
+ return ch;
+ else
+ return static_cast<char>(ch - 'a' + 'A');
+}
+
+static inline bool IsLetter(char ch) {
+ return ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'));
+}
+
+inline bool IsASpace(unsigned int ch) {
+ return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
+}
+
+int CompareCaseInsensitive(const char *a, const char *b) {
+ while (*a && *b) {
+ if (*a != *b) {
+ char upperA = MakeUpperCase(*a);
+ char upperB = MakeUpperCase(*b);
+ if (upperA != upperB)
+ return upperA - upperB;
+ }
+ a++;
+ b++;
+ }
+ // Either *a or *b is nul
+ return *a - *b;
+}
+
+int CompareNCaseInsensitive(const char *a, const char *b, size_t len) {
+ while (*a && *b && len) {
+ if (*a != *b) {
+ char upperA = MakeUpperCase(*a);
+ char upperB = MakeUpperCase(*b);
+ if (upperA != upperB)
+ return upperA - upperB;
+ }
+ a++;
+ b++;
+ len--;
+ }
+ if (len == 0)
+ return 0;
+ else
+ // Either *a or *b is nul
+ return *a - *b;
+}
+
bool EqualCaseInsensitive(const char *a, const char *b) {
-#if PLAT_GTK
- return 0 == strcasecmp(a, b);
-#elif PLAT_WIN
- return 0 == stricmp(a, b);
-#elif PLAT_WX
- return 0 == wxStricmp(a, b);
-#endif
-}
-
-// Get a line of input. If end of line escaped with '\\' then continue reading.
-static bool GetFullLine(const char *&fpc, int &lenData, char *s, int len) {
- bool continuation = true;
- s[0] = '\0';
- while ((len > 1) && lenData > 0) {
- char ch = *fpc;
- fpc++;
- lenData--;
- if ((ch == '\r') || (ch == '\n')) {
- if (!continuation) {
- if ((lenData > 0) && (ch == '\r') && ((*fpc) == '\n')) {
- // munch the second half of a crlf
- fpc++;
- lenData--;
- }
- *s++ = '\0';
- return true;
- }
- } else if ((ch == '\\') && (lenData > 0) && ((*fpc == '\r') || (*fpc == '\n'))) {
- continuation = true;
+ return 0 == CompareCaseInsensitive(a, b);
+}
+
+// Since the CaseInsensitive functions declared in SString
+// are implemented here, I will for now put the non-inline
+// implementations of the SString members here as well, so
+// that I can quickly see what effect this has.
+
+SString::SString(int i) : sizeGrowth(sizeGrowthDefault) {
+ char number[32];
+ sprintf(number, "%0d", i);
+ s = StringAllocate(number);
+ sSize = sLen = (s) ? strlen(s) : 0;
+}
+
+SString::SString(double d, int precision) : sizeGrowth(sizeGrowthDefault) {
+ char number[32];
+ sprintf(number, "%.*f", precision, d);
+ s = StringAllocate(number);
+ sSize = sLen = (s) ? strlen(s) : 0;
+}
+
+bool SString::grow(lenpos_t lenNew) {
+ while (sizeGrowth * 6 < lenNew) {
+ sizeGrowth *= 2;
+ }
+ char *sNew = new char[lenNew + sizeGrowth + 1];
+ if (sNew) {
+ if (s) {
+ memcpy(sNew, s, sLen);
+ delete []s;
+ }
+ s = sNew;
+ s[sLen] = '\0';
+ sSize = lenNew + sizeGrowth;
+ }
+ return sNew != 0;
+}
+
+SString &SString::assign(const char *sOther, lenpos_t sSize_) {
+ if (!sOther) {
+ sSize_ = 0;
+ } else if (sSize_ == measure_length) {
+ sSize_ = strlen(sOther);
+ }
+ if (sSize > 0 && sSize_ <= sSize) { // Does not allocate new buffer if the current is big enough
+ if (s && sSize_) {
+ memcpy(s, sOther, sSize_);
+ }
+ s[sSize_] = '\0';
+ sLen = sSize_;
+ } else {
+ delete []s;
+ s = StringAllocate(sOther, sSize_);
+ if (s) {
+ sSize = sSize_; // Allow buffer bigger than real string, thus providing space to grow
+ sLen = strlen(s);
} else {
- continuation = false;
- *s++ = ch;
- *s = '\0';
- len--;
+ sSize = sLen = 0;
}
}
- return false;
+ return *this;
}
-PropSet::PropSet() {
- superPS = 0;
- size = 10;
- used = 0;
- vals = new char * [size];
+bool SString::operator==(const SString &sOther) const {
+ if ((s == 0) && (sOther.s == 0))
+ return true;
+ if ((s == 0) || (sOther.s == 0))
+ return false;
+ return strcmp(s, sOther.s) == 0;
}
-PropSet::~PropSet() {
- superPS = 0;
- Clear();
- delete []vals;
+bool SString::operator==(const char *sOther) const {
+ if ((s == 0) && (sOther == 0))
+ return true;
+ if ((s == 0) || (sOther == 0))
+ return false;
+ return strcmp(s, sOther) == 0;
}
-void PropSet::EnsureCanAddEntry() {
- if (used >= size - 2) {
- int newsize = size + 10;
- char **newvals = new char * [newsize];
+SString SString::substr(lenpos_t subPos, lenpos_t subLen) const {
+ if (subPos >= sLen) {
+ return SString(); // return a null string if start index is out of bounds
+ }
+ if ((subLen == measure_length) || (subPos + subLen > sLen)) {
+ subLen = sLen - subPos; // can't substr past end of source string
+ }
+ return SString(s, subPos, subPos + subLen);
+}
- for (int i = 0; i < used; i++) {
- newvals[i] = vals[i];
+SString &SString::lowercase(lenpos_t subPos, lenpos_t subLen) {
+ if ((subLen == measure_length) || (subPos + subLen > sLen)) {
+ subLen = sLen - subPos; // don't apply past end of string
+ }
+ for (lenpos_t i = subPos; i < subPos + subLen; i++) {
+ if (s[i] < 'A' || s[i] > 'Z')
+ continue;
+ else
+ s[i] = static_cast<char>(s[i] - 'A' + 'a');
+ }
+ return *this;
+}
+
+SString &SString::uppercase(lenpos_t subPos, lenpos_t subLen) {
+ if ((subLen == measure_length) || (subPos + subLen > sLen)) {
+ subLen = sLen - subPos; // don't apply past end of string
+ }
+ for (lenpos_t i = subPos; i < subPos + subLen; i++) {
+ if (s[i] < 'a' || s[i] > 'z')
+ continue;
+ else
+ s[i] = static_cast<char>(s[i] - 'a' + 'A');
+ }
+ return *this;
+}
+
+SString &SString::append(const char *sOther, lenpos_t sLenOther, char sep) {
+ if (!sOther) {
+ return *this;
+ }
+ if (sLenOther == measure_length) {
+ sLenOther = strlen(sOther);
+ }
+ int lenSep = 0;
+ if (sLen && sep) { // Only add a separator if not empty
+ lenSep = 1;
+ }
+ lenpos_t lenNew = sLen + sLenOther + lenSep;
+ // Conservative about growing the buffer: don't do it, unless really needed
+ if ((lenNew < sSize) || (grow(lenNew))) {
+ if (lenSep) {
+ s[sLen] = sep;
+ sLen++;
+ }
+ memcpy(&s[sLen], sOther, sLenOther);
+ sLen += sLenOther;
+ s[sLen] = '\0';
+ }
+ return *this;
+}
+
+SString &SString::insert(lenpos_t pos, const char *sOther, lenpos_t sLenOther) {
+ if (!sOther || pos > sLen) {
+ return *this;
+ }
+ if (sLenOther == measure_length) {
+ sLenOther = strlen(sOther);
+ }
+ lenpos_t lenNew = sLen + sLenOther;
+ // Conservative about growing the buffer: don't do it, unless really needed
+ if ((lenNew < sSize) || grow(lenNew)) {
+ lenpos_t moveChars = sLen - pos + 1;
+ for (lenpos_t i = moveChars; i > 0; i--) {
+ s[pos + sLenOther + i - 1] = s[pos + i - 1];
+ }
+ memcpy(s + pos, sOther, sLenOther);
+ sLen = lenNew;
+ }
+ return *this;
+}
+
+/**
+ * Remove @a len characters from the @a pos position, included.
+ * Characters at pos + len and beyond replace characters at pos.
+ * If @a len is 0, or greater than the length of the string
+ * starting at @a pos, the string is just truncated at @a pos.
+ */
+void SString::remove(lenpos_t pos, lenpos_t len) {
+ if (pos >= sLen) {
+ return;
+ }
+ if (len < 1 || pos + len >= sLen) {
+ s[pos] = '\0';
+ sLen = pos;
+ } else {
+ for (lenpos_t i = pos; i < sLen - len + 1; i++) {
+ s[i] = s[i+len];
+ }
+ sLen -= len;
+ }
+}
+
+bool SString::startswith(const char *prefix) {
+ lenpos_t lenPrefix = strlen(prefix);
+ if (lenPrefix > sLen) {
+ return false;
+ }
+ return strncmp(s, prefix, lenPrefix) == 0;
+}
+
+bool SString::endswith(const char *suffix) {
+ lenpos_t lenSuffix = strlen(suffix);
+ if (lenSuffix > sLen) {
+ return false;
+ }
+ return strncmp(s + sLen - lenSuffix, suffix, lenSuffix) == 0;
+}
+
+int SString::search(const char *sFind, lenpos_t start) const {
+ if (start < sLen) {
+ const char *sFound = strstr(s + start, sFind);
+ if (sFound) {
+ return sFound - s;
}
- delete []vals;
- vals = newvals;
- size = newsize;
}
+ return -1;
+}
+
+int SString::substitute(char chFind, char chReplace) {
+ int c = 0;
+ char *t = s;
+ while (t) {
+ t = strchr(t, chFind);
+ if (t) {
+ *t = chReplace;
+ t++;
+ c++;
+ }
+ }
+ return c;
+}
+
+int SString::substitute(const char *sFind, const char *sReplace) {
+ int c = 0;
+ lenpos_t lenFind = strlen(sFind);
+ lenpos_t lenReplace = strlen(sReplace);
+ int posFound = search(sFind);
+ while (posFound >= 0) {
+ remove(posFound, lenFind);
+ insert(posFound, sReplace, lenReplace);
+ posFound = search(sFind, posFound + lenReplace);
+ c++;
+ }
+ return c;
+}
+
+char *SContainer::StringAllocate(lenpos_t len) {
+ if (len != measure_length) {
+ return new char[len + 1];
+ } else {
+ return 0;
+ }
+}
+
+char *SContainer::StringAllocate(const char *s, lenpos_t len) {
+ if (s == 0) {
+ return 0;
+ }
+ if (len == measure_length) {
+ len = strlen(s);
+ }
+ char *sNew = new char[len + 1];
+ if (sNew) {
+ memcpy(sNew, s, len);
+ sNew[len] = '\0';
+ }
+ return sNew;
+}
+
+// End SString functions
+
+PropSet::PropSet() {
+ superPS = 0;
+ for (int root = 0; root < hashRoots; root++)
+ props[root] = 0;
+}
+
+PropSet::~PropSet() {
+ superPS = 0;
+ Clear();
}
-void PropSet::Set(const char *key, const char *val) {
- EnsureCanAddEntry();
- for (int i = 0; i < used; i += 2) {
- if (EqualCaseInsensitive(vals[i], key)) {
+void PropSet::Set(const char *key, const char *val, int lenKey, int lenVal) {
+ if (!*key) // Empty keys are not supported
+ return;
+ if (lenKey == -1)
+ lenKey = static_cast<int>(strlen(key));
+ if (lenVal == -1)
+ lenVal = static_cast<int>(strlen(val));
+ unsigned int hash = HashString(key, lenKey);
+ for (Property *p = props[hash % hashRoots]; p; p = p->next) {
+ if ((hash == p->hash) &&
+ ((strlen(p->key) == static_cast<unsigned int>(lenKey)) &&
+ (0 == strncmp(p->key, key, lenKey)))) {
// Replace current value
- delete [](vals[i + 1]);
- vals[i + 1] = StringDup(val);
+ delete [](p->val);
+ p->val = StringDup(val, lenVal);
return;
}
}
// Not found
- vals[used++] = StringDup(key);
- vals[used++] = StringDup(val);
+ Property *pNew = new Property;
+ if (pNew) {
+ pNew->hash = hash;
+ pNew->key = StringDup(key, lenKey);
+ pNew->val = StringDup(val, lenVal);
+ pNew->next = props[hash % hashRoots];
+ props[hash % hashRoots] = pNew;
+ }
+}
+
+void PropSet::Set(const char *keyVal) {
+ while (IsASpace(*keyVal))
+ keyVal++;
+ const char *endVal = keyVal;
+ while (*endVal && (*endVal != '\n'))
+ endVal++;
+ const char *eqAt = strchr(keyVal, '=');
+ if (eqAt) {
+ Set(keyVal, eqAt + 1, eqAt-keyVal, endVal - eqAt - 1);
+ } else if (*keyVal) { // No '=' so assume '=1'
+ Set(keyVal, "1", endVal-keyVal, 1);
+ }
}
-void PropSet::Set(char *keyval) {
- char *eqat = strchr(keyval, '=');
- if (eqat) {
- *eqat = '\0';
- Set(keyval, eqat + 1);
- *eqat = '=';
+void PropSet::SetMultiple(const char *s) {
+ const char *eol = strchr(s, '\n');
+ while (eol) {
+ Set(s);
+ s = eol + 1;
+ eol = strchr(s, '\n');
}
+ Set(s);
}
SString PropSet::Get(const char *key) {
- for (int i = 0; i < used; i += 2) {
- if (EqualCaseInsensitive(vals[i], key)) {
- return vals[i + 1];
+ unsigned int hash = HashString(key, strlen(key));
+ for (Property *p = props[hash % hashRoots]; p; p = p->next) {
+ if ((hash == p->hash) && (0 == strcmp(p->key, key))) {
+ return p->val;
}
}
if (superPS) {
}
}
-int PropSet::GetInt(const char *key, int defaultValue) {
+bool PropSet::IncludesVar(const char *value, const char *key) {
+ const char *var = strstr(value, "$(");
+ while (var) {
+ if (isprefix(var + 2, key) && (var[2 + strlen(key)] == ')')) {
+ // Found $(key) which would lead to an infinite loop so exit
+ return true;
+ }
+ var = strstr(var + 2, ")");
+ if (var)
+ var = strstr(var + 1, "$(");
+ }
+ return false;
+}
+
+SString PropSet::GetExpanded(const char *key) {
SString val = Get(key);
+ if (IncludesVar(val.c_str(), key))
+ return val;
+ return Expand(val.c_str());
+}
+
+SString PropSet::Expand(const char *withVars, int maxExpands) {
+ char *base = StringDup(withVars);
+ char *cpvar = strstr(base, "$(");
+ while (cpvar && (maxExpands > 0)) {
+ char *cpendvar = strchr(cpvar, ')');
+ if (!cpendvar)
+ break;
+ int lenvar = cpendvar - cpvar - 2; // Subtract the $()
+ char *var = StringDup(cpvar + 2, lenvar);
+ SString val = Get(var);
+ if (IncludesVar(val.c_str(), var))
+ break;
+ size_t newlenbase = strlen(base) + val.length() - lenvar;
+ char *newbase = new char[newlenbase];
+ strncpy(newbase, base, cpvar - base);
+ strcpy(newbase + (cpvar - base), val.c_str());
+ strcpy(newbase + (cpvar - base) + val.length(), cpendvar + 1);
+ delete []var;
+ delete []base;
+ base = newbase;
+ cpvar = strstr(base, "$(");
+ maxExpands--;
+ }
+ SString sret = base;
+ delete []base;
+ return sret;
+}
+
+int PropSet::GetInt(const char *key, int defaultValue) {
+ SString val = GetExpanded(key);
if (val.length())
- return Get(key).value();
- else
- return defaultValue;
+ return val.value();
+ return defaultValue;
}
bool isprefix(const char *target, const char *prefix) {
while (*target && *prefix) {
- if (toupper(*target) != toupper(*prefix))
+ if (*target != *prefix)
return false;
target++;
prefix++;
return true;
}
-bool issuffix(const char *target, const char *suffix) {
- int lentarget = strlen(target);
- int lensuffix = strlen(suffix);
+static bool IsSuffixCaseInsensitive(const char *target, const char *suffix) {
+ size_t lentarget = strlen(target);
+ size_t lensuffix = strlen(suffix);
if (lensuffix > lentarget)
return false;
- for (int i = lensuffix - 1; i >= 0; i--) {
- if (toupper(target[i + lentarget - lensuffix]) != toupper(suffix[i]))
+ for (int i = static_cast<int>(lensuffix) - 1; i >= 0; i--) {
+ if (MakeUpperCase(target[i + lentarget - lensuffix]) !=
+ MakeUpperCase(suffix[i]))
return false;
}
return true;
}
SString PropSet::GetWild(const char *keybase, const char *filename) {
- for (int i = 0; i < used; i += 2) {
- if (isprefix(vals[i], keybase)) {
- char *orgkeyfile = vals[i] + strlen(keybase);
- char *keyfile = NULL;
-
- if (strstr(orgkeyfile, "$(") == orgkeyfile) {
- char *cpendvar = strchr(orgkeyfile, ')');
- if (cpendvar) {
- int lenvar = cpendvar - orgkeyfile - 2; // Subtract the $()
- char *var = static_cast<char *>(malloc(lenvar + 1));
- strncpy(var, orgkeyfile + 2, lenvar);
- var[lenvar] = '\0';
- SString s = Get(var);
- free(var);
- keyfile = strdup(s.c_str());
+ for (int root = 0; root < hashRoots; root++) {
+ for (Property *p = props[root]; p; p = p->next) {
+ if (isprefix(p->key, keybase)) {
+ char * orgkeyfile = p->key + strlen(keybase);
+ char *keyfile = NULL;
+
+ if (strstr(orgkeyfile, "$(") == orgkeyfile) {
+ char *cpendvar = strchr(orgkeyfile, ')');
+ if (cpendvar) {
+ *cpendvar = '\0';
+ SString s = GetExpanded(orgkeyfile + 2);
+ *cpendvar = ')';
+ keyfile = StringDup(s.c_str());
+ }
}
- }
- char *keyptr = keyfile;
-
- if (keyfile == NULL)
- keyfile = orgkeyfile;
-
- for (; ; ) {
- char *del = strchr(keyfile, ';');
- if (del == NULL)
- del = keyfile + strlen(keyfile);
- char delchr = *del;
- *del = '\0';
- if (*keyfile == '*') {
- if (issuffix(filename, keyfile + 1)) {
+ char *keyptr = keyfile;
+
+ if (keyfile == NULL)
+ keyfile = orgkeyfile;
+
+ for (;;) {
+ char *del = strchr(keyfile, ';');
+ if (del == NULL)
+ del = keyfile + strlen(keyfile);
+ char delchr = *del;
+ *del = '\0';
+ if (*keyfile == '*') {
+ if (IsSuffixCaseInsensitive(filename, keyfile + 1)) {
+ *del = delchr;
+ delete []keyptr;
+ return p->val;
+ }
+ } else if (0 == strcmp(keyfile, filename)) {
*del = delchr;
- free(keyptr);
- return vals[i + 1];
+ delete []keyptr;
+ return p->val;
}
- } else if (EqualCaseInsensitive(keyfile, filename)) {
+ if (delchr == '\0')
+ break;
*del = delchr;
- free(keyptr);
- return vals[i + 1];
+ keyfile = del + 1;
}
- if (delchr == '\0')
- break;
- *del = delchr;
- keyfile = del + 1;
- }
- free(keyptr);
+ delete []keyptr;
- if (EqualCaseInsensitive(vals[i], keybase)) {
- return vals[i + 1];
+ if (0 == strcmp(p->key, keybase)) {
+ return p->val;
+ }
}
}
}
}
}
+// GetNewExpand does not use Expand as it has to use GetWild with the filename for each
+// variable reference found.
SString PropSet::GetNewExpand(const char *keybase, const char *filename) {
char *base = StringDup(GetWild(keybase, filename).c_str());
char *cpvar = strstr(base, "$(");
- while (cpvar) {
+ int maxExpands = 1000; // Avoid infinite expansion of recursive definitions
+ while (cpvar && (maxExpands > 0)) {
char *cpendvar = strchr(cpvar, ')');
if (cpendvar) {
int lenvar = cpendvar - cpvar - 2; // Subtract the $()
- char *var = new char[lenvar + 1];
- strncpy(var, cpvar + 2, lenvar);
- var[lenvar] = '\0';
+ char *var = StringDup(cpvar + 2, lenvar);
SString val = GetWild(var, filename);
- int newlenbase = strlen(base) + val.length() - lenvar;
+ size_t newlenbase = strlen(base) + val.length() - lenvar;
char *newbase = new char[newlenbase];
strncpy(newbase, base, cpvar - base);
strcpy(newbase + (cpvar - base), val.c_str());
base = newbase;
}
cpvar = strstr(base, "$(");
+ maxExpands--;
}
SString sret = base;
delete []base;
}
void PropSet::Clear() {
- for (int i = 0; i < used; i++) {
- delete [](vals[i]);
- vals[i] = 0;
+ for (int root = 0; root < hashRoots; root++) {
+ Property *p = props[root];
+ while (p) {
+ Property *pNext = p->next;
+ p->hash = 0;
+ delete []p->key;
+ p->key = 0;
+ delete []p->val;
+ p->val = 0;
+ delete p;
+ p = pNext;
+ }
+ props[root] = 0;
}
- used = 0;
}
-void PropSet::ReadFromMemory(const char *data, int len) {
- if (len > 0) {
- const char *pd = data;
- char linebuf[60000];
- while (GetFullLine(pd, len, linebuf, sizeof(linebuf))) {
- if (isalpha(linebuf[0]))
- Set(linebuf);
+char *PropSet::ToString() {
+ size_t len=0;
+ for (int r = 0; r < hashRoots; r++) {
+ for (Property *p = props[r]; p; p = p->next) {
+ len += strlen(p->key) + 1;
+ len += strlen(p->val) + 1;
+ }
+ }
+ if (len == 0)
+ len = 1; // Return as empty string
+ char *ret = new char [len];
+ if (ret) {
+ char *w = ret;
+ for (int root = 0; root < hashRoots; root++) {
+ for (Property *p = props[root]; p; p = p->next) {
+ strcpy(w, p->key);
+ w += strlen(p->key);
+ *w++ = '=';
+ strcpy(w, p->val);
+ w += strlen(p->val);
+ *w++ = '\n';
+ }
}
- // If there is a final line:
- if (isalpha(linebuf[0]))
- Set(linebuf);
+ ret[len-1] = '\0';
}
+ return ret;
}
-void PropSet::Read(const char *filename) {
- //printf("Opening properties <%s>\n", filename);
- Clear();
- char propsData[60000];
- FILE *rcfile = fopen(filename, "rb");
- if (rcfile) {
- int lenFile = fread(propsData, 1, sizeof(propsData), rcfile);
- fclose(rcfile);
- ReadFromMemory(propsData, lenFile);
- } else {
- //printf("Could not open <%s>\n", filename);
+/**
+ * Initiate enumeration.
+ */
+bool PropSet::GetFirst(char **key, char **val) {
+ for (int i = 0; i < hashRoots; i++) {
+ for (Property *p = props[i]; p; p = p->next) {
+ if (p) {
+ *key = p->key;
+ *val = p->val;
+ enumnext = p->next; // GetNext will begin here ...
+ enumhash = i; // ... in this block
+ return true;
+ }
+ }
}
+ return false;
}
-static bool iswordsep(char ch, bool onlyLineEnds) {
- if (!isspace(ch))
- return false;
- if (!onlyLineEnds)
- return true;
- return ch == '\r' || ch == '\n';
+/**
+ * Continue enumeration.
+ */
+bool PropSet::GetNext(char ** key, char ** val) {
+ bool firstloop = true;
+
+ // search begins where we left it : in enumhash block
+ for (int i = enumhash; i < hashRoots; i++) {
+ if (!firstloop)
+ enumnext = props[i]; // Begin with first property in block
+ // else : begin where we left
+ firstloop = false;
+
+ for (Property *p = enumnext; p; p = p->next) {
+ if (p) {
+ *key = p->key;
+ *val = p->val;
+ enumnext = p->next; // for GetNext
+ enumhash = i;
+ return true;
+ }
+ }
+ }
+ return false;
}
-// Creates an array that points into each word in the string and puts \0 terminators
-// after each word.
-static char **ArrayFromWordList(char *wordlist, bool onlyLineEnds = false) {
- char prev = '\n';
+/**
+ * Creates an array that points into each word in the string and puts \0 terminators
+ * after each word.
+ */
+static char **ArrayFromWordList(char *wordlist, int *len, bool onlyLineEnds = false) {
+ int prev = '\n';
int words = 0;
+ // For rapid determination of whether a character is a separator, build
+ // a look up table.
+ bool wordSeparator[256];
+ for (int i=0;i<256; i++) {
+ wordSeparator[i] = false;
+ }
+ wordSeparator['\r'] = true;
+ wordSeparator['\n'] = true;
+ if (!onlyLineEnds) {
+ wordSeparator[' '] = true;
+ wordSeparator['\t'] = true;
+ }
for (int j = 0; wordlist[j]; j++) {
- if (!iswordsep(wordlist[j], onlyLineEnds) && iswordsep(prev, onlyLineEnds))
+ int curr = static_cast<unsigned char>(wordlist[j]);
+ if (!wordSeparator[curr] && wordSeparator[prev])
words++;
- prev = wordlist[j];
+ prev = curr;
}
- char **keywords = new char * [words + 1];
+ char **keywords = new char *[words + 1];
if (keywords) {
words = 0;
prev = '\0';
- int len = strlen(wordlist);
- for (int k = 0; k < len; k++) {
- if (!iswordsep(wordlist[k], onlyLineEnds)) {
+ size_t slen = strlen(wordlist);
+ for (size_t k = 0; k < slen; k++) {
+ if (!wordSeparator[static_cast<unsigned char>(wordlist[k])]) {
if (!prev) {
keywords[words] = &wordlist[k];
words++;
}
prev = wordlist[k];
}
- keywords[words] = &wordlist[len];
+ keywords[words] = &wordlist[slen];
+ *len = words;
+ } else {
+ *len = 0;
}
return keywords;
}
void WordList::Clear() {
if (words) {
- delete []words;
delete []list;
+ delete []words;
+ delete []wordsNoCase;
}
words = 0;
+ wordsNoCase = 0;
list = 0;
len = 0;
+ sorted = false;
}
void WordList::Set(const char *s) {
- len = 0;
list = StringDup(s);
- words = ArrayFromWordList(list, onlyLineEnds);
+ sorted = false;
+ words = ArrayFromWordList(list, &len, onlyLineEnds);
+ wordsNoCase = new char * [len + 1];
+ memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
}
char *WordList::Allocate(int size) {
}
void WordList::SetFromAllocated() {
- len = 0;
- words = ArrayFromWordList(list, onlyLineEnds);
-}
-
-// Shell sort based upon public domain C implementation by Raymond Gardner 1991
-// Used here because of problems with mingw qsort.
-static void SortWordList(char **words, unsigned int len) {
- unsigned int gap = len / 2;
-
- while (gap > 0) {
- unsigned int i = gap;
- while (i < len) {
- unsigned int j = i;
- char **a = words + j;
- do {
- j -= gap;
- char **b = a;
- a -= gap;
- if (strcmp(*a, *b) > 0) {
- char *tmp = *a;
- *a = *b;
- *b = tmp;
- } else {
- break;
- }
- } while (j >= gap);
- i++;
- }
- gap = gap / 2;
- }
+ sorted = false;
+ words = ArrayFromWordList(list, &len, onlyLineEnds);
+ wordsNoCase = new char * [len + 1];
+ memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
+}
+
+int cmpString(const void *a1, const void *a2) {
+ // Can't work out the correct incantation to use modern casts here
+ return strcmp(*(char**)(a1), *(char**)(a2));
+}
+
+int cmpStringNoCase(const void *a1, const void *a2) {
+ // Can't work out the correct incantation to use modern casts here
+ return CompareCaseInsensitive(*(char**)(a1), *(char**)(a2));
+}
+
+static void SortWordList(char **words, char **wordsNoCase, unsigned int len) {
+ qsort(reinterpret_cast<void*>(words), len, sizeof(*words),
+ cmpString);
+ qsort(reinterpret_cast<void*>(wordsNoCase), len, sizeof(*wordsNoCase),
+ cmpStringNoCase);
}
bool WordList::InList(const char *s) {
if (0 == words)
return false;
- if (len == 0) {
- for (int i = 0; words[i][0]; i++)
- len++;
- SortWordList(words, len);
- for (int k = 0; k < (sizeof(starts) / sizeof(starts[0])); k++)
+ if (!sorted) {
+ sorted = true;
+ SortWordList(words, wordsNoCase, len);
+ for (unsigned int k = 0; k < (sizeof(starts) / sizeof(starts[0])); k++)
starts[k] = -1;
for (int l = len - 1; l >= 0; l--) {
unsigned char indexChar = words[l][0];
j++;
}
}
+ j = starts['^'];
+ if (j >= 0) {
+ while (words[j][0] == '^') {
+ const char *a = words[j] + 1;
+ const char *b = s;
+ while (*a && *a == *b) {
+ a++;
+ b++;
+ }
+ if (!*a)
+ return true;
+ j++;
+ }
+ }
return false;
}
+
+/**
+ * Returns an element (complete) of the wordlist array which has
+ * the same beginning as the passed string.
+ * The length of the word to compare is passed too.
+ * Letter case can be ignored or preserved (default).
+ */
+const char *WordList::GetNearestWord(const char *wordStart, int searchLen /*= -1*/, bool ignoreCase /*= false*/, SString wordCharacters /*='/0' */, int wordIndex /*= -1 */) {
+ int start = 0; // lower bound of the api array block to search
+ int end = len - 1; // upper bound of the api array block to search
+ int pivot; // index of api array element just being compared
+ int cond; // comparison result (in the sense of strcmp() result)
+ const char *word; // api array element just being compared
+
+ if (0 == words)
+ return NULL;
+ if (!sorted) {
+ sorted = true;
+ SortWordList(words, wordsNoCase, len);
+ }
+ if (ignoreCase) {
+ while (start <= end) { // binary searching loop
+ pivot = (start + end) >> 1;
+ word = wordsNoCase[pivot];
+ cond = CompareNCaseInsensitive(wordStart, word, searchLen);
+ if (!cond && (!wordCharacters.contains(word[searchLen]))) {
+ // Found a word in a binary fashion. Now checks if a specific index was requested
+ if (wordIndex < 0)
+ return word; // result must not be freed with free()
+
+ // Finds first word in a series of equal words
+ int first = pivot;
+ end = pivot - 1;
+ while (start <= end) {
+ pivot = (start + end) >> 1;
+ word = wordsNoCase[pivot];
+ cond = CompareNCaseInsensitive(wordStart, word, searchLen);
+ if (!cond && (!wordCharacters.contains(word[searchLen]))) {
+ // Found another word
+ first = pivot;
+ end = pivot - 1;
+ }
+ else if (cond > 0)
+ start = pivot + 1;
+ else if (cond <= 0)
+ break;
+ }
+
+ // Gets the word at the requested index
+ word = wordsNoCase[first + wordIndex];
+ return word;
+ }
+ else if (cond > 0)
+ start = pivot + 1;
+ else if (cond <= 0)
+ end = pivot - 1;
+ }
+ } else { // preserve the letter case
+ while (start <= end) { // binary searching loop
+ pivot = (start + end) >> 1;
+ word = words[pivot];
+ cond = strncmp(wordStart, word, searchLen);
+ if (!cond && (!wordCharacters.contains(word[searchLen]))) {
+ // Found a word in a binary fashion. Now checks if a specific index was requested
+ if (wordIndex < 0)
+ return word; // result must not be freed with free()
+
+ // Finds first word in a series of equal words
+ int first = pivot;
+ end = pivot - 1;
+ while (start <= end) {
+ pivot = (start + end) >> 1;
+ word = words[pivot];
+ cond = strncmp(wordStart, word, searchLen);
+ if (!cond && (!wordCharacters.contains(word[searchLen]))) {
+ // Found another word
+ first = pivot;
+ end = pivot - 1;
+ }
+ else if (cond > 0)
+ start = pivot + 1;
+ else if (cond <= 0)
+ break;
+ }
+
+ // Gets the word at the requested index
+ word = words[first + wordIndex];
+ return word;
+ }
+ else if (cond > 0)
+ start = pivot + 1;
+ else if (cond <= 0)
+ end = pivot - 1;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Find the length of a 'word' which is actually an identifier in a string
+ * which looks like "identifier(..." or "identifier:" or "identifier" and where
+ * there may be extra spaces after the identifier that should not be
+ * counted in the length.
+ */
+static unsigned int LengthWord(const char *word, char otherSeparator) {
+ // Find a '(', or ':'. If that fails go to the end of the string.
+ const char *endWord = strchr(word, '(');
+ if (!endWord)
+ endWord = strchr(word, ':');
+ if (!endWord && otherSeparator)
+ endWord = strchr(word, otherSeparator);
+ if (!endWord)
+ endWord = word + strlen(word);
+ // Last case always succeeds so endWord != 0
+
+ // Drop any space characters.
+ if (endWord > word) {
+ endWord--; // Back from the '(', ':', or '\0'
+ // Move backwards over any spaces
+ while ((endWord > word) && (IsASpace(*endWord))) {
+ endWord--;
+ }
+ }
+ return endWord - word;
+}
+
+/**
+ * Returns elements (first words of them) of the wordlist array which have
+ * the same beginning as the passed string.
+ * The length of the word to compare is passed too.
+ * Letter case can be ignored or preserved (default).
+ * If there are more words meeting the condition they are returned all of
+ * them in the ascending order separated with spaces.
+ *
+ * NOTE: returned buffer has to be freed with delete[].
+ */
+char *WordList::GetNearestWords(
+ const char *wordStart,
+ int searchLen /*= -1*/,
+ bool ignoreCase /*= false*/,
+ char otherSeparator /*= '\0'*/,
+ bool exactLen /*=false*/) {
+ unsigned int wordlen; // length of the word part (before the '(' brace) of the api array element
+ SString wordsNear;
+ wordsNear.setsizegrowth(1000);
+ int start = 0; // lower bound of the api array block to search
+ int end = len - 1; // upper bound of the api array block to search
+ int pivot; // index of api array element just being compared
+ int cond; // comparison result (in the sense of strcmp() result)
+
+ if (0 == words)
+ return NULL;
+ if (!sorted) {
+ sorted = true;
+ SortWordList(words, wordsNoCase, len);
+ }
+ if (ignoreCase) {
+ while (start <= end) { // Binary searching loop
+ pivot = (start + end) / 2;
+ cond = CompareNCaseInsensitive(wordStart, wordsNoCase[pivot], searchLen);
+ if (!cond) {
+ // Find first match
+ while ((pivot > start) &&
+ (0 == CompareNCaseInsensitive(wordStart,
+ wordsNoCase[pivot-1], searchLen))) {
+ --pivot;
+ }
+ // Grab each match
+ while ((pivot <= end) &&
+ (0 == CompareNCaseInsensitive(wordStart,
+ wordsNoCase[pivot], searchLen))) {
+ wordlen = LengthWord(wordsNoCase[pivot], otherSeparator) + 1;
+ if (exactLen && wordlen != LengthWord(wordStart, otherSeparator) + 1)
+ break;
+ wordsNear.append(wordsNoCase[pivot], wordlen, ' ');
+ ++pivot;
+ }
+ return wordsNear.detach();
+ } else if (cond < 0) {
+ end = pivot - 1;
+ } else if (cond > 0) {
+ start = pivot + 1;
+ }
+ }
+ } else { // Preserve the letter case
+ while (start <= end) { // Binary searching loop
+ pivot = (start + end) / 2;
+ cond = strncmp(wordStart, words[pivot], searchLen);
+ if (!cond) {
+ // Find first match
+ while ((pivot > start) &&
+ (0 == strncmp(wordStart,
+ words[pivot-1], searchLen))) {
+ --pivot;
+ }
+ // Grab each match
+ while ((pivot <= end) &&
+ (0 == strncmp(wordStart,
+ words[pivot], searchLen))) {
+ wordlen = LengthWord(words[pivot], otherSeparator) + 1;
+ if (exactLen && wordlen != LengthWord(wordStart, otherSeparator) + 1)
+ break;
+ wordsNear.append(words[pivot], wordlen, ' ');
+ ++pivot;
+ }
+ return wordsNear.detach();
+ } else if (cond < 0) {
+ end = pivot - 1;
+ } else if (cond > 0) {
+ start = pivot + 1;
+ }
+ }
+ }
+ return NULL;
+}