1 //////////////////////////////////////////////////////////////////////
2 //
3 // FILE: misc.cpp
4 // Miscellaneous routines (File I/O, etc)
5 //
6 // Part of: Scid (Shane's Chess Information Database)
7 // Version: 3.5
8 //
9 // Notice: Copyright (c) 2001-2003 Shane Hudson. All rights reserved.
10 //
11 // Author: Shane Hudson (sgh@users.sourceforge.net)
12 //
13 //////////////////////////////////////////////////////////////////////
14
15 #include "common.h"
16 #include "misc.h"
17 #include <stdio.h>
18 #include <ctype.h> // For isspace() function.
19 #include <cmath>
20
21 //////////////////////////////////////////////////////////////////////
22 // ECO Code Routines
23
24 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25 // eco_FromString():
26 // Extract an ECO code from a string.
27 //
28 // Eco code numbering: no eco = 0, A00 = 1, A01 = 132, etc.
29 // That is, each basic ECO code = previous + 131.
30 // The extra 130 subcodes are the extended code:
31 // a, a1, a2, a3, a4, b, b1, .... z, z1, z2, z3, z4. (130 in total).
32 //
33 // Improvement, March 2000: now case-insensitive for first letter,
34 // for example, a41 == A41.
35 ecoT
eco_FromString(const char * ecoStr)36 eco_FromString (const char * ecoStr)
37 {
38 ecoT eco = ECO_None;
39 // Get the basic Eco code from the first 3 characters: they MUST be in
40 // the range "A00" to "E99" or the eco code will be considered empty.
41 // Changed, June 1999: now accepts partial ECO codes, e.g. "C1" -> C10
42
43 if (*ecoStr >= 'A' && *ecoStr <= 'E') {
44 eco = (*ecoStr - 'A') * 13100;
45 } else if (*ecoStr >= 'a' && *ecoStr <= 'e') {
46 eco = (*ecoStr - 'a') * 13100;
47 } else {
48 return 0;
49 }
50 ecoStr++;
51 if (! *ecoStr) { return eco + 1; }
52
53 if (*ecoStr < '0' || *ecoStr > '9') { return 0; }
54 eco += (*ecoStr - '0') * 1310;
55 ecoStr++;
56 if (! *ecoStr) { return eco + 1; }
57
58 if (*ecoStr < '0' || *ecoStr > '9') { return 0; }
59 eco += (*ecoStr - '0') * 131;
60 ecoStr++;
61
62 // Now check for the optional extended code: a, a1, ... z2, z3, z4.
63 if (*ecoStr >= 'a' && *ecoStr <= 'z') {
64 eco++;
65 eco += (*ecoStr - 'a') * 5;
66 ecoStr++;
67 if (*ecoStr >= '1' && *ecoStr <= '4') {
68 eco += *ecoStr - '0';
69 }
70 }
71 return eco + 1;
72 }
73
74 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
75 // eco_ToString():
76 // Convert an ECO code to its string representation.
77 void
eco_ToString(ecoT ecoCode,char * ecoStr,bool extensions)78 eco_ToString (ecoT ecoCode, char * ecoStr, bool extensions)
79 {
80 char * s = ecoStr;
81 if (ecoCode == ECO_None) { *s = 0; return; }
82 ecoCode--;
83
84 // First the base code value:
85
86 ecoT basicCode = ecoCode / 131; // 131 = 26 * 5 + 1 subcodes.
87 *s++ = basicCode / 100 + 'A';
88 *s++ = (basicCode % 100) / 10 + '0';
89 *s++ = (basicCode % 10) + '0';
90
91 // Now the optional extensions:
92 if (extensions) {
93 ecoCode = ecoCode % 131;
94 if (ecoCode > 0) {
95 ecoCode--;
96 *s++ = (ecoCode / 5) + 'a';
97 ecoCode = ecoCode % 5;
98 if (ecoCode > 0) { *s++ = (ecoCode + '0'); }
99 }
100 *s = 0;
101 }
102 return;
103 }
104
105 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
106 // eco_BasicCode():
107 // Converts an ECO code to its basic form, without any
108 // Scid-specific extensions.
109 ecoT
eco_BasicCode(ecoT eco)110 eco_BasicCode (ecoT eco)
111 {
112 if (eco == ECO_None) { return ECO_None; }
113
114 eco--;
115 eco /= 131;
116 eco *= 131;
117 return eco + 1;
118 }
119
120 /**
121 * ecoReduce() - maps eco to a smaller set
122 * @param eco: the eco value to convert (must be != 0)
123 *
124 * Scid ECO subcodes use 131 values for each canonical ECO.
125 * For example A00 is divided in A00,A00a,A00a1,A00a2,A00a3,A00a4,A00b...A00z4
126 * corresponding to eco values 1,2,3,4,5,6,7...131 (value 0 means no ECO).
127 * This functions will map subECOs like A00a1...A00a4 into A00a, reducing
128 * the 131 values to 27. The previous sequence will became 0,1,1,1,1,1,2...26
129 */
eco_Reduce(ecoT eco)130 ecoT eco_Reduce(ecoT eco) {
131 ASSERT(eco != 0);
132
133 eco--;
134 ecoT res = (eco / 131) * 27;
135 return res + static_cast<ecoT>(std::ceil((eco % 131) / 5.0));
136 }
137
138 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
139 // eco_LastSubCode():
140 // Converts an ECO code to the deepest subcode it could contain.
141 // Examples: B91a -> B91a4 and B91 -> B91z4.
142 ecoT
eco_LastSubCode(ecoT eco)143 eco_LastSubCode (ecoT eco)
144 {
145 if (eco == ECO_None) { return ECO_None; }
146
147 // if just a basic ECO code (1 letter, 2 digits), add the "z":
148 eco--;
149 if ((eco % 131) == 0) { eco += 126; } // 126 = 5 * 25 + 1.
150
151 // Now if no final digit, add the "4":
152 if (((eco % 131) % 5) == 1) { eco += 4; }
153 return eco + 1;
154 }
155
156 //////////////////////////////////////////////////////////////////////
157 // String Routines
158
159 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160 // strAppend():
161 // Appends extra to the end of target, and returns a pointer
162 // to the new END of the string target.
163 //
164 char *
strAppend(char * target,const char * extra)165 strAppend (char * target, const char * extra)
166 {
167 ASSERT (target != NULL && extra != NULL);
168 while (*target != 0) { target++; } // get to end of target string
169 while (*extra != 0) {
170 *target = *extra;
171 target++;
172 extra++;
173 }
174 *target = 0;
175 return target;
176 }
177
178 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179 // strDuplicate(): Duplicates a string using new[] operator.
180 //
181 char *
strDuplicate(const char * original)182 strDuplicate (const char * original)
183 {
184 ASSERT (original != NULL);
185 char * newStr = new char [strLength(original) + 1];
186 if (newStr == NULL) return NULL;
187 char *s = newStr;
188 while (*original != 0) {
189 *s = *original;
190 s++; original++;
191 }
192 *s = 0; // Add trailing '\0'.
193 //printf ("Dup: %p: %s\n", newStr, newStr);
194 return newStr;
195 }
196
197 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
198 // strPad():
199 // Copies original string to target, but copies *exactly* 'width'
200 // bytes. If the original is longer than specified width, not all of
201 // original will be copied to target. If original is shorter, then
202 // target will be padded out to 'width' bytes with the padding char.
203 //
204 // If the width is negative, no trimming or padding is done and
205 // the result is just a regular string copy.
206 //
207 // The return value is the length copied: always 'width' if
208 // width is >= 0, or the length of original if 'width' is negative.
209 //
210 uint
strPad(char * target,const char * original,int width,char padding)211 strPad (char * target, const char * original, int width, char padding)
212 {
213 ASSERT (target != NULL && original != NULL);
214 if (width < 0) {
215 strCopy (target, original);
216 return strLength (original);
217 }
218 int len = width;
219 while (len > 0) {
220 if (*original == 0) {
221 break;
222 }
223 *target = *original;
224 target++;
225 original++;
226 len--;
227 }
228 while (len--) { *target++ = padding; }
229 *target = 0;
230 return width;
231 }
232
233 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
234 // strFirstChar():
235 // Returns the pointer into the provided string where the
236 // FIRST occurrence of matchChar is, or NULL if the string
237 // does not contain matchChar at all.
238 // Equivalent to strchr().
239 const char *
strFirstChar(const char * target,char matchChar)240 strFirstChar (const char * target, char matchChar)
241 {
242 const char * s = target;
243 while (*s != 0) {
244 if (*s == matchChar) { return s; }
245 s++;
246 }
247 return NULL;
248 }
249
250 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
251 // strLastChar():
252 // Returns the pointer into the provided string where the
253 // LAST occurrence of matchChar is, or NULL if the string
254 // does not contain matchChar at all.
255 // Equivalent to strrchr().
256 const char *
strLastChar(const char * target,char matchChar)257 strLastChar (const char * target, char matchChar)
258 {
259 const char * s = target;
260 const char * last = NULL;
261 while (*s != 0) {
262 if (*s == matchChar) { last = s; }
263 s++;
264 }
265 return last;
266 }
267
268 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
269 // strStrip():
270 // Removes all occurrences of the specified char from the string.
271 void
strStrip(char * str,char ch)272 strStrip (char * str, char ch)
273 {
274 char * s = str;
275 while (*str != 0) {
276 if (*str != ch) {
277 if (s != str) { *s = *str; }
278 s++;
279 }
280 str++;
281 }
282 *s = 0;
283 }
284
285 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
286 // strTrimLeft():
287 // Returns the pointer into the provided string where the first
288 // character that does NOT equal a trimChar occurs.
289 const char *
strTrimLeft(const char * target,const char * trimChars)290 strTrimLeft (const char * target, const char * trimChars)
291 {
292 const char * s = target;
293 while (*s != 0) {
294 if (! strContainsChar (trimChars, *s)) { break; }
295 s++;
296 }
297 return s;
298 }
299
300 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
301 // strTrimSuffix():
302 // Trims the provided string in-place, at the last
303 // occurrence of the provided suffix character.
304 // Returns the number of characters trimmed.
305 // E.g., strTrimSuffix ("file.txt", '.') would leave the
306 // string as "file" and return 4.
307 uint
strTrimSuffix(char * target,char suffixChar)308 strTrimSuffix (char * target, char suffixChar)
309 {
310 uint trimCount = 0;
311 char * lastSuffixPtr = NULL;
312 char * s = target;
313 while (*s) {
314 if (*s == suffixChar) {
315 lastSuffixPtr = s;
316 trimCount = 0;
317 }
318 trimCount++;
319 s++;
320 }
321 if (lastSuffixPtr == NULL) { return 0; }
322 *lastSuffixPtr = 0;
323 return trimCount;
324 }
325
326 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
327 // strTrimDate():
328 // Takes a date string ("xxxx.xx.xx" format) and trims
329 // the day part if it is ".??", and also the month part
330 // if it too is ".??".
331 void
strTrimDate(char * str)332 strTrimDate (char * str)
333 {
334 if (str[7] == '.' && str[8] == '?' && str[9] == '?') {
335 str[7] = 0;
336 if (str[4] == '.' && str[5] == '?' && str[6] == '?') {
337 str[4] = 0;
338 }
339 }
340 }
341
342 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
343 // strTrimMarkCodes():
344 // Trims in-place all Scid-recognised board mark codes
345 // in a comment string, such as "[%mark ...]" and "[%arrow ...]"
346 void
strTrimMarkCodes(char * str)347 strTrimMarkCodes (char * str)
348 {
349 char * in = str;
350 char * out = str;
351 bool inCode = false;
352 char * startLocation = NULL;
353
354 while (1) {
355 char ch = *in;
356 if (inCode) {
357 // If we see end-of-string or code-starting '[', there is some
358 // error so go back to the start of this code and treat it
359 // normally.
360 if (ch == 0 || ch == '[') {
361 *out++ = *startLocation;
362 inCode = false;
363 in = startLocation;
364 } else if (ch == ']') {
365 // See a code-ending ']', so end the code.
366 inCode = false;
367 }
368 // For all other characters in a code, just ignore it.
369 } else {
370 // Stop at end-of-string:
371 if (ch == 0) { break; }
372 // Look for the start of a code that is to be stripped:
373 if (ch == '[' && in[1] == '%') {
374 inCode = true;
375 startLocation = in;
376 } else {
377 *out++ = ch;
378 }
379 }
380 in++;
381 }
382 // Terminate the modified string:
383 *out = 0;
384 }
385
386 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
387 // strTrimMarkup():
388 // Trims in-place all HTML-like markup codes (<b>, </i>, etc)
389 // from the provided string.
390 void
strTrimMarkup(char * str)391 strTrimMarkup (char * str)
392 {
393 char * in = str;
394 char * out = str;
395 bool inTag = false;
396
397 while (*in != 0) {
398 char ch = *in;
399 if (inTag) {
400 if (ch == '>') { inTag = false; }
401 } else {
402 if (ch == '<') { inTag = true; } else { *out++ = ch; }
403 }
404 in++;
405 }
406 *out = 0;
407 }
408
409 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
410 // strFirstWord:
411 // Skips over all whitespace at the start of the
412 // string to reach the first word.
413 const char *
strFirstWord(const char * str)414 strFirstWord (const char * str)
415 {
416 ASSERT (str != NULL);
417 while (*str != 0 && isspace(static_cast<unsigned char>(*str))) { str++; }
418 return str;
419 }
420
421 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
422 // strNextWord:
423 // Skips over all successive non-whitespace characters
424 // in the string, then all successive whitespace chars,
425 // to reach the next word in the string.
426 const char *
strNextWord(const char * str)427 strNextWord (const char * str)
428 {
429 ASSERT (str != NULL);
430 while (*str != 0 && !isspace(static_cast<unsigned char>(*str))) { str++; }
431 while (*str != 0 && isspace(static_cast<unsigned char>(*str))) { str++; }
432 return str;
433 }
434
435 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
436 // strIsUnknownName():
437 // Returns true if the string is an "unknown" name: the empty
438 // string, "?" or "-". Used primarily to test if an event, site
439 // or round name string contains information worth printing.
440 bool
strIsUnknownName(const char * str)441 strIsUnknownName (const char * str)
442 {
443 if (str[0] == 0) { return true; }
444 if (str[0] == '-' && str[1] == 0) { return true; }
445 if (str[0] == '?' && str[1] == 0) { return true; }
446 return false;
447 }
448
449 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
450 // strIsSurnameOnly():
451 // Returns true if the name appears to be a surname only.
452 bool
strIsSurnameOnly(const char * name)453 strIsSurnameOnly (const char * name)
454 {
455 uint capcount = 0;
456 const char * s = name;
457 while (*s != 0) {
458 unsigned char c = *s;
459 if (! isalpha(c)) { return false; }
460 if (isupper(c)) {
461 capcount++;
462 if (capcount > 1) { return false; }
463 }
464 s++;
465 }
466 return true;
467 }
468
469 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
470 // strGetBoolean():
471 // Extracts a boolean value from a string.
472 // True strings start with one of "TtYy1", false strings with
473 // one of "FfNn0".
474 // Returns false if the string does not contain a boolean value.
475 bool
strGetBoolean(const char * str)476 strGetBoolean (const char * str)
477 {
478 static const char * sTrue[] = {
479 "true", "yes", "on", "1", "ja", "si", "oui", NULL
480 };
481 static const char * sFalse[] = {
482 "false", "no", "off", "0", NULL
483 };
484 if (str[0] == 0) { return false; }
485
486 bool matchedTrue = false;
487 bool matchedFalse = false;
488
489 const char ** next = sTrue;
490 while (*next != NULL) {
491 if (strIsCasePrefix (str, *next) || strIsCasePrefix (*next, str)) {
492 matchedTrue = true;
493 }
494 next++;
495 }
496 next = sFalse;
497 while (*next != NULL) {
498 if (strIsCasePrefix (str, *next) || strIsCasePrefix (*next, str)) {
499 matchedFalse = true;
500 }
501 next++;
502 }
503 if (matchedTrue && !matchedFalse) { return true; }
504 if (matchedFalse && !matchedTrue) { return false; }
505
506 // default: return false.
507 return false;
508 }
509
510 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
511 // strGetIntegers:
512 // Extracts the specified number of signed integers in a
513 // whitespace-separated string to an array.
514 void
strGetIntegers(const char * str,int * results,uint nResults)515 strGetIntegers (const char * str, int * results, uint nResults)
516 {
517 for (uint i=0; i < nResults; i++) {
518 while (*str != 0 && isspace(static_cast<unsigned char>(*str))) { str++; }
519 results[i] = strGetInteger (str);
520 while (*str != 0 && !isspace(static_cast<unsigned char>(*str))) { str++; }
521 }
522 }
523
524 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
525 // strGetUnsigneds:
526 // Extracts the specified number of unsigned integers in a
527 // whitespace-separated string to an array.
528 void
strGetUnsigneds(const char * str,uint * results,uint nResults)529 strGetUnsigneds (const char * str, uint * results, uint nResults)
530 {
531 for (uint i=0; i < nResults; i++) {
532 while (*str != 0 && isspace(static_cast<unsigned char>(*str))) { str++; }
533 results[i] = strGetUnsigned (str);
534 while (*str != 0 && !isspace(static_cast<unsigned char>(*str))) { str++; }
535 }
536 }
537
538 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
539 // strGetResult:
540 // Extracts a game result value from a string.
541 resultT
strGetResult(const char * str)542 strGetResult (const char * str)
543 {
544 switch (*str) {
545 case '1':
546 // Check for "1/2"-style draw result:
547 if (str[1] == '/' && str[2] == '2') {
548 return RESULT_Draw;
549 }
550 return RESULT_White;
551 case '=': return RESULT_Draw;
552 case '0': return RESULT_Black;
553 case '*': return RESULT_None;
554 }
555 return RESULT_None;
556 }
557
558 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
559 // strGetFlag():
560 // Extracts a flag (FLAG_YES, FLAG_NO or FLAG_BOTH) value from
561 // a string. Defaults to FLAG_EMPTY.
562 flagT
strGetFlag(const char * str)563 strGetFlag (const char * str)
564 {
565 char c = *str;
566 switch (c) {
567 case 'T': case 't':
568 case 'Y': case 'y':
569 case 'J': case 'j':
570 case 'O': case 'o':
571 case 'S': case 's':
572 case '1':
573 return FLAG_YES;
574 case 'F': case 'f':
575 case 'N': case 'n':
576 case '0':
577 return FLAG_NO;
578 case 'B': case 'b':
579 case '2':
580 return FLAG_BOTH;
581 }
582 // default: return empty.
583 return FLAG_EMPTY;
584 }
585
586 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
587 // strGetSquare():
588 // Extracts a square value from a string, such as "a2".
589 squareT
strGetSquare(const char * str)590 strGetSquare (const char * str)
591 {
592 char chFyle = str[0];
593 if (chFyle < 'a' || chFyle > 'h') { return NULL_SQUARE; }
594 char chRank = str[1];
595 if (chRank < '1' || chRank > '8') { return NULL_SQUARE; }
596 return square_Make (fyle_FromChar(chFyle), rank_FromChar(chRank));
597 }
598
599 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
600 // strUniqueExactMatch():
601 // Given a string <keyStr> and a null-terminated array of strings
602 // <strTable>, returns the index of the unique match of the key
603 // string in the string table. If no match was found, or there was
604 // more than one match, -1 is returned.
605 //
606 // If the flag <exact> is true, only complete matches are considered.
607 // Otherwise, unique abbreviations are accepted.
608 // Example: looking up "repl" in {"repeat", "replace", NULL} would
609 // return 1 (matching "replace") but looking up "rep" would
610 // return -1 because its match is ambiguous.
611 //
612 // The array "strTable" does NOT need to be in any order, but the last
613 // entry must be NULL.
614 int
strUniqueExactMatch(const char * keyStr,const char ** strTable,bool exact)615 strUniqueExactMatch (const char * keyStr, const char ** strTable, bool exact)
616 {
617 int index = -1;
618 int abbrevMatches = 0;
619 const char * s1;
620 const char * s2;
621 const char ** entryPtr = strTable;
622
623 // If keyStr or strTable are null, return no match:
624 if (keyStr == NULL || strTable == NULL) { return -1; }
625
626 // Check each entry in turn:
627 for (int i=0; *entryPtr != NULL; entryPtr++, i++) {
628 // Check the key against this entry, character by character:
629 for (s1 = keyStr, s2 = *entryPtr; *s1 == *s2; s1++, s2++) {
630 // If *s1 is 0, we found an EXACT match, so return it now:
631 if (*s1 == 0) {
632 return i;
633 }
634 }
635 // If *s1 == 0 now, key is an abbreviation of this entry:
636 if (*s1 == 0) {
637 index = i;
638 abbrevMatches++;
639 }
640 }
641
642 // If we reach here, there is no exact match. If an exact match was
643 // required, or there is not exactly one abbreviation, return no match:
644 if (exact || abbrevMatches != 1) {
645 return -1;
646 }
647 // Otherwise, return the match found:
648 return index;
649 }
650
651 //////////////////////////////////////////////////////////////////////
652 // EOF: misc.cpp
653 //////////////////////////////////////////////////////////////////////
654
655