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