core/logic/stringutil.cpp
author David Anderson <dvander@alliedmods.net>
Fri May 14 20:04:30 2010 -0700 (2010-05-14)
changeset 3005 2db965f8db3f
parent 3004 e3ac0dc6d9a4
child 3007 6076827663ba
permissions -rw-r--r--
Move replace functions from logic to core (bug 4406 part 7, r=fyren).
dvander@3002
     1
/**
dvander@3002
     2
 * vim: set ts=4 sw=4 tw=99 et :
dvander@3002
     3
 * =============================================================================
dvander@3002
     4
 * SourceMod
dvander@3002
     5
 * Copyright (C) 2004-2009 AlliedModders LLC.  All rights reserved.
dvander@3002
     6
 * =============================================================================
dvander@3002
     7
 *
dvander@3002
     8
 * This program is free software; you can redistribute it and/or modify it under
dvander@3002
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
dvander@3002
    10
 * Free Software Foundation.
dvander@3002
    11
 * 
dvander@3002
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
dvander@3002
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@3002
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
dvander@3002
    15
 * details.
dvander@3002
    16
 *
dvander@3002
    17
 * You should have received a copy of the GNU General Public License along with
dvander@3002
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
dvander@3002
    19
 *
dvander@3002
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
dvander@3002
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
dvander@3002
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
dvander@3002
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
dvander@3002
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
dvander@3002
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
dvander@3002
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
dvander@3002
    27
 * or <http://www.sourcemod.net/license.php>.
dvander@3002
    28
 *
dvander@3002
    29
 * Version: $Id$
dvander@3002
    30
 */
dvander@3002
    31
dvander@3002
    32
#include <string.h>
dvander@3002
    33
#include <ctype.h>
dvander@3002
    34
#include "stringutil.h"
dvander@3002
    35
dvander@3002
    36
const char *stristr(const char *str, const char *substr)
dvander@3002
    37
{
dvander@3002
    38
	if (!*substr)
dvander@3002
    39
	{
dvander@3002
    40
		return ((char *)str);
dvander@3002
    41
	}
dvander@3002
    42
dvander@3002
    43
	char *needle = (char *)substr;
dvander@3002
    44
	char *prevloc = (char *)str;
dvander@3002
    45
	char *haystack = (char *)str;
dvander@3002
    46
dvander@3002
    47
	while (*haystack)
dvander@3002
    48
	{
dvander@3002
    49
		if (tolower(*haystack) == tolower(*needle))
dvander@3002
    50
		{
dvander@3002
    51
			haystack++;
dvander@3002
    52
			if (!*++needle)
dvander@3002
    53
			{
dvander@3002
    54
				return prevloc;
dvander@3002
    55
			}
dvander@3002
    56
		}
dvander@3002
    57
		else
dvander@3002
    58
		{
dvander@3002
    59
			haystack = ++prevloc;
dvander@3002
    60
			needle = (char *)substr;
dvander@3002
    61
		}
dvander@3002
    62
	}
dvander@3002
    63
dvander@3002
    64
	return NULL;
dvander@3002
    65
}
dvander@3002
    66
dvander@3004
    67
unsigned int strncopy(char *dest, const char *src, size_t count)
dvander@3004
    68
{
dvander@3004
    69
	if (!count)
dvander@3004
    70
	{
dvander@3004
    71
		return 0;
dvander@3004
    72
	}
dvander@3002
    73
dvander@3004
    74
	char *start = dest;
dvander@3004
    75
	while ((*src) && (--count))
dvander@3004
    76
	{
dvander@3004
    77
		*dest++ = *src++;
dvander@3004
    78
	}
dvander@3004
    79
	*dest = '\0';
dvander@3004
    80
dvander@3004
    81
	return (dest - start);
dvander@3004
    82
}
dvander@3004
    83
dvander@3005
    84
unsigned int UTIL_ReplaceAll(char *subject, size_t maxlength, const char *search, const char *replace, bool caseSensitive)
dvander@3005
    85
{
dvander@3005
    86
	size_t searchLen = strlen(search);
dvander@3005
    87
	size_t replaceLen = strlen(replace);
dvander@3004
    88
dvander@3005
    89
	char *ptr = subject;
dvander@3005
    90
	unsigned int total = 0;
dvander@3005
    91
	while ((ptr = UTIL_ReplaceEx(ptr, maxlength, search, searchLen, replace, replaceLen, caseSensitive)) != NULL)
dvander@3005
    92
	{
dvander@3005
    93
		total++;
dvander@3005
    94
		if (*ptr == '\0')
dvander@3005
    95
		{
dvander@3005
    96
			break;
dvander@3005
    97
		}
dvander@3005
    98
	}
dvander@3005
    99
dvander@3005
   100
	return total;
dvander@3005
   101
}
dvander@3005
   102
dvander@3005
   103
