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