Skip to content
Snippets Groups Projects
llstring.cpp 17.09 KiB
/** 
 * @file llstring.cpp
 * @brief String utility functions and the LLString class.
 *
 * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc.
 * $License$
 */

#include "linden_common.h"

#include "llstring.h"
#include "llerror.h"

std::string ll_safe_string(const char* in)
{
	if(in) return std::string(in);
	return std::string();
}

U8 hex_as_nybble(char hex)
{
	if((hex >= '0') && (hex <= '9'))
	{
		return (U8)(hex - '0');
	}
	else if((hex >= 'a') && (hex <='f'))
	{
		return (U8)(10 + hex - 'a');
	}
	else if((hex >= 'A') && (hex <='F'))
	{
		return (U8)(10 + hex - 'A');
	}
	return 0; // uh - oh, not hex any more...
}


// See http://www.unicode.org/Public/BETA/CVTUTF-1-2/ConvertUTF.c
// for the Unicode implementation - this doesn't match because it was written before finding
// it.


std::ostream& operator<<(std::ostream &s, const LLWString &wstr)
{
	std::string utf8_str = wstring_to_utf8str(wstr);
	s << utf8_str;
	return s;
}

std::string rawstr_to_utf8(const std::string& raw)
{
	LLWString wstr(utf8str_to_wstring(raw));
	return wstring_to_utf8str(wstr);
}

S32 wchar_to_utf8chars(llwchar in_char, char* outchars)
{
	U32 cur_char = (U32)in_char;
	char* base = outchars;
	if (cur_char < 0x80)
	{
		*outchars++ = (U8)cur_char;
	}
	else if (cur_char < 0x800)
	{
		*outchars++ = 0xC0 | (cur_char >> 6);
		*outchars++ = 0x80 | (cur_char & 0x3F);
	}
	else if (cur_char < 0x10000)
	{
		*outchars++ = 0xE0 | (cur_char >> 12);
		*outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
		*outchars++ = 0x80 | (cur_char & 0x3F);
	}
	else if (cur_char < 0x200000)
	{
		*outchars++ = 0xF0 | (cur_char >> 18);
		*outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
		*outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
		*outchars++ = 0x80 | cur_char & 0x3F;
	}
	else if (cur_char < 0x4000000)
	{
		*outchars++ = 0xF8 | (cur_char >> 24);
		*outchars++ = 0x80 | ((cur_char >> 18) & 0x3F);
		*outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
		*outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
		*outchars++ = 0x80 | cur_char & 0x3F;
	}
	else if (cur_char < 0x80000000)
	{
		*outchars++ = 0xFC | (cur_char >> 30);
		*outchars++ = 0x80 | ((cur_char >> 24) & 0x3F);
		*outchars++ = 0x80 | ((cur_char >> 18) & 0x3F);
		*outchars++ = 0x80 | ((cur_char >> 12) & 0x3F);
		*outchars++ = 0x80 | ((cur_char >> 6) & 0x3F);
		*outchars++ = 0x80 | cur_char & 0x3F;
	}
	else
	{
		llwarns << "Invalid Unicode character " << cur_char << "!" << llendl;
		*outchars++ = LL_UNKNOWN_CHAR;
	}
	return outchars - base;
}	

S32 utf16chars_to_wchar(const U16* inchars, llwchar* outchar)
{
	const U16* base = inchars;
	U16 cur_char = *inchars++;
	llwchar char32 = cur_char;
	if ((cur_char >= 0xD800) && (cur_char <= 0xDFFF))
	{
		// Surrogates
		char32 = ((llwchar)(cur_char - 0xD800)) << 10;
		cur_char = *inchars++;
		char32 += (llwchar)(cur_char - 0xDC00) + 0x0010000UL;
	}
	else
	{
		char32 = (llwchar)cur_char;
	}
	*outchar = char32;
	return inchars - base;
}

S32 utf16chars_to_utf8chars(const U16* inchars, char* outchars, S32* nchars8p)
{
	// Get 32 bit char32
	llwchar char32;
	S32 nchars16 = utf16chars_to_wchar(inchars, &char32);
	// Convert to utf8
	S32 nchars8  = wchar_to_utf8chars(char32, outchars);
	if (nchars8p)
	{
		*nchars8p = nchars8;
	}
	return nchars16;
}
llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len)
{
	llutf16string out;

	S32 i = 0;
	while (i < len)
	{
		U32 cur_char = utf32str[i];
		if (cur_char > 0xFFFF)
		{
			out += (0xD7C0 + (cur_char >> 10));
			out += (0xDC00 | (cur_char & 0x3FF));
		}
		else
		{
			out += cur_char;
		}
		i++;
	}
	return out;
}

