]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/stc/scintilla/src/PropSet.cxx
29679969132f0f3eeabcf9da2c084875303df0a0
[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 static bool iswordsep(char ch, bool onlyLineEnds) {
415 if (!isspace(ch))
416 return false;
417 if (!onlyLineEnds)
418 return true;
419 return ch == '\r' || ch == '\n';
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 char prev = '\n';
428 int words = 0;
429 for (int j = 0; wordlist[j]; j++) {
430 if (!iswordsep(wordlist[j], onlyLineEnds) && iswordsep(prev, onlyLineEnds))
431 words++;
432 prev = wordlist[j];
433 }
434 char **keywords = new char * [words + 1];
435 if (keywords) {
436 words = 0;
437 prev = '\0';
438 size_t slen = strlen(wordlist);
439 for (size_t k = 0; k < slen; k++) {
440 if (!iswordsep(wordlist[k], onlyLineEnds)) {
441 if (!prev) {
442 keywords[words] = &wordlist[k];
443 words++;
444 }
445 } else {
446 wordlist[k] = '\0';
447 }
448 prev = wordlist[k];
449 }
450 keywords[words] = &wordlist[slen];
451 *len = words;
452 } else {
453 *len = 0;
454 }
455 return keywords;
456 }
457
458 void WordList::Clear() {
459 if (words) {
460 delete []list;
461 delete []words;
462 delete []wordsNoCase;
463 }
464 words = 0;
465 wordsNoCase = 0;
466 list = 0;
467 len = 0;
468 sorted = false;
469 }
470
471 void WordList::Set(const char *s) {
472 list = StringDup(s);
473 sorted = false;
474 words = ArrayFromWordList(list, &len, onlyLineEnds);
475 wordsNoCase = new char * [len + 1];
476 memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
477 }
478
479 char *WordList::Allocate(int size) {
480 list = new char[size + 1];
481 list[size] = '\0';
482 return list;
483 }
484
485 void WordList::SetFromAllocated() {
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 int cmpString(const void *a1, const void *a2) {
493 // Can't work out the correct incantation to use modern casts here
494 return strcmp(*(char**)(a1), *(char**)(a2));
495 }
496
497 int cmpStringNoCase(const void *a1, const void *a2) {
498 // Can't work out the correct incantation to use modern casts here
499 return CompareCaseInsensitive(*(char**)(a1), *(char**)(a2));
500 }
501
502 static void SortWordList(char **words, char **wordsNoCase, unsigned int len) {
503 qsort(reinterpret_cast<void*>(words), len, sizeof(*words),
504 cmpString);
505 qsort(reinterpret_cast<void*>(wordsNoCase), len, sizeof(*wordsNoCase),
506 cmpStringNoCase);
507 }
508
509 bool WordList::InList(const char *s) {
510 if (0 == words)
511 return false;
512 if (!sorted) {
513 sorted = true;
514 SortWordList(words, wordsNoCase, len);
515 for (unsigned int k = 0; k < (sizeof(starts) / sizeof(starts[0])); k++)
516 starts[k] = -1;
517 for (int l = len - 1; l >= 0; l--) {
518 unsigned char indexChar = words[l][0];
519 starts[indexChar] = l;
520 }
521 }
522 unsigned char firstChar = s[0];
523 int j = starts[firstChar];
524 if (j >= 0) {
525 while (words[j][0] == firstChar) {
526 if (s[1] == words[j][1]) {
527 const char *a = words[j] + 1;
528 const char *b = s + 1;
529 while (*a && *a == *b) {
530 a++;
531 b++;
532 }
533 if (!*a && !*b)
534 return true;
535 }
536 j++;
537 }
538 }
539 j = starts['^'];
540 if (j >= 0) {
541 while (words[j][0] == '^') {
542 const char *a = words[j] + 1;
543 const char *b = s;
544 while (*a && *a == *b) {
545 a++;
546 b++;
547 }
548 if (!*a)
549 return true;
550 j++;
551 }
552 }
553 return false;
554 }
555
556 /**
557 * Returns an element (complete) of the wordlist array which has
558 * the same beginning as the passed string.
559 * The length of the word to compare is passed too.
560 * Letter case can be ignored or preserved (default).
561 */
562 const char *WordList::GetNearestWord(const char *wordStart, int searchLen /*= -1*/, bool ignoreCase /*= false*/, SString wordCharacters /*='/0' */) {
563 int start = 0; // lower bound of the api array block to search
564 int end = len - 1; // upper bound of the api array block to search
565 int pivot; // index of api array element just being compared
566 int cond; // comparison result (in the sense of strcmp() result)
567 const char *word; // api array element just being compared
568
569 if (0 == words)
570 return NULL;
571 if (!sorted) {
572 sorted = true;
573 SortWordList(words, wordsNoCase, len);
574 }
575 if (ignoreCase) {
576 while (start <= end) { // binary searching loop
577 pivot = (start + end) >> 1;
578 word = wordsNoCase[pivot];
579 cond = CompareNCaseInsensitive(wordStart, word, searchLen);
580 if (!cond && (!wordCharacters.contains(word[searchLen])))
581 return word; // result must not be freed with free()
582 else if (cond > 0)
583 start = pivot + 1;
584 else if (cond <= 0)
585 end = pivot - 1;
586 }
587 } else { // preserve the letter case
588 while (start <= end) { // binary searching loop
589 pivot = (start + end) >> 1;
590 word = words[pivot];
591 cond = strncmp(wordStart, word, searchLen);
592 if (!cond && (!wordCharacters.contains(word[searchLen])))
593 return word; // result must not be freed with free()
594 else if (cond > 0)
595 start = pivot + 1;
596 else if (cond <= 0)
597 end = pivot - 1;
598 }
599 }
600 return NULL;
601 }
602
603 /**
604 * Find the length of a 'word' which is actually an identifier in a string
605 * which looks like "identifier(..." or "identifier:" or "identifier" and where
606 * there may be extra spaces after the identifier that should not be
607 * counted in the length.
608 */
609 static unsigned int LengthWord(const char *word, char otherSeparator) {
610 // Find a '(', or ':'. If that fails go to the end of the string.
611 const char *endWord = strchr(word, '(');
612 if (!endWord)
613 endWord = strchr(word, ':');
614 if (!endWord && otherSeparator)
615 endWord = strchr(word, otherSeparator);
616 if (!endWord)
617 endWord = word + strlen(word);
618 // Last case always succeeds so endWord != 0
619
620 // Drop any space characters.
621 if (endWord > word) {
622 endWord--; // Back from the '(', ':', or '\0'
623 // Move backwards over any spaces
624 while ((endWord > word) && (isspace(*endWord))) {
625 endWord--;
626 }
627 }
628 return endWord - word;
629 }
630
631 /**
632 * Returns elements (first words of them) of the wordlist array which have
633 * the same beginning as the passed string.
634 * The length of the word to compare is passed too.
635 * Letter case can be ignored or preserved (default).
636 * If there are more words meeting the condition they are returned all of
637 * them in the ascending order separated with spaces.
638 *
639 * NOTE: returned buffer has to be freed with delete[].
640 */
641 char *WordList::GetNearestWords(
642 const char *wordStart,
643 int searchLen /*= -1*/,
644 bool ignoreCase /*= false*/,
645 char otherSeparator /*= '\0'*/) {
646 int wordlen; // length of the word part (before the '(' brace) of the api array element
647 SString wordsNear;
648 wordsNear.setsizegrowth(1000);
649 int start = 0; // lower bound of the api array block to search
650 int end = len - 1; // upper bound of the api array block to search
651 int pivot; // index of api array element just being compared
652 int cond; // comparison result (in the sense of strcmp() result)
653
654 if (0 == words)
655 return NULL;
656 if (!sorted) {
657 sorted = true;
658 SortWordList(words, wordsNoCase, len);
659 }
660 if (ignoreCase) {
661 while (start <= end) { // Binary searching loop
662 pivot = (start + end) / 2;
663 cond = CompareNCaseInsensitive(wordStart, wordsNoCase[pivot], searchLen);
664 if (!cond) {
665 // Find first match
666 while ((pivot > start) &&
667 (0 == CompareNCaseInsensitive(wordStart,
668 wordsNoCase[pivot-1], searchLen))) {
669 --pivot;
670 }
671 // Grab each match
672 while ((pivot <= end) &&
673 (0 == CompareNCaseInsensitive(wordStart,
674 wordsNoCase[pivot], searchLen))) {
675 wordlen = LengthWord(wordsNoCase[pivot], otherSeparator) + 1;
676 wordsNear.append(wordsNoCase[pivot], wordlen, ' ');
677 ++pivot;
678 }
679 return wordsNear.detach();
680 } else if (cond < 0) {
681 end = pivot - 1;
682 } else if (cond > 0) {
683 start = pivot + 1;
684 }
685 }
686 } else { // Preserve the letter case
687 while (start <= end) { // Binary searching loop
688 pivot = (start + end) / 2;
689 cond = strncmp(wordStart, words[pivot], searchLen);
690 if (!cond) {
691 // Find first match
692 while ((pivot > start) &&
693 (0 == strncmp(wordStart,
694 words[pivot-1], searchLen))) {
695 --pivot;
696 }
697 // Grab each match
698 while ((pivot <= end) &&
699 (0 == strncmp(wordStart,
700 words[pivot], searchLen))) {
701 wordlen = LengthWord(words[pivot], otherSeparator) + 1;
702 wordsNear.append(words[pivot], wordlen, ' ');
703 ++pivot;
704 }
705 return wordsNear.detach();
706 } else if (cond < 0) {
707 end = pivot - 1;
708 } else if (cond > 0) {
709 start = pivot + 1;
710 }
711 }
712 }
713 return NULL;
714 }