sources/mystring.cpp

Wed, 27 Jan 2021 13:08:51 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Wed, 27 Jan 2021 13:08:51 +0200
changeset 180
2e7225dbd9b2
parent 179
7fc34735178e
child 181
e254398fcc7c
permissions
-rw-r--r--

continue cleanup

/*
	Copyright 2014 - 2016 Teemu Piippo
	All rights reserved.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions
	are met:

	1. Redistributions of source code must retain the above copyright
	   notice, this list of conditions and the following disclaimer.
	2. Redistributions in binary form must reproduce the above copyright
	   notice, this list of conditions and the following disclaimer in the
	   documentation and/or other materials provided with the distribution.
	3. Neither the name of the copyright holder nor the names of its
	   contributors may be used to endorse or promote products derived from
	   this software without specific prior written permission.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <cstring>
#include "main.h"
#include "mystring.h"
#include "md5.h"

BEGIN_ZFC_NAMESPACE

/*!
 * \brief Compares this string with another.
 * \param other The string to compare with.
 * \returns -1 if this string is lexicographically less than \c other,
 *          0 if they are equal, or
 *          1 if this string is lexicographically greater than \c other.
 */
int String::compare (const String& other) const
{
	return m_string.compare (other.stdString());
}

/*!
 * \returns an upper-case version of this string.
 */
String String::toUpperCase() const
{
	String result (m_string);

	for (char &ch : result)
	{
		if (islower(ch))
			ch -= 'a' - 'A';
	}

	return result;
}

/*!
 * \returns a lower-case version of this string.
 */
String String::toLowerCase() const
{
	String result (m_string);

	for (char &ch : result)
	{
		if (isupper(ch))
			ch += 'a' - 'A';
	}

	return result;
}

/*!
 * \brief Splits this string using the provided delimeter.
 * \param delimeter Delimeter to use for splitting.
 * \returns a string list containing the split strings.
 */
StringList String::split (char delimeter) const
{
	String delimeterString;
	delimeterString += delimeter;
	return split (delimeterString);
}

/*!
 * \brief Splits this string using the provided delimeter.
 * \param delimeter Delimeter to use for splitting.
 * \returns a string list containing the split strings.
 */
StringList String::split (const String& delimeter) const
{
	StringList result;
	int a = 0;
	int b;

	// Find all separators and store the text left to them.
	while ((b = find (delimeter, a)) != -1)
	{
		String sub = mid (a, b);

		if (sub.length() > 0)
			result.push_back(sub);

		a = b + delimeter.length();
	}

	// Add the string at the right of the last separator
	if (a < (int) length())
		result.push_back(mid(a, length()));

	return result;
}

/*!
 * \brief Replaces all instances of \c text with \c replacement.
 * \param text Text to replace away.
 * \param replacement Text to replace \c text with.
 */
void String::replace (const char* text, const char* replacement)
{
	int position;

	while ((position = find (text)) != -1)
		m_string = m_string.replace (position, strlen (text), replacement);
}

/*!
 * \param character Character to count.
 * \returns the amount of \c character found in the string.
 */
int String::count (char character) const
{
	int result = 0;

	for (char ch : *this)
	{
		if (ch == character)
			result++;
	}

	return result;
}

/*!
 * \param a Starting index of the range.
 * \param b Ending index of the range.
 * \returns a sub-string containing all characters from \c a to \c b, not including the character at \c b.
 */
String String::mid (int rangeBegin, int rangeEnd) const
{
	modifyIndex(rangeBegin);
	modifyIndex(rangeEnd);
	rangeBegin = max(rangeBegin, 0);
	rangeEnd = min(rangeEnd, length());

	if (rangeEnd <= rangeBegin)
		return "";
	else
		return m_string.substr(rangeBegin, rangeEnd - rangeBegin);
}

/*!
 * \param length Amount of characters to return.
 * \returns the \c length right-most characters of the string.
 */
String String::right(int length) const
{
	if (length >= this->length())
		return *this;
	else
		return String(chars() + this->length() - length);
}

/*!
 * \brief Finds the first instance of a sub-string.
 * \param subString Sub-string to search within this string.
 * \param startingPosition Position to start looking for the sub-string from.
 * \returns the position the first instance of sub-string found, or -1 if not found.
 */
int String::find (const char* subString, int startingPosition) const
{
	int position = m_string.find (subString, startingPosition);

	if (position == int (std::string::npos))
		return -1;
	else
		return position;
}

/*!
 * \brief Finds the first instance of a character.
 * \param character Character to search within this string.
 * \param startingPosition Position to start looking for the character from.
 * \returns the position of the first instance of the provided character found, or -1 if not found.
 */
int String::find (char character, int startingPosition) const
{
	int position = m_string.find (character, startingPosition);

	if (position == int (std::string::npos))
		return -1;
	else
		return position;
}