llutf16string wstring_to_utf16str(const LLWString &utf32str)
{
	const S32 len = (S32)utf32str.length();
	return wstring_to_utf16str(utf32str, len);
}

llutf16string utf8str_to_utf16str ( const LLString& utf8str )
{
	LLWString wstr = utf8str_to_wstring ( utf8str );
	return wstring_to_utf16str ( wstr );
}


LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len)
{
	LLWString wout;
	if((len <= 0) || utf16str.empty()) return wout;

	S32 i = 0;
	// craziness to make gcc happy (llutf16string.c_str() is tweaked on linux):
	const U16* chars16 = &(*(utf16str.begin()));
	while (i < len)
	{
		llwchar cur_char;
		i += utf16chars_to_wchar(chars16+i, &cur_char);
		wout += cur_char;
	}
	return wout;
}

LLWString utf16str_to_wstring(const llutf16string &utf16str)
{
	const S32 len = (S32)utf16str.length();
	return utf16str_to_wstring(utf16str, len);
}

S32 wchar_utf8_length(const llwchar wc)
{
	if (wc < 0x80)
	{
		// This case will also catch negative values which are
		// technically invalid.
		return 1;
	}
	else if (wc < 0x800)
	{
		return 2;
	}
	else if (wc < 0x10000)
	{
		return 3;
	}
	else if (wc < 0x200000)
	{
		return 4;
	}
	else if (wc < 0x4000000)
	{
		return 5;
	}
	else
	{
		return 6;
	}
}


S32 wstring_utf8_length(const LLWString& wstr)
{
	S32 len = 0;
	for (S32 i = 0; i < (S32)wstr.length(); i++)
	{
		len += wchar_utf8_length(wstr[i]);
	}
	return len;
}


LLWString utf8str_to_wstring(const std::string& utf8str, S32 len)
{
	LLWString wout;

	S32 i = 0;
	while (i < len)
	{
		llwchar unichar;
		U8 cur_char = utf8str[i];

		if (cur_char < 0x80)
		{
			// Ascii character, just add it
			unichar = cur_char;
		}
		else
		{
			S32 cont_bytes = 0;
			if ((cur_char >> 5) == 0x6)			// Two byte UTF8 -> 1 UTF32
			{
				unichar = (0x1F&cur_char);
				cont_bytes = 1;
			}
			else if ((cur_char >> 4) == 0xe)	// Three byte UTF8 -> 1 UTF32
			{
				unichar = (0x0F&cur_char);
				cont_bytes = 2;
			}
			else if ((cur_char >> 3) == 0x1e)	// Four byte UTF8 -> 1 UTF32
			{
				unichar = (0x07&cur_char);
				cont_bytes = 3;
			}
			else if ((cur_char >> 2) == 0x3e)	// Five byte UTF8 -> 1 UTF32
			{
				unichar = (0x03&cur_char);
				cont_bytes = 4;
			}
			else if ((cur_char >> 1) == 0x7e)	// Six byte UTF8 -> 1 UTF32
			{
				unichar = (0x01&cur_char);
				cont_bytes = 5;
			}
			else
			{
				wout += LL_UNKNOWN_CHAR;
				++i;
				continue;
			}

			// Check that this character doesn't go past the end of the string
			S32 end = (len < (i + cont_bytes)) ? len : (i + cont_bytes);
			do
			{
				++i;

				cur_char = utf8str[i];
				if ( (cur_char >> 6) == 0x2 )
				{
					unichar <<= 6;
					unichar += (0x3F&cur_char);
				}
				else
				{
					// Malformed sequence - roll back to look at this as a new char
					unichar = LL_UNKNOWN_CHAR;
					--i;
					break;
				}
			} while(i < end);

			// Handle overlong characters and NULL characters
			if ( ((cont_bytes == 1) && (unichar < 0x80))
				|| ((cont_bytes == 2) && (unichar < 0x800))
				|| ((cont_bytes == 3) && (unichar < 0x10000))
				|| ((cont_bytes == 4) && (unichar < 0x200000))
				|| ((cont_bytes == 5) && (unichar < 0x4000000)) )
			{
				unichar = LL_UNKNOWN_CHAR;
			}
		}

		wout += unichar;
		++i;
	}
	return wout;
}

LLWString utf8str_to_wstring(const std::string& utf8str)
{
	const S32 len = (S32)utf8str.length();
	return utf8str_to_wstring(utf8str, len);
}

