Resolves: tdf#122191 BOOLEAN is a keyword, treat it as such

... to not end up with "BOOL"E"AN" instead, which is a date type
with an era year and literal strings.

This never worked but only was uncovered by

    commit ce4fc2fc08be8ea2773194e303ed42d2579e93a0
    CommitDate: Fri Mar 2 20:27:45 2018 +0100

        Resolves: tdf#115351 convert boolean equivalent format codes to proper Boolean

if the format also had to be converted between locales.

Also preserve boolean equivalent formats during Excel export and
try hard to convert back as much as possible if a literal boolean
string format is a Boolean equivalent of the target locale.

Change-Id: I54f65c276cbf7bb99e960b6d7053c5fa95fbccb6
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/118591
Reviewed-by: Eike Rathke <erack@redhat.com>
Tested-by: Jenkins
(cherry picked from commit 7a58221f800e215934cbcb2d3907c35b44981611)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/118510
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/include/svl/nfkeytab.hxx b/include/svl/nfkeytab.hxx
index fe1e3bb..3b814049 100644
--- a/include/svl/nfkeytab.hxx
+++ b/include/svl/nfkeytab.hxx
@@ -71,13 +71,13 @@ enum NfKeywordIndex
    NF_KEY_WW,          // week of year, as of version 8, 19.06.98
    NF_KEY_THAI_T,      // Thai T modifier, speciality of Thai Excel, only used with Thai locale and converted to [NatNum1]
    NF_KEY_CCC,         // currency bank symbol (old version)
    NF_KEY_BOOLEAN,     // boolean
    NF_KEY_GENERAL,     // General / Standard
    NF_KEY_LASTKEYWORD = NF_KEY_GENERAL,

    // Reserved words translated and color names follow:
    NF_KEY_TRUE,        // boolean true
    NF_KEY_FALSE,       // boolean false
    NF_KEY_BOOLEAN,     // boolean
    NF_KEY_COLOR,       // color
    NF_KEY_FIRSTCOLOR,
    NF_KEY_BLACK = NF_KEY_FIRSTCOLOR,   // you do know colors, don't you?