/*!
 * \brief Finds the last instance of a sub-string.
 * \param subString Sub-string to search within this string.
 * \param startingPosition Position to start looking for the sub-string from.
 * \returns the position the last instance of sub-string found, or -1 if not found.
 */
int String::findLast (const char* subString, int startingPosition) const
{
	modifyIndex(startingPosition);

	for (; startingPosition > 0; startingPosition--)
	{
		if (strncmp (chars() + startingPosition, subString, strlen (subString)) == 0)
			return startingPosition;
	}

	return -1;
}

/*!
 * \brief Converts this string to an integer.
 * \param ok An pointer to a boolean to store whether or not the conversion was successful.
 *           If \c ok is \c NULL, the success state is not stored.
 * \param base The base to interpret this string with.
 * \returns the resulting integer.
 */
long String::toInt (bool* ok, int base) const
{
	errno = 0;
	char* endPointer;
	long result = strtol (chars(), &endPointer, base);

	if (ok != nullptr)
		*ok = (errno == 0 and *endPointer == '\0');

	return result;
}

/*!
 * \brief Converts this string to a floating-point number.
 * \param ok An pointer to a boolean to store whether or not the conversion was successful.
 *           If \c ok is \c NULL, the success state is not stored.
 * \returns the resulting floating-point number.
 */
float String::toFloat (bool* ok) const
{
	return static_cast<float>(toDouble(ok));
}

/*!
 * \brief Converts this string to a double-precision floating-point number.
 * \param ok An pointer to a boolean to store whether or not the conversion was successful.
 *           If \c ok is \c NULL, the success state is not stored.
 * \returns the resulting floating-point number.
 */
double String::toDouble (bool* ok) const
{
	errno = 0;
	char* endptr;
	double i = strtod (chars(), &endptr);

	if (ok != nullptr)
		*ok = (errno == 0 and *endptr == '\0');

	return i;
}

/*!
 * \brief Catenates this string with another string.
 * \param text String to catenate to the end of this string.
 * \returns the resulting string.
 */
String String::operator+ (const String& text) const
{
	String newString = *this;
	newString.append (text);
	return newString;
}

/*!
 * \brief Catenates this string with another string.
 * \param text String to catenate to the end of this string.
 * \returns the resulting string.
 */
String String::operator+ (const char* text) const
{
	String newString = *this;
	newString.append (text);
	return newString;
}

/*!
 * \returns whether or not this string represents a number.
 */
bool String::isNumeric() const
{
	char* endPointer;
	strtol (chars(), &endPointer, 10);
	return (endPointer != nullptr) and (*endPointer != '\0');
}

/*!
 * \param other Sub-string to find from the end of this string.
 * \return whether or not this string ends with the provided sub-string.
 */
bool String::endsWith (const String& other) const
{
	if (length() < other.length())
	{
		return false;
	}
	else
	{
		const int offset = length() - other.length();
		return strncmp (chars() + offset, other.chars(), other.length()) == 0;
	}
}

/*!
 * \param other Sub-string to find from the beginning of this string.
 * \returns whether or not this string begins with the provided sub-string.
 */
bool String::startsWith (const String& other) const
{
	if (length() < other.length())
		return false;
	else
		return strncmp (chars(), other.chars(), other.length()) == 0;
}

/*!
 * \brief Formats this string using \c printf -like syntax.
 * \param formatString Template string to use with formatting.
 * \param ... Variadic arguments to use with formatting.
 */
void __cdecl String::sprintf (const char* formatString, ...)
{
	va_list args;
	va_start (args, formatString);
	this->vsprintf (formatString, args);
	va_end (args);
}

/*!
 * \brief Formats this string using \c vsnprintf, using the provided arguments.
 * \param formatString Template string to use with formatting.
 * \param args Variadic arguments to use with formatting.
 */
void String::vsprintf (const char* formatString, va_list args)
{
	// Copy the argument list so that we have something to provide to vsnprintf in case we have to call it again.
	va_list argsCopy;
	va_copy(argsCopy, args);

	// First, attempt to format using a fixed-size buffer.
	static char buffer[1024];
	size_t length = vsnprintf(buffer, sizeof buffer, formatString, args);

	if (length < sizeof buffer)
	{
		// vsnprintf succeeded in fitting the formatted string into the buffer, so we're done.
		m_string = buffer;
	}
	else
	{
		// vsnprintf needs more space, so we have to allocate a new buffer and try again.
		Vector<char> newBuffer(length + 1);
		vsnprintf(newBuffer.data(), length + 1, formatString, argsCopy);
		m_string = newBuffer;
	}
}

/*!
 * \brief Joins the elements of this string list into one longer string.
 * \param delimeter The delimeter to place between the element strings.
 * \returns the catenated string.
 */