/**
dvander@3005
   104
 * NOTE: Do not edit this for the love of god unless you have 
dvander@3005
   105
 * read the test cases and understand the code behind each one.
dvander@3005
   106
 * While I don't guarantee there aren't mistakes, I do guarantee
dvander@3005
   107
 * that plugins will end up relying on tiny idiosyncrasies of this
dvander@3005
   108
 * function, just like they did with AMX Mod X.
dvander@3005
   109
 *
dvander@3005
   110
 * There are explicitly more cases than the AMX Mod X version because
dvander@3005
   111
 * we're not doing a blind copy.  Each case is specifically optimized
dvander@3005
   112
 * for what needs to be done.  Even better, we don't have to error on
dvander@3005
   113
 * bad buffer sizes.  Instead, this function will smartly cut off the 
dvander@3005
   114
 * string in a way that pushes old data out.
dvander@3005
   115
 */
dvander@3005
   116
char *UTIL_ReplaceEx(char *subject, size_t maxLen, const char *search, size_t searchLen, const char *replace, size_t replaceLen, bool caseSensitive)
dvander@3005
   117
{
dvander@3005
   118
	char *ptr = subject;
dvander@3005
   119
	size_t browsed = 0;
dvander@3005
   120
	size_t textLen = strlen(subject);
dvander@3005
   121
dvander@3005
   122
	/* It's not possible to search or replace */
dvander@3005
   123
	if (searchLen > textLen)
dvander@3005
   124
	{
dvander@3005
   125
		return NULL;
dvander@3005
   126
	}
dvander@3005
   127
dvander@3005
   128
	/* Handle the case of one byte replacement.
dvander@3005
   129
	 * It's only valid in one case.
dvander@3005
   130
	 */
dvander@3005
   131
	if (maxLen == 1)
dvander@3005
   132
	{
dvander@3005
   133
		/* If the search matches and the replace length is 0, 
dvander@3005
   134
		 * we can just terminate the string and be done.
dvander@3005
   135
		 */
dvander@3005
   136
		if ((caseSensitive ? strcmp(subject, search) : strcasecmp(subject, search)) == 0 && replaceLen == 0)
dvander@3005
   137
		{
dvander@3005
   138
			*subject = '\0';
dvander@3005
   139
			return subject;
dvander@3005
   140
		}
dvander@3005
   141
		else
dvander@3005
   142
		{
dvander@3005
   143
			return NULL;
dvander@3005
   144
		}
dvander@3005
   145
	}
dvander@3005
   146
dvander@3005
   147
	/* Subtract one off the maxlength so we can include the null terminator */
dvander@3005
   148
	maxLen--;
dvander@3005
   149
dvander@3005
   150
	while (*ptr != '\0' && (browsed <= textLen - searchLen))
dvander@3005
   151
	{
dvander@3005
   152
		/* See if we get a comparison */
dvander@3005
   153
		if ((caseSensitive ? strncmp(ptr, search, searchLen) : strncasecmp(ptr, search, searchLen)) == 0)
dvander@3005
   154
		{
dvander@3005
   155
			if (replaceLen > searchLen)
dvander@3005
   156
			{
dvander@3005
   157
				/* First, see if we have enough space to do this operation */
dvander@3005
   158
				if (maxLen - textLen < replaceLen - searchLen)
dvander@3005
   159
				{
dvander@3005
   160
					/* First, see if the replacement length goes out of bounds. */
dvander@3005
   161
					if (browsed + replaceLen >= maxLen)
dvander@3005
   162
					{
dvander@3005
   163
						/* EXAMPLE CASE:
dvander@3005
   164
						 * Subject: AABBBCCC
dvander@3005
   165
						 * Buffer : 12 bytes
dvander@3005
   166
						 * Search : BBB
dvander@3005
   167
						 * Replace: DDDDDDDDDD
dvander@3005
   168
						 * OUTPUT : AADDDDDDDDD
dvander@3005
   169
						 * POSITION:           ^
dvander@3005
   170
						 */
dvander@3005
   171
						/* If it does, we'll just bound the length and do a strcpy. */
dvander@3005
   172
						replaceLen = maxLen - browsed;
dvander@3005
   173
						/* Note, we add one to the final result for the null terminator */
dvander@3005
   174
						strncopy(ptr, replace, replaceLen+1);
dvander@3005
   175
					}
dvander@3005
   176
					else
dvander@3005
   177
					{
dvander@3005
   178
						/* EXAMPLE CASE:
dvander@3005
   179
						 * Subject: AABBBCCC
dvander@3005
   180
						 * Buffer : 12 bytes
dvander@3005
   181
						 * Search : BBB
dvander@3005
   182
						 * Replace: DDDDDDD
dvander@3005
   183
						 * OUTPUT : AADDDDDDDCC
dvander@3005
   184
						 * POSITION:         ^
dvander@3005
   185
						 */
dvander@3005
   186
						/* We're going to have some bytes left over... */
dvander@3005
   187
						size_t origBytesToCopy = (textLen - (browsed + searchLen)) + 1;
dvander@3005
   188
						size_t realBytesToCopy = (maxLen - (browsed + replaceLen)) + 1;
dvander@3005
   189
						char *moveFrom = ptr + searchLen + (origBytesToCopy - realBytesToCopy);
dvander@3005
   190
						char *moveTo = ptr + replaceLen;
dvander@3005
   191
dvander@3005
   192
						/* First, move our old data out of the way. */
dvander@3005
   193
						memmove(moveTo, moveFrom, realBytesToCopy);
dvander@3005
   194
dvander@3005
   195
						/* Now, do our replacement. */
dvander@3005
   196
						memcpy(ptr, replace, replaceLen);
dvander@3005
   197
					}
dvander@3005
   198
				}
dvander@3005
   199
				else
dvander@3005
   200
				{
dvander@3005
   201
					/* EXAMPLE CASE:
dvander@3005
   202
					 * Subject: AABBBCCC
dvander@3005
   203
					 * Buffer : 12 bytes
dvander@3005
   204
					 * Search : BBB
dvander@3005
   205
					 * Replace: DDDD
dvander@3005
   206
					 * OUTPUT : AADDDDCCC
dvander@3005
   207
					 * POSITION:      ^
dvander@3005
   208
					 */
dvander@3005
   209
					/* Yes, we have enough space.  Do a normal move operation. */
dvander@3005
   210
					char *moveFrom = ptr + searchLen;
dvander@3005
   211
					char *moveTo = ptr + replaceLen;
dvander@3005
   212
dvander@3005
   213
					/* First move our old data out of the way. */
dvander@3005
   214
					size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
dvander@3005
   215
					memmove(moveTo, moveFrom, bytesToCopy);
dvander@3005
   216
dvander@3005
   217
					/* Now do our replacement. */
dvander@3005
   218
					memcpy(ptr, replace, replaceLen);
dvander@3005
   219
				}
dvander@3005
   220
			}
dvander@3005
   221
			else if (replaceLen < searchLen)
dvander@3005
   222
			{
dvander@3005
   223
				/* EXAMPLE CASE:
dvander@3005
   224
				 * Subject: AABBBCCC
dvander@3005
   225
				 * Buffer : 12 bytes
dvander@3005
   226
				 * Search : BBB
dvander@3005
   227
				 * Replace: D
dvander@3005
   228
				 * OUTPUT : AADCCC
dvander@3005
   229
				 * POSITION:   ^
dvander@3005
   230
				 */
dvander@3005
   231
				/* If the replacement does not grow the string length, we do not
dvander@3005
   232
				 * need to do any fancy checking at all.  Yay!
dvander@3005
   233
				 */
dvander@3005
   234
				char *moveFrom = ptr + searchLen;		/* Start after the search pointer */
dvander@3005
   235
				char *moveTo = ptr + replaceLen;		/* Copy to where the replacement ends */
dvander@3005
   236
dvander@3005
   237
				/* Copy our replacement in, if any */
dvander@3005
   238
				if (replaceLen)
dvander@3005
   239
				{
dvander@3005
   240
					memcpy(ptr, replace, replaceLen);
dvander@3005
   241
				}
dvander@3005
   242
dvander@3005
   243
				/* Figure out how many bytes to move down, including null terminator */
dvander@3005
   244
				size_t bytesToCopy = (textLen - (browsed + searchLen)) + 1;
dvander@3005
   245
dvander@3005
   246
				/* Move the rest of the string down */
dvander@3005
   247
				memmove(moveTo, moveFrom, bytesToCopy);
dvander@3005
   248
			}
dvander@3005
   249
			else
dvander@3005
   250
			{
dvander@3005
   251
				/* EXAMPLE CASE:
dvander@3005
   252
				 * Subject: AABBBCCC
dvander@3005
   253
				 * Buffer : 12 bytes
dvander@3005
   254
				 * Search : BBB
dvander@3005
   255
				 * Replace: DDD
dvander@3005
   256
				 * OUTPUT : AADDDCCC
dvander@3005
   257
				 * POSITION:     ^
dvander@3005
   258
				 */
dvander@3005
   259
				/* We don't have to move anything around, just do a straight copy */
dvander@3005
   260
				memcpy(ptr, replace, replaceLen);
dvander@3005
   261
			}
dvander@3005
   262
dvander@3005
   263
			return ptr + replaceLen;
dvander@3005
   264
		}
dvander@3005
   265
		ptr++;
dvander@3005
   266
		browsed++;
dvander@3005
   267
	}
dvander@3005
   268
dvander@3005
   269
	return NULL;
dvander@3005
   270
}
dvander@3005
   271