mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-04 13:05:31 +00:00
Some checks failed
GHA ICU4J / icu4j-mvn-build-and-test (17) (push) Blocked by required conditions
GHA ICU4J / icu4j-mvn-build-and-test (21) (push) Blocked by required conditions
GHA ICU4J / icu4j-mvn-build-and-test (8) (push) Blocked by required conditions
GHA ICU4J / adaboost-icu4j-build-and-test (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm zzzz" "13:13 Pacific Standard Time" 3, TestICUConstruction) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm zzzz" "13:13 Pacific Standard Time" 3, TestICUFormat) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm zzzz" "13:13 Pacific Standard Time" 3, TestICUParse) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm" "13:13" 2, TestICUConstruction) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm" "13:13" 2, TestICUFormat) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (sw_KE, "dddd MMM yyyy" "15 Jan 2007" 1, TestICUConstruction) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (sw_KE, "dddd MMM yyyy" "15 Jan 2007" 1, TestICUFormat) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-little-endian-data-test (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests (charperf, TestIsAlpha TestIsUpper TestIsLower TestIsDigit TestIsSpace TestIsAlphaNumeric TestIsPrint TestIsControl TestToLower TestToUpper TestIsWhiteSpace) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Chinese, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Chinese, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Chinese, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Japanese, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-decimalformatperf (de_DE, TestICUConstruction) (push) Blocked by required conditions
GHA ICU4J / lstm-icu4j-build-and-test (push) Blocked by required conditions
GHA ICU4J / icu4j-mvn-init-cache (push) Waiting to run
GHA ICU4J / icu4j-mvn-build-and-test (11) (push) Blocked by required conditions
ICU Common / copyright-scan (push) Waiting to run
ICU Common / valid-UTF-8-and-no-BOM-check (push) Waiting to run
ICU Common / icu4j-mvn-init-cache (push) Waiting to run
ICU Common / icu4c-release-tools (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm z" "13:13 PST" 4, TestICUConstruction) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm z" "13:13 PST" 4, TestICUFormat) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm z" "13:13 PST" 4, TestICUParse) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm" "13:13" 2, TestICUParse) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "dddd MMM yyyy" "15 Jan 2007" 1, TestICUConstruction) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "dddd MMM yyyy" "15 Jan 2007" 1, TestICUFormat) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "dddd MMM yyyy" "15 Jan 2007" 1, TestICUParse) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-mvn-init-cache (push) Waiting to run
GHA ICU Merge CI / icu4c-store-perf-libs (push) Waiting to run
GHA ICU Merge CI / icu4c-performance-tests (-f ../../icu4j/perf-tests/data/conversion/xuzhimo.txt, -e gb18030, utfperf, Roundtrip FromUnicode FromUTF8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (sw_KE, "dddd MMM yyyy" "15 Jan 2007" 1, TestICUParse) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests (usetperf, titlecase_letter_add titlecase_letter_contains titlecase_letter_iterator unassigned_add unassigned_contains unassigned_iterator pattern1 pattern2 pattern3) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Asian, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Asian, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Asian, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Japanese_k, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-strsrchperf (udhr_cmn_hans, zh) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-strsrchperf (udhr_deu_1996, de) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-unicodesetperf (UnicodeSetContains) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-unicodesetperf (UnicodeSetIterate) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFC_NFC_Text, TestNames_SerbianSH) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFC_Orig_Text, TestNames_Asian) (push) Blocked by required conditions
GHA ICU Merge CI / Copy perf data to remote repo for visualization (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFC_Orig_Text, TestNames_Chinese) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFC_Orig_Text, TestNames_SerbianSH) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_NFC_Text, TestNames_Asian) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_NFC_Text, TestNames_Chinese) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, arabic, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, english, US-ASCII) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, english, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, french, UTF-16BE) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, french, UTF-16LE) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, french, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, french, csisolatin1) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Japanese, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Japanese_h, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Japanese_h, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Japanese_h, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Japanese_k, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Japanese_k, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Korean, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Korean, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Korean, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Latin, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Latin, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Latin, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Russian, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Russian, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Russian, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_SerbianSH, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_SerbianSH, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_SerbianSH, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_SerbianSR, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_SerbianSR, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_SerbianSR, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Simplified_Chinese, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Simplified_Chinese, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Thai, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Thai, collperf, TestIcu_KeyGen_null TestIcu_qsort_strcoll_null TestIcu_qsort_usekey TestIcu_BinarySearch_strcoll_null TestIcu_BinarySearch_usekey) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (TestNames_Thai, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (th18057, -l -u, ustrperf, TestCtor TestCtor1 TestCtor2 TestCtor3 TestAssign TestAssign1 TestAssign2 TestGetch TestCatenate TestScan TestScan1 TestScan2) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (th18057, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (thesis, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-performance-tests-with-files (vfear11a, normperf, TestICU_NFC_NFD_Text TestICU_NFC_NFC_Text TestICU_NFC_Orig_Text TestICU_NFD_NFD_Text TestICU_NFD_NFC_Text TestICU_NFD_Orig_Text) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-strsrchperf (udhr_eng, en) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-strsrchperf (udhr_fra, fr) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-strsrchperf (udhr_jpn, ja) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-strsrchperf (udhr_rus, ru) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-strsrchperf (udhr_tha, th) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-unicodesetperf (UnicodeSetAdd) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-ucharacterperf (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-decimalformatperf (de_DE, TestICUFormat) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-decimalformatperf (de_DE, TestICUParse) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-decimalformatperf (en_US, TestICUConstruction) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-decimalformatperf (en_US, TestICUFormat) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-decimalformatperf (en_US, TestICUParse) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFC_NFC_Text, TestNames_Asian) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFC_NFC_Text, TestNames_Chinese) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_NFC_Text, TestNames_SerbianSH) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_NFD_Text, TestNames_Asian) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_NFD_Text, TestNames_Chinese) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_NFD_Text, TestNames_SerbianSH) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_Orig_Text, TestNames_Asian) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_Orig_Text, TestNames_Chinese) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-normperf (-l, TestICU_NFD_Orig_Text, TestNames_SerbianSH) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, arabic, csisolatinarabic) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, korean, csiso2022kr) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, s-chinese, EUC-CN) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, s-chinese, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, hebrew, csisolatinhebrew) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, hindi, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, japanese, EUC-JP) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, korean, csiso2022kr) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, s-chinese, EUC-CN) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-windows-msvc-postmerge (x64, Debug, x64) (push) Waiting to run
GHA ICU Merge CI / icu4c-windows-msvc-postmerge (x64, Release, x64) (push) Waiting to run
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, greek, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, greek, csisolatingreek) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, hebrew, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, hebrew, csisolatinhebrew) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, hindi, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, japanese, EUC-JP) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, japanese, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, japanese, csiso2022jp) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetDecoderICU, korean, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, arabic, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, arabic, csisolatinarabic) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, english, US-ASCII) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, english, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, french, UTF-16BE) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, french, UTF-16LE) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, french, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, french, csisolatin1) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, greek, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, greek, csisolatingreek) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, hebrew, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, japanese, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, japanese, csiso2022jp) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, korean, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-converterperf (TestCharsetEncoderICU, s-chinese, UTF-8) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm Z" "13:13 -0800" 5, TestICUConstruction) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm Z" "13:13 -0800" 5, TestICUFormat) (push) Blocked by required conditions
GHA ICU Merge CI / icu4j-dateformatperf (en_US, "HH:mm Z" "13:13 -0800" 5, TestICUParse) (push) Blocked by required conditions
GHA ICU Merge CI / icu4c-windows-msvc-postmerge (x86, Debug, Win32) (push) Waiting to run
GHA ICU Merge CI / icu4c-windows-msvc-postmerge (x86, Release, Win32) (push) Waiting to run
GHA ICU Merge CI / icu4c-windows-cygwin-gcc (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
GHA CI Valgrind / clang-valgrind-intltest (spoof) (push) Has been cancelled
GHA ICU4C / windows-msvc (/p:Configuration=Debug /p:Platform=Win32, x86 Debug) (push) Has been cancelled
GHA ICU4C / windows-msvc (/p:Configuration=Debug /p:Platform=x64, x64 Debug) (push) Has been cancelled
GHA ICU4C / windows-msvc (/p:Configuration=Release /p:Platform=ARM, arm Release) (push) Has been cancelled
GHA ICU4C / windows-msvc (/p:LanguageStandard=stdcpplatest /p:Configuration=Release /p:Platform=x64, x64 Release) (push) Has been cancelled
GHA ICU4C / windows-msvc (/p:_HAS_EXCEPTIONS=0 /p:Configuration=Release /p:Platform=x64, x64 Release) (push) Has been cancelled
CIFuzz / Fuzzing (address) (push) Has been cancelled
CIFuzz / Fuzzing (undefined) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (translit) (push) Has been cancelled
GHA ICU4C / icu4c-docs-build (push) Has been cancelled
GHA ICU4C / clang18-cpp20-warning-as-errors (-std=c++20) (push) Has been cancelled
GHA ICU4C / windows-msys2-gcc-x86_64 (push) Has been cancelled
GHA ICU4C / run-with-stubdata (push) Has been cancelled
GHA ICU4C / u-charset-is-utf8-test (push) Has been cancelled
GHA ICU4C / u-override-cxx-allocation-is-0-test (push) Has been cancelled
GHA ICU4C / lstm-test (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_BREAK_ITERATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_IDNA=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_LEGACY_CONVERSION=1 -DUCONFIG_NO_NORMALIZATION=1 -DUCONFIG_NO_BREAK_ITERATION=1 -DUCONFIG_NO_IDNA=1 -DUCONFIG_NO_COLLATION=1 -DUCONFIG_NO_FORMATTING=1 -DUCONFIG_NO_MF2=1 -DUCONFIG_NO_TRANSLITERATION=1 -DUCONFIG_NO_REG… (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_LEGACY_CONVERSION=1) (push) Has been cancelled
GHA ICU4C / icu4c-without-collation-rule-strings (push) Has been cancelled
GHA ICU4C / icu4c-icuexportdata (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (rbbi) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-test (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (bidi) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (collator) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (convert) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (csdet) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (format) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (rbnfrt) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (regex) (push) Has been cancelled
GHA ICU4C / gcc-debug-build-and-test (push) Has been cancelled
GHA ICU4C / clang-asan (push) Has been cancelled
GHA ICU4C / gcc11-cpp20 (push) Has been cancelled
GHA ICU4C / clang-release-build-and-test (push) Has been cancelled
GHA ICU4C / clang-options-build-and-test (--enable-static --disable-shared) (push) Has been cancelled
GHA ICU4C / clang-options-build-and-test (--enable-static) (push) Has been cancelled
GHA ICU4C / gcc-10-stdlib17 (push) Has been cancelled
GHA ICU4C / clang-lsan (push) Has been cancelled
GHA ICU4C / clang-ubsan (push) Has been cancelled
GHA ICU4C / clang-cfi (push) Has been cancelled
GHA ICU4C / clang-tsan (push) Has been cancelled
GHA ICU4C / clang-datafilter (push) Has been cancelled
GHA ICU4C / clang-cpp17 (push) Has been cancelled
GHA ICU4C / clang-lang-with-extn-tags (push) Has been cancelled
GHA ICU4C / clang18-cpp20-warning-as-errors (-std=c++20 -stdlib=libc++) (push) Has been cancelled
GHA ICU4C / macos-clang (push) Has been cancelled
GHA ICU4C / windows-msvc-datafilter (push) Has been cancelled
GHA ICU4C / windows-msvc-dist-release (arm64, ARM64, WinARM64) (push) Has been cancelled
GHA ICU4C / windows-msvc-dist-release (x64, x64, Win64) (push) Has been cancelled
GHA ICU4C / windows-msvc-dist-release (x86, Win32, Win32) (push) Has been cancelled
GHA ICU4C / adaboost-test (push) Has been cancelled
GHA ICU4C / testmap (push) Has been cancelled
GHA ICU4C / copyright-scan (push) Has been cancelled
GHA ICU4C / internal-header-compilation (push) Has been cancelled
GHA ICU4C / valid-UTF-8-and-no-BOM-check (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_COLLATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_FILTERED_BREAK_ITERATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_FORMATTING=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_IDNA=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_LEGACY_CONVERSION=1 -DUCONFIG_NO_NORMALIZATION=1 -DUCONFIG_NO_BREAK_ITERATION=1 -DUCONFIG_NO_IDNA=1 -DUCONFIG_NO_COLLATION=1 -DUCONFIG_NO_FORMATTING=1 -DUCONFIG_NO_MF2=1 -DUCONFIG_NO_TRANSLITERATION=1 -DUCONFIG_NO_REGUL… (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_LEGACY_CONVERSION=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_MF2=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_NORMALIZATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_REGULAR_EXPRESSIONS=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_SERVICE=1) (push) Has been cancelled
GHA ICU4C / uconfig-unit-tests (-DUCONFIG_NO_TRANSLITERATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_BREAK_ITERATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_COLLATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_FILTERED_BREAK_ITERATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_FORMATTING=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_MF2=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_NORMALIZATION=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_REGULAR_EXPRESSIONS=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_SERVICE=1) (push) Has been cancelled
GHA ICU4C / uconfig-header-tests (-DUCONFIG_NO_TRANSLITERATION=1) (push) Has been cancelled
GHA ICU4C / unicode-update-tools (push) Has been cancelled
GHA ICU4C / icu4c-test-samples (push) Has been cancelled
GHA ICU4C / icu4c-uconfig-no-conversion (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (icuserv) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (idna) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (normalize) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (rbnf) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (rbnfp) (push) Has been cancelled
GHA CI Valgrind / clang-valgrind-intltest (utility) (push) Has been cancelled
Update spec tests to current version from message-format-wg - Update parser for changed name-start grammar rule - Validate number literals in :number implementation (since parser no longer does this) - Disallow `:number`/`:integer` select option set from variable See https://github.com/unicode-org/message-format-wg/pull/1016 As part of this, un-skip tests where the `bad-option` error is expected, and implement validating digit size options (pending PR https://github.com/unicode-org/icu/pull/2973 is intended to do this more fully)
1709 lines
59 KiB
C++
1709 lines
59 KiB
C++
// © 2024 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
|
|
#include "unicode/utypes.h"
|
|
|
|
#if !UCONFIG_NO_NORMALIZATION
|
|
|
|
#if !UCONFIG_NO_FORMATTING
|
|
|
|
#if !UCONFIG_NO_MF2
|
|
|
|
#include <math.h>
|
|
#include <cmath>
|
|
|
|
#include "unicode/dtptngen.h"
|
|
#include "unicode/messageformat2.h"
|
|
#include "unicode/messageformat2_data_model_names.h"
|
|
#include "unicode/messageformat2_function_registry.h"
|
|
#include "unicode/normalizer2.h"
|
|
#include "unicode/smpdtfmt.h"
|
|
#include "charstr.h"
|
|
#include "double-conversion.h"
|
|
#include "messageformat2_allocation.h"
|
|
#include "messageformat2_function_registry_internal.h"
|
|
#include "messageformat2_macros.h"
|
|
#include "hash.h"
|
|
#include "number_types.h"
|
|
#include "uvector.h" // U_ASSERT
|
|
|
|
// The C99 standard suggested that C++ implementations not define PRId64 etc. constants
|
|
// unless this macro is defined.
|
|
// See the Notes at https://en.cppreference.com/w/cpp/types/integer .
|
|
// Similar to defining __STDC_LIMIT_MACROS in unicode/ptypes.h .
|
|
#ifndef __STDC_FORMAT_MACROS
|
|
# define __STDC_FORMAT_MACROS
|
|
#endif
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
|
|
U_NAMESPACE_BEGIN
|
|
|
|
namespace message2 {
|
|
|
|
// Function registry implementation
|
|
|
|
Formatter::~Formatter() {}
|
|
Selector::~Selector() {}
|
|
FormatterFactory::~FormatterFactory() {}
|
|
SelectorFactory::~SelectorFactory() {}
|
|
|
|
MFFunctionRegistry MFFunctionRegistry::Builder::build() {
|
|
U_ASSERT(formatters != nullptr && selectors != nullptr && formattersByType != nullptr);
|
|
MFFunctionRegistry result = MFFunctionRegistry(formatters, selectors, formattersByType);
|
|
formatters = nullptr;
|
|
selectors = nullptr;
|
|
formattersByType = nullptr;
|
|
return result;
|
|
}
|
|
|
|
MFFunctionRegistry::Builder& MFFunctionRegistry::Builder::adoptSelector(const FunctionName& selectorName, SelectorFactory* selectorFactory, UErrorCode& errorCode) {
|
|
if (U_SUCCESS(errorCode)) {
|
|
U_ASSERT(selectors != nullptr);
|
|
selectors->put(selectorName, selectorFactory, errorCode);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MFFunctionRegistry::Builder& MFFunctionRegistry::Builder::adoptFormatter(const FunctionName& formatterName, FormatterFactory* formatterFactory, UErrorCode& errorCode) {
|
|
if (U_SUCCESS(errorCode)) {
|
|
U_ASSERT(formatters != nullptr);
|
|
formatters->put(formatterName, formatterFactory, errorCode);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MFFunctionRegistry::Builder& MFFunctionRegistry::Builder::setDefaultFormatterNameByType(const UnicodeString& type, const FunctionName& functionName, UErrorCode& errorCode) {
|
|
if (U_SUCCESS(errorCode)) {
|
|
U_ASSERT(formattersByType != nullptr);
|
|
FunctionName* f = create<FunctionName>(FunctionName(functionName), errorCode);
|
|
formattersByType->put(type, f, errorCode);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
MFFunctionRegistry::Builder::Builder(UErrorCode& errorCode) {
|
|
CHECK_ERROR(errorCode);
|
|
|
|
formatters = new Hashtable();
|
|
selectors = new Hashtable();
|
|
formattersByType = new Hashtable();
|
|
if (!(formatters != nullptr && selectors != nullptr && formattersByType != nullptr)) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
} else {
|
|
formatters->setValueDeleter(uprv_deleteUObject);
|
|
selectors->setValueDeleter(uprv_deleteUObject);
|
|
formattersByType->setValueDeleter(uprv_deleteUObject);
|
|
}
|
|
}
|
|
|
|
MFFunctionRegistry::Builder::~Builder() {
|
|
if (formatters != nullptr) {
|
|
delete formatters;
|
|
}
|
|
if (selectors != nullptr) {
|
|
delete selectors;
|
|
}
|
|
if (formattersByType != nullptr) {
|
|
delete formattersByType;
|
|
}
|
|
}
|
|
|
|
// Returns non-owned pointer. Returns pointer rather than reference because it can fail.
|
|
// Returns non-const because FormatterFactory is mutable.
|
|
// TODO: This is unsafe because of the cached-formatters map
|
|
// (the caller could delete the resulting pointer)
|
|
FormatterFactory* MFFunctionRegistry::getFormatter(const FunctionName& formatterName) const {
|
|
U_ASSERT(formatters != nullptr);
|
|
return static_cast<FormatterFactory*>(formatters->get(formatterName));
|
|
}
|
|
|
|
UBool MFFunctionRegistry::getDefaultFormatterNameByType(const UnicodeString& type, FunctionName& name) const {
|
|
U_ASSERT(formatters != nullptr);
|
|
const FunctionName* f = static_cast<FunctionName*>(formattersByType->get(type));
|
|
if (f != nullptr) {
|
|
name = *f;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const SelectorFactory* MFFunctionRegistry::getSelector(const FunctionName& selectorName) const {
|
|
U_ASSERT(selectors != nullptr);
|
|
return static_cast<const SelectorFactory*>(selectors->get(selectorName));
|
|
}
|
|
|
|
bool MFFunctionRegistry::hasFormatter(const FunctionName& f) const {
|
|
return getFormatter(f) != nullptr;
|
|
}
|
|
|
|
bool MFFunctionRegistry::hasSelector(const FunctionName& s) const {
|
|
return getSelector(s) != nullptr;
|
|
}
|
|
|
|
void MFFunctionRegistry::checkFormatter(const char* s) const {
|
|
#if U_DEBUG
|
|
U_ASSERT(hasFormatter(FunctionName(UnicodeString(s))));
|
|
#else
|
|
(void) s;
|
|
#endif
|
|
}
|
|
|
|
void MFFunctionRegistry::checkSelector(const char* s) const {
|
|
#if U_DEBUG
|
|
U_ASSERT(hasSelector(FunctionName(UnicodeString(s))));
|
|
#else
|
|
(void) s;
|
|
#endif
|
|
}
|
|
|
|
// Debugging
|
|
void MFFunctionRegistry::checkStandard() const {
|
|
checkFormatter("datetime");
|
|
checkFormatter("date");
|
|
checkFormatter("time");
|
|
checkFormatter("number");
|
|
checkFormatter("integer");
|
|
checkFormatter("test:function");
|
|
checkFormatter("test:format");
|
|
checkSelector("number");
|
|
checkSelector("integer");
|
|
checkSelector("string");
|
|
checkSelector("test:function");
|
|
checkSelector("test:select");
|
|
}
|
|
|
|
// Formatter/selector helpers
|
|
|
|
// Returns the NFC-normalized version of s, returning s itself
|
|
// if it's already normalized.
|
|
/* static */ UnicodeString StandardFunctions::normalizeNFC(const UnicodeString& s) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
const Normalizer2* nfcNormalizer = Normalizer2::getNFCInstance(status);
|
|
if (U_FAILURE(status)) {
|
|
return s;
|
|
}
|
|
// Check if string is already normalized
|
|
UNormalizationCheckResult result = nfcNormalizer->quickCheck(s, status);
|
|
// If so, return it
|
|
if (U_SUCCESS(status) && result == UNORM_YES) {
|
|
return s;
|
|
}
|
|
// Otherwise, normalize it
|
|
UnicodeString normalized = nfcNormalizer->normalize(s, status);
|
|
if (U_FAILURE(status)) {
|
|
return {};
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
// Converts `s` to a double, indicating failure via `errorCode`
|
|
static void strToDouble(const UnicodeString& s, double& result, UErrorCode& errorCode) {
|
|
CHECK_ERROR(errorCode);
|
|
|
|
// Using en-US locale because it happens to correspond to the spec:
|
|
// https://github.com/unicode-org/message-format-wg/blob/main/spec/registry.md#number-operands
|
|
// Ideally, this should re-use the code for parsing number literals (Parser::parseUnquotedLiteral())
|
|
// It's hard to reuse the same code because of how parse errors work.
|
|
// TODO: Refactor
|
|
LocalPointer<NumberFormat> numberFormat(NumberFormat::createInstance(Locale("en-US"), errorCode));
|
|
CHECK_ERROR(errorCode);
|
|
icu::Formattable asNumber;
|
|
numberFormat->parse(s, asNumber, errorCode);
|
|
CHECK_ERROR(errorCode);
|
|
result = asNumber.getDouble(errorCode);
|
|
}
|
|
|
|
static double tryStringAsNumber(const Locale& locale, const Formattable& val, UErrorCode& errorCode) {
|
|
// Check for a string option, try to parse it as a number if present
|
|
UnicodeString tempString = val.getString(errorCode);
|
|
LocalPointer<NumberFormat> numberFormat(NumberFormat::createInstance(locale, errorCode));
|
|
if (U_SUCCESS(errorCode)) {
|
|
icu::Formattable asNumber;
|
|
numberFormat->parse(tempString, asNumber, errorCode);
|
|
if (U_SUCCESS(errorCode)) {
|
|
return asNumber.getDouble(errorCode);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int64_t getInt64Value(const Locale& locale, const Formattable& value, UErrorCode& errorCode) {
|
|
if (U_SUCCESS(errorCode)) {
|
|
if (!value.isNumeric()) {
|
|
double doubleResult = tryStringAsNumber(locale, value, errorCode);
|
|
if (U_SUCCESS(errorCode)) {
|
|
return static_cast<int64_t>(doubleResult);
|
|
}
|
|
}
|
|
else {
|
|
int64_t result = value.getInt64(errorCode);
|
|
if (U_SUCCESS(errorCode)) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
// Option was numeric but couldn't be converted to int64_t -- could be overflow
|
|
return 0;
|
|
}
|
|
|
|
// Adopts its arguments
|
|
MFFunctionRegistry::MFFunctionRegistry(FormatterMap* f, SelectorMap* s, Hashtable* byType) : formatters(f), selectors(s), formattersByType(byType) {
|
|
U_ASSERT(f != nullptr && s != nullptr && byType != nullptr);
|
|
}
|
|
|
|
MFFunctionRegistry& MFFunctionRegistry::operator=(MFFunctionRegistry&& other) noexcept {
|
|
cleanup();
|
|
|
|
formatters = other.formatters;
|
|
selectors = other.selectors;
|
|
formattersByType = other.formattersByType;
|
|
other.formatters = nullptr;
|
|
other.selectors = nullptr;
|
|
other.formattersByType = nullptr;
|
|
|
|
return *this;
|
|
}
|
|
|
|
void MFFunctionRegistry::cleanup() noexcept {
|
|
if (formatters != nullptr) {
|
|
delete formatters;
|
|
}
|
|
if (selectors != nullptr) {
|
|
delete selectors;
|
|
}
|
|
if (formattersByType != nullptr) {
|
|
delete formattersByType;
|
|
}
|
|
}
|
|
|
|
|
|
MFFunctionRegistry::~MFFunctionRegistry() {
|
|
cleanup();
|
|
}
|
|
|
|
// Specific formatter implementations
|
|
|
|
// --------- Number
|
|
|
|
bool inBounds(const UnicodeString& s, int32_t i) {
|
|
return i < s.length();
|
|
}
|
|
|
|
bool isDigit(UChar32 c) {
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
bool parseDigits(const UnicodeString& s, int32_t& i) {
|
|
if (!isDigit(s[i])) {
|
|
return false;
|
|
}
|
|
while (inBounds(s, i) && isDigit(s[i])) {
|
|
i++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// number-literal = ["-"] (%x30 / (%x31-39 *DIGIT)) ["." 1*DIGIT] [%i"e" ["-" / "+"] 1*DIGIT]
|
|
bool validateNumberLiteral(const UnicodeString& s) {
|
|
int32_t i = 0;
|
|
|
|
if (s.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
// Parse optional sign
|
|
// ["-"]
|
|
if (s[0] == HYPHEN) {
|
|
i++;
|
|
}
|
|
|
|
if (!inBounds(s, i)) {
|
|
return false;
|
|
}
|
|
|
|
// Parse integer digits
|
|
// (%x30 / (%x31-39 *DIGIT))
|
|
if (s[i] == '0') {
|
|
if (!inBounds(s, i + 1) || s[i + 1] != PERIOD) {
|
|
return false;
|
|
}
|
|
i++;
|
|
} else {
|
|
if (!parseDigits(s, i)) {
|
|
return false;
|
|
}
|
|
}
|
|
// The rest is optional
|
|
if (!inBounds(s, i)) {
|
|
return true;
|
|
}
|
|
|
|
// Parse optional decimal digits
|
|
// ["." 1*DIGIT]
|
|
if (s[i] == PERIOD) {
|
|
i++;
|
|
if (!parseDigits(s, i)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!inBounds(s, i)) {
|
|
return true;
|
|
}
|
|
|
|
// Parse optional exponent
|
|
// [%i"e" ["-" / "+"] 1*DIGIT]
|
|
if (s[i] == 'e' || s[i] == 'E') {
|
|
i++;
|
|
if (!inBounds(s, i)) {
|
|
return false;
|
|
}
|
|
// Parse optional sign
|
|
if (s[i] == HYPHEN || s[i] == PLUS) {
|
|
i++;
|
|
}
|
|
if (!inBounds(s, i)) {
|
|
return false;
|
|
}
|
|
if (!parseDigits(s, i)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (i != s.length()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool isInteger(const Formattable& s) {
|
|
switch (s.getType()) {
|
|
case UFMT_DOUBLE:
|
|
case UFMT_LONG:
|
|
case UFMT_INT64:
|
|
return true;
|
|
case UFMT_STRING: {
|
|
UErrorCode ignore = U_ZERO_ERROR;
|
|
const UnicodeString& str = s.getString(ignore);
|
|
return validateNumberLiteral(str);
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool isDigitSizeOption(const UnicodeString& s) {
|
|
return s == UnicodeString("minimumIntegerDigits")
|
|
|| s == UnicodeString("minimumFractionDigits")
|
|
|| s == UnicodeString("maximumFractionDigits")
|
|
|| s == UnicodeString("minimumSignificantDigits")
|
|
|| s == UnicodeString("maximumSignificantDigits");
|
|
}
|
|
|
|
/* static */ void StandardFunctions::validateDigitSizeOptions(const FunctionOptions& opts,
|
|
UErrorCode& status) {
|
|
CHECK_ERROR(status);
|
|
|
|
for (int32_t i = 0; i < opts.optionsCount(); i++) {
|
|
const ResolvedFunctionOption& opt = opts.options[i];
|
|
if (isDigitSizeOption(opt.getName()) && !isInteger(opt.getValue())) {
|
|
status = U_MF_BAD_OPTION;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */ number::LocalizedNumberFormatter StandardFunctions::formatterForOptions(const Number& number,
|
|
const FunctionOptions& opts,
|
|
UErrorCode& status) {
|
|
number::UnlocalizedNumberFormatter nf;
|
|
|
|
using namespace number;
|
|
|
|
validateDigitSizeOptions(opts, status);
|
|
if (U_FAILURE(status)) {
|
|
return {};
|
|
}
|
|
|
|
if (U_SUCCESS(status)) {
|
|
Formattable opt;
|
|
nf = NumberFormatter::with();
|
|
bool isInteger = number.isInteger;
|
|
|
|
if (isInteger) {
|
|
nf = nf.precision(Precision::integer());
|
|
}
|
|
|
|
// Notation options
|
|
if (!isInteger) {
|
|
// These options only apply to `:number`
|
|
|
|
// Default notation is simple
|
|
Notation notation = Notation::simple();
|
|
UnicodeString notationOpt = opts.getStringFunctionOption(options::NOTATION);
|
|
if (notationOpt == options::SCIENTIFIC) {
|
|
notation = Notation::scientific();
|
|
} else if (notationOpt == options::ENGINEERING) {
|
|
notation = Notation::engineering();
|
|
} else if (notationOpt == options::COMPACT) {
|
|
UnicodeString displayOpt = opts.getStringFunctionOption(options::COMPACT_DISPLAY);
|
|
if (displayOpt == options::LONG) {
|
|
notation = Notation::compactLong();
|
|
} else {
|
|
// Default is short
|
|
notation = Notation::compactShort();
|
|
}
|
|
} else {
|
|
// Already set to default
|
|
}
|
|
nf = nf.notation(notation);
|
|
}
|
|
|
|
// Style options -- specific to `:number`
|
|
if (!isInteger) {
|
|
if (number.usePercent(opts)) {
|
|
nf = nf.unit(NoUnit::percent()).scale(Scale::powerOfTen(2));
|
|
}
|
|
}
|
|
|
|
int32_t maxSignificantDigits = number.maximumSignificantDigits(opts);
|
|
if (!isInteger) {
|
|
int32_t minFractionDigits = number.minimumFractionDigits(opts);
|
|
int32_t maxFractionDigits = number.maximumFractionDigits(opts);
|
|
int32_t minSignificantDigits = number.minimumSignificantDigits(opts);
|
|
Precision p = Precision::unlimited();
|
|
bool precisionOptions = false;
|
|
|
|
// Returning -1 means the option wasn't provided
|
|
if (maxFractionDigits != -1 && minFractionDigits != -1) {
|
|
precisionOptions = true;
|
|
p = Precision::minMaxFraction(minFractionDigits, maxFractionDigits);
|
|
} else if (minFractionDigits != -1) {
|
|
precisionOptions = true;
|
|
p = Precision::minFraction(minFractionDigits);
|
|
} else if (maxFractionDigits != -1) {
|
|
precisionOptions = true;
|
|
p = Precision::maxFraction(maxFractionDigits);
|
|
}
|
|
|
|
if (minSignificantDigits != -1) {
|
|
precisionOptions = true;
|
|
p = p.minSignificantDigits(minSignificantDigits);
|
|
}
|
|
if (maxSignificantDigits != -1) {
|
|
precisionOptions = true;
|
|
p = p.maxSignificantDigits(maxSignificantDigits);
|
|
}
|
|
if (precisionOptions) {
|
|
nf = nf.precision(p);
|
|
}
|
|
} else {
|
|
// maxSignificantDigits applies to `:integer`, but the other precision options don't
|
|
Precision p = Precision::integer();
|
|
if (maxSignificantDigits != -1) {
|
|
p = p.maxSignificantDigits(maxSignificantDigits);
|
|
}
|
|
nf = nf.precision(p);
|
|
}
|
|
|
|
// All other options apply to both `:number` and `:integer`
|
|
int32_t minIntegerDigits = number.minimumIntegerDigits(opts);
|
|
nf = nf.integerWidth(IntegerWidth::zeroFillTo(minIntegerDigits));
|
|
|
|
// signDisplay
|
|
UnicodeString sd = opts.getStringFunctionOption(options::SIGN_DISPLAY);
|
|
UNumberSignDisplay signDisplay;
|
|
if (sd == options::ALWAYS) {
|
|
signDisplay = UNumberSignDisplay::UNUM_SIGN_ALWAYS;
|
|
} else if (sd == options::EXCEPT_ZERO) {
|
|
signDisplay = UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO;
|
|
} else if (sd == options::NEGATIVE) {
|
|
signDisplay = UNumberSignDisplay::UNUM_SIGN_NEGATIVE;
|
|
} else if (sd == options::NEVER) {
|
|
signDisplay = UNumberSignDisplay::UNUM_SIGN_NEVER;
|
|
} else {
|
|
signDisplay = UNumberSignDisplay::UNUM_SIGN_AUTO;
|
|
}
|
|
nf = nf.sign(signDisplay);
|
|
|
|
// useGrouping
|
|
UnicodeString ug = opts.getStringFunctionOption(options::USE_GROUPING);
|
|
UNumberGroupingStrategy grp;
|
|
if (ug == options::ALWAYS) {
|
|
grp = UNumberGroupingStrategy::UNUM_GROUPING_ON_ALIGNED;
|
|
} else if (ug == options::NEVER) {
|
|
grp = UNumberGroupingStrategy::UNUM_GROUPING_OFF;
|
|
} else if (ug == options::MIN2) {
|
|
grp = UNumberGroupingStrategy::UNUM_GROUPING_MIN2;
|
|
} else {
|
|
// Default is "auto"
|
|
grp = UNumberGroupingStrategy::UNUM_GROUPING_AUTO;
|
|
}
|
|
nf = nf.grouping(grp);
|
|
|
|
// numberingSystem
|
|
UnicodeString ns = opts.getStringFunctionOption(options::NUMBERING_SYSTEM);
|
|
if (ns.length() > 0) {
|
|
ns = ns.toLower(Locale("en-US"));
|
|
CharString buffer;
|
|
// Ignore bad option values, so use a local status
|
|
UErrorCode localStatus = U_ZERO_ERROR;
|
|
// Copied from number_skeletons.cpp (helpers::parseNumberingSystemOption)
|
|
buffer.appendInvariantChars({false, ns.getBuffer(), ns.length()}, localStatus);
|
|
if (U_SUCCESS(localStatus)) {
|
|
LocalPointer<NumberingSystem> symbols
|
|
(NumberingSystem::createInstanceByName(buffer.data(), localStatus));
|
|
if (U_SUCCESS(localStatus)) {
|
|
nf = nf.adoptSymbols(symbols.orphan());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nf.locale(number.locale);
|
|
}
|
|
|
|
Formatter* StandardFunctions::NumberFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
Formatter* result = new Number(locale);
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Formatter* StandardFunctions::IntegerFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
Formatter* result = new Number(Number::integer(locale));
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
StandardFunctions::IntegerFactory::~IntegerFactory() {}
|
|
|
|
static FormattedPlaceholder notANumber(const FormattedPlaceholder& input) {
|
|
return FormattedPlaceholder(input, FormattedValue(UnicodeString("NaN")));
|
|
}
|
|
|
|
static double parseNumberLiteral(const Formattable& input, UErrorCode& errorCode) {
|
|
if (U_FAILURE(errorCode)) {
|
|
return {};
|
|
}
|
|
|
|
// Copying string to avoid GCC dangling-reference warning
|
|
// (although the reference is safe)
|
|
UnicodeString inputStr = input.getString(errorCode);
|
|
// Precondition: `input`'s source Formattable has type string
|
|
if (U_FAILURE(errorCode)) {
|
|
return {};
|
|
}
|
|
|
|
// Validate string according to `number-literal` production
|
|
// in the spec for `:number`. This is because some cases are
|
|
// forbidden by this grammar, but allowed by StringToDouble.
|
|
if (!validateNumberLiteral(inputStr)) {
|
|
errorCode = U_MF_OPERAND_MISMATCH_ERROR;
|
|
return 0;
|
|
}
|
|
|
|
// Convert to double using double_conversion::StringToDoubleConverter
|
|
using namespace double_conversion;
|
|
int processedCharactersCount = 0;
|
|
StringToDoubleConverter converter(0, 0, 0, "", "");
|
|
int32_t len = inputStr.length();
|
|
double result =
|
|
converter.StringToDouble(reinterpret_cast<const uint16_t*>(inputStr.getBuffer()),
|
|
len,
|
|
&processedCharactersCount);
|
|
if (processedCharactersCount != len) {
|
|
errorCode = U_MF_OPERAND_MISMATCH_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static UChar32 digitToChar(int32_t val, UErrorCode errorCode) {
|
|
if (U_FAILURE(errorCode)) {
|
|
return '0';
|
|
}
|
|
if (val < 0 || val > 9) {
|
|
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
switch(val) {
|
|
case 0:
|
|
return '0';
|
|
case 1:
|
|
return '1';
|
|
case 2:
|
|
return '2';
|
|
case 3:
|
|
return '3';
|
|
case 4:
|
|
return '4';
|
|
case 5:
|
|
return '5';
|
|
case 6:
|
|
return '6';
|
|
case 7:
|
|
return '7';
|
|
case 8:
|
|
return '8';
|
|
case 9:
|
|
return '9';
|
|
default:
|
|
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return '0';
|
|
}
|
|
}
|
|
|
|
int32_t StandardFunctions::Number::maximumFractionDigits(const FunctionOptions& opts) const {
|
|
Formattable opt;
|
|
|
|
if (isInteger) {
|
|
return 0;
|
|
}
|
|
|
|
if (opts.getFunctionOption(options::MAXIMUM_FRACTION_DIGITS, opt)) {
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
int64_t val = getInt64Value(locale, opt, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return static_cast<int32_t>(val);
|
|
}
|
|
}
|
|
// Returning -1 indicates that the option wasn't provided or was a non-integer.
|
|
// The caller needs to check for that case, since passing -1 to Precision::maxFraction()
|
|
// is an error.
|
|
return -1;
|
|
}
|
|
|
|
int32_t StandardFunctions::Number::minimumFractionDigits(const FunctionOptions& opts) const {
|
|
Formattable opt;
|
|
|
|
if (!isInteger) {
|
|
if (opts.getFunctionOption(options::MINIMUM_FRACTION_DIGITS, opt)) {
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
int64_t val = getInt64Value(locale, opt, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return static_cast<int32_t>(val);
|
|
}
|
|
}
|
|
}
|
|
// Returning -1 indicates that the option wasn't provided or was a non-integer.
|
|
// The caller needs to check for that case, since passing -1 to Precision::minFraction()
|
|
// is an error.
|
|
return -1;
|
|
}
|
|
|
|
int32_t StandardFunctions::Number::minimumIntegerDigits(const FunctionOptions& opts) const {
|
|
Formattable opt;
|
|
|
|
if (opts.getFunctionOption(options::MINIMUM_INTEGER_DIGITS, opt)) {
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
int64_t val = getInt64Value(locale, opt, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return static_cast<int32_t>(val);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int32_t StandardFunctions::Number::minimumSignificantDigits(const FunctionOptions& opts) const {
|
|
Formattable opt;
|
|
|
|
if (!isInteger) {
|
|
if (opts.getFunctionOption(options::MINIMUM_SIGNIFICANT_DIGITS, opt)) {
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
int64_t val = getInt64Value(locale, opt, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return static_cast<int32_t>(val);
|
|
}
|
|
}
|
|
}
|
|
// Returning -1 indicates that the option wasn't provided or was a non-integer.
|
|
// The caller needs to check for that case, since passing -1 to Precision::minSignificantDigits()
|
|
// is an error.
|
|
return -1;
|
|
}
|
|
|
|
int32_t StandardFunctions::Number::maximumSignificantDigits(const FunctionOptions& opts) const {
|
|
Formattable opt;
|
|
|
|
if (opts.getFunctionOption(options::MAXIMUM_SIGNIFICANT_DIGITS, opt)) {
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
int64_t val = getInt64Value(locale, opt, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return static_cast<int32_t>(val);
|
|
}
|
|
}
|
|
// Returning -1 indicates that the option wasn't provided or was a non-integer.
|
|
// The caller needs to check for that case, since passing -1 to Precision::maxSignificantDigits()
|
|
// is an error.
|
|
return -1; // Not a valid value for Precision; has to be checked
|
|
}
|
|
|
|
bool StandardFunctions::Number::usePercent(const FunctionOptions& opts) const {
|
|
Formattable opt;
|
|
if (isInteger
|
|
|| !opts.getFunctionOption(options::STYLE, opt)
|
|
|| opt.getType() != UFMT_STRING) {
|
|
return false;
|
|
}
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
const UnicodeString& style = opt.getString(localErrorCode);
|
|
U_ASSERT(U_SUCCESS(localErrorCode));
|
|
return (style == options::PERCENT_STRING);
|
|
}
|
|
|
|
/* static */ StandardFunctions::Number StandardFunctions::Number::integer(const Locale& loc) {
|
|
return StandardFunctions::Number(loc, true);
|
|
}
|
|
|
|
FormattedPlaceholder StandardFunctions::Number::format(FormattedPlaceholder&& arg, FunctionOptions&& opts, UErrorCode& errorCode) const {
|
|
if (U_FAILURE(errorCode)) {
|
|
return {};
|
|
}
|
|
|
|
// No argument => return "NaN"
|
|
if (!arg.canFormat()) {
|
|
errorCode = U_MF_OPERAND_MISMATCH_ERROR;
|
|
return notANumber(arg);
|
|
}
|
|
|
|
number::LocalizedNumberFormatter realFormatter;
|
|
realFormatter = formatterForOptions(*this, opts, errorCode);
|
|
|
|
number::FormattedNumber numberResult;
|
|
int64_t integerValue = 0;
|
|
|
|
if (U_SUCCESS(errorCode)) {
|
|
// Already checked that contents can be formatted
|
|
const Formattable& toFormat = arg.asFormattable();
|
|
switch (toFormat.getType()) {
|
|
case UFMT_DOUBLE: {
|
|
double d = toFormat.getDouble(errorCode);
|
|
U_ASSERT(U_SUCCESS(errorCode));
|
|
numberResult = realFormatter.formatDouble(d, errorCode);
|
|
integerValue = static_cast<int64_t>(std::round(d));
|
|
break;
|
|
}
|
|
case UFMT_LONG: {
|
|
int32_t l = toFormat.getLong(errorCode);
|
|
U_ASSERT(U_SUCCESS(errorCode));
|
|
numberResult = realFormatter.formatInt(l, errorCode);
|
|
integerValue = l;
|
|
break;
|
|
}
|
|
case UFMT_INT64: {
|
|
int64_t i = toFormat.getInt64(errorCode);
|
|
U_ASSERT(U_SUCCESS(errorCode));
|
|
numberResult = realFormatter.formatInt(i, errorCode);
|
|
integerValue = i;
|
|
break;
|
|
}
|
|
case UFMT_STRING: {
|
|
// Try to parse the string as a number
|
|
double d = parseNumberLiteral(toFormat, errorCode);
|
|
if (U_FAILURE(errorCode))
|
|
return {};
|
|
numberResult = realFormatter.formatDouble(d, errorCode);
|
|
integerValue = static_cast<int64_t>(std::round(d));
|
|
break;
|
|
}
|
|
default: {
|
|
// Other types can't be parsed as a number
|
|
errorCode = U_MF_OPERAND_MISMATCH_ERROR;
|
|
return notANumber(arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Need to return the integer value if invoked as :integer
|
|
if (isInteger) {
|
|
return FormattedPlaceholder(FormattedPlaceholder(Formattable(integerValue), arg.getFallback()),
|
|
std::move(opts),
|
|
FormattedValue(std::move(numberResult)));
|
|
}
|
|
return FormattedPlaceholder(arg, std::move(opts), FormattedValue(std::move(numberResult)));
|
|
}
|
|
|
|
StandardFunctions::Number::~Number() {}
|
|
StandardFunctions::NumberFactory::~NumberFactory() {}
|
|
|
|
// --------- PluralFactory
|
|
|
|
StandardFunctions::Plural::PluralType StandardFunctions::Plural::pluralType(const FunctionOptions& opts) const {
|
|
Formattable opt;
|
|
|
|
if (opts.getFunctionOption(options::SELECT, opt)) {
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
UnicodeString val = opt.getString(localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
if (val == options::ORDINAL) {
|
|
return PluralType::PLURAL_ORDINAL;
|
|
}
|
|
if (val == options::EXACT) {
|
|
return PluralType::PLURAL_EXACT;
|
|
}
|
|
}
|
|
}
|
|
return PluralType::PLURAL_CARDINAL;
|
|
}
|
|
|
|
Selector* StandardFunctions::PluralFactory::createSelector(const Locale& locale, UErrorCode& errorCode) const {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
Selector* result;
|
|
if (isInteger) {
|
|
result = new Plural(Plural::integer(locale, errorCode));
|
|
} else {
|
|
result = new Plural(locale, errorCode);
|
|
}
|
|
NULL_ON_ERROR(errorCode);
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void StandardFunctions::Plural::selectKey(FormattedPlaceholder&& toFormat,
|
|
FunctionOptions&& opts,
|
|
const UnicodeString* keys,
|
|
int32_t keysLen,
|
|
UnicodeString* prefs,
|
|
int32_t& prefsLen,
|
|
UErrorCode& errorCode) const {
|
|
CHECK_ERROR(errorCode);
|
|
|
|
// No argument => return "NaN"
|
|
if (!toFormat.canFormat()) {
|
|
errorCode = U_MF_SELECTOR_ERROR;
|
|
return;
|
|
}
|
|
|
|
// Handle any formatting options
|
|
PluralType type = pluralType(opts);
|
|
FormattedPlaceholder resolvedSelector = numberFormatter->format(std::move(toFormat),
|
|
std::move(opts),
|
|
errorCode);
|
|
CHECK_ERROR(errorCode);
|
|
|
|
U_ASSERT(resolvedSelector.isEvaluated() && resolvedSelector.output().isNumber());
|
|
|
|
// See https://github.com/unicode-org/message-format-wg/blob/main/spec/registry.md#number-selection
|
|
// 1. Let exact be the JSON string representation of the numeric value of resolvedSelector
|
|
const number::FormattedNumber& formattedNumber = resolvedSelector.output().getNumber();
|
|
UnicodeString exact = formattedNumber.toString(errorCode);
|
|
|
|
if (U_FAILURE(errorCode)) {
|
|
// Non-number => selector error
|
|
errorCode = U_MF_SELECTOR_ERROR;
|
|
return;
|
|
}
|
|
|
|
// Step 2. Let keyword be a string which is the result of rule selection on resolvedSelector.
|
|
// If the option select is set to exact, rule-based selection is not used. Return the empty string.
|
|
UnicodeString keyword;
|
|
if (type != PluralType::PLURAL_EXACT) {
|
|
UPluralType t = type == PluralType::PLURAL_ORDINAL ? UPLURAL_TYPE_ORDINAL : UPLURAL_TYPE_CARDINAL;
|
|
// Look up plural rules by locale and type
|
|
LocalPointer<PluralRules> rules(PluralRules::forLocale(locale, t, errorCode));
|
|
CHECK_ERROR(errorCode);
|
|
|
|
keyword = rules->select(formattedNumber, errorCode);
|
|
}
|
|
|
|
// Steps 3-4 elided:
|
|
// 3. Let resultExact be a new empty list of strings.
|
|
// 4. Let resultKeyword be a new empty list of strings.
|
|
// Instead, we use `prefs` the concatenation of `resultExact`
|
|
// and `resultKeyword`.
|
|
|
|
prefsLen = 0;
|
|
|
|
// 5. For each string key in keys:
|
|
double keyAsDouble = 0;
|
|
for (int32_t i = 0; i < keysLen; i++) {
|
|
// Try parsing the key as a double
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
strToDouble(keys[i], keyAsDouble, localErrorCode);
|
|
// 5i. If the value of key matches the production number-literal, then
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
// 5i(a). If key and exact consist of the same sequence of Unicode code points, then
|
|
if (exact == keys[i]) {
|
|
// 5i(a)(a) Append key as the last element of the list resultExact.
|
|
prefs[prefsLen] = keys[i];
|
|
prefsLen++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return immediately if exact matching was requested
|
|
if (prefsLen == keysLen || type == PluralType::PLURAL_EXACT) {
|
|
return;
|
|
}
|
|
|
|
|
|
for (int32_t i = 0; i < keysLen; i ++) {
|
|
if (prefsLen >= keysLen) {
|
|
break;
|
|
}
|
|
// 5ii. Else if key is one of the keywords zero, one, two, few, many, or other, then
|
|
// 5ii(a). If key and keyword consist of the same sequence of Unicode code points, then
|
|
if (keyword == keys[i]) {
|
|
// 5ii(a)(a) Append key as the last element of the list resultKeyword.
|
|
prefs[prefsLen] = keys[i];
|
|
prefsLen++;
|
|
}
|
|
}
|
|
|
|
// Note: Step 5(iii) "Else, emit a Selection Error" is omitted in both loops
|
|
|
|
// 6. Return a new list whose elements are the concatenation of the elements
|
|
// (in order) of resultExact followed by the elements (in order) of resultKeyword.
|
|
// (Implicit, since `prefs` is an out-parameter)
|
|
}
|
|
|
|
StandardFunctions::Plural::Plural(const Locale& loc, UErrorCode& status) : locale(loc) {
|
|
CHECK_ERROR(status);
|
|
|
|
numberFormatter.adoptInstead(new StandardFunctions::Number(loc));
|
|
if (!numberFormatter.isValid()) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
}
|
|
|
|
StandardFunctions::Plural::Plural(const Locale& loc, bool isInt, UErrorCode& status) : locale(loc), isInteger(isInt) {
|
|
CHECK_ERROR(status);
|
|
|
|
if (isInteger) {
|
|
numberFormatter.adoptInstead(new StandardFunctions::Number(loc, true));
|
|
} else {
|
|
numberFormatter.adoptInstead(new StandardFunctions::Number(loc));
|
|
}
|
|
|
|
if (!numberFormatter.isValid()) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
}
|
|
|
|
StandardFunctions::Plural::~Plural() {}
|
|
|
|
StandardFunctions::PluralFactory::~PluralFactory() {}
|
|
|
|
// --------- DateTimeFactory
|
|
|
|
/* static */ UnicodeString StandardFunctions::getStringOption(const FunctionOptions& opts,
|
|
std::u16string_view optionName,
|
|
UErrorCode& errorCode) {
|
|
if (U_SUCCESS(errorCode)) {
|
|
Formattable opt;
|
|
if (opts.getFunctionOption(optionName, opt)) {
|
|
return opt.getString(errorCode); // In case it's not a string, error code will be set
|
|
} else {
|
|
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
}
|
|
// Default is empty string
|
|
return {};
|
|
}
|
|
|
|
// Date/time options only
|
|
static UnicodeString defaultForOption(std::u16string_view optionName) {
|
|
if (optionName == options::DATE_STYLE
|
|
|| optionName == options::TIME_STYLE
|
|
|| optionName == options::STYLE) {
|
|
return UnicodeString(options::SHORT);
|
|
}
|
|
return {}; // Empty string is default
|
|
}
|
|
|
|
// TODO
|
|
// Only DateTime currently uses the function options stored in the placeholder.
|
|
// It also doesn't use them very consistently (it looks at the previous set of options,
|
|
// and others aren't preserved). This needs to be generalized,
|
|
// but that depends on https://github.com/unicode-org/message-format-wg/issues/515
|
|
// Finally, the option value is assumed to be a string,
|
|
// which works for datetime options but not necessarily in general.
|
|
UnicodeString StandardFunctions::DateTime::getFunctionOption(const FormattedPlaceholder& toFormat,
|
|
const FunctionOptions& opts,
|
|
std::u16string_view optionName) const {
|
|
// Options passed to the current function invocation take priority
|
|
Formattable opt;
|
|
UnicodeString s;
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
s = getStringOption(opts, optionName, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return s;
|
|
}
|
|
// Next try the set of options used to construct `toFormat`
|
|
localErrorCode = U_ZERO_ERROR;
|
|
s = getStringOption(toFormat.options(), optionName, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return s;
|
|
}
|
|
// Finally, use default
|
|
return defaultForOption(optionName);
|
|
}
|
|
|
|
// Used for options that don't have defaults
|
|
UnicodeString StandardFunctions::DateTime::getFunctionOption(const FormattedPlaceholder& toFormat,
|
|
const FunctionOptions& opts,
|
|
std::u16string_view optionName,
|
|
UErrorCode& errorCode) const {
|
|
if (U_SUCCESS(errorCode)) {
|
|
// Options passed to the current function invocation take priority
|
|
Formattable opt;
|
|
UnicodeString s;
|
|
UErrorCode localErrorCode = U_ZERO_ERROR;
|
|
s = getStringOption(opts, optionName, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return s;
|
|
}
|
|
// Next try the set of options used to construct `toFormat`
|
|
localErrorCode = U_ZERO_ERROR;
|
|
s = getStringOption(toFormat.options(), optionName, localErrorCode);
|
|
if (U_SUCCESS(localErrorCode)) {
|
|
return s;
|
|
}
|
|
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
static DateFormat::EStyle stringToStyle(UnicodeString option, UErrorCode& errorCode) {
|
|
if (U_SUCCESS(errorCode)) {
|
|
UnicodeString upper = option.toUpper();
|
|
if (upper == options::FULL_UPPER) {
|
|
return DateFormat::EStyle::kFull;
|
|
}
|
|
if (upper == options::LONG_UPPER) {
|
|
return DateFormat::EStyle::kLong;
|
|
}
|
|
if (upper == options::MEDIUM_UPPER) {
|
|
return DateFormat::EStyle::kMedium;
|
|
}
|
|
if (upper == options::SHORT_UPPER) {
|
|
return DateFormat::EStyle::kShort;
|
|
}
|
|
if (upper.isEmpty() || upper == options::DEFAULT_UPPER) {
|
|
return DateFormat::EStyle::kDefault;
|
|
}
|
|
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
return DateFormat::EStyle::kNone;
|
|
}
|
|
|
|
/* static */ StandardFunctions::DateTimeFactory* StandardFunctions::DateTimeFactory::dateTime(UErrorCode& errorCode) {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
DateTimeFactory* result = new StandardFunctions::DateTimeFactory(DateTimeType::DateTime);
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* static */ StandardFunctions::DateTimeFactory* StandardFunctions::DateTimeFactory::date(UErrorCode& errorCode) {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
DateTimeFactory* result = new DateTimeFactory(DateTimeType::Date);
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* static */ StandardFunctions::DateTimeFactory* StandardFunctions::DateTimeFactory::time(UErrorCode& errorCode) {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
DateTimeFactory* result = new DateTimeFactory(DateTimeType::Time);
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Formatter* StandardFunctions::DateTimeFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
Formatter* result = new StandardFunctions::DateTime(locale, type);
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
FormattedPlaceholder StandardFunctions::DateTime::format(FormattedPlaceholder&& toFormat,
|
|
FunctionOptions&& opts,
|
|
UErrorCode& errorCode) const {
|
|
if (U_FAILURE(errorCode)) {
|
|
return {};
|
|
}
|
|
|
|
// Argument must be present
|
|
if (!toFormat.canFormat()) {
|
|
errorCode = U_MF_OPERAND_MISMATCH_ERROR;
|
|
return std::move(toFormat);
|
|
}
|
|
|
|
LocalPointer<DateFormat> df;
|
|
Formattable opt;
|
|
|
|
DateFormat::EStyle dateStyle = DateFormat::kShort;
|
|
DateFormat::EStyle timeStyle = DateFormat::kShort;
|
|
|
|
UnicodeString dateStyleName("dateStyle");
|
|
UnicodeString timeStyleName("timeStyle");
|
|
UnicodeString styleName("style");
|
|
|
|
bool hasDateStyleOption = opts.getFunctionOption(dateStyleName, opt);
|
|
bool hasTimeStyleOption = opts.getFunctionOption(timeStyleName, opt);
|
|
bool noOptions = opts.optionsCount() == 0;
|
|
|
|
bool useStyle = (type == DateTimeFactory::DateTimeType::DateTime
|
|
&& (hasDateStyleOption || hasTimeStyleOption
|
|
|| noOptions))
|
|
|| (type != DateTimeFactory::DateTimeType::DateTime);
|
|
|
|
bool useDate = type == DateTimeFactory::DateTimeType::Date
|
|
|| (type == DateTimeFactory::DateTimeType::DateTime
|
|
&& hasDateStyleOption);
|
|
bool useTime = type == DateTimeFactory::DateTimeType::Time
|
|
|| (type == DateTimeFactory::DateTimeType::DateTime
|
|
&& hasTimeStyleOption);
|
|
|
|
if (useStyle) {
|
|
// Extract style options
|
|
if (type == DateTimeFactory::DateTimeType::DateTime) {
|
|
// Note that the options-getting has to be repeated across the three cases,
|
|
// since `:datetime` uses "dateStyle"/"timeStyle" and `:date` and `:time`
|
|
// use "style"
|
|
dateStyle = stringToStyle(getFunctionOption(toFormat, opts, dateStyleName), errorCode);
|
|
timeStyle = stringToStyle(getFunctionOption(toFormat, opts, timeStyleName), errorCode);
|
|
|
|
if (useDate && !useTime) {
|
|
df.adoptInstead(DateFormat::createDateInstance(dateStyle, locale));
|
|
} else if (useTime && !useDate) {
|
|
df.adoptInstead(DateFormat::createTimeInstance(timeStyle, locale));
|
|
} else {
|
|
df.adoptInstead(DateFormat::createDateTimeInstance(dateStyle, timeStyle, locale));
|
|
}
|
|
} else if (type == DateTimeFactory::DateTimeType::Date) {
|
|
dateStyle = stringToStyle(getFunctionOption(toFormat, opts, styleName), errorCode);
|
|
df.adoptInstead(DateFormat::createDateInstance(dateStyle, locale));
|
|
} else {
|
|
// :time
|
|
timeStyle = stringToStyle(getFunctionOption(toFormat, opts, styleName), errorCode);
|
|
df.adoptInstead(DateFormat::createTimeInstance(timeStyle, locale));
|
|
}
|
|
} else {
|
|
// Build up a skeleton based on the field options, then use that to
|
|
// create the date formatter
|
|
|
|
UnicodeString skeleton;
|
|
#define ADD_PATTERN(s) skeleton += UnicodeString(s)
|
|
if (U_SUCCESS(errorCode)) {
|
|
// Year
|
|
UnicodeString year = getFunctionOption(toFormat, opts, options::YEAR, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_ZERO_ERROR;
|
|
} else {
|
|
useDate = true;
|
|
if (year == options::TWO_DIGIT) {
|
|
ADD_PATTERN("YY");
|
|
} else if (year == options::NUMERIC) {
|
|
ADD_PATTERN("YYYY");
|
|
}
|
|
}
|
|
// Month
|
|
UnicodeString month = getFunctionOption(toFormat, opts, options::MONTH, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_ZERO_ERROR;
|
|
} else {
|
|
useDate = true;
|
|
/* numeric, 2-digit, long, short, narrow */
|
|
if (month == options::LONG) {
|
|
ADD_PATTERN("MMMM");
|
|
} else if (month == options::SHORT) {
|
|
ADD_PATTERN("MMM");
|
|
} else if (month == options::NARROW) {
|
|
ADD_PATTERN("MMMMM");
|
|
} else if (month == options::NUMERIC) {
|
|
ADD_PATTERN("M");
|
|
} else if (month == options::TWO_DIGIT) {
|
|
ADD_PATTERN("MM");
|
|
}
|
|
}
|
|
// Weekday
|
|
UnicodeString weekday = getFunctionOption(toFormat, opts, options::WEEKDAY, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_ZERO_ERROR;
|
|
} else {
|
|
useDate = true;
|
|
if (weekday == options::LONG) {
|
|
ADD_PATTERN("EEEE");
|
|
} else if (weekday == options::SHORT) {
|
|
ADD_PATTERN("EEEEE");
|
|
} else if (weekday == options::NARROW) {
|
|
ADD_PATTERN("EEEEE");
|
|
}
|
|
}
|
|
// Day
|
|
UnicodeString day = getFunctionOption(toFormat, opts, options::DAY, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_ZERO_ERROR;
|
|
} else {
|
|
useDate = true;
|
|
if (day == options::NUMERIC) {
|
|
ADD_PATTERN("d");
|
|
} else if (day == options::TWO_DIGIT) {
|
|
ADD_PATTERN("dd");
|
|
}
|
|
}
|
|
// Hour
|
|
UnicodeString hour = getFunctionOption(toFormat, opts, options::HOUR, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_ZERO_ERROR;
|
|
} else {
|
|
useTime = true;
|
|
if (hour == options::NUMERIC) {
|
|
ADD_PATTERN("h");
|
|
} else if (hour == options::TWO_DIGIT) {
|
|
ADD_PATTERN("hh");
|
|
}
|
|
}
|
|
// Minute
|
|
UnicodeString minute = getFunctionOption(toFormat, opts, options::MINUTE, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_ZERO_ERROR;
|
|
} else {
|
|
useTime = true;
|
|
if (minute == options::NUMERIC) {
|
|
ADD_PATTERN("m");
|
|
} else if (minute == options::TWO_DIGIT) {
|
|
ADD_PATTERN("mm");
|
|
}
|
|
}
|
|
// Second
|
|
UnicodeString second = getFunctionOption(toFormat, opts, options::SECOND, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_ZERO_ERROR;
|
|
} else {
|
|
useTime = true;
|
|
if (second == options::NUMERIC) {
|
|
ADD_PATTERN("s");
|
|
} else if (second == options::TWO_DIGIT) {
|
|
ADD_PATTERN("ss");
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
TODO
|
|
fractionalSecondDigits
|
|
hourCycle
|
|
timeZoneName
|
|
era
|
|
*/
|
|
df.adoptInstead(DateFormat::createInstanceForSkeleton(skeleton, errorCode));
|
|
}
|
|
|
|
if (U_FAILURE(errorCode)) {
|
|
return {};
|
|
}
|
|
if (!df.isValid()) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
return {};
|
|
}
|
|
|
|
UnicodeString result;
|
|
const Formattable& source = toFormat.asFormattable();
|
|
switch (source.getType()) {
|
|
case UFMT_STRING: {
|
|
const UnicodeString& sourceStr = source.getString(errorCode);
|
|
U_ASSERT(U_SUCCESS(errorCode));
|
|
// Pattern for ISO 8601 format - datetime
|
|
UnicodeString pattern("YYYY-MM-dd'T'HH:mm:ss");
|
|
LocalPointer<DateFormat> dateParser(new SimpleDateFormat(pattern, errorCode));
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_MF_FORMATTING_ERROR;
|
|
} else {
|
|
// Parse the date
|
|
UDate d = dateParser->parse(sourceStr, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
// Pattern for ISO 8601 format - date
|
|
UnicodeString pattern("YYYY-MM-dd");
|
|
errorCode = U_ZERO_ERROR;
|
|
dateParser.adoptInstead(new SimpleDateFormat(pattern, errorCode));
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_MF_FORMATTING_ERROR;
|
|
} else {
|
|
d = dateParser->parse(sourceStr, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
errorCode = U_MF_OPERAND_MISMATCH_ERROR;
|
|
}
|
|
}
|
|
}
|
|
// Use the parsed date as the source value
|
|
// in the returned FormattedPlaceholder; this is necessary
|
|
// so the date can be re-formatted
|
|
toFormat = FormattedPlaceholder(message2::Formattable::forDate(d),
|
|
toFormat.getFallback());
|
|
df->format(d, result, 0, errorCode);
|
|
}
|
|
break;
|
|
}
|
|
case UFMT_DATE: {
|
|
df->format(source.asICUFormattable(errorCode), result, 0, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
if (errorCode == U_ILLEGAL_ARGUMENT_ERROR) {
|
|
errorCode = U_MF_OPERAND_MISMATCH_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// Any other cases are an error
|
|
default: {
|
|
errorCode = U_MF_OPERAND_MISMATCH_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
if (U_FAILURE(errorCode)) {
|
|
return {};
|
|
}
|
|
return FormattedPlaceholder(toFormat, std::move(opts), FormattedValue(std::move(result)));
|
|
}
|
|
|
|
StandardFunctions::DateTimeFactory::~DateTimeFactory() {}
|
|
StandardFunctions::DateTime::~DateTime() {}
|
|
|
|
// --------- TextFactory
|
|
|
|
Selector* StandardFunctions::TextFactory::createSelector(const Locale& locale, UErrorCode& errorCode) const {
|
|
Selector* result = new TextSelector(locale);
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
return nullptr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void StandardFunctions::TextSelector::selectKey(FormattedPlaceholder&& toFormat,
|
|
FunctionOptions&& opts,
|
|
const UnicodeString* keys,
|
|
int32_t keysLen,
|
|
UnicodeString* prefs,
|
|
int32_t& prefsLen,
|
|
UErrorCode& errorCode) const {
|
|
// No options
|
|
(void) opts;
|
|
|
|
CHECK_ERROR(errorCode);
|
|
|
|
// Just compares the key and value as strings
|
|
|
|
// Argument must be present
|
|
if (!toFormat.canFormat()) {
|
|
errorCode = U_MF_SELECTOR_ERROR;
|
|
return;
|
|
}
|
|
|
|
prefsLen = 0;
|
|
|
|
// Convert to string
|
|
const UnicodeString& formattedValue = toFormat.formatToString(locale, errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
return;
|
|
}
|
|
// Normalize result
|
|
UnicodeString normalized = normalizeNFC(formattedValue);
|
|
|
|
for (int32_t i = 0; i < keysLen; i++) {
|
|
if (keys[i] == normalized) {
|
|
prefs[0] = keys[i];
|
|
prefsLen = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
StandardFunctions::TextFactory::~TextFactory() {}
|
|
StandardFunctions::TextSelector::~TextSelector() {}
|
|
|
|
// ------------ TestFormatFactory
|
|
|
|
Formatter* StandardFunctions::TestFormatFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
// Results are not locale-dependent
|
|
(void) locale;
|
|
|
|
Formatter* result = new TestFormat();
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
StandardFunctions::TestFormatFactory::~TestFormatFactory() {}
|
|
StandardFunctions::TestFormat::~TestFormat() {}
|
|
|
|
// Extract numeric value from a Formattable or, if it's a string,
|
|
// parse it as a number according to the MF2 `number-literal` grammar production
|
|
double formattableToNumber(const Formattable& arg, UErrorCode& status) {
|
|
if (U_FAILURE(status)) {
|
|
return 0;
|
|
}
|
|
|
|
double result = 0;
|
|
|
|
switch (arg.getType()) {
|
|
case UFMT_DOUBLE: {
|
|
result = arg.getDouble(status);
|
|
U_ASSERT(U_SUCCESS(status));
|
|
break;
|
|
}
|
|
case UFMT_LONG: {
|
|
result = (double) arg.getLong(status);
|
|
U_ASSERT(U_SUCCESS(status));
|
|
break;
|
|
}
|
|
case UFMT_INT64: {
|
|
result = (double) arg.getInt64(status);
|
|
U_ASSERT(U_SUCCESS(status));
|
|
break;
|
|
}
|
|
case UFMT_STRING: {
|
|
// Try to parse the string as a number
|
|
result = parseNumberLiteral(arg, status);
|
|
if (U_FAILURE(status)) {
|
|
status = U_MF_OPERAND_MISMATCH_ERROR;
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
// Other types can't be parsed as a number
|
|
status = U_MF_OPERAND_MISMATCH_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/* static */ void StandardFunctions::TestFormat::testFunctionParameters(const FormattedPlaceholder& arg,
|
|
const FunctionOptions& options,
|
|
int32_t& decimalPlaces,
|
|
bool& failsFormat,
|
|
bool& failsSelect,
|
|
double& input,
|
|
UErrorCode& status) {
|
|
CHECK_ERROR(status);
|
|
|
|
// 1. Let DecimalPlaces be 0.
|
|
decimalPlaces = 0;
|
|
|
|
// 2. Let FailsFormat be false.
|
|
failsFormat = false;
|
|
|
|
// 3. Let FailsSelect be false.
|
|
failsSelect = false;
|
|
|
|
// 4. Let arg be the resolved value of the expression operand.
|
|
// (already true)
|
|
|
|
// Step 5 omitted because composition isn't fully implemented yet
|
|
// 6. Else if arg is a numerical value or a string matching the number-literal production, then
|
|
input = formattableToNumber(arg.asFormattable(), status);
|
|
if (U_FAILURE(status)) {
|
|
// 7. Else,
|
|
// 7i. Emit "bad-input" Resolution Error.
|
|
status = U_MF_OPERAND_MISMATCH_ERROR;
|
|
// 7ii. Use a fallback value as the resolved value of the expression.
|
|
// Further steps of this algorithm are not followed.
|
|
}
|
|
// 8. If the decimalPlaces option is set, then
|
|
Formattable opt;
|
|
if (options.getFunctionOption(options::DECIMAL_PLACES, opt)) {
|
|
// 8i. If its value resolves to a numerical integer value 0 or 1
|
|
// or their corresponding string representations '0' or '1', then
|
|
double decimalPlacesInput = formattableToNumber(opt, status);
|
|
if (U_SUCCESS(status)) {
|
|
if (decimalPlacesInput == 0 || decimalPlacesInput == 1) {
|
|
// 8ia. Set DecimalPlaces to be the numerical value of the option.
|
|
decimalPlaces = decimalPlacesInput;
|
|
}
|
|
}
|
|
// 8ii. Else if its value is not an unresolved value set by option resolution,
|
|
else {
|
|
// 8iia. Emit "bad-option" Resolution Error.
|
|
status = U_MF_BAD_OPTION;
|
|
// 8iib. Use a fallback value as the resolved value of the expression.
|
|
}
|
|
}
|
|
// 9. If the fails option is set, then
|
|
Formattable failsOpt;
|
|
if (options.getFunctionOption(options::FAILS, failsOpt)) {
|
|
UnicodeString failsString = failsOpt.getString(status);
|
|
if (U_SUCCESS(status)) {
|
|
// 9i. If its value resolves to the string 'always', then
|
|
if (failsString == u"always") {
|
|
// 9ia. Set FailsFormat to be true
|
|
failsFormat = true;
|
|
// 9ib. Set FailsSelect to be true.
|
|
failsSelect = true;
|
|
}
|
|
// 9ii. Else if its value resolves to the string "format", then
|
|
else if (failsString == u"format") {
|
|
// 9ia. Set FailsFormat to be true
|
|
failsFormat = true;
|
|
}
|
|
// 9iii. Else if its value resolves to the string "select", then
|
|
else if (failsString == u"select") {
|
|
// 9iiia. Set FailsSelect to be true.
|
|
failsSelect = true;
|
|
}
|
|
// 9iv. Else if its value does not resolve to the string "never", then
|
|
else if (failsString != u"never") {
|
|
// 9iv(a). Emit "bad-option" Resolution Error.
|
|
status = U_MF_BAD_OPTION;
|
|
}
|
|
} else {
|
|
// 9iv. again
|
|
status = U_MF_BAD_OPTION;
|
|
}
|
|
}
|
|
}
|
|
|
|
FormattedPlaceholder StandardFunctions::TestFormat::format(FormattedPlaceholder&& arg,
|
|
FunctionOptions&& options,
|
|
UErrorCode& status) const{
|
|
|
|
int32_t decimalPlaces;
|
|
bool failsFormat;
|
|
bool failsSelect;
|
|
double input;
|
|
|
|
testFunctionParameters(arg, options, decimalPlaces,
|
|
failsFormat, failsSelect, input, status);
|
|
if (U_FAILURE(status)) {
|
|
return FormattedPlaceholder(arg.getFallback());
|
|
}
|
|
|
|
// If FailsFormat is true, attempting to format the placeholder to any
|
|
// formatting target will fail.
|
|
if (failsFormat) {
|
|
status = U_MF_FORMATTING_ERROR;
|
|
return FormattedPlaceholder(arg.getFallback());
|
|
}
|
|
UnicodeString result;
|
|
// When :test:function is used as a formatter, a placeholder resolving to a value
|
|
// with a :test:function expression is formatted as a concatenation of the following parts:
|
|
// 1. If Input is less than 0, the character - U+002D Hyphen-Minus.
|
|
if (input < 0) {
|
|
result += HYPHEN;
|
|
}
|
|
// 2. The truncated absolute integer value of Input, i.e. floor(abs(Input)), formatted as a
|
|
// sequence of decimal digit characters (U+0030...U+0039).
|
|
char buffer[256];
|
|
bool ignore;
|
|
int ignoreLen;
|
|
int ignorePoint;
|
|
double_conversion::DoubleToStringConverter::DoubleToAscii(floor(abs(input)),
|
|
double_conversion::DoubleToStringConverter::DtoaMode::SHORTEST,
|
|
0,
|
|
buffer,
|
|
256,
|
|
&ignore,
|
|
&ignoreLen,
|
|
&ignorePoint);
|
|
result += UnicodeString(buffer);
|
|
// 3. If DecimalPlaces is 1, then
|
|
if (decimalPlaces == 1) {
|
|
// 3i. The character . U+002E Full Stop.
|
|
result += u".";
|
|
// 3ii. The single decimal digit character representing the value
|
|
// floor((abs(Input) - floor(abs(Input))) * 10)
|
|
int32_t val = floor((abs(input) - floor(abs(input)) * 10));
|
|
result += digitToChar(val, status);
|
|
U_ASSERT(U_SUCCESS(status));
|
|
}
|
|
return FormattedPlaceholder(result);
|
|
}
|
|
|
|
// ------------ TestSelectFactory
|
|
|
|
StandardFunctions::TestSelectFactory::~TestSelectFactory() {}
|
|
StandardFunctions::TestSelect::~TestSelect() {}
|
|
|
|
Selector* StandardFunctions::TestSelectFactory::createSelector(const Locale& locale,
|
|
UErrorCode& errorCode) const {
|
|
NULL_ON_ERROR(errorCode);
|
|
|
|
// Results are not locale-dependent
|
|
(void) locale;
|
|
|
|
Selector* result = new TestSelect();
|
|
if (result == nullptr) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void StandardFunctions::TestSelect::selectKey(FormattedPlaceholder&& val,
|
|
FunctionOptions&& options,
|
|
const UnicodeString* keys,
|
|
int32_t keysLen,
|
|
UnicodeString* prefs,
|
|
int32_t& prefsLen,
|
|
UErrorCode& status) const {
|
|
int32_t decimalPlaces;
|
|
bool failsFormat;
|
|
bool failsSelect;
|
|
double input;
|
|
|
|
TestFormat::testFunctionParameters(val, options, decimalPlaces,
|
|
failsFormat, failsSelect, input, status);
|
|
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
|
|
if (failsSelect) {
|
|
status = U_MF_SELECTOR_ERROR;
|
|
return;
|
|
}
|
|
|
|
// If the Input is 1 and DecimalPlaces is 1, the method will return some slice
|
|
// of the list « '1.0', '1' », depending on whether those values are included in keys.
|
|
bool include1point0 = false;
|
|
bool include1 = false;
|
|
if (input == 1 && decimalPlaces == 1) {
|
|
include1point0 = true;
|
|
include1 = true;
|
|
} else if (input == 1 && decimalPlaces == 0) {
|
|
include1 = true;
|
|
}
|
|
|
|
// If the Input is 1 and DecimalPlaces is 0, the method will return the list « '1' » if
|
|
// keys includes '1', or an empty list otherwise.
|
|
// If the Input is any other value, the method will return an empty list.
|
|
for (int32_t i = 0; i < keysLen; i++) {
|
|
if ((keys[i] == u"1" && include1)
|
|
|| (keys[i] == u"1.0" && include1point0)) {
|
|
prefs[prefsLen] = keys[i];
|
|
prefsLen++;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace message2
|
|
U_NAMESPACE_END
|
|
|
|
#endif /* #if !UCONFIG_NO_MF2 */
|
|
|
|
#endif /* #if !UCONFIG_NO_FORMATTING */
|
|
|
|
#endif /* #if !UCONFIG_NO_NORMALIZATION */
|