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