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