]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/stc/scintilla/src/PropSet.cxx
18544aef23c87b54b8e09c91b7edffa1944895b6
[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-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 <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 inline bool IsASpace(unsigned int ch) {
34 return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
35 }
36
37 int CompareCaseInsensitive(const char *a, const char *b) {
38 while (*a && *b) {
39 if (*a != *b) {
40 char upperA = MakeUpperCase(*a);
41 char upperB = MakeUpperCase(*b);
42 if (upperA != upperB)
43 return upperA - upperB;
44 }
45 a++;
46 b++;
47 }
48 // Either *a or *b is nul
49 return *a - *b;
50 }
51
52 int CompareNCaseInsensitive(const char *a, const char *b, size_t len) {
53 while (*a && *b && len) {
54 if (*a != *b) {
55 char upperA = MakeUpperCase(*a);
56 char upperB = MakeUpperCase(*b);
57 if (upperA != upperB)
58 return upperA - upperB;
59 }
60 a++;
61 b++;
62 len--;
63 }
64 if (len == 0)
65 return 0;
66 else
67 // Either *a or *b is nul
68 return *a - *b;
69 }
70
71 bool EqualCaseInsensitive(const char *a, const char *b) {
72 return 0 == CompareCaseInsensitive(a, b);
73 }
74
75 inline unsigned int HashString(const char *s, size_t len) {
76 unsigned int ret = 0;
77 while (len--) {
78 ret <<= 4;
79 ret ^= *s;
80 s++;
81 }
82 return ret;
83 }
84
85 PropSet::PropSet() {
86 superPS = 0;
87 for (int root = 0; root < hashRoots; root++)
88 props[root] = 0;
89 }
90
91 PropSet::~PropSet() {
92 superPS = 0;
93 Clear();
94 }
95
96 void PropSet::Set(const char *key, const char *val, int lenKey, int lenVal) {
97 if (!*key) // Empty keys are not supported
98 return;
99 if (lenKey == -1)
100 lenKey = static_cast<int>(strlen(key));
101 if (lenVal == -1)
102 lenVal = static_cast<int>(strlen(val));
103 unsigned int hash = HashString(key, lenKey);
104 for (Property *p = props[hash % hashRoots]; p; p = p->next) {
105 if ((hash == p->hash) &&
106 ((strlen(p->key) == static_cast<unsigned int>(lenKey)) &&
107 (0 == strncmp(p->key, key, lenKey)))) {
108 // Replace current value
109 delete [](p->val);
110 p->val = StringDup(val, lenVal);
111 return;
112 }
113 }
114 // Not found
115 Property *pNew = new Property;
116 if (pNew) {
117 pNew->hash = hash;
118 pNew->key = StringDup(key, lenKey);
119 pNew->val = StringDup(val, lenVal);
120 pNew->next = props[hash % hashRoots];
121 props[hash % hashRoots] = pNew;
122 }
123 }
124
125 void PropSet::Set(const char *keyVal) {
126 while (IsASpace(*keyVal))
127 keyVal++;
128 const char *endVal = keyVal;
129 while (*endVal && (*endVal != '\n'))
130 endVal++;
131 const char *eqAt = strchr(keyVal, '=');
132 if (eqAt) {
133 Set(keyVal, eqAt + 1, eqAt-keyVal, endVal - eqAt - 1);
134 } else if (*keyVal) { // No '=' so assume '=1'
135 Set(keyVal, "1", endVal-keyVal, 1);
136 }
137 }
138
139 void PropSet::SetMultiple(const char *s) {
140 const char *eol = strchr(s, '\n');
141 while (eol) {
142 Set(s);
143 s = eol + 1;
144 eol = strchr(s, '\n');
145 }
146 Set(s);
147 }
148
149 SString PropSet::Get(const char *key) {
150 unsigned int hash = HashString(key, strlen(key));
151 for (Property *p = props[hash % hashRoots]; p; p = p->next) {
152 if ((hash == p->hash) && (0 == strcmp(p->key, key))) {
153 return p->val;
154 }
155 }
156 if (superPS) {
157 // Failed here, so try in base property set
158 return superPS->Get(key);
159 } else {
160 return "";
161 }
162 }
163
164 static bool IncludesVar(const char *value, const char *key) {
165 const char *var = strstr(value, "$(");
166 while (var) {
167 if (isprefix(var + 2, key) && (var[2 + strlen(key)] == ')')) {
168 // Found $(key) which would lead to an infinite loop so exit
169 return true;
170 }
171 var = strstr(var + 2, ")");
172 if (var)
173 var = strstr(var + 1, "$(");
174 }
175 return false;
176 }
177
178 SString PropSet::GetExpanded(const char *key) {
179 SString val = Get(key);
180 if (IncludesVar(val.c_str(), key))
181 return val;
182 return Expand(val.c_str());
183 }
184
185 SString PropSet::Expand(const char *withVars) {
186 char *base = StringDup(withVars);
187 char *cpvar = strstr(base, "$(");
188 int maxExpands = 1000; // Avoid infinite expansion of recursive definitions
189 while (cpvar && (maxExpands > 0)) {
190 char *cpendvar = strchr(cpvar, ')');
191 if (cpendvar) {
192 int lenvar = cpendvar - cpvar - 2; // Subtract the $()
193 char *var = StringDup(cpvar + 2, lenvar);
194 SString val = GetExpanded(var);
195 size_t newlenbase = strlen(base) + val.length() - lenvar;
196 char *newbase = new char[newlenbase];
197 strncpy(newbase, base, cpvar - base);
198 strcpy(newbase + (cpvar - base), val.c_str());
199 strcpy(newbase + (cpvar - base) + val.length(), cpendvar + 1);
200 delete []var;
201 delete []base;
202 base = newbase;
203 }
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 }