diff --git a/include/svl/zforlist.hxx b/include/svl/zforlist.hxx
index 7a870387..c3d82d7 100644
--- a/include/svl/zforlist.hxx
+++ b/include/svl/zforlist.hxx
@@ -473,7 +473,7 @@ public:
            nKey contains the index key of the format.
     */
    bool PutEntry( OUString& rString, sal_Int32& nCheckPos, SvNumFormatType& nType, sal_uInt32& nKey,
                   LanguageType eLnge = LANGUAGE_DONTKNOW );
                   LanguageType eLnge = LANGUAGE_DONTKNOW, bool bReplaceBooleanEquivalent = true );

    /** Same as <method>PutEntry</method> but the format code string is
         considered to be of language/country eLnge and is converted to
@@ -481,7 +481,7 @@ public:
    bool PutandConvertEntry( OUString& rString, sal_Int32& nCheckPos,
                             SvNumFormatType& nType, sal_uInt32& nKey,
                             LanguageType eLnge, LanguageType eNewLnge,
                             bool bConvertDateOrder );
                             bool bConvertDateOrder, bool bReplaceBooleanEquivalent = true );

    /** Same as <method>PutandConvertEntry</method> but the format code string
         is considered to be of the System language/country eLnge and is
diff --git a/include/svl/zformat.hxx b/include/svl/zformat.hxx
index 543c596..9764882 100644
--- a/include/svl/zformat.hxx
+++ b/include/svl/zformat.hxx
@@ -171,7 +171,8 @@ public:
                   ImpSvNumberformatScan* pSc,
                   ImpSvNumberInputScan* pISc,
                   sal_Int32& nCheckPos,
                   LanguageType& eLan );
                   LanguageType& eLan,
                   bool bReplaceBooleanEquivalent = true );

    // Copy ctor
    SvNumberformat( SvNumberformat const & rFormat );
diff --git a/svl/source/numbers/zforlist.cxx b/svl/source/numbers/zforlist.cxx
index b1885ad..0a2f699 100644
--- a/svl/source/numbers/zforlist.cxx
+++ b/svl/source/numbers/zforlist.cxx
@@ -569,7 +569,8 @@ bool SvNumberFormatter::PutEntry(OUString& rString,
                                 sal_Int32& nCheckPos,
                                 SvNumFormatType& nType,
                                 sal_uInt32& nKey,      // format key
                                 LanguageType eLnge)
                                 LanguageType eLnge,
                                 bool bReplaceBooleanEquivalent)
{
    ::osl::MutexGuard aGuard( GetInstanceMutex() );
    nKey = 0;
@@ -589,7 +590,8 @@ bool SvNumberFormatter::PutEntry(OUString& rString,
                                                               pFormatScanner.get(),
                                                               pStringScanner.get(),
                                                               nCheckPos,
                                                               eLge));
                                                               eLge,
                                                               bReplaceBooleanEquivalent));

    if (nCheckPos == 0)                         // Format ok
    {                                           // Type comparison:
@@ -637,7 +639,8 @@ bool SvNumberFormatter::PutandConvertEntry(OUString& rString,
                                           sal_uInt32& nKey,
                                           LanguageType eLnge,
                                           LanguageType eNewLnge,
                                           bool bConvertDateOrder )
                                           bool bConvertDateOrder,
                                           bool bReplaceBooleanEquivalent )
{
    ::osl::MutexGuard aGuard( GetInstanceMutex() );
    bool bRes;
@@ -646,8 +649,43 @@ bool SvNumberFormatter::PutandConvertEntry(OUString& rString,
        eNewLnge = IniLnge;
    }
    pFormatScanner->SetConvertMode(eLnge, eNewLnge, false, bConvertDateOrder);
    bRes = PutEntry(rString, nCheckPos, nType, nKey, eLnge);
    bRes = PutEntry(rString, nCheckPos, nType, nKey, eLnge, bReplaceBooleanEquivalent);
    pFormatScanner->SetConvertMode(false);

    if (bReplaceBooleanEquivalent && nType == SvNumFormatType::DEFINED && nCheckPos == 0
            && nKey != NUMBERFORMAT_ENTRY_NOT_FOUND)
    {
        // The boolean string formats are always "user defined" without any
        // other type.
        const SvNumberformat* pEntry = GetFormatEntry(nKey);
        if (pEntry && pEntry->GetType() == SvNumFormatType::DEFINED)
        {
            // Replace boolean string format with Boolean in target locale, in
            // case the source strings are the target locale's.
            const OUString aSaveString = rString;
            ChangeIntl(eNewLnge);
            if (pFormatScanner->ReplaceBooleanEquivalent( rString))
            {
                const sal_Int32 nSaveCheckPos = nCheckPos;
                const SvNumFormatType nSaveType = nType;
                const sal_uInt32 nSaveKey = nKey;
                const bool bTargetRes = PutEntry(rString, nCheckPos, nType, nKey, eNewLnge, false);
                if (nCheckPos == 0 && nType == SvNumFormatType::LOGICAL && nKey != NUMBERFORMAT_ENTRY_NOT_FOUND)
                {
                    bRes = bTargetRes;
                }
                else
                {
                    SAL_WARN("svl.numbers", "SvNumberFormatter::PutandConvertEntry: can't scan boolean replacement");
                    // Live with the source boolean string format.
                    rString = aSaveString;
                    nCheckPos = nSaveCheckPos;
                    nType = nSaveType;
                    nKey = nSaveKey;
                }
            }
        }
    }
    return bRes;
}

@@ -838,6 +876,20 @@ void SvNumberFormatter::FillKeywordTableForExcel( NfKeywordTable& rKeywords )
}


static OUString lcl_buildBooleanStringFormat( SvNumberformat* pEntry )
{
    // Build Boolean number format, which needs non-zero and zero subformat
    // codes with TRUE and FALSE strings.
    const Color* pColor = nullptr;
    OUString aFormatStr, aTemp;
    pEntry->GetOutputString( 1.0, aTemp, &pColor );
    aFormatStr += "\"" + aTemp + "\";\"" + aTemp + "\";\"";
    pEntry->GetOutputString( 0.0, aTemp, &pColor );
    aFormatStr += aTemp + "\"";
    return aFormatStr;
}


OUString SvNumberFormatter::GetFormatStringForExcel( sal_uInt32 nKey, const NfKeywordTable& rKeywords,
        SvNumberFormatter& rTempFormatter ) const
{
@@ -847,14 +899,13 @@ OUString SvNumberFormatter::GetFormatStringForExcel( sal_uInt32 nKey, const NfKe
    {
        if (pEntry->GetType() == SvNumFormatType::LOGICAL)
        {
            // Build Boolean number format, which needs non-zero and zero
            // subformat codes with TRUE and FALSE strings.
            const Color* pColor = nullptr;
            OUString aTemp;
            const_cast< SvNumberformat* >( pEntry )->GetOutputString( 1.0, aTemp, &pColor );
            aFormatStr += "\"" + aTemp + "\";\"" + aTemp + "\";\"";
            const_cast< SvNumberformat* >( pEntry )->GetOutputString( 0.0, aTemp, &pColor );
            aFormatStr += aTemp + "\"";
            // Build a source locale dependent string boolean. This is
            // expected when loading the document in the same locale or if
            // several locales are used, but not for other system/default
            // locales. You can't have both. We could force to English for all
            // locales like below, but Excel would display English strings then
            // even for the system locale matching this locale. YMMV.
            aFormatStr = lcl_buildBooleanStringFormat( const_cast< SvNumberformat* >(pEntry));
        }
        else
        {
@@ -871,7 +922,10 @@ OUString SvNumberFormatter::GetFormatStringForExcel( sal_uInt32 nKey, const NfKe
                SvNumFormatType nType = SvNumFormatType::DEFINED;
                sal_uInt32 nTempKey;
                OUString aTemp( pEntry->GetFormatstring());
                rTempFormatter.PutandConvertEntry( aTemp, nCheckPos, nType, nTempKey, nLang, LANGUAGE_ENGLISH_US, false);
                /* TODO: do we want bReplaceBooleanEquivalent=true in any case
                 * to write it as English string boolean? */
                rTempFormatter.PutandConvertEntry( aTemp, nCheckPos, nType, nTempKey, nLang, LANGUAGE_ENGLISH_US,
                        false /*bConvertDateOrder*/, false /*bReplaceBooleanEquivalent*/);
                SAL_WARN_IF( nCheckPos != 0, "svl.numbers",
                        "SvNumberFormatter::GetFormatStringForExcel - format code not convertible");
                if (nTempKey != NUMBERFORMAT_ENTRY_NOT_FOUND)
