]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/stc/scintilla/src/PropSet.cxx
e97a7f7bef439732c831fab3106c291096b7bb70
[wxWidgets.git] / contrib / src / stc / scintilla / src / PropSet.cxx
1 // SciTE - Scintilla based Text Editor
2 /** @file PropSet.cxx
3 ** A Java style properties file module.
4 **/
5 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7
8 // Maintain a dictionary of properties
9
10 #include <stdlib.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include <stdio.h>
14
15 #include "Platform.h"
16
17 #include "PropSet.h"
18
19 // The comparison and case changing functions here assume ASCII
20 // or extended ASCII such as the normal Windows code page.
21
22 static inline char MakeUpperCase(char ch) {
23 if (ch < 'a' || ch > 'z')
24 return ch;
25 else
26 return static_cast<char>(ch - 'a' + 'A');
27 }
28
29 static inline bool IsLetter(char ch) {
30 return ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'));
31 }
32
33
34 int CompareCaseInsensitive(const char *a, const char *b) {
35 while (*a && *b) {
36 if (*a != *b) {
37 char upperA = MakeUpperCase(*a);
38 char upperB = MakeUpperCase(*b);
39 if (upperA != upperB)
40 return upperA - upperB;
41 }
42 a++;
43 b++;
44 }
45 // Either *a or *b is nul
46 return *a - *b;
47 }
48
49 int CompareNCaseInsensitive(const char *a, const char *b, int len) {
50 while (*a && *b && len) {
51 if (*a != *b) {
52 char upperA = MakeUpperCase(*a);
53 char upperB = MakeUpperCase(*b);
54 if (upperA != upperB)
55 return upperA - upperB;
56 }
57 a++;
58 b++;
59 len--;
60 }
61 if (len == 0)
62 return 0;
63 else
64 // Either *a or *b is nul
65 return *a - *b;
66 }
67
68
69 bool EqualCaseInsensitive(const char *a, const char *b) {
70 return 0 == CompareCaseInsensitive(a, b);
71 }
72
73 inline unsigned int HashString(const char *s, int len) {
74 unsigned int ret = 0;
75 while (len--) {
76 ret <<= 4;
77 ret ^= *s;
78 s++;
79 }
80 return ret;
81 }
82
83 PropSet::PropSet() {
84 superPS = 0;
85 for (int root = 0; root < hashRoots; root++)
86 props[root] = 0;
87 }
88
89 PropSet::~PropSet() {
90 superPS = 0;
91 Clear();
92 }
93
94 void PropSet::Set(const char *key, const char *val, int lenKey, int lenVal) {
95 if (!*key) // Empty keys are not supported
96 return;
97 if (lenKey == -1)
98 lenKey = strlen(key);
99 if (lenVal == -1)
100 lenVal = strlen(val);
101 unsigned int hash = HashString(key, lenKey);
102 for (Property *p = props[hash % hashRoots]; p; p = p->next) {
103 if ((hash == p->hash) &&
104 ((strlen(p->key) == static_cast<unsigned int>(lenKey)) &&
105 (0 == strncmp(p->key, key, lenKey)))) {
106 // Replace current value
107 delete [](p->val);
108 p->val = StringDup(val, lenVal);
109 return ;
110 }
111 }
112 // Not found
113 Property *pNew = new Property;
114 if (pNew) {
115 pNew->hash = hash;
116 pNew->key = StringDup(key, lenKey);
117 pNew->val = StringDup(val, lenVal);
118 pNew->next = props[hash % hashRoots];
119 props[hash % hashRoots] = pNew;
120 }
121 }
122
123 void PropSet::Set(const char *keyVal) {
124 while (isspace(*keyVal))
125 keyVal++;
126 const char *endVal = keyVal;
127 while (*endVal && (*endVal != '\n'))
128 endVal++;
129 const char *eqAt = strchr(keyVal, '=');
130 if (eqAt) {
131 Set(keyVal, eqAt + 1, eqAt-keyVal, endVal - eqAt - 1);
132 } else if (*keyVal) { // No '=' so assume '=1'
133 Set(keyVal, "1", endVal-keyVal, 1);
134 }
135 }
136
137 void PropSet::SetMultiple(const char *s) {
138 const char *eol = strchr(s, '\n');
139 while (eol) {
140 Set(s);
141 s = eol + 1;
142 eol = strchr(s, '\n');
143 }
144 Set(s);
145 }
146
147 SString PropSet::Get(const char *key) {
148 unsigned int hash = HashString(key, strlen(key));
149 for (Property *p = props[hash % hashRoots]; p; p = p->next) {
150 if ((hash == p->hash) && (0 == strcmp(p->key, key))) {
151 return p->val;
152 }
153 }
154 if (superPS) {
155 // Failed here, so try in base property set
156 return superPS->Get(key);
157 } else {
158 return "";
159 }
160 }
161
162 static bool IncludesVar(const char *value, const char *key) {
163 const char *var = strstr(value, "$(");
164 while (var) {
165 if (isprefix(var + 2, key) && (var[2 + strlen(key)] == ')')) {
166 // Found $(key) which would lead to an infinite loop so exit
167 return true;
168 }
169 var = strstr(var + 2, ")");
170 if (var)
171 var = strstr(var + 1, "$(");
172 }
173 return false;
174 }
175
176 SString PropSet::GetExpanded(const char *key) {
177 SString val = Get(key);
178 if (IncludesVar(val.c_str(), key))
179 return val;
180 return Expand(val.c_str());
181 }
182
183 SString PropSet::Expand(const char *withVars) {
184 char *base = StringDup(withVars);
185 char *cpvar = strstr(base, "$(");
186 while (cpvar) {
187 char *cpendvar = strchr(cpvar, ')');
188 if (cpendvar) {
189 int lenvar = cpendvar - cpvar - 2; // Subtract the $()
190 char *var = StringDup(cpvar + 2, lenvar);
191 SString val = GetExpanded(var);
192 int newlenbase = strlen(base) + val.length() - lenvar;
193 char *newbase = new char[newlenbase];
194 strncpy(newbase, base, cpvar - base);
195 strcpy(newbase + (cpvar - base), val.c_str());
196 strcpy(newbase + (cpvar - base) + val.length(), cpendvar + 1);
197 delete []var;
198 delete []base;
199 base = newbase;
200 }
201 cpvar = strstr(base, "$(");
202 }
203 SString sret = base;
204 delete []base;
205 return sret;
206 }
207
208 int PropSet::GetInt(const char *key, int defaultValue) {
209 SString val = GetExpanded(key);
210 if (val.length())
211 return val.value();
212 return defaultValue;
213 }
214
215 bool isprefix(const char *target, const char *prefix) {
216 while (*target && *prefix) {
217 if (*target != *prefix)
218 return false;
219 target++;
220 prefix++;
221 }
222 if (*prefix)
223 return false;
224 else
225 return true;
226 }
227
228 static bool IsSuffixCaseInsensitive(const char *target, const char *suffix) {
229 int lentarget = strlen(target);
230 int lensuffix = strlen(suffix);
231 if (lensuffix > lentarget)
232 return false;
233 for (int i = lensuffix - 1; i >= 0; i--) {
234 if (MakeUpperCase(target[i + lentarget - lensuffix]) !=
235 MakeUpperCase(suffix[i]))
236 return false;
237 }
238 return true;
239 }
240
241 SString PropSet::GetWild(const char *keybase, const char *filename) {
242 for (int root = 0; root < hashRoots; root++) {
243 for (Property *p = props[root]; p; p = p->next) {
244 if (isprefix(p->key, keybase)) {
245 char * orgkeyfile = p->key + strlen(keybase);
246 char *keyfile = NULL;
247
248 if (strstr(orgkeyfile, "$(") == orgkeyfile) {
249 char *cpendvar = strchr(orgkeyfile, ')');
250 if (cpendvar) {
251 *cpendvar = '\0';
252 SString s = GetExpanded(orgkeyfile + 2);
253 *cpendvar = ')';
254 keyfile = StringDup(s.c_str());
255 }
256 }
257 char *keyptr = keyfile;
258
259 if (keyfile == NULL)
260 keyfile = orgkeyfile;
261
262 for (; ; ) {
263 char *del = strchr(keyfile, ';');
264 if (del == NULL)
265 del = keyfile + strlen(keyfile);
266 char delchr = *del;
267 *del = '\0';
268 if (*keyfile == '*') {
269 if (IsSuffixCaseInsensitive(filename, keyfile + 1)) {
270 *del = delchr;
271 delete []keyptr;
272 return p->val;
273 }
274 } else if (0 == strcmp(keyfile, filename)) {
275 *del = delchr;
276 delete []keyptr;
277 return p->val;
278 }
279 if (delchr == '\0')
280 break;
281 *del = delchr;
282 keyfile = del + 1;
283 }
284 delete []keyptr;
285
286 if (0 == strcmp(p->key, keybase)) {
287 return p->val;
288 }
289 }
290 }
291 }
292 if (superPS) {
293 // Failed here, so try in base property set
294 return superPS->GetWild(keybase, filename);
295 } else {
296 return "";
297 }
298 }
299
300 // GetNewExpand does not use Expand as it has to use GetWild with the filename for each
301 // variable reference found.
302 SString PropSet::GetNewExpand(const char *keybase, const char *filename) {
303 char *base = StringDup(GetWild(keybase, filename).c_str());
304 char *cpvar = strstr(base, "$(");
305 while (cpvar) {
306 char *cpendvar = strchr(cpvar, ')');
307 if (cpendvar) {
308 int lenvar = cpendvar - cpvar - 2; // Subtract the $()
309 char *var = StringDup(cpvar + 2, lenvar);
310 SString val = GetWild(var, filename);
311 int newlenbase = strlen(base) + val.length() - lenvar;
312 char *newbase = new char[newlenbase];
313 strncpy(newbase, base, cpvar - base);
314 strcpy(newbase + (cpvar - base), val.c_str());
315 strcpy(newbase + (cpvar - base) + val.length(), cpendvar + 1);
316 delete []var;
317 delete []base;
318 base = newbase;
319 }
320 cpvar = strstr(base, "$(");
321 }
322 SString sret = base;
323 delete []base;
324 return sret;
325 }
326
327 void PropSet::Clear() {
328 for (int root = 0; root < hashRoots; root++) {
329 Property *p = props[root];
330 while (p) {
331 Property *pNext = p->next;
332 p->hash = 0;
333 delete p->key;
334 p->key = 0;
335 delete p->val;
336 p->val = 0;
337 delete p;
338 p = pNext;
339 }
340 props[root] = 0;
341 }
342 }
343
344 char *PropSet::ToString() {
345 unsigned int len=0;
346 for (int r = 0; r < hashRoots; r++) {
347 for (Property *p = props[r]; p; p = p->next) {
348 len += strlen(p->key) + 1;
349 len += strlen(p->val) + 1;
350 }
351 }
352 if (len == 0)
353 len = 1; // Return as empty string
354 char *ret = new char [len];
355 if (ret) {
356 char *w = ret;
357 for (int root = 0; root < hashRoots; root++) {
358 for (Property *p = props[root]; p; p = p->next) {
359 strcpy(w, p->key);
360 w += strlen(p->key);
361 *w++ = '=';
362 strcpy(w, p->val);
363 w += strlen(p->val);
364 *w++ = '\n';
365 }
366 }
367 ret[len-1] = '\0';
368 }
369 return ret;
370 }
371
372 /**
373 * Initiate enumeration.
374 */
375 bool PropSet::GetFirst(char **key, char **val) {
376 for (int i = 0; i < hashRoots; i++) {
377 for (Property *p = props[i]; p; p = p->next) {
378 if (p) {
379 *key = p->key;
380 *val = p->val;
381 enumnext = p->next; // GetNext will begin here ...
382 enumhash = i; // ... in this block
383 return true;
384 }
385 }
386 }
387 return false;
388 }
389
390 /**
391 * Continue enumeration.
392 */
393 bool PropSet::GetNext(char ** key, char ** val) {
394 bool firstloop = true;
395
396 // search begins where we left it : in enumhash block
397 for (int i = enumhash; i < hashRoots; i++) {
398 if (!firstloop)
399 enumnext = props[i]; // Begin with first property in block
400 // else : begin where we left
401 firstloop = false;
402
403 for (Property *p = enumnext; p; p = p->next) {
404 if (p) {
405 *key = p->key;
406 *val = p->val;
407 enumnext = p->next; // for GetNext
408 enumhash = i;
409 return true;
410 }
411 }
412 }
413 return false;
414 }
415
416 static bool iswordsep(char ch, bool onlyLineEnds) {
417 if (!isspace(ch))
418 return false;
419 if (!onlyLineEnds)
420 return true;
421 return ch == '\r' || ch == '\n';
422 }
423
424 /**
425 * Creates an array that points into each word in the string and puts \0 terminators
426 * after each word.
427 */
428 static char **ArrayFromWordList(char *wordlist, int *len, bool onlyLineEnds = false) {
429 char prev = '\n';
430 int words = 0;
431 for (int j = 0; wordlist[j]; j++) {
432 if (!iswordsep(wordlist[j], onlyLineEnds) && iswordsep(prev, onlyLineEnds))
433 words++;
434 prev = wordlist[j];
435 }
436 char **keywords = new char * [words + 1];
437 if (keywords) {
438 words = 0;
439 prev = '\0';
440 int slen = strlen(wordlist);
441 for (int k = 0; k < slen; k++) {
442 if (!iswordsep(wordlist[k], onlyLineEnds)) {
443 if (!prev) {
444 keywords[words] = &wordlist[k];
445 words++;
446 }
447 } else {
448 wordlist[k] = '\0';
449 }
450 prev = wordlist[k];
451 }
452 keywords[words] = &wordlist[slen];
453 *len = words;
454 } else {
455 *len = 0;
456 }
457 return keywords;
458 }
459
460 void WordList::Clear() {
461 if (words) {
462 delete []list;
463 delete []words;
464 delete []wordsNoCase;
465 }
466 words = 0;
467 wordsNoCase = 0;
468 list = 0;
469 len = 0;
470 sorted = false;
471 }
472
473 void WordList::Set(const char *s) {
474 list = StringDup(s);
475 sorted = false;
476 words = ArrayFromWordList(list, &len, onlyLineEnds);
477 wordsNoCase = new char * [len + 1];
478 memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
479 }
480
481 char *WordList::Allocate(int size) {
482 list = new char[size + 1];
483 list[size] = '\0';
484 return list;
485 }
486
487 void WordList::SetFromAllocated() {
488 sorted = false;
489 words = ArrayFromWordList(list, &len, onlyLineEnds);
490 wordsNoCase = new char * [len + 1];
491 memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
492 }
493
494 int cmpString(const void *a1, const void *a2) {
495 // Can't work out the correct incantation to use modern casts here
496 return strcmp(*(char**)(a1), *(char**)(a2));
497 }
498
499 int cmpStringNoCase(const void *a1, const void *a2) {
500 // Can't work out the correct incantation to use modern casts here
501 return CompareCaseInsensitive(*(char**)(a1), *(char**)(a2));
502 }
503
504 static void SortWordList(char **words, char **wordsNoCase, unsigned int len) {
505 qsort(reinterpret_cast<void*>(words), len, sizeof(*words),
506 cmpString);
507 qsort(reinterpret_cast<void*>(wordsNoCase), len, sizeof(*wordsNoCase),
508 cmpStringNoCase);
509 }
510
511 bool WordList::InList(const char *s) {
512 if (0 == words)
513 return false;
514 if (!sorted) {
515 sorted = true;
516 SortWordList(words, wordsNoCase, len);
517 for (unsigned int k = 0; k < (sizeof(starts) / sizeof(starts[0])); k++)
518 starts[k] = -1;
519 for (int l = len - 1; l >= 0; l--) {
520 unsigned char indexChar = words[l][0];
521 starts[indexChar] = l;
522 }
523 }
524 unsigned char firstChar = s[0];
525 int j = starts[firstChar];
526 if (j >= 0) {
527 while (words[j][0] == firstChar) {
528 if (s[1] == words[j][1]) {
529 const char *a = words[j] + 1;
530 const char *b = s + 1;
531 while (*a && *a == *b) {
532 a++;
533 b++;
534 }
535 if (!*a && !*b)
536 return true;
537 }
538 j++;
539 }
540 }
541 j = starts['^'];
542 if (j >= 0) {
543 while (words[j][0] == '^') {
544 const char *a = words[j] + 1;
545 const char *b = s;
546 while (*a && *a == *b) {
547 a++;
548 b++;
549 }
550 if (!*a)
551 return true;
552 j++;
553 }
554 }
555 return false;
556 }
557
558 /**
559 * Returns an element (complete) of the wordlist array which has
560 * the same beginning as the passed string.
561 * The length of the word to compare is passed too.
562 * Letter case can be ignored or preserved (default).
563 */
564 const char *WordList::GetNearestWord(const char *wordStart, int searchLen /*= -1*/, bool ignoreCase /*= false*/) {
565 int start = 0; // lower bound of the api array block to search
566 int end = len - 1; // upper bound of the api array block to search
567 int pivot; // index of api array element just being compared
568 int cond; // comparison result (in the sense of strcmp() result)
569 const char *word; // api array element just being compared
570
571 if (0 == words)
572 return NULL;
573 if (!sorted) {
574 sorted = true;
575 SortWordList(words, wordsNoCase, len);
576 }
577 if (ignoreCase) {
578 while (start <= end) { // binary searching loop
579 pivot = (start + end) >> 1;
580 word = wordsNoCase[pivot];
581 cond = CompareNCaseInsensitive(wordStart, word, searchLen);
582 if (!cond && nonFuncChar(word[searchLen])) // maybe there should be a "non-word character" test here?
583 return word; // result must not be freed with free()
584 else if (cond > 0)
585 start = pivot + 1;
586 else if (cond <= 0)
587 end = pivot - 1;
588 }
589 } else { // preserve the letter case
590 while (start <= end) { // binary searching loop
591 pivot = (start + end) >> 1;
592 word = words[pivot];
593 cond = strncmp(wordStart, word, searchLen);
594 if (!cond && nonFuncChar(word[searchLen])) // maybe there should be a "non-word character" test here?
595 return word; // result must not be freed with free()
596 else if (cond > 0)
597 start = pivot + 1;
598 else if (cond <= 0)
599 end = pivot - 1;
600 }
601 }
602 return NULL;
603 }
604
605 /**
606 * Find the length of a 'word' which is actually an identifier in a string
607 * which looks like "identifier(..." or "identifier:" or "identifier" and where
608 * there may be extra spaces after the identifier that should not be
609 * counted in the length.
610 */
611 static unsigned int LengthWord(const char *word, char otherSeparator) {
612 // Find a '(', or ':'. If that fails go to the end of the string.
613 const char *endWord = strchr(word, '(');
614 if (!endWord)
615 endWord = strchr(word, ':');
616 if (!endWord && otherSeparator)
617 endWord = strchr(word, otherSeparator);
618 if (!endWord)
619 endWord = word + strlen(word);
620 // Last case always succeeds so endWord != 0
621
622 // Drop any space characters.
623 if (endWord > word) {
624 endWord--; // Back from the '(', ':', or '\0'
625 // Move backwards over any spaces
626 while ((endWord > word) && (isspace(*endWord))) {
627 endWord--;
628 }
629 }
630 return endWord - word;
631 }
632
633 /**
634 * Returns elements (first words of them) of the wordlist array which have
635 * the same beginning as the passed string.
636 * The length of the word to compare is passed too.
637 * Letter case can be ignored or preserved (default).
638 * If there are more words meeting the condition they are returned all of
639 * them in the ascending order separated with spaces.
640 *
641 * NOTE: returned buffer has to be freed with delete[].
642 */
643 char *WordList::GetNearestWords(
644 const char *wordStart,
645 int searchLen /*= -1*/,
646 bool ignoreCase /*= false*/,
647 char otherSeparator /*= '\0'*/) {
648 int wordlen; // length of the word part (before the '(' brace) of the api array element
649 SString wordsNear;
650 wordsNear.setsizegrowth(1000);
651 int start = 0; // lower bound of the api array block to search
652 int end = len - 1; // upper bound of the api array block to search
653 int pivot; // index of api array element just being compared
654 int cond; // comparison result (in the sense of strcmp() result)
655
656 if (0 == words)
657 return NULL;
658 if (!sorted) {
659 sorted = true;
660 SortWordList(words, wordsNoCase, len);
661 }
662 if (ignoreCase) {
663 while (start <= end) { // Binary searching loop
664 pivot = (start + end) / 2;
665 cond = CompareNCaseInsensitive(wordStart, wordsNoCase[pivot], searchLen);
666 if (!cond) {
667 // Find first match
668 while ((pivot > start) &&
669 (0 == CompareNCaseInsensitive(wordStart,
670 wordsNoCase[pivot-1], searchLen))) {
671 --pivot;
672 }
673 // Grab each match
674 while ((pivot <= end) &&
675 (0 == CompareNCaseInsensitive(wordStart,
676 wordsNoCase[pivot], searchLen))) {
677 wordlen = LengthWord(wordsNoCase[pivot], otherSeparator) + 1;
678 wordsNear.append(wordsNoCase[pivot], wordlen, ' ');
679 ++pivot;
680 }
681 return wordsNear.detach();
682 } else if (cond < 0) {
683 end = pivot - 1;
684 } else if (cond > 0) {
685 start = pivot + 1;
686 }
687 }
688 } else { // Preserve the letter case
689 while (start <= end) { // Binary searching loop
690 pivot = (start + end) / 2;
691 cond = strncmp(wordStart, words[pivot], searchLen);
692 if (!cond) {
693 // Find first match
694 while ((pivot > start) &&
695 (0 == strncmp(wordStart,
696 words[pivot-1], searchLen))) {
697 --pivot;
698 }
699 // Grab each match
700 while ((pivot <= end) &&
701 (0 == strncmp(wordStart,
702 words[pivot], searchLen))) {
703 wordlen = LengthWord(words[pivot], otherSeparator) + 1;
704 wordsNear.append(words[pivot], wordlen, ' ');
705 ++pivot;
706 }
707 return wordsNear.detach();
708 } else if (cond < 0) {
709 end = pivot - 1;
710 } else if (cond > 0) {
711 start = pivot + 1;
712 }
713 }
714 }
715 return NULL;
716 }