numbertest_affixutils.cpp   [plain text]


// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING

#include "putilimp.h"
#include "unicode/dcfmtsym.h"
#include "numbertest.h"
#include "number_utils.h"

using namespace icu::number::impl;

class DefaultSymbolProvider : public SymbolProvider {
    DecimalFormatSymbols fSymbols;

  public:
    DefaultSymbolProvider(UErrorCode &status) : fSymbols(Locale("ar_SA"), status) {}

    UnicodeString getSymbol(AffixPatternType type) const U_OVERRIDE {
        switch (type) {
            case TYPE_MINUS_SIGN:
                return u"−";
            case TYPE_PLUS_SIGN:
                return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol);
            case TYPE_PERCENT:
                return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPercentSymbol);
            case TYPE_PERMILLE:
                return fSymbols.getConstSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPerMillSymbol);
            case TYPE_CURRENCY_SINGLE:
                return u"$";
            case TYPE_CURRENCY_DOUBLE:
                return u"XXX";
            case TYPE_CURRENCY_TRIPLE:
                return u"long name";
            case TYPE_CURRENCY_QUAD:
                return u"\uFFFD";
            case TYPE_CURRENCY_QUINT:
                // TODO: Add support for narrow currency symbols here.
                return u"\uFFFD";
            case TYPE_CURRENCY_OVERFLOW:
                return u"\uFFFD";
            default:
                UPRV_UNREACHABLE;
        }
    }
};

void AffixUtilsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, char *) {
    if (exec) {
        logln("TestSuite AffixUtilsTest: ");
    }
    TESTCASE_AUTO_BEGIN;
        TESTCASE_AUTO(testEscape);
        TESTCASE_AUTO(testUnescape);
        TESTCASE_AUTO(testContainsReplaceType);
        TESTCASE_AUTO(testInvalid);
        TESTCASE_AUTO(testUnescapeWithSymbolProvider);
    TESTCASE_AUTO_END;
}

void AffixUtilsTest::testEscape() {
    static const char16_t *cases[][2] = {{u"", u""},
                                         {u"abc", u"abc"},
                                         {u"-", u"'-'"},
                                         {u"-!", u"'-'!"},
                                         {u"−", u"−"},
                                         {u"---", u"'---'"},
                                         {u"-%-", u"'-%-'"},
                                         {u"'", u"''"},
                                         {u"-'", u"'-'''"},
                                         {u"-'-", u"'-''-'"},
                                         {u"a-'-", u"a'-''-'"}};

    for (auto &cas : cases) {
        UnicodeString input(cas[0]);
        UnicodeString expected(cas[1]);
        UnicodeString result = AffixUtils::escape(input);
        assertEquals(input, expected, result);
    }
}

void AffixUtilsTest::testUnescape() {
    static struct TestCase {
        const char16_t *input;
        bool currency;
        int32_t expectedLength;
        const char16_t *output;
    } cases[] = {{u"", false, 0, u""},
                 {u"abc", false, 3, u"abc"},
                 {u"-", false, 1, u"−"},
                 {u"-!", false, 2, u"−!"},
                 {u"+", false, 1, u"\u061C+"},
                 {u"+!", false, 2, u"\u061C+!"},
                 {u"‰", false, 1, u"؉"},
                 {u"‰!", false, 2, u"؉!"},
                 {u"-x", false, 2, u"−x"},
                 {u"'-'x", false, 2, u"-x"},
                 {u"'--''-'-x", false, 6, u"--'-−x"},
                 {u"''", false, 1, u"'"},
                 {u"''''", false, 2, u"''"},
                 {u"''''''", false, 3, u"'''"},
                 {u"''x''", false, 3, u"'x'"},
                 {u"¤", true, 1, u"$"},
                 {u"¤¤", true, 2, u"XXX"},
                 {u"¤¤¤", true, 3, u"long name"},
                 {u"¤¤¤¤", true, 4, u"\uFFFD"},
                 {u"¤¤¤¤¤", true, 5, u"\uFFFD"},
                 {u"¤¤¤¤¤¤", true, 6, u"\uFFFD"},
                 {u"¤¤¤a¤¤¤¤", true, 8, u"long namea\uFFFD"},
                 {u"a¤¤¤¤b¤¤¤¤¤c", true, 12, u"a\uFFFDb\uFFFDc"},
                 {u"¤!", true, 2, u"$!"},
                 {u"¤¤!", true, 3, u"XXX!"},
                 {u"¤¤¤!", true, 4, u"long name!"},
                 {u"-¤¤", true, 3, u"−XXX"},
                 {u"¤¤-", true, 3, u"XXX−"},
                 {u"'¤'", false, 1, u"¤"},
                 {u"%", false, 1, u"٪\u061C"},
                 {u"'%'", false, 1, u"%"},
                 {u"¤'-'%", true, 3, u"$-٪\u061C"},
                 {u"#0#@#*#;#", false, 9, u"#0#@#*#;#"}};

    UErrorCode status = U_ZERO_ERROR;
    DefaultSymbolProvider defaultProvider(status);
    assertSuccess("Constructing DefaultSymbolProvider", status);

    for (TestCase cas : cases) {
        UnicodeString input(cas.input);
        UnicodeString output(cas.output);

        assertEquals(input, cas.currency, AffixUtils::hasCurrencySymbols(input, status));
        assertSuccess("Spot 1", status);
        assertEquals(input, cas.expectedLength, AffixUtils::estimateLength(input, status));
        assertSuccess("Spot 2", status);

        UnicodeString actual = unescapeWithDefaults(defaultProvider, input, status);
        assertSuccess("Spot 3", status);
        assertEquals(input, output, actual);

        int32_t ulength = AffixUtils::unescapedCodePointCount(input, defaultProvider, status);
        assertSuccess("Spot 4", status);
        assertEquals(input, output.countChar32(), ulength);
    }
}