std::string wstring_to_utf8str(const LLWString& utf32str, S32 len)
{
	std::string out;

	S32 i = 0;
	while (i < len)
	{
		char tchars[8];		/* Flawfinder: ignore */
		S32 n = wchar_to_utf8chars(utf32str[i], tchars);
		tchars[n] = 0;
		out += tchars;
		i++;
	}
	return out;
}
std::string wstring_to_utf8str(const LLWString& utf32str)
{
	const S32 len = (S32)utf32str.length();
	return wstring_to_utf8str(utf32str, len);
}

std::string utf16str_to_utf8str(const llutf16string& utf16str)
{
	return wstring_to_utf8str(utf16str_to_wstring(utf16str));
}

std::string utf16str_to_utf8str(const llutf16string& utf16str, S32 len)
{
	return wstring_to_utf8str(utf16str_to_wstring(utf16str, len), len);
}


//LLWString wstring_truncate(const LLWString &wstr, const S32 max_len)
//{
//	return wstr.substr(0, llmin((S32)wstr.length(), max_len));
//}
//
//
//LLWString wstring_trim(const LLWString &wstr)
//{
//	LLWString outstr;
//	outstr = wstring_trimhead(wstr);
//	outstr = wstring_trimtail(outstr);
//	return outstr;
//}
//
//
//LLWString wstring_trimhead(const LLWString &wstr)
//{
//	if(wstr.empty())
//	{
//		return wstr;
//	}
//
//    S32 i = 0;
//	while((i < (S32)wstr.length()) && iswspace(wstr[i]))
//	{
//		i++;
//	}
//	return wstr.substr(i, wstr.length() - i);
//}
//
//
//LLWString wstring_trimtail(const LLWString &wstr)
//{			
//	if(wstr.empty())
//	{
//		return wstr;
//	}
//
//	S32 len = (S32)wstr.length();
//
//	S32 i = len - 1;
//	while (i >= 0 && iswspace(wstr[i]))
//	{
//		i--;
//	}
//
//	if (i >= 0)
//	{
//		return wstr.substr(0, i + 1);
//	}
//	return wstr;
//}
//
//
//LLWString wstring_copyinto(const LLWString &dest, const LLWString &src, const S32 insert_offset)
//{
//	llassert( insert_offset <= (S32)dest.length() );
//
//	LLWString out_str = dest.substr(0, insert_offset);
//	out_str += src;
//	LLWString tail = dest.substr(insert_offset);
//	out_str += tail;
//
//	return out_str;
//}


//LLWString wstring_detabify(const LLWString &wstr, const S32 num_spaces)
//{
//	LLWString out_str;
//	// Replace tabs with spaces
//	for (S32 i = 0; i < (S32)wstr.length(); i++)
//	{
//		if (wstr[i] == '\t')
//		{
//			for (S32 j = 0; j < num_spaces; j++)
//				out_str += ' ';
//		}
//		else
//		{
//			out_str += wstr[i];
//		}
//	}
//	return out_str;
//}


//LLWString wstring_makeASCII(const LLWString &wstr)
//{
//	// Replace non-ASCII chars with replace_char
//	LLWString out_str = wstr;
//	for (S32 i = 0; i < (S32)out_str.length(); i++)
//	{
//		if (out_str[i] > 0x7f)
//		{
//			out_str[i] = LL_UNKNOWN_CHAR;
//		}
//	}
//	return out_str;
//}


//LLWString wstring_substChar(const LLWString &wstr, const llwchar target_char, const llwchar replace_char)
//{
//	// Replace all occurences of target_char with replace_char
//	LLWString out_str = wstr;
//	for (S32 i = 0; i < (S32)out_str.length(); i++)
//	{
//		if (out_str[i] == target_char)
//		{
//			out_str[i] = replace_char;
//		}
//	}
//	return out_str;
//}
//
//
//LLWString wstring_tolower(const LLWString &wstr)
//{
//	LLWString out_str = wstr;
//	for (S32 i = 0; i < (S32)out_str.length(); i++)
//	{
//		out_str[i] = towlower(out_str[i]);
//	}
//	return out_str;
//}
//
//
//LLWString wstring_convert_to_lf(const LLWString &wstr)
//{
//	const llwchar CR = 13;
//	// Remove carriage returns from string with CRLF
//	LLWString out_str;
//
//	for (S32 i = 0; i < (S32)wstr.length(); i++)
//	{
//		if (wstr[i] != CR)
//		{
//			out_str += wstr[i];
//		}
//	}
//	return out_str;
//}
//
//
//LLWString wstring_convert_to_crlf(const LLWString &wstr)
//{
//	const llwchar LF = 10;
//	const llwchar CR = 13;
//	// Remove carriage returns from string with CRLF
//	LLWString out_str;
//
//	for (S32 i = 0; i < (S32)wstr.length(); i++)
//	{
//		if (wstr[i] == LF)
//		{
//			out_str += CR;
//		}
//		out_str += wstr[i];
//	}
//	return out_str;
//}


