tdf#115007 add NatNum12 number format list items, fix title case

Add NumberText NatNum12 number formats, e.g. "One Hundred",
and currency formats, e.g. "One U.S. Dollar and Twenty Cents"
to number formatting dialog windows, i.e. Format Cells->Numbers
in Calc and Format Numbers in Writer (Edit Fields->Format->
Additional formats...).

Fix also bad English title case:

"One Euro *and* *Twenty-Two* cents" (not *And* and *Twenty-two*)

Details:

– svl: list NatNum12 NumberText currency codes in Currency
  formats (i.e. after choosing Currency category).

– svx: Recognize bank symbol "CURRENCY" in NatNum12 parameters
  defined in locale resource files. For example,
  "[NatNum12 CURRENCY]" is converted to "[NatNum12 USD]" in
  the number format dialog windows, using bank symbol of
  the current locale settings.

  Recognize compatible (old) bank symbol "CCC" in NatNum12
  parameters defined in locale resource files. For example,
  "[NatNum12 CCC]" is converted to "[NatNum12 DEM]" in
  the number format dialog windows, using bank symbol of
  the compatible currency of the German locale settings.

  User-defined formats with arbitrary bank codes are
  recognized as currency formats, e.g. modifying
  "[NatNum12 USD]" to "[NatNum12 EUR]" in the dialog window
  results a new currency format item.

– i18npool/*en_US.xml: define four Standard NatNum12 formats
  (lower case, sentence case, title case, upper case) and
  four Currency NatNum12 formats (title case, title case with
  digits, upper case, upper case with digits).

– cui: use lower sample numbers for spell out formats:

  – 100 for Standard:

  One Hundred
  one hundred
  One hundred
  ONE HUNDRED

  – 1.2 for Currency:

  One U.S. Dollar
  ONE U.S. DOLLAR
  One U.S. Dollar and Twenty Cents
  ONE U.S. DOLLAR AND TWENTY CENTS

– i18npool: fix English title casing of NatNum12 conversions:

  – Don't apply casing on "and", according to the title
    case rules, for example:

    "One Euro and One Cent" instead of
    "One Euro And One Cent".

  – Apply casing on the second element of the hyphenated
    compound words:

    "Twenty-One" instead of the bad "Twenty-one".

– add unit test for extended Number and Currency categories.

Note: according to the changes, update user-defined number format id in
chart2/qa/extras/chart2dump/reference/chartdatatest/simple_chart.txt

Change-Id: Ieaf9a8f75a4f197b858eaf67f83484df70295834
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/141994
Tested-by: Jenkins
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/chart2/qa/extras/chart2dump/reference/chartdatatest/simple_chart.txt b/chart2/qa/extras/chart2dump/reference/chartdatatest/simple_chart.txt
index 28a125d..ec3bfa7 100644
--- a/chart2/qa/extras/chart2dump/reference/chartdatatest/simple_chart.txt
+++ b/chart2/qa/extras/chart2dump/reference/chartdatatest/simple_chart.txt
@@ -11,7 +11,7 @@ Quarter
// sYAxisTitle
Income (Ft)
// nYAxisNumberFormat
151
159
// nYAxisNumberType
17
// aColumnLabels.getLength()
diff --git a/cui/source/tabpages/numfmt.cxx b/cui/source/tabpages/numfmt.cxx
index ee6d949..54b5b53 100644
--- a/cui/source/tabpages/numfmt.cxx
+++ b/cui/source/tabpages/numfmt.cxx
@@ -1665,6 +1665,16 @@ OUString SvxNumberFormatTabPage::GetExpColorString(
    }
    double fVal = fSvxNumValConst[i];

    // use lower number for long NatNum12 transliteration
    if ( ( CAT_CURRENCY == nTmpCatPos || CAT_NUMBER == nTmpCatPos ) &&
             rFormatStr.indexOf("NatNum12") >= 0 )
    {
        if ( CAT_CURRENCY == nTmpCatPos )
            fVal = 1.2;
        else
            fVal = 100; // show also title case for English: One Hundred
    }

    OUString aPreviewString;
    pNumFmtShell->MakePrevStringFromVal( rFormatStr, aPreviewString, rpPreviewColor, fVal );
    return aPreviewString;
diff --git a/i18npool/source/localedata/data/en_US.xml b/i18npool/source/localedata/data/en_US.xml
index 9e852f7..14c74a7 100644
--- a/i18npool/source/localedata/data/en_US.xml
+++ b/i18npool/source/localedata/data/en_US.xml
@@ -77,6 +77,18 @@
    <FormatElement msgid="FixedFormatskey8" default="false" type="medium" usage="FIXED_NUMBER" formatindex="81">
      <FormatCode>#,##0.00_);(#,##0.00)</FormatCode>
    </FormatElement>
    <FormatElement msgid="FixedFormatskey9" default="false" type="medium" usage="FIXED_NUMBER" formatindex="101">
      <FormatCode>[NatNum12 cardinal]0</FormatCode>
    </FormatElement>
    <FormatElement msgid="FixedFormatskey10" default="false" type="medium" usage="FIXED_NUMBER" formatindex="102">
      <FormatCode>[NatNum12 capitalize cardinal]0</FormatCode>
    </FormatElement>
    <FormatElement msgid="FixedFormatskey11" default="false" type="medium" usage="FIXED_NUMBER" formatindex="103">
      <FormatCode>[NatNum12 title cardinal]0</FormatCode>
    </FormatElement>
    <FormatElement msgid="FixedFormatskey12" default="false" type="medium" usage="FIXED_NUMBER" formatindex="104">
      <FormatCode>[NatNum12 upper cardinal]0</FormatCode>
    </FormatElement>
    <FormatElement msgid="ScientificFormatskey1" default="false" type="medium" usage="SCIENTIFIC_NUMBER" formatindex="6">
      <FormatCode>0.00E+000</FormatCode>
    </FormatElement>
@@ -116,6 +128,18 @@
    <FormatElement msgid="CurrencyFormatskey8" default="false" type="medium" usage="CURRENCY"  formatindex="83">
      <FormatCode>[CURRENCY]* #,##0.00;-[CURRENCY]* #,##0.00</FormatCode>
    </FormatElement>
    <FormatElement msgid="CurrencyFormatskey9" default="false" type="medium" usage="CURRENCY" formatindex="105">
      <FormatCode>[NatNum12 title CURRENCY]0</FormatCode>
    </FormatElement>
    <FormatElement msgid="CurrencyFormatskey10" default="false" type="medium" usage="CURRENCY" formatindex="106">
      <FormatCode>[NatNum12 title CURRENCY]0.00</FormatCode>
    </FormatElement>
    <FormatElement msgid="CurrencyFormatskey11" default="false" type="medium" usage="CURRENCY" formatindex="107">
      <FormatCode>[NatNum12 upper CURRENCY]0</FormatCode>
    </FormatElement>
    <FormatElement msgid="CurrencyFormatskey12" default="false" type="medium" usage="CURRENCY" formatindex="108">
      <FormatCode>[NatNum12 upper CURRENCY]0.00</FormatCode>
    </FormatElement>
    <FormatElement msgid="DateFormatskey1" default="true" type="short" usage="DATE" formatindex="18">
      <FormatCode>M/D/YY</FormatCode>
    </FormatElement>
diff --git a/i18npool/source/nativenumber/nativenumbersupplier.cxx b/i18npool/source/nativenumber/nativenumbersupplier.cxx
index 59c38b3..756866a 100644
--- a/i18npool/source/nativenumber/nativenumbersupplier.cxx
+++ b/i18npool/source/nativenumber/nativenumbersupplier.cxx
@@ -699,7 +699,23 @@ OUString NativeNumberSupplierService::getNativeNumberString(const OUString& aNum
                case LOWER:
                    return xCharClass->toLower(aStr, 0, aStr.getLength(), aLocale);
                case TITLE:
                    return xCharClass->toTitle(aStr, 0, aStr.getLength(), aLocale);
                {
                    if ( rLocale.Language == "en" )
                    {
                        // title case is common in English, so fix bugs of toTitle():
                        // not "One Dollar *And* *Twenty-two* Cents", but
                        // "One Dollar *and* *Twenty-Two* Cents".

                        // Add spaces after hyphens to separate the elements of the
                        // hyphenated compound words temporarily, allowing their
                        // capitalization by toTitle()
                        aStr = aStr.replaceAll("-", "- ");
                        aStr = xCharClass->toTitle(aStr, 0, aStr.getLength(), aLocale);
                        return aStr.replaceAll("- ", "-").replaceAll(" And ", " and ");
                    }
                    else
                        return xCharClass->toTitle(aStr, 0, aStr.getLength(), aLocale);
               }
            }
        }
        else
diff --git a/sc/qa/uitest/calc_tests/formatCells.py b/sc/qa/uitest/calc_tests/formatCells.py
index 4320f75..2d9f369 100644
--- a/sc/qa/uitest/calc_tests/formatCells.py
+++ b/sc/qa/uitest/calc_tests/formatCells.py
@@ -403,6 +403,65 @@ class formatCell(UITestCase):
                xspinDegrees.executeAction("UP", tuple())
                self.assertEqual(get_state_as_dict(xspinDegrees)["Text"].replace('°', ''), "0")

    def test_format_cell_spell_out_numbering(self):
        #numberingformatpage.ui
        with self.ui_test.create_doc_in_start_center("calc"):
            xCalcDoc = self.xUITest.getTopFocusWindow()
            gridwin = xCalcDoc.getChild("grid_window")
            #select cell A1
            gridwin.executeAction("SELECT", mkPropertyValues({"CELL": "A1"}))
            #format - cell
            with self.ui_test.execute_dialog_through_command(".uno:FormatCellDialog") as xDialog:
                xTabs = xDialog.getChild("tabcontrol")
                select_pos(xTabs, "0")  #tab Numbers

                formatlb = xDialog.getChild("formatlb")
                xformatted = xDialog.getChild("formatted")

                # NatNum12 number formats

                entry = formatlb.getChild("6")
                self.assertEqual(get_state_as_dict(entry)["Text"], "ONE HUNDRED")
                entry.executeAction("SELECT", tuple())
                self.assertEqual(get_state_as_dict(xformatted)["Text"], "[NatNum12 upper cardinal]0")

                entry = formatlb.getChild("7")
                self.assertEqual(get_state_as_dict(entry)["Text"], "One Hundred")
                entry.executeAction("SELECT", tuple())
                self.assertEqual(get_state_as_dict(xformatted)["Text"], "[NatNum12 title cardinal]0")

                entry = formatlb.getChild("8")
                self.assertEqual(get_state_as_dict(entry)["Text"], "One hundred")
                entry.executeAction("SELECT", tuple())
                self.assertEqual(get_state_as_dict(xformatted)["Text"], "[NatNum12 capitalize cardinal]0")

                entry = formatlb.getChild("9")
                self.assertEqual(get_state_as_dict(entry)["Text"], "one hundred")
                entry.executeAction("SELECT", tuple())
                self.assertEqual(get_state_as_dict(xformatted)["Text"], "[NatNum12 cardinal]0")

                # NatNum12 en_US currency formats

                categorylb = xDialog.getChild("categorylb")
                entry = categorylb.getChild("4") # Currency
                entry.executeAction("SELECT", tuple())

                currencies = ["ONE U.S. DOLLAR AND TWENTY CENTS", "ONE U.S. DOLLAR", "One U.S. Dollar and Twenty Cents", "One U.S. Dollar"]
                formats = ["[NatNum12 upper USD]0.00", "[NatNum12 upper USD]0", "[NatNum12 title USD]0.00", "[NatNum12 title USD]0"]

                # handle different order of the items
                numCurrency = 0
                numFormat = 0
                for i in formatlb.getChildren():
                    entry = formatlb.getChild(i)
                    if get_state_as_dict(entry)["Text"] in currencies:
                        numCurrency = numCurrency + 1
                    entry.executeAction("SELECT", tuple())
                    xformatted = xDialog.getChild("formatted")
                    if get_state_as_dict(xformatted)["Text"] in formats:
                        numFormat = numFormat + 1

                self.assertEqual(4, numCurrency)
                self.assertEqual(4, numFormat)

# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/svl/source/numbers/zformat.cxx b/svl/source/numbers/zformat.cxx
index 4aa5543..54f3ce6 100644
--- a/svl/source/numbers/zformat.cxx
+++ b/svl/source/numbers/zformat.cxx
@@ -721,6 +721,32 @@ bool NatNumTakesParameters(sal_Int16 nNum)
}
}

// is there a 3-letter bank code in NatNum12 param (but not
// followed by an equal mark, like in the date code "NNN=")?
static bool lcl_isNatNum12Currency( const OUString& sParam )
{
    sal_Int32 nUpper = 0;
    sal_Int32 nLen = sParam.getLength();
    for (sal_Int32 n = 0; n < nLen; ++n)
    {
        sal_Unicode c = sParam[n];
        if ( 'A' <= c && c <= 'Z' )
        {
            ++nUpper;
        }
        else if ( c == ' ' && nUpper == 3 && (n == 3 || sParam[n - 4] == ' ') )
        {
            return true;
        }
        else
        {
            nUpper = 0;
        }
    }

    return nUpper == 3 && (nLen == 3 || sParam[nLen - 4] == ' ');
}

SvNumberformat::SvNumberformat(OUString& rString,
                               ImpSvNumberformatScan* pSc,
                               ImpSvNumberInputScan* pISc,
@@ -938,6 +964,12 @@ SvNumberformat::SvNumberformat(OUString& rString,
                        {
                            if (sParams.isEmpty())
                                sParams = "cardinal"; // default NatNum12 format is "cardinal"
                            else if (sParams.indexOf("CURRENCY") >= 0)
                                sParams = sParams.replaceAll("CURRENCY",
                                    GetFormatter().GetLocaleData()->getCurrBankSymbol());
                            // compatible (old) currency format
                            else if (sParams.indexOf("CCC") >= 0)
                                sParams = sParams.replaceAll("CCC", rScan.GetCurAbbrev());
                            NumFor[nIndex].SetNatNumParams(sParams);
                            sStr += " " + sParams;
                        }
@@ -1176,7 +1208,11 @@ SvNumberformat::SvNumberformat(OUString& rString,
                        // type check
                        if (nIndex == 0)
                        {
                            eType = NumFor[nIndex].Info().eScannedType;
                            if ( NumFor[nIndex].GetNatNum().GetNatNum() == 12 &&
                                    lcl_isNatNum12Currency(NumFor[nIndex].GetNatNum().GetParams()) )
                                eType = SvNumFormatType::CURRENCY;
                            else
                                eType = NumFor[nIndex].Info().eScannedType;
                        }
                        else if (nIndex == 3)
                        {   // #77026# Everything recognized IS text
diff --git a/svx/source/items/numfmtsh.cxx b/svx/source/items/numfmtsh.cxx
index 40811d8..1aa58a32 100644
--- a/svx/source/items/numfmtsh.cxx
+++ b/svx/source/items/numfmtsh.cxx
@@ -546,8 +546,7 @@ short SvxNumberFormatShell::FillEntryList_Impl(std::vector<OUString>& rList)
    else
    {
        FillEListWithStd_Impl(rList, nCurCategory, nSelPos, true);
        if (nCurCategory != SvNumFormatType::CURRENCY)
            nSelPos = FillEListWithUsD_Impl(rList, nCurCategory, nSelPos);
        nSelPos = FillEListWithUsD_Impl(rList, nCurCategory, nSelPos);
        if (nCurCategory == SvNumFormatType::DATE || nCurCategory == SvNumFormatType::TIME)
            nSelPos = FillEListWithDateTime_Impl(rList, nSelPos, true);
    }
@@ -1038,6 +1037,7 @@ short SvxNumberFormatShell::FillEListWithUsD_Impl(std::vector<OUString>& rList,

    const bool bCatDefined = (eCategory == SvNumFormatType::DEFINED);
    const bool bCategoryMatch = (eCategory != SvNumFormatType::ALL && !bCatDefined);
    const bool bNatNumCurrency = (eCategory == SvNumFormatType::CURRENCY);

    for (const auto& rEntry : *pCurFmtTable)
    {
@@ -1058,6 +1058,9 @@ short SvxNumberFormatShell::FillEListWithUsD_Impl(std::vector<OUString>& rList,
        {
            aNewFormNInfo = pNumEntry->GetFormatstring();

            if (bNatNumCurrency && (aNewFormNInfo.indexOf("NatNum12") < 0 || bUserDefined))
                continue; // for; extra CURRENCY must be not user-defined NatNum12 type

            bool bAdd = true;
            if (pNumEntry->HasNewCurrency())
            {
@@ -1315,7 +1318,10 @@ OUString SvxNumberFormatShell::GetFormat4Entry(short nEntry)
    if (nEntry < 0)
        return OUString();

    if (!aCurrencyFormatList.empty())
    if (!aCurrencyFormatList.empty()
        && (!pFormatter->GetEntry(aCurEntryList[nEntry])
            || pFormatter->GetEntry(aCurEntryList[nEntry])->GetFormatstring().indexOf("NatNum12")
                   < 0))
    {
        if (aCurrencyFormatList.size() > o3tl::make_unsigned(nEntry))
            return aCurrencyFormatList[nEntry];