]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/stc/scintilla/src/PropSet.cxx
701b2a82c75aa9f2c164b935ab6589c49ec7cf0b
[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-2001 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 int CompareCaseInsensitive(const char *a, const char *b) {
30 while (*a && *b) {
31 if (*a != *b) {
32 char upperA = MakeUpperCase(*a);
33 char upperB = MakeUpperCase(*b);
34 if (upperA != upperB)
35 return upperA - upperB;
36 }
37 a++;
38 b++;
39 }
40 // Either *a or *b is nul
41 return *a - *b;
42 }
43
44 int CompareNCaseInsensitive(const char *a, const char *b, int len) {
45 while (*a && *b && len) {
46 if (*a != *b) {
47 char upperA = MakeUpperCase(*a);
48 char upperB = MakeUpperCase(*b);
49 if (upperA != upperB)
50 return upperA - upperB;
51 }
52 a++;
53 b++;
54 len--;
55 }
56 if (len == 0)
57 return 0;
58 else
59 // Either *a or *b is nul
60 return *a - *b;
61 }
62
63 bool EqualCaseInsensitive(const char *a, const char *b) {
64 return 0 == CompareCaseInsensitive(a, b);
65 }
66
67 inline unsigned int HashString(const char *s, int len) {
68 unsigned int ret = 0;
69 while (len--) {
70 ret <<= 4;
71 ret ^= *s;
72 s++;
73 }
74 return ret;
75 }
76
77 PropSet::PropSet() {
78 superPS = 0;
79 for (int root = 0; root < hashRoots; root++)
80 props[root] = 0;
81 }
82
83 PropSet::~PropSet() {
84 superPS = 0;
85 Clear();
86 }
87
88 void PropSet::Set(const char *key, const char *val, int lenKey, int lenVal) {
89 if (!*key) // Empty keys are not supported
90 return;
91 if (lenKey == -1)
92 lenKey = strlen(key);
93 if (lenVal == -1)
94 lenVal = strlen(val);
95 unsigned int hash = HashString(key, lenKey);
96 for (Property *p = props[hash % hashRoots]; p; p = p->next) {
97 if ((hash == p->hash) &&
98 ((strlen(p->key) == static_cast<unsigned int>(lenKey)) &&
99 (0 == strncmp(p->key, key, lenKey)))) {
100 // Replace current value
101 delete [](p->val);
102 p->val = StringDup(val, lenVal);
103 return ;
104 }
105 }
106 // Not found
107 Property *pNew = new Property;
108 if (pNew) {
109 pNew->hash = hash;
110 pNew->key = StringDup(key, lenKey);
111 pNew->val = StringDup(val, lenVal);
112 pNew->next = props[hash % hashRoots];
113 props[hash % hashRoots] = pNew;
114 }
115 }
116
117 void PropSet::Set(const char *keyVal) {
118 while (isspace(*keyVal))
119 keyVal++;
120 const char *endVal = keyVal;
121 while (*endVal && (*endVal != '\n'))
122 endVal++;
123 const char *eqAt = strchr(keyVal, '=');
124 if (eqAt) {
125 Set(keyVal, eqAt + 1, eqAt-keyVal, endVal - eqAt - 1);
126 } else if (*keyVal) { // No '=' so assume '=1'
127 Set(keyVal, "1", endVal-keyVal, 1);
128 }
129 }
130
131 void PropSet::SetMultiple(const char *s) {
132 const char *eol = strchr(s, '\n');
133 while (eol) {
134 Set(s);
135 s = eol + 1;
136 eol = strchr(s, '\n');
137 }
138 Set(s);
139 }
140
141 SString PropSet::Get(const char *key) {
142 unsigned int hash = HashString(key, strlen(key));
143 for (Property *p = props[hash % hashRoots]; p; p = p->next) {
144 if ((hash == p->hash) && (0 == strcmp(p->key, key))) {
145 return p->val;
146 }
147 }
148 if (superPS) {
149 // Failed here, so try in base property set
150 return superPS->Get(key);
151 } else {
152 return "";
153 }
154 }
155
156 static bool IncludesVar(const char *value, const char *key) {
157 const char *var = strstr(value, "$(");
158 while (var) {
159 if (isprefix(var + 2, key) && (var[2 + strlen(key)] == ')')) {
160 // Found $(key) which would lead to an infinite loop so exit
161 return true;
162 }
163 var = strstr(var + 2, ")");
164 if (var)
165 var = strstr(var + 1, "$(");
166 }
167 return false;
168 }
169
170 SString PropSet::GetExpanded(const char *key) {
171 SString val = Get(key);
172 if (IncludesVar(val.c_str(), key))
173 return val;
174 return Expand(val.c_str());
175 }
176
177 SString PropSet::Expand(const char *withVars) {
178 char *base = StringDup(withVars);
179 char *cpvar = strstr(base, "$(");
180 while (cpvar) {
181 char *cpendvar = strchr(cpvar, ')');
182 if (cpendvar) {
183 int lenvar = cpendvar - cpvar - 2; // Subtract the $()
184 char *var = StringDup(cpvar + 2, lenvar);
185 SString val = GetExpanded(var);
186 int 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 }
195 cpvar = strstr(base, "$(");
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 int lentarget = strlen(target);
224 int lensuffix = strlen(suffix);
225 if (lensuffix > lentarget)
226 return false;
227 for (int i = 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 while (cpvar) {
300 char *cpendvar = strchr(cpvar, ')');
301 if (cpendvar) {
302 int lenvar = cpendvar - cpvar - 2; // Subtract the $()
303 char *var = StringDup(cpvar + 2, lenvar);
304 SString val = GetWild(var, filename);
305 int newlenbase = strlen(base) + val.length() - lenvar;
306 char *newbase = new char[newlenbase];
307 strncpy(newbase, base, cpvar - base);
308 strcpy(newbase + (cpvar - base), val.c_str());
309 strcpy(newbase + (cpvar - base) + val.length(), cpendvar + 1);
310 delete []var;
311 delete []base;
312 base = newbase;
313 }
314 cpvar = strstr(base, "$(");
315 }
316 SString sret = base;
317 delete []base;
318 return sret;
319 }
320
321 void PropSet::Clear() {
322 for (int root = 0; root < hashRoots; root++) {
323 Property *p = props[root];
324 while (p) {
325 Property *pNext = p->next;
326 p->hash = 0;
327 delete p->key;
328 p->key = 0;
329 delete p->val;
330 p->val = 0;
331 delete p;
332 p = pNext;
333 }
334 props[root] = 0;
335 }
336 }
337
338 char *PropSet::ToString() {
339 unsigned int len=0;
340 for (int r = 0; r < hashRoots; r++) {
341 for (Property *p = props[r]; p; p = p->next) {
342 len += strlen(p->key) + 1;
343 len += strlen(p->val) + 1;
344 }
345 }
346 if (len == 0)
347 len = 1; // Return as empty string
348 char *ret = new char [len];
349 if (ret) {
350 char *w = ret;
351 for (int root = 0; root < hashRoots; root++) {
352 for (Property *p = props[root]; p; p = p->next) {
353 strcpy(w, p->key);
354 w += strlen(p->key);
355 *w++ = '=';
356 strcpy(w, p->val);
357 w += strlen(p->val);
358 *w++ = '\n';
359 }
360 }
361 ret[len-1] = '\0';
362 }
363 return ret;
364 }
365
366 /**
367 * Initiate enumeration.
368 */
369 bool PropSet::GetFirst(char **key, char **val) {
370 for (int i = 0; i < hashRoots; i++) {
371 for (Property *p = props[i]; p; p = p->next) {
372 if (p) {
373 *key = p->key;
374 *val = p->val;
375 enumnext = p->next; // GetNext will begin here ...
376 enumhash = i; // ... in this block
377 return true;
378 }
379 }
380 }
381 return false;
382 }
383
384 /**
385 * Continue enumeration.
386 */
387 bool PropSet::GetNext(char ** key, char ** val) {
388 bool firstloop = true;
389
390 // search begins where we left it : in enumhash block
391 for (int i = enumhash; i < hashRoots; i++) {
392 if (!firstloop)
393 enumnext = props[i]; // Begin with first property in block
394 // else : begin where we left
395 firstloop = false;
396
397 for (Property *p = enumnext; p; p = p->next) {
398 if (p) {
399 *key = p->key;
400 *val = p->val;
401 enumnext = p->next; // for GetNext
402 enumhash = i;
403 return true;
404 }
405 }
406 }
407 return false;
408 }
409
410 static bool iswordsep(char ch, bool onlyLineEnds) {
411 if (!isspace(ch))
412 return false;
413 if (!onlyLineEnds)
414 return true;
415 return ch == '\r' || ch == '\n';
416 }
417
418 /**
419 * Creates an array that points into each word in the string and puts \0 terminators
420 * after each word.
421 */
422 static char **ArrayFromWordList(char *wordlist, int *len, bool onlyLineEnds = false) {
423 char prev = '\n';
424 int words = 0;
425 for (int j = 0; wordlist[j]; j++) {
426 if (!iswordsep(wordlist[j], onlyLineEnds) && iswordsep(prev, onlyLineEnds))
427 words++;
428 prev = wordlist[j];
429 }
430 char **keywords = new char * [words + 1];
431 if (keywords) {
432 words = 0;
433 prev = '\0';
434 int slen = strlen(wordlist);
435 for (int k = 0; k < slen; k++) {
436 if (!iswordsep(wordlist[k], onlyLineEnds)) {
437 if (!prev) {
438 keywords[words] = &wordlist[k];
439 words++;
440 }
441 } else {
442 wordlist[k] = '\0';
443 }
444 prev = wordlist[k];
445 }
446 keywords[words] = &wordlist[slen];
447 *len = words;
448 } else {
449 *len = 0;
450 }
451 return keywords;
452 }
453
454 void WordList::Clear() {
455 if (words) {
456 delete []list;
457 delete []words;
458 delete []wordsNoCase;
459 }
460 words = 0;
461 wordsNoCase = 0;
462 list = 0;
463 len = 0;
464 sorted = false;
465 }
466
467 void WordList::Set(const char *s) {
468 list = StringDup(s);
469 sorted = false;
470 words = ArrayFromWordList(list, &len, onlyLineEnds);
471 wordsNoCase = new char * [len + 1];
472 memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
473 }
474
475 char *WordList::Allocate(int size) {
476 list = new char[size + 1];
477 list[size] = '\0';
478 return list;
479 }
480
481 void WordList::SetFromAllocated() {
482 sorted = false;
483 words = ArrayFromWordList(list, &len, onlyLineEnds);
484 wordsNoCase = new char * [len + 1];
485 memcpy(wordsNoCase, words, (len + 1) * sizeof (*words));
486 }
487
488 int cmpString(const void *a1, const void *a2) {
489 // Can't work out the correct incantation to use modern casts here
490 return strcmp(*(char**)(a1), *(char**)(a2));
491 }
492
493 int cmpStringNoCase(const void *a1, const void *a2) {
494 // Can't work out the correct incantation to use modern casts here
495 return CompareCaseInsensitive(*(char**)(a1), *(char**)(a2));
496 }
497
498 static void SortWordList(char **words, char **wordsNoCase, unsigned int len) {
499 qsort(reinterpret_cast<void*>(words), len, sizeof(*words),
500 cmpString);
501 qsort(reinterpret_cast<void*>(wordsNoCase), len, sizeof(*wordsNoCase),
502 cmpStringNoCase);
503 }
504
505 bool WordList::InList(const char *s) {
506 if (0 == words)
507 return false;
508 if (!sorted) {
509 sorted = true;
510 SortWordList(words, wordsNoCase, len);
511 for (unsigned int k = 0; k < (sizeof(starts) / sizeof(starts[0])); k++)
512 starts[k] = -1;
513 for (int l = len - 1; l >= 0; l--) {
514 unsigned char indexChar = words[l][0];
515 starts[indexChar] = l;
516 }
517 }
518 unsigned char firstChar = s[0];
519 int j = starts[firstChar];
520 if (j >= 0) {
521 while (words[j][0] == firstChar) {
522 if (s[1] == words[j][1]) {
523 const char *a = words[j] + 1;
524 const char *b = s + 1;
525 while (*a && *a == *b) {
526 a++;
527 b++;
528 }
529 if (!*a && !*b)
530 return true;
531 }
532 j++;
533 }
534 }
535 j = starts['^'];
536 if (j >= 0) {
537 while (words[j][0] == '^') {
538 const char *a = words[j] + 1;
539 const char *b = s;
540 while (*a && *a == *b) {
541 a++;
542 b++;
543 }
544 if (!*a)
545 return true;
546 j++;
547 }
548 }
549 return false;
550 }
551
552 /**
553 * Returns an element (complete) of the wordlist array which has
554 * the same beginning as the passed string.
555 * The length of the word to compare is passed too.
556 * Letter case can be ignored or preserved (default).
557 */
558 const char *WordList::GetNearestWord(const char *wordStart, int searchLen /*= -1*/, bool ignoreCase /*= false*/) {
559 int start = 0; // lower bound of the api array block to search
560 int end = len - 1; // upper bound of the api array block to search
561 int pivot; // index of api array element just being compared
562 int cond; // comparison result (in the sense of strcmp() result)
563 const char *word; // api array element just being compared
564
565 if (0 == words)
566 return NULL;
567 if (!sorted) {
568 sorted = true;
569 SortWordList(words, wordsNoCase, len);
570 }
571 if (ignoreCase) {
572 while (start <= end) { // binary searching loop
573 pivot = (start + end) >> 1;
574 word = wordsNoCase[pivot];
575 cond = CompareNCaseInsensitive(wordStart, word, searchLen);
576 if (!cond && nonFuncChar(word[searchLen])) // maybe there should be a "non-word character" test here?
577 return word; // result must not be freed with free()
578 else if (cond > 0)
579 start = pivot + 1;
580 else if (cond <= 0)
581 end = pivot - 1;
582 }
583 } else { // preserve the letter case
584 while (start <= end) { // binary searching loop
585 pivot = (start + end) >> 1;
586 word = words[pivot];
587 cond = strncmp(wordStart, word, searchLen);
588 if (!cond && nonFuncChar(word[searchLen])) // maybe there should be a "non-word character" test here?
589 return word; // result must not be freed with free()
590 else if (cond > 0)
591 start = pivot + 1;
592 else if (cond <= 0)
593 end = pivot - 1;
594 }
595 }
596 return NULL;
597 }
598
599 /**
600 * Find the length of a 'word' which is actually an identifier in a string
601 * which looks like "identifier(..." or "identifier:" or "identifier" and where
602 * there may be extra spaces after the identifier that should not be
603 * counted in the length.
604 */
605 static unsigned int LengthWord(const char *word, char otherSeparator) {
606 // Find a '(', or ':'. If that fails go to the end of the string.
607 const char *endWord = strchr(word, '(');
608 if (!endWord)
609 endWord = strchr(word, ':');
610 if (!endWord && otherSeparator)
611 endWord = strchr(word, otherSeparator);
612 if (!endWord)
613 endWord = word + strlen(word);
614 // Last case always succeeds so endWord != 0
615
616 // Drop any space characters.
617 if (endWord > word) {
618 endWord--; // Back from the '(', ':', or '\0'
619 // Move backwards over any spaces
620 while ((endWord > word) && (isspace(*endWord))) {
621 endWord--;
622 }
623 }
624 return endWord - word;
625 }
626
627 /**
628 * Returns elements (first words of them) of the wordlist array which have
629 * the same beginning as the passed string.
630 * The length of the word to compare is passed too.
631 * Letter case can be ignored or preserved (default).
632 * If there are more words meeting the condition they are returned all of
633 * them in the ascending order separated with spaces.
634 *
635 * NOTE: returned buffer has to be freed with delete[].
636 */
637 char *WordList::GetNearestWords(
638 const char *wordStart,
639 int searchLen /*= -1*/,
640 bool ignoreCase /*= false*/,
641 char otherSeparator /*= '\0'*/) {
642 int wordlen; // length of the word part (before the '(' brace) of the api array element
643 SString wordsNear;
644 wordsNear.setsizegrowth(1000);
645 int start = 0; // lower bound of the api array block to search
646 int end = len - 1; // upper bound of the api array block to search
647 int pivot; // index of api array element just being compared
648 int cond; // comparison result (in the sense of strcmp() result)
649
650 if (0 == words)
651 return NULL;
652 if (!sorted) {
653 sorted = true;
654 SortWordList(words, wordsNoCase, len);
655 }
656 if (ignoreCase) {
657 while (start <= end) { // Binary searching loop
658 pivot = (start + end) / 2;
659 cond = CompareNCaseInsensitive(wordStart, wordsNoCase[pivot], searchLen);
660 if (!cond) {
661 // Find first match
662 while ((pivot > start) &&
663 (0 == CompareNCaseInsensitive(wordStart,
664 wordsNoCase[pivot-1], searchLen))) {
665 --pivot;
666 }
667 // Grab each match
668 while ((pivot <= end) &&
669 (0 == CompareNCaseInsensitive(wordStart,
670 wordsNoCase[pivot], searchLen))) {
671 wordlen = LengthWord(wordsNoCase[pivot], otherSeparator) + 1;
672 wordsNear.append(wordsNoCase[pivot], wordlen, ' ');
673 ++pivot;
674 }
675 return wordsNear.detach();
676 } else if (cond < 0) {
677 end = pivot - 1;
678 } else if (cond > 0) {
679 start = pivot + 1;
680 }
681 }
682 } else { // Preserve the letter case
683 while (start <= end) { // Binary searching loop
684 pivot = (start + end) / 2;
685 cond = strncmp(wordStart, words[pivot], searchLen);
686 if (!cond) {
687 // Find first match
688 while ((pivot > start) &&
689 (0 == strncmp(wordStart,
690 words[pivot-1], searchLen))) {
691 --pivot;
692 }
693 // Grab each match
694 while ((pivot <= end) &&
695 (0 == strncmp(wordStart,
696 words[pivot], searchLen))) {
697 wordlen = LengthWord(words[pivot], otherSeparator) + 1;
698 wordsNear.append(words[pivot], wordlen, ' ');
699 ++pivot;
700 }
701 return wordsNear.detach();
702 } else if (cond < 0) {
703 end = pivot - 1;
704 } else if (cond > 0) {
705 start = pivot + 1;
706 }
707 }
708 }
709 return NULL;
710 }