You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1171 lines
29 KiB
1171 lines
29 KiB
13 years ago
|
// SciTE - Scintilla based Text Editor
|
||
|
/** @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 <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) {
|
||
|
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 = sSize_;
|
||
|
} else {
|
||
|
sSize = sLen = 0;
|
||
|
}
|
||
|
}
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
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
|
||
|
|
||
|
bool PropSet::caseSensitiveFilenames = false;
|
||
|
|
||
|
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, 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 [](p->val);
|
||
|
p->val = StringDup(val, lenVal);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
// Not found
|
||
|
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::Unset(const char *key, int lenKey) {
|
||
|
if (!*key) // Empty keys are not supported
|
||
|
return;
|
||
|
if (lenKey == -1)
|
||
|
lenKey = static_cast<int>(strlen(key));
|
||
|
unsigned int hash = HashString(key, lenKey);
|
||
|
Property *pPrev = NULL;
|
||
|
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)))) {
|
||
|
if (pPrev)
|
||
|
pPrev->next = p->next;
|
||
|
else
|
||
|
props[hash % hashRoots] = p->next;
|
||
|
if (p == enumnext)
|
||
|
enumnext = p->next; // Not that anyone should mix enum and Set / Unset.
|
||
|
delete [](p->key);
|
||
|
delete [](p->val);
|
||
|
delete p;
|
||
|
return;
|
||
|
} else {
|
||
|
pPrev = p;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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) {
|
||
|
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) {
|
||
|
// Failed here, so try in base property set
|
||
|
return superPS->Get(key);
|
||
|
} else {
|
||
|
return "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
|
||
|
// There is some inconsistency between GetExpanded("foo") and Expand("$(foo)").
|
||
|
// A solution is to keep a stack of variables that have been expanded, so that
|
||
|
// recursive expansions can be skipped. For now I'll just use the C++ stack
|
||
|
// for that, through a recursive function and a simple chain of pointers.
|
||
|
|
||
|
struct VarChain {
|
||
|
VarChain(const char*var_=NULL, const VarChain *link_=NULL): var(var_), link(link_) {}
|
||
|
|
||
|
bool contains(const char *testVar) const {
|
||
|
return (var && (0 == strcmp(var, testVar)))
|
||
|
|| (link && link->contains(testVar));
|
||
|
}
|
||
|
|
||
|
const char *var;
|
||
|
const VarChain *link;
|
||
|
};
|
||
|
|
||
|
static int ExpandAllInPlace(PropSet &props, SString &withVars, int maxExpands, const VarChain &blankVars = VarChain()) {
|
||
|
int varStart = withVars.search("$(");
|
||
|
while ((varStart >= 0) && (maxExpands > 0)) {
|
||
|
int varEnd = withVars.search(")", varStart+2);
|
||
|
if (varEnd < 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// For consistency, when we see '$(ab$(cde))', expand the inner variable first,
|
||
|
// regardless whether there is actually a degenerate variable named 'ab$(cde'.
|
||
|
int innerVarStart = withVars.search("$(", varStart+2);
|
||
|
while ((innerVarStart > varStart) && (innerVarStart < varEnd)) {
|
||
|
varStart = innerVarStart;
|
||
|
innerVarStart = withVars.search("$(", varStart+2);
|
||
|
}
|
||
|
|
||
|
SString var(withVars.c_str(), varStart + 2, varEnd);
|
||
|
SString val = props.Get(var.c_str());
|
||
|
|
||
|
if (blankVars.contains(var.c_str())) {
|
||
|
val.clear(); // treat blankVar as an empty string (e.g. to block self-reference)
|
||
|
}
|
||
|
|
||
|
if (--maxExpands >= 0) {
|
||
|
maxExpands = ExpandAllInPlace(props, val, maxExpands, VarChain(var.c_str(), &blankVars));
|
||
|
}
|
||
|
|
||
|
withVars.remove(varStart, varEnd-varStart+1);
|
||
|
withVars.insert(varStart, val.c_str(), val.length());
|
||
|
|
||
|
varStart = withVars.search("$(");
|
||
|
}
|
||
|
|
||
|
return maxExpands;
|
||
|
}
|
||
|
|
||
|
|
||
|
SString PropSet::GetExpanded(const char *key) {
|
||
|
SString val = Get(key);
|
||
|
ExpandAllInPlace(*this, val, 100, VarChain(key));
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
SString PropSet::Expand(const char *withVars, int maxExpands) {
|
||
|
SString val = withVars;
|
||
|
ExpandAllInPlace(*this, val, maxExpands);
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
int PropSet::GetInt(const char *key, int defaultValue) {
|
||
|
SString val = GetExpanded(key);
|
||
|
if (val.length())
|
||
|
return val.value();
|
||
|
return defaultValue;
|
||
|
}
|
||
|
|
||
|
bool isprefix(const char *target, const char *prefix) {
|
||
|
while (*target && *prefix) {
|
||
|
if (*target != *prefix)
|
||
|
return false;
|
||
|
target++;
|
||
|
prefix++;
|
||
|
}
|
||
|
if (*prefix)
|
||
|
return false;
|
||
|
else
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool IsSuffix(const char *target, const char *suffix, bool caseSensitive) {
|
||
|
size_t lentarget = strlen(target);
|
||
|
size_t lensuffix = strlen(suffix);
|
||
|
if (lensuffix > lentarget)
|
||
|
return false;
|
||
|
if (caseSensitive) {
|
||
|
for (int i = static_cast<int>(lensuffix) - 1; i >= 0; i--) {
|
||
|
if (target[i + lentarget - lensuffix] != suffix[i])
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
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 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, caseSensitiveFilenames)) {
|
||
|
*del = delchr;
|
||
|
delete []keyptr;
|
||
|
return p->val;
|
||
|
}
|
||
|
} else if (0 == strcmp(keyfile, filename)) {
|
||
|
*del = delchr;
|
||
|
delete []keyptr;
|
||
|
return p->val;
|
||
|
}
|
||
|
if (delchr == '\0')
|
||
|
break;
|
||
|
*del = delchr;
|
||
|
keyfile = del + 1;
|
||
|
}
|
||
|
delete []keyptr;
|
||
|
|
||
|
if (0 == strcmp(p->key, keybase)) {
|
||
|
return p->val;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (superPS) {
|
||
|
// Failed here, so try in base property set
|
||
|
return superPS->GetWild(keybase, filename);
|
||
|
} else {
|
||
|
return "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// 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, "$(");
|
||
|
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 = StringDup(cpvar + 2, lenvar);
|
||
|
SString val = GetWild(var, filename);
|
||
|
if (0 == strcmp(var, keybase))
|
||
|
val.clear(); // Self-references evaluate to empty string
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
void PropSet::Clear() {
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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';
|
||
|
}
|
||
|
}
|
||
|
ret[len-1] = '\0';
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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, 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++) {
|
||
|
int curr = static_cast<unsigned char>(wordlist[j]);
|
||
|
if (!wordSeparator[curr] && wordSeparator[prev])
|
||
|
words++;
|
||
|
prev = curr;
|
||
|
}
|
||
|
char **keywords = new char *[words + 1];
|
||
|
if (keywords) {
|
||
|
words = 0;
|
||
|
prev = '\0';
|
||
|
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++;
|
||
|
}
|
||
|
} else {
|
||
|
wordlist[k] = '\0';
|
||
|
}
|
||
|
prev = wordlist[k];
|
||
|
}
|
||
|
keywords[words] = &wordlist[slen];
|
||
|
*len = words;
|
||
|
} else {
|
||
|
*len = 0;
|
||
|
}
|
||
|
return keywords;
|
||
|
}
|
||
|
|
||
|
void WordList::Clear() {
|
||
|
if (words) {
|
||
|
delete []list;
|
||
|
delete []words;
|
||
|
delete []wordsNoCase;
|
||
|
}
|
||
|
words = 0;
|
||
|
wordsNoCase = 0;
|
||
|
list = 0;
|
||
|
len = 0;
|
||
|
sorted = false;
|
||
|
sortedNoCase = false;
|
||
|
}
|
||
|
|
||
|
void WordList::Set(const char *s) {
|
||
|
list = StringDup(s);
|
||
|
sorted = false;
|
||
|
sortedNoCase = false;
|
||
|
words = ArrayFromWordList(list, &len, onlyLineEnds);
|
||
|
wordsNoCase = new char * [len + 1];
|
||
|
memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
|
||
|
}
|
||
|
|
||
|
char *WordList::Allocate(int size) {
|
||
|
list = new char[size + 1];
|
||
|
list[size] = '\0';
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
void WordList::SetFromAllocated() {
|
||
|
sorted = false;
|
||
|
sortedNoCase = false;
|
||
|
words = ArrayFromWordList(list, &len, onlyLineEnds);
|
||
|
wordsNoCase = new char * [len + 1];
|
||
|
memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
|
||
|
}
|
||
|
|
||
|
extern "C" 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));
|
||
|
}
|
||
|
|
||
|
extern "C" 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, unsigned int len) {
|
||
|
qsort(reinterpret_cast<void*>(words), len, sizeof(*words),
|
||
|
cmpString);
|
||
|
}
|
||
|
|
||
|
static void SortWordListNoCase(char **wordsNoCase, unsigned int len) {
|
||
|
qsort(reinterpret_cast<void*>(wordsNoCase), len, sizeof(*wordsNoCase),
|
||
|
cmpStringNoCase);
|
||
|
}
|
||
|
|
||
|
bool WordList::InList(const char *s) {
|
||
|
if (0 == words)
|
||
|
return false;
|
||
|
if (!sorted) {
|
||
|
sorted = true;
|
||
|
SortWordList(words, 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];
|
||
|
starts[indexChar] = l;
|
||
|
}
|
||
|
}
|
||
|
unsigned char firstChar = s[0];
|
||
|
int j = starts[firstChar];
|
||
|
if (j >= 0) {
|
||
|
while (words[j][0] == firstChar) {
|
||
|
if (s[1] == words[j][1]) {
|
||
|
const char *a = words[j] + 1;
|
||
|
const char *b = s + 1;
|
||
|
while (*a && *a == *b) {
|
||
|
a++;
|
||
|
b++;
|
||
|
}
|
||
|
if (!*a && !*b)
|
||
|
return true;
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/** similar to InList, but word s can be a substring of keyword.
|
||
|
* eg. the keyword define is defined as def~ine. This means the word must start
|
||
|
* with def to be a keyword, but also defi, defin and define are valid.
|
||
|
* The marker is ~ in this case.
|
||
|
*/
|
||
|
bool WordList::InListAbbreviated(const char *s, const char marker) {
|
||
|
if (0 == words)
|
||
|
return false;
|
||
|
if (!sorted) {
|
||
|
sorted = true;
|
||
|
SortWordList(words, 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];
|
||
|
starts[indexChar] = l;
|
||
|
}
|
||
|
}
|
||
|
unsigned char firstChar = s[0];
|
||
|
int j = starts[firstChar];
|
||
|
if (j >= 0) {
|
||
|
while (words[j][0] == firstChar) {
|
||
|
bool isSubword = false;
|
||
|
int start = 1;
|
||
|
if (words[j][1] == marker) {
|
||
|
isSubword = true;
|
||
|
start++;
|
||
|
}
|
||
|
if (s[1] == words[j][start]) {
|
||
|
const char *a = words[j] + start;
|
||
|
const char *b = s + 1;
|
||
|
while (*a && *a == *b) {
|
||
|
a++;
|
||
|
if (*a == marker) {
|
||
|
isSubword = true;
|
||
|
a++;
|
||
|
}
|
||
|
b++;
|
||
|
}
|
||
|
if ((!*a || isSubword) && !*b)
|
||
|
return true;
|
||
|
}
|
||
|
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, 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 (ignoreCase) {
|
||
|
if (!sortedNoCase) {
|
||
|
sortedNoCase = true;
|
||
|
SortWordListNoCase(wordsNoCase, len);
|
||
|
}
|
||
|
while (start <= end) { // binary searching loop
|
||
|
pivot = (start + end) >> 1;
|
||
|
word = wordsNoCase[pivot];
|
||
|
cond = CompareNCaseInsensitive(wordStart, word, searchLen);
|
||
|
if (!cond) {
|
||
|
// find first word
|
||
|
start = pivot;
|
||
|
while (start > 0 && !CompareNCaseInsensitive(wordStart, wordsNoCase[start-1], searchLen)) {
|
||
|
start--;
|
||
|
}
|
||
|
// find last word
|
||
|
end = pivot;
|
||
|
while (end < len-1 && !CompareNCaseInsensitive(wordStart, wordsNoCase[end+1], searchLen)) {
|
||
|
end++;
|
||
|
}
|
||
|
|
||
|
// Finds first word in a series of equal words
|
||
|
for (pivot = start; pivot <= end; pivot++) {
|
||
|
word = wordsNoCase[pivot];
|
||
|
if (!wordCharacters.contains(word[searchLen])) {
|
||
|
if (wordIndex <= 0) // Checks if a specific index was requested
|
||
|
return word; // result must not be freed with free()
|
||
|
wordIndex--;
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
else if (cond > 0)
|
||
|
start = pivot + 1;
|
||
|
else if (cond < 0)
|
||
|
end = pivot - 1;
|
||
|
}
|
||
|
} else { // preserve the letter case
|
||
|
if (!sorted) {
|
||
|
sorted = true;
|
||
|
SortWordList(words, len);
|
||
|
}
|
||
|
while (start <= end) { // binary searching loop
|
||
|
pivot = (start + end) >> 1;
|
||
|
word = words[pivot];
|
||
|
cond = strncmp(wordStart, word, searchLen);
|
||
|
if (!cond) {
|
||
|
// find first word
|
||
|
start = pivot;
|
||
|
while (start > 0 && !strncmp(wordStart, words[start-1], searchLen)) {
|
||
|
start--;
|
||
|
}
|
||
|
// find last word
|
||
|
end = pivot;
|
||
|
while (end < len-1 && !strncmp(wordStart, words[end+1], searchLen)) {
|
||
|
end++;
|
||
|
}
|
||
|
|
||
|
// Finds first word in a series of equal words
|
||
|
pivot = start;
|
||
|
while (pivot <= end) {
|
||
|
word = words[pivot];
|
||
|
if (!wordCharacters.contains(word[searchLen])) {
|
||
|
if (wordIndex <= 0) // Checks if a specific index was requested
|
||
|
return word; // result must not be freed with free()
|
||
|
wordIndex--;
|
||
|
}
|
||
|
pivot++;
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
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" 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 '('. If that fails go to the end of the string.
|
||
|
const char *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 '(', otherSeparator, 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,
|
||
|
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 (ignoreCase) {
|
||
|
if (!sortedNoCase) {
|
||
|
sortedNoCase = true;
|
||
|
SortWordListNoCase(wordsNoCase, len);
|
||
|
}
|
||
|
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;
|
||
|
++pivot;
|
||
|
if (exactLen && wordlen != LengthWord(wordStart, otherSeparator) + 1)
|
||
|
continue;
|
||
|
wordsNear.append(wordsNoCase[pivot-1], wordlen, ' ');
|
||
|
}
|
||
|
return wordsNear.detach();
|
||
|
} else if (cond < 0) {
|
||
|
end = pivot - 1;
|
||
|
} else if (cond > 0) {
|
||
|
start = pivot + 1;
|
||
|
}
|
||
|
}
|
||
|
} else { // Preserve the letter case
|
||
|
if (!sorted) {
|
||
|
sorted = true;
|
||
|
SortWordList(words, len);
|
||
|
}
|
||
|
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;
|
||
|
++pivot;
|
||
|
if (exactLen && wordlen != LengthWord(wordStart, otherSeparator) + 1)
|
||
|
continue;
|
||
|
wordsNear.append(words[pivot-1], wordlen, ' ');
|
||
|
}
|
||
|
return wordsNear.detach();
|
||
|
} else if (cond < 0) {
|
||
|
end = pivot - 1;
|
||
|
} else if (cond > 0) {
|
||
|
start = pivot + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|