/*************************************************************************** * File: string.c * * Usage: various functions for new NOTE system * * * * Much time and thought has gone into this software and you are * * benefitting. We hope that you share your changes too. What goes * * around, comes around. * * * * Much of this code came from CircleMUD. * * CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. * * * * Revised for Merc 2.1 by Jason Dinkel. * * Revised for Envy 1.0 by Jason Dinkel. * ***************************************************************************/ /*************************************************************************** * God Wars Mud originally written by KaVir aka Richard Woolcock. * * Changes done to the code done by Sage aka Walter Howard, this mud is * * for the public, however if you run this code it means you agree * * to the license.low, license.gw, and license.merc have fun. :) * ***************************************************************************/ #if defined(macintosh) #include <types.h> #else #include <sys/types.h> #endif #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "merc.h" /* * Colour codes. */ int is_colour args( ( char code ) ); /* * Special codes. */ #define C_UDL "\x1B[4m" /* Underline ANSI code */ #define C_NRM CLEAR #define MAX_COLOURS 17 const char *COLOURLIST[] = { C_NRM, C_RED, C_GREEN, C_YELLOW, C_BLUE, C_MAGENTA, C_CYAN, C_WHITE, C_D_GREY, C_B_RED, C_B_GREEN, C_B_YELLOW, C_B_BLUE, C_B_MAGENTA, C_B_CYAN, C_B_WHITE, C_UDL }; void string_edit( CHAR_DATA *ch, char **pString ) { send_to_char( " --<[====]>--<[ String Editor ]>--<[====]>--\n\r" " [ Type .h on a new line for help. ]\n\r" " [ @ on a blank line to finish. ]\n\r" " --<[===================================]>--\n\r", ch ); if ( *pString == NULL ) { *pString = str_dup( "" ); } else { **pString = '\0'; } ch->desc->pString = pString; return; } void string_append( CHAR_DATA *ch, char **pString ) { send_to_char( " --<[====]>--<[ String Editor ]>--<[====]>--\n\r" " [ Type .h on a new line for help. ]\n\r" " [ @ on a blank line to finish. ]\n\r" " --<[===================================]>--\n\r", ch ); if ( *pString == NULL ) { *pString = str_dup( "" ); } send_to_char( *pString, ch ); if ( *(*pString + strlen( *pString ) - 1) != '\r' ) send_to_char( "\n\r", ch ); ch->desc->pString = pString; return; } char * string_replace( char * orig, char * old, char * new ) { char xbuf[MAX_STRING_LENGTH]; int i; xbuf[0] = '\0'; strcpy( xbuf, orig ); if ( strstr( orig, old ) != NULL ) { i = strlen( orig ) - strlen( strstr( orig, old ) ); xbuf[i] = '\0'; strcat( xbuf, new ); strcat( xbuf, &orig[i+strlen( old )] ); free_string( orig ); } return str_dup( xbuf ); } char *string_replace2 (CHAR_DATA * ch, char *orig, int line, char *new) { char *rdesc; char xbuf[MAX_STRING_LENGTH]; int current_line = 1; int i; bool fReplaced = FALSE; xbuf[0] = '\0'; strcpy (xbuf, orig); i = 0; for (rdesc = orig; *rdesc; rdesc++) { if (current_line == line && !fReplaced) { xbuf[i] = '\0'; if (*new) strcat (xbuf, new); strcat (xbuf, "\n\r"); fReplaced = TRUE; } if (current_line == line + 1) { strcat (xbuf, &orig[i]); free_string (orig); send_to_char ("Line replaced.\n\r", ch); return str_dup (xbuf); } i++; if (*rdesc == '\r') current_line++; } if (current_line - 1 != line) { send_to_char ("That line does not exist.\n\r", ch); return str_dup (xbuf); } free_string (orig); send_to_char ("Line replaced.\n\r", ch); return str_dup (xbuf); } /***************************************************************************** Name: string_insertline Purpose: Inserts a line, blank or containing text. Called by: string_add(string.c) (aedit_builder)olc_act.c. ****************************************************************************/ char *string_insertline (CHAR_DATA * ch, char *orig, int line, char *addstring) { char *rdesc; char xbuf[MAX_STRING_LENGTH]; int current_line = 1; int i; xbuf[0] = '\0'; strcpy (xbuf, orig); i = 0; for (rdesc = orig; *rdesc; rdesc++) { if (current_line == line) break; i++; if (*rdesc == '\r') current_line++; } if (!*rdesc) { send_to_char ("That line does not exist.\n\r", ch); return str_dup (xbuf); } xbuf[i] = '\0'; if (*addstring) strcat (xbuf, addstring); strcat (xbuf, "\n\r"); strcat (xbuf, &orig[i]); free_string (orig); send_to_char ("Line inserted.\n\r", ch); return str_dup (xbuf); } /***************************************************************************** Name: string_deleteline Purpose: Deletes a specified line of the string. Called by: string_add(string.c) (aedit_builder)olc_act.c. ****************************************************************************/ char *string_deleteline (char *orig, int line) { char *rdesc; char xbuf[MAX_STRING_LENGTH]; int current_line = 1; int i = 0; xbuf[0] = '\0'; for (rdesc = orig; *rdesc; rdesc++) { if (current_line != line) { xbuf[i] = *rdesc; i++; } if (*rdesc == '\r') current_line++; } free_string (orig); xbuf[i] = 0; return str_dup (xbuf); } /***************************************************************************** Name: string_add Purpose: Interpreter for string editing. Called by: game_loop_xxxx(comm.c). ****************************************************************************/ void string_add (CHAR_DATA * ch, char *argument) { char buf[MAX_STRING_LENGTH]; /* * Thanks to James Seng */ smash_tilde (argument); if (*argument == '.') { char arg1[MAX_INPUT_LENGTH]; char arg2[MAX_INPUT_LENGTH]; char arg3[MAX_INPUT_LENGTH]; argument = one_argument (argument, arg1); if (!str_cmp (arg1, "./")) { interpret(ch, argument); send_to_char ("Command performed.\n\r", ch); return; } argument = first_arg (argument, arg2, FALSE); argument = first_arg (argument, arg3, FALSE); if (!str_cmp (arg1, ".c")) { send_to_char ("String cleared.\n\r", ch); **ch->desc->pString = '\0'; return; } if (!str_cmp (arg1, ".s")) { char *rdesc; int i = 1; ch_printf (ch, "`5%2d`` ", i); for (rdesc = *ch->desc->pString; *rdesc; rdesc++) { if (*rdesc != '{') /* ` */ ch_printf (ch, "%c", rdesc[0]); else { if (rdesc[1] == 'Z') send_to_char ("{Z}", ch); else ch_printf (ch, "%c%c", rdesc[0], rdesc[1]); rdesc++; } if (*rdesc == '\r' && *(rdesc + 1)) { i++; ch_printf (ch, "`5%2d`` ", i); } } /* send_to_char( *ch->desc->pString, ch ); */ return; } #ifdef ISPELL if (!str_cmp (arg1, ".sp")) { spell_check (ch, *ch->desc->pString); return; } #endif if (!str_cmp (arg1, ".r")) { if (arg2[0] == '\0') { send_to_char ( "usage: .r \"old string\" \"new string\"\n\r", ch); return; } smash_tilde (arg3); /* Just to be sure -- Hugin */ *ch->desc->pString = string_replace (*ch->desc->pString, arg2, arg3); sprintf (buf, "'%s' replaced with '%s'.\n\r", arg2, arg3); send_to_char (buf, ch); return; } if (!str_cmp (arg1, ".rl")) { if (arg2[0] == '\0' || !is_number (arg2)) { send_to_char ( "usage: .rl <line> <text>\n\r", ch); return; } smash_tilde (arg3); /* Just to be sure -- Hugin */ *ch->desc->pString = string_replace2 (ch, *ch->desc->pString, atoi (arg2), arg3); return; } if (!str_cmp (arg1, ".i")) { if (arg2[0] == '\0' || !is_number (arg2)) { send_to_char ( "usage: .i <line> {text}\n\r", ch); return; } smash_tilde (arg3); /* Just to be sure -- Hugin */ *ch->desc->pString = string_insertline (ch, *ch->desc->pString, atoi (arg2), arg3); return; } if (!str_cmp (arg1, ".d")) { if (arg2[0] == '\0' || !is_number (arg2)) { send_to_char ( "usage: .d <line>\n\r", ch); return; } *ch->desc->pString = string_deleteline (*ch->desc->pString, atoi (arg2)); sprintf (buf, "Line %d deleted.\n\r", atoi (arg2)); send_to_char (buf, ch); return; } if (!str_cmp (arg1, ".f")) { *ch->desc->pString = format_string (*ch->desc->pString); send_to_char ("String formatted.\n\r", ch); return; } if (!str_cmp (arg1, ".h")) { send_to_char ("Sedit help (commands on blank line): \n\r", ch); send_to_char (".r 'old' 'new' - replace a substring \n\r", ch); send_to_char (" (requires '', \"\") \n\r", ch); send_to_char (".rl <line> <text> - replaces a line \n\r", ch); send_to_char (".h - get help (this info)\n\r", ch); send_to_char (".s - show string so far \n\r", ch); #ifdef ISPELL send_to_char (".sp - spell check string \n\r", ch); #endif send_to_char (".f - (word wrap) string \n\r", ch); send_to_char (".c - clear string so far \n\r", ch); send_to_char (".d <line> - deletes a line \n\r", ch); send_to_char (".i <line> {{text} - inserts a line \n\r", ch); send_to_char ("./ <command> - do a regular command\n\r", ch); send_to_char ("@ - end string \n\r", ch); return; } send_to_char ("SEdit: Invalid dot command.\n\r", ch); return; } if (*argument == '~' || *argument == '@') { /* if (ch->desc->editor == ED_NOTE_AJUST) write_all_notes (); */ ch->desc->pString = NULL; ch->desc->editor = 0; return; } strcpy (buf, *ch->desc->pString); /* * Truncate strings to MAX_STRING_LENGTH. * -------------------------------------- */ if (strlen (buf) + strlen (argument) >= (MAX_STRING_LENGTH - 4)) { send_to_char ("String too long, last line skipped.\n\r", ch); /* Force character out of editing mode. */ ch->desc->pString = NULL; return; } /* * Ensure no tilde's inside string. * -------------------------------- */ smash_tilde (argument); strcat (buf, argument); strcat (buf, "\n\r"); free_string (*ch->desc->pString); *ch->desc->pString = str_dup (buf); return; } /* * Thanks to Kalgen for the new procedure (no more bug!) * Original wordwrap() written by Surreality. */ char *format_string( char *oldstring /*, bool fSpace */) { char xbuf[MAX_STRING_LENGTH]; char xbuf2[MAX_STRING_LENGTH]; char *rdesc; int i=0; bool cap=TRUE; xbuf[0]=xbuf2[0]=0; i=0; for (rdesc = oldstring; *rdesc; rdesc++) { if (*rdesc=='\n') { if (xbuf[i-1] != ' ') { xbuf[i]=' '; i++; } } else if (*rdesc=='\r') ; else if (*rdesc==' ') { if (xbuf[i-1] != ' ') { xbuf[i]=' '; i++; } } else if (*rdesc==')') { if (xbuf[i-1]==' ' && xbuf[i-2]==' ' && (xbuf[i-3]=='.' || xbuf[i-3]=='?' || xbuf[i-3]=='!')) { xbuf[i-2]=*rdesc; xbuf[i-1]=' '; xbuf[i]=' '; i++; } else { xbuf[i]=*rdesc; i++; } } else if (*rdesc=='.' || *rdesc=='?' || *rdesc=='!') { if (xbuf[i-1]==' ' && xbuf[i-2]==' ' && (xbuf[i-3]=='.' || xbuf[i-3]=='?' || xbuf[i-3]=='!')) { xbuf[i-2]=*rdesc; if (*(rdesc+1) != '\"') { xbuf[i-1]=' '; xbuf[i]=' '; i++; } else { xbuf[i-1]='\"'; xbuf[i]=' '; xbuf[i+1]=' '; i+=2; rdesc++; } } else { xbuf[i]=*rdesc; if (*(rdesc+1) != '\"') { xbuf[i+1]=' '; xbuf[i+2]=' '; i += 3; } else { xbuf[i+1]='\"'; xbuf[i+2]=' '; xbuf[i+3]=' '; i += 4; rdesc++; } } cap = TRUE; } else { xbuf[i]=*rdesc; if ( cap ) { cap = FALSE; xbuf[i] = UPPER( xbuf[i] ); } i++; } } xbuf[i]=0; strcpy(xbuf2,xbuf); rdesc=xbuf2; xbuf[0]=0; for ( ; ; ) { for (i=0; i<77; i++) { if (!*(rdesc+i)) break; } if (i<77) { break; } for (i=(xbuf[0]?76:73) ; i ; i--) { if (*(rdesc+i)==' ') break; } if (i) { *(rdesc+i)=0; strcat(xbuf,rdesc); strcat(xbuf,"\n\r"); rdesc += i+1; while (*rdesc == ' ') rdesc++; } else { bug ("No spaces", 0); *(rdesc+75)=0; strcat(xbuf,rdesc); strcat(xbuf,"-\n\r"); rdesc += 76; } } while (*(rdesc+i) && (*(rdesc+i)==' '|| *(rdesc+i)=='\n'|| *(rdesc+i)=='\r')) i--; *(rdesc+i+1)=0; strcat(xbuf,rdesc); if (xbuf[strlen(xbuf)-2] != '\n') strcat(xbuf,"\n\r"); free_string(oldstring); return(str_dup(xbuf)); } /* * Pick off one argument from a string and return the rest. * Understands quotes, parenthesis (barring ) ('s) and percentages. */ char *first_arg( char *argument, char *arg_first, bool fCase ) { char cEnd; while ( *argument == ' ' ) argument++; cEnd = ' '; if ( *argument == '\'' || *argument == '"' || *argument == '%' || *argument == '(' ) { if ( *argument == '(' ) { cEnd = ')'; argument++; } else cEnd = *argument++; } while ( *argument != '\0' ) { if ( *argument == cEnd ) { argument++; break; } if ( fCase ) *arg_first = LOWER(*argument); else *arg_first = *argument; arg_first++; argument++; } *arg_first = '\0'; while ( *argument == ' ' ) argument++; return argument; } char * string_unpad( char * argument ) { char buf[MAX_STRING_LENGTH]; char *s; s = argument; while ( *s == ' ' ) s++; strcpy( buf, s ); s = buf; if ( *s != '\0' ) { while ( *s != '\0' ) s++; s--; while( *s == ' ' ) s--; s++; *s = '\0'; } free_string( argument ); return str_dup( buf ); } char * string_proper( char * argument ) { char *s; s = argument; while ( *s != '\0' ) { if ( *s != ' ' ) { *s = UPPER(*s); while ( *s != ' ' && *s != '\0' ) s++; } else { s++; } } return argument; } int arg_count( char *argument ) { int total; char *s; total = 0; s = argument; while ( *s != '\0' ) { if ( *s != ' ' ) { total++; while ( *s != ' ' && *s != '\0' ) s++; } else { s++; } } return total; } char * current_date( ) { static char buf [ 128 ]; struct tm * datetime; datetime = localtime( ¤t_time ); strftime( buf, sizeof( buf ), "%x", datetime ); return buf; } /* * This operation draws an 80 character line with a word in the centre */ void text_bar( CHAR_DATA *ch, char *argument, bool pager ) { char bar_buf[MAX_INPUT_LENGTH]; /* Buffer to store text bar */ int text_length = strlen( argument ); /* Length of the text */ int word_start; /* Start of text word */ int loop; /* Loop counter */ word_start = 37 - ((text_length+1) >> 1); /* Initialise word start */ /* If the text is too big, just display it as is */ if ( text_length > 70 ) { send_to_char(argument,ch); send_to_char("\n\r",ch); return; } sprintf( bar_buf, "-%s", capitalize(ch->name) ); /* Set the left half of the bar to spaces */ for ( loop = strlen(bar_buf); loop < word_start; loop++ ) { bar_buf[loop] = '-'; } /* Terminate the bar string so that strcat can be used to add the word */ bar_buf[word_start] = '\0'; strcat( bar_buf, "[ "); strcat( bar_buf, argument ); strcat( bar_buf, " ]" ); for( loop = strlen(bar_buf)-1; loop < SCREEN_WIDTH-10; loop++ ) strcat( bar_buf, "-"); strcat( bar_buf, current_date()); strcat( bar_buf, "-" ); strcat( bar_buf, "\n\r" ); /* Display the text bar */ if( pager ) page_to_char( bar_buf, ch ); else send_to_char( bar_buf, ch ); return; } /* * Take a number like 43 and make it forty-three. */ char * const ones_numerals [10] = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; char * const tens_numerals [10] = { "-", "-", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" }; char * const meta_numerals [4] = { "hundred", "thousand", "million", "billion" }; char * const special_numbers [10] = { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" }; char *numberize( int n ) { static char buf[MAX_STRING_LENGTH]; sh_int digits[3]; int t = abs(n); buf[0] = '\0'; /* * Special cases (10-19) */ if ( n >= 10 && n <= 19 ) { sprintf( buf, "%s", special_numbers[n-10] ); return buf; } if ( n < 10 && n >= 0 ) { sprintf( buf, "%s", ones_numerals[n] ); return buf; } /* * Cha. */ if ( n >= 10000 || n < 0 ) { sprintf( buf, "%d", n ); return buf; } digits[3] = t / 1000; t -= 1000*digits[3]; digits[2] = t / 100; t -= 100*digits[2]; digits[1] = t / 10; t -= 10*digits[1]; digits[0] = t; if ( digits[3] > 0 ) { sprintf( buf, "%s%s", buf, ones_numerals[digits[3]] ); sprintf( buf, "%s thousand ", buf ); } if ( digits[2] > 0 ) { sprintf( buf, "%s%s", buf, ones_numerals[digits[2]] ); sprintf( buf, "%s hundred ", buf ); } if ( digits[1] > 0 ) { sprintf( buf, "%s%s", buf, tens_numerals[digits[1]] ); if ( digits[0] > 0 ) sprintf( buf, "%s-", buf ); } if ( digits[0] > 0 ) { sprintf( buf, "%s%s", buf, ones_numerals[digits[0]] ); } if ( buf[(t = strlen(buf)-1)] == ' ' ) buf[t] = '\0'; if ( buf[(t = strlen(buf)-1)] == ' ' ) buf[t] = '\0'; return buf; } char *smash_article( char *text ) { char *arg; char buf[MAX_STRING_LENGTH]; static char buf2[MAX_STRING_LENGTH]; one_argument( text, buf ); if ( !str_cmp( buf, "the" ) || !str_cmp( buf, "an" ) || !str_cmp( buf, "a" ) ) { arg = one_argument( text, buf ); sprintf( buf2, "%s", arg ); } else strcpy( buf2, text ); return buf2; } /* * Sees if last char is 's' and returns 'is' or 'are' pending. */ char * is_are( char *text ) { while ( *text != '\0' ) { text++; } text--; if ( LOWER(*text) == 's' && LOWER(*text-1) != 's' ) return "are"; else return "is"; } char *pluralize( char *argument ) { static char buf[MAX_STRING_LENGTH]; char *v; sprintf( buf, "%s", smash_article(argument) ); v = strstr( buf, " of " ); if ( v == NULL ) { if ( LOWER(buf[strlen(buf)-1]) == 'y' ) { buf[strlen(buf)-1] = 'i'; strcat( buf, "es" ); } else if ( LOWER(buf[strlen(buf)-1]) == 'f' && !(LOWER(buf[strlen(buf)-2]) == 'i' && LOWER(buf[strlen(buf)-3]) == 'o') ) { buf[strlen(buf)-1] = 'v'; strcat( buf, "es" ); } else if ( LOWER(buf[strlen(buf)-1]) == 'h' ) strcat( buf, "es" ); else if ( LOWER(buf[strlen(buf)-1]) == 's' ) { if ( LOWER(buf[strlen(buf)-2]) == 'u' && !IS_VOWEL(LOWER(buf[strlen(buf)-3])) ) { buf[strlen(buf)-2] = 'i'; buf[strlen(buf)-1] = '\0'; } else strcat( buf, "es" ); } else strcat( buf, "s" ); } else { char xbuf[MAX_STRING_LENGTH]; sprintf( xbuf, "%s", v ); buf[strlen(buf)-strlen(v)] = '\0'; if ( LOWER(buf[strlen(buf)-1]) == 'y' ) { buf[strlen(buf)-1] = 'i'; strcat( buf, "es" ); } else if ( LOWER(buf[strlen(buf)-1]) == 'f' && !(LOWER(buf[strlen(buf)-2]) == 'i' && LOWER(buf[strlen(buf)-3]) == 'o') ) { buf[strlen(buf)-1] = 'v'; strcat( buf, "es" ); } else if ( LOWER(buf[strlen(buf)-1]) == 'h' ) strcat( buf, "es" ); else if ( LOWER(buf[strlen(buf)-1]) == 's' ) { if ( LOWER(buf[strlen(buf)-2]) == 'u' && !IS_VOWEL(LOWER(buf[strlen(buf)-3])) ) { buf[strlen(buf)-2] = 'i'; buf[strlen(buf)-1] = '\0'; } else strcat( buf, "es" ); } else strcat( buf, "s" ); strcat( buf, xbuf ); } return buf; } char *trunc_fit( char *argument, int length ) { static char buf[MAX_STRING_LENGTH]; int x; if ( argument == NULL ) return argument; for ( x = 0; (x < length) && (*argument != '\0'); x++ ) { buf[x] = *argument; argument++; }; buf[x] = '\0'; return buf; }; int is_colour( char code ) { switch( code ) { case 'x': return 0; break; case 'r': return 1; break; /* Red */ case 'g': return 2; break; /* Green */ case 'y': return 3; break; /* Yellow */ case 'b': return 4; break; /* Blue */ case 'm': return 5; break; /* Magenta */ case 'c': return 6; break; /* Cyan */ case 'n': return 7; break; /* White */ case 'D': return 8; break; /* Black */ case 'R': return 9; break; /* Bold red */ case 'G': return 10; break; /* Bold green */ case 'Y': return 11; break; /* Bold yellow */ case 'B': return 12; break; /* Bold blue */ case 'M': return 13; break; /* Bold magenta */ case 'C': return 14; break; /* Bold cyan */ case 'W': return 15; break; /* Bold white */ } return 0; } /* * Length of the string as seen minus colour codes. */ int colour_strlen( const char *str ) { int j; j = 0; while( *str != '\0' ) { if( *str != '{' ) { str++; j++; continue; } str++; } return j; } /* * Makes the string the correct length for format with colour. * Note: isprint checks for any printable character, including * a space. */ char *colour_strpad( char *outstr, const char *str, const int length ) { int i, j; j = 0; for( i = 0; str[i] && j < length; i++ ) { outstr[i] = str[i]; if( str[i] == '{' ) { if( !isprint( COLOURLIST[is_colour( str[i + 1] )][0] ) ) j--; } else j++; } outstr[i++] = '{'; outstr[i++] = 'x'; while( j < length ) { outstr[i++] = ' '; j++; } outstr[i] = '\0'; return outstr; } /* * Centres the string on a certain length line. */ char *colour_strcentre( char *outstr, const char *str, const int length ) { char *p = outstr; int pad, i; pad = length - colour_strlen( str ); if( pad <= 0 ) return colour_strpad( outstr, str, length ); for( i = 0; i < pad / 2; ++i ) *p++ = ' '; for( i = 0; str[i] != '\0'; ++i ) *p++ = str[i]; for( i = pad / 2; i < pad; ++i ) *p++ = ' '; *p = '\0'; return outstr; } /* * Limits the length of a colourised string. * Very similar to the above, except destructive to the string. */ void str_limit( char *str, const int length ) { int i, j; j = 0; for( i = 0; str[i] && j < length; i++ ) { if( str[i] == '{' ) { if( !isprint( COLOURLIST[is_colour( str[i + 1] )][0] ) ) j--; } else j++; } str[i++] = '{'; str[i++] = 'x'; str[i] = '\0'; } /* * Remove as much of the colour code as possible. */ char *kill_colour( char *outstr, const char *str ) { int i, j; j = 0; for( i = 0; str[i]; i++ ) { if( str[i] == '{' ) { if( isprint( COLOURLIST[is_colour( str[++i] )][0] ) ) outstr[j++] = COLOURLIST[is_colour( str[i] )][0]; } else outstr[j++] = str[i]; } outstr[j] = '\0'; return outstr; }