//S32	wstring_compare_insensitive(const LLWString &lhs, const LLWString &rhs)
//{
//
//	if (lhs == rhs)
//	{
//		return 0;
//	}
//
//	if (lhs.empty())
//	{
//		return rhs.empty() ? 0 : 1;
//	}
//
//	if (rhs.empty())
//	{
//		return -1;
//	}
//
//#ifdef LL_LINUX
//	// doesn't work because gcc 2.95 doesn't correctly implement c_str().  Sigh...
//	llerrs << "wstring_compare_insensitive doesn't work on Linux!" << llendl;
//	return 0;
//#else
//	LLWString lhs_lower = lhs;
//	LLWString::toLower(lhs_lower);
//	std::string lhs_lower = wstring_to_utf8str(lhs_lower);
//	LLWString rhs_lower = lhs;
//	LLWString::toLower(rhs_lower);
//	std::string rhs_lower = wstring_to_utf8str(rhs_lower);
//
//	return strcmp(lhs_lower.c_str(), rhs_lower.c_str());
//#endif
//}


std::string utf8str_trim(const std::string& utf8str)
{
	LLWString wstr = utf8str_to_wstring(utf8str);
	LLWString::trim(wstr);
	return wstring_to_utf8str(wstr);
}


std::string utf8str_tolower(const std::string& utf8str)
{
	LLWString out_str = utf8str_to_wstring(utf8str);
	LLWString::toLower(out_str);
	return wstring_to_utf8str(out_str);
}


S32 utf8str_compare_insensitive(const std::string& lhs, const std::string& rhs)
{
	LLWString wlhs = utf8str_to_wstring(lhs);
	LLWString wrhs = utf8str_to_wstring(rhs);
	return LLWString::compareInsensitive(wlhs.c_str(), wrhs.c_str());
}

std::string utf8str_truncate(const std::string& utf8str, const S32 max_len)
{
	if (0 == max_len)
	{
		return std::string();
	}
	if ((S32)utf8str.length() <= max_len)
	{
		return utf8str;
	}
	else
	{
		S32 cur_char = max_len;

		// If we're ASCII, we don't need to do anything
		if ((U8)utf8str[cur_char] > 0x7f)
		{
			// If first two bits are (10), it's the tail end of a multibyte char.  We need to shift back
			// to the first character
			while (0x80 == (0xc0 & utf8str[cur_char]))
			{
				cur_char--;
				// Keep moving forward until we hit the first char;
				if (cur_char == 0)
				{
					// Make sure we don't trash memory if we've got a bogus string.
					break;
				}
			}
		}
		// The byte index we're on is one we want to get rid of, so we only want to copy up to (cur_char-1) chars
		return utf8str.substr(0, cur_char);
	}
}

std::string utf8str_substChar(
	const std::string& utf8str,
	const llwchar target_char,
	const llwchar replace_char)
{
	LLWString wstr = utf8str_to_wstring(utf8str);
	LLWString::replaceChar(wstr, target_char, replace_char);
	//wstr = wstring_substChar(wstr, target_char, replace_char);
	return wstring_to_utf8str(wstr);
}

std::string utf8str_makeASCII(const std::string& utf8str)
{
	LLWString wstr = utf8str_to_wstring(utf8str);
	LLWString::_makeASCII(wstr);
	return wstring_to_utf8str(wstr);
}

std::string mbcsstring_makeASCII(const std::string& wstr)
{
	// Replace non-ASCII chars with replace_char
	std::string out_str = wstr;
	for (S32 i = 0; i < (S32)out_str.length(); i++)
	{
		if ((U8)out_str[i] > 0x7f)
		{
			out_str[i] = LL_UNKNOWN_CHAR;
		}
	}
	return out_str;
}

S32	LLStringOps::collate(const llwchar* a, const llwchar* b)
{ 
	#if LL_WINDOWS
		// in Windows, wide string functions operator on 16-bit strings, 
		// not the proper 32 bit wide string
		return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str());
	#else
		return wcscoll(a, b);
	#endif
}

