669 lines
14 KiB
C++
669 lines
14 KiB
C++
|
// File: crn_dynamic_string.cpp
|
||
|
// See Copyright Notice and license at the end of inc/crnlib.h
|
||
|
#include "crn_core.h"
|
||
|
#include "crn_strutils.h"
|
||
|
|
||
|
namespace crnlib
|
||
|
{
|
||
|
dynamic_string g_empty_dynamic_string;
|
||
|
|
||
|
dynamic_string::dynamic_string(eVarArg dummy, const char* p, ...) :
|
||
|
m_buf_size(0), m_len(0), m_pStr(NULL)
|
||
|
{
|
||
|
dummy;
|
||
|
|
||
|
CRNLIB_ASSERT(p);
|
||
|
|
||
|
va_list args;
|
||
|
va_start(args, p);
|
||
|
format_args(p, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
dynamic_string::dynamic_string(const char* p) :
|
||
|
m_buf_size(0), m_len(0), m_pStr(NULL)
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
set(p);
|
||
|
}
|
||
|
|
||
|
dynamic_string::dynamic_string(const char* p, uint len) :
|
||
|
m_buf_size(0), m_len(0), m_pStr(NULL)
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
set_from_buf(p, len);
|
||
|
}
|
||
|
|
||
|
dynamic_string::dynamic_string(const dynamic_string& other) :
|
||
|
m_buf_size(0), m_len(0), m_pStr(NULL)
|
||
|
{
|
||
|
set(other);
|
||
|
}
|
||
|
|
||
|
void dynamic_string::clear()
|
||
|
{
|
||
|
check();
|
||
|
|
||
|
if (m_pStr)
|
||
|
{
|
||
|
crnlib_delete_array(m_pStr);
|
||
|
m_pStr = NULL;
|
||
|
|
||
|
m_len = 0;
|
||
|
m_buf_size = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void dynamic_string::empty()
|
||
|
{
|
||
|
truncate(0);
|
||
|
}
|
||
|
|
||
|
void dynamic_string::optimize()
|
||
|
{
|
||
|
if (!m_len)
|
||
|
clear();
|
||
|
else
|
||
|
{
|
||
|
uint min_buf_size = math::next_pow2((uint)m_len + 1);
|
||
|
if (m_buf_size > min_buf_size)
|
||
|
{
|
||
|
char* p = crnlib_new_array<char>(min_buf_size);
|
||
|
memcpy(p, m_pStr, m_len + 1);
|
||
|
|
||
|
crnlib_delete_array(m_pStr);
|
||
|
m_pStr = p;
|
||
|
|
||
|
m_buf_size = static_cast<uint16>(min_buf_size);
|
||
|
|
||
|
check();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int dynamic_string::compare(const char* p, bool case_sensitive) const
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
|
||
|
const int result = (case_sensitive ? strcmp : crn_stricmp)(get_ptr_priv(), p);
|
||
|
|
||
|
if (result < 0)
|
||
|
return -1;
|
||
|
else if (result > 0)
|
||
|
return 1;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int dynamic_string::compare(const dynamic_string& rhs, bool case_sensitive) const
|
||
|
{
|
||
|
return compare(rhs.get_ptr_priv(), case_sensitive);
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::set(const char* p, uint max_len)
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
|
||
|
const uint len = math::minimum<uint>(max_len, static_cast<uint>(strlen(p)));
|
||
|
CRNLIB_ASSERT(len < cUINT16_MAX);
|
||
|
|
||
|
if ((!len) || (len >= cUINT16_MAX))
|
||
|
clear();
|
||
|
else if ((m_pStr) && (p >= m_pStr) && (p < (m_pStr + m_buf_size)))
|
||
|
{
|
||
|
if (m_pStr != p)
|
||
|
memmove(m_pStr, p, len);
|
||
|
m_pStr[len] = '\0';
|
||
|
m_len = static_cast<uint16>(len);
|
||
|
}
|
||
|
else if (ensure_buf(len, false))
|
||
|
{
|
||
|
m_len = static_cast<uint16>(len);
|
||
|
memcpy(m_pStr, p, m_len + 1);
|
||
|
}
|
||
|
|
||
|
check();
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::set(const dynamic_string& other, uint max_len)
|
||
|
{
|
||
|
if (this == &other)
|
||
|
{
|
||
|
if (max_len < m_len)
|
||
|
{
|
||
|
m_pStr[max_len] = '\0';
|
||
|
m_len = static_cast<uint16>(max_len);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const uint len = math::minimum<uint>(max_len, other.m_len);
|
||
|
|
||
|
if (!len)
|
||
|
clear();
|
||
|
else if (ensure_buf(len, false))
|
||
|
{
|
||
|
m_len = static_cast<uint16>(len);
|
||
|
memcpy(m_pStr, other.get_ptr_priv(), m_len);
|
||
|
m_pStr[len] = '\0';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
check();
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
bool dynamic_string::set_len(uint new_len, char fill_char)
|
||
|
{
|
||
|
if ((new_len >= cUINT16_MAX) || (!fill_char))
|
||
|
{
|
||
|
CRNLIB_ASSERT(0);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
uint cur_len = m_len;
|
||
|
|
||
|
if (ensure_buf(new_len, true))
|
||
|
{
|
||
|
if (new_len > cur_len)
|
||
|
memset(m_pStr + cur_len, fill_char, new_len - cur_len);
|
||
|
|
||
|
m_pStr[new_len] = 0;
|
||
|
|
||
|
m_len = static_cast<uint16>(new_len);
|
||
|
|
||
|
check();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::set_from_raw_buf_and_assume_ownership(char *pBuf, uint buf_size_in_chars, uint len_in_chars)
|
||
|
{
|
||
|
CRNLIB_ASSERT(buf_size_in_chars <= cUINT16_MAX);
|
||
|
CRNLIB_ASSERT(math::is_power_of_2(buf_size_in_chars) || (buf_size_in_chars == cUINT16_MAX));
|
||
|
CRNLIB_ASSERT((len_in_chars + 1) <= buf_size_in_chars);
|
||
|
|
||
|
clear();
|
||
|
|
||
|
m_pStr = pBuf;
|
||
|
m_buf_size = static_cast<uint16>(buf_size_in_chars);
|
||
|
m_len = static_cast<uint16>(len_in_chars);
|
||
|
|
||
|
check();
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::set_from_buf(const void* pBuf, uint buf_size)
|
||
|
{
|
||
|
CRNLIB_ASSERT(pBuf);
|
||
|
|
||
|
if (buf_size >= cUINT16_MAX)
|
||
|
{
|
||
|
clear();
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
#ifdef CRNLIB_BUILD_DEBUG
|
||
|
if ((buf_size) && (memchr(pBuf, 0, buf_size) != NULL))
|
||
|
{
|
||
|
CRNLIB_ASSERT(0);
|
||
|
clear();
|
||
|
return *this;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (ensure_buf(buf_size, false))
|
||
|
{
|
||
|
if (buf_size)
|
||
|
memcpy(m_pStr, pBuf, buf_size);
|
||
|
|
||
|
m_pStr[buf_size] = 0;
|
||
|
|
||
|
m_len = static_cast<uint16>(buf_size);
|
||
|
|
||
|
check();
|
||
|
}
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::set_char(uint index, char c)
|
||
|
{
|
||
|
CRNLIB_ASSERT(index <= m_len);
|
||
|
|
||
|
if (!c)
|
||
|
truncate(index);
|
||
|
else if (index < m_len)
|
||
|
{
|
||
|
m_pStr[index] = c;
|
||
|
|
||
|
check();
|
||
|
}
|
||
|
else if (index == m_len)
|
||
|
append_char(c);
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::append_char(char c)
|
||
|
{
|
||
|
if (ensure_buf(m_len + 1))
|
||
|
{
|
||
|
m_pStr[m_len] = c;
|
||
|
m_pStr[m_len + 1] = '\0';
|
||
|
m_len++;
|
||
|
check();
|
||
|
}
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::truncate(uint new_len)
|
||
|
{
|
||
|
if (new_len < m_len)
|
||
|
{
|
||
|
m_pStr[new_len] = '\0';
|
||
|
m_len = static_cast<uint16>(new_len);
|
||
|
check();
|
||
|
}
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::tolower()
|
||
|
{
|
||
|
if (m_len)
|
||
|
{
|
||
|
#ifdef _MSC_VER
|
||
|
_strlwr_s(get_ptr_priv(), m_buf_size);
|
||
|
#else
|
||
|
strlwr(get_ptr_priv());
|
||
|
#endif
|
||
|
}
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::toupper()
|
||
|
{
|
||
|
if (m_len)
|
||
|
{
|
||
|
#ifdef _MSC_VER
|
||
|
_strupr_s(get_ptr_priv(), m_buf_size);
|
||
|
#else
|
||
|
strupr(get_ptr_priv());
|
||
|
#endif
|
||
|
}
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::append(const char* p)
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
|
||
|
uint len = static_cast<uint>(strlen(p));
|
||
|
uint new_total_len = m_len + len;
|
||
|
if ((new_total_len) && ensure_buf(new_total_len))
|
||
|
{
|
||
|
memcpy(m_pStr + m_len, p, len + 1);
|
||
|
m_len = static_cast<uint16>(m_len + len);
|
||
|
check();
|
||
|
}
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::append(const dynamic_string& other)
|
||
|
{
|
||
|
uint len = other.m_len;
|
||
|
uint new_total_len = m_len + len;
|
||
|
if ((new_total_len) && ensure_buf(new_total_len))
|
||
|
{
|
||
|
memcpy(m_pStr + m_len, other.get_ptr_priv(), len + 1);
|
||
|
m_len = static_cast<uint16>(m_len + len);
|
||
|
check();
|
||
|
}
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string operator+ (const char* p, const dynamic_string& a)
|
||
|
{
|
||
|
return dynamic_string(p).append(a);
|
||
|
}
|
||
|
|
||
|
dynamic_string operator+ (const dynamic_string& a, const char* p)
|
||
|
{
|
||
|
return dynamic_string(a).append(p);
|
||
|
}
|
||
|
|
||
|
dynamic_string operator+ (const dynamic_string& a, const dynamic_string& b)
|
||
|
{
|
||
|
return dynamic_string(a).append(b);
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::format_args(const char* p, va_list args)
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
|
||
|
const uint cBufSize = 4096;
|
||
|
char buf[cBufSize];
|
||
|
|
||
|
#ifdef _MSC_VER
|
||
|
int l = vsnprintf_s(buf, cBufSize, _TRUNCATE, p, args);
|
||
|
#else
|
||
|
int l = vsnprintf(buf, cBufSize, p, args);
|
||
|
#endif
|
||
|
if (l <= 0)
|
||
|
clear();
|
||
|
else if (ensure_buf(l, false))
|
||
|
{
|
||
|
memcpy(m_pStr, buf, l + 1);
|
||
|
|
||
|
m_len = static_cast<uint16>(l);
|
||
|
|
||
|
check();
|
||
|
}
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::format(const char* p, ...)
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
|
||
|
va_list args;
|
||
|
va_start(args, p);
|
||
|
format_args(p, args);
|
||
|
va_end(args);
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::crop(uint start, uint len)
|
||
|
{
|
||
|
if (start >= m_len)
|
||
|
{
|
||
|
clear();
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
len = math::minimum<uint>(len, m_len - start);
|
||
|
|
||
|
if (start)
|
||
|
memmove(get_ptr_priv(), get_ptr_priv() + start, len);
|
||
|
|
||
|
m_pStr[len] = '\0';
|
||
|
|
||
|
m_len = static_cast<uint16>(len);
|
||
|
|
||
|
check();
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::substring(uint start, uint end)
|
||
|
{
|
||
|
CRNLIB_ASSERT(start <= end);
|
||
|
if (start > end)
|
||
|
return *this;
|
||
|
return crop(start, end - start);
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::left(uint len)
|
||
|
{
|
||
|
return substring(0, len);
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::mid(uint start, uint len)
|
||
|
{
|
||
|
return crop(start, len);
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::right(uint start)
|
||
|
{
|
||
|
return substring(start, get_len());
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::tail(uint num)
|
||
|
{
|
||
|
return substring(math::maximum<int>(static_cast<int>(get_len()) - static_cast<int>(num), 0), get_len());
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::unquote()
|
||
|
{
|
||
|
if (m_len >= 2)
|
||
|
{
|
||
|
if ( ((*this)[0] == '\"') && ((*this)[m_len - 1] == '\"') )
|
||
|
{
|
||
|
return mid(1, m_len - 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
int dynamic_string::find_left(const char* p, bool case_sensitive) const
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
|
||
|
const int p_len = (int)strlen(p);
|
||
|
|
||
|
for (int i = 0; i <= (m_len - p_len); i++)
|
||
|
if ((case_sensitive ? strncmp : _strnicmp)(p, &m_pStr[i], p_len) == 0)
|
||
|
return i;
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
bool dynamic_string::contains(const char* p, bool case_sensitive) const
|
||
|
{
|
||
|
return find_left(p, case_sensitive) >= 0;
|
||
|
}
|
||
|
|
||
|
uint dynamic_string::count_char(char c) const
|
||
|
{
|
||
|
uint count = 0;
|
||
|
for (uint i = 0; i < m_len; i++)
|
||
|
if (m_pStr[i] == c)
|
||
|
count++;
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
int dynamic_string::find_left(char c) const
|
||
|
{
|
||
|
for (uint i = 0; i < m_len; i++)
|
||
|
if (m_pStr[i] == c)
|
||
|
return i;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int dynamic_string::find_right(char c) const
|
||
|
{
|
||
|
for (int i = (int)m_len - 1; i >= 0; i--)
|
||
|
if (m_pStr[i] == c)
|
||
|
return i;
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int dynamic_string::find_right(const char* p, bool case_sensitive) const
|
||
|
{
|
||
|
CRNLIB_ASSERT(p);
|
||
|
const int p_len = (int)strlen(p);
|
||
|
|
||
|
for (int i = m_len - p_len; i >= 0; i--)
|
||
|
if ((case_sensitive ? strncmp : _strnicmp)(p, &m_pStr[i], p_len) == 0)
|
||
|
return i;
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::trim()
|
||
|
{
|
||
|
int s, e;
|
||
|
for (s = 0; s < (int)m_len; s++)
|
||
|
if (!isspace(m_pStr[s]))
|
||
|
break;
|
||
|
|
||
|
for (e = m_len - 1; e > s; e--)
|
||
|
if (!isspace(m_pStr[e]))
|
||
|
break;
|
||
|
|
||
|
return crop(s, e - s + 1);
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::trim_crlf()
|
||
|
{
|
||
|
int s = 0, e;
|
||
|
|
||
|
for (e = m_len - 1; e > s; e--)
|
||
|
if ((m_pStr[e] != 13) && (m_pStr[e] != 10))
|
||
|
break;
|
||
|
|
||
|
return crop(s, e - s + 1);
|
||
|
}
|
||
|
|
||
|
dynamic_string& dynamic_string::remap(int from_char, int to_char)
|
||
|
{
|
||
|
for (uint i = 0; i < m_len; i++)
|
||
|
if (m_pStr[i] == from_char)
|
||
|
m_pStr[i] = (char)to_char;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
#ifdef CRNLIB_BUILD_DEBUG
|
||
|
void dynamic_string::check() const
|
||
|
{
|
||
|
if (!m_pStr)
|
||
|
{
|
||
|
CRNLIB_ASSERT(!m_buf_size && !m_len);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CRNLIB_ASSERT(m_buf_size);
|
||
|
CRNLIB_ASSERT((m_buf_size == cUINT16_MAX) || math::is_power_of_2((uint32)m_buf_size));
|
||
|
CRNLIB_ASSERT(m_len < m_buf_size);
|
||
|
CRNLIB_ASSERT(!m_pStr[m_len]);
|
||
|
#if CRNLIB_SLOW_STRING_LEN_CHECKS
|
||
|
CRNLIB_ASSERT(strlen(m_pStr) == m_len);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
bool dynamic_string::ensure_buf(uint len, bool preserve_contents)
|
||
|
{
|
||
|
uint buf_size_needed = len + 1;
|
||
|
|
||
|
CRNLIB_ASSERT(buf_size_needed <= cUINT16_MAX);
|
||
|
|
||
|
if (buf_size_needed <= cUINT16_MAX)
|
||
|
{
|
||
|
if (buf_size_needed > m_buf_size)
|
||
|
expand_buf(buf_size_needed, preserve_contents);
|
||
|
}
|
||
|
|
||
|
return m_buf_size >= buf_size_needed;
|
||
|
}
|
||
|
|
||
|
bool dynamic_string::expand_buf(uint new_buf_size, bool preserve_contents)
|
||
|
{
|
||
|
new_buf_size = math::minimum<uint>(cUINT16_MAX, math::next_pow2(math::maximum<uint>(m_buf_size, new_buf_size)));
|
||
|
|
||
|
if (new_buf_size != m_buf_size)
|
||
|
{
|
||
|
char* p = crnlib_new_array<char>(new_buf_size);
|
||
|
|
||
|
if (preserve_contents)
|
||
|
memcpy(p, get_ptr_priv(), m_len + 1);
|
||
|
|
||
|
crnlib_delete_array(m_pStr);
|
||
|
m_pStr = p;
|
||
|
|
||
|
m_buf_size = static_cast<uint16>(new_buf_size);
|
||
|
|
||
|
if (preserve_contents)
|
||
|
check();
|
||
|
}
|
||
|
|
||
|
return m_buf_size >= new_buf_size;
|
||
|
}
|
||
|
|
||
|
void dynamic_string::swap(dynamic_string& other)
|
||
|
{
|
||
|
utils::swap(other.m_buf_size, m_buf_size);
|
||
|
utils::swap(other.m_len, m_len);
|
||
|
utils::swap(other.m_pStr, m_pStr);
|
||
|
}
|
||
|
|
||
|
int dynamic_string::serialize(void* pBuf, uint buf_size, bool little_endian) const
|
||
|
{
|
||
|
uint buf_left = buf_size;
|
||
|
|
||
|
//if (m_len > cUINT16_MAX)
|
||
|
// return -1;
|
||
|
CRNLIB_ASSUME(sizeof(m_len) == sizeof(uint16));
|
||
|
|
||
|
if (!utils::write_val((uint16)m_len, pBuf, buf_left, little_endian))
|
||
|
return -1;
|
||
|
|
||
|
if (buf_left < m_len)
|
||
|
return -1;
|
||
|
|
||
|
memcpy(pBuf, get_ptr(), m_len);
|
||
|
|
||
|
buf_left -= m_len;
|
||
|
|
||
|
return buf_size - buf_left;
|
||
|
}
|
||
|
|
||
|
int dynamic_string::deserialize(const void* pBuf, uint buf_size, bool little_endian)
|
||
|
{
|
||
|
uint buf_left = buf_size;
|
||
|
|
||
|
if (buf_left < sizeof(uint16)) return -1;
|
||
|
|
||
|
uint16 l;
|
||
|
if (!utils::read_obj(l, pBuf, buf_left, little_endian))
|
||
|
return -1;
|
||
|
|
||
|
if (buf_left < l)
|
||
|
return -1;
|
||
|
|
||
|
set_from_buf(pBuf, l);
|
||
|
|
||
|
buf_left -= l;
|
||
|
|
||
|
return buf_size - buf_left;
|
||
|
}
|
||
|
|
||
|
void dynamic_string::translate_lf_to_crlf()
|
||
|
{
|
||
|
if (find_left(0x0A) < 0)
|
||
|
return;
|
||
|
|
||
|
dynamic_string tmp;
|
||
|
tmp.ensure_buf(m_len + 2);
|
||
|
|
||
|
// normal sequence is 0x0D 0x0A (CR LF, \r\n)
|
||
|
|
||
|
int prev_char = -1;
|
||
|
for (uint i = 0; i < get_len(); i++)
|
||
|
{
|
||
|
const int cur_char = (*this)[i];
|
||
|
|
||
|
if ((cur_char == 0x0A) && (prev_char != 0x0D))
|
||
|
tmp.append_char(0x0D);
|
||
|
|
||
|
tmp.append_char(cur_char);
|
||
|
|
||
|
prev_char = cur_char;
|
||
|
}
|
||
|
|
||
|
swap(tmp);
|
||
|
}
|
||
|
|
||
|
} // namespace crnlib
|