@@ -880,12 +934,24 @@ OUString SvNumberFormatter::GetFormatStringForExcel( sal_uInt32 nKey, const NfKe

            if (pEntry)
            {
                // GetLocaleData() returns the current locale's data, so switch
                // before (which doesn't do anything if it was the same locale
                // already).
                rTempFormatter.ChangeIntl( LANGUAGE_ENGLISH_US);
                aFormatStr = pEntry->GetMappedFormatstring( rKeywords, *rTempFormatter.GetLocaleData(), nLang,
                        bSystemLanguage);
                if (pEntry->GetType() == SvNumFormatType::LOGICAL)
                {
                    // This would be reached if bReplaceBooleanEquivalent was
                    // true and the source format is a string boolean like
                    // >"VRAI";"VRAI";"FAUX"< recognized as real boolean and
                    // properly converted. Then written as
                    // >"TRUE";"TRUE";"FALSE"<
                    aFormatStr = lcl_buildBooleanStringFormat( const_cast< SvNumberformat* >(pEntry));
                }
                else
                {
                    // GetLocaleData() returns the current locale's data, so switch
                    // before (which doesn't do anything if it was the same locale
                    // already).
                    rTempFormatter.ChangeIntl( LANGUAGE_ENGLISH_US);
                    aFormatStr = pEntry->GetMappedFormatstring( rKeywords, *rTempFormatter.GetLocaleData(), nLang,
                            bSystemLanguage);
                }
            }
        }
    }
