]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/PropSet.cxx
021a6572739172bb6868a3c8518be713356bf297
[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' */) {
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 return word; // result must not be freed with free()
585 else if (cond > 0)
586 start = pivot + 1;
587 else if (cond <= 0)
588 end = pivot - 1;
589 }
590 } else { // preserve the letter case
591 while (start <= end) { // binary searching loop
592 pivot = (start + end) >> 1;
593 word = words[pivot];
594 cond = strncmp(wordStart, word, searchLen);
595 if (!cond && (!wordCharacters.contains(word[searchLen])))
596 return word; // result must not be freed with free()
597 else if (cond > 0)
598 start = pivot + 1;
599 else if (cond <= 0)
600 end = pivot - 1;
601 }
602 }
603 return NULL;
604 }
605
606 /**
607 * Find the length of a 'word' which is actually an identifier in a string
608 * which looks like "identifier(..." or "identifier:" or "identifier" and where
609 * there may be extra spaces after the identifier that should not be
610 * counted in the length.
611 */
612 static unsigned int LengthWord(const char *word, char otherSeparator) {
613 // Find a '(', or ':'. If that fails go to the end of the string.
614 const char *endWord = strchr(word, '(');
615 if (!endWord)
616 endWord = strchr(word, ':');
617 if (!endWord && otherSeparator)
618 endWord = strchr(word, otherSeparator);
619 if (!endWord)
620 endWord = word + strlen(word);
621 // Last case always succeeds so endWord != 0
622
623 // Drop any space characters.
624 if (endWord > word) {
625 endWord--; // Back from the '(', ':', or '\0'
626 // Move backwards over any spaces
627 while ((endWord > word) && (IsASpace(*endWord))) {
628 endWord--;
629 }
630 }
631 return endWord - word;
632 }
633
634 /**
635 * Returns elements (first words of them) of the wordlist array which have
636 * the same beginning as the passed string.
637 * The length of the word to compare is passed too.
638 * Letter case can be ignored or preserved (default).
639 * If there are more words meeting the condition they are returned all of
640 * them in the ascending order separated with spaces.
641 *
642 * NOTE: returned buffer has to be freed with delete[].
643 */
644 char *WordList::GetNearestWords(
645 const char *wordStart,
646 int searchLen /*= -1*/,
647 bool ignoreCase /*= false*/,
648 char otherSeparator /*= '\0'*/) {
649 int wordlen; // length of the word part (before the '(' brace) of the api array element
650 SString wordsNear;
651 wordsNear.setsizegrowth(1000);
652 int start = 0; // lower bound of the api array block to search
653 int end = len - 1; // upper bound of the api array block to search
654 int pivot; // index of api array element just being compared
655 int cond; // comparison result (in the sense of strcmp() result)
656
657 if (0 == words)
658 return NULL;
659 if (!sorted) {
660 sorted = true;
661 SortWordList(words, wordsNoCase, len);
662 }
663 if (ignoreCase) {
664 while (start <= end) { // Binary searching loop
665 pivot = (start + end) / 2;
666 cond = CompareNCaseInsensitive(wordStart, wordsNoCase[pivot], searchLen);
667 if (!cond) {
668 // Find first match
669 while ((pivot > start) &&
670 (0 == CompareNCaseInsensitive(wordStart,
671 wordsNoCase[pivot-1], searchLen))) {
672 --pivot;
673 }
674 // Grab each match
675 while ((pivot <= end) &&
676 (0 == CompareNCaseInsensitive(wordStart,
677 wordsNoCase[pivot], searchLen))) {
678 wordlen = LengthWord(wordsNoCase[pivot], otherSeparator) + 1;
679 wordsNear.append(wordsNoCase[pivot], wordlen, ' ');
680 ++pivot;
681 }
682 return wordsNear.detach();
683 } else if (cond < 0) {
684 end = pivot - 1;
685 } else if (cond > 0) {
686 start = pivot + 1;
687 }
688 }
689 } else { // Preserve the letter case
690 while (start <= end) { // Binary searching loop
691 pivot = (start + end) / 2;
692 cond = strncmp(wordStart, words[pivot], searchLen);
693 if (!cond) {
694 // Find first match
695 while ((pivot > start) &&
696 (0 == strncmp(wordStart,
697 words[pivot-1], searchLen))) {
698 --pivot;
699 }
700 // Grab each match
701 while ((pivot <= end) &&
702 (0 == strncmp(wordStart,
703 words[pivot], searchLen))) {
704 wordlen = LengthWord(words[pivot], otherSeparator) + 1;
705 wordsNear.append(words[pivot], wordlen, ' ');
706 ++pivot;
707 }
708 return wordsNear.detach();
709 } else if (cond < 0) {
710 end = pivot - 1;
711 } else if (cond > 0) {
712 start = pivot + 1;
713 }
714 }
715 }
716 return NULL;
717 }