basic/qa/basic_coverage/test_format_function.bas | 17 +++++ basic/source/sbx/sbxscan.cxx | 72 ++++++++++++----------- 2 files changed, 55 insertions(+), 34 deletions(-)
New commits: commit a3597edb52b671e6095f96c8705cd458a50d8799 Author: Mike Kaganski <[email protected]> AuthorDate: Fri Aug 29 03:48:15 2025 +0500 Commit: Mike Kaganski <[email protected]> CommitDate: Fri Aug 29 18:33:37 2025 +0200 tdf#143182: fix string handling in SbxValue::Format When a value is convertible to number, check if the format string is for numbers of text (in SbxValue::Format). If the value is not convertible to number, handle it according to the format string in printfmtstr. Change-Id: I6e11aeffd8dd8581ebc868762944e9d6d9393514 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/190348 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/basic/qa/basic_coverage/test_format_function.bas b/basic/qa/basic_coverage/test_format_function.bas index d1c51fe791a5..f72e504a2379 100644 --- a/basic/qa/basic_coverage/test_format_function.bas +++ b/basic/qa/basic_coverage/test_format_function.bas @@ -21,6 +21,23 @@ Sub verify_testFormat d = "2024-09-16 17:03:30" TestUtil.AssertEqual(Format(d, "YYYY-MM-DD"), "2024-09-16", "Format(d, ""YYYY-MM-DD"")") TestUtil.AssertEqual(Format("2024-09-16 05:03:30 PM", "hh-mm-ss"), "17-03-30", "Format(""2024-09-16 05:03:30 PM"", ""hh-mm-ss"")") + ' A string that can be converted to number, with a text-format string + TestUtil.AssertEqual(Format("001", "foo @ bar"), "foo 001 bar", "Format(""001"", ""foo @ bar"")") + ' A string that cannot be converted to number, with a text-format string + TestUtil.AssertEqual(Format("baz", "foo @ bar"), "foo baz bar", "Format(""baz"", ""foo @ bar"")") + ' Legacy format strings + ' leading '!': get only the first character of the source string + TestUtil.AssertEqual(Format("abc", "! @"), "a", "Format(""abc"", ""! @"")") + ' leading '\': get as many characters from source string, as in format string until the next '\', padding with spaces as needed + TestUtil.AssertEqual(Format("abcdefgh", "S%"), "abcde", "Format(""abcdefgh"", ""S%"")") + TestUtil.AssertEqual(Format("abcdefgh", "S45"), "abcdef", "Format(""abcdefgh"", ""S45"")") + TestUtil.AssertEqual(Format("abc", "S45\"), "abc ", "Format(""abc"", ""S45\"")") + ' leading '&': get the whole source string unmodified + TestUtil.AssertEqual(Format("abc", "& @"), "abc", "Format(""abc"", ""& @"")") + ' non-leading positions + TestUtil.AssertEqual(Format("abc", "@ !"), "abc !", "Format(""abc"", ""@ !"")") + TestUtil.AssertEqual(Format("abc", "1�5"), "abc", "Format(""abc"", ""1�5"")") + TestUtil.AssertEqual(Format("abc", "@ &"), "abc &", "Format(""abc"", ""@ &"")") Exit Sub errorHandler: diff --git a/basic/source/sbx/sbxscan.cxx b/basic/source/sbx/sbxscan.cxx index 546366af5698..df6e704299b8 100644 --- a/basic/source/sbx/sbxscan.cxx +++ b/basic/source/sbx/sbxscan.cxx @@ -299,39 +299,6 @@ void ImpCvtNum( double nNum, short nPrec, OUString& rRes, bool bCoreString ) rRes = rtl::math::doubleToUString(nNum, rtl_math_StringFormat_Automatic, nPrec, cDecimalSep, true); } -// formatted number output - -static void printfmtstr(std::u16string_view rStr, OUString& rRes, std::u16string_view rFmt) -{ - if (rFmt.empty()) - rFmt = u"&"; - - OUStringBuffer aTemp; - auto pStr = rStr.begin(); - auto pFmt = rFmt.begin(); - - switch( *pFmt ) - { - case '!': - if (pStr != rStr.end()) - aTemp.append(*pStr); - break; - case '\': - do - { - aTemp.append(pStr != rStr.end() ? *pStr++ : u' '); - } while (++pFmt != rFmt.end() && *pFmt != '\'); - aTemp.append(pStr != rStr.end() ? *pStr : u' '); - break; - case '&': - default: - aTemp = rStr; - break; - } - rRes = aTemp.makeStringAndClear(); -} - - bool SbxValue::Scan(std::u16string_view rSrc, sal_Int32* pLen) { ErrCode eRes = ERRCODE_NONE; @@ -527,6 +494,40 @@ std::optional<double> GetNumberIntl(const SbxValue& val, OUString& rStrVal, : std::nullopt; } } + +void printfmtstr(const OUString& rStr, OUString& rRes, const OUString& rFmt) +{ + rRes = rStr; + + switch (rFmt.isEmpty() ? '&' : rFmt[0]) + { + case '!': + if (!rStr.isEmpty()) + rRes = rStr.copy(0, 1); + break; + case '\': + if (auto i = rFmt.indexOf('\', 1) + 1, l = i ? i : rFmt.getLength(); + l < rStr.getLength()) + rRes = rStr.copy(0, l); + else if (l > rStr.getLength()) + rRes += OUString::Concat(RepeatedUChar(' ', l - rStr.getLength())); + break; + case '&': + break; // Already assigned the correct value + default: + if (auto formatter = GetFormatter()) + { + sal_uInt32 nIndex; + formatter->PutandConvertEntry( + // [-loplugin:redundantfcast] a temporary is required here + o3tl::temporary(OUString(rFmt)), o3tl::temporary(sal_Int32()), + o3tl::temporary(SvNumFormatType()), nIndex, LANGUAGE_ENGLISH_US, + Application::GetSettings().GetLanguageTag().getLanguageType(), true); + formatter->GetOutputString(rStr, nIndex, rRes, &o3tl::temporary<const Color*>({})); + } + break; + } +} } // namespace void SbxValue::Format( OUString& rRes, const OUString* pFmt ) const @@ -658,7 +659,10 @@ void SbxValue::Format( OUString& rRes, const OUString* pFmt ) const else { pFormatter->PutandConvertEntry( aFmtStr, nCheckPos, nType, nIndex, LANGUAGE_ENGLISH_US, eLangType, true); - pFormatter->GetOutputString(*number, nIndex, rRes, &pCol); + if (nType == SvNumFormatType::TEXT && IsString()) + pFormatter->GetOutputString(GetOUString(), nIndex, rRes, &pCol); + else + pFormatter->GetOutputString(*number, nIndex, rRes, &pCol); } #endif }