diff --git a/svl/source/numbers/zformat.cxx b/svl/source/numbers/zformat.cxx
index 19ee213..d14bf73 100644
--- a/svl/source/numbers/zformat.cxx
+++ b/svl/source/numbers/zformat.cxx
@@ -722,12 +722,14 @@ SvNumberformat::SvNumberformat(OUString& rString,
                               ImpSvNumberformatScan* pSc,
                               ImpSvNumberInputScan* pISc,
                               sal_Int32& nCheckPos,
                               LanguageType& eLan)
                               LanguageType& eLan,
                               bool bReplaceBooleanEquivalent)
        : rScan(*pSc)
        , bAdditionalBuiltin( false )
        , bStarFlag( false )
{
    rScan.ReplaceBooleanEquivalent( rString);
    if (bReplaceBooleanEquivalent)
        rScan.ReplaceBooleanEquivalent( rString);

    OUStringBuffer sBuff(rString);

diff --git a/svl/source/numbers/zforscan.cxx b/svl/source/numbers/zforscan.cxx
index c3f97d8..f6cdfdb5 100644
--- a/svl/source/numbers/zforscan.cxx
+++ b/svl/source/numbers/zforscan.cxx
@@ -85,12 +85,12 @@ const NfKeywordTable ImpSvNumberformatScan::sEnglishKeyword =
                // used with Thai locale and converted to [NatNum1], only
                // exception as lowercase
    "CCC",     // NF_KEY_CCC Currency abbreviation
    "BOOLEAN", // NF_KEY_BOOLEAN boolean
    "GENERAL", // NF_KEY_GENERAL General / Standard

    // Reserved words translated and color names follow:
    "TRUE",    // NF_KEY_TRUE boolean true
    "FALSE",   // NF_KEY_FALSE boolean false
    "BOOLEAN", // NF_KEY_BOOLEAN boolean
    "COLOR",   // NF_KEY_COLOR color
        // colours
    "BLACK",   // NF_KEY_BLACK
@@ -1353,7 +1353,10 @@ sal_Int32 ImpSvNumberformatScan::ScanType()
            case NF_KEY_CCC:                        // CCC
                eNewType = SvNumFormatType::CURRENCY;
                break;
            case NF_KEY_GENERAL:                    // Standard
            case NF_KEY_BOOLEAN:                    // BOOLEAN
                eNewType = SvNumFormatType::LOGICAL;
                break;
            case NF_KEY_GENERAL:                    // General
                eNewType = SvNumFormatType::NUMBER;
                bHaveGeneral = true;
                break;
@@ -3274,14 +3277,18 @@ void ImpSvNumberformatScan::CopyInfo(ImpSvNumberformatInfo* pInfo, sal_uInt16 nC
    pInfo->nCntExp      = nCntExp;
}

void ImpSvNumberformatScan::ReplaceBooleanEquivalent( OUString& rString )
bool ImpSvNumberformatScan::ReplaceBooleanEquivalent( OUString& rString )
{
    InitKeywords();
    /* TODO: compare case insensitive? Or rather leave as is and case not
     * matching indicates user supplied on purpose? Written to file / generated
     * was always uppercase. */
    if (rString == sBooleanEquivalent1 || rString == sBooleanEquivalent2)
    {
        rString = GetKeywords()[NF_KEY_BOOLEAN];
        return true;
    }
    return false;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svl/source/numbers/zforscan.hxx b/svl/source/numbers/zforscan.hxx
index 95f5a0b9..3f0cea9 100644
--- a/svl/source/numbers/zforscan.hxx
+++ b/svl/source/numbers/zforscan.hxx
@@ -146,7 +146,7 @@ public:
        }

    /// Replace Boolean equivalent format codes with proper Boolean format.
    void ReplaceBooleanEquivalent( OUString& rString );
    bool ReplaceBooleanEquivalent( OUString& rString );

    void SetConvertMode(LanguageType eTmpLge, LanguageType eNewLge,
            bool bSystemToSystem, bool bConvertDateOrder)