1 /**
2 * @file str.c
3 * Dynamic text string.
4 *
5 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
6 * @authors Copyright © 2008-2013 Daniel Swanson <danij@dengine.net>
7 *
8 * @par License
9 * GPL: http://www.gnu.org/licenses/gpl.html
10 *
11 * <small>This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by the
13 * Free Software Foundation; either version 2 of the License, or (at your
14 * option) any later version. This program is distributed in the hope that it
15 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
16 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
17 * Public License for more details. You should have received a copy of the GNU
18 * General Public License along with this program; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA</small>
21 */
22
23 #include <ctype.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <string.h>
28
29 #if WIN32
30 # define strcasecmp _stricmp
31 #else
32 # include <strings.h>
33 #endif
34
35 #include "de/str.h"
36 #include "de/memory.h"
37 #include "de/memoryzone.h"
38 #include "de/strutil.h"
39 #include <de/c_wrapper.h>
40 #include <de/Garbage>
41
zoneAlloc(size_t n)42 static void *zoneAlloc(size_t n) {
43 return Z_Malloc(n, PU_APPSTATIC, 0);
44 }
45
zoneCalloc(size_t n)46 static void *zoneCalloc(size_t n) {
47 return Z_Calloc(n, PU_APPSTATIC, 0);
48 }
49
stdCalloc(size_t n)50 static void *stdCalloc(size_t n) {
51 return M_Calloc(n);
52 }
53
autoselectMemoryManagement(ddstring_t * str)54 static void autoselectMemoryManagement(ddstring_t *str)
55 {
56 if (!str->memFree && !str->memAlloc && !str->memCalloc)
57 {
58 // If the memory model is unspecified, default to the standard,
59 // it is safer for threading.
60 str->memFree = M_Free;
61 str->memAlloc = M_Malloc;
62 str->memCalloc = stdCalloc;
63 }
64 DENG_ASSERT(str->memFree);
65 DENG_ASSERT(str->memAlloc);
66 DENG_ASSERT(str->memCalloc);
67 }
68
allocateString(ddstring_t * str,size_t forLength,int preserve)69 static void allocateString(ddstring_t *str, size_t forLength, int preserve)
70 {
71 size_t oldSize = str->size;
72 char *buf;
73
74 // Include the terminating null character.
75 forLength++;
76
77 if (str->size >= forLength)
78 return; // We're OK.
79
80 autoselectMemoryManagement(str);
81
82 // Determine the new size of the memory buffer.
83 if (!str->size) str->size = 1;
84 while (str->size < forLength)
85 {
86 str->size *= 2;
87 }
88
89 DENG_ASSERT(str->memCalloc);
90 buf = str->memCalloc(str->size);
91
92 if (preserve && str->str && oldSize)
93 {
94 // Copy the old contents of the string.
95 DENG_ASSERT(oldSize <= str->size);
96 memcpy(buf, str->str, oldSize);
97 }
98
99 // Replace the old string with the new buffer.
100 if (oldSize)
101 {
102 DENG_ASSERT(str->memFree);
103 str->memFree(str->str);
104 }
105 str->str = buf;
106 }
107
108 /**
109 * Call this for uninitialized strings. Global variables are
110 * automatically cleared, so they don't need initialization.
111 * The string will use the memory zone.
112 */
Str_Init(ddstring_t * str)113 ddstring_t *Str_Init(ddstring_t *str)
114 {
115 DENG_ASSERT(str);
116 if (!str) return 0;
117
118 if (!Z_IsInited())
119 {
120 // The memory zone is not available at the moment.
121 return Str_InitStd(str);
122 }
123
124 memset(str, 0, sizeof(*str));
125
126 // Init the memory management.
127 str->memFree = Z_Free;
128 str->memAlloc = zoneAlloc;
129 str->memCalloc = zoneCalloc;
130 return str;
131 }
132
133 /**
134 * The string will use standard memory allocation.
135 */
Str_InitStd(ddstring_t * str)136 ddstring_t *Str_InitStd(ddstring_t *str)
137 {
138 memset(str, 0, sizeof(*str));
139
140 // Init the memory management.
141 str->memFree = free;
142 str->memAlloc = malloc;
143 str->memCalloc = stdCalloc;
144 return str;
145 }
146
Str_InitStatic(ddstring_t * str,char const * staticConstStr)147 ddstring_t *Str_InitStatic(ddstring_t *str, char const *staticConstStr)
148 {
149 memset(str, 0, sizeof(*str));
150 str->str = (char *) staticConstStr;
151 str->length = (staticConstStr? strlen(staticConstStr) : 0);
152 return str;
153 }
154
Str_Free(ddstring_t * str)155 void Str_Free(ddstring_t *str)
156 {
157 DENG_ASSERT(str);
158 if (!str) return;
159
160 autoselectMemoryManagement(str);
161
162 if (str->size)
163 {
164 // The string has memory allocated, free it.
165 str->memFree(str->str);
166 }
167
168 // Memory model left unchanged.
169 str->length = 0;
170 str->size = 0;
171 str->str = 0;
172 }
173
Str_NewStd(void)174 ddstring_t *Str_NewStd(void)
175 {
176 ddstring_t *str = (ddstring_t *) M_Calloc(sizeof(ddstring_t));
177 Str_InitStd(str);
178 return str;
179 }
180
Str_New(void)181 ddstring_t *Str_New(void)
182 {
183 ddstring_t *str = (ddstring_t *) M_Calloc(sizeof(ddstring_t));
184 Str_Init(str);
185 return str;
186 }
187
Str_NewFromReader(Reader1 * reader)188 ddstring_t *Str_NewFromReader(Reader1 *reader)
189 {
190 ddstring_t *str = Str_New();
191 Str_Read(str, reader);
192 return str;
193 }
194
deleteString(Str * str)195 static void deleteString(Str *str)
196 {
197 DENG_ASSERT(str);
198 if (!str) return;
199
200 Str_Free(str);
201 M_Free(str);
202 }
203
Str_Delete(Str * str)204 void Str_Delete(Str *str)
205 {
206 if (!str) return;
207
208 DENG_ASSERT(!Garbage_IsTrashed(str));
209
210 #if 0 // use this is release builds if encountering Str/AutoStr errors
211 if (Garbage_IsTrashed(str))
212 {
213 App_FatalError("Str_Delete: Trying to manually delete an AutoStr!");
214 }
215 #endif
216
217 deleteString(str);
218 }
219
Str_Clear(ddstring_t * str)220 ddstring_t *Str_Clear(ddstring_t *str)
221 {
222 return Str_Set(str, "");
223 }
224
Str_Reserve(ddstring_t * str,int length)225 ddstring_t *Str_Reserve(ddstring_t *str, int length)
226 {
227 DENG_ASSERT(str);
228 if (!str) return 0;
229
230 if (length > 0)
231 {
232 allocateString(str, length, true);
233 }
234 return str;
235 }
236
Str_ReserveNotPreserving(ddstring_t * str,int length)237 ddstring_t *Str_ReserveNotPreserving(ddstring_t *str, int length)
238 {
239 DENG_ASSERT(str);
240 if (!str) return 0;
241
242 if (length > 0)
243 {
244 allocateString(str, length, false);
245 }
246 return str;
247 }
248
Str_Set(ddstring_t * str,char const * text)249 ddstring_t *Str_Set(ddstring_t *str, char const *text)
250 {
251 DENG_ASSERT(str);
252 if (!str) return 0;
253
254 {
255 size_t incoming = strlen(text);
256 char *copied = M_Malloc(incoming + 1); // take a copy in case text points to (a part of) str->str
257 strcpy(copied, text);
258 allocateString(str, incoming, false);
259 strcpy(str->str, copied);
260 str->length = incoming;
261 M_Free(copied);
262 return str;
263 }
264 }
265
Str_AppendWithoutAllocs(ddstring_t * str,const ddstring_t * append)266 ddstring_t *Str_AppendWithoutAllocs(ddstring_t *str, const ddstring_t *append)
267 {
268 DENG_ASSERT(str);
269 DENG_ASSERT(append);
270 DENG_ASSERT(str->length + append->length + 1 <= str->size); // including the null
271
272 if (!str) return 0;
273
274 memcpy(str->str + str->length, append->str, append->length);
275 str->length += append->length;
276 str->str[str->length] = 0;
277 return str;
278 }
279
Str_AppendCharWithoutAllocs(ddstring_t * str,char ch)280 ddstring_t *Str_AppendCharWithoutAllocs(ddstring_t *str, char ch)
281 {
282 DENG_ASSERT(str);
283 DENG_ASSERT(ch); // null not accepted
284 DENG_ASSERT(str->length + 2 <= str->size); // including a terminating null
285
286 if (!str) return 0;
287
288 str->str[str->length++] = ch;
289 str->str[str->length] = 0;
290 return str;
291 }
292
Str_Append(ddstring_t * str,char const * append)293 ddstring_t *Str_Append(ddstring_t *str, char const *append)
294 {
295 DENG_ASSERT(str);
296 if (!str) return 0;
297
298 if (append && append[0])
299 {
300 size_t incoming = strlen(append);
301 // Take a copy in case append_text points to (a part of) ds->str, which may
302 // be invalidated by allocateString.
303 char *copied = M_Malloc(incoming + 1);
304
305 strcpy(copied, append);
306 allocateString(str, str->length + incoming, true);
307 strcpy(str->str + str->length, copied);
308 str->length += incoming;
309
310 M_Free(copied);
311 }
312 return str;
313 }
314
Str_AppendChar(ddstring_t * str,char ch)315 ddstring_t *Str_AppendChar(ddstring_t *str, char ch)
316 {
317 char append[2];
318 append[0] = ch;
319 append[1] = '\0';
320 return Str_Append(str, append);
321 }
322
Str_Appendf(ddstring_t * str,char const * format,...)323 ddstring_t *Str_Appendf(ddstring_t *str, char const *format, ...)
324 {
325 DENG_ASSERT(str);
326 if (!str) return 0;
327
328 { char buf[4096];
329 va_list args;
330
331 // Print the message into the buffer.
332 va_start(args, format);
333 dd_vsnprintf(buf, sizeof(buf), format, args);
334 va_end(args);
335 Str_Append(str, buf);
336 return str;
337 }
338 }
339
Str_PartAppend(ddstring_t * str,char const * append,int start,int count)340 ddstring_t *Str_PartAppend(ddstring_t *str, char const *append, int start, int count)
341 {
342 int partLen;
343 char *copied;
344
345 DENG_ASSERT(str);
346 DENG_ASSERT(append);
347
348 if (!str || !append) return str;
349 if (start < 0 || count <= 0) return str;
350
351 copied = M_Malloc(count + 1);
352 copied[0] = 0; // empty string
353
354 strncat(copied, append + start, count);
355 partLen = strlen(copied);
356
357 allocateString(str, str->length + partLen + 1, true);
358 memcpy(str->str + str->length, copied, partLen);
359 str->length += partLen;
360
361 // Terminate the appended part.
362 str->str[str->length] = 0;
363
364 M_Free(copied);
365 return str;
366 }
367
Str_Prepend(ddstring_t * str,char const * prepend)368 ddstring_t *Str_Prepend(ddstring_t *str, char const *prepend)
369 {
370 char *copied;
371 size_t incoming;
372
373 DENG_ASSERT(str);
374 DENG_ASSERT(prepend);
375
376 if (!str || !prepend) return str;
377
378 incoming = strlen(prepend);
379 if (incoming == 0)
380 return str;
381
382 copied = M_Malloc(incoming);
383 memcpy(copied, prepend, incoming);
384
385 allocateString(str, str->length + incoming, true);
386 memmove(str->str + incoming, str->str, str->length + 1);
387 memcpy(str->str, copied, incoming);
388 str->length += incoming;
389
390 M_Free(copied);
391 return str;
392 }
393
Str_PrependChar(ddstring_t * str,char ch)394 ddstring_t *Str_PrependChar(ddstring_t *str, char ch)
395 {
396 char prepend[2];
397 prepend[0] = ch;
398 prepend[1] = '\0';
399 return Str_Prepend(str, prepend);
400 }
401
Str_Text(const ddstring_t * str)402 char *Str_Text(const ddstring_t *str)
403 {
404 if (!str) return "[null]";
405 return str->str ? str->str : "";
406 }
407
Str_Length(const ddstring_t * str)408 int Str_Length(const ddstring_t *str)
409 {
410 return (int) Str_Size(str);
411 }
412
Str_Size(Str const * str)413 size_t Str_Size(Str const *str)
414 {
415 DENG_ASSERT(str);
416
417 if (!str) return 0;
418 if (str->length)
419 {
420 return str->length;
421 }
422 return strlen(Str_Text(str));
423 }
424
Str_IsEmpty(const ddstring_t * str)425 dd_bool Str_IsEmpty(const ddstring_t *str)
426 {
427 DENG_ASSERT(str);
428 return Str_Length(str) == 0;
429 }
430
Str_Copy(ddstring_t * str,const ddstring_t * other)431 ddstring_t *Str_Copy(ddstring_t *str, const ddstring_t *other)
432 {
433 DENG_ASSERT(str);
434 DENG_ASSERT(other);
435 if (!str || !other) return str;
436
437 Str_Free(str);
438
439 if (!other->size)
440 {
441 // The original string has no memory allocated; it's a static string.
442 allocateString(str, other->length, false);
443 if (other->str)
444 strcpy(str->str, other->str);
445 str->length = other->length;
446 }
447 else
448 {
449 // Duplicate the other string's buffer in its entirety.
450 str->str = str->memAlloc(other->size);
451 memcpy(str->str, other->str, other->size);
452 str->size = other->size;
453 str->length = other->length;
454 }
455 return str;
456 }
457
Str_CopyOrClear(ddstring_t * dest,const ddstring_t * src)458 ddstring_t *Str_CopyOrClear(ddstring_t *dest, const ddstring_t *src)
459 {
460 DENG_ASSERT(dest);
461 if (!dest) return 0;
462
463 if (src)
464 {
465 return Str_Copy(dest, src);
466 }
467 return Str_Clear(dest);
468 }
469
Str_StripLeft2(ddstring_t * str,int * count)470 ddstring_t *Str_StripLeft2(ddstring_t *str, int *count)
471 {
472 int i, num;
473 dd_bool isDone;
474
475 DENG_ASSERT(str);
476 if (!str) return 0;
477
478 if (!str->length)
479 {
480 if (count) *count = 0;
481 return str;
482 }
483
484 // Find out how many whitespace chars are at the beginning.
485 isDone = false;
486 i = 0;
487 num = 0;
488 while (i < (int)str->length && !isDone)
489 {
490 if (isspace(str->str[i]))
491 {
492 num++;
493 i++;
494 }
495 else
496 isDone = true;
497 }
498
499 if (num)
500 {
501 // Remove 'num' chars.
502 memmove(str->str, str->str + num, str->length - num);
503 str->length -= num;
504 str->str[str->length] = 0;
505 }
506
507 if (count) *count = num;
508 return str;
509 }
510
Str_StripLeft(ddstring_t * str)511 ddstring_t *Str_StripLeft(ddstring_t *str)
512 {
513 return Str_StripLeft2(str, NULL/*not interested in the stripped character count*/);
514 }
515
Str_StripRight2(ddstring_t * str,int * count)516 ddstring_t *Str_StripRight2(ddstring_t *str, int *count)
517 {
518 int i, num;
519
520 DENG_ASSERT(str);
521 if (!str) return 0;
522
523 if (str->length == 0)
524 {
525 if (count) *count = 0;
526 return str;
527 }
528
529 i = str->length - 1;
530 num = 0;
531 if (isspace(str->str[i]))
532 do
533 {
534 // Remove this char.
535 num++;
536 str->str[i] = '\0';
537 str->length--;
538 } while (i != 0 && isspace(str->str[--i]));
539
540 if (count) *count = num;
541 return str;
542 }
543
Str_StripRight(ddstring_t * str)544 ddstring_t *Str_StripRight(ddstring_t *str)
545 {
546 return Str_StripRight2(str, NULL/*not interested in the stripped character count*/);
547 }
548
Str_Strip2(ddstring_t * str,int * count)549 ddstring_t *Str_Strip2(ddstring_t *str, int *count)
550 {
551 int right_count, left_count;
552 Str_StripLeft2(Str_StripRight2(str, &right_count), &left_count);
553 if (count) *count = right_count + left_count;
554 return str;
555 }
556
Str_Strip(ddstring_t * str)557 ddstring_t *Str_Strip(ddstring_t *str)
558 {
559 return Str_Strip2(str, NULL/*not interested in the stripped character count*/);
560 }
561
Str_StartsWith(Str const * ds,char const * text)562 dd_bool Str_StartsWith(Str const *ds, char const *text)
563 {
564 size_t len = strlen(text);
565 DENG_ASSERT(ds);
566 if (!ds->str || !text || Str_Size(ds) < len) return false;
567 return !strncmp(ds->str, text, len);
568 }
569
Str_EndsWith(Str const * ds,char const * text)570 dd_bool Str_EndsWith(Str const *ds, char const *text)
571 {
572 size_t len = strlen(text);
573 DENG_ASSERT(ds);
574 if (!ds->str || !text || Str_Size(ds) < len) return false;
575 return !strcmp(ds->str + Str_Size(ds) - len, text);
576 }
577
Str_ReplaceAll(Str * ds,char from,char to)578 Str *Str_ReplaceAll(Str *ds, char from, char to)
579 {
580 size_t i;
581 size_t len = Str_Length(ds);
582
583 if (!ds || !ds->str) return ds;
584
585 for (i = 0; i < len; ++i)
586 {
587 if (ds->str[i] == from)
588 ds->str[i] = to;
589 }
590
591 return ds;
592 }
593
Str_GetLine(ddstring_t * str,char const * src)594 char const *Str_GetLine(ddstring_t *str, char const *src)
595 {
596 DENG_ASSERT(str);
597 if (!str) return 0;
598
599 if (src != 0)
600 {
601 // We'll append the chars one by one.
602 char buf[2];
603 memset(buf, 0, sizeof(buf));
604 for (Str_Clear(str); *src && *src != '\n'; src++)
605 {
606 if (*src != '\r')
607 {
608 buf[0] = *src;
609 Str_Append(str, buf);
610 }
611 }
612
613 // Strip whitespace around the line.
614 Str_Strip(str);
615
616 // The newline is excluded.
617 if (*src == '\n')
618 src++;
619 }
620 return src;
621 }
622
Str_Compare(const ddstring_t * str,char const * text)623 int Str_Compare(const ddstring_t *str, char const *text)
624 {
625 DENG_ASSERT(str);
626 return strcmp(Str_Text(str), text);
627 }
628
Str_CompareIgnoreCase(const ddstring_t * str,char const * text)629 int Str_CompareIgnoreCase(const ddstring_t *str, char const *text)
630 {
631 DENG_ASSERT(str);
632 return strcasecmp(Str_Text(str), text);
633 }
634
Str_CopyDelim2(ddstring_t * str,char const * src,char delimiter,int cdflags)635 char const *Str_CopyDelim2(ddstring_t *str, char const *src, char delimiter, int cdflags)
636 {
637 DENG_ASSERT(str);
638 if (!str) return 0;
639
640 Str_Clear(str);
641
642 if (!src) return 0;
643
644 { char const *cursor;
645 ddstring_t buf; Str_Init(&buf);
646 for (cursor = src; *cursor && *cursor != delimiter; ++cursor)
647 {
648 if ((cdflags & CDF_OMIT_WHITESPACE) && isspace(*cursor))
649 continue;
650 Str_PartAppend(&buf, cursor, 0, 1);
651 }
652 if (!Str_IsEmpty(&buf))
653 Str_Copy(str, &buf);
654 Str_Free(&buf);
655
656 if (!*cursor)
657 return 0; // It ended.
658
659 if (!(cdflags & CDF_OMIT_DELIMITER))
660 Str_PartAppend(str, cursor, 0, 1);
661
662 // Skip past the delimiter.
663 return cursor + 1;
664 }
665 }
666
Str_CopyDelim(ddstring_t * dest,char const * src,char delimiter)667 char const *Str_CopyDelim(ddstring_t *dest, char const *src, char delimiter)
668 {
669 return Str_CopyDelim2(dest, src, delimiter, CDF_OMIT_DELIMITER | CDF_OMIT_WHITESPACE);
670 }
671
Str_At(const ddstring_t * str,int index)672 char Str_At(const ddstring_t *str, int index)
673 {
674 DENG_ASSERT(str);
675 if (!str) return 0;
676
677 if (index < 0 || index >= (int)str->length)
678 return 0;
679 return str->str[index];
680 }
681
Str_RAt(const ddstring_t * str,int reverseIndex)682 char Str_RAt(const ddstring_t *str, int reverseIndex)
683 {
684 DENG_ASSERT(str);
685 if (!str) return 0;
686
687 if (reverseIndex < 0 || reverseIndex >= (int)str->length)
688 return 0;
689 return str->str[str->length - 1 - reverseIndex];
690 }
691
Str_Truncate(ddstring_t * str,int position)692 void Str_Truncate(ddstring_t *str, int position)
693 {
694 DENG_ASSERT(str);
695 if (!str) return;
696
697 if (position < 0)
698 position = 0;
699 if (!(position < Str_Length(str)))
700 return;
701 str->length = position;
702 str->str[str->length] = '\0';
703 }
704
705 /// @note Derived from Qt's QByteArray q_toPercentEncoding
Str_PercentEncode2(ddstring_t * str,char const * excludeChars,char const * includeChars)706 ddstring_t *Str_PercentEncode2(ddstring_t *str, char const *excludeChars, char const *includeChars)
707 {
708 dd_bool didEncode = false;
709 int i, span, begin, len;
710 ddstring_t buf;
711
712 DENG_ASSERT(str);
713 if (!str) return 0;
714
715 if (Str_IsEmpty(str)) return str;
716
717 len = Str_Length(str);
718 begin = span = 0;
719 for (i = 0; i < len; ++i)
720 {
721 char ch = str->str[i];
722
723 // Are we encoding this?
724 if (((ch >= 0x61 && ch <= 0x7A) // ALPHA
725 || (ch >= 0x41 && ch <= 0x5A) // ALPHA
726 || (ch >= 0x30 && ch <= 0x39) // DIGIT
727 || ch == 0x2D // -
728 || ch == 0x2E // .
729 || ch == 0x5F // _
730 || ch == 0x7E // ~
731 || (excludeChars && strchr(excludeChars, ch)))
732 && !(includeChars && strchr(includeChars, ch)))
733 {
734 // Not an encodeable. Span grows.
735 span++;
736 }
737 else
738 {
739 // Found an encodeable.
740 if (!didEncode)
741 {
742 Str_InitStd(&buf);
743 Str_Reserve(&buf, len*3); // Worst case.
744 didEncode = true;
745 }
746
747 Str_PartAppend(&buf, str->str, begin, span);
748 Str_Appendf(&buf, "%%%X", (uint)ch);
749
750 // Start a new span.
751 begin += span + 1;
752 span = 0;
753 }
754 }
755
756 if (didEncode)
757 {
758 // Copy anything remaining.
759 if (span)
760 {
761 Str_PartAppend(&buf, str->str, begin, span);
762 }
763
764 Str_Set(str, Str_Text(&buf));
765 Str_Free(&buf);
766 }
767
768 return str;
769 }
770
Str_PercentEncode(ddstring_t * str)771 ddstring_t *Str_PercentEncode(ddstring_t *str)
772 {
773 return Str_PercentEncode2(str, 0/*no exclusions*/, 0/*no forced inclussions*/);
774 }
775
776 /// @note Derived from Qt's QByteArray q_fromPercentEncoding
Str_PercentDecode(ddstring_t * str)777 ddstring_t *Str_PercentDecode(ddstring_t *str)
778 {
779 int i, len, outlen, a, b;
780 char const *inputPtr;
781 char *data;
782 char c;
783
784 DENG_ASSERT(str);
785 if (!str) return 0;
786
787 if (Str_IsEmpty(str)) return str;
788
789 data = str->str;
790 inputPtr = data;
791
792 i = 0;
793 len = Str_Length(str);
794 outlen = 0;
795
796 while (i < len)
797 {
798 c = inputPtr[i];
799 if (c == '%' && i + 2 < len)
800 {
801 a = inputPtr[++i];
802 b = inputPtr[++i];
803
804 if (a >= '0' && a <= '9') a -= '0';
805 else if (a >= 'a' && a <= 'f') a = a - 'a' + 10;
806 else if (a >= 'A' && a <= 'F') a = a - 'A' + 10;
807
808 if (b >= '0' && b <= '9') b -= '0';
809 else if (b >= 'a' && b <= 'f') b = b - 'a' + 10;
810 else if (b >= 'A' && b <= 'F') b = b - 'A' + 10;
811
812 *data++ = (char)((a << 4) | b);
813 }
814 else
815 {
816 *data++ = c;
817 }
818
819 ++i;
820 ++outlen;
821 }
822
823 if (outlen != len)
824 Str_Truncate(str, outlen);
825
826 return str;
827 }
828
Str_Write(const ddstring_t * str,Writer1 * writer)829 void Str_Write(const ddstring_t *str, Writer1 *writer)
830 {
831 size_t len = Str_Length(str);
832
833 DENG_ASSERT(str);
834
835 Writer_WriteUInt32(writer, len);
836 Writer_Write(writer, Str_Text(str), len);
837 }
838
Str_Read(ddstring_t * str,Reader1 * reader)839 void Str_Read(ddstring_t *str, Reader1 *reader)
840 {
841 size_t len = Reader_ReadUInt32(reader);
842 char *buf = malloc(len + 1);
843 Reader_Read(reader, buf, len);
844 buf[len] = 0;
845 Str_Set(str, buf);
846 free(buf);
847 }
848
AutoStr_New(void)849 AutoStr *AutoStr_New(void)
850 {
851 return AutoStr_FromStr(Str_New());
852 }
853
AutoStr_NewStd(void)854 AutoStr *AutoStr_NewStd(void)
855 {
856 return AutoStr_FromStr(Str_NewStd());
857 }
858
AutoStr_Delete(AutoStr * as)859 void AutoStr_Delete(AutoStr *as)
860 {
861 deleteString(as);
862 }
863
AutoStr_FromStr(Str * str)864 AutoStr *AutoStr_FromStr(Str *str)
865 {
866 DENG_ASSERT(str);
867 Garbage_TrashInstance(str, (GarbageDestructor) AutoStr_Delete);
868 return str;
869 }
870
AutoStr_FromText(char const * text)871 AutoStr *AutoStr_FromText(char const *text)
872 {
873 return Str_Set(AutoStr_New(), text);
874 }
875
AutoStr_FromTextStd(const char * text)876 AutoStr *AutoStr_FromTextStd(const char *text)
877 {
878 return Str_Set(AutoStr_NewStd(), text);
879 }
880
Str_FromAutoStr(AutoStr * as)881 ddstring_t *Str_FromAutoStr(AutoStr *as)
882 {
883 DENG_ASSERT(as);
884 Garbage_Untrash(as);
885 return as;
886 }
887