String join_string_list(const StringList& strings, const String& delimeter)
{
	String result;

	for (const String &item : strings)
	{
		if (result.isEmpty() == false)
			result += delimeter;

		result += item;
	}

	return result;
}

/*!
 * \brief Tries to match this string against a mask pattern. In the pattern, '?' refers to one character, and '*' to
 *        any number of characters.
 * \param pattern The masking pattern to use for matching.
 * \returns whether or not this string matches the provided pattern.
 */
bool String::maskAgainst (const String& pattern) const
{
	// Elevate to uppercase for case-insensitive matching
	String pattern_upper = pattern.toUpperCase();
	String this_upper = toUpperCase();
	const char* maskstring = pattern_upper.chars();
	const char* mptr = &maskstring[0];

	for (const char* sptr = this_upper.chars(); *sptr != '\0'; sptr++)
	{
		if (*mptr == '?')
		{
			if (*(sptr + 1) == '\0')
			{
				// ? demands that there's a character here and there wasn't.
				// Therefore, mask matching fails
				return false;
			}
		}
		else if (*mptr == '*')
		{
			char end = *(++mptr);

			// If '*' is the final character of the message, all of the remaining
			// string matches against the '*'. We don't need to bother checking
			// the string any further.
			if (end == '\0')
				return true;

			// Skip to the end character
			while (*sptr != end and *sptr != '\0')
				sptr++;

			// String ended while the mask still had stuff
			if (*sptr == '\0')
				return false;
		}
		else if (*sptr != *mptr)
			return false;

		mptr++;
	}

	return true;
}

/*!
 * \brief Converts a short integer into a string.
 * \param value The value to convert.
 * \returns the resulting string.
 */
String String::fromNumber (short int value)
{
	char buffer[32];
	::sprintf (buffer, "%d", value);
	return String (buffer);
}

/*!
 * \brief Converts an integer into a string.
 * \param value The value to convert.
 * \returns the resulting string.
 */
String String::fromNumber (int value)
{
	char buffer[32];
	::sprintf (buffer, "%d", value);
	return String (buffer);
}

/*!
 * \brief Converts a long integer into a string.
 * \param value The value to convert.
 * \returns the resulting string.
 */
String String::fromNumber (long int value)
{
	char buffer[32];
	::sprintf (buffer, "%ld", value);
	return String (buffer);
}

/*!
 * \brief Converts an unsigned short integer into a string.
 * \param value The value to convert.
 * \returns the resulting string.
 */
String String::fromNumber (unsigned short int value)
{
	char buffer[32];
	::sprintf (buffer, "%u", value);
	return String (buffer);
}

/*!
 * \brief Converts an unsigned integer into a string.
 * \param value The value to convert.
 * \returns the resulting string.
 */
String String::fromNumber (unsigned int value)
{
	char buffer[32];
	::sprintf (buffer, "%u", value);
	return String (buffer);
}

/*!
 * \brief Converts an unsigned long integer into a string.
 * \param value The value to convert.
 * \returns the resulting string.
 */
String String::fromNumber (unsigned long int value)
{
	char buffer[32];
	::sprintf (buffer, "%lu", value);
	return String (buffer);
}

/*!
 * \brief Converts a double-precision floating point number into a string, using the "%f" format specifier.
 * \param value The value to convert.
 * \returns the resulting string.
 */
String String::fromNumber (double value)
{
	char buffer[64];
	::sprintf (buffer, "%f", value);
	return String (buffer);
}

/*!
 * \brief Constructs a string from a vector of bytes. The bytes do not have to be null-terminated.
 * \param bytes Bytes to use for construction
 * \returns the resulting string.
 */
String String::fromBytes(const ByteArray& bytes)
{
	return String(reinterpret_cast<const Vector<char>&>(bytes));
}

/*!
 * \returns the MD5-checksum of this string.
 */
String String::md5() const
{
	char checksum[33];
	CalculateMD5 (reinterpret_cast<const unsigned char*> (chars()), length(), checksum);
	checksum[sizeof checksum - 1] = '\0';
	return String (checksum);
}

/*!
 * \brief Removes leading and trailing whitespace from this string. Alternatively a custom filter can be used to strip
 *        something else than whitespace.
 * \param filter The filtering function to use.
 */
void String::normalize (int (*filter)(int))
{
	int a = 0;
	int b = length() - 1;

	while ((*filter) (m_string[a]) and a != b)
		++a;

	while ((*filter) (m_string[b]) and a != b)
		--b;

	if (a == b)
		m_string = "";
	else if (a != 0 or b != length() - 1)
		m_string = m_string.substr (a, b - a + 1);
}

/*!
 * \returns a version of this string without leading or trailing whitespace. Alternatively a custom filter can be used
 *          to strip something else than whitespace.
 */
String String::normalized (int (*filter)(int)) const
{
	String result = *this;
	result.normalize(filter);
	return result;
}

END_ZFC_NAMESPACE

mercurial