1
0
Fork 0
libreoffice/sal/rtl/strtmpl.hxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

1737 lines
53 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#pragma once
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <limits>
#include <new>
#include <string_view>
#include <type_traits>
#include <utility>
#include "strimp.hxx"
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <sal/log.hxx>
#include <rtl/character.hxx>
#include <rtl/math.h>
#include <rtl/string.h>
#include <rtl/ustring.h>
#include <dragonbox/dragonbox.h>
void internRelease(rtl_uString*);
namespace rtl::str
{
template <typename C> auto UChar(C c) { return std::make_unsigned_t<C>(c); }
// Wrappers around null-terminated/known-length strings, that allow to generalize algorithms
// without overhead (e.g., without need to get length of null-terminated strings).
template <typename C> struct null_terminated
{
C* p;
null_terminated(C* pStr)
: p(pStr)
{
assert(pStr);
}
auto begin() const { return p; }
struct EndDetector
{
friend bool operator==(EndDetector, C* iter) { return *iter == 0; }
friend bool operator==(C* iter, EndDetector) { return *iter == 0; }
friend bool operator!=(EndDetector, C* iter) { return *iter != 0; }
friend bool operator!=(C* iter, EndDetector) { return *iter != 0; }
};
static auto end() { return EndDetector{}; }
};
template <typename C> struct with_length
{
C* p;
sal_Int32 len;
with_length(C* pStr, sal_Int32 nLength)
: p(pStr)
, len(nLength)
{
assert(len >= 0);
}
auto begin() const { return p; }
auto end() const { return p + len; }
};
template <bool (&fApplicable)(sal_uInt32), sal_uInt32 (&fReplace)(sal_uInt32)> struct CaseReplace
{
static auto Applicable() { return [](auto c) { return fApplicable(UChar(c)); }; }
template <typename C> static C Replace(C c) { return fReplace(UChar(c)); }
};
constexpr CaseReplace<rtl::isAsciiUpperCase, rtl::toAsciiLowerCase> toAsciiLower;
constexpr CaseReplace<rtl::isAsciiLowerCase, rtl::toAsciiUpperCase> toAsciiUpper;
template <typename C> struct FromTo
{
C from;
C to;
FromTo(C cFrom, C cTo) : from(cFrom), to(cTo) {}
auto Applicable() const { return [this](C c) { return c == from; }; }
C Replace(C c) const { return c == from ? to : c; }
};
template <typename C> void Copy(C* _pDest, const C* _pSrc, sal_Int32 _nCount)
{
// take advantage of builtin optimisations
std::copy(_pSrc, _pSrc + _nCount, _pDest);
}
template <typename C> void CopyBackward(C* _pDest, const C* _pSrc, sal_Int32 _nCount)
{
// take advantage of builtin optimisations
std::copy_backward(_pSrc, _pSrc + _nCount, _pDest + _nCount);
}
inline void Copy(sal_Unicode* _pDest, const char* _pSrc, sal_Int32 _nCount)
{
std::transform(_pSrc, _pSrc + _nCount, _pDest,
[](char c)
{
assert(rtl::isAscii(static_cast<unsigned char>(c)));
SAL_WARN_IF(c == '\0', "rtl.string", "Found embedded \\0 ASCII character");
return static_cast<unsigned char>(c);
});
}
inline sal_Int16 implGetDigit(sal_Unicode ch, sal_Int16 nRadix)
{
sal_Int16 n = -1;
if ((ch >= '0') && (ch <= '9'))
n = ch - '0';
else if ((ch >= 'a') && (ch <= 'z'))
n = ch - 'a' + 10;
else if ((ch >= 'A') && (ch <= 'Z'))
n = ch - 'A' + 10;
return (n < nRadix) ? n : -1;
}
/* ======================================================================= */
/* C-String functions which could be used without the String-Class */
/* ======================================================================= */
template <typename T> sal_Int32 getLength( const T* pStr )
{
assert(pStr);
if constexpr (std::is_class_v<T>)
{
return pStr->length;
}
else
{
// take advantage of builtin optimisations
return std::char_traits<T>::length(pStr);
}
}
/* ----------------------------------------------------------------------- */
template <typename C> void warnIfCharAndNotAscii(C c)
{
if constexpr (sizeof(c) == sizeof(char))
SAL_WARN_IF(!rtl::isAscii(static_cast<unsigned char>(c)), "rtl.string",
"Found non-ASCII char");
}
template <typename C1, typename C2> void warnIfOneIsCharAndNotAscii(C1 c1, C2 c2)
{
if constexpr (sizeof(c1) != sizeof(c2))
{
warnIfCharAndNotAscii(c1);
warnIfCharAndNotAscii(c2);
}
}
struct CompareNormal
{
template <typename C1, typename C2> static sal_Int32 compare(C1 c1, C2 c2)
{
warnIfOneIsCharAndNotAscii(c1, c2);
return static_cast<sal_Int32>(UChar(c1))
- static_cast<sal_Int32>(UChar(c2));
}
};
struct CompareIgnoreAsciiCase
{
template <typename C1, typename C2> static sal_Int32 compare(C1 c1, C2 c2)
{
warnIfOneIsCharAndNotAscii(c1, c2);
return rtl::compareIgnoreAsciiCase(UChar(c1), UChar(c2));
}
};
/* ----------------------------------------------------------------------- */
struct NoShortening
{
constexpr bool operator>=(int) { return true; } // for assert
constexpr bool operator==(int) { return false; } // for loop break check
constexpr void operator--() {} // for decrement in loop
} constexpr noShortening;
template <class S1, class S2, class Compare, typename Shorten_t>
sal_Int32 compare(S1 s1, S2 s2, Compare, Shorten_t shortenedLength)
{
static_assert(std::is_same_v<Shorten_t, NoShortening> || std::is_same_v<Shorten_t, sal_Int32>);
assert(shortenedLength >= 0);
auto pStr1 = s1.begin();
const auto end1 = s1.end();
auto pStr2 = s2.begin();
const auto end2 = s2.end();
for (;;)
{
if (shortenedLength == 0)
return 0;
if (pStr2 == end2)
return pStr1 == end1 ? 0 : 1;
if (pStr1 == end1)
return -1;
if (const sal_Int32 nRet = Compare::compare(*pStr1, *pStr2))
return nRet;
--shortenedLength;
++pStr1;
++pStr2;
}
}
// take advantage of builtin optimisations
template <typename C> requires (sizeof(C) == sizeof(wchar_t))
sal_Int32 compare(null_terminated<C> s1, null_terminated<C> s2, CompareNormal, NoShortening)
{
return wcscmp(reinterpret_cast<wchar_t const*>(s1.p), reinterpret_cast<wchar_t const*>(s2.p));
}
template <typename C> requires (sizeof(C) == sizeof(char))
sal_Int32 compare(null_terminated<C> s1, null_terminated<C> s2, CompareNormal, NoShortening)
{
return strcmp(reinterpret_cast<char const*>(s1.p), reinterpret_cast<char const*>(s2.p));
}
template <typename C>
sal_Int32 compare(with_length<C> s1, with_length<C> s2, CompareNormal, NoShortening)
{
std::basic_string_view sv1(s1.p, s1.len);
return sv1.compare(std::basic_string_view(s2.p, s2.len));
}
template <typename C1, typename C2, class Compare>
sal_Int32 compare(with_length<C1> s1, with_length<C2> s2, Compare cf, sal_Int32 nShortenedLength)
{
assert(nShortenedLength >= 0);
s1.len = std::min(s1.len, nShortenedLength);
s2.len = std::min(s2.len, nShortenedLength);
return compare(s1, s2, cf, noShortening);
}
/* ----------------------------------------------------------------------- */
template <typename C1, typename C2, class Compare>
sal_Int32 reverseCompare_WithLengths(const C1* pStr1, sal_Int32 nStr1Len,
const C2* pStr2, sal_Int32 nStr2Len, Compare)
{
assert(pStr1 || nStr1Len == 0);
assert(nStr1Len >= 0);
assert(pStr2 || nStr2Len == 0);
assert(nStr2Len >= 0);
const C1* pStr1Run = pStr1+nStr1Len;
const C2* pStr2Run = pStr2+nStr2Len;
while ((pStr1 < pStr1Run) && (pStr2 < pStr2Run))
{
pStr1Run--;
pStr2Run--;
if (const sal_Int32 nRet = Compare::compare(*pStr1Run, *pStr2Run))
return nRet;
}
return nStr1Len - nStr2Len;
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 hashCode_WithLength(const C* pStr, sal_Int32 nLen)
{
assert(nLen >= 0);
sal_uInt32 h = static_cast<sal_uInt32>(nLen);
while ( nLen > 0 )
{
h = (h*37U) + UChar( *pStr );
pStr++;
nLen--;
}
return static_cast<sal_Int32>(h);
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 hashCode(const C* pStr)
{
return hashCode_WithLength( pStr, getLength( pStr ) );
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 indexOfChar(const C* pStr, C c)
{
assert(pStr);
if (!c)
return -1; // Unifies behavior of strchr/wcschr and unoptimized algorithm wrt '\0'
if constexpr (sizeof(C) == sizeof(char))
{
// take advantage of builtin optimisations
const C* p = strchr(pStr, c);
return p ? p - pStr : -1;
}
else if constexpr (sizeof(C) == sizeof(wchar_t))
{
// take advantage of builtin optimisations
wchar_t const * p = wcschr(reinterpret_cast<wchar_t const *>(pStr), static_cast<wchar_t>(c));
return p ? p - reinterpret_cast<wchar_t const *>(pStr) : -1;
}
else
{
const C* pTempStr = pStr;
while ( *pTempStr )
{
if ( *pTempStr == c )
return pTempStr-pStr;
pTempStr++;
}
return -1;
}
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 indexOfChar_WithLength(const C* pStr, sal_Int32 nLen, C c)
{
// assert(nLen >= 0);
if (nLen <= 0)
return -1;
// take advantage of builtin optimisations
std::basic_string_view v(pStr, nLen);
auto idx = v.find(c);
return idx == v.npos ? -1 : idx;
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 lastIndexOfChar_WithLength(const C* pStr, sal_Int32 nLen, C c)
{
assert(nLen >= 0);
// take advantage of builtin optimisations
std::basic_string_view v(pStr, nLen);
auto idx = v.rfind(c);
return idx == v.npos ? -1 : idx;
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 lastIndexOfChar(const C* pStr, C c)
{
assert(pStr);
if (!c)
return -1; // Unifies behavior of strrchr/wcsrchr and lastIndexOfChar_WithLength wrt '\0'
if constexpr (sizeof(C) == sizeof(char))
{
// take advantage of builtin optimisations
const C* p = strrchr(pStr, c);
return p ? p - pStr : -1;
}
else if constexpr (sizeof(C) == sizeof(wchar_t))
{
// take advantage of builtin optimisations
wchar_t const * p = wcsrchr(reinterpret_cast<wchar_t const *>(pStr), static_cast<wchar_t>(c));
return p ? p - reinterpret_cast<wchar_t const *>(pStr) : -1;
}
else
{
return lastIndexOfChar_WithLength( pStr, getLength( pStr ), c );
}
}
/* ----------------------------------------------------------------------- */
template <typename C>
sal_Int32 indexOfStr_WithLength(const C* pStr, sal_Int32 nStrLen,
const C* pSubStr, sal_Int32 nSubLen)
{
assert(nStrLen >= 0);
assert(nSubLen >= 0);
/* an empty SubString is always not findable */
if ( nSubLen == 0 )
return -1;
// take advantage of builtin optimisations
std::basic_string_view v(pStr, nStrLen);
auto idx = nSubLen == 1 ? v.find(*pSubStr) : v.find(pSubStr, 0, nSubLen);
return idx == v.npos ? -1 : idx;
}
inline sal_Int32 indexOfStr_WithLength(const sal_Unicode* pStr, sal_Int32 nStrLen,
const char* pSubStr, sal_Int32 nSubLen)
{
assert(nStrLen >= 0);
assert(nSubLen >= 0);
if (nSubLen > 0 && nSubLen <= nStrLen)
{
sal_Unicode const* end = pStr + nStrLen;
sal_Unicode const* cursor = pStr;
while (cursor < end)
{
cursor = std::char_traits<sal_Unicode>::find(cursor, end - cursor, *pSubStr);
if (!cursor || (end - cursor < nSubLen))
{
/* no enough left to actually have a match */
break;
}
/* now it is worth trying a full match */
if (nSubLen == 1 || rtl_ustr_asciil_reverseEquals_WithLength(cursor, pSubStr, nSubLen))
{
return cursor - pStr;
}
cursor += 1;
}
}
return -1;
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 indexOfStr(const C* pStr, const C* pSubStr)
{
assert(pStr);
assert(pSubStr);
/* an empty SubString is always not findable */
if (*pSubStr == 0)
return -1;
if constexpr (sizeof(C) == sizeof(char))
{
// take advantage of builtin optimisations
const C* p = strstr(pStr, pSubStr);
return p ? p - pStr : -1;
}
else if constexpr (sizeof(C) == sizeof(wchar_t))
{
// take advantage of builtin optimisations
wchar_t const * p = wcsstr(reinterpret_cast<wchar_t const *>(pStr), reinterpret_cast<wchar_t const *>(pSubStr));
return p ? p - reinterpret_cast<wchar_t const *>(pStr) : -1;
}
else
{
return indexOfStr_WithLength( pStr, getLength( pStr ),
pSubStr, getLength( pSubStr ) );
}
}
/* ----------------------------------------------------------------------- */
template <typename C>
sal_Int32 lastIndexOfStr_WithLength(const C* pStr, sal_Int32 nStrLen,
const C* pSubStr, sal_Int32 nSubLen)
{
assert(nStrLen >= 0);
assert(nSubLen >= 0);
/* an empty SubString is always not findable */
if ( nSubLen == 0 )
return -1;
// take advantage of builtin optimisations
std::basic_string_view v(pStr, nStrLen);
std::basic_string_view needle(pSubStr, nSubLen);
auto idx = v.rfind(needle);
return idx == v.npos ? -1 : idx;
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 lastIndexOfStr(const C* pStr, const C* pSubStr)
{
return lastIndexOfStr_WithLength(pStr, getLength(pStr), pSubStr, getLength(pSubStr));
}
/* ----------------------------------------------------------------------- */
template <class S, class Replacer> void replaceChars(S str, Replacer replacer)
{
for (auto& rChar : str)
rChar = replacer.Replace(rChar);
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 trim_WithLength(C* pStr, sal_Int32 nLen)
{
const auto view = o3tl::trim(std::basic_string_view(pStr, nLen));
if (static_cast<sal_Int32>(view.size()) != nLen)
{
nLen = static_cast<sal_Int32>(view.size());
if (view.data() != pStr)
Copy(pStr, view.data(), nLen);
*(pStr+nLen) = 0;
}
return nLen;
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 trim(C* pStr) { return trim_WithLength(pStr, getLength(pStr)); }
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 valueOfBoolean(C* pStr, sal_Bool b)
{
assert(pStr);
if ( b )
{
*pStr = 't';
pStr++;
*pStr = 'r';
pStr++;
*pStr = 'u';
pStr++;
*pStr = 'e';
pStr++;
*pStr = 0;
return 4;
}
else
{
*pStr = 'f';
pStr++;
*pStr = 'a';
pStr++;
*pStr = 'l';
pStr++;
*pStr = 's';
pStr++;
*pStr = 'e';
pStr++;
*pStr = 0;
return 5;
}
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Int32 valueOfChar(C* pStr, C c)
{
assert(pStr);
*pStr++ = c;
*pStr = 0;
return 1;
}
/* ----------------------------------------------------------------------- */
template <sal_Int32 maxLen, typename C, typename T>
sal_Int32 valueOfInt(C* pStr, T n, sal_Int16 nRadix)
{
assert(pStr);
assert( nRadix >= RTL_STR_MIN_RADIX && nRadix <= RTL_STR_MAX_RADIX );
const auto* const pStart = pStr;
char aBuf[maxLen];
char* pBuf = aBuf;
using uT = std::make_unsigned_t<T>;
uT nValue;
/* Radix must be valid */
if ( (nRadix < RTL_STR_MIN_RADIX) || (nRadix > RTL_STR_MAX_RADIX) )
nRadix = 10;
if constexpr (std::is_signed_v<T>)
{
/* is value negative */
if ( n < 0 )
{
*pStr = '-';
pStr++;
nValue = n == std::numeric_limits<T>::min() ? static_cast<uT>(n) : -n;
}
else
nValue = n;
}
else
nValue = n;
/* create a recursive buffer with all values, except the last one */
do
{
char nDigit = static_cast<char>(nValue % nRadix);
nValue /= nRadix;
if ( nDigit > 9 )
*pBuf = (nDigit-10) + 'a';
else
*pBuf = (nDigit + '0' );
pBuf++;
}
while ( nValue > 0 );
/* copy the values in the right direction into the destination buffer */
pStr = std::reverse_copy(aBuf, pBuf, pStr);
*pStr = 0;
return pStr - pStart;
}
/* ----------------------------------------------------------------------- */
template <typename C> sal_Bool toBoolean(const C* pStr)
{
assert(pStr);
if ( *pStr == '1' )
return true;
if ( (*pStr == 'T') || (*pStr == 't') )
{
pStr++;
if ( (*pStr == 'R') || (*pStr == 'r') )
{
pStr++;
if ( (*pStr == 'U') || (*pStr == 'u') )
{
pStr++;
if ( (*pStr == 'E') || (*pStr == 'e') )
return true;
}
}
}
return false;
}
/* ----------------------------------------------------------------------- */
template <typename T, class Iter> inline bool HandleSignChar(Iter& iter)
{
if constexpr (std::numeric_limits<T>::is_signed)
{
if (*iter == '-')
{
++iter;
return true;
}
}
if (*iter == '+')
++iter;
return false;
}
template <typename T> std::pair<T, sal_Int16> DivMod(sal_Int16 nRadix, [[maybe_unused]] bool bNeg)
{
if constexpr (std::numeric_limits<T>::is_signed)
if (bNeg)
return { -(std::numeric_limits<T>::min() / nRadix),
-(std::numeric_limits<T>::min() % nRadix) };
return { std::numeric_limits<T>::max() / nRadix, std::numeric_limits<T>::max() % nRadix };
}
template <typename T, class S> T toInt(S str, sal_Int16 nRadix)
{
assert( nRadix >= RTL_STR_MIN_RADIX && nRadix <= RTL_STR_MAX_RADIX );
if ( (nRadix < RTL_STR_MIN_RADIX) || (nRadix > RTL_STR_MAX_RADIX) )
nRadix = 10;
auto pStr = str.begin();
const auto end = str.end();
/* Skip whitespaces */
while (pStr != end && o3tl::internal::implIsWhitespace(UChar(*pStr)))
pStr++;
if (pStr == end)
return 0;
const bool bNeg = HandleSignChar<T>(pStr);
const auto& [nDiv, nMod] = DivMod<T>(nRadix, bNeg);
assert(nDiv > 0);
std::make_unsigned_t<T> n = 0;
while (pStr != end)
{
sal_Int16 nDigit = implGetDigit(UChar(*pStr), nRadix);
if ( nDigit < 0 )
break;
if (static_cast<std::make_unsigned_t<T>>(nMod < nDigit ? nDiv - 1 : nDiv) < n)
return 0;
n *= nRadix;
n += nDigit;
pStr++;
}
if constexpr (std::numeric_limits<T>::is_signed)
if (bNeg)
return n == static_cast<std::make_unsigned_t<T>>(std::numeric_limits<T>::min())
? std::numeric_limits<T>::min()
: -static_cast<T>(n);
return static_cast<T>(n);
}
/* ======================================================================= */
/* Internal String-Class help functions */
/* ======================================================================= */
template <class rtl_tString> using Char_T = std::remove_extent_t<decltype(rtl_tString::buffer)>;
template <typename rtl_tString> rtl_tString* Alloc(sal_Int32 nLen)
{
constexpr auto fix = offsetof(rtl_tString, buffer) + sizeof rtl_tString::buffer;
rtl_tString * pData
= (o3tl::make_unsigned(nLen)
<= ((std::numeric_limits<std::size_t>::max() - fix)
/ sizeof (Char_T<rtl_tString>)))
? static_cast<rtl_tString *>(rtl_allocateString(
fix + nLen * sizeof (Char_T<rtl_tString>)))
: nullptr;
if (pData != nullptr) {
pData->refCount = 1;
pData->length = nLen;
pData->buffer[nLen] = 0;
}
return pData;
}
/* ======================================================================= */
/* String-Class functions */
/* ======================================================================= */
template <typename rtl_tString> void acquire(rtl_tString* pThis)
{
if (!SAL_STRING_IS_STATIC (pThis))
osl_atomic_increment( &((pThis)->refCount) );
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString> void release(rtl_tString* pThis)
{
if (SAL_UNLIKELY(SAL_STRING_IS_STATIC (pThis)))
return;
/* OString doesn't have an 'intern' */
if constexpr (sizeof(Char_T<rtl_tString>) == sizeof(sal_Unicode))
{
if (SAL_STRING_IS_INTERN (pThis))
{
internRelease (pThis);
return;
}
}
if ( !osl_atomic_decrement( &(pThis->refCount) ) )
{
RTL_LOG_STRING_DELETE( pThis );
rtl_freeString( pThis );
}
}
/* ----------------------------------------------------------------------- */
/* static data to be referenced by all empty strings
* the refCount is predefined to 1 and must never become 0 !
*/
template <typename rtl_tString> struct EmptyStringImpl
{
static rtl_tString data;
};
template <>
inline rtl_uString EmptyStringImpl<rtl_uString>::data = {
sal_Int32(SAL_STRING_INTERN_FLAG | SAL_STRING_STATIC_FLAG | 1), /* sal_Int32 refCount; */
0, /* sal_Int32 length; */
{ 0 } /* sal_Unicode buffer[1]; */
};
template <>
inline rtl_String EmptyStringImpl<rtl_String>::data = {
SAL_STRING_STATIC_FLAG | 1, /* sal_Int32 refCount; */
0, /* sal_Int32 length; */
{ 0 } /* char buffer[1]; */
};
template <typename rtl_tString> void new_(rtl_tString** ppThis)
{
assert(ppThis);
if ( *ppThis)
release( *ppThis );
*ppThis = &EmptyStringImpl<rtl_tString>::data;
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString> void new_WithLength(rtl_tString** ppThis, sal_Int32 nLen)
{
assert(ppThis);
assert(nLen >= 0);
if ( nLen <= 0 )
new_( ppThis );
else
{
if ( *ppThis)
release( *ppThis );
*ppThis = Alloc<rtl_tString>( nLen );
assert(*ppThis != nullptr);
(*ppThis)->length = 0;
(*ppThis)->buffer[0] = 0;
}
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString, typename C>
void newFromStr_WithLength(rtl_tString** ppThis, const C* pCharStr, sal_Int32 nLen,
sal_Int32 allocExtra = 0)
{
assert(ppThis);
assert(nLen >= 0);
assert(pCharStr || nLen == 0);
assert(allocExtra >= 0);
if (nLen + allocExtra == 0)
return new_(ppThis);
rtl_tString* pOrg = *ppThis;
*ppThis = Alloc<rtl_tString>(nLen + allocExtra);
assert(*ppThis != nullptr);
if (nLen > 0)
Copy((*ppThis)->buffer, pCharStr, nLen);
if (allocExtra > 0)
{
(*ppThis)->length = nLen;
(*ppThis)->buffer[nLen] = 0;
}
RTL_LOG_STRING_NEW(*ppThis);
/* must be done last, if pCharStr belongs to *ppThis */
if (pOrg)
release(pOrg);
}
template <typename rtl_tString> void newFromString(rtl_tString** ppThis, const rtl_tString* pStr)
{
assert(pStr);
newFromStr_WithLength(ppThis, pStr->buffer, pStr->length);
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString>
void newFromStr(rtl_tString** ppThis, const Char_T<rtl_tString>* pCharStr)
{
newFromStr_WithLength(ppThis, pCharStr, getLength(pCharStr));
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString> void assign(rtl_tString** ppThis, rtl_tString* pStr)
{
assert(ppThis);
/* must be done at first, if pStr == *ppThis */
acquire( pStr );
if ( *ppThis )
release( *ppThis );
*ppThis = pStr;
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString>
void newFromSubString(rtl_tString** ppThis, const rtl_tString* pFrom, sal_Int32 beginIndex,
sal_Int32 count)
{
assert(ppThis);
if ( beginIndex == 0 && count == pFrom->length )
return assign(ppThis, const_cast<rtl_tString*>(pFrom));
if ( count < 0 || beginIndex < 0 || beginIndex + count > pFrom->length )
{
assert(false); // fail fast at least in debug builds
return newFromStr_WithLength(ppThis, "!!br0ken!!", 10);
}
newFromStr_WithLength( ppThis, pFrom->buffer + beginIndex, count );
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString> auto* getStr(rtl_tString* pThis)
{
assert(pThis);
return pThis->buffer;
}
/* ----------------------------------------------------------------------- */
enum ThrowPolicy { NoThrow, Throw };
template <ThrowPolicy throwPolicy, typename rtl_tString, typename C1, typename C2>
void newConcat(rtl_tString** ppThis, const C1* pLeft, sal_Int32 nLeftLength,
const C2* pRight, sal_Int32 nRightLength)
{
assert(ppThis);
assert(nLeftLength >= 0);
assert(pLeft || nLeftLength == 0);
assert(nRightLength >= 0);
assert(pRight || nRightLength == 0);
rtl_tString* pOrg = *ppThis;
if (nLeftLength > std::numeric_limits<sal_Int32>::max() - nRightLength)
{
if constexpr (throwPolicy == NoThrow)
*ppThis = nullptr;
else
{
#if !defined(__COVERITY__) || __COVERITY_MAJOR__ > 2023
throw std::length_error("newConcat");
#else
//coverity doesn't report std::bad_alloc as an unhandled exception when
//potentially thrown from destructors but does report std::length_error
throw std::bad_alloc();
#endif
}
}
else
{
auto* pTempStr = Alloc<rtl_tString>(nLeftLength + nRightLength);
OSL_ASSERT(pTempStr != nullptr);
*ppThis = pTempStr;
if (*ppThis != nullptr) {
if (nLeftLength)
Copy( pTempStr->buffer, pLeft, nLeftLength );
if (nRightLength)
Copy( pTempStr->buffer+nLeftLength, pRight, nRightLength );
RTL_LOG_STRING_NEW( *ppThis );
}
}
/* must be done last, if left or right == *ppThis */
if ( pOrg )
release( pOrg );
}
template <typename rtl_tString, typename C>
void newConcat(rtl_tString** ppThis, rtl_tString* pLeft, const C* pRight, sal_Int32 nRightLength)
{
assert(pLeft != nullptr);
if (nRightLength == 0)
assign(ppThis, pLeft);
else
newConcat<Throw>(ppThis, pLeft->buffer, pLeft->length, pRight, nRightLength);
}
template <typename rtl_tString>
void newConcat(rtl_tString** ppThis, rtl_tString* pLeft, rtl_tString* pRight)
{
/* Test for 0-Pointer - if not, change newReplaceStrAt! */
if ( !pRight || !pRight->length )
{
assert(pLeft != nullptr);
assign(ppThis, pLeft);
}
else if ( !pLeft || !pLeft->length )
assign(ppThis, pRight);
else
newConcat<NoThrow>(ppThis, pLeft->buffer, pLeft->length, pRight->buffer, pRight->length);
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString> void ensureCapacity(rtl_tString** ppThis, sal_Int32 size)
{
assert(ppThis);
rtl_tString* const pOrg = *ppThis;
if ( pOrg->refCount == 1 && pOrg->length >= size )
return;
assert( pOrg->length <= size ); // do not truncate
auto* pTempStr = Alloc<rtl_tString>( size );
Copy( pTempStr->buffer, pOrg->buffer, pOrg->length );
// right now the length is still the same as of the original
pTempStr->length = pOrg->length;
pTempStr->buffer[ pOrg->length ] = '\0';
*ppThis = pTempStr;
RTL_LOG_STRING_NEW( *ppThis );
release( pOrg );
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString, typename C>
void newReplaceStrAt(rtl_tString** ppThis, rtl_tString* pStr, sal_Int32 nIndex, sal_Int32 nCount,
const C* pNewSubStr, sal_Int32 nNewSubStrLen)
{
assert(ppThis);
assert(nIndex >= 0 && nIndex <= pStr->length);
assert(nCount >= 0);
assert(nCount <= pStr->length - nIndex);
assert(pNewSubStr != nullptr || nNewSubStrLen == 0);
assert(nNewSubStrLen >= 0);
/* Append? */
if ( nIndex >= pStr->length )
return newConcat(ppThis, pStr, pNewSubStr, nNewSubStrLen);
/* not more than the String length could be deleted */
if ( nCount >= pStr->length-nIndex )
{
/* Assign of NewSubStr? */
if (nIndex == 0)
return newFromStr_WithLength( ppThis, pNewSubStr, nNewSubStrLen );
nCount = pStr->length - nIndex;
}
/* Assign of Str? */
if ( !nCount && !nNewSubStrLen )
return assign(ppThis, pStr);
rtl_tString* pOrg = *ppThis;
/* Alloc New Buffer */
*ppThis = Alloc<rtl_tString>(pStr->length - nCount + nNewSubStrLen);
assert(*ppThis != nullptr);
auto* pBuffer = (*ppThis)->buffer;
if ( nIndex )
{
Copy( pBuffer, pStr->buffer, nIndex );
pBuffer += nIndex;
}
if ( nNewSubStrLen )
{
Copy( pBuffer, pNewSubStr, nNewSubStrLen );
pBuffer += nNewSubStrLen;
}
Copy( pBuffer, pStr->buffer+nIndex+nCount, pStr->length-nIndex-nCount );
RTL_LOG_STRING_NEW( *ppThis );
/* must be done last, if pStr or pNewSubStr == *ppThis */
if ( pOrg )
release( pOrg );
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString>
void newReplaceStrAt(rtl_tString** ppThis, rtl_tString* pStr, sal_Int32 nIndex, sal_Int32 nCount,
rtl_tString* pNewSubStr)
{
assert(ppThis);
assert(nIndex >= 0 && nIndex <= pStr->length);
assert(nCount >= 0);
assert(nCount <= pStr->length - nIndex);
/* Append? */
if (nIndex >= pStr->length)
{
/* newConcat test, if pNewSubStr is 0 */
newConcat(ppThis, pStr, pNewSubStr);
return;
}
/* not more than the String length could be deleted */
if (nCount >= pStr->length-nIndex)
{
/* Assign of NewSubStr? */
if (nIndex == 0)
{
if (!pNewSubStr)
return new_(ppThis);
else
return assign(ppThis, pNewSubStr);
}
nCount = pStr->length - nIndex;
}
const auto* pNewSubStrBuf = pNewSubStr ? pNewSubStr->buffer : nullptr;
const sal_Int32 nNewSubStrLength = pNewSubStr ? pNewSubStr->length : 0;
newReplaceStrAt(ppThis, pStr, nIndex, nCount, pNewSubStrBuf, nNewSubStrLength);
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString, class Replacer>
void newReplaceChars(rtl_tString** ppThis, rtl_tString* pStr, Replacer replacer)
{
assert(ppThis);
assert(pStr);
const auto pEnd = pStr->buffer + pStr->length;
auto pCharStr = std::find_if(pStr->buffer, pEnd, replacer.Applicable());
if (pCharStr != pEnd)
{
rtl_tString* pOrg = *ppThis;
*ppThis = Alloc<rtl_tString>(pStr->length);
assert(*ppThis != nullptr);
auto* pNewCharStr = (*ppThis)->buffer;
/* Copy String */
const sal_Int32 nCount = pCharStr - pStr->buffer;
Copy(pNewCharStr, pStr->buffer, nCount);
pNewCharStr += nCount;
/* replace/copy rest of the string */
do
{
*pNewCharStr = replacer.Replace(*pCharStr);
pNewCharStr++;
pCharStr++;
} while (pCharStr != pEnd);
RTL_LOG_STRING_NEW(*ppThis);
/* must be done last, if pStr == *ppThis */
if (pOrg)
release(pOrg);
}
else
assign(ppThis, pStr);
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString> void newTrim(rtl_tString** ppThis, rtl_tString* pStr)
{
assert(pStr);
const auto view = o3tl::trim(std::basic_string_view(pStr->buffer, pStr->length));
if (static_cast<sal_Int32>(view.size()) == pStr->length)
assign(ppThis, pStr);
else
newFromStr_WithLength(ppThis, view.data(), view.size());
}
/* ----------------------------------------------------------------------- */
template <typename rtl_tString>
sal_Int32 getToken(rtl_tString** ppThis, rtl_tString* pStr, sal_Int32 nToken,
Char_T<rtl_tString> cTok, sal_Int32 nIndex)
{
assert(ppThis);
assert(pStr);
assert(nIndex <= pStr->length);
// Set ppThis to an empty string and return -1 if either nToken or nIndex is
// negative:
if (nIndex >= 0 && nToken >= 0)
{
const auto* pOrgCharStr = pStr->buffer;
const auto* pCharStr = pOrgCharStr + nIndex;
sal_Int32 nLen = pStr->length - nIndex;
sal_Int32 nTokCount = 0;
const auto* pCharStrStart = pCharStr;
while (nLen > 0)
{
if (*pCharStr == cTok)
{
nTokCount++;
if (nTokCount > nToken)
break;
if (nTokCount == nToken)
pCharStrStart = pCharStr + 1;
}
pCharStr++;
nLen--;
}
if (nTokCount >= nToken)
{
newFromStr_WithLength(ppThis, pCharStrStart, pCharStr - pCharStrStart);
if (nLen > 0)
return pCharStr - pOrgCharStr + 1;
else
return -1;
}
}
new_(ppThis);
return -1;
}
/* ======================================================================= */
/* String buffer help functions */
/* ======================================================================= */
template <class rtl_tString>
void stringbuffer_newFromStr_WithLength(rtl_tString** ppThis,
const Char_T<rtl_tString>* pStr, sal_Int32 count)
{
assert(ppThis);
assert(count >= 0);
if (!pStr)
count = 0; // Because old code didn't care about count when !pStr
newFromStr_WithLength(ppThis, pStr, count, 16);
}
template <class rtl_tString>
sal_Int32 stringbuffer_newFromStringBuffer(rtl_tString** ppThis, sal_Int32 capacity,
rtl_tString* pStr)
{
assert(capacity >= 0);
assert(pStr);
if (capacity < pStr->length)
capacity = pStr->length;
newFromStr_WithLength(ppThis, pStr->buffer, pStr->length, capacity - pStr->length);
return capacity;
}
template <class rtl_tString>
void stringbuffer_ensureCapacity(rtl_tString** ppThis, sal_Int32* capacity,
sal_Int32 minimumCapacity)
{
assert(ppThis);
assert(capacity && *capacity >= 0);
// assert(minimumCapacity >= 0); // It was commented out in rtl_stringbuffer_ensureCapacity
if (minimumCapacity <= *capacity)
return;
const auto nLength = (*ppThis)->length;
*capacity = (nLength + 1) * 2;
if (minimumCapacity > *capacity)
*capacity = minimumCapacity;
newFromStr_WithLength(ppThis, (*ppThis)->buffer, nLength, *capacity - nLength);
}
template <class rtl_tString, typename C>
void stringbuffer_insert(rtl_tString** ppThis, sal_Int32* capacity, sal_Int32 offset,
const C* pStr, sal_Int32 len)
{
assert(ppThis);
assert(capacity && *capacity >= 0);
assert(offset >= 0 && offset <= (*ppThis)->length);
assert(len >= 0);
if (len == 0)
return;
if (len > std::numeric_limits<sal_Int32>::max() - (*ppThis)->length) {
throw std::bad_alloc();
}
stringbuffer_ensureCapacity(ppThis, capacity, (*ppThis)->length + len);
sal_Int32 nOldLen = (*ppThis)->length;
auto* pBuf = (*ppThis)->buffer;
/* copy the tail */
const sal_Int32 n = nOldLen - offset;
if (n > 0)
CopyBackward(pBuf + offset + len, pBuf + offset, n);
/* insert the new characters */
if (pStr != nullptr)
Copy(pBuf + offset, pStr, len);
(*ppThis)->length = nOldLen + len;
pBuf[nOldLen + len] = 0;
}
template <class rtl_tString>
void stringbuffer_remove(rtl_tString** ppThis, sal_Int32 start, sal_Int32 len)
{
assert(ppThis);
assert(start >= 0 && start <= (*ppThis)->length);
assert(len >= 0);
if (len > (*ppThis)->length - start)
len = (*ppThis)->length - start;
//remove nothing
if (!len)
return;
auto* pBuf = (*ppThis)->buffer;
const sal_Int32 nTailLen = (*ppThis)->length - (start + len);
if (nTailLen)
{
/* move the tail */
Copy(pBuf + start, pBuf + start + len, nTailLen);
}
(*ppThis)->length -= len;
pBuf[(*ppThis)->length] = 0;
}
template <class S, typename CharTypeFrom, typename CharTypeTo>
void newReplaceAllFromIndex(S** s, S* s1, CharTypeFrom const* from, sal_Int32 fromLength,
CharTypeTo const* to, sal_Int32 toLength, sal_Int32 fromIndex)
{
assert(s != nullptr);
assert(s1 != nullptr);
assert(fromLength >= 0);
assert(from != nullptr || fromLength == 0);
assert(toLength >= 0);
assert(to != nullptr || toLength == 0);
assert(fromIndex >= 0 && fromIndex <= s1->length);
sal_Int32 i = indexOfStr_WithLength(s1->buffer + fromIndex, s1->length - fromIndex,
from, fromLength);
if (i >= 0)
{
if (s1->length - fromLength > SAL_MAX_INT32 - toLength)
std::abort();
i += fromIndex;
sal_Int32 nCapacity = s1->length + (toLength - fromLength);
if (fromLength < toLength)
{
// Pre-allocate up to 16 replacements more
const sal_Int32 nMaxMoreFinds = (s1->length - i - fromLength) / fromLength;
const sal_Int32 nIncrease = toLength - fromLength;
const sal_Int32 nMoreReplacements = std::min(
{ nMaxMoreFinds, (SAL_MAX_INT32 - nCapacity) / nIncrease, sal_Int32(16) });
nCapacity += nMoreReplacements * nIncrease;
}
const auto pOld = *s;
*s = Alloc<S>(nCapacity);
(*s)->length = 0;
fromIndex = 0;
do
{
stringbuffer_insert(s, &nCapacity, (*s)->length, s1->buffer + fromIndex, i);
stringbuffer_insert(s, &nCapacity, (*s)->length, to, toLength);
fromIndex += i + fromLength;
i = indexOfStr_WithLength(s1->buffer + fromIndex, s1->length - fromIndex,
from, fromLength);
} while (i >= 0);
// the rest
stringbuffer_insert(s, &nCapacity, (*s)->length,
s1->buffer + fromIndex, s1->length - fromIndex);
if (pOld)
release(pOld); // Must be last in case *s == s1
}
else
assign(s, s1);
RTL_LOG_STRING_NEW(*s);
}
template <class rtl_tString, typename C1, typename C2>
void newReplaceFirst(rtl_tString** s, rtl_tString* s1, C1 const* from, sal_Int32 fromLength,
C2 const* to, sal_Int32 toLength, sal_Int32& fromIndex)
{
assert(s != nullptr);
assert(s1 != nullptr);
assert(fromLength >= 0);
assert(from != nullptr || fromLength == 0);
assert(toLength >= 0);
assert(to != nullptr || toLength == 0);
assert(fromIndex >= 0 && fromIndex <= s1->length);
sal_Int32 i = indexOfStr_WithLength(s1->buffer + fromIndex, s1->length - fromIndex,
from, fromLength);
if (i >= 0)
{
if (s1->length - fromLength > SAL_MAX_INT32 - toLength)
std::abort();
i += fromIndex;
newReplaceStrAt(s, s1, i, fromLength, to, toLength);
}
else
assign(s, s1);
fromIndex = i;
}
// doubleToString implementation
static inline constexpr sal_uInt64 eX[] = { 10ull,
100ull,
1000ull,
10000ull,
100000ull,
1000000ull,
10000000ull,
100000000ull,
1000000000ull,
10000000000ull,
100000000000ull,
1000000000000ull,
10000000000000ull,
100000000000000ull,
1000000000000000ull,
10000000000000000ull,
100000000000000000ull,
1000000000000000000ull,
10000000000000000000ull };
template <typename rtl_tString>
void doubleToString(rtl_tString** pResult, sal_Int32* pResultCapacity, sal_Int32 nResultOffset,
double fValue, rtl_math_StringFormat eFormat, sal_Int32 nDecPlaces,
Char_T<rtl_tString> cDecSeparator, sal_Int32 const* pGroups,
Char_T<rtl_tString> cGroupSeparator, bool bEraseTrailingDecZeros)
{
auto decimalDigits = [](sal_uInt64 n) {
return std::distance(std::begin(eX), std::upper_bound(std::begin(eX), std::end(eX), n)) + 1;
};
auto roundToPow10 = [](sal_uInt64 n, int e) {
assert(e > 0 && o3tl::make_unsigned(e) <= std::size(eX));
const sal_uInt64 d = eX[e - 1];
return (n + d / 2) / d * d;
};
auto append = [](rtl_tString** s, sal_Int32* pCapacity, sal_Int32 rOffset, auto sv)
{
if (!pCapacity)
newFromStr_WithLength(s, sv.data(), sv.size());
else
stringbuffer_insert(s, pCapacity, rOffset, sv.data(), sv.size());
};
if (std::isnan(fValue))
{
// #i112652# XMLSchema-2
static constexpr std::string_view nan{ "NaN" };
return append(pResult, pResultCapacity, nResultOffset, nan);
}
// sign adjustment, instead of testing for fValue<0.0 this will also fetch -0.0
bool bSign = std::signbit(fValue);
if (std::isinf(fValue))
{
// #i112652# XMLSchema-2
std::string_view inf = bSign ? std::string_view("-INF") : std::string_view("INF");
return append(pResult, pResultCapacity, nResultOffset, inf);
}
if (bSign)
fValue = -fValue;
decltype(jkj::dragonbox::to_decimal(fValue, jkj::dragonbox::policy::sign::ignore,
jkj::dragonbox::policy::trailing_zero::ignore)) aParts{};
if (fValue) // to_decimal is documented to only handle non-zero finite numbers
aParts = jkj::dragonbox::to_decimal(fValue, jkj::dragonbox::policy::sign::ignore,
jkj::dragonbox::policy::trailing_zero::ignore);
int nOrigDigits = decimalDigits(aParts.significand);
int nExp = nOrigDigits + aParts.exponent - 1;
int nRoundDigits = 15;
// Unfortunately the old rounding below writes 1.79769313486232e+308 for
// DBL_MAX and 4 subsequent nextafter(...,0).
static const double fB1 = std::nextafter(std::numeric_limits<double>::max(), 0);
static const double fB2 = std::nextafter(fB1, 0);
static const double fB3 = std::nextafter(fB2, 0);
static const double fB4 = std::nextafter(fB3, 0);
if ((fValue >= fB4) && eFormat != rtl_math_StringFormat_F)
{
// 1.7976931348623157e+308 instead of rounded 1.79769313486232e+308
// that can't be converted back as out of range. For rounded values if
// they exceed range they should not be written to exchange strings or
// file formats.
eFormat = rtl_math_StringFormat_E;
nDecPlaces = std::clamp<sal_Int32>(nDecPlaces, 0, 16);
nRoundDigits = 17;
}
// Use integer representation for integer values that fit into the
// mantissa (1.((2^53)-1)) with a precision of 1 for highest accuracy.
if ((eFormat == rtl_math_StringFormat_Automatic || eFormat == rtl_math_StringFormat_F)
&& aParts.exponent >= 0 && fValue < 0x1p53)
{
eFormat = rtl_math_StringFormat_F;
if (nDecPlaces == rtl_math_DecimalPlaces_Max)
nDecPlaces = 0;
else
nDecPlaces = std::clamp<sal_Int32>(nDecPlaces, -15, 15);
if (bEraseTrailingDecZeros && nDecPlaces > 0)
nDecPlaces = 0;
nRoundDigits = nOrigDigits; // no rounding
}
switch (eFormat)
{
case rtl_math_StringFormat_Automatic:
// E or F depending on exponent magnitude
if (nExp <= -15 || nExp >= 15)
{
if (nDecPlaces == rtl_math_DecimalPlaces_Max)
nDecPlaces = 14;
eFormat = rtl_math_StringFormat_E;
}
else
{
if (nDecPlaces == rtl_math_DecimalPlaces_Max)
nDecPlaces = (nExp < 14) ? 15 - nExp - 1 : 15;
eFormat = rtl_math_StringFormat_F;
}
break;
case rtl_math_StringFormat_G:
case rtl_math_StringFormat_G1:
case rtl_math_StringFormat_G2:
// G-Point, similar to sprintf %G
if (nDecPlaces == rtl_math_DecimalPlaces_DefaultSignificance)
nDecPlaces = 6;
if (nExp < -4 || nExp >= nDecPlaces)
{
nDecPlaces = std::max<sal_Int32>(1, nDecPlaces - 1);
if (eFormat == rtl_math_StringFormat_G)
eFormat = rtl_math_StringFormat_E;
else if (eFormat == rtl_math_StringFormat_G2)
eFormat = rtl_math_StringFormat_E2;
else if (eFormat == rtl_math_StringFormat_G1)
eFormat = rtl_math_StringFormat_E1;
}
else
{
if (nOrigDigits <= nDecPlaces && aParts.exponent >= 0 && fValue < 0x1p53)
{
// Use integer representation with highest accuracy.
nRoundDigits = nOrigDigits; // no rounding
}
nDecPlaces = std::max<sal_Int32>(0, nDecPlaces - nExp - 1);
eFormat = rtl_math_StringFormat_F;
}
break;
default:
break;
}
// Too large values for nDecPlaces make no sense; it might also be
// rtl_math_DecimalPlaces_Max was passed with rtl_math_StringFormat_F or
// others, but we don't want to allocate/deallocate 2GB just to fill it
// with trailing '0' characters..
nDecPlaces = std::clamp<sal_Int32>(nDecPlaces, -309, 309);
sal_Int32 nDigits = nDecPlaces + 1;
if (eFormat == rtl_math_StringFormat_F)
nDigits += nExp;
// Round the number
nRoundDigits = std::min<int>(nDigits, nRoundDigits);
if (nDigits >= 0 && nOrigDigits > nRoundDigits)
{
aParts.significand = roundToPow10(aParts.significand, nOrigDigits - nRoundDigits);
assert(aParts.significand <= eX[nOrigDigits - 1]);
if (aParts.significand == eX[nOrigDigits - 1]) // up-rounding to the next decade
{
nOrigDigits++;
nExp++;
if (eFormat == rtl_math_StringFormat_F)
nDigits++;
}
}
sal_Int32 nBuf
= (nDigits <= 0 ? std::max<sal_Int32>(nDecPlaces, std::abs(nExp)) : nDigits + nDecPlaces)
+ 10 + (pGroups ? std::abs(nDigits) * 2 : 0);
// max(nDigits) = max(nDecPlaces) + 1 + max(nExp) + 1 = 309 + 1 + 308 + 1 = 619
// max(nBuf) = max(nDigits) + max(nDecPlaces) + 10 + max(nDigits) * 2 = 619 * 3 + 309 + 10 = 2176
assert(nBuf <= 2176);
auto* const pBuf = static_cast<Char_T<rtl_tString>*>(alloca(nBuf * sizeof(Char_T<rtl_tString>)));
auto* p = pBuf;
if (bSign)
*p++ = '-';
bool bHasDec = false;
int nDecPos;
// Check for F format and number < 1
if (eFormat == rtl_math_StringFormat_F)
{
if (nExp < 0)
{
*p++ = '0';
if (nDecPlaces > 0)
{
*p++ = cDecSeparator;
bHasDec = true;
}
sal_Int32 i = (nDigits <= 0 ? nDecPlaces : -nExp - 1);
while ((i--) > 0)
*p++ = '0';
nDecPos = 0;
}
else
nDecPos = nExp + 1;
}
else
nDecPos = 1;
int nGrouping = 0, nGroupSelector = 0, nGroupExceed = 0;
if (nDecPos > 1 && pGroups && pGroups[0] && cGroupSeparator)
{
while (nGrouping + pGroups[nGroupSelector] < nDecPos)
{
nGrouping += pGroups[nGroupSelector];
if (pGroups[nGroupSelector + 1])
{
if (nGrouping + pGroups[nGroupSelector + 1] >= nDecPos)
break; // while
++nGroupSelector;
}
else if (!nGroupExceed)
nGroupExceed = nGrouping;
}
}
// print the number
if (nDigits > 0)
{
for (int nCurExp = nOrigDigits - 1;;)
{
int nDigit;
if (aParts.significand > 0 && nCurExp > 0)
{
--nCurExp;
nDigit = aParts.significand / eX[nCurExp];
aParts.significand %= eX[nCurExp];
}
else
{
nDigit = aParts.significand;
aParts.significand = 0;
}
assert(nDigit >= 0 && nDigit < 10);
*p++ = nDigit + '0';
if (!--nDigits)
break; // for
if (nDecPos)
{
if (!--nDecPos)
{
*p++ = cDecSeparator;
bHasDec = true;
}
else if (nDecPos == nGrouping)
{
*p++ = cGroupSeparator;
nGrouping -= pGroups[nGroupSelector];
if (nGroupSelector && nGrouping < nGroupExceed)
--nGroupSelector;
}
}
}
}
if (!bHasDec && eFormat == rtl_math_StringFormat_F)
{ // nDecPlaces < 0 did round the value
while (--nDecPos > 0)
{ // fill before decimal point
if (nDecPos == nGrouping)
{
*p++ = cGroupSeparator;
nGrouping -= pGroups[nGroupSelector];
if (nGroupSelector && nGrouping < nGroupExceed)
--nGroupSelector;
}
*p++ = '0';
}
}
if (bEraseTrailingDecZeros && bHasDec)
{
while (*(p - 1) == '0')
p--;
if (*(p - 1) == cDecSeparator)
p--;
}
// Print the exponent ('E', followed by '+' or '-', followed by exactly
// three digits for rtl_math_StringFormat_E). The code in
// rtl_[u]str_valueOf{Float|Double} relies on this format.
if (eFormat == rtl_math_StringFormat_E || eFormat == rtl_math_StringFormat_E2
|| eFormat == rtl_math_StringFormat_E1)
{
if (p == pBuf)
*p++ = '1';
// maybe no nDigits if nDecPlaces < 0
*p++ = 'E';
if (nExp < 0)
{
nExp = -nExp;
*p++ = '-';
}
else
*p++ = '+';
if (eFormat == rtl_math_StringFormat_E || nExp >= 100)
*p++ = nExp / 100 + '0';
nExp %= 100;
if (eFormat == rtl_math_StringFormat_E || eFormat == rtl_math_StringFormat_E2 || nExp >= 10)
*p++ = nExp / 10 + '0';
*p++ = nExp % 10 + '0';
}
append(pResult, pResultCapacity, nResultOffset, std::basic_string_view(pBuf, p - pBuf));
}
template <sal_Int32 maxLen, typename C, typename T> sal_Int32 SAL_CALL valueOfFP(C* pStr, T f)
{
assert(pStr);
rtl_String* pResult = nullptr;
doubleToString(&pResult, nullptr, 0, f, rtl_math_StringFormat_G,
maxLen - std::size("-x.E-xxx") + 1, '.', nullptr, 0, true);
const sal_Int32 nLen = pResult->length;
assert(nLen < maxLen);
Copy(pStr, pResult->buffer, nLen + 1);
release(pResult);
return nLen;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */