Scribunto: Trim zeroes when stringifying Doubles [#697]

staging
gnosygnu 4 years ago
parent f122a8d8a3
commit bdae819908

@ -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;
}
}

@ -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" , "&#32;and");
msg_mgr.Itm_by_key_or_new("word-separator" , "&#32;");
msg_mgr.Itm_by_key_or_new("comma-separator" , ",&#32;");
}
}
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" , "&#32;and");
msg_mgr.Itm_by_key_or_new("word-separator" , "&#32;");
msg_mgr.Itm_by_key_or_new("comma-separator" , ",&#32;");
}
}

@ -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;
}
}

@ -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));
}
}
Loading…
Cancel
Save