namespace LLStringFn
{
	void replace_nonprintable(std::basic_string<char>& string, char replacement)
	{
		const char MIN = 0x20;
		std::basic_string<char>::size_type len = string.size();
		for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii)
		{
			if(string[ii] < MIN)
			{
				string[ii] = replacement;
			}
		}
	}

	void replace_nonprintable(
		std::basic_string<llwchar>& string,
		llwchar replacement)
	{
		const llwchar MIN = 0x20;
		const llwchar MAX = 0x7f;
		std::basic_string<llwchar>::size_type len = string.size();
		for(std::basic_string<llwchar>::size_type ii = 0; ii < len; ++ii)
		{
			if((string[ii] < MIN) || (string[ii] > MAX))
			{
				string[ii] = replacement;
			}
		}
	}

	void replace_nonprintable_and_pipe(std::basic_string<char>& str,
									   char replacement)
	{
		const char MIN  = 0x20;
		const char PIPE = 0x7c;
		std::basic_string<char>::size_type len = str.size();
		for(std::basic_string<char>::size_type ii = 0; ii < len; ++ii)
		{
			if( (str[ii] < MIN) || (str[ii] == PIPE) )
			{
				str[ii] = replacement;
			}
		}
	}

	void replace_nonprintable_and_pipe(std::basic_string<llwchar>& str,
									   llwchar replacement)
	{
		const llwchar MIN  = 0x20;
		const llwchar MAX  = 0x7f;
		const llwchar PIPE = 0x7c;
		std::basic_string<llwchar>::size_type len = str.size();
		for(std::basic_string<llwchar>::size_type ii = 0; ii < len; ++ii)
		{
			if( (str[ii] < MIN) || (str[ii] > MAX) || (str[ii] == PIPE) )
			{
				str[ii] = replacement;
			}
		}
	}
}


////////////////////////////////////////////////////////////
// Testing

#ifdef _DEBUG

template<class T> 
void LLStringBase<T>::testHarness()
{
	LLString s1;
	
	llassert( s1.c_str() == NULL );
	llassert( s1.size() == 0 );
	llassert( s1.empty() );
	
	LLString s2( "hello");
	llassert( !strcmp( s2.c_str(), "hello" ) );
	llassert( s2.size() == 5 ); 
	llassert( !s2.empty() );
	LLString s3( s2 );

	llassert( "hello" == s2 );
	llassert( s2 == "hello" );
	llassert( s2 > "gello" );
	llassert( "gello" < s2 );
	llassert( "gello" != s2 );
	llassert( s2 != "gello" );

	LLString s4 = s2;
	llassert( !s4.empty() );
	s4.empty();
	llassert( s4.empty() );
	
	LLString s5("");
	llassert( s5.empty() );
	
	llassert( isValidIndex(s5, 0) );
	llassert( !isValidIndex(s5, 1) );
	
	s3 = s2;
	s4 = "hello again";
	
	s4 += "!";
	s4 += s4;
	llassert( s4 == "hello again!hello again!" );
	
	
	LLString s6 = s2 + " " + s2;
	LLString s7 = s6;
	llassert( s6 == s7 );
	llassert( !( s6 != s7) );
	llassert( !(s6 < s7) );
	llassert( !(s6 > s7) );
	
	llassert( !(s6 == "hi"));
	llassert( s6 == "hello hello");
	llassert( s6 < "hi");
	
	llassert( s6[1] == 'e' );
	s6[1] = 'f';
	llassert( s6[1] == 'f' );
	
	s2.erase( 4, 1 );
	llassert( s2 == "hell");
	s2.insert( 0, 'y' );
	llassert( s2 == "yhell");
	s2.erase( 1, 3 );
	llassert( s2 == "yl");
	s2.insert( 1, "awn, don't yel");
	llassert( s2 == "yawn, don't yell");
	
	LLString s8 = s2.substr( 6, 5 );
	llassert( s8 == "don't"  );
	
	LLString s9 = "   \t\ntest  \t\t\n  ";
	trim(s9);
	llassert( s9 == "test"  );

	s8 = "abc123&*(ABC";

	s9 = s8;
	toUpper(s9);
	llassert( s9 == "ABC123&*(ABC"  );

	s9 = s8;
	toLower(s9);
	llassert( s9 == "abc123&*(abc"  );


	LLString s10( 10, 'x' );
	llassert( s10 == "xxxxxxxxxx" );

	LLString s11( "monkey in the middle", 7, 2 );
	llassert( s11 == "in" );

	LLString s12;  //empty
	s12 += "foo";
	llassert( s12 == "foo" );

	LLString s13;  //empty
	s13 += 'f';
	llassert( s13 == "f" );
}


#endif  // _DEBUG