1 /*
2 OpenLieroX
3
4 string utilities
5
6 code under LGPL
7 created 01-05-2007
8 by Albert Zeyer and Dark Charlie
9 */
10
11 #ifdef _MSC_VER
12 #pragma warning(disable: 4786) // WARNING: identifier XXX was truncated to 255 characters in the debug info...
13 #endif
14
15 #include "LieroX.h" // for tLX
16
17 #include "StringUtils.h"
18 #include "CFont.h"
19 #include "ConfigHandler.h" // for getting color value from data/frontend/colours.cfg
20 #include "FindFile.h"
21 #include "MathLib.h"
22 #include "StringBuf.h"
23
24
25 #include <sstream>
26 #include <iomanip>
27 // HTML parsing library for StripHtmlTags()
28 #include <libxml/xmlmemory.h>
29 #include <libxml/HTMLparser.h>
30 #include <zlib.h>
31
32
33 ///////////////////
34 // Trim the leading & ending spaces from a string
TrimSpaces(std::string & szLine)35 void TrimSpaces(std::string& szLine) {
36 size_t n = 0;
37 std::string::iterator p;
38 for(p = szLine.begin(); p != szLine.end(); p++, n++)
39 if(!isspace((uchar)*p) || isgraph((uchar)*p)) break;
40 if(n>0) szLine.erase(0,n);
41
42 n = 0;
43 std::string::reverse_iterator p2;
44 for(p2 = szLine.rbegin(); p2 != szLine.rend(); p2++, n++)
45 if(!isspace((uchar)*p2) || isgraph((uchar)*p2)) break;
46 if(n>0) szLine.erase(szLine.size()-n);
47 }
48
49
50 ///////////////////
51 // Replace a string in text, returns true, if something was replaced
replace(const std::string & text,const std::string & what,const std::string & with,std::string & result)52 bool replace(const std::string& text, const std::string& what, const std::string& with, std::string& result)
53 {
54 result = text;
55 return replace(result, what, with);
56 }
57
58 ///////////////////
59 // Replace a string in text, returns result, replaces maximally max occurences
replacemax(const std::string & text,const std::string & what,const std::string & with,std::string & result,int max)60 std::string replacemax(const std::string& text, const std::string& what, const std::string& with, std::string& result, int max)
61 {
62 result = text;
63
64 size_t pos = 0;
65 size_t what_len = what.length();
66 size_t with_len = with.length();
67 if((pos = result.find(what, pos)) != std::string::npos) {
68 result.replace(pos, what_len, with);
69 pos += with_len;
70 }
71
72 return result;
73 }
74
replacemax(const std::string & text,const std::string & what,const std::string & with,int max)75 std::string replacemax(const std::string& text, const std::string& what, const std::string& with, int max) {
76 std::string result;
77 return replacemax(text, what, with, result, max);
78 }
79
80 ///////////////////
81 // Replace a string in text, returns result, replaces maximally max occurences
82 // returns true, if at least one replace was made
replace(std::string & text,const std::string & what,const std::string & with)83 bool replace(std::string& text, const std::string& what, const std::string& with) {
84 // Make sure there is something to replace
85 if (!what.size()) {
86 return false;
87 }
88
89 bool one_repl = false;
90 size_t pos = 0;
91 while((pos = text.find(what, pos)) != std::string::npos) {
92 text.replace(pos, what.length(), with);
93 pos += with.length();
94 one_repl = true;
95 }
96 return one_repl;
97 }
98
99 // Previous function has uncomfortable API
Replace(const std::string & text,const std::string & what,const std::string & with)100 std::string Replace(const std::string & text, const std::string& what, const std::string& with)
101 {
102 std::string tmp;
103 std::string::size_type idx1 = 0, idx2 = 0;
104
105 while( ( idx2 = text.find(what, idx1) ) != std::string::npos )
106 {
107 tmp.append( text, idx1, idx2 - idx1 );
108 tmp.append(with);
109 idx1 = idx2 + what.size();
110 }
111 if( tmp == "" )
112 return text;
113 tmp.append( text, idx1, text.size() - idx1 );
114 return tmp;
115 }
116
117 // chrcasecmp - like strcasecomp, but for a single char
chrcasecmp(const char c1,const char c2)118 int chrcasecmp(const char c1, const char c2)
119 {
120 return (tolower(c1) == tolower(c2));
121 }
122
123 //////////////////
124 // Gets the string [beginning of text,searched character)
ReadUntil(const std::string & text,char until_character)125 std::string ReadUntil(const std::string& text, char until_character) {
126 size_t pos = 0;
127 for(std::string::const_iterator i = text.begin(); i != text.end(); i++, pos++) {
128 if(*i == until_character)
129 return text.substr(0, pos);
130 }
131 return text;
132 }
133
ReadUntil(const std::string & text,std::string::const_iterator & it,char until_character,const std::string & alternative)134 std::string ReadUntil(const std::string& text, std::string::const_iterator& it, char until_character, const std::string& alternative) {
135 std::string::const_iterator start = it;
136 for(; it != text.end(); it++) {
137 if(*it == until_character)
138 return std::string(start, it);
139 }
140 return alternative;
141 }
142
143
ReadUntil(FILE * fp,char until_character)144 std::string ReadUntil(FILE* fp, char until_character) {
145 char buf[256];
146 std::string res;
147 res = "";
148 size_t buf_pos = 0;
149 while(true) {
150 if(fread(&buf[buf_pos],1,1,fp) == 0 || buf[buf_pos] == until_character) {
151 res.append(buf,buf_pos);
152 break;
153 }
154 buf_pos++;
155 if(buf_pos >= sizeof(buf)) {
156 buf_pos = 0;
157 res.append(buf,sizeof(buf));
158 }
159 }
160
161 return res;
162 }
163
PrettyPrint(const std::string & prefix,const std::string & buf,const PrintOutFct & printOutFct,bool firstLineWithPrefix)164 bool PrettyPrint(const std::string& prefix, const std::string& buf, const PrintOutFct& printOutFct, bool firstLineWithPrefix) {
165 std::string::const_iterator it = buf.begin();
166 bool firstLine = true;
167 while(true) {
168 std::string tmp = ReadUntil(buf, it, '\n', std::string(it, buf.end()));
169 if(it == buf.end()) {
170 if(tmp != "") {
171 printOutFct.print( (!firstLineWithPrefix && firstLine) ? tmp : (prefix + tmp) );
172 return false;
173 }
174 return !firstLine || firstLineWithPrefix;
175 }
176 ++it;
177 printOutFct.print( (!firstLineWithPrefix && firstLine) ? (tmp + "\n") : (prefix + tmp + "\n") );
178 firstLine = false;
179 }
180 }
181
182
HexDump(Iterator<char>::Ref start,const PrintOutFct & printOutFct,const std::set<size_t> & marks,size_t count)183 Iterator<char>::Ref HexDump(Iterator<char>::Ref start, const PrintOutFct& printOutFct, const std::set<size_t>& marks, size_t count) {
184 std::string tmpLeft;
185 std::string tmpRight;
186 unsigned int tmpChars = 0;
187 static const unsigned int charsInLine = 16;
188
189 size_t c = 0;
190 while(start->isValid() && c < count) {
191 unsigned char ch = start->get();
192
193 tmpLeft += FixedWidthStr_LeftFill(hex(ch), 2, '0');
194 tmpLeft += marks.count(c) ? "]" : marks.count(c + 1) ? "[" : " ";
195 if(ch >= 32 && ch <= 126)
196 tmpRight += ch;
197 else
198 tmpRight += '.';
199
200 tmpChars++;
201 if(tmpChars == charsInLine / 2) {
202 tmpLeft += " ";
203 tmpRight += " ";
204 }
205 if(tmpChars == charsInLine) {
206 printOutFct.print( tmpLeft + "| " + tmpRight + "\n" );
207 tmpChars = 0;
208 tmpLeft = tmpRight = "";
209 }
210
211 start->next();
212 c++;
213 }
214
215 tmpLeft += std::string((charsInLine - tmpChars) * 3, ' ');
216 tmpRight += std::string((charsInLine - tmpChars), ' ');
217 if(tmpChars < charsInLine / 2) { tmpLeft += " "; tmpRight += " "; }
218 printOutFct.print( tmpLeft + "| " + tmpRight + "\n" );
219
220 return start;
221 }
222
223
224 //////////////////////////
225 // Contains a definition of some common colors, used internally by StrToCol
GetColorByName(const std::string & str,bool & fail)226 Color GetColorByName(const std::string& str, bool& fail)
227 {
228 fail = false;
229
230 struct ColorEntry {
231 std::string sName;
232 Uint8 r, g, b, a;
233 };
234 #define OP SDL_ALPHA_OPAQUE
235 #define TR SDL_ALPHA_TRANSPARENT
236
237 // TODO: more? These are taken from the HTML specification
238 static const ColorEntry predefined[] = {
239 { "white", 255, 255, 255,OP },
240 { "black", 0, 0, 0, OP },
241 { "red", 255, 0, 0, OP },
242 { "green", 0, 127, 0, OP },
243 { "blue", 0, 0, 255, OP },
244 { "silver", 0xC0, 0xC0, 0xC0, OP },
245 { "gray", 127, 127, 127, OP },
246 { "grey", 127, 127, 127, OP },
247 { "purple", 127, 0, 127, OP },
248 { "fuchsia", 255, 0, 255, OP },
249 { "pink", 255, 0, 255, OP },
250 { "lime", 0, 255, 0, OP },
251 { "olive", 127, 127, 0, OP },
252 { "yellow", 255, 255, 0, OP },
253 { "navy", 0, 0, 127, OP },
254 { "teal", 0, 127, 127, OP },
255 { "aqua", 0, 255, 255, OP },
256 { "gold", 255, 215, 0, OP },
257 { "transparent", 0, 0, 0, TR }
258 };
259
260 for (size_t i = 0; i < sizeof(predefined) / sizeof(ColorEntry); ++i)
261 if (stringcaseequal(str, predefined[i].sName))
262 return Color(predefined[i].r, predefined[i].g, predefined[i].b, predefined[i].a);
263
264 fail = true;
265
266 return Color();
267 }
268
269 /////////////////////
270 // Helper function for HexToCol, accepts only adjusted values
HexToCol_Pure(const std::string & hex,Color & col,bool & fail)271 static void HexToCol_Pure(const std::string& hex, Color& col, bool& fail)
272 {
273 col.r = MIN(from_string<int>(hex.substr(0,2), std::hex, fail), 255);
274 col.g = MIN(from_string<int>(hex.substr(2,2), std::hex, fail), 255);
275 col.b = MIN(from_string<int>(hex.substr(4,2), std::hex, fail), 255);
276 if (hex.size() >= 8)
277 col.a = MIN(from_string<int>(hex.substr(6,2), std::hex, fail), 255);
278 else
279 col.a = SDL_ALPHA_OPAQUE;
280 }
281
282 ////////////////////////
283 // Helper function for StrToCol
HexToCol(const std::string & hex,bool & fail)284 static Color HexToCol(const std::string& hex, bool& fail)
285 {
286 fail = false;
287 Color res;
288
289 // For example FFF for white
290 if (hex.size() == 3) {
291 std::string tmp;
292 tmp += hex[0]; tmp += hex[0];
293 tmp += hex[1]; tmp += hex[1];
294 tmp += hex[2]; tmp += hex[2];
295
296 HexToCol_Pure(tmp, res, fail);
297
298 return res;
299 }
300
301 // Same as the above but with alpha
302 if (hex.size() == 4) {
303 std::string tmp;
304 tmp += hex[0]; tmp += hex[0];
305 tmp += hex[1]; tmp += hex[1];
306 tmp += hex[2]; tmp += hex[2];
307 tmp += hex[3]; tmp += hex[3];
308
309 HexToCol_Pure(tmp, res, fail);
310
311 return res;
312 }
313
314 // For example FFFFFF for white
315 if (hex.size() == 6) {
316 HexToCol_Pure(hex, res, fail);
317 return res;
318 }
319
320 // Same as the above but with alpha
321 if (hex.size() >= 8) {
322 HexToCol_Pure(hex, res, fail);
323 return res;
324 }
325
326 fail = true;
327 return Color();
328 }
329
ColToHex(Color col)330 std::string ColToHex(Color col) {
331 std::string buf;
332 buf += FixedWidthStr_LeftFill(itoa(col.r, 16), 2, '0') +
333 buf += FixedWidthStr_LeftFill(itoa(col.g, 16), 2, '0') +
334 buf += FixedWidthStr_LeftFill(itoa(col.b, 16), 2, '0');
335 if(col.a != SDL_ALPHA_OPAQUE)
336 buf += FixedWidthStr_LeftFill(itoa(col.a, 16), 2, '0');
337
338 return "#" + buf;
339 }
340
341
342 //////////////////////
343 // Returns true if the value ends with a percent sign
is_percent(const std::string & str)344 static bool is_percent(const std::string& str)
345 {
346 if (!str.size())
347 return false;
348 return (*(str.rbegin())) == '%';
349 }
350
351 ///////////////////
352 // Returns a color defined function-like: rgba(0, 0, 0, 0)
ColFromFunc(const std::string & func,bool & fail)353 static Color ColFromFunc(const std::string& func, bool& fail)
354 {
355 StringBuf tmp(func);
356
357 tmp.trimBlank();
358 tmp.adjustBlank();
359 std::string func_name = tmp.readUntil('(');
360 TrimSpaces(func_name);
361
362 // Get the params
363 StringBuf params = tmp.readUntil(')');
364 std::vector<std::string> tokens = params.splitBy(',');
365 if (tokens.size() < 3) {
366 fail = true;
367 return Color();
368 }
369
370 // Adjust the tokens
371 for (std::vector<std::string>::iterator it = tokens.begin(); it != tokens.end(); it++) {
372 TrimSpaces(*it);
373 }
374
375 // The param count is >= 3
376 Color res;
377 res.r = MIN(255, from_string<int>(tokens[0], fail)); if (is_percent(tokens[0])) res.r = (Uint8)MIN(255.0f, (float)res.r * 2.55f);
378 res.g = MIN(255, from_string<int>(tokens[1], fail)); if (is_percent(tokens[1])) res.g = (Uint8)MIN(255.0f, (float)res.g * 2.55f);
379 res.b = MIN(255, from_string<int>(tokens[2], fail)); if (is_percent(tokens[2])) res.b = (Uint8)MIN(255.0f, (float)res.b * 2.55f);
380 if (tokens.size() >= 4 && stringcaseequal(func_name, "rgba")) {
381 res.a = MIN(255, from_string<int>(tokens[3], fail)); if (is_percent(tokens[3])) res.a = (Uint8)MIN(255.0f, (float)res.a * 2.55f);
382 } else
383 res.a = SDL_ALPHA_OPAQUE;
384
385 return res;
386 }
387
ColFromSeperatedNums(const std::string & txt,bool & fail)388 static Color ColFromSeperatedNums(const std::string& txt, bool& fail) {
389 std::vector<std::string> tokens = explode(txt, ",");
390 if (tokens.size() < 3) {
391 fail = true;
392 return Color();
393 }
394
395 Color res;
396 res.r = MIN(255, from_string<int>(tokens[0], fail)); if (is_percent(tokens[0])) res.r = (Uint8)MIN(255.0f, (float)res.r * 2.55f);
397 res.g = MIN(255, from_string<int>(tokens[1], fail)); if (is_percent(tokens[1])) res.g = (Uint8)MIN(255.0f, (float)res.g * 2.55f);
398 res.b = MIN(255, from_string<int>(tokens[2], fail)); if (is_percent(tokens[2])) res.b = (Uint8)MIN(255.0f, (float)res.b * 2.55f);
399 if(tokens.size() >= 4) {
400 res.a = MIN(255, from_string<int>(tokens[3], fail)); if (is_percent(tokens[3])) res.a = (Uint8)MIN(255.0f, (float)res.a * 2.55f);
401 } else
402 res.a = SDL_ALPHA_OPAQUE;
403
404 return res;
405 }
406
407 //////////////////
408 // Converts a string to a colour
StrToCol(const std::string & str,bool & fail)409 Color StrToCol(const std::string& str, bool& fail) {
410 fail = false;
411
412 // Create the temp and copy it there
413 std::string temp = str;
414 TrimSpaces(temp);
415
416 if(temp.size() > 0 && temp[temp.size()-1] == ';') {
417 temp.erase(temp.size()-1);
418 TrimSpaces(temp);
419 }
420
421 // Check for a blank string
422 if (temp.size() == 0) {
423 fail = true;
424 return Color();
425 }
426
427 // Is the # character present?
428 if (temp[0] == '#') { // str != "" here
429 temp.erase(0,1);
430 stringlwr(temp);
431 return HexToCol(temp, fail);
432 }
433
434 // Is the function-style present?
435 if (temp.size() >= 4)
436 if (stringcaseequal(temp.substr(0, 4), "rgba"))
437 return ColFromFunc(temp, fail);
438 if (temp.size() >= 3)
439 if (stringcaseequal(temp.substr(0, 3), "rgb"))
440 return ColFromFunc(temp, fail);
441
442 // like "r,g,b", e.g. this is used in gamescripts
443 if(temp.find(",") != std::string::npos)
444 return ColFromSeperatedNums(temp, fail);
445
446 // Check if it's a known predefined color
447 return GetColorByName(str, fail);
448 }
449
StrToCol(const std::string & str)450 Color StrToCol(const std::string& str)
451 {
452 bool fail = false;
453 return StrToCol(str, fail);
454 }
455
stringcasecmp(const std::string & s1,const std::string & s2)456 short stringcasecmp(const std::string& s1, const std::string& s2) {
457 std::string::const_iterator p1, p2;
458 p1 = s1.begin();
459 p2 = s2.begin();
460 short dif;
461 while(true) {
462 if(p1 == s1.end()) {
463 if(p2 == s2.end())
464 return 0;
465 // not at end of s2
466 return -1; // s1 < s2
467 }
468 if(p2 == s2.end())
469 // not at end of s1
470 return 1; // s1 > s2
471
472 dif = (short)(uchar)tolower((uchar)*p1) - (short)(uchar)tolower((uchar)*p2);
473 if(dif != 0) return dif; // dif > 0 <=> s1 > s2
474
475 p1++; p2++;
476 }
477 }
478
stringcaseequal(const std::string & s1,const std::string & s2)479 bool stringcaseequal(const std::string& s1, const std::string& s2) {
480 if (s1.size() != s2.size()) return false;
481 return stringcasecmp(s1, s2) == 0;
482 }
483
subStrEqual(const std::string & s1,const std::string s2,size_t p)484 bool subStrEqual(const std::string& s1, const std::string s2, size_t p) {
485 if((s1.size() < p || s2.size() < p) && s1.size() != s2.size()) return false;
486 for(size_t i = 0; i < p && i < s1.size(); i++)
487 if(s1[i] != s2[i]) return false;
488 return true;
489 }
490
subStrCaseEqual(const std::string & s1,const std::string s2,size_t p)491 bool subStrCaseEqual(const std::string& s1, const std::string s2, size_t p) {
492 if((s1.size() < p || s2.size() < p) && s1.size() != s2.size()) return false;
493 for(size_t i = 0; i < p && i < s1.size(); i++)
494 if(tolower(s1[i]) != tolower(s2[i])) return false;
495 return true;
496 }
497
maxStartingEqualStr(const std::list<std::string> & strs,bool caseSensitive)498 static size_t maxStartingEqualStr(const std::list<std::string>& strs, bool caseSensitive) {
499 if(strs.size() == 0) return 0;
500
501 size_t l = 0;
502 while(true) {
503 int i = 0;
504 char c = 0;
505 for(std::list<std::string>::const_iterator it = strs.begin(); it != strs.end(); ++it, ++i) {
506 if(it->size() <= l) return l;
507 if(i == 0)
508 c = (*it)[l];
509 else {
510 bool equal = false;
511 if(caseSensitive) equal = (*it)[l] == c;
512 else equal = tolower((*it)[l]) == tolower(c);
513 if(!equal) return l;
514 }
515 }
516
517 l++;
518 }
519 }
520
maxStartingEqualStr(const std::list<std::string> & strs)521 size_t maxStartingEqualStr(const std::list<std::string>& strs) {
522 return maxStartingEqualStr(strs, true);
523 }
524
maxStartingCaseEqualStr(const std::list<std::string> & strs)525 size_t maxStartingCaseEqualStr(const std::list<std::string>& strs) {
526 return maxStartingEqualStr(strs, false);
527 }
528
529
explode(const std::string & str,const std::string & delim)530 std::vector<std::string> explode(const std::string& str, const std::string& delim) {
531 std::vector<std::string> result;
532
533 size_t delim_len = delim.size();
534 std::string rest = str;
535 size_t pos;
536 while((pos = rest.find(delim)) != std::string::npos) {
537 result.push_back(rest.substr(0,pos));
538 rest.erase(0,pos+delim_len);
539 }
540 result.push_back(rest);
541
542 return result;
543 }
544
545 // reads up to maxlen-1 chars from fp
freadstr(std::string & result,size_t maxlen,FILE * fp)546 void freadstr(std::string& result, size_t maxlen, FILE *fp) {
547 if (!fp) return;
548
549 char buf[1024];
550 size_t ret, c;
551 result = "";
552
553 for(size_t len = 0; len < maxlen; len += sizeof(buf)) {
554 c = MIN(sizeof(buf), maxlen - len);
555 ret = fread(buf, 1, c, fp);
556 if(ret > 0)
557 result.append(buf, ret);
558 if(ret < c)
559 break;
560 }
561 }
562
563
fwrite(const std::string & txt,size_t len,FILE * fp)564 size_t fwrite(const std::string& txt, size_t len, FILE* fp) {
565 size_t len_of_txt = MIN(txt.size()+1, len-1);
566 size_t ret = fwrite(txt.c_str(), 1, len_of_txt, fp);
567 if(ret != len_of_txt)
568 return ret;
569 for(; len_of_txt < len; len_of_txt++)
570 if(fwrite("\0", 1, 1, fp) == 0)
571 return len_of_txt;
572 return len;
573 }
574
575
findLastPathSep(const std::string & path)576 size_t findLastPathSep(const std::string& path) {
577 size_t slash = path.rfind('\\');
578 size_t slash2 = path.rfind('/');
579 if(slash == std::string::npos)
580 slash = slash2;
581 else if(slash2 != std::string::npos)
582 slash = MAX(slash, slash2);
583 return slash;
584 }
585
586
stringlwr(std::string & txt)587 void stringlwr(std::string& txt) {
588 for(std::string::iterator i = txt.begin(); i != txt.end(); i++)
589 *i = tolower((uchar)*i);
590 }
591
stringtolower(const std::string & txt)592 std::string stringtolower(const std::string& txt)
593 {
594 std::string res;
595 for(std::string::const_iterator i = txt.begin(); i != txt.end(); i++)
596 res += tolower((uchar)*i);
597 return res;
598 }
599
600
strincludes(const std::string & str,const std::string & what)601 bool strincludes(const std::string& str, const std::string& what) {
602 return str.find(what) != std::string::npos;
603 }
604
GetFileExtension(const std::string & path)605 std::string GetFileExtension(const std::string& path) {
606 std::string filename = GetBaseFilename(path);
607 size_t p = filename.rfind('.');
608 if(p == std::string::npos) return "";
609 return filename.substr(p+1);
610 }
611
GetBaseFilename(const std::string & filename)612 std::string GetBaseFilename(const std::string& filename) {
613 size_t p = findLastPathSep(filename);
614 if(p == std::string::npos) return filename;
615 return filename.substr(p+1);
616 }
617
GetDirName(const std::string & filename)618 std::string GetDirName(const std::string& filename) {
619 size_t p = findLastPathSep(filename);
620 if(p == std::string::npos) return "";
621 return filename.substr(0, p);
622 }
623
GetBaseFilenameWithoutExt(const std::string & filename)624 std::string GetBaseFilenameWithoutExt(const std::string& filename) {
625 std::string f = GetBaseFilename(filename);
626 size_t p = f.rfind('.');
627 if(p == std::string::npos) return f;
628 return f.substr(0,p);
629 }
630
SplitFilename(const std::string & filename,size_t numPartsFromRight)631 std::list<std::string> SplitFilename(const std::string& filename, size_t numPartsFromRight) {
632 std::list<std::string> ret;
633 std::string restFn = filename;
634 while(ret.size() < numPartsFromRight) {
635 std::string next = GetBaseFilename(restFn);
636 ret.push_front(next);
637 if(next.size() == restFn.size()) break;
638 restFn.erase(restFn.size() - next.size() - 1);
639 }
640 return ret;
641 }
642
643
644
ucfirst(std::string & text)645 void ucfirst(std::string& text)
646 {
647 if (text == "") return;
648
649 text[0] = toupper(text[0]);
650 bool wasalpha = isalpha((uchar)text[0]) != 0;
651
652 for (std::string::iterator it=text.begin()+1;it != text.end();it++) {
653 if (isalpha((uchar)*it)) {
654 if (wasalpha)
655 *it = tolower((uchar)*it);
656 else
657 *it = toupper((uchar)*it);
658 wasalpha = true;
659 } else {
660 wasalpha = false;
661 }
662 }
663
664
665 }
666
667
668 /////////////////////////
669 // Find a substring in a string
670 // WARNING: does NOT support UTF8, use Utf8StringCaseFind instead
stringcasefind(const std::string & text,const std::string & search_for)671 size_t stringcasefind(const std::string& text, const std::string& search_for)
672 {
673 if (text.size() == 0 || search_for.size() == 0 || search_for.size() > text.size())
674 return std::string::npos;
675
676 std::string::const_iterator it1 = text.begin();
677 std::string::const_iterator it2 = search_for.begin();
678
679 size_t number_of_same = 0;
680 size_t result = 0;
681
682 // Go through the text
683 while (it1 != text.end()) {
684 char c1 = (char)tolower((uchar)*it1);
685 char c2 = (char)tolower((uchar)*it2);
686
687 // The two characters are the same
688 if (c1 == c2) {
689 number_of_same++; // If number of same characters equals to the size of the substring, we've found it!
690 if (number_of_same == search_for.size())
691 return result - number_of_same + 1;
692 it2++;
693 } else {
694 number_of_same = 0;
695 it2 = search_for.begin();
696 }
697
698 result++;
699 it1++;
700 }
701
702 return std::string::npos; // Not found
703 }
704
705 /////////////////////////
706 // Find a substring in a string, starts searching from the end of the text
707 // WARNING: does NOT support UTF8
stringcaserfind(const std::string & text,const std::string & search_for)708 size_t stringcaserfind(const std::string& text, const std::string& search_for)
709 {
710 // HINT: simply the above one with reverse iterators
711
712 if (text.size() == 0 || search_for.size() == 0 || search_for.size() > text.size())
713 return std::string::npos;
714
715 std::string::const_reverse_iterator it1 = text.rbegin();
716 std::string::const_reverse_iterator it2 = search_for.rbegin();
717
718 size_t number_of_same = 0;
719 size_t result = 0;
720
721 // Go through the text
722 while (it1 != text.rend()) {
723 char c1 = (char)tolower((uchar)*it1);
724 char c2 = (char)tolower((uchar)*it2);
725
726 // The two characters are the same
727 if (c1 == c2) {
728 number_of_same++; // If number of same characters equals to the size of the substring, we've found it!
729 if (number_of_same == search_for.size())
730 return text.size() - result - 1;
731 it2++;
732 } else {
733 number_of_same = 0;
734 it2 = search_for.rbegin();
735 }
736
737 result++;
738 it1++;
739 }
740
741 return std::string::npos; // Not found
742 }
743
744 // StripHTMLTags() copied from http://sugarmaplesoftware.com/25/strip-html-tags/
745
charactersParsed(void * context,const xmlChar * ch,int len)746 static void charactersParsed(void* context, const xmlChar* ch, int len)
747 /*" Callback function for stringByStrippingHTML. "*/
748 {
749 std::string* result = (std::string*) context;
750 *result += std::string( (const char *) ch, len );
751 }
752
753 /* GCS: custom error function to ignore errors */
xmlErrorHandlerDummy(void *,xmlErrorPtr)754 static void xmlErrorHandlerDummy(void *, xmlErrorPtr)
755 {
756 /* ignore all errors */
757 }
758
StripHtmlTags(const std::string & src)759 std::string StripHtmlTags( const std::string & src )
760 /*" Interpretes the receiver as HTML, removes all tags and returns the plain text. "*/
761 {
762 if (!src.size())
763 return "";
764
765 std::string str;
766 htmlSAXHandler handler;
767 memset(&handler, 0, sizeof(handler));
768 handler.characters = & charactersParsed;
769
770 /* GCS: override structuredErrorFunc to mine so I can ignore errors */
771 xmlSetStructuredErrorFunc(NULL, &xmlErrorHandlerDummy);
772
773 std::string tmp = Replace(HtmlEntityUnpairedBrackets(src), "<br>", "\n" );
774
775 htmlDocPtr doc = htmlSAXParseDoc( (xmlChar *) tmp.c_str(), "utf-8", &handler, &str );
776
777 xmlFree(doc);
778 xmlResetLastError();
779
780 // Remove all "\r" and spaces at the beginning of the line
781 // TODO: use some faster method, like str.find_first_of(" \n\r\t")
782 std::string ret;
783 bool lineStart = true;
784 for( std::string::size_type f = 0; f < str.size(); f++ )
785 {
786 if( lineStart )
787 {
788 if( str[f] == ' ' || str[f] == '\t' )
789 continue;
790 else lineStart = false;
791 }
792 if( str[f] == '\n' )
793 lineStart = true;
794 if( str[f] != '\r' )
795 ret += str[f];
796 }
797
798 return ret;
799 }
800
801 /////////////////
802 // Get next word from a string
GetNextWord(std::string::const_iterator it,const std::string & str)803 std::string GetNextWord(std::string::const_iterator it, const std::string& str)
804 {
805 // Check
806 if (str == "" || it == str.end())
807 return "";
808
809 // Check
810 if (it == str.end())
811 return "";
812
813 // Get the word
814 std::string res;
815 while (it != str.end()) {
816 if (isspace((uchar)*it))
817 return res;
818 res += *it;
819 it++;
820 }
821
822 return res;
823 }
824
825 ////////////////////////
826 // Checks for standalone < and > and replaces them with the corresponding entities
HtmlEntityUnpairedBrackets(const std::string & txt)827 std::string HtmlEntityUnpairedBrackets(const std::string &txt)
828 {
829 // Check
830 if (!txt.size())
831 return "";
832
833 // Get the positions of unclosed brackets
834 bool wait_for_close = false;
835 size_t wait_for_close_pos = 0;
836 std::list<size_t> unpaired_pos;
837 size_t curpos = 0;
838 for (std::string::const_iterator it = txt.begin(); it != txt.end(); it++, curpos++) {
839 if (*it == '<') {
840 if (wait_for_close)
841 unpaired_pos.push_back(wait_for_close_pos);
842 wait_for_close = true;
843 wait_for_close_pos = curpos;
844 }
845
846 // One character after the < character
847 if (wait_for_close && curpos == wait_for_close_pos + 1) {
848 // Make sure it's a a-z A-Z letter or a slash
849 if (!((*it >= 'a' && *it <= 'z') || (*it >= 'A' && *it <= 'Z') || *it == '/')) {
850 unpaired_pos.push_back(wait_for_close_pos);
851 wait_for_close = false;
852 wait_for_close_pos = 0;
853 }
854 }
855
856 // Closing bracket
857 if (wait_for_close && *it == '>') {
858 wait_for_close = false;
859 wait_for_close_pos = 0;
860 }
861 }
862
863 if (wait_for_close)
864 unpaired_pos.push_back(wait_for_close_pos);
865
866 // Replace the unclosed brackets with html entities
867 std::string result;
868 size_t startpos = 0;
869 for (std::list<size_t>::iterator it = unpaired_pos.begin(); it != unpaired_pos.end(); it++) {
870 result += txt.substr(startpos, *it - startpos) + "<";
871 startpos = *it + 1;
872 }
873
874 // Last chunk
875 if (startpos < txt.size())
876 result += txt.substr(startpos);
877
878 return result;
879 }
880
881
882 ////////////////////
883 // Helper function for AutoDetectLinks
GetLink(std::string::const_iterator & it,const std::string::const_iterator & end,size_t & pos)884 static std::string GetLink(std::string::const_iterator& it, const std::string::const_iterator& end, size_t& pos)
885 {
886 /*
887 The URI standard, RFC 2396, <http://www.ietf.org/rfc/rfc2396.txt>
888
889 ; HTTP
890
891 httpurl = "http://" hostport [ "/" hpath [ "?" search ]]
892 hpath = hsegment *[ "/" hsegment ]
893 hsegment = *[ uchar | ";" | ":" | "@" | "&" | "=" ]
894 search = *[ uchar | ";" | ":" | "@" | "&" | "=" ]
895
896 lowalpha = ...
897 hialpha = ...
898 digit = ...
899
900 alpha = lowalpha | hialpha
901 safe = "$" | "-" | "_" | "." | "+"
902 extra = "!" | "*" | "'" | "(" | ")" | ","
903 national = "{" | "}" | "|" | "\" | "^" | "~" | "[" | "]" | "`"
904 punctuation = "<" | ">" | "#" | "%" | <">
905
906 reserved = ";" | "/" | "?" | ":" | "@" | "&" | "="
907 hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f"
908 escape = "%" hex hex
909
910 unreserved = alpha | digit | safe | extra
911 uchar = unreserved | escape
912 xchar = unreserved | reserved | escape
913 digits = 1*digit
914 */
915
916 const std::string valid_url_chars = "/" "%" "?" // reserved
917 ";" ":" "@" "&" "=" // search
918 "$" "-" "_" "." "+" // safe
919 "!" "*" "'" "(" ")" "," // extra
920 "{" "}" "|" "\\" "^" "~" "[" "]" "`" // national
921 "#" "\""; // punctuation (part of)
922
923 std::string link;
924 bool was_dot = false;
925 bool was_ques = false;
926 for (; it != end; it++, ++pos) {
927 // Breaking characters
928 if (!isalnum((uchar)*it) && valid_url_chars.find(*it) == std::string::npos) {
929 if (was_ques) {
930 link.resize(link.size() - 1);
931 it--;
932 was_ques = false;
933 }
934 break;
935 }
936
937 // Multiple question marks
938 if (*it == '?') {
939 if (was_ques) {
940 link.resize(link.size() - 1);
941 it--;
942 was_ques = false;
943 break;
944 }
945 was_ques = true;
946 } else
947 was_ques = false;
948
949 // Multiple dots
950 if (*it == '.') {
951 if (was_dot) {
952 link.resize(link.size() - 1);
953 it--;
954 was_dot = false;
955 break;
956 }
957 was_dot = true;
958 } else
959 was_dot = false;
960
961 link += *it;
962 }
963
964 if ((was_ques || was_dot) && link.size()) {
965 link.resize(link.size() - 1);
966 }
967
968 TrimSpaces(link);
969
970 return link;
971 }
972
973 //////////////////////////
974 // Automatically find hyperlinks in the given text and encapsulate them with <a> and </a>
AutoDetectLinks(const std::string & text)975 std::string AutoDetectLinks(const std::string& text)
976 {
977 static const std::string prefixes[] = { "www.", "http://", "https://", "mailto:", "ftp://" };
978
979 std::string result;
980 size_t pos = 0;
981 bool in_tag = false;
982 for (std::string::const_iterator it = text.begin(); it != text.end(); it++, pos++) {
983 if (*it == '<') {
984 in_tag = true;
985 result += *it;
986 continue;
987 }
988
989 if (*it == '>') {
990 in_tag = false;
991 result += *it;
992 continue;
993 }
994
995 // Do not search inside html tags
996 if (in_tag) {
997 result += *it;
998 continue;
999 }
1000
1001 for (size_t i = 0; i < sizeof(prefixes)/sizeof(std::string); ++i) {
1002 if (text.size() - pos > prefixes[i].size() + 4) { // 4 = minimum length of the address, for example a.de
1003 if (stringcaseequal(text.substr(pos, prefixes[i].size()), prefixes[i])) {
1004
1005 // Get the link
1006 std::string link = GetLink(it, text.end(), pos);
1007
1008 // Add the link
1009 result += "<a href=\"" + link + "\">" + link + "</a>";
1010 break;
1011 }
1012 }
1013 }
1014
1015 if (it != text.end())
1016 result += *it;
1017 else
1018 break;
1019 }
1020
1021 return result;
1022 }
1023
EscapeHtmlTags(const std::string & src)1024 std::string EscapeHtmlTags( const std::string & src )
1025 {
1026 std::string ret ( src );
1027
1028 replace( ret, "&", "&" );
1029 replace( ret, "<", "<" );
1030 replace( ret, ">", ">" );
1031 replace( ret, "\"", """ );
1032 replace( ret, "\'", "'" );
1033
1034 return ret;
1035 }
1036
Compress(const std::string & in,std::string * out,bool noCompression)1037 bool Compress( const std::string & in, std::string * out, bool noCompression )
1038 {
1039 *out = ""; //out->clear();
1040 z_stream strm;
1041 int ret;
1042 /* allocate deflate state */
1043 strm.zalloc = Z_NULL;
1044 strm.zfree = Z_NULL;
1045 strm.opaque = Z_NULL;
1046 int compression = Z_BEST_COMPRESSION;
1047 if( noCompression )
1048 compression = Z_NO_COMPRESSION;
1049 ret = deflateInit(&strm, compression);
1050 if (ret != Z_OK)
1051 return false;
1052 strm.avail_in = (uint)in.size(); // TODO: possible overflow on 64-bit systems
1053 strm.next_in = (Bytef *) in.c_str();
1054 char buf[16384];
1055 do{
1056 strm.avail_out = sizeof(buf);
1057 strm.next_out = (Bytef *) buf;
1058 ret = deflate(&strm, Z_FINISH);
1059 if( ret != Z_OK && ret != Z_STREAM_END )
1060 {
1061 //errors << "Compress() error" << endl;
1062 deflateEnd(&strm);
1063 return false;
1064 };
1065 out->append( buf, sizeof(buf) - strm.avail_out );
1066 } while( ret != Z_STREAM_END );
1067
1068 deflateEnd(&strm);
1069 return true;
1070 }
1071
Decompress(const std::string & in,std::string * out)1072 bool Decompress( const std::string & in, std::string * out )
1073 {
1074 *out = ""; //out->clear();
1075 z_stream strm;
1076 int ret;
1077 /* allocate inflate state */
1078 strm.zalloc = Z_NULL;
1079 strm.zfree = Z_NULL;
1080 strm.opaque = Z_NULL;
1081 strm.avail_in = 0;
1082 strm.next_in = Z_NULL;
1083 ret = inflateInit(&strm);
1084 if (ret != Z_OK)
1085 return false;
1086
1087 strm.avail_in = (uint)in.size(); // TODO: possible overflow on 64-bit systems
1088 strm.next_in = (Bytef *) in.c_str();
1089 char buf[16384];
1090 do{
1091 strm.avail_out = sizeof(buf);
1092 strm.next_out = (Bytef *) buf;
1093 ret = inflate(&strm, Z_NO_FLUSH);
1094 if( ret != Z_OK && ret != Z_STREAM_END )
1095 {
1096 //errors << "Decompress() error" << endl;
1097 inflateEnd(&strm);
1098 return false;
1099 };
1100 out->append( buf, sizeof(buf) - strm.avail_out );
1101 } while( ret != Z_STREAM_END );
1102
1103 inflateEnd(&strm);
1104 return true;
1105 }
1106
StringChecksum(const std::string & data)1107 size_t StringChecksum( const std::string & data )
1108 {
1109 return adler32(0L, (const Bytef *)data.c_str(), (uint)data.size());
1110 }
1111
FileChecksum(const std::string & path,size_t * _checksum,size_t * _filesize)1112 bool FileChecksum( const std::string & path, size_t * _checksum, size_t * _filesize )
1113 {
1114 FILE * ff = OpenGameFile( path, "rb" );
1115 if( ff == NULL )
1116 return false;
1117 char buf[16384];
1118 uLong checksum = adler32(0L, Z_NULL, 0);
1119 size_t size = 0;
1120
1121 while( ! feof( ff ) )
1122 {
1123 size_t read = fread( buf, 1, sizeof(buf), ff );
1124 checksum = adler32( checksum, (const Bytef *)buf, read);
1125 size += read;
1126 };
1127 fclose( ff );
1128
1129 if( _checksum )
1130 *_checksum = checksum;
1131 if( _filesize )
1132 *_filesize = size;
1133 return true;
1134 }
1135
1136 // Oops, wget is under GPL, so replaced Base64Encode() with version from CURL which is under LGPL
1137 /* ---- Base64 Encoding/Decoding Table --- */
1138 static const char table64[]=
1139 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1140
decodeQuantum(const std::string & src,std::string::size_type pos)1141 static std::string decodeQuantum ( const std::string &src, std::string::size_type pos )
1142 {
1143 unsigned int x = 0;
1144 std::string dest ( 3, '\0' );
1145
1146 for ( int i = 0; i < 4 && src.size() > i + pos ; i++ ) {
1147 const char* found = strchr ( table64, src[i+pos] );
1148 if ( found != NULL )
1149 x = ( x << 6 ) + ( unsigned int ) ( found - table64 );
1150 else if ( src[i+pos] == '=' )
1151 x = ( x << 6 );
1152 }
1153
1154 dest[2] = ( char ) ( ( unsigned char ) ( x & 255 ) );
1155 x >>= 8;
1156 dest[1] = ( char ) ( ( unsigned char ) ( x & 255 ) );
1157 x >>= 8;
1158 dest[0] = ( char ) ( ( unsigned char ) ( x & 255 ) );
1159 return dest;
1160 }
1161
Base64Decode(const std::string & src)1162 std::string Base64Decode(const std::string &src)
1163 {
1164 size_t length = 0;
1165 int equalsTerm = 0;
1166 int i;
1167 int numQuantums;
1168 std::string::size_type srcPos = 0;
1169
1170 while( src.size() > length && src[length] != '=' )
1171 length++;
1172 /* A maximum of two = padding characters is allowed */
1173 if(src[length] == '=') {
1174 equalsTerm++;
1175 if(src[length+equalsTerm] == '=')
1176 equalsTerm++;
1177 }
1178 numQuantums = (length + equalsTerm) / 4;
1179
1180 /* Don't allocate a buffer if the decoded length is 0 */
1181 if(numQuantums <= 0)
1182 return "";
1183
1184 std::string newstr;
1185
1186 /* Decode all but the last quantum (which may not decode to a
1187 multiple of 3 bytes) */
1188 for(i = 0; i < numQuantums - 1; i++) {
1189 newstr += decodeQuantum(src.c_str(), srcPos);
1190 srcPos += 4;
1191 }
1192
1193 std::string lastQuantum = decodeQuantum(src, srcPos);
1194 for(i = 0; i < 3 - equalsTerm; i++)
1195 newstr += lastQuantum[i];
1196
1197 return newstr;
1198 }
1199
Base64Encode(const std::string & indata)1200 std::string Base64Encode(const std::string &indata)
1201 {
1202 unsigned char ibuf[3];
1203 unsigned char obuf[4];
1204 int i;
1205 int inputparts;
1206 std::string::size_type inPos = 0;
1207
1208 std::string output;
1209
1210 while(indata.length() > inPos) {
1211 for (i = inputparts = 0; i < 3; i++) {
1212 if(indata.length() > inPos) {
1213 inputparts++;
1214 ibuf[i] = (unsigned char)indata[inPos];
1215 inPos++;
1216 }
1217 else
1218 ibuf[i] = 0;
1219 }
1220
1221 // TODO: ugly, substitute with constants
1222 obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2);
1223 obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \
1224 ((ibuf[1] & 0xF0) >> 4));
1225 obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \
1226 ((ibuf[2] & 0xC0) >> 6));
1227 obuf[3] = (unsigned char) (ibuf[2] & 0x3F);
1228
1229 switch(inputparts) {
1230 case 1: /* only one byte read */
1231 output += table64[obuf[0]];
1232 output += table64[obuf[1]];
1233 output += "==";
1234 break;
1235 case 2: /* two bytes read */
1236 output += table64[obuf[0]];
1237 output += table64[obuf[1]];
1238 output += table64[obuf[2]];
1239 output += "=";
1240 break;
1241 default:
1242 output += table64[obuf[0]];
1243 output += table64[obuf[1]];
1244 output += table64[obuf[2]];
1245 output += table64[obuf[3]];
1246 break;
1247 }
1248 }
1249 return output;
1250 }
1251
1252 // Substitute space with + and all non-alphanum symbols with %XX
UrlEncode(const std::string & data)1253 std::string UrlEncode(const std::string &data)
1254 {
1255 std::string ret;
1256 for( size_t f=0; f<data.size(); f++ )
1257 {
1258 char c = data[f];
1259 if( isalnum(c) || c == '.' || c == '-' || c == '_' )
1260 ret += c;
1261 else
1262 {
1263 std::ostringstream os;
1264 // unsigned(c) will produce numbers like 0xFFFFFF80 for value -128, so I'm using unsigned((unsigned char)c)
1265 os << "%" << std::hex << std::setw(2) << std::setfill('0') << unsigned((unsigned char)c);
1266 ret += os.str();
1267 };
1268 };
1269 return ret;
1270 };
1271
strSeemsLikeChatCommand(const std::string & str)1272 bool strSeemsLikeChatCommand(const std::string& str) {
1273 if(str.size() == 0) return false;
1274 if(str[0] == '/') {
1275 if(str.size() == 1) return true;
1276 if(str[1] == '/') return false;
1277 return true;
1278 }
1279 return false;
1280 }
1281
1282
1283
from_string(const std::string & s,bool & fail)1284 template<> VectorD2<int> from_string< VectorD2<int> >(const std::string& s, bool& fail) {
1285 std::string tmp = s;
1286 TrimSpaces(tmp);
1287 if(tmp.size() > 2 && tmp[0] == '(' && tmp[tmp.size()-1] == ')')
1288 tmp = tmp.substr(1, tmp.size() - 2);
1289 size_t f = tmp.find(',');
1290 if(f == std::string::npos) { fail = true; return VectorD2<int>(); }
1291 VectorD2<int> v;
1292 fail = false;
1293 v.x = from_string<int>(tmp.substr(0, f), fail);
1294 if(fail) return VectorD2<int>();
1295 v.y = from_string<int>(tmp.substr(f + 1), fail);
1296 if(fail) return VectorD2<int>();
1297 return v;
1298 }
1299