void AffixUtilsTest::testContainsReplaceType() {
    static struct TestCase {
        const char16_t *input;
        bool hasMinusSign;
        const char16_t *output;
    } cases[] = {{u"", false, u""},
                 {u"-", true, u"+"},
                 {u"-a", true, u"+a"},
                 {u"a-", true, u"a+"},
                 {u"a-b", true, u"a+b"},
                 {u"--", true, u"++"},
                 {u"x", false, u"x"}};

    UErrorCode status = U_ZERO_ERROR;
    for (TestCase cas : cases) {
        UnicodeString input(cas.input);
        bool hasMinusSign = cas.hasMinusSign;
        UnicodeString output(cas.output);

        assertEquals(
                input, hasMinusSign, AffixUtils::containsType(input, TYPE_MINUS_SIGN, status));
        assertSuccess("Spot 1", status);
        assertEquals(
                input, output, AffixUtils::replaceType(input, TYPE_MINUS_SIGN, u'+', status));
        assertSuccess("Spot 2", status);
    }
}

void AffixUtilsTest::testInvalid() {
    static const char16_t *invalidExamples[] = {
            u"'", u"x'", u"'x", u"'x''", u"''x'"};

    UErrorCode status = U_ZERO_ERROR;
    DefaultSymbolProvider defaultProvider(status);
    assertSuccess("Constructing DefaultSymbolProvider", status);

    for (const char16_t *strPtr : invalidExamples) {
        UnicodeString str(strPtr);

        status = U_ZERO_ERROR;
        AffixUtils::hasCurrencySymbols(str, status);
        assertEquals("Should set error code spot 1", status, U_ILLEGAL_ARGUMENT_ERROR);

        status = U_ZERO_ERROR;
        AffixUtils::estimateLength(str, status);
        assertEquals("Should set error code spot 2", status, U_ILLEGAL_ARGUMENT_ERROR);

        status = U_ZERO_ERROR;
        unescapeWithDefaults(defaultProvider, str, status);
        assertEquals("Should set error code spot 3", status, U_ILLEGAL_ARGUMENT_ERROR);
    }
}

class NumericSymbolProvider : public SymbolProvider {
  public:
    virtual UnicodeString getSymbol(AffixPatternType type) const {
        return Int64ToUnicodeString(type < 0 ? -type : type);
    }
};

void AffixUtilsTest::testUnescapeWithSymbolProvider() {
    static const char16_t* cases[][2] = {
            {u"", u""},
            {u"-", u"1"},
            {u"'-'", u"-"},
            {u"- + % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", u"1 2 3 4 5 6 7 8 9"},
            {u"'¤¤¤¤¤¤'", u"¤¤¤¤¤¤"},
            {u"¤¤¤¤¤¤", u"\uFFFD"}
    };

    NumericSymbolProvider provider;

    UErrorCode status = U_ZERO_ERROR;
    FormattedStringBuilder sb;
    for (auto& cas : cases) {
        UnicodeString input(cas[0]);
        UnicodeString expected(cas[1]);
        sb.clear();
        AffixUtils::unescape(input, sb, 0, provider, UNUM_FIELD_COUNT, status);
        assertSuccess("Spot 1", status);
        assertEquals(input, expected, sb.toUnicodeString());
        assertEquals(input, expected, sb.toTempUnicodeString());
    }

    // Test insertion position
    sb.clear();
    sb.append(u"abcdefg", UNUM_FIELD_COUNT, status);
    assertSuccess("Spot 2", status);
    AffixUtils::unescape(u"-+%", sb, 4, provider, UNUM_FIELD_COUNT, status);
    assertSuccess("Spot 3", status);
    assertEquals(u"Symbol provider into middle", u"abcd123efg", sb.toUnicodeString());
}

UnicodeString AffixUtilsTest::unescapeWithDefaults(const SymbolProvider &defaultProvider,
                                                          UnicodeString input, UErrorCode &status) {
    FormattedStringBuilder nsb;
    int32_t length = AffixUtils::unescape(input, nsb, 0, defaultProvider, UNUM_FIELD_COUNT, status);
    assertEquals("Return value of unescape", nsb.length(), length);
    return nsb.toUnicodeString();
}

#endif /* #if !UCONFIG_NO_FORMATTING */