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