From bdae81990851c07c1c069322991470a97f2bb426 Mon Sep 17 00:00:00 2001 From: gnosygnu Date: Wed, 12 Aug 2020 08:48:30 -0400 Subject: [PATCH] Scribunto: Trim zeroes when stringifying Doubles [#697] --- 100_core/src/gplx/Double_.java | 77 ++-- .../libs/Scrib_lib_language_tst.java | 375 +++++++++--------- .../src/gplx/objects/primitives/Double_.java | 104 ++++- .../gplx/objects/primitives/Double_Test.java | 51 +++ res/bin/any/java/luaj/luaj_xowa.jar | Bin 379399 -> 379626 bytes 5 files changed, 385 insertions(+), 222 deletions(-) create mode 100644 baselib/tst/gplx/objects/primitives/Double_Test.java diff --git a/100_core/src/gplx/Double_.java b/100_core/src/gplx/Double_.java index 18f12e217..841ccf272 100644 --- a/100_core/src/gplx/Double_.java +++ b/100_core/src/gplx/Double_.java @@ -1,6 +1,6 @@ /* XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com +Copyright (C) 2012-2020 gnosygnu@gmail.com XOWA is licensed under the terms of the General Public License (GPL) Version 3, or alternatively under the terms of the Apache License Version 2.0. @@ -13,40 +13,43 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx; +package gplx; + public class Double_ { - public static final String Cls_val_name = "double"; - public static final Class Cls_ref_type = Double.class; - public static final double - MinValue = Double.MIN_VALUE - , NaN = Double.NaN - , Inf_pos = Double.POSITIVE_INFINITY - ; - public static final byte[] - NaN_bry = Bry_.new_a7("NaN") - , Inf_pos_bry = Bry_.new_a7("INF") - ; - public static boolean IsNaN(double v) {return Double.isNaN(v);} - public static double cast(Object o) {try {return (Double)o;} catch(Exception e) {throw Err_.new_type_mismatch_w_exc(e, double.class, o);}} - public static double parse(String raw) {try {return Double.parseDouble(raw);} catch(Exception e) {throw Err_.new_parse_exc(e, double.class, raw);}} - public static double parse_or(String raw, double v) {try {return Double.parseDouble(raw);} catch(Exception e) {Err_.Noop(e); return v;}} - public static double coerce_(Object v) { - try {String s = String_.as_(v); return s == null ? Double_.cast(v) : Double_.parse(s);} - catch (Exception e) {throw Err_.new_cast(e, double.class, v);} - } - public static String To_str(double v) { - int v_int = (int)v; - return v - v_int == 0 ? Int_.To_str(v_int) : Double.toString(v); - } - public static String To_str_loose(double v) { - int v_as_int = (int)v; - return v == v_as_int - ? Int_.To_str(v_as_int) // convert to int, and call print String to eliminate any trailing decimal places - : Float_.To_str((float)v); // calling ((float)v).toString is better at removing trailing 0s than String.format("%g", v). note that .net .toString() handles it better; EX:2449.600000000000d; DATE:2014-07-29 - } - public static int Compare(double lhs, double rhs) { - if (lhs == rhs) return CompareAble_.Same; - else if (lhs < rhs) return CompareAble_.Less; - else return CompareAble_.More; - } -} + public static final String Cls_val_name = "double"; + public static final Class Cls_ref_type = Double.class; + public static final double + MinValue = Double.MIN_VALUE + , NaN = Double.NaN + , Inf_pos = Double.POSITIVE_INFINITY + ; + public static final byte[] + NaN_bry = Bry_.new_a7("NaN") + , Inf_pos_bry = Bry_.new_a7("INF") + ; + public static boolean IsNaN(double v) {return Double.isNaN(v);} + public static double cast(Object o) {try {return (Double)o;} catch(Exception e) {throw Err_.new_type_mismatch_w_exc(e, double.class, o);}} + public static double parse(String raw) {try {return Double.parseDouble(raw);} catch(Exception e) {throw Err_.new_parse_exc(e, double.class, raw);}} + public static double parse_or(String raw, double v) {try {return Double.parseDouble(raw);} catch(Exception e) {Err_.Noop(e); return v;}} + public static double coerce_(Object v) { + try {String s = String_.as_(v); return s == null ? Double_.cast(v) : Double_.parse(s);} + catch (Exception e) {throw Err_.new_cast(e, double.class, v);} + } + public static String To_str(double v) { + int v_int = (int)v; + return v - v_int == 0 ? Int_.To_str(v_int) : Double.toString(v); + } + public static String To_str_loose(double v) { + int v_as_int = (int)v; + return v == v_as_int + ? Int_.To_str(v_as_int) // convert to int, and call print String to eliminate any trailing decimal places + // DATE:2014-07-29; calling ((float)v).toString is better at removing trailing 0s than String.format("%g", v). note that .net .toString() handles it better; EX:2449.600000000000d + // DATE:2020-08-12; calling ToStrByPrintF b/c better at removing trailing 0s; ISSUE#:697; + : gplx.objects.primitives.Double_.ToStrByPrintF(v); + } + public static int Compare(double lhs, double rhs) { + if (lhs == rhs) return CompareAble_.Same; + else if (lhs < rhs) return CompareAble_.Less; + else return CompareAble_.More; + } +} diff --git a/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_language_tst.java b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_language_tst.java index 3069fc023..1f06cdd37 100644 --- a/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_language_tst.java +++ b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_language_tst.java @@ -1,6 +1,6 @@ /* XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com +Copyright (C) 2012-2020 gnosygnu@gmail.com XOWA is licensed under the terms of the General Public License (GPL) Version 3, or alternatively under the terms of the Apache License Version 2.0. @@ -13,181 +13,198 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx.xowa.xtns.scribunto.libs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.scribunto.*; -import org.junit.*; -import gplx.langs.jsons.*; -import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.numbers.*; -import gplx.xowa.xtns.cldrs.*; -public class Scrib_lib_language_tst { - @Before public void init() { - fxt.Clear_for_lib(); - lib = fxt.Core().Lib_language().Init(); - } private Scrib_invoke_func_fxt fxt = new Scrib_invoke_func_fxt(); private Scrib_lib lib; - @Test public void GetContLangCode() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_getContLangCode, Object_.Ary_empty, "en"); - } - @Test public void IsSupportedLanguage() { - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isSupportedLanguage, Object_.Ary("fr"), true); - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isSupportedLanguage, Object_.Ary("qq"), false); - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isSupportedLanguage, Object_.Ary("EN"), false); - } - @Test public void IsKnownLanguageTag() { - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isKnownLanguageTag, Object_.Ary("fr"), true); - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isKnownLanguageTag, Object_.Ary("qq"), false); - } - @Test public void IsKnownLanguageTag_cldr() { - Io_mgr.Instance.InitEngine_mem(); - Cldr_name_loader_fxt.Create_file_w_langs(fxt.Core().Lang().Key_str(), Keyval_.Ary(Keyval_.new_("goh", "goh_name"))); - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isKnownLanguageTag, Object_.Ary("goh"), true); - } - @Test public void IsValidCode() { - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isValidCode, Object_.Ary("a,b"), true); - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isValidCode, Object_.Ary("a'b"), false); - } - @Test public void IsValidBuiltInCode() { - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isValidBuiltInCode, Object_.Ary("e-N"), true); - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isValidBuiltInCode, Object_.Ary("e n"), false); - } - @Test public void FetchLanguageName() { - Io_mgr.Instance.SaveFilStr("mem/xowa/bin/any/xowa/cfg/lang/data/names.json", Json_doc.Make_str_by_apos - ( "[" - , " {" - , " 'code':'en'" - , " , 'name':'English'" - , " , 'note':'en_note'" - , " }" - , ", {" - , " 'code':'fr'" - , " , 'name':'Français'" - , " , 'note':'fr_note'" - , " }" - , "]" - - )); - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_fetchLanguageName, Object_.Ary("en"), "English"); - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_fetchLanguageName, Object_.Ary("fr"), "Français"); - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_fetchLanguageName, Object_.Ary("qz"), ""); - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_fetchLanguageName, Object_.Ary("459"), ""); // PAGE:en.w:United_States_Strategic_Bombing_Survey; DATE:2018-07-01 - } - @Test public void GetFallbacksFor() { - Xol_lang_itm other_lang = fxt.Core().App().Lang_mgr().Get_by_or_new(Bry_.new_a7("zh")); - other_lang.Fallback_bry_(Bry_.new_a7("gan-hant, zh-hant, zh-hans")); - fxt.Test_scrib_proc_str_ary(lib, Scrib_lib_language.Invk_getFallbacksFor, Object_.Ary("zh"), String_.Concat_lines_nl - ( "1=" - , " 1=gan-hant" - , " 2=zh-hant" - , " 3=zh-hans" - , " 4=en" // auto-add en - )); - } - @Test public void GetFallbacksFor_unknown() { - fxt.Test_scrib_proc_str_ary(lib, Scrib_lib_language.Invk_getFallbacksFor, Object_.Ary("unknown"), String_.Concat_lines_nl - ( "1=" - , " 1=en" // use "en" if unknown; REF:/languages/Language.php|getFallbacksFor; ISSUE#:340; DATE:2019-02-01 - )); - } - @Test public void FormatNum() { - // init - Xol_lang_itm other_lang = fxt.Core().App().Lang_mgr().Get_by_or_load(Bry_.new_a7("de")); // NOTE: must call load now, else load will be called by scrib and sprs below will get overwritten during load; - fxt.Parser_fxt().Init_lang_numbers_separators(other_lang, ".", ","); - - // test - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("de", 1234), "1.234"); // german spr - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", 1234), "1,234"); // english spr - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", "1234"), "1,234"); // String passed (not int) - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", "1234", Keyval_.Ary(Keyval_.new_("noCommafy", true))) , "1234"); // noCommafy.y - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", "1234", Keyval_.Ary(Keyval_.new_("noCommafy", false))) , "1,234"); // noCommafy.n - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", "1234", Keyval_.Ary(Keyval_.new_("noCommafy", "y"))) , "1234"); // noCommafy."y"; ISSUE#:372 DATE:2019-03-02 - } - @Test public void FormatDate() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", "2013-03-17", false), "2013-03-17"); - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d"), Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd()); // empty date should default to today; - } - @Test public void FormatDate__ymd_12() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", "201603160102", false), "2016-03-16"); // handle long numeric date (12 digits); PAGE:en.w:Boron; DATE:2015-07-29 - } - @Test public void FormatDate__utc() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", "+00000002010-05-01T00:00:00Z", false), "2010-05-01"); // handle Wikidata style dates; PAGE:en.w:Mountain_Province; DATE:2015-07-29 - } - @Test public void FormatDate__bce() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", "+0065-12-08T00:00:00Z", false), "0065-12-08"); // ISSUE#:500 - } - @Test public void FormatDate_date_omitted() { // PURPOSE: some calls skip the date; retrieve arg_4 by int; EX: pl.w:L._Frank_Baum - Datetime_now.Manual_y_(); - Datetime_now.Manual_(DateAdp_.new_(2013, 12, 19, 1, 2, 3, 4)); - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Keyval_.Ary(Keyval_.int_(1, "en"), Keyval_.int_(2, "Y-m-d"), Keyval_.int_(4, false)), "2013-12-19"); - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", ""), "2013-12-19");// PURPOSE: '' should return today, not fail; EX: th.w:สถานีรถไฟตรัง - Datetime_now.Manual_n_(); - } - @Test public void Lc() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_lc, Object_.Ary("en", "ABC"), "abc"); - } - @Test public void Uc() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_uc, Object_.Ary("en", "abc"), "ABC"); - } - @Test public void LcFirst() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_lcfirst, Object_.Ary("en", "ABC"), "aBC"); - } - @Test public void UcFirst() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_uc, Object_.Ary("en", "abc"), "ABC"); - } - @Test public void ParseFormattedNumber() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en", "1,234.56") , "1234.56"); // formatted String - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en", "1234") , "1234"); // String - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en", 1234) , "1234"); // int - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en", 1234.56) , "1234.56"); // double - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en"), Scrib_invoke_func_fxt.Null_rslt); // PURPOSE: missing arg should not fail; EX: ru.w:Туйон DATE:2014-01-06 - } - @Test public void ConvertGrammar() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_convertGrammar, Object_.Ary("fi", "talo", "elative"), "talosta"); - } - @Test public void ConvertPlural() { - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_convertPlural, Object_.Ary("ru", 5, Kv_ary_("a", "b", "c")), "c"); // forms in kv_ary - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_convertPlural, Object_.Ary("ru", 5, "a", "b", "c"), "c"); // forms as rest of ary; PAGE:ru.w:Ленин,_Владимир_Ильич DATE:2014-07-01 - } - @Test public void IsRTL() { - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isRTL, Object_.Ary("en"), false); - Xol_lang_itm other_lang = fxt.Core().App().Lang_mgr().Get_by_or_new(Bry_.new_a7("ar")); - Gfo_invk_.Invk_by_val(other_lang, Xol_lang_itm.Invk_dir_rtl_, true); - fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isRTL, Object_.Ary("ar"), true); - } - @Test public void Format_duration() { - Init_lang_durations(fxt.Core().Wiki()); - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDuration, Object_.Ary("en", 3723d, Kv_ary_("hours", "minutes", "seconds")), "1 hour, 2 minutes and 3 seconds"); // basic - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDuration, Object_.Ary("en", 123d, Kv_ary_("hours", "minutes", "seconds")), "2 minutes and 3 seconds"); // omit hour since < 1 - fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDuration, Object_.Ary("en", 123d, Kv_ary_("hours")) , "0 hours"); // handle fractional duration - } - @Test public void Get_duration_intervals() { - Init_lang_durations(fxt.Core().Wiki()); - fxt.Test_scrib_proc_str_ary(lib, Scrib_lib_language.Invk_getDurationIntervals, Object_.Ary("en", 3723d, Kv_ary_("hours", "minutes", "seconds")), String_.Concat_lines_nl_skip_last - ( "1=" - , " hours=1" - , " minutes=2" - , " seconds=3" - )); - } - public static Keyval[] Kv_ary_(String... ary) { - int ary_len = ary.length; - Keyval[] rv = new Keyval[ary_len]; - for (int i = 0; i < ary_len; i++) { - rv[i] = Keyval_.int_(i, ary[i]); - } - return rv; - } - public static void Init_lang_durations(Xowe_wiki wiki) { - Xol_msg_mgr msg_mgr = wiki.Lang().Msg_mgr(); - msg_mgr.Clear(); - msg_mgr.Itm_by_key_or_new("duration-millenia" , "~{0} {{PLURAL:~{0}\u007Cmillennium\u007Cmillennia}}"); - msg_mgr.Itm_by_key_or_new("duration-centuries" , "~{0} {{PLURAL:~{0}\u007Ccentury\u007Ccenturies}}"); - msg_mgr.Itm_by_key_or_new("duration-decades" , "~{0} {{PLURAL:~{0}\u007Cdecade\u007Cdecades}}"); - msg_mgr.Itm_by_key_or_new("duration-years" , "~{0} {{PLURAL:~{0}\u007Cyear\u007Cyears}}"); - msg_mgr.Itm_by_key_or_new("duration-weeks" , "~{0} {{PLURAL:~{0}\u007Cweek\u007Cweeks}}"); - msg_mgr.Itm_by_key_or_new("duration-days" , "~{0} {{PLURAL:~{0}\u007Cday\u007Cdays}}"); - msg_mgr.Itm_by_key_or_new("duration-hours" , "~{0} {{PLURAL:~{0}\u007Chour\u007Chours}}"); - msg_mgr.Itm_by_key_or_new("duration-minutes" , "~{0} {{PLURAL:~{0}\u007Cminute\u007Cminutes}}"); - msg_mgr.Itm_by_key_or_new("duration-seconds" , "~{0} {{PLURAL:~{0}\u007Csecond\u007Cseconds}}"); - msg_mgr.Itm_by_key_or_new("and" , " and"); - msg_mgr.Itm_by_key_or_new("word-separator" , " "); - msg_mgr.Itm_by_key_or_new("comma-separator" , ", "); - } -} +package gplx.xowa.xtns.scribunto.libs; + +import gplx.Bry_; +import gplx.DateAdp_; +import gplx.Datetime_now; +import gplx.Gfo_invk_; +import gplx.Io_mgr; +import gplx.Keyval; +import gplx.Keyval_; +import gplx.Object_; +import gplx.String_; +import gplx.langs.jsons.Json_doc; +import gplx.xowa.Xowe_wiki; +import gplx.xowa.langs.Xol_lang_itm; +import gplx.xowa.langs.msgs.Xol_msg_mgr; +import gplx.xowa.xtns.cldrs.Cldr_name_loader_fxt; +import gplx.xowa.xtns.scribunto.Scrib_invoke_func_fxt; +import gplx.xowa.xtns.scribunto.Scrib_lib; +import org.junit.Before; +import org.junit.Test; + +public class Scrib_lib_language_tst { + @Before public void init() { + fxt.Clear_for_lib(); + lib = fxt.Core().Lib_language().Init(); + } private Scrib_invoke_func_fxt fxt = new Scrib_invoke_func_fxt(); private Scrib_lib lib; + @Test public void GetContLangCode() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_getContLangCode, Object_.Ary_empty, "en"); + } + @Test public void IsSupportedLanguage() { + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isSupportedLanguage, Object_.Ary("fr"), true); + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isSupportedLanguage, Object_.Ary("qq"), false); + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isSupportedLanguage, Object_.Ary("EN"), false); + } + @Test public void IsKnownLanguageTag() { + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isKnownLanguageTag, Object_.Ary("fr"), true); + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isKnownLanguageTag, Object_.Ary("qq"), false); + } + @Test public void IsKnownLanguageTag_cldr() { + Io_mgr.Instance.InitEngine_mem(); + Cldr_name_loader_fxt.Create_file_w_langs(fxt.Core().Lang().Key_str(), Keyval_.Ary(Keyval_.new_("goh", "goh_name"))); + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isKnownLanguageTag, Object_.Ary("goh"), true); + } + @Test public void IsValidCode() { + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isValidCode, Object_.Ary("a,b"), true); + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isValidCode, Object_.Ary("a'b"), false); + } + @Test public void IsValidBuiltInCode() { + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isValidBuiltInCode, Object_.Ary("e-N"), true); + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isValidBuiltInCode, Object_.Ary("e n"), false); + } + @Test public void FetchLanguageName() { + Io_mgr.Instance.SaveFilStr("mem/xowa/bin/any/xowa/cfg/lang/data/names.json", Json_doc.Make_str_by_apos + ( "[" + , " {" + , " 'code':'en'" + , " , 'name':'English'" + , " , 'note':'en_note'" + , " }" + , ", {" + , " 'code':'fr'" + , " , 'name':'Français'" + , " , 'note':'fr_note'" + , " }" + , "]" + + )); + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_fetchLanguageName, Object_.Ary("en"), "English"); + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_fetchLanguageName, Object_.Ary("fr"), "Français"); + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_fetchLanguageName, Object_.Ary("qz"), ""); + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_fetchLanguageName, Object_.Ary("459"), ""); // PAGE:en.w:United_States_Strategic_Bombing_Survey; DATE:2018-07-01 + } + @Test public void GetFallbacksFor() { + Xol_lang_itm other_lang = fxt.Core().App().Lang_mgr().Get_by_or_new(Bry_.new_a7("zh")); + other_lang.Fallback_bry_(Bry_.new_a7("gan-hant, zh-hant, zh-hans")); + fxt.Test_scrib_proc_str_ary(lib, Scrib_lib_language.Invk_getFallbacksFor, Object_.Ary("zh"), String_.Concat_lines_nl + ( "1=" + , " 1=gan-hant" + , " 2=zh-hant" + , " 3=zh-hans" + , " 4=en" // auto-add en + )); + } + @Test public void GetFallbacksFor_unknown() { + fxt.Test_scrib_proc_str_ary(lib, Scrib_lib_language.Invk_getFallbacksFor, Object_.Ary("unknown"), String_.Concat_lines_nl + ( "1=" + , " 1=en" // use "en" if unknown; REF:/languages/Language.php|getFallbacksFor; ISSUE#:340; DATE:2019-02-01 + )); + } + @Test public void FormatNum() { + // init + Xol_lang_itm other_lang = fxt.Core().App().Lang_mgr().Get_by_or_load(Bry_.new_a7("de")); // NOTE: must call load now, else load will be called by scrib and sprs below will get overwritten during load; + fxt.Parser_fxt().Init_lang_numbers_separators(other_lang, ".", ","); + + // test + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("de", 1234), "1.234"); // german spr + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", 1234), "1,234"); // english spr + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", "1234"), "1,234"); // String passed (not int) + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", "1234", Keyval_.Ary(Keyval_.new_("noCommafy", true))) , "1234"); // noCommafy.y + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", "1234", Keyval_.Ary(Keyval_.new_("noCommafy", false))) , "1,234"); // noCommafy.n + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", "1234", Keyval_.Ary(Keyval_.new_("noCommafy", "y"))) , "1234"); // noCommafy."y"; ISSUE#:372 DATE:2019-03-02 + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatNum, Object_.Ary("en", 768d / 10000000d) , "7.68E-5"); // ensure "e-05" -> "E-5" ISSUE#:697; DATE:2020-08-12 + } + @Test public void FormatDate() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", "2013-03-17", false), "2013-03-17"); + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d"), Datetime_now.Get().XtoStr_fmt_yyyy_MM_dd()); // empty date should default to today; + } + @Test public void FormatDate__ymd_12() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", "201603160102", false), "2016-03-16"); // handle long numeric date (12 digits); PAGE:en.w:Boron; DATE:2015-07-29 + } + @Test public void FormatDate__utc() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", "+00000002010-05-01T00:00:00Z", false), "2010-05-01"); // handle Wikidata style dates; PAGE:en.w:Mountain_Province; DATE:2015-07-29 + } + @Test public void FormatDate__bce() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", "+0065-12-08T00:00:00Z", false), "0065-12-08"); // ISSUE#:500 + } + @Test public void FormatDate_date_omitted() { // PURPOSE: some calls skip the date; retrieve arg_4 by int; EX: pl.w:L._Frank_Baum + Datetime_now.Manual_y_(); + Datetime_now.Manual_(DateAdp_.new_(2013, 12, 19, 1, 2, 3, 4)); + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Keyval_.Ary(Keyval_.int_(1, "en"), Keyval_.int_(2, "Y-m-d"), Keyval_.int_(4, false)), "2013-12-19"); + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDate, Object_.Ary("en", "Y-m-d", ""), "2013-12-19");// PURPOSE: '' should return today, not fail; EX: th.w:สถานีรถไฟตรัง + Datetime_now.Manual_n_(); + } + @Test public void Lc() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_lc, Object_.Ary("en", "ABC"), "abc"); + } + @Test public void Uc() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_uc, Object_.Ary("en", "abc"), "ABC"); + } + @Test public void LcFirst() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_lcfirst, Object_.Ary("en", "ABC"), "aBC"); + } + @Test public void UcFirst() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_uc, Object_.Ary("en", "abc"), "ABC"); + } + @Test public void ParseFormattedNumber() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en", "1,234.56") , "1234.56"); // formatted String + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en", "1234") , "1234"); // String + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en", 1234) , "1234"); // int + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en", 1234.56) , "1234.56"); // double + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_parseFormattedNumber, Object_.Ary("en"), Scrib_invoke_func_fxt.Null_rslt); // PURPOSE: missing arg should not fail; EX: ru.w:Туйон DATE:2014-01-06 + } + @Test public void ConvertGrammar() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_convertGrammar, Object_.Ary("fi", "talo", "elative"), "talosta"); + } + @Test public void ConvertPlural() { + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_convertPlural, Object_.Ary("ru", 5, Kv_ary_("a", "b", "c")), "c"); // forms in kv_ary + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_convertPlural, Object_.Ary("ru", 5, "a", "b", "c"), "c"); // forms as rest of ary; PAGE:ru.w:Ленин,_Владимир_Ильич DATE:2014-07-01 + } + @Test public void IsRTL() { + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isRTL, Object_.Ary("en"), false); + Xol_lang_itm other_lang = fxt.Core().App().Lang_mgr().Get_by_or_new(Bry_.new_a7("ar")); + Gfo_invk_.Invk_by_val(other_lang, Xol_lang_itm.Invk_dir_rtl_, true); + fxt.Test_scrib_proc_bool(lib, Scrib_lib_language.Invk_isRTL, Object_.Ary("ar"), true); + } + @Test public void Format_duration() { + Init_lang_durations(fxt.Core().Wiki()); + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDuration, Object_.Ary("en", 3723d, Kv_ary_("hours", "minutes", "seconds")), "1 hour, 2 minutes and 3 seconds"); // basic + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDuration, Object_.Ary("en", 123d, Kv_ary_("hours", "minutes", "seconds")), "2 minutes and 3 seconds"); // omit hour since < 1 + fxt.Test_scrib_proc_str(lib, Scrib_lib_language.Invk_formatDuration, Object_.Ary("en", 123d, Kv_ary_("hours")) , "0 hours"); // handle fractional duration + } + @Test public void Get_duration_intervals() { + Init_lang_durations(fxt.Core().Wiki()); + fxt.Test_scrib_proc_str_ary(lib, Scrib_lib_language.Invk_getDurationIntervals, Object_.Ary("en", 3723d, Kv_ary_("hours", "minutes", "seconds")), String_.Concat_lines_nl_skip_last + ( "1=" + , " hours=1" + , " minutes=2" + , " seconds=3" + )); + } + public static Keyval[] Kv_ary_(String... ary) { + int ary_len = ary.length; + Keyval[] rv = new Keyval[ary_len]; + for (int i = 0; i < ary_len; i++) { + rv[i] = Keyval_.int_(i, ary[i]); + } + return rv; + } + public static void Init_lang_durations(Xowe_wiki wiki) { + Xol_msg_mgr msg_mgr = wiki.Lang().Msg_mgr(); + msg_mgr.Clear(); + msg_mgr.Itm_by_key_or_new("duration-millenia" , "~{0} {{PLURAL:~{0}\u007Cmillennium\u007Cmillennia}}"); + msg_mgr.Itm_by_key_or_new("duration-centuries" , "~{0} {{PLURAL:~{0}\u007Ccentury\u007Ccenturies}}"); + msg_mgr.Itm_by_key_or_new("duration-decades" , "~{0} {{PLURAL:~{0}\u007Cdecade\u007Cdecades}}"); + msg_mgr.Itm_by_key_or_new("duration-years" , "~{0} {{PLURAL:~{0}\u007Cyear\u007Cyears}}"); + msg_mgr.Itm_by_key_or_new("duration-weeks" , "~{0} {{PLURAL:~{0}\u007Cweek\u007Cweeks}}"); + msg_mgr.Itm_by_key_or_new("duration-days" , "~{0} {{PLURAL:~{0}\u007Cday\u007Cdays}}"); + msg_mgr.Itm_by_key_or_new("duration-hours" , "~{0} {{PLURAL:~{0}\u007Chour\u007Chours}}"); + msg_mgr.Itm_by_key_or_new("duration-minutes" , "~{0} {{PLURAL:~{0}\u007Cminute\u007Cminutes}}"); + msg_mgr.Itm_by_key_or_new("duration-seconds" , "~{0} {{PLURAL:~{0}\u007Csecond\u007Cseconds}}"); + msg_mgr.Itm_by_key_or_new("and" , " and"); + msg_mgr.Itm_by_key_or_new("word-separator" , " "); + msg_mgr.Itm_by_key_or_new("comma-separator" , ", "); + } +} diff --git a/baselib/src/gplx/objects/primitives/Double_.java b/baselib/src/gplx/objects/primitives/Double_.java index fe2c36a2c..6dd778d80 100644 --- a/baselib/src/gplx/objects/primitives/Double_.java +++ b/baselib/src/gplx/objects/primitives/Double_.java @@ -1,6 +1,6 @@ /* XOWA: the XOWA Offline Wiki Application -Copyright (C) 2012-2017 gnosygnu@gmail.com +Copyright (C) 2012-2020 gnosygnu@gmail.com XOWA is licensed under the terms of the General Public License (GPL) Version 3, or alternatively under the terms of the Apache License Version 2.0. @@ -13,8 +13,100 @@ The terms of each license can be found in the source code repository: GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt */ -package gplx.objects.primitives; import gplx.*; import gplx.objects.*; -public class Double_ { - public static final String Cls_val_name = "double"; - public static final Class Cls_ref_type = Double.class; -} +package gplx.objects.primitives; + +public class Double_ { + public static final String Cls_val_name = "double"; + public static final Class Cls_ref_type = Double.class; + + public static String ToStrByPrintF(double val) { + // call sprintf-like format; EX:"sprintf((s), "%.14g", (n));" + return TrimZeroes(String.format("%.14g", val)); + } + public static String TrimZeroes(String valStr) { // TEST + // NOTE:this function was originally in LuaDouble; it was refactored for clarity, and also to handle trimming zeroes from exponent; EX:"e-05" ISSUE#:697; DATE:2020-08-12 + int valStrLen = valStr.length(); + boolean startTrimming = true; // will be set to true at end of string (12.00), or before "e" (1.00e5) + boolean trimmingZeroes = false; + int trimIdx = -1; + int expIdx = -1; + + // read backwards from end of string + for (int i = valStrLen - 1; i > -1; i--) { + switch (valStr.charAt(i)) { + case '0': + // if startTrimming is true, then enable trimmingZeroes; should only occur twice; (1) end of string ("12.00"); (2) before "e" ("1.00e5") + if (startTrimming) { + startTrimming = false; + trimmingZeroes = true; + } + + // if trimmingZeroes, set trimIdx just before this `0` + if (trimmingZeroes) + trimIdx = i; + break; + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + // disable trimmingZeroes; EX: "12.30" + trimmingZeroes = false; + startTrimming = false; // mark startTrimming false else will gobble up zeroes before numbers; EX: "0.00001" x> "0.1"; ISSUE#:697; DATE:2020-08-11 + break; + case '-': // ignore scientific notation or negative sign; EX: "5.0e-17", "-1" + break; + case 'e': // scientific notation; reset startTrimming; EX: 5.0000000000000e-17 + expIdx = i; + startTrimming = true; + break; + case '.': + // if still trimmingZeroes, and reached decimalPoint, then update trimIdx to truncate decimalPoint also; EX: "123.0000" -> "123" x> "123." + if (trimmingZeroes) + trimIdx = i; + + // trimIdx has been set + if (trimIdx != -1) { + if (expIdx == -1) { // decimal; EX: "12.00" + valStr = valStr.substring(0, trimIdx); + } + else { // exponent; "1.00e5" + // get exponent portion; EX: "e-05" + String expStr = valStr.substring(expIdx, valStrLen); + int expStrLen = expStr.length(); + + // set expSymIdx to be after "e" or "e-" + int expSymIdx = 1; // skip the "e" + if (expSymIdx < expStrLen && !Character.isDigit(expStr.charAt(expSymIdx))) { // skip "-" if it's there + expSymIdx++; + } + + // exponent can have 0s; skip them; EX: 1.1e-05 + int expNumIdx = expSymIdx; + for (int j = expNumIdx; j < expStrLen; j++) { + if (expStr.charAt(j) == '0') { // skip zeroes + expNumIdx++; + } + else { + break; + } + } + + // now stitch it together + valStr = valStr.substring(0, trimIdx) + + expStr.substring(0, expSymIdx) // add "e-" + + expStr.substring(expNumIdx); // add numbers with any leading zeroes trimmed + } + } + + // NOTE: "e" needs to be uppercased to "E"; PAGE:en.w:List_of_countries_and_dependencies_by_population + if (expIdx != -1) { + valStr = valStr.toUpperCase(); + } + i = -1; // no more trimming needed before decimalPoint; stop looping + break; + default: // anything else; stop looping + i = -1; + break; + } + } + return valStr; + } +} diff --git a/baselib/tst/gplx/objects/primitives/Double_Test.java b/baselib/tst/gplx/objects/primitives/Double_Test.java new file mode 100644 index 000000000..91dd977f5 --- /dev/null +++ b/baselib/tst/gplx/objects/primitives/Double_Test.java @@ -0,0 +1,51 @@ +/* +XOWA: the XOWA Offline Wiki Application +Copyright (C) 2012-2020 gnosygnu@gmail.com + +XOWA is licensed under the terms of the General Public License (GPL) Version 3, +or alternatively under the terms of the Apache License Version 2.0. + +You may use XOWA according to either of these licenses as is most appropriate +for your project on a case-by-case basis. + +The terms of each license can be found in the source code repository: + +GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt +Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt +*/ +package gplx.objects.primitives; + +import gplx.tests.Gftest_fxt; +import org.junit.Test; + +public class Double_Test { + private final Double_Tstr tstr = new Double_Tstr(); + + @Test + public void TrimZeroes() { + tstr.Test_TrimZeroes("12.100" , "12.1"); + tstr.Test_TrimZeroes("12.000" , "12"); + tstr.Test_TrimZeroes("12.001" , "12.001"); + tstr.Test_TrimZeroes("1020.00" , "1020"); + tstr.Test_TrimZeroes("1020.00" , "1020"); + tstr.Test_TrimZeroes("1.200e5" , "1.2E5"); + tstr.Test_TrimZeroes("1.200e-05" , "1.2E-5"); + } + + @Test + public void ToStrByPrintF() { + tstr.Test_ToStrByPrintF(1d / 2d , "0.5"); // fails with 0.50000000000000 + tstr.Test_ToStrByPrintF(5d / 100000000000000000d, "5E-17"); // fails with 5.0000000000000e-17 + tstr.Test_ToStrByPrintF(7538000d / 7773352000d , "0.00096972322879499"); // fails with 0; ISSUE#:697; DATE:2020-08-11 + tstr.Test_ToStrByPrintF(56225d / 7776747000d , "7.2298867379895E-06"); // fails with 0; ISSUE#:697; DATE:2020-08-11 + tstr.Test_ToStrByPrintF(35746d / 7805411000d , "4.5796435319037E-06"); // fails with 0; ISSUE#:697; DATE:2020-08-11 + } +} +class Double_Tstr { + public void Test_ToStrByPrintF(double v, String expd) { + Gftest_fxt.Eq__str(expd, Double_.ToStrByPrintF(v)); + } + public void Test_TrimZeroes(String val, String expd) { + Gftest_fxt.Eq__str(expd, Double_.TrimZeroes(val)); + } +} diff --git a/res/bin/any/java/luaj/luaj_xowa.jar b/res/bin/any/java/luaj/luaj_xowa.jar index f2299edfdebebdf553f99ad9656194e080ae4353..35f4c696d835ac73d9ff3276e47cf414101c285c 100644 GIT binary patch delta 22074 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