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