]> git.lizzy.rs Git - dragonfireclient.git/blob - src/util/string.cpp
Revert "Make Lint Happy"
[dragonfireclient.git] / src / util / string.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "string.h"
21 #include "pointer.h"
22 #include "numeric.h"
23 #include "log.h"
24
25 #include "hex.h"
26 #include "porting.h"
27 #include "translation.h"
28
29 #include <algorithm>
30 #include <sstream>
31 #include <iomanip>
32 #include <map>
33
34 #ifndef _WIN32
35         #include <iconv.h>
36 #else
37         #define _WIN32_WINNT 0x0501
38         #include <windows.h>
39 #endif
40
41 #if defined(_ICONV_H_) && (defined(__FreeBSD__) || defined(__NetBSD__) || \
42         defined(__OpenBSD__) || defined(__DragonFly__))
43         #define BSD_ICONV_USED
44 #endif
45
46 static bool parseHexColorString(const std::string &value, video::SColor &color,
47                 unsigned char default_alpha = 0xff);
48 static bool parseNamedColorString(const std::string &value, video::SColor &color);
49
50 #ifndef _WIN32
51
52 bool convert(const char *to, const char *from, char *outbuf,
53                 size_t outbuf_size, char *inbuf, size_t inbuf_size)
54 {
55         iconv_t cd = iconv_open(to, from);
56
57 #ifdef BSD_ICONV_USED
58         const char *inbuf_ptr = inbuf;
59 #else
60         char *inbuf_ptr = inbuf;
61 #endif
62
63         char *outbuf_ptr = outbuf;
64
65         size_t *inbuf_left_ptr = &inbuf_size;
66         size_t *outbuf_left_ptr = &outbuf_size;
67
68         size_t old_size = inbuf_size;
69         while (inbuf_size > 0) {
70                 iconv(cd, &inbuf_ptr, inbuf_left_ptr, &outbuf_ptr, outbuf_left_ptr);
71                 if (inbuf_size == old_size) {
72                         iconv_close(cd);
73                         return false;
74                 }
75                 old_size = inbuf_size;
76         }
77
78         iconv_close(cd);
79         return true;
80 }
81
82 #ifdef __ANDROID__
83 // Android need manual caring to support the full character set possible with wchar_t
84 const char *DEFAULT_ENCODING = "UTF-32LE";
85 #else
86 const char *DEFAULT_ENCODING = "WCHAR_T";
87 #endif
88
89 std::wstring utf8_to_wide(const std::string &input)
90 {
91         size_t inbuf_size = input.length() + 1;
92         // maximum possible size, every character is sizeof(wchar_t) bytes
93         size_t outbuf_size = (input.length() + 1) * sizeof(wchar_t);
94
95         char *inbuf = new char[inbuf_size];
96         memcpy(inbuf, input.c_str(), inbuf_size);
97         char *outbuf = new char[outbuf_size];
98         memset(outbuf, 0, outbuf_size);
99
100 #ifdef __ANDROID__
101         // Android need manual caring to support the full character set possible with wchar_t
102         SANITY_CHECK(sizeof(wchar_t) == 4);
103 #endif
104
105         if (!convert(DEFAULT_ENCODING, "UTF-8", outbuf, outbuf_size, inbuf, inbuf_size)) {
106                 infostream << "Couldn't convert UTF-8 string 0x" << hex_encode(input)
107                         << " into wstring" << std::endl;
108                 delete[] inbuf;
109                 delete[] outbuf;
110                 return L"<invalid UTF-8 string>";
111         }
112         std::wstring out((wchar_t *)outbuf);
113
114         delete[] inbuf;
115         delete[] outbuf;
116
117         return out;
118 }
119
120 std::string wide_to_utf8(const std::wstring &input)
121 {
122         size_t inbuf_size = (input.length() + 1) * sizeof(wchar_t);
123         // maximum possible size: utf-8 encodes codepoints using 1 up to 6 bytes
124         size_t outbuf_size = (input.length() + 1) * 6;
125
126         char *inbuf = new char[inbuf_size];
127         memcpy(inbuf, input.c_str(), inbuf_size);
128         char *outbuf = new char[outbuf_size];
129         memset(outbuf, 0, outbuf_size);
130
131         if (!convert("UTF-8", DEFAULT_ENCODING, outbuf, outbuf_size, inbuf, inbuf_size)) {
132                 infostream << "Couldn't convert wstring 0x" << hex_encode(inbuf, inbuf_size)
133                         << " into UTF-8 string" << std::endl;
134                 delete[] inbuf;
135                 delete[] outbuf;
136                 return "<invalid wstring>";
137         }
138         std::string out(outbuf);
139
140         delete[] inbuf;
141         delete[] outbuf;
142
143         return out;
144 }
145
146 #else // _WIN32
147
148 std::wstring utf8_to_wide(const std::string &input)
149 {
150         size_t outbuf_size = input.size() + 1;
151         wchar_t *outbuf = new wchar_t[outbuf_size];
152         memset(outbuf, 0, outbuf_size * sizeof(wchar_t));
153         MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.size(),
154                 outbuf, outbuf_size);
155         std::wstring out(outbuf);
156         delete[] outbuf;
157         return out;
158 }
159
160 std::string wide_to_utf8(const std::wstring &input)
161 {
162         size_t outbuf_size = (input.size() + 1) * 6;
163         char *outbuf = new char[outbuf_size];
164         memset(outbuf, 0, outbuf_size);
165         WideCharToMultiByte(CP_UTF8, 0, input.c_str(), input.size(),
166                 outbuf, outbuf_size, NULL, NULL);
167         std::string out(outbuf);
168         delete[] outbuf;
169         return out;
170 }
171
172 #endif // _WIN32
173
174 // You must free the returned string!
175 // The returned string is allocated using new
176 wchar_t *utf8_to_wide_c(const char *str)
177 {
178         std::wstring ret = utf8_to_wide(std::string(str));
179         size_t len = ret.length();
180         wchar_t *ret_c = new wchar_t[len + 1];
181         memset(ret_c, 0, (len + 1) * sizeof(wchar_t));
182         memcpy(ret_c, ret.c_str(), len * sizeof(wchar_t));
183         return ret_c;
184 }
185
186 // You must free the returned string!
187 // The returned string is allocated using new
188 wchar_t *narrow_to_wide_c(const char *str)
189 {
190         wchar_t *nstr = nullptr;
191 #if defined(_WIN32)
192         int nResult = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) str, -1, 0, 0);
193         if (nResult == 0) {
194                 errorstream<<"gettext: MultiByteToWideChar returned null"<<std::endl;
195         } else {
196                 nstr = new wchar_t[nResult];
197                 MultiByteToWideChar(CP_UTF8, 0, (LPCSTR) str, -1, (WCHAR *) nstr, nResult);
198         }
199 #else
200         size_t len = strlen(str);
201         nstr = new wchar_t[len + 1];
202
203         std::wstring intermediate = narrow_to_wide(str);
204         memset(nstr, 0, (len + 1) * sizeof(wchar_t));
205         memcpy(nstr, intermediate.c_str(), len * sizeof(wchar_t));
206 #endif
207
208         return nstr;
209 }
210
211 std::wstring narrow_to_wide(const std::string &mbs) {
212 #ifdef __ANDROID__
213         return utf8_to_wide(mbs);
214 #else
215         size_t wcl = mbs.size();
216         Buffer<wchar_t> wcs(wcl + 1);
217         size_t len = mbstowcs(*wcs, mbs.c_str(), wcl);
218         if (len == (size_t)(-1))
219                 return L"<invalid multibyte string>";
220         wcs[len] = 0;
221         return *wcs;
222 #endif
223 }
224
225
226 std::string wide_to_narrow(const std::wstring &wcs)
227 {
228 #ifdef __ANDROID__
229         return wide_to_utf8(wcs);
230 #else
231         size_t mbl = wcs.size() * 4;
232         SharedBuffer<char> mbs(mbl+1);
233         size_t len = wcstombs(*mbs, wcs.c_str(), mbl);
234         if (len == (size_t)(-1))
235                 return "Character conversion failed!";
236
237         mbs[len] = 0;
238         return *mbs;
239 #endif
240 }
241
242
243 std::string urlencode(const std::string &str)
244 {
245         // Encodes non-unreserved URI characters by a percent sign
246         // followed by two hex digits. See RFC 3986, section 2.3.
247         static const char url_hex_chars[] = "0123456789ABCDEF";
248         std::ostringstream oss(std::ios::binary);
249         for (unsigned char c : str) {
250                 if (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
251                         oss << c;
252                 } else {
253                         oss << "%"
254                                 << url_hex_chars[(c & 0xf0) >> 4]
255                                 << url_hex_chars[c & 0x0f];
256                 }
257         }
258         return oss.str();
259 }
260
261 std::string urldecode(const std::string &str)
262 {
263         // Inverse of urlencode
264         std::ostringstream oss(std::ios::binary);
265         for (u32 i = 0; i < str.size(); i++) {
266                 unsigned char highvalue, lowvalue;
267                 if (str[i] == '%' &&
268                                 hex_digit_decode(str[i+1], highvalue) &&
269                                 hex_digit_decode(str[i+2], lowvalue)) {
270                         oss << (char) ((highvalue << 4) | lowvalue);
271                         i += 2;
272                 } else {
273                         oss << str[i];
274                 }
275         }
276         return oss.str();
277 }
278
279 u32 readFlagString(std::string str, const FlagDesc *flagdesc, u32 *flagmask)
280 {
281         u32 result = 0;
282         u32 mask = 0;
283         char *s = &str[0];
284         char *flagstr;
285         char *strpos = nullptr;
286
287         while ((flagstr = strtok_r(s, ",", &strpos))) {
288                 s = nullptr;
289
290                 while (*flagstr == ' ' || *flagstr == '\t')
291                         flagstr++;
292
293                 bool flagset = true;
294                 if (!strncasecmp(flagstr, "no", 2)) {
295                         flagset = false;
296                         flagstr += 2;
297                 }
298
299                 for (int i = 0; flagdesc[i].name; i++) {
300                         if (!strcasecmp(flagstr, flagdesc[i].name)) {
301                                 mask |= flagdesc[i].flag;
302                                 if (flagset)
303                                         result |= flagdesc[i].flag;
304                                 break;
305                         }
306                 }
307         }
308
309         if (flagmask)
310                 *flagmask = mask;
311
312         return result;
313 }
314
315 std::string writeFlagString(u32 flags, const FlagDesc *flagdesc, u32 flagmask)
316 {
317         std::string result;
318
319         for (int i = 0; flagdesc[i].name; i++) {
320                 if (flagmask & flagdesc[i].flag) {
321                         if (!(flags & flagdesc[i].flag))
322                                 result += "no";
323
324                         result += flagdesc[i].name;
325                         result += ", ";
326                 }
327         }
328
329         size_t len = result.length();
330         if (len >= 2)
331                 result.erase(len - 2, 2);
332
333         return result;
334 }
335
336 size_t mystrlcpy(char *dst, const char *src, size_t size)
337 {
338         size_t srclen  = strlen(src) + 1;
339         size_t copylen = MYMIN(srclen, size);
340
341         if (copylen > 0) {
342                 memcpy(dst, src, copylen);
343                 dst[copylen - 1] = '\0';
344         }
345
346         return srclen;
347 }
348
349 char *mystrtok_r(char *s, const char *sep, char **lasts)
350 {
351         char *t;
352
353         if (!s)
354                 s = *lasts;
355
356         while (*s && strchr(sep, *s))
357                 s++;
358
359         if (!*s)
360                 return nullptr;
361
362         t = s;
363         while (*t) {
364                 if (strchr(sep, *t)) {
365                         *t++ = '\0';
366                         break;
367                 }
368                 t++;
369         }
370
371         *lasts = t;
372         return s;
373 }
374
375 u64 read_seed(const char *str)
376 {
377         char *endptr;
378         u64 num;
379
380         if (str[0] == '0' && str[1] == 'x')
381                 num = strtoull(str, &endptr, 16);
382         else
383                 num = strtoull(str, &endptr, 10);
384
385         if (*endptr)
386                 num = murmur_hash_64_ua(str, (int)strlen(str), 0x1337);
387
388         return num;
389 }
390
391 bool parseColorString(const std::string &value, video::SColor &color, bool quiet,
392                 unsigned char default_alpha)
393 {
394         bool success;
395
396         if (value[0] == '#')
397                 success = parseHexColorString(value, color, default_alpha);
398         else
399                 success = parseNamedColorString(value, color);
400
401         if (!success && !quiet)
402                 errorstream << "Invalid color: \"" << value << "\"" << std::endl;
403
404         return success;
405 }
406
407 static bool parseHexColorString(const std::string &value, video::SColor &color,
408                 unsigned char default_alpha)
409 {
410         unsigned char components[] = { 0x00, 0x00, 0x00, default_alpha }; // R,G,B,A
411
412         if (value[0] != '#')
413                 return false;
414
415         size_t len = value.size();
416         bool short_form;
417
418         if (len == 9 || len == 7) // #RRGGBBAA or #RRGGBB
419                 short_form = false;
420         else if (len == 5 || len == 4) // #RGBA or #RGB
421                 short_form = true;
422         else
423                 return false;
424
425         bool success = true;
426
427         for (size_t pos = 1, cc = 0; pos < len; pos++, cc++) {
428                 assert(cc < sizeof components / sizeof components[0]);
429                 if (short_form) {
430                         unsigned char d;
431                         if (!hex_digit_decode(value[pos], d)) {
432                                 success = false;
433                                 break;
434                         }
435                         components[cc] = (d & 0xf) << 4 | (d & 0xf);
436                 } else {
437                         unsigned char d1, d2;
438                         if (!hex_digit_decode(value[pos], d1) ||
439                                         !hex_digit_decode(value[pos+1], d2)) {
440                                 success = false;
441                                 break;
442                         }
443                         components[cc] = (d1 & 0xf) << 4 | (d2 & 0xf);
444                         pos++;  // skip the second digit -- it's already used
445                 }
446         }
447
448         if (success) {
449                 color.setRed(components[0]);
450                 color.setGreen(components[1]);
451                 color.setBlue(components[2]);
452                 color.setAlpha(components[3]);
453         }
454
455         return success;
456 }
457
458 struct ColorContainer {
459         ColorContainer();
460         std::map<const std::string, u32> colors;
461 };
462
463 ColorContainer::ColorContainer()
464 {
465         colors["aliceblue"]              = 0xf0f8ff;
466         colors["antiquewhite"]           = 0xfaebd7;
467         colors["aqua"]                   = 0x00ffff;
468         colors["aquamarine"]             = 0x7fffd4;
469         colors["azure"]                  = 0xf0ffff;
470         colors["beige"]                  = 0xf5f5dc;
471         colors["bisque"]                 = 0xffe4c4;
472         colors["black"]                  = 00000000;
473         colors["blanchedalmond"]         = 0xffebcd;
474         colors["blue"]                   = 0x0000ff;
475         colors["blueviolet"]             = 0x8a2be2;
476         colors["brown"]                  = 0xa52a2a;
477         colors["burlywood"]              = 0xdeb887;
478         colors["cadetblue"]              = 0x5f9ea0;
479         colors["chartreuse"]             = 0x7fff00;
480         colors["chocolate"]              = 0xd2691e;
481         colors["coral"]                  = 0xff7f50;
482         colors["cornflowerblue"]         = 0x6495ed;
483         colors["cornsilk"]               = 0xfff8dc;
484         colors["crimson"]                = 0xdc143c;
485         colors["cyan"]                   = 0x00ffff;
486         colors["darkblue"]               = 0x00008b;
487         colors["darkcyan"]               = 0x008b8b;
488         colors["darkgoldenrod"]          = 0xb8860b;
489         colors["darkgray"]               = 0xa9a9a9;
490         colors["darkgreen"]              = 0x006400;
491         colors["darkgrey"]               = 0xa9a9a9;
492         colors["darkkhaki"]              = 0xbdb76b;
493         colors["darkmagenta"]            = 0x8b008b;
494         colors["darkolivegreen"]         = 0x556b2f;
495         colors["darkorange"]             = 0xff8c00;
496         colors["darkorchid"]             = 0x9932cc;
497         colors["darkred"]                = 0x8b0000;
498         colors["darksalmon"]             = 0xe9967a;
499         colors["darkseagreen"]           = 0x8fbc8f;
500         colors["darkslateblue"]          = 0x483d8b;
501         colors["darkslategray"]          = 0x2f4f4f;
502         colors["darkslategrey"]          = 0x2f4f4f;
503         colors["darkturquoise"]          = 0x00ced1;
504         colors["darkviolet"]             = 0x9400d3;
505         colors["deeppink"]               = 0xff1493;
506         colors["deepskyblue"]            = 0x00bfff;
507         colors["dimgray"]                = 0x696969;
508         colors["dimgrey"]                = 0x696969;
509         colors["dodgerblue"]             = 0x1e90ff;
510         colors["firebrick"]              = 0xb22222;
511         colors["floralwhite"]            = 0xfffaf0;
512         colors["forestgreen"]            = 0x228b22;
513         colors["fuchsia"]                = 0xff00ff;
514         colors["gainsboro"]              = 0xdcdcdc;
515         colors["ghostwhite"]             = 0xf8f8ff;
516         colors["gold"]                   = 0xffd700;
517         colors["goldenrod"]              = 0xdaa520;
518         colors["gray"]                   = 0x808080;
519         colors["green"]                  = 0x008000;
520         colors["greenyellow"]            = 0xadff2f;
521         colors["grey"]                   = 0x808080;
522         colors["honeydew"]               = 0xf0fff0;
523         colors["hotpink"]                = 0xff69b4;
524         colors["indianred"]              = 0xcd5c5c;
525         colors["indigo"]                 = 0x4b0082;
526         colors["ivory"]                  = 0xfffff0;
527         colors["khaki"]                  = 0xf0e68c;
528         colors["lavender"]               = 0xe6e6fa;
529         colors["lavenderblush"]          = 0xfff0f5;
530         colors["lawngreen"]              = 0x7cfc00;
531         colors["lemonchiffon"]           = 0xfffacd;
532         colors["lightblue"]              = 0xadd8e6;
533         colors["lightcoral"]             = 0xf08080;
534         colors["lightcyan"]              = 0xe0ffff;
535         colors["lightgoldenrodyellow"]   = 0xfafad2;
536         colors["lightgray"]              = 0xd3d3d3;
537         colors["lightgreen"]             = 0x90ee90;
538         colors["lightgrey"]              = 0xd3d3d3;
539         colors["lightpink"]              = 0xffb6c1;
540         colors["lightsalmon"]            = 0xffa07a;
541         colors["lightseagreen"]          = 0x20b2aa;
542         colors["lightskyblue"]           = 0x87cefa;
543         colors["lightslategray"]         = 0x778899;
544         colors["lightslategrey"]         = 0x778899;
545         colors["lightsteelblue"]         = 0xb0c4de;
546         colors["lightyellow"]            = 0xffffe0;
547         colors["lime"]                   = 0x00ff00;
548         colors["limegreen"]              = 0x32cd32;
549         colors["linen"]                  = 0xfaf0e6;
550         colors["magenta"]                = 0xff00ff;
551         colors["maroon"]                 = 0x800000;
552         colors["mediumaquamarine"]       = 0x66cdaa;
553         colors["mediumblue"]             = 0x0000cd;
554         colors["mediumorchid"]           = 0xba55d3;
555         colors["mediumpurple"]           = 0x9370db;
556         colors["mediumseagreen"]         = 0x3cb371;
557         colors["mediumslateblue"]        = 0x7b68ee;
558         colors["mediumspringgreen"]      = 0x00fa9a;
559         colors["mediumturquoise"]        = 0x48d1cc;
560         colors["mediumvioletred"]        = 0xc71585;
561         colors["midnightblue"]           = 0x191970;
562         colors["mintcream"]              = 0xf5fffa;
563         colors["mistyrose"]              = 0xffe4e1;
564         colors["moccasin"]               = 0xffe4b5;
565         colors["navajowhite"]            = 0xffdead;
566         colors["navy"]                   = 0x000080;
567         colors["oldlace"]                = 0xfdf5e6;
568         colors["olive"]                  = 0x808000;
569         colors["olivedrab"]              = 0x6b8e23;
570         colors["orange"]                 = 0xffa500;
571         colors["orangered"]              = 0xff4500;
572         colors["orchid"]                 = 0xda70d6;
573         colors["palegoldenrod"]          = 0xeee8aa;
574         colors["palegreen"]              = 0x98fb98;
575         colors["paleturquoise"]          = 0xafeeee;
576         colors["palevioletred"]          = 0xdb7093;
577         colors["papayawhip"]             = 0xffefd5;
578         colors["peachpuff"]              = 0xffdab9;
579         colors["peru"]                   = 0xcd853f;
580         colors["pink"]                   = 0xffc0cb;
581         colors["plum"]                   = 0xdda0dd;
582         colors["powderblue"]             = 0xb0e0e6;
583         colors["purple"]                 = 0x800080;
584         colors["red"]                    = 0xff0000;
585         colors["rosybrown"]              = 0xbc8f8f;
586         colors["royalblue"]              = 0x4169e1;
587         colors["saddlebrown"]            = 0x8b4513;
588         colors["salmon"]                 = 0xfa8072;
589         colors["sandybrown"]             = 0xf4a460;
590         colors["seagreen"]               = 0x2e8b57;
591         colors["seashell"]               = 0xfff5ee;
592         colors["sienna"]                 = 0xa0522d;
593         colors["silver"]                 = 0xc0c0c0;
594         colors["skyblue"]                = 0x87ceeb;
595         colors["slateblue"]              = 0x6a5acd;
596         colors["slategray"]              = 0x708090;
597         colors["slategrey"]              = 0x708090;
598         colors["snow"]                   = 0xfffafa;
599         colors["springgreen"]            = 0x00ff7f;
600         colors["steelblue"]              = 0x4682b4;
601         colors["tan"]                    = 0xd2b48c;
602         colors["teal"]                   = 0x008080;
603         colors["thistle"]                = 0xd8bfd8;
604         colors["tomato"]                 = 0xff6347;
605         colors["turquoise"]              = 0x40e0d0;
606         colors["violet"]                 = 0xee82ee;
607         colors["wheat"]                  = 0xf5deb3;
608         colors["white"]                  = 0xffffff;
609         colors["whitesmoke"]             = 0xf5f5f5;
610         colors["yellow"]                 = 0xffff00;
611         colors["yellowgreen"]            = 0x9acd32;
612
613 }
614
615 static const ColorContainer named_colors;
616
617 static bool parseNamedColorString(const std::string &value, video::SColor &color)
618 {
619         std::string color_name;
620         std::string alpha_string;
621
622         /* If the string has a # in it, assume this is the start of a specified
623          * alpha value (if it isn't the string is invalid and the error will be
624          * caught later on, either because the color name won't be found or the
625          * alpha value will fail conversion)
626          */
627         size_t alpha_pos = value.find('#');
628         if (alpha_pos != std::string::npos) {
629                 color_name = value.substr(0, alpha_pos);
630                 alpha_string = value.substr(alpha_pos + 1);
631         } else {
632                 color_name = value;
633         }
634
635         color_name = lowercase(value);
636
637         std::map<const std::string, unsigned>::const_iterator it;
638         it = named_colors.colors.find(color_name);
639         if (it == named_colors.colors.end())
640                 return false;
641
642         u32 color_temp = it->second;
643
644         /* An empty string for alpha is ok (none of the color table entries
645          * have an alpha value either). Color strings without an alpha specified
646          * are interpreted as fully opaque
647          *
648          * For named colors the supplied alpha string (representing a hex value)
649          * must be exactly two digits. For example:  colorname#08
650          */
651         if (!alpha_string.empty()) {
652                 if (alpha_string.length() != 2)
653                         return false;
654
655                 unsigned char d1, d2;
656                 if (!hex_digit_decode(alpha_string.at(0), d1)
657                                 || !hex_digit_decode(alpha_string.at(1), d2))
658                         return false;
659                 color_temp |= ((d1 & 0xf) << 4 | (d2 & 0xf)) << 24;
660         } else {
661                 color_temp |= 0xff << 24;  // Fully opaque
662         }
663
664         color = video::SColor(color_temp);
665
666         return true;
667 }
668
669 void str_replace(std::string &str, char from, char to)
670 {
671         std::replace(str.begin(), str.end(), from, to);
672 }
673
674 /* Translated strings have the following format:
675  * \x1bT marks the beginning of a translated string
676  * \x1bE marks its end
677  *
678  * \x1bF marks the beginning of an argument, and \x1bE its end.
679  *
680  * Arguments are *not* translated, as they may contain escape codes.
681  * Thus, if you want a translated argument, it should be inside \x1bT/\x1bE tags as well.
682  *
683  * This representation is chosen so that clients ignoring escape codes will
684  * see untranslated strings.
685  *
686  * For instance, suppose we have a string such as "@1 Wool" with the argument "White"
687  * The string will be sent as "\x1bT\x1bF\x1bTWhite\x1bE\x1bE Wool\x1bE"
688  * To translate this string, we extract what is inside \x1bT/\x1bE tags.
689  * When we notice the \x1bF tag, we recursively extract what is there up to the \x1bE end tag,
690  * translating it as well.
691  * We get the argument "White", translated, and create a template string with "@1" instead of it.
692  * We finally get the template "@1 Wool" that was used in the beginning, which we translate
693  * before filling it again.
694  */
695
696 void translate_all(const std::wstring &s, size_t &i,
697                 Translations *translations, std::wstring &res);
698
699 void translate_string(const std::wstring &s, Translations *translations,
700                 const std::wstring &textdomain, size_t &i, std::wstring &res)
701 {
702         std::wostringstream output;
703         std::vector<std::wstring> args;
704         int arg_number = 1;
705         while (i < s.length()) {
706                 // Not an escape sequence: just add the character.
707                 if (s[i] != '\x1b') {
708                         output.put(s[i]);
709                         // The character is a literal '@'; add it twice
710                         // so that it is not mistaken for an argument.
711                         if (s[i] == L'@')
712                                 output.put(L'@');
713                         ++i;
714                         continue;
715                 }
716
717                 // We have an escape sequence: locate it and its data
718                 // It is either a single character, or it begins with '('
719                 // and extends up to the following ')', with '\' as an escape character.
720                 ++i;
721                 size_t start_index = i;
722                 size_t length;
723                 if (i == s.length()) {
724                         length = 0;
725                 } else if (s[i] == L'(') {
726                         ++i;
727                         ++start_index;
728                         while (i < s.length() && s[i] != L')') {
729                                 if (s[i] == L'\\')
730                                         ++i;
731                                 ++i;
732                         }
733                         length = i - start_index;
734                         ++i;
735                         if (i > s.length())
736                                 i = s.length();
737                 } else {
738                         ++i;
739                         length = 1;
740                 }
741                 std::wstring escape_sequence(s, start_index, length);
742
743                 // The escape sequence is now reconstructed.
744                 std::vector<std::wstring> parts = split(escape_sequence, L'@');
745                 if (parts[0] == L"E") {
746                         // "End of translation" escape sequence. We are done locating the string to translate.
747                         break;
748                 } else if (parts[0] == L"F") {
749                         // "Start of argument" escape sequence.
750                         // Recursively translate the argument, and add it to the argument list.
751                         // Add an "@n" instead of the argument to the template to translate.
752                         if (arg_number >= 10) {
753                                 errorstream << "Ignoring too many arguments to translation" << std::endl;
754                                 std::wstring arg;
755                                 translate_all(s, i, translations, arg);
756                                 args.push_back(arg);
757                                 continue;
758                         }
759                         output.put(L'@');
760                         output << arg_number;
761                         ++arg_number;
762                         std::wstring arg;
763                         translate_all(s, i, translations, arg);
764                         args.push_back(arg);
765                 } else {
766                         // This is an escape sequence *inside* the template string to translate itself.
767                         // This should not happen, show an error message.
768                         errorstream << "Ignoring escape sequence '" << wide_to_narrow(escape_sequence) << "' in translation" << std::endl;
769                 }
770         }
771
772         std::wstring toutput;
773         // Translate the template.
774         if (translations != nullptr)
775                 toutput = translations->getTranslation(
776                                 textdomain, output.str());
777         else
778                 toutput = output.str();
779
780         // Put back the arguments in the translated template.
781         std::wostringstream result;
782         size_t j = 0;
783         while (j < toutput.length()) {
784                 // Normal character, add it to output and continue.
785                 if (toutput[j] != L'@' || j == toutput.length() - 1) {
786                         result.put(toutput[j]);
787                         ++j;
788                         continue;
789                 }
790
791                 ++j;
792                 // Literal escape for '@'.
793                 if (toutput[j] == L'@') {
794                         result.put(L'@');
795                         ++j;
796                         continue;
797                 }
798
799                 // Here we have an argument; get its index and add the translated argument to the output.
800                 int arg_index = toutput[j] - L'1';
801                 ++j;
802                 if (0 <= arg_index && (size_t)arg_index < args.size()) {
803                         result << args[arg_index];
804                 } else {
805                         // This is not allowed: show an error message
806                         errorstream << "Ignoring out-of-bounds argument escape sequence in translation" << std::endl;
807                 }
808         }
809         res = result.str();
810 }
811
812 void translate_all(const std::wstring &s, size_t &i,
813                 Translations *translations, std::wstring &res)
814 {
815         std::wostringstream output;
816         while (i < s.length()) {
817                 // Not an escape sequence: just add the character.
818                 if (s[i] != '\x1b') {
819                         output.put(s[i]);
820                         ++i;
821                         continue;
822                 }
823
824                 // We have an escape sequence: locate it and its data
825                 // It is either a single character, or it begins with '('
826                 // and extends up to the following ')', with '\' as an escape character.
827                 size_t escape_start = i;
828                 ++i;
829                 size_t start_index = i;
830                 size_t length;
831                 if (i == s.length()) {
832                         length = 0;
833                 } else if (s[i] == L'(') {
834                         ++i;
835                         ++start_index;
836                         while (i < s.length() && s[i] != L')') {
837                                 if (s[i] == L'\\') {
838                                         ++i;
839                                 }
840                                 ++i;
841                         }
842                         length = i - start_index;
843                         ++i;
844                         if (i > s.length())
845                                 i = s.length();
846                 } else {
847                         ++i;
848                         length = 1;
849                 }
850                 std::wstring escape_sequence(s, start_index, length);
851
852                 // The escape sequence is now reconstructed.
853                 std::vector<std::wstring> parts = split(escape_sequence, L'@');
854                 if (parts[0] == L"E") {
855                         // "End of argument" escape sequence. Exit.
856                         break;
857                 } else if (parts[0] == L"T") {
858                         // Beginning of translated string.
859                         std::wstring textdomain;
860                         if (parts.size() > 1)
861                                 textdomain = parts[1];
862                         std::wstring translated;
863                         translate_string(s, translations, textdomain, i, translated);
864                         output << translated;
865                 } else {
866                         // Another escape sequence, such as colors. Preserve it.
867                         output << std::wstring(s, escape_start, i - escape_start);
868                 }
869         }
870
871         res = output.str();
872 }
873
874 // Translate string server side
875 std::wstring translate_string(const std::wstring &s, Translations *translations)
876 {
877         size_t i = 0;
878         std::wstring res;
879         translate_all(s, i, translations, res);
880         return res;
881 }
882
883 // Translate string client side
884 std::wstring translate_string(const std::wstring &s)
885 {
886 #ifdef SERVER
887         return translate_string(s, nullptr);
888 #else
889         return translate_string(s, g_client_translations);
890 #endif
891 }