Lang: Support explicit plural indexes and rules in other languages [#633]

staging
gnosygnu 4 years ago
parent 5f1d9c6f15
commit 5aec368f8d

@ -63,6 +63,7 @@ public class Char_ {
default: return or;
}
}
public static int To_int(char c) {return (int)c;}
public static String To_str(char[] ary, int pos, int length) {return new String(ary, pos, length);}
public static String To_str(int b) {return To_str((char)b);}
public static String To_str(char c) {return String.valueOf(c);}

@ -20,6 +20,7 @@ import java.math.RoundingMode;
import java.text.DecimalFormat;
public class Decimal_adp implements CompareAble {
public int compareTo(Object obj) {Decimal_adp comp = (Decimal_adp)obj; return under.compareTo(comp.under);}
public Decimal_adp Floor() {return Decimal_adp_.int_(this.To_int());}
protected Decimal_adp(BigDecimal v) {this.under = v;} private final BigDecimal under;
protected Decimal_adp(int v) {this.under = new BigDecimal(v);}
public Object Under() {return under;}

@ -21,6 +21,7 @@ public interface Hash_adp extends gplx.core.lists.EnumerAble {
Object Get_by_or_fail(Object key);
void Add(Object key, Object val);
Hash_adp Add_and_more(Object key, Object val);
Hash_adp Add_many_as_key_and_val(Object... ary);
void Add_as_key_and_val(Object val);
boolean Add_if_dupe_use_1st(Object key, Object val);
void Add_if_dupe_use_nth(Object key, Object val);

@ -27,6 +27,7 @@ class Hash_adp_noop implements Hash_adp {
public Object Get_by_or_fail(Object key) {throw Err_.new_missing_key(Object_.Xto_str_strict_or_null_mark(key));}
public void Add(Object key, Object val) {}
public Hash_adp Add_and_more(Object key, Object val) {return this;}
public Hash_adp Add_many_as_key_and_val(Object... ary) {return this;}
public void Add_as_key_and_val(Object val) {}
public void Add_if_dupe_use_nth(Object key, Object val) {}
public boolean Add_if_dupe_use_1st(Object key, Object val) {return false;}

@ -16,6 +16,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
package gplx;
public class Math_ {
public static double Pow(double val, double exponent) {return java.lang.Math.pow(val, exponent);}
public static int Pow_int(int val, int exponent) {return (int)java.lang.Math.pow(val, exponent);}
public static double Pi = java.lang.Math.PI;
public static double E = java.lang.Math.E;
public static int Ceil_as_int(double v) {return (int)Ceil(v);}

@ -20,6 +20,11 @@ public abstract class Hash_adp_base implements Hash_adp {
public Object Get_by_or_fail(Object key) {return Get_by_or_fail_base(key);}
public void Add(Object key, Object val) {Add_base(key, val);}
public Hash_adp Add_and_more(Object key, Object val) {Add_base(key, val); return this;}
public Hash_adp Add_many_as_key_and_val(Object... ary) {
for (Object itm : ary)
Add_base(itm, itm);
return this;
}
public void Add_as_key_and_val(Object val) {Add_base(val, val);}
public void Add_if_dupe_use_nth(Object key, Object val) {
Object existing = Fetch_base(key); if (existing != null) Del(key); // overwrite if exists

@ -22,6 +22,11 @@ public class Sorted_hash implements Hash_adp {
public Object Get_by_or_fail(Object key) {return Get_by_or_fail_base(key);}
public void Add(Object key, Object val) {Add_base(key, val);}
public Hash_adp Add_and_more(Object key, Object val) {Add_base(key, val); return this;}
public Hash_adp Add_many_as_key_and_val(Object... ary) {
for (Object itm : ary)
Add_base(itm, itm);
return this;
}
public void Add_as_key_and_val(Object val) {Add_base(val, val);}
public void Add_if_dupe_use_nth(Object key, Object val) {
Object existing = Fetch_base(key); if (existing != null) Del(key); // overwrite if exists

@ -38,6 +38,7 @@ public interface String_bldr {
String_bldr Add(int i);
String_bldr Add_obj(Object o);
String_bldr Add_mid(char[] ary, int bgn, int count);
String_bldr Add_mid(String str, int bgn, int count);
String_bldr Add_at(int idx, String s);
String_bldr Del(int bgn, int len);
}
@ -83,6 +84,7 @@ abstract class String_bldr_base implements String_bldr {
public abstract String_bldr Add(char c);
public abstract String_bldr Add(int i);
public abstract String_bldr Add_mid(char[] ary, int bgn, int count);
public abstract String_bldr Add_mid(String str, int bgn, int count);
public abstract String_bldr Add_obj(Object o);
public abstract String_bldr Del(int bgn, int len);
}
@ -96,6 +98,7 @@ class String_bldr_thread_single extends String_bldr_base {
@Override public String_bldr Add(char c) {sb.append(c); return this;}
@Override public String_bldr Add(int i) {sb.append(i); return this;}
@Override public String_bldr Add_mid(char[] ary, int bgn, int count) {sb.append(ary, bgn, count); return this;}
@Override public String_bldr Add_mid(String str, int bgn, int count) {sb.append(str, bgn, count); return this;}
@Override public String_bldr Add_obj(Object o) {sb.append(o); return this;}
@Override public String_bldr Del(int bgn, int len) {sb.delete(bgn, len); return this;}
}
@ -109,6 +112,7 @@ class String_bldr_thread_multiple extends String_bldr_base {
@Override public String_bldr Add(char c) {sb.append(c); return this;}
@Override public String_bldr Add(int i) {sb.append(i); return this;}
@Override public String_bldr Add_mid(char[] ary, int bgn, int count) {sb.append(ary, bgn, count); return this;}
@Override public String_bldr Add_mid(String str, int bgn, int count) {sb.append(str, bgn, count); return this;}
@Override public String_bldr Add_obj(Object o) {sb.append(o); return this;}
@Override public String_bldr Del(int bgn, int len) {sb.delete(bgn, len); return this;}
}

@ -56,9 +56,13 @@ public class Regx_adp {
Regx_group[] ary = Regx_group.Ary_empty;
int groups_len = match.groupCount();
if (success && groups_len > 0) {
// NOTE: by convention, there are n groups, but groups.count is n - 1 and groups[0] is entire match (not 1st group); see TEST: DATE:2019-12-28
groups_len++;
ary = new Regx_group[groups_len];
for (int i = 0; i < groups_len; i++)
ary[i] = new Regx_group(true, match.start(i + 1), match.end(i + 1), match.group(i + 1));
for (int i = 0; i < groups_len; i++) {
int match_start = match.start(i);
ary[i] = new Regx_group(match_start != -1, match_start, match.end(i), match.group(i));
}
}
return new Regx_match(success, match_bgn, match_end, ary);
}

@ -49,7 +49,9 @@ public class Regx_adp__tst implements TfdsEqListItmStr {
tst_Matches("b", "a b c b a b b", matches_(2, 6, 10, 12)); // BUGFIX: multiple entries did not work b/c of += instead of +
}
@Test public void Groups() {
tst_Groups("abc def ghi dz", "(d\\p{L}+)", "def", "dz");
tst_Groups("abc def ghi dz", "(d\\p{L}+)", "def", "def", "dz", "dz");
tst_Groups("abc def", "(de)(g?)", "de", "de", ""); // NOTE: (g?) doesn't capture anything, but still add a group for it; DATE:2019-12-28
tst_Groups("-123.456", "^-?(([0-9]+)(?:\\.([0-9]+))?)", "-123.456", "123.456", "123", "456"); // NOTE: -123.456 captured even though it's not part of a group; DATE:2019-12-28
}
Regx_match[] matches_(int... bgnAry) {
int aryLen = Array_.Len(bgnAry);

@ -22,6 +22,11 @@ public class XmlAtrList {
Node xatr = list.getNamedItem(key);
return (xatr == null) ? or : xatr.getNodeValue();
}
public XmlAtr Get_by(String key) {
Node xatr = list.getNamedItem(key);
if (xatr == null) throw Err_.new_missing_key(key);
return new XmlAtr(xatr);
}
public XmlAtr Fetch(String key) {
Node xatr = list.getNamedItem(key); if (xatr == null) throw Err_.new_missing_key(key);
return new XmlAtr(xatr);

@ -29,23 +29,52 @@ import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class XmlDoc_ {
public static XmlDoc parse(String raw) {return new XmlDoc(doc_(raw));}
public static XmlNdeList Select_tags(XmlNde cur, String tag) {
XmlNdeList_cls_list rv = new XmlNdeList_cls_list(4); // NOTE: pass in an initial amount; do not pass 0
Select_tags(rv, cur, tag);
return rv;
}
private static void Select_tags(XmlNdeList_cls_list rv, XmlNde cur, String tag) {
if (String_.Eq(cur.Name(), tag)) {
rv.Add(cur);
}
XmlNdeList sub_ndes = cur.SubNdes();
int sub_ndes_len = sub_ndes.Count();
for (int i = 0; i < sub_ndes_len; i++) {
XmlNde sub_nde = sub_ndes.Get_at(i);
Select_tags(rv, sub_nde, tag);
}
}
public static XmlDoc parse(String raw) {return new XmlDoc(doc_(raw));}
static Document doc_(String raw) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder bldr = null;
try {bldr = factory.newDocumentBuilder();}
catch (ParserConfigurationException e) {throw Err_.new_exc(e, "xml", "failed to create newDocumentBuilder");}
try {
// NOTE: disable DTD validation else errors for "ldmlSupplemental.dtd" in plurals.xml; DATE:2020-01-01
// REF:https://stackoverflow.com/questions/24744175/non-validating-documentbuilder-trying-to-read-dtd-file
// REF:https://stackoverflow.com/questions/6204827/xml-parsing-too-slow
factory.setNamespaceAware(false);
factory.setValidating(false);
factory.setFeature("http://xml.org/sax/features/namespaces", false);
factory.setFeature("http://xml.org/sax/features/validation", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
bldr = factory.newDocumentBuilder();
}
catch (ParserConfigurationException e) {
throw Err_.new_exc(e, "xml", "failed to create newDocumentBuilder");
}
StringReader reader = new StringReader(raw);
InputSource source = new InputSource(reader);
Document doc = null;
try {doc = bldr.parse(source);}
catch (SAXException e) {throw Err_.new_exc(e, "xml", "failed to parse xml", "raw", raw);}
catch (IOException e) {throw Err_.new_exc(e, "xml", "failed to parse xml", "raw", raw);}
return doc;
return doc;
}
public static final String Err_XmlException = "gplx.xmls.XmlException";
}
//#}
}

@ -37,6 +37,7 @@ public class Xoae_app implements Xoa_app, Gfo_invk {
this.mode = mode;
Io_url.Http_file_str_encoder = Gfo_url_encoder_.New__fsys_lnx().Make();
fsys_mgr = new Xoa_fsys_mgr(bin_dir_name, root_dir, wiki_dir, file_dir, css_dir, root_dir);
gplx.xowa.mediawiki.includes.cache.localisation.XomwLocalisationCacheForXowa.Init_ip(fsys_mgr.Bin_any_dir().GenSubDir("mediawiki"));
log_wtr = usr_dlg.Log_wkr();
api_root = new Xoapi_root(this);

@ -16,7 +16,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
package gplx.xowa.guis.cmds; import gplx.*; import gplx.xowa.*; import gplx.xowa.guis.*;
public class Xog_cmd_itm_ {
private static final Ordered_hash regy = Ordered_hash_.New(); // NOTE: must be defined at top
public static final String
public static final String
Key_app_exit = new_dflt_(Xog_ctg_itm_.Tid_app , "xowa.app.exit")
, Key_nav_go_bwd = new_dflt_(Xog_ctg_itm_.Tid_nav , "xowa.nav.go_bwd")

@ -19,6 +19,7 @@ import gplx.gfui.draws.*;
import gplx.xowa.langs.cases.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.kwds.*; import gplx.xowa.langs.grammars.*; import gplx.xowa.langs.genders.*; import gplx.xowa.langs.plurals.*; import gplx.xowa.langs.vnts.*; import gplx.xowa.langs.vnts.converts.*; import gplx.xowa.langs.numbers.*; import gplx.xowa.langs.durations.*; import gplx.xowa.langs.lnki_trails.*; import gplx.xowa.langs.funcs.*; import gplx.xowa.langs.specials.*; import gplx.xowa.langs.bldrs.*; import gplx.xowa.langs.commas.*;
import gplx.xowa.apps.gfs.*; import gplx.xowa.apps.fsys.*; import gplx.core.intls.*; import gplx.xowa.wikis.nss.*; import gplx.xowa.xtns.lst.*; import gplx.xowa.wikis.caches.*; import gplx.xowa.parsers.lnkis.*;
import gplx.xowa.guis.langs.*;
import gplx.xowa.mediawiki.languages.*;
public class Xol_lang_itm implements Gfo_invk {
private boolean loaded = false;
private final Object thread_lock = new Object();
@ -26,6 +27,7 @@ public class Xol_lang_itm implements Gfo_invk {
this.lang_mgr = lang_mgr; this.key_bry = key_bry; this.key_str = String_.new_u8(key_bry);
Xol_lang_stub lang_itm = Xol_lang_stub_.Get_by_key_or_null(key_bry); if (lang_itm == null) throw Err_.new_wo_type("unknown lang_key", "key", String_.new_u8(key_bry));
this.lang_id = lang_itm.Id();
this.mw_lang = new XomwLanguage(this);
this.func_regy = new Xol_func_regy(lang_mgr, this);
this.ns_names = new Xol_ns_grp(this); this.ns_aliases = new Xol_ns_grp(this);
this.kwd_mgr = new Xol_kwd_mgr(this);
@ -40,6 +42,7 @@ public class Xol_lang_itm implements Gfo_invk {
this.duration_mgr = new Xol_duration_mgr(this);
if (lang_id != Xol_lang_stub_.Id_en) fallback_bry_ary = Fallback_bry_ary__en; // NOTE: do not set fallback_ary for en to en, else recursive loop
}
public XomwLanguage Mw_lang() {return mw_lang;} private final XomwLanguage mw_lang;
public Xoa_lang_mgr Lang_mgr() {return lang_mgr;} private final Xoa_lang_mgr lang_mgr;
public byte[] Key_bry() {return key_bry;} private final byte[] key_bry;
public String Key_str() {return key_str;} private final String key_str;

@ -0,0 +1,21 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public class XomwLog_ {
public static void wfDebug_by_method(String method, String msg) {
Gfo_log_.Instance.Note(method + msg);
}
}

@ -19,7 +19,9 @@ public class XophpArray implements Bry_bfr_able {
private final Ordered_hash hash = Ordered_hash_.New();
private int nxt_idx;
public int Len() {return hash.Len();}
// TODO: lowercase count
public int Count() {return hash.Len();}
public boolean Count_bool() {return hash.Len() > 0;}
public void Clear() {
hash.Clear();
nxt_idx = 0;
@ -57,6 +59,18 @@ public class XophpArray implements Bry_bfr_able {
}
return this;
}
public XophpArray Add_as_key_and_val_many(String... val) {
for (String itm : val) {
Add(itm, itm);
}
return this;
}
public XophpArray Add_many(Object... val) {
for (Object itm : val) {
Add(itm);
}
return this;
}
public XophpArray Get_at_ary(int i) {return (XophpArray)Get_at(i);}
public String Get_at_str(int i) {return (String)Get_at(i);}
public int Get_at_int(int i) {return Int_.Cast(Get_at(i));}
@ -65,33 +79,39 @@ public class XophpArray implements Bry_bfr_able {
XophpArrayItm itm = (XophpArrayItm)hash.Get_at(i);
return itm == null ? null : itm.Val();
}
public XophpArrayItm Get_at_itm(int i) {
if (i < 0 || i >= hash.Len()) return null;
return (XophpArrayItm)hash.Get_at(i);
}
public void Del_at(int i) {
XophpArrayItm itm = (XophpArrayItm)hash.Get_at(i);
if (itm != null) {
hash.Del(itm.Key());
}
}
public XophpArray Add_many(Object... val) {
for (Object itm : val) {
Add(itm);
}
return this;
}
public Object Get_by_obj(Object key) {return Get_by(Object_.Xto_str_strict_or_null(key));}
public Object Get_by(int key) {return Get_by(Int_.To_str(key));}
public boolean Get_by_bool(String key) {return Bool_.Cast(this.Get_by(key));}
public int Get_by_int(String key) {return Int_.Cast(this.Get_by(key));}
public Object Get_by_str(String key) {return (String)this.Get_by(key);}
public XophpArray Get_by_ary(String key) {return (XophpArray)this.Get_by(key);}
public String Get_by_str(char key) {return (String)this.Get_by(Char_.To_str(key));}
public String Get_by_str(String key) {return (String)this.Get_by(key);}
public Object Get_by(String key) {
XophpArrayItm itm = (XophpArrayItm)hash.Get_by(key);
return itm.Val();
return itm == null ? null : itm.Val();
}
public void Set(int key, Object val) {
this.Set(XophpArrayItm.New_int(key, val));
}
public void Set(String key, Object val) {
this.Set(XophpArrayItm.New_str(key, val));
}
// TODO: lowercase unset
public void Unset(int key) {Unset(Int_.To_str(key));}
public void Unset(String key) {
hash.Del(key);
}
public boolean in_array(String v) {return Has(v);}
public boolean Has_obj(Object key) {return Has(Object_.Xto_str_strict_or_null(key));}
public boolean Has(String key) {
return hash.Has(key);
@ -122,8 +142,11 @@ public class XophpArray implements Bry_bfr_able {
cur.Val_(itm.Val());
}
}
public Object pop() {return Pop();}
// TODO: remove uppercase Pop
public Object Pop() {
int pos = this.Count() - 1;
if (pos < 0) return null;
XophpArrayItm itm = (XophpArrayItm)hash.Get_at(pos);
this.Del_at(pos);
return itm.Val();
@ -133,6 +156,15 @@ public class XophpArray implements Bry_bfr_able {
itm += v;
this.Set(idx, itm);
}
public XophpArray Clone() {
XophpArray rv = new XophpArray();
int len = hash.Len();
for (int i = 0; i < len; i++) {
XophpArrayItm itm = (XophpArrayItm)hash.Get_at(i);
rv.Add(itm.Key(), itm.Val());
}
return rv;
}
public static XophpArray New(Object... vals) {
XophpArray rv = new XophpArray();
for (Object val : vals)
@ -142,5 +174,9 @@ public class XophpArray implements Bry_bfr_able {
public static boolean is_array(Object val) {
return Type_.Eq_by_obj(val, XophpArray.class);
}
public Object end() {
int len = hash.Len();
return len == 0 ? null : ((XophpArrayItm)hash.Get_at(len - 1)).Val();
}
public static final XophpArray False = null; // handles code like "if ($var === false)" where var is an Object;
}

@ -25,7 +25,7 @@ public class XophpArrayUtl {
return rv;
}
public static boolean isset(XophpArray ary, int idx) {
return ary.Get_at(idx) == null;
return ary.Get_at(idx) != null;
}
public static String[] array_keys_str(Ordered_hash array) {
int len = array.Len();
@ -150,4 +150,14 @@ public class XophpArrayUtl {
}
return rv;
}
// REF.PHP: https://www.php.net/manual/en/function.array-values.php
public static XophpArray array_values(XophpArray array) {
XophpArray rv = new XophpArray();
int len = array.Len();
for (int i = 0; i < len; i++) {
rv.Add(i, array.Get_at(i));
}
return rv;
}
}

@ -215,6 +215,13 @@ public class XophpArrayUtl_tst { // REF:https://www.php.net/manual/en/function.a
, del
);
}
@Test public void array_values() {
XophpArray orig = fxt.Make().Add("size", "XL").Add("color", "gold");
fxt.Test__eq
( fxt.Make().Add(0, "XL").Add(1, "gold")
, XophpArrayUtl.array_values(orig)
);
}
}
class XophpArrayUtl_fxt {
public XophpArray Make() {return new XophpArray();}

@ -129,6 +129,7 @@ public class XophpArray_tst { // REF: http://php.net/manual/en/language.types.ar
fxt.Test__Pop(ary, "b");
fxt.Test__Pop(ary, "a");
fxt.Test__Count(ary, 0);
fxt.Test__Pop(ary, null);
}
@Test public void Itm_str_concat_end() {
XophpArray ary = XophpArray.New();
@ -139,6 +140,19 @@ public class XophpArray_tst { // REF: http://php.net/manual/en/language.types.ar
fxt.Test__Itm_str_concat_end(ary, "b1", 1, "1");
fxt.Test__Itm_str_concat_end(ary, "c2", 2, "2");
}
@Test public void Clone() {
XophpArray ary = XophpArray.New();
ary.Add(0, "a").Add(1, "b").Add(2, "c");
fxt.Test__Eq(ary, ary.Clone());
}
@Test public void Get_by() {
XophpArray ary = XophpArray.New();
ary.Add("0", "a").Add("1", "b").Add("2", "c");
fxt.Test__Get_by(ary, "0", "a");
fxt.Test__Get_by(ary, "missing", null);
}
}
class XophpArray_fxt {
public void Test__Count(XophpArray ary, int expd) {
@ -161,4 +175,10 @@ class XophpArray_fxt {
String actl = ary.Get_at_str(idx);
Gftest.Eq__str(expd, actl);
}
public void Test__Eq(XophpArray lhs, XophpArray rhs) {
Gftest.Eq__ary(lhs.To_ary(), rhs.To_ary());
}
public void Test__Get_by(XophpArray ary, String key, Object expd) {
Gftest.Eq__obj_or_null(expd, ary.Get_by(key));
}
}

@ -0,0 +1,19 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public class XophpError extends Err { public XophpError(String msg) {super(true, "", "", msg);
}
}

@ -0,0 +1,22 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public class XophpFloat_ {
// REF.PHP:https://www.php.net/manual/en/language.types.float.php
public static double floatval(String val) {
return Double_.parse(val); // NOTE:PHP float has roughly 14 decimal digits of precision which is more similar to Java's double than float
}
}

@ -0,0 +1,25 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public class XophpInt_ {
public static final int False = -1; // handles code like "if ($var === false)" where var is an Object;
public static String strval(int number) {
return Int_.To_str(number);
}
public static int intval(String val) {
return Int_.Parse_or(val, 0);
}
}

@ -0,0 +1,25 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public class XophpIo_ {
public static String file_get_contents(String path) {
String rv = Io_mgr.Instance.LoadFilStr(path);
return String_.Eq(rv, String_.Empty) ? XophpString_.Null : rv;
}
public static boolean file_exists(String path) {
return Io_mgr.Instance.ExistsFil(Io_url_.new_fil_(path));
}
}

@ -23,5 +23,39 @@ public class XophpMath {
else {
return Math_.Round(v, places);
}
}
}
public static int min(int lhs, int rhs) {
return Math_.Min(lhs, rhs);
}
public static int min_many(int... ary) {
int rv = Int_.Max_value;
for (int itm : ary) {
if (itm < rv)
rv = itm;
}
return rv;
}
public static int max_many(int... ary) {
int rv = Int_.Min_value;
for (int itm : ary) {
if (itm > rv)
rv = itm;
}
return rv;
}
// REF.PHP:https://www.php.net/manual/en/function.fmod.php
public static Decimal_adp fmod_decimal(Decimal_adp lhs, Decimal_adp rhs) {return Decimal_adp_.double_(fmod(lhs.To_double(), rhs.To_double()));}
public static double fmod(double lhs, double rhs) {
return (double)lhs % (double)rhs;
}
/*
fmod
$x = 5.7;
$y = 1.3;
$r = fmod($x, $y);
// $r equals 0.5, because 4 * 1.3 + 0.5 = 5.7
*/
}

@ -0,0 +1,30 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
import org.junit.*; import gplx.core.tests.*;
public class XophpMath__tst {
private final XophpMath__fxt fxt = new XophpMath__fxt();
@Test public void fmod() {
fxt.Test__fmod(8, 2, 0);
fxt.Test__fmod(7, 2, 1);
fxt.Test__fmod(5.7d, 1.3d, .5d);
}
}
class XophpMath__fxt {
public void Test__fmod(double lhs, double rhs, double expd) {
Gftest.Eq__double(expd, XophpMath.fmod(lhs, rhs));
}
}

@ -17,4 +17,6 @@ package gplx.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public class XophpObject {
public static final Object False = null; // handles code like "if ($var === false)" where var is an Object;
public static boolean is_true(Object val) {return val != null;}
public static boolean is_null(Object val) {return val == null;}
public static Object coalesce(Object val, Object if_null) {return val == null ? if_null : val;}
}

@ -0,0 +1,20 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public interface XophpPerfTimer {
void Bgn();
void End();
}

@ -0,0 +1,23 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public class XophpPerfTimer_ {
public static final XophpPerfTimer Noop = new XophpPerfTimerNoop();
}
class XophpPerfTimerNoop implements XophpPerfTimer {
public void Bgn() {}
public void End() {}
}

@ -0,0 +1,99 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
import gplx.langs.regxs.*;
public class XophpRegex_ {
public static boolean preg_match_bool(Regx_adp pattern, int modifier, String subject) {return preg_match_bool(pattern, modifier, subject, null, 0, 0);}
public static boolean preg_match_bool(Regx_adp pattern, String subject, XophpArray matches, int flags, int offset) {return preg_match(pattern, MODIFIER_NONE, subject, matches, flags, offset) == FOUND;}
public static boolean preg_match_bool(Regx_adp pattern, int modifier, String subject, XophpArray matches, int flags, int offset) {return preg_match(pattern, modifier, subject, matches, flags, offset) == FOUND;}
public static int preg_match(Regx_adp pattern, String subject) {return preg_match(pattern, MODIFIER_NONE, subject, null, 0, 0);}
public static int preg_match(Regx_adp pattern, int modifier, String subject) {return preg_match(pattern, modifier, subject, null, 0, 0);}
// REF.PHP: https://www.php.net/manual/en/function.preg-match.php
public static int preg_match(Regx_adp pattern, int modifier, String subject, XophpArray matches, int flags, int offset) {
// handle offset
int subject_len = String_.Len(subject);
if (offset >= subject_len || offset < 0) return PREG_ERR;
// exec match
// FUTURE: offset is in bytes, whereas subject will be in chars
Regx_match match = pattern.Match(subject, offset);
// update vars if something found
int rv = NOT_FOUND;
if (match.Rslt()) {
rv = FOUND;
int find_bgn = match.Find_bgn();
String match_str = String_.Mid(subject, find_bgn, match.Find_end());
Regx_group[] grps = match.Groups();
int grps_len = grps.length;
// handle grps
if (matches != null) {
if (grps_len == 0) {
if (flags == PREG_OFFSET_CAPTURE) {
matches.Add(XophpArray.New(match_str, find_bgn));
}
else {
matches.Add(match_str);
}
}
else {
preg_match_fill(subject, matches, flags, match, match_str, grps, grps_len);
}
}
}
return rv;
}
private static void preg_match_fill(String subject, XophpArray matches, int flags, Regx_match match, String match_str, Regx_group[] grps, int grps_len) {
for (int i = 0; i < grps_len; i++) {
Regx_group grp = grps[i];
if (!grp.Rslt()) continue; // ignore non matches in group; EX: "1" and "^-?(([0-9]+)(?:\\.([0-9]+))?)" returns a match=false for group(2)
String grp_match = grp.Val();
if (flags == PREG_OFFSET_CAPTURE) {
matches.Add(XophpArray.New(grp_match, grp.Bgn()));
}
else {
matches.Add(grp_match);
}
}
}
// REF.PHP:https://www.php.net/manual/en/pcre.constants.php
public static final int
PREG_OFFSET_CAPTURE = 256
, PREG_UNMATCHED_AS_NULL = 0
, PREG_NO_FLAG = Int_.Min_value
, PREG_ERR = XophpInt_.False
;
public static final int NOT_FOUND = 0, FOUND = 1;
// REF.PHP:https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php
public static final int
MODIFIER_NONE = 0
, MODIFIER_i = Math_.Pow_int(2, 0) // PCRE_CASELESS: If this modifier is set, letters in the pattern match both upper and lower case letters.
, MODIFIER_m = Math_.Pow_int(2, 1) // PCRE_MULTILINE: By default, PCRE treats the subject String as consisting of a single "line" of characters (even if it actually contains several newlines). The "start of line" metacharacter (^) matches only at the start of the String, while the "end of line" metacharacter ($) matches only at the end of the String, or before a terminating newline (unless D modifier is set). This is the same as Perl. When this modifier is set, the "start of line" and "end of line" constructs match immediately following or immediately before any newline in the subject String, respectively, as well as at the very start and end. This is equivalent to Perl's /m modifier. If there are no "\n" characters in a subject String, or no occurrences of ^ or $ in a pattern, setting this modifier has no effect.
, MODIFIER_s = Math_.Pow_int(2, 2) // PCRE_DOTALL: If this modifier is set, a dot metacharacter in the pattern matches all characters, including newlines. Without it, newlines are excluded. This modifier is equivalent to Perl's /s modifier. A negative class such as [^a] always matches a newline character, independent of the setting of this modifier.
, MODIFIER_x = Math_.Pow_int(2, 3) // PCRE_EXTENDED: If this modifier is set, whitespace data characters in the pattern are totally ignored except when escaped or inside a character class, and characters between an unescaped # outside a character class and the next newline character, inclusive, are also ignored. This is equivalent to Perl's /x modifier, and makes it possible to include commentary inside complicated patterns. Note, however, that this applies only to data characters. Whitespace characters may never appear within special character sequences in a pattern, for example within the sequence (?( which introduces a conditional subpattern.
, MODIFIER_e = Math_.Pow_int(2, 4) // PREG_REPLACE_EVAL: If this deprecated modifier is set, preg_replace() does normal substitution of backreferences in the replacement String, evaluates it as PHP code, and uses the result for replacing the search String. Single quotes, double quotes, backslashes (\) and NULL chars will be escaped by backslashes in substituted backreferences.
, MODIFIER_A = Math_.Pow_int(2, 5) // PREG_ANCHORED: If this modifier is set, the pattern is forced to be "anchored", that is, it is constrained to match only at the start of the String which is being searched (the "subject String"). This effect can also be achieved by appropriate constructs in the pattern itself, which is the only way to do it in Perl.
, MODIFIER_D = Math_.Pow_int(2, 6) // PCRE_DOLLAR_ENDONLY: If this modifier is set, a dollar metacharacter in the pattern matches only at the end of the subject String. Without this modifier, a dollar also matches immediately before the final character if it is a newline (but not before any other newlines). This modifier is ignored if m modifier is set. There is no equivalent to this modifier in Perl.
, MODIFIER_S = Math_.Pow_int(2, 7) // PCRE_STUDY: When a pattern is going to be used several times, it is worth spending more time analyzing it in order to speed up the time taken for matching. If this modifier is set, then this extra analysis is performed. At present, studying a pattern is useful only for non-anchored patterns that do not have a single fixed starting character.
, MODIFIER_U = Math_.Pow_int(2, 8) // PCRE_UNGREEDY: This modifier inverts the "greediness" of the quantifiers so that they are not greedy by default, but become greedy if followed by ?. It is not compatible with Perl. It can also be set by a (?U) modifier setting within the pattern or by a question mark behind a quantifier (e.g. .*?).
, MODIFIER_X = Math_.Pow_int(2, 9) // PCRE_EXTRA: This modifier turns on additional functionality of PCRE that is incompatible with Perl. Any backslash in a pattern that is followed by a letter that has no special meaning causes an error, thus reserving these combinations for future expansion. By default, as in Perl, a backslash followed by a letter with no special meaning is treated as a literal. There are at present no other features controlled by this modifier.
, MODIFIER_J = Math_.Pow_int(2, 10) // PCRE_INFO_JCHANGED: The (?J) @gplx.Internal protected option setting changes the local PCRE_DUPNAMES option. Allow duplicate names for subpatterns. As of PHP 7.2.0 J is supported as modifier as well.
, MODIFIER_u = Math_.Pow_int(2, 11) // PCRE_UTF8: This modifier turns on additional functionality of PCRE that is incompatible with Perl. Pattern and subject strings are treated as UTF-8. An invalid subject will cause the preg_* function to match nothing; an invalid pattern will trigger an error of level E_WARNING. Five and six octet UTF-8 sequences are regarded as invalid since PHP 5.3.4 (resp. PCRE 7.3 2007-08-28); formerly those have been regarded as valid UTF-8.
;
}

@ -0,0 +1,102 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
import org.junit.*; import gplx.core.tests.*; import gplx.core.strings.*;
import gplx.langs.regxs.*;
public class XophpRegex__tst {
private final XophpRegex__fxt fxt = new XophpRegex__fxt();
@After public void term() {fxt.Term();}
@Test public void Basic() {
fxt.Test__preg_match("a", "abc", fxt.Expd__y().Add("a")); // found
fxt.Test__preg_match("z", "abc", fxt.Expd__n()); // not found
fxt.Test__preg_match("c", "abc", 1, fxt.Expd__y().Add("c")); // offset
fxt.Test__preg_match("c", "abc", 3, fxt.Expd__err()); // offset: too large
fxt.Test__preg_match("c", "abc", -1, fxt.Expd__err()); // offset: negative
}
@Test public void Not_found() {
fxt.Test__preg_match("a", "abc", fxt.Expd__y().Add("a")); // found
fxt.Test__preg_match("z", "abc", fxt.Expd__n()); // not found
fxt.Test__preg_match("c", "abc", 1, fxt.Expd__y().Add("c")); // offset
fxt.Test__preg_match("c", "abc", 3, fxt.Expd__err()); // offset: too large
fxt.Test__preg_match("c", "abc", -1, fxt.Expd__err()); // offset: negative
}
@Test public void Character_classes() {
fxt.Test__preg_match("[bc]", "abc", fxt.Expd__y().Add("b")); // character class
fxt.Test__preg_match("[bc]", "abc", XophpRegex_.PREG_OFFSET_CAPTURE, 2, fxt.Expd__y().Add("c", 2)); // character class
}
@Test public void Groups() {
fxt.Test__preg_match("(foo)(bar)(baz)", "foobarbaz", XophpRegex_.PREG_OFFSET_CAPTURE, 0, fxt.Expd__y()
.Add("foobarbaz", 0)
.Add("foo", 0)
.Add("bar", 3)
.Add("baz", 6)
);
fxt.Test__preg_match("(foo)(bar)(baz)", "foobarbaz", XophpRegex_.PREG_NO_FLAG, 0, fxt.Expd__y()
.Add("foobarbaz")
.Add("foo")
.Add("bar")
.Add("baz")
);
}
}
class XophpRegex__fxt {
private String_bldr print_php = null;//String_bldr_.new_();
public XophpRegex__expd Expd__err() {return new XophpRegex__expd(XophpRegex_.PREG_ERR);}
public XophpRegex__expd Expd__y() {return new XophpRegex__expd(1);}
public XophpRegex__expd Expd__n() {return new XophpRegex__expd(XophpRegex_.NOT_FOUND);}
public void Test__preg_match(String pattern, String str, XophpRegex__expd rslt) {Test__preg_match(pattern, str, XophpRegex_.PREG_NO_FLAG, 0, rslt);}
public void Test__preg_match(String pattern, String str, int offset, XophpRegex__expd rslt) {Test__preg_match(pattern, str, XophpRegex_.PREG_NO_FLAG, offset, rslt);}
public void Test__preg_match(String pattern, String str, int flags, int offset, XophpRegex__expd rslt) {
if (print_php != null) {
String flag_str = "";
switch (flags) {
case XophpRegex_.PREG_OFFSET_CAPTURE: flag_str = "PREG_OFFSET_CAPTURE"; break;
case XophpRegex_.PREG_UNMATCHED_AS_NULL: flag_str = "PREG_UNMATCHED_AS_NULL"; break;
case XophpRegex_.PREG_NO_FLAG: flag_str = "0"; break;
}
print_php.Add(String_.Format("\necho \"<br>\" . preg_match('/{0}/', '{1}', $m, {2}, {3}) . ' '; var_dump($m);", pattern, str, flag_str, offset));
}
XophpArray actl_matches = XophpArray.New();
int actl_pos = XophpRegex_.preg_match(Regx_adp_.new_(pattern), XophpRegex_.MODIFIER_NONE, str, actl_matches, flags, offset);
Gftest.Eq__int(rslt.Pos(), actl_pos);
XophpArray expd_matches = rslt.Matches();
if (expd_matches != null) {
Gftest.Eq__ary__lines(expd_matches.To_str(), actl_matches.To_str());
}
}
public void Term() {
if (print_php != null)
Tfds.Write(print_php.Add_char_nl().To_str_and_clear());
}
}
class XophpRegex__expd {
public XophpRegex__expd(int pos) {
this.pos = pos;
}
public int Pos() {return pos;} private final int pos;
public XophpArray Matches() {return matches;} private XophpArray matches;
public XophpRegex__expd Add(String... ary) {
if (matches == null) matches = XophpArray.New();
for (Object itm : ary)
matches.Add(itm);
return this;
}
public XophpRegex__expd Add(String s, int pos) {
if (matches == null) matches = XophpArray.New();
matches.Add(XophpArray.New(s, pos));
return this;
}
}

@ -1,177 +0,0 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
import gplx.core.btries.*;
public class XophpString {
public static int strpos(byte[] src, byte find) {return strpos(src, find, 0, src.length);}
public static int strpos(byte[] src, byte find, int bgn, int end) {
return Bry_find_.Find_fwd(src, find, bgn, end);
}
public static String substr(String src, int bgn, int len) {return String_.new_u8(substr(Bry_.new_u8(src), bgn, len));}
public static String substr(String src, int bgn) {return String_.new_u8(substr(Bry_.new_u8(src), bgn, String_.Len(src)));}
public static byte[] substr(byte[] src, int bgn) {return substr(src, bgn, src.length);}
public static byte[] substr(byte[] src, int bgn, int len) {
int src_len = src.length;
if (bgn < 0) bgn = src_len + bgn; // handle negative
if (bgn < 0) bgn = 0; // handle out of bounds; EX: ("a", -1, -1)
int end = len < 0 ? src_len + len : bgn + len;
if (end > src.length) end = src.length;; // handle out of bounds;
return Bry_.Mid(src, bgn, end);
}
public static byte substr_byte(byte[] src, int bgn) {return substr_byte(src, bgn, src.length);}
public static byte substr_byte(byte[] src, int bgn, int len) {
int src_len = src.length;
if (src_len == 0) return Byte_ascii.Null;
if (bgn < 0) bgn = src_len + bgn; // handle negative
if (bgn < 0) bgn = 0; // handle out of bounds; EX: ("a", -1, -1)
int end = len < 0 ? src_len + len : bgn + len;
if (end > src.length) end = src.length;; // handle out of bounds;
return src[bgn];
}
public static int strspn_fwd__ary(byte[] src, boolean[] find, int bgn, int max, int src_len) {
if (max == -1) max = src_len;
int rv = 0;
for (int i = bgn; i < src_len; i++) {
if (find[src[i] & 0xFF] && rv < max) // PATCH.JAVA:need to convert to unsigned byte
rv++;
else
break;
}
return rv;
}
public static int strspn_fwd__byte(byte[] src, byte find, int bgn, int max, int src_len) {
if (max == -1) max = src_len;
int rv = 0;
for (int i = bgn; i < src_len; i++) {
if (find == src[i] && rv < max)
rv++;
else
break;
}
return rv;
}
public static int strspn_fwd__space_or_tab(byte[] src, int bgn, int max, int src_len) {
if (max == -1) max = src_len;
int rv = 0;
for (int i = bgn; i < src_len; i++) {
switch (src[i]) {
case Byte_ascii.Space:
case Byte_ascii.Tab:
if (rv < max) {
rv++;
continue;
}
break;
}
break;
}
return rv;
}
public static int strspn_bwd__byte(byte[] src, byte find, int bgn, int max) {
if (max == -1) max = Int_.Max_value;
int rv = 0;
for (int i = bgn - 1; i > -1; i--) {
if (find == src[i] && rv < max)
rv++;
else
break;
}
return rv;
}
public static int strspn_bwd__ary(byte[] src, boolean[] find, int bgn, int max) {
if (max == -1) max = Int_.Max_value;
int rv = 0;
for (int i = bgn - 1; i > -1; i--) {
if (find[src[i & 0xFF]] && rv < max) // PATCH.JAVA:need to convert to unsigned byte
rv++;
else
break;
}
return rv;
}
public static int strspn_bwd__space_or_tab(byte[] src, int bgn, int max) {
if (max == -1) max = Int_.Max_value;
int rv = 0;
for (int i = bgn - 1; i > -1; i--) {
switch (src[i]) {
case Byte_ascii.Space:
case Byte_ascii.Tab:
if (rv < max) {
rv++;
continue;
}
break;
}
break;
}
return rv;
}
public static byte[] strtr(byte[] src, Btrie_slim_mgr trie, Bry_bfr tmp, Btrie_rv trv) {
boolean dirty = false;
int src_bgn = 0;
int src_end = src.length;
int i = src_bgn;
while (true) {
if (i == src_end) break;
byte b = src[i];
Object o = trie.Match_at_w_b0(trv, b, src, i, src_end);
if (o == null) {
if (dirty) {
tmp.Add_byte(b);
}
i++;
}
else {
if (!dirty) {
dirty = true;
tmp.Add_mid(src, 0, i);
}
tmp.Add((byte[])o);
i = trv.Pos();
}
}
return dirty ? tmp.To_bry_and_clear() : src;
}
public static byte[] strtr(byte[] src, byte find, byte repl) {
return Bry_.Replace(src, 0, src.length, find, repl);
}
public static byte[] str_replace(byte find, byte repl, byte[] src) {
return Bry_.Replace(src, 0, src.length, find, repl);
}
public static byte[] str_replace(byte[] find, byte[] repl, byte[] src) {
return Bry_.Replace(src, find, repl);
}
public static byte[] strstr(byte[] src, byte[] find) {
int pos = Bry_find_.Find_fwd(src, find);
return pos == Bry_find_.Not_found ? null : Bry_.Mid(src, pos, src.length);
}
public static int strlen(byte[] src) {return src.length;}
public static String str_repeat(String val, int count) {
int val_len = String_.Len(val);
int chry_len = val_len * count;
char[] chry = new char[chry_len];
for (int i = 0; i < count; i++) {
for (int j = 0; j < val_len; j++) {
chry[(i * val_len) + j] = String_.CharAt(val, j);
}
}
return String_.new_charAry_(chry, 0, chry_len);
}
public static boolean is_string(Object o) {
return String_.as_(o) != null;
}
}

@ -14,6 +14,452 @@ 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.mediawiki; import gplx.*; import gplx.xowa.*;
import gplx.core.btries.*;
import gplx.core.intls.*;
import gplx.objects.strings.unicodes.*;
import gplx.core.primitives.*;
public class XophpString_ {
public static final String False = null; // handles code like "if ($var === false)" where var is an Object;
public static final String Null = null;
public static boolean is_true(String s) {return s != null;} // handles code like "if ($var)" where var is an Object;
// REF.PHP: https://www.php.net/manual/en/function.strpos.php
public static int strpos(String haystack, String needle) {return strpos(haystack, needle, 0);}
public static int strpos(String haystack, String needle, int offset) {
if (offset < 0) {
offset = String_.Len(haystack) + offset;
}
return String_.FindFwd(haystack, needle, offset);
}
public static int strpos(byte[] src, byte find) {return strpos(src, find, 0, src.length);}
public static int strpos(byte[] src, byte find, int bgn, int end) {
return Bry_find_.Find_fwd(src, find, bgn, end);
}
// REF.PHP: https://www.php.net/manual/en/function.substr.php
public static String substr(String src, int bgn, int len) {return String_.new_u8(substr(Bry_.new_u8(src), bgn, len));}
public static String substr(String src, int bgn) {return String_.new_u8(substr(Bry_.new_u8(src), bgn, String_.Len(src)));}
public static byte[] substr(byte[] src, int bgn) {return substr(src, bgn, src.length);}
public static byte[] substr(byte[] src, int bgn, int len) {
int src_len = src.length;
if (bgn < 0) bgn = src_len + bgn; // handle negative
if (bgn < 0) bgn = 0; // handle out of bounds; EX: ("a", -1, -1)
int end = len < 0 ? src_len + len : bgn + len;
if (end > src.length) end = src.length;; // handle out of bounds;
return Bry_.Mid(src, bgn, end);
}
public static byte substr_byte(byte[] src, int bgn) {return substr_byte(src, bgn, src.length);}
public static byte substr_byte(byte[] src, int bgn, int len) {
int src_len = src.length;
if (src_len == 0) return Byte_ascii.Null;
if (bgn < 0) bgn = src_len + bgn; // handle negative
if (bgn < 0) bgn = 0; // handle out of bounds; EX: ("a", -1, -1)
int end = len < 0 ? src_len + len : bgn + len;
if (end > src.length) end = src.length;; // handle out of bounds;
return src[bgn];
}
// REF.PHP: https://www.php.net/manual/en/function.strspn.php
public static Hash_adp strspn_hash(String mask) {
Hash_adp rv = Hash_adp_.New();
int mask_len = String_.Len(mask);
int i = 0;
while (i < mask_len) {
char hi_char = String_.CharAt(mask, i);
String key = "";
if (Utf16_.Len_by_char(hi_char) == 2) {
i++;
char lo_char = String_.CharAt(mask, i);
int surrogate_char = Utf16_.Surrogate_merge(Char_.To_int(hi_char), Char_.To_int(lo_char));
key = String_.new_u8(Utf16_.Encode_int_to_bry(surrogate_char));
}
else {
key = Char_.To_str(hi_char);
}
rv.Add_if_dupe_use_1st(key, key);
i++;
}
return rv;
}
public static int strspn(String subject, Hash_adp mask, int start) {return strspn(subject, mask, start, Int_.Null);}
public static int strspn(String subject, Hash_adp mask, int start, int length) {
int subject_len = String_.Len(subject);
// get subject_end
int subject_end = 0;
if (length == Int_.Null) {
subject_end = subject_len;
}
else if (length < 0) {
subject_end = subject_len + length; // If length is given and is negative, then subject will be examined from the starting position up to length characters from the end of subject.
if (subject_end < start)
subject_end = start;
}
else {
subject_end = start + length; // If length is given and is non-negative, then subject will be examined for length characters after the starting position.
if (subject_end > subject_len)
subject_end = subject_len;
}
// loop subject until encountering character not in mask
int rv = 0;
int i = start;
while (i < subject_end) {
char subject_char = String_.CharAt(subject, i);
String mask_key = "";
if (Utf16_.Len_by_char(subject_char) == 2) {
i++;
char lo_char = String_.CharAt(subject, i);
// TODO: change Char_.To_int_or to Char_.To_digit
int surrogate_char = Utf16_.Surrogate_merge(Char_.To_int(subject_char), Char_.To_int(lo_char));
mask_key = String_.new_u8(Utf16_.Encode_int_to_bry(surrogate_char));
}
else {
mask_key = Char_.To_str(subject_char);
}
if (mask.Has(mask_key)) {
rv++;
}
else {
break;
}
i++;
}
return rv;
}
public static int strspn_fwd__ary(byte[] src, boolean[] find, int bgn, int max, int src_len) {
if (max == -1) max = src_len;
int rv = 0;
for (int i = bgn; i < src_len; i++) {
if (find[src[i] & 0xFF] && rv < max) // PATCH.JAVA:need to convert to unsigned byte
rv++;
else
break;
}
return rv;
}
public static int strspn_fwd__byte(byte[] src, byte find, int bgn, int max, int src_len) {
if (max == -1) max = src_len;
int rv = 0;
for (int i = bgn; i < src_len; i++) {
if (find == src[i] && rv < max)
rv++;
else
break;
}
return rv;
}
public static int strspn_fwd__space_or_tab(byte[] src, int bgn, int max, int src_len) {
if (max == -1) max = src_len;
int rv = 0;
for (int i = bgn; i < src_len; i++) {
switch (src[i]) {
case Byte_ascii.Space:
case Byte_ascii.Tab:
if (rv < max) {
rv++;
continue;
}
break;
}
break;
}
return rv;
}
public static int strspn_bwd__byte(byte[] src, byte find, int bgn, int max) {
if (max == -1) max = Int_.Max_value;
int rv = 0;
for (int i = bgn - 1; i > -1; i--) {
if (find == src[i] && rv < max)
rv++;
else
break;
}
return rv;
}
public static int strspn_bwd__ary(byte[] src, boolean[] find, int bgn, int max) {
if (max == -1) max = Int_.Max_value;
int rv = 0;
for (int i = bgn - 1; i > -1; i--) {
if (find[src[i & 0xFF]] && rv < max) // PATCH.JAVA:need to convert to unsigned byte
rv++;
else
break;
}
return rv;
}
public static int strspn_bwd__space_or_tab(byte[] src, int bgn, int max) {
if (max == -1) max = Int_.Max_value;
int rv = 0;
for (int i = bgn - 1; i > -1; i--) {
switch (src[i]) {
case Byte_ascii.Space:
case Byte_ascii.Tab:
if (rv < max) {
rv++;
continue;
}
break;
}
break;
}
return rv;
}
public static byte[] strtr(byte[] src, Btrie_slim_mgr trie, Bry_bfr tmp, Btrie_rv trv) {
boolean dirty = false;
int src_bgn = 0;
int src_end = src.length;
int i = src_bgn;
while (true) {
if (i == src_end) break;
byte b = src[i];
Object o = trie.Match_at_w_b0(trv, b, src, i, src_end);
if (o == null) {
if (dirty) {
tmp.Add_byte(b);
}
i++;
}
else {
if (!dirty) {
dirty = true;
tmp.Add_mid(src, 0, i);
}
tmp.Add((byte[])o);
i = trv.Pos();
}
}
return dirty ? tmp.To_bry_and_clear() : src;
}
public static byte[] strtr(byte[] src, byte find, byte repl) {
return Bry_.Replace(src, 0, src.length, find, repl);
}
public static byte[] str_replace(byte find, byte repl, byte[] src) {
return Bry_.Replace(src, 0, src.length, find, repl);
}
public static byte[] str_replace(byte[] find, byte[] repl, byte[] src) {
return Bry_.Replace(src, find, repl);
}
public static byte[] strstr(byte[] src, byte[] find) {
int pos = Bry_find_.Find_fwd(src, find);
return pos == Bry_find_.Not_found ? null : Bry_.Mid(src, pos, src.length);
}
public static int strlen(String src) {return String_.Len(src);}
public static int strlen(byte[] src) {return src.length;}
// REF.PHP: https://www.php.net/manual/en/function.rtrim.php
private static final Hash_adp trim_ws_hash = Hash_adp_.New().Add_many_as_key_and_val
( Int_obj_ref.New(Byte_ascii.Space)
, Int_obj_ref.New(Byte_ascii.Tab)
, Int_obj_ref.New(Byte_ascii.Nl)
, Int_obj_ref.New(Byte_ascii.Cr)
, Int_obj_ref.New(Byte_ascii.Null)
, Int_obj_ref.New(Byte_ascii.Vertical_tab)
);
public static String rtrim(String src) {return rtrim(src, null);}
public static String rtrim(String src_str, String pad_str) {
Hash_adp pad_hash = null;
if (pad_str == null) pad_hash = trim_ws_hash;
// init brys / lens
byte[] src_bry = Bry_.new_u8(src_str);
int src_len = src_bry.length;
byte[] pad_bry = Bry_.new_u8(pad_str);
int pad_len = pad_bry.length;
// ----------------------
// 0, 1 chars (optimized)
// ----------------------
int last = 0;
switch (pad_len) {
// pad is ""
case 0:
return src_str;
// pad is 1 char
case 1:
last = src_len;
byte pad_byte = pad_bry[0];
for (int i = src_len - 1; i > -1; i--) {
byte cur = src_bry[i];
last = i + 1;
if (cur != pad_byte) {
break;
}
}
return (last == src_len) ? src_str : String_.new_u8(Bry_.Mid(src_bry, 0, last));
}
// --------
// 2+ chars
// --------
// create pad_hash if not ws_hash
// NOTE: PHP does not support multibyte strings; see TEST
if (pad_hash == null) {
pad_hash = Hash_adp_.New();
byte prv_byte = Byte_.Zero;
for (int i = 0; i < pad_len; i++) {
byte pad_byte = pad_bry[i];
if (pad_byte == Byte_ascii.Dot && i < pad_len - 1) {
byte nxt_byte = pad_bry[i + 1];
if (nxt_byte == Byte_ascii.Dot) {
if (i == 0) {
throw new XophpError(".. found but at start of String; src=" + pad_str);
}
else if (i == pad_len - 2) {
throw new XophpError(".. found but at end of String; src=" + pad_str);
}
else {
nxt_byte = pad_bry[i + 2];
if (nxt_byte > prv_byte) {
for (byte j = prv_byte; j < nxt_byte; j++) {
Byte_obj_ref rng_obj = Byte_obj_ref.new_(j);
if (!pad_hash.Has(rng_obj))
pad_hash.Add_as_key_and_val(rng_obj);
}
i += 2;
continue;
}
else {
throw new XophpError(".. found but next byte must be greater than previous byte; src=" + pad_str);
}
}
}
}
prv_byte = pad_byte;
Byte_obj_ref pad_obj = Byte_obj_ref.new_(pad_byte);
if (!pad_hash.Has(pad_obj))
pad_hash.Add_as_key_and_val(pad_obj);
}
}
// loop src until non-matching pad int
Byte_obj_ref temp = Byte_obj_ref.zero_();
last = src_len;
for (int i = src_len - 1; i > -1; i--) {
temp.Val_(src_bry[i]);
last = i + 1;
if (!pad_hash.Has(temp)) {
break;
}
}
return (last == src_len) ? src_str : String_.new_u8(Bry_.Mid(src_bry, 0, last));
}
public static String str_repeat(String val, int count) {
int val_len = String_.Len(val);
int chry_len = val_len * count;
char[] chry = new char[chry_len];
for (int i = 0; i < count; i++) {
for (int j = 0; j < val_len; j++) {
chry[(i * val_len) + j] = String_.CharAt(val, j);
}
}
return String_.new_charAry_(chry, 0, chry_len);
}
public static boolean is_string(Object o) {
return String_.as_(o) != null;
}
public static String strtolower(String s) {
return String_.Lower(s);
}
// REF.PHP: https://www.php.net/manual/en/function.ord.php
public static int ord(String s) {
return String_.Len_eq_0(s) ? 0 : Char_.To_int(String_.CharAt(s, 0));
}
public static String[] explode(String delimiter, String str) {
return String_.Split(str, delimiter);
}
// NOTE: support simple syntax only
// REF.PHP: https://www.php.net/manual/en/language.types.String.php#language.types.String.parsing
public static String Fmt(String fmt_str, Object... args) {
byte[] fmt = Bry_.new_u8(fmt_str);
int len = fmt.length;
Bry_bfr bfr = Bry_bfr_.New();
int pos = 0;
int arg_idx = 0;
while (pos < len) {
// find next $
int dollar_pos = Bry_find_.Find_fwd(fmt, Byte_ascii.Dollar, pos);
// no more $
if (dollar_pos == Bry_find_.Not_found) {
// add rest of fmt
bfr.Add_mid(fmt, pos, len);
break;
}
int key_bgn = dollar_pos + 1;
// if $ at end, then just add it literally; also bound-check
if (key_bgn == len) {
bfr.Add_mid(fmt, pos, len);
break;
}
int key_end = len;
byte key_bgn_byte = fmt[key_bgn];
// if { after $, then search forward for }
if (key_bgn_byte == Byte_ascii.Curly_bgn) {
key_end = Bry_find_.Find_fwd(fmt, Byte_ascii.Curly_end, key_bgn + 1, len);
// no } found; fail; EX: $b = 'z'; echo("a${b");
if (key_end == Bry_find_.Not_found) {
throw Err_.new_wo_type("invalid fmt; fmt=" + fmt);
}
// skip past "}"
key_end++;
}
// no "{"
else {
// search forward according to regex; ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$; REF.PHP: https://www.php.net/manual/en/language.variables.basics.php
for (int i = key_bgn; i < key_end; i++) {
byte key_cur = fmt[i];
if (!Is_identifier_char(key_cur, i == key_bgn)) {
key_end = i;
break;
}
}
}
// invalid key; EX: $0
if (key_bgn == key_end) {
bfr.Add_mid(fmt, pos, key_bgn);
pos = key_bgn;
continue;
}
// valid key; add everything before key_bgn
bfr.Add_mid(fmt, pos, dollar_pos);
// add arg_idx
bfr.Add_str_u8(Object_.Xto_str_strict_or_empty(args[arg_idx++]));
// update pos
pos = key_end;
}
return bfr.To_str_and_clear();
}
private static boolean Is_identifier_char(byte b, boolean is_first) {
switch (b) {
// alpha and _ is always valid
case Byte_ascii.Ltr_A: case Byte_ascii.Ltr_B: case Byte_ascii.Ltr_C: case Byte_ascii.Ltr_D: case Byte_ascii.Ltr_E:
case Byte_ascii.Ltr_F: case Byte_ascii.Ltr_G: case Byte_ascii.Ltr_H: case Byte_ascii.Ltr_I: case Byte_ascii.Ltr_J:
case Byte_ascii.Ltr_K: case Byte_ascii.Ltr_L: case Byte_ascii.Ltr_M: case Byte_ascii.Ltr_N: case Byte_ascii.Ltr_O:
case Byte_ascii.Ltr_P: case Byte_ascii.Ltr_Q: case Byte_ascii.Ltr_R: case Byte_ascii.Ltr_S: case Byte_ascii.Ltr_T:
case Byte_ascii.Ltr_U: case Byte_ascii.Ltr_V: case Byte_ascii.Ltr_W: case Byte_ascii.Ltr_X: case Byte_ascii.Ltr_Y: case Byte_ascii.Ltr_Z:
case Byte_ascii.Ltr_a: case Byte_ascii.Ltr_b: case Byte_ascii.Ltr_c: case Byte_ascii.Ltr_d: case Byte_ascii.Ltr_e:
case Byte_ascii.Ltr_f: case Byte_ascii.Ltr_g: case Byte_ascii.Ltr_h: case Byte_ascii.Ltr_i: case Byte_ascii.Ltr_j:
case Byte_ascii.Ltr_k: case Byte_ascii.Ltr_l: case Byte_ascii.Ltr_m: case Byte_ascii.Ltr_n: case Byte_ascii.Ltr_o:
case Byte_ascii.Ltr_p: case Byte_ascii.Ltr_q: case Byte_ascii.Ltr_r: case Byte_ascii.Ltr_s: case Byte_ascii.Ltr_t:
case Byte_ascii.Ltr_u: case Byte_ascii.Ltr_v: case Byte_ascii.Ltr_w: case Byte_ascii.Ltr_x: case Byte_ascii.Ltr_y: case Byte_ascii.Ltr_z:
case Byte_ascii.Underline:
return true;
// number is only valid if !is_first
case Byte_ascii.Num_0: case Byte_ascii.Num_1: case Byte_ascii.Num_2: case Byte_ascii.Num_3: case Byte_ascii.Num_4:
case Byte_ascii.Num_5: case Byte_ascii.Num_6: case Byte_ascii.Num_7: case Byte_ascii.Num_8: case Byte_ascii.Num_9:
return !is_first;
default:
// \x80-\xff is always true;
return b >= 128 && b <= 255;
}
}
}

@ -0,0 +1,177 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
import org.junit.*; import gplx.core.tests.*; import gplx.core.btries.*;
public class XophpString__tst {
private final XophpString__fxt fxt = new XophpString__fxt();
@Test public void Strspn_fwd__byte() {
fxt.Test_strspn_fwd__byte("aaaaab", Byte_ascii.Ltr_a, 0, -1, 5); // basic
fxt.Test_strspn_fwd__byte("aaaaab", Byte_ascii.Ltr_a, 1, -1, 4); // bgn
fxt.Test_strspn_fwd__byte("aaaaab", Byte_ascii.Ltr_a, 1, 2, 2); // max
}
@Test public void Strspn_fwd__space_or_tab() {
fxt.Test_strspn_fwd__space_or_tab(" a", 0, -1, 5); // basic
fxt.Test_strspn_fwd__space_or_tab(" a", 1, -1, 4); // bgn
fxt.Test_strspn_fwd__space_or_tab(" a", 1, 2, 2); // max
}
@Test public void Strspn_bwd__byte() {
fxt.Test_strspn_bwd__byte("aaaaab", Byte_ascii.Ltr_a, 5, -1, 5); // basic
fxt.Test_strspn_bwd__byte("aaaaab", Byte_ascii.Ltr_a, 4, -1, 4); // bgn
fxt.Test_strspn_bwd__byte("aaaaab", Byte_ascii.Ltr_a, 4, 2, 2); // max
}
@Test public void Strspn_bwd__space_or_tab() {
fxt.Test_strspn_bwd__space_or_tab(" a", 5, -1, 5); // basic
fxt.Test_strspn_bwd__space_or_tab(" a", 4, -1, 4); // bgn
fxt.Test_strspn_bwd__space_or_tab(" a", 4, 2, 2); // max
}
@Test public void Substr__bgn_is_neg() {
fxt.Test_substr("abcde" , -1, "e");
fxt.Test_substr("abcde" , -3, -1, "cd");
}
@Test public void Strtr() {
fxt.Init_strtr_by_trie("01", "89", "02", "79");
fxt.Test_strtr_by_trie("abc" , "abc"); // found=none
fxt.Test_strtr_by_trie("ab_01_cd" , "ab_89_cd"); // found=one
fxt.Test_strtr_by_trie("ab_01_cd_02_ef", "ab_89_cd_79_ef"); // found=many
fxt.Test_strtr_by_trie("01_ab" , "89_ab"); // BOS
fxt.Test_strtr_by_trie("ab_01" , "ab_89"); // EOS
}
@Test public void Str_repeat() {
fxt.Test_str_repeat("abc", 3, "abcabcabc");
fxt.Test_str_repeat("", 3, "");
fxt.Test_str_repeat("abc", 0, "");
}
@Test public void Strpos() {
fxt.Test__strpos("abc", "b", 0, 1);
fxt.Test__strpos("abc", "z", 0, XophpInt_.False);
fxt.Test__strpos("aba", "a", 1, 2);
fxt.Test__strpos("aba", "a", -2, 2);
}
@Test public void strspn() {
fxt.Test__strspn("42 is the answer to the 128th question", fxt.Init__strspn_hash("1234567890"), 0, Int_.Min_value, 2);
fxt.Test__strspn("foo", fxt.Init__strspn_hash("o"), 0, Int_.Min_value, 0);
fxt.Test__strspn("foo", fxt.Init__strspn_hash("o"), 1, 2, 2);
fxt.Test__strspn("foo", fxt.Init__strspn_hash("o"), 1, 1, 1);
}
@Test public void rtrim() {
// pad is 0, 1 char
fxt.Test__rtrim("0100", "", "0100"); // empty pad returns String
fxt.Test__rtrim("010", "0", "01"); // basic test; trim 1;
fxt.Test__rtrim("0100", "0", "01"); // basic test; trim 2;
fxt.Test__rtrim("01", "0", "01"); // nothing to trim
// pad is 2+char
fxt.Test__rtrim("10ab10", "01", "10ab"); // basic test
fxt.Test__rtrim("10ab10", "34", "10ab10"); // nothing to trim
fxt.Test__rtrim("10ab10", "010", "10ab"); // don't fail if repeated chars
// pad has ..
fxt.Test__rtrim("23ab23", "0..4", "23ab"); // basic test
fxt.Test__rtrim("23ab23.", "0.4", "23ab23"); // single dot is not range
fxt.Test__rtrim__fail("abc", "0..", ".. found but at end of String");
fxt.Test__rtrim__fail("abc", "..0", ".. found but at start of String");
fxt.Test__rtrim__fail("abc", "4..0", ".. found but next byte must be greater than previous byte");
// PHP samples
fxt.Test__rtrim("\t\tThese are a few words :) ... ", " \t.", "\t\tThese are a few words :)");
fxt.Test__rtrim("Hello World", "Hdle", "Hello Wor");
fxt.Test__rtrim("\u0009Example String\n", "\u0000..\u001F", "\u0009Example String");
// non breaking-space is "\xA0" or "\xC2\xA0" in utf-8, "µ" is "\xB5" or "\xC2\xB5" in utf-8 and "à" is "\xE0" or "\xC3\xA0" in utf-8
// REF.MW:https://www.php.net/manual/en/function.trim.php
fxt.Test__rtrim("\u00A0µ déjà\u00A0", "\u00A0", "\u00A0µ déj<C3A9>");// NOTE: technically should be "...j\xC3", but String_.new_u8 ignores invalid bytes
}
@Test public void ord() {
fxt.Test__ord("a", 97); // 1 char
fxt.Test__ord("abc", 97); // 2+ chars takes first
fxt.Test__ord("", 0); // no chars returns 0
fxt.Test__ord(null, 0); // null returns 0
}
@Test public void Fmt() {
fxt.Test__Fmt("a", "a"); // no keys
fxt.Test__Fmt("a$", "a$"); // key at end
fxt.Test__Fmt("ax", "a${x}", "x"); // curly
fxt.Test__Fmt("ax", "a$x", "x"); // basic
fxt.Test__Fmt("axyz", "a$xb$yc$zd", "x", "y", "z"); // multiple
fxt.Test__Fmt("a$0b", "a$0b", "z"); // invalid identifier
fxt.Test__Fmt("a0", "a$xyz0b", "0"); // long identifier
}
}
class XophpString__fxt {
public void Test_strspn_fwd__byte(String src_str, byte find, int bgn, int max, int expd) {
byte[] src_bry = Bry_.new_u8(src_str);
Gftest.Eq__int(expd, XophpString_.strspn_fwd__byte(src_bry, find, bgn, max, src_bry.length));
}
public void Test_strspn_fwd__space_or_tab(String src_str, int bgn, int max, int expd) {
byte[] src_bry = Bry_.new_u8(src_str);
Gftest.Eq__int(expd, XophpString_.strspn_fwd__space_or_tab(src_bry, bgn, max, src_bry.length));
}
public void Test_strspn_bwd__byte(String src_str, byte find, int bgn, int max, int expd) {
Gftest.Eq__int(expd, XophpString_.strspn_bwd__byte(Bry_.new_u8(src_str), find, bgn, max));
}
public void Test_strspn_bwd__space_or_tab(String src_str, int bgn, int max, int expd) {
Gftest.Eq__int(expd, XophpString_.strspn_bwd__space_or_tab(Bry_.new_u8(src_str), bgn, max));
}
public void Test_substr(String src_str, int bgn, String expd) {Test_substr(src_str, bgn, String_.Len(src_str), expd);}
public void Test_substr(String src_str, int bgn, int len, String expd) {
Gftest.Eq__str(expd, XophpString_.substr(Bry_.new_u8(src_str), bgn, len));
}
private Btrie_slim_mgr strtr_trie;
public void Init_strtr_by_trie(String... kvs) {
if (strtr_trie == null) strtr_trie = Btrie_slim_mgr.cs();
int len = kvs.length;
for (int i = 0; i < len; i += 2) {
strtr_trie.Add_str_str(kvs[i], kvs[i + 1]);
}
}
public void Test_strtr_by_trie(String src, String expd) {
Bry_bfr tmp = Bry_bfr_.New();
Btrie_rv trv = new Btrie_rv();
Gftest.Eq__str(expd, XophpString_.strtr(Bry_.new_u8(src), strtr_trie, tmp, trv));
}
public void Test_str_repeat(String str, int count, String expd) {
Gftest.Eq__str(expd, XophpString_.str_repeat(str, count));
}
public void Test__strpos(String haystack, String needle, int offset, int expd) {
Gftest.Eq__int(expd, XophpString_.strpos(haystack, needle, offset));
}
public Hash_adp Init__strspn_hash(String mask) {return XophpString_.strspn_hash(mask);}
public void Test__strspn(String subject, Hash_adp mask, int start, int length, int expd) {
int actl = XophpString_.strspn(subject, mask, start, length);
Gftest.Eq__int(expd, actl);
}
public void Test__rtrim(String str, String character_mask, String expd) {
Gftest.Eq__str(expd, XophpString_.rtrim(str, character_mask));
}
public void Test__rtrim__fail(String str, String character_mask, String expd_exc) {
try {
XophpString_.rtrim(str, character_mask);
} catch (Exception exc) {
String actl_exc = Err_.Message_lang(exc);
if (!String_.Has(actl_exc, expd_exc)) {
Gftest.Fail("expected failure, but got this: " + actl_exc);
}
return;
}
Gftest.Fail("expected failure, but got none: " + character_mask);
}
public void Test__ord(String str, int expd) {
Gftest.Eq__int(expd, XophpString_.ord(str));
}
public void Test__Fmt(String expd, String fmt, Object... args) {
Gftest.Eq__str(expd, XophpString_.Fmt(fmt, args));
}
}

@ -1,93 +0,0 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
import org.junit.*; import gplx.core.tests.*; import gplx.core.btries.*;
public class XophpString_tst {
private final XophpString_fxt fxt = new XophpString_fxt();
@Test public void Strspn_fwd__byte() {
fxt.Test_strspn_fwd__byte("aaaaab", Byte_ascii.Ltr_a, 0, -1, 5); // basic
fxt.Test_strspn_fwd__byte("aaaaab", Byte_ascii.Ltr_a, 1, -1, 4); // bgn
fxt.Test_strspn_fwd__byte("aaaaab", Byte_ascii.Ltr_a, 1, 2, 2); // max
}
@Test public void Strspn_fwd__space_or_tab() {
fxt.Test_strspn_fwd__space_or_tab(" a", 0, -1, 5); // basic
fxt.Test_strspn_fwd__space_or_tab(" a", 1, -1, 4); // bgn
fxt.Test_strspn_fwd__space_or_tab(" a", 1, 2, 2); // max
}
@Test public void Strspn_bwd__byte() {
fxt.Test_strspn_bwd__byte("aaaaab", Byte_ascii.Ltr_a, 5, -1, 5); // basic
fxt.Test_strspn_bwd__byte("aaaaab", Byte_ascii.Ltr_a, 4, -1, 4); // bgn
fxt.Test_strspn_bwd__byte("aaaaab", Byte_ascii.Ltr_a, 4, 2, 2); // max
}
@Test public void Strspn_bwd__space_or_tab() {
fxt.Test_strspn_bwd__space_or_tab(" a", 5, -1, 5); // basic
fxt.Test_strspn_bwd__space_or_tab(" a", 4, -1, 4); // bgn
fxt.Test_strspn_bwd__space_or_tab(" a", 4, 2, 2); // max
}
@Test public void Substr__bgn_is_neg() {
fxt.Test_substr("abcde" , -1, "e");
fxt.Test_substr("abcde" , -3, -1, "cd");
}
@Test public void Strtr() {
fxt.Init_strtr_by_trie("01", "89", "02", "79");
fxt.Test_strtr_by_trie("abc" , "abc"); // found=none
fxt.Test_strtr_by_trie("ab_01_cd" , "ab_89_cd"); // found=one
fxt.Test_strtr_by_trie("ab_01_cd_02_ef", "ab_89_cd_79_ef"); // found=many
fxt.Test_strtr_by_trie("01_ab" , "89_ab"); // BOS
fxt.Test_strtr_by_trie("ab_01" , "ab_89"); // EOS
}
@Test public void Str_repeat() {
fxt.Test_str_repeat("abc", 3, "abcabcabc");
fxt.Test_str_repeat("", 3, "");
fxt.Test_str_repeat("abc", 0, "");
}
}
class XophpString_fxt {
public void Test_strspn_fwd__byte(String src_str, byte find, int bgn, int max, int expd) {
byte[] src_bry = Bry_.new_u8(src_str);
Gftest.Eq__int(expd, XophpString.strspn_fwd__byte(src_bry, find, bgn, max, src_bry.length));
}
public void Test_strspn_fwd__space_or_tab(String src_str, int bgn, int max, int expd) {
byte[] src_bry = Bry_.new_u8(src_str);
Gftest.Eq__int(expd, XophpString.strspn_fwd__space_or_tab(src_bry, bgn, max, src_bry.length));
}
public void Test_strspn_bwd__byte(String src_str, byte find, int bgn, int max, int expd) {
Gftest.Eq__int(expd, XophpString.strspn_bwd__byte(Bry_.new_u8(src_str), find, bgn, max));
}
public void Test_strspn_bwd__space_or_tab(String src_str, int bgn, int max, int expd) {
Gftest.Eq__int(expd, XophpString.strspn_bwd__space_or_tab(Bry_.new_u8(src_str), bgn, max));
}
public void Test_substr(String src_str, int bgn, String expd) {Test_substr(src_str, bgn, String_.Len(src_str), expd);}
public void Test_substr(String src_str, int bgn, int len, String expd) {
Gftest.Eq__str(expd, XophpString.substr(Bry_.new_u8(src_str), bgn, len));
}
private Btrie_slim_mgr strtr_trie;
public void Init_strtr_by_trie(String... kvs) {
if (strtr_trie == null) strtr_trie = Btrie_slim_mgr.cs();
int len = kvs.length;
for (int i = 0; i < len; i += 2) {
strtr_trie.Add_str_str(kvs[i], kvs[i + 1]);
}
}
public void Test_strtr_by_trie(String src, String expd) {
Bry_bfr tmp = Bry_bfr_.New();
Btrie_rv trv = new Btrie_rv();
Gftest.Eq__str(expd, XophpString.strtr(Bry_.new_u8(src), strtr_trie, tmp, trv));
}
public void Test_str_repeat(String str, int count, String expd) {
Gftest.Eq__str(expd, XophpString.str_repeat(str, count));
}
}

@ -28,7 +28,7 @@ public class Wbase_repo_linker {
public byte[] getPageUrl(byte[] page) {
byte[] encodedPage = this.encodePage(page);
return Bry_.Add(this.getBaseUrl(), XophpString.str_replace(Format_Arg1, encodedPage, this.articlePath));
return Bry_.Add(this.getBaseUrl(), XophpString_.str_replace(Format_Arg1, encodedPage, this.articlePath));
}
private byte[] encodePage(byte[] page) {

@ -519,7 +519,7 @@ public class XomwHtml {
}
else {
// PORTED.HEADER:atrValEncodings
val = XophpString.strtr(val, atrValEncodings, temp.bfr, temp.trv);
val = XophpString_.strtr(val, atrValEncodings, temp.bfr, temp.trv);
bfr.Add_byte_space().Add(key).Add(ATR_VAL_QUOTE).Add(val).Add_byte_quote();
}
}

@ -838,7 +838,7 @@ public class XomwMessage {
}
// Replace $* with a list of parameters for &uselang=qqx.
// if (XophpString.strpos(s, "$*") != false) {
// if (XophpString_.strpos(s, "$*") != false) {
// String paramlist = "";
// if (this.parameters != []) {
// paramlist = ": $" . implode(", $", range(1, count(this.parameters)));

@ -309,7 +309,7 @@ public class XomwTitle {
byte[] filteredText = text;
XomwTitle t = new XomwTitle(env);
t.mDbkeyform = XophpString.strtr(filteredText, Byte_ascii.Space, Byte_ascii.Underline);
t.mDbkeyform = XophpString_.strtr(filteredText, Byte_ascii.Space, Byte_ascii.Underline);
t.mDefaultNamespace = defaultNamespace;
t.secureAndSplit(env);
@ -1435,7 +1435,7 @@ public class XomwTitle {
*/
public byte[] getPrefixedDBkey() {
byte[] s = this.prefix(this.mDbkeyform);
s = XophpString.strtr(s, Byte_ascii.Space, Byte_ascii.Underline);
s = XophpString_.strtr(s, Byte_ascii.Space, Byte_ascii.Underline);
return s;
}
public String getPrefixedDBkeyStr() {return String_.new_u8(getPrefixedDBkey());}
@ -1449,7 +1449,7 @@ public class XomwTitle {
public byte[] getPrefixedText() {
if (this.mPrefixedText == null) {
byte[] s = this.prefix(this.mTextform);
s = XophpString.strtr(s, Byte_ascii.Underline, Byte_ascii.Space);
s = XophpString_.strtr(s, Byte_ascii.Underline, Byte_ascii.Space);
this.mPrefixedText = s;
}
return this.mPrefixedText;
@ -3380,7 +3380,7 @@ public class XomwTitle {
this.mDbkeyform = parts.dbkey;
this.mUrlform = XomwGlobalFunctions.wfUrlencode(this.mDbkeyform);
this.mTextform = XophpString.strtr(this.mDbkeyform, Byte_ascii.Underline, Byte_ascii.Space);
this.mTextform = XophpString_.strtr(this.mDbkeyform, Byte_ascii.Underline, Byte_ascii.Space);
// We already know that some pages won't be in the database!
if (this.isExternal() || this.mNamespace == XomwDefines.NS_SPECIAL) {

@ -0,0 +1,286 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.includes.cache; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
// /**
// * This class stores an arbitrary value along with its dependencies.
// * Users should typically only use DependencyWrapper::getValueFromCache(),
// * rather than instantiating one of these objects directly.
// * @ingroup Cache
// */
// class DependencyWrapper {
// private $value;
// /** @var CacheDependency[] */
// private $deps;
//
// /**
// * Create an instance.
// * @param mixed $value The user-supplied value
// * @param CacheDependency|CacheDependency[] $deps A dependency or dependency
// * array. All dependencies must be objects implementing CacheDependency.
// */
// function __construct( $value = false, $deps = [] ) {
// $this->value = $value;
//
// if ( !is_array( $deps ) ) {
// $deps = [ $deps ];
// }
//
// $this->deps = $deps;
// }
//
// /**
// * Returns true if any of the dependencies have expired
// *
// * @return boolean
// */
// function isExpired() {
// foreach ( $this->deps as $dep ) {
// if ( $dep->isExpired() ) {
// return true;
// }
// }
//
// return false;
// }
//
// /**
// * Initialise dependency values in preparation for storing. This must be
// * called before serialization.
// */
// function initialiseDeps() {
// foreach ( $this->deps as $dep ) {
// $dep->loadDependencyValues();
// }
// }
//
// /**
// * Get the user-defined value
// * @return boolean|mixed
// */
// function getValue() {
// return $this->value;
// }
//
// /**
// * Store the wrapper to a cache
// *
// * @param BagOStuff $cache
// * @param String $key
// * @param int $expiry
// */
// function storeToCache( $cache, $key, $expiry = 0 ) {
// $this->initialiseDeps();
// $cache->set( $key, $this, $expiry );
// }
//
// /**
// * Attempt to get a value from the cache. If the value is expired or missing,
// * it will be generated with the callback function (if present), and the newly
// * calculated value will be stored to the cache in a wrapper.
// *
// * @param BagOStuff $cache A cache Object
// * @param String $key The cache key
// * @param int $expiry The expiry timestamp or interval in seconds
// * @param boolean|callable $callback The callback for generating the value, or false
// * @param array $callbackParams The function parameters for the callback
// * @param array $deps The dependencies to store on a cache miss. Note: these
// * are not the dependencies used on a cache hit! Cache hits use the stored
// * dependency array.
// *
// * @return mixed The value, or null if it was not present in the cache and no
// * callback was defined.
// */
// static function getValueFromCache( $cache, $key, $expiry = 0, $callback = false,
// $callbackParams = [], $deps = []
// ) {
// $obj = $cache->get( $key );
//
// if ( is_object( $obj ) && $obj instanceof DependencyWrapper && !$obj->isExpired() ) {
// $value = $obj->value;
// } elseif ( $callback ) {
// $value = call_user_func_array( $callback, $callbackParams );
// # Cache the newly-generated value
// $wrapper = new DependencyWrapper( $value, $deps );
// $wrapper->storeToCache( $cache, $key, $expiry );
// } else {
// $value = null;
// }
//
// return $value;
// }
// }
//
// /**
// * @ingroup Cache
// */
// abstract class CacheDependency {
// /**
// * Returns true if the dependency is expired, false otherwise
// */
// abstract function isExpired();
//
// /**
// * Hook to perform any expensive pre-serialize loading of dependency values.
// */
// function loadDependencyValues() {
// }
// }
//
// /**
// * @ingroup Cache
// */
// class FileDependency extends CacheDependency {
// private $filename;
// private $timestamp;
//
// /**
// * Create a file dependency
// *
// * @param String $filename The name of the file, preferably fully qualified
// * @param null|boolean|int $timestamp The unix last modified timestamp, or false if the
// * file does not exist. If omitted, the timestamp will be loaded from
// * the file.
// *
// * A dependency on a nonexistent file will be triggered when the file is
// * created. A dependency on an existing file will be triggered when the
// * file is changed.
// */
// function __construct( $filename, $timestamp = null ) {
// $this->filename = $filename;
// $this->timestamp = $timestamp;
// }
//
// /**
// * @return array
// */
// function __sleep() {
// $this->loadDependencyValues();
//
// return [ 'filename', 'timestamp' ];
// }
//
// function loadDependencyValues() {
// if ( is_null( $this->timestamp ) ) {
// MediaWiki\suppressWarnings();
// # Dependency on a non-existent file stores "false"
// # This is a valid concept!
// $this->timestamp = filemtime( $this->filename );
// MediaWiki\restoreWarnings();
// }
// }
//
// /**
// * @return boolean
// */
// function isExpired() {
// MediaWiki\suppressWarnings();
// $lastmod = filemtime( $this->filename );
// MediaWiki\restoreWarnings();
// if ( $lastmod === false ) {
// if ( $this->timestamp === false ) {
// # Still nonexistent
// return false;
// } else {
// # Deleted
// wfDebug( "Dependency triggered: {$this->filename} deleted.\n" );
//
// return true;
// }
// } else {
// if ( $lastmod > $this->timestamp ) {
// # Modified or created
// wfDebug( "Dependency triggered: {$this->filename} changed.\n" );
//
// return true;
// } else {
// # Not modified
// return false;
// }
// }
// }
// }
//
// /**
// * @ingroup Cache
// */
// class GlobalDependency extends CacheDependency {
// private $name;
// private $value;
//
// function __construct( $name ) {
// $this->name = $name;
// $this->value = $GLOBALS[$name];
// }
//
// /**
// * @return boolean
// */
// function isExpired() {
// if ( !isset( $GLOBALS[$this->name] ) ) {
// return true;
// }
//
// return $GLOBALS[$this->name] != $this->value;
// }
// }
//
// /**
// * @ingroup Cache
// */
// class MainConfigDependency extends CacheDependency {
// private $name;
// private $value;
//
// function __construct( $name ) {
// $this->name = $name;
// $this->value = $this->getConfig()->get( $this->name );
// }
//
// private function getConfig() {
// return MediaWikiServices::getInstance()->getMainConfig();
// }
//
// /**
// * @return boolean
// */
// function isExpired() {
// if ( !$this->getConfig()->has( $this->name ) ) {
// return true;
// }
//
// return $this->getConfig()->get( $this->name ) != $this->value;
// }
// }
//
// /**
// * @ingroup Cache
// */
// class ConstantDependency extends CacheDependency {
// private $name;
// private $value;
//
// function __construct( $name ) {
// $this->name = $name;
// $this->value = constant( $name );
// }
//
// /**
// * @return boolean
// */
// function isExpired() {
// return constant( $this->name ) != $this->value;
// }
// }

@ -0,0 +1,62 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.includes.cache.localisation; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.cache.*;
// MW.SRC:1.33
/**
* Interface for the persistence layer of LocalisationCache.
*
* The persistence layer is two-level hierarchical cache. The first level
* is the language, the second level is the item or subitem.
*
* Since the data for a whole language is rebuilt in one operation, it needs
* to have a fast and atomic method for deleting or replacing all of the
* current data for a given language. The interface reflects this bulk update
* operation. Callers writing to the cache must first call startWrite(), then
* will call set() a couple of thousand times, then will call finishWrite()
* to commit the operation. When finishWrite() is called, the cache is
* expected to delete all data previously stored for that language.
*
* The values stored are PHP variables suitable for serialize(). Implementations
* of LCStore are responsible for serializing and unserializing.
*/
interface XomwLCStore {
/**
* Get a value.
* @param String code Language code
* @param String key Cache key
*/
Object get(String code, String key);
/**
* Start a write transaction.
* @param String code Language code
*/
void startWrite(String code);
/**
* Finish a write transaction.
*/
void finishWrite();
/**
* Set a key to a given value. startWrite() must be called before this
* is called, and finishWrite() must be called afterwards.
* @param String key
* @param mixed value
*/
void set(String key, String value);
}

@ -0,0 +1,35 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.includes.cache.localisation; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.cache.*;
// MW.SRC:1.33
/**
* Null store backend, used to avoid DB errors during install
*/
class XomwLCStoreNull implements XomwLCStore {
public Object get(String code, String key) {
return null;
}
public void startWrite(String code) {
}
public void finishWrite() {
}
public void set(String key, String value) {
}
}

@ -0,0 +1,31 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.includes.cache.localisation; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.cache.*;
public class XomwLocalisationCacheForXowa extends XomwLocalisationCache { private static boolean init;
public static void Init_ip(Io_url val) {
init = false;
IP = val.Raw();
}
public XophpArray getItem_ary(String code, String key) {
if (!init) {
init = true;
this.loadPluralFiles();
}
XophpArray rv = getCompiledPluralRules(code);
return rv;
}
}

@ -14,6 +14,7 @@ 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.mediawiki.includes.exception; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
import gplx.core.strings.*;
public class XomwMWException extends Err {
public XomwMWException(String msg) {super(true, "", "", msg);
}

@ -293,9 +293,9 @@ public class XomwFile {
*/
private byte[] getExtension() {
if (!XophpUtility.isset(this.extension)) {
int n = XophpString.strpos(this.getName(), Byte_ascii.Dot);
int n = XophpString_.strpos(this.getName(), Byte_ascii.Dot);
this.extension = normalizeExtension(
n != Bry_find_.Not_found ? XophpString.substr(this.getName(), n + 1) : Bry_.Empty);
n != Bry_find_.Not_found ? XophpString_.substr(this.getName(), n + 1) : Bry_.Empty);
}
return this.extension;

@ -111,7 +111,7 @@ public class XomwInterwiki {
public byte[] getURL(byte[] title) {
byte[] url = this.mURL;
if (title != null) {
url = XophpString.str_replace(ARG_1, XomwGlobalFunctions.wfUrlencode(title), url);
url = XophpString_.str_replace(ARG_1, XomwGlobalFunctions.wfUrlencode(title), url);
}
return url;

@ -278,8 +278,8 @@ public class XomwBlockLevelPass {
// If not in a <pre> element, scan for and figure out what prefixes are there.
if (!this.inPre) {
// Multiple prefixes may abut each other for nested lists.
prefixLen = XophpString.strspn_fwd__ary(src, block_chars_ary, lineBgn, lineEnd, lineEnd); // strspn($oLine, '*#:;');
prefix = XophpString.substr(src, lineBgn, prefixLen);
prefixLen = XophpString_.strspn_fwd__ary(src, block_chars_ary, lineBgn, lineEnd, lineEnd); // strspn($oLine, '*#:;');
prefix = XophpString_.substr(src, lineBgn, prefixLen);
// eh?
// ; and : are both from definition-lists, so they're equivalent
@ -302,7 +302,7 @@ public class XomwBlockLevelPass {
int commonPrefixLen = -1;
if (prefixLen > 0 && Bry_.Eq(lastPrefix, prefix2)) {
// Same as the last item, so no need to deal with nesting or opening stuff
bfr.Add(this.nextItem(XophpString.substr_byte(prefix, -1)));
bfr.Add(this.nextItem(XophpString_.substr_byte(prefix, -1)));
pendingPTag = PARA_STACK_NONE;
if (prefixLen > 0 && prefix[prefixLen - 1] == Byte_ascii.Semic) {
@ -342,7 +342,7 @@ public class XomwBlockLevelPass {
bfr.Add_byte_nl();
}
while (prefixLen > commonPrefixLen) {
byte c = XophpString.substr_byte(prefix, commonPrefixLen, 1);
byte c = XophpString_.substr_byte(prefix, commonPrefixLen, 1);
bfr.Add(this.openList(c));
if (c == Byte_ascii.Semic) {
@ -396,7 +396,7 @@ public class XomwBlockLevelPass {
inBlockElem = !closeMatch;
}
else if (!inBlockElem && !this.inPre) {
if (XophpString.substr_byte(t, 0) == Byte_ascii.Space
if (XophpString_.substr_byte(t, 0) == Byte_ascii.Space
&& (this.lastSection == LAST_SECTION_PRE || Bry_.Trim(t) != Bry_.Empty)
&& !inBlockquote
) {
@ -481,18 +481,18 @@ public class XomwBlockLevelPass {
*/
private int findColonNoLinks(byte[] str, byte[] before, byte[] after) {
int len = str.length;
int colonPos = XophpString.strpos(str, Byte_ascii.Colon, 0, len);
int colonPos = XophpString_.strpos(str, Byte_ascii.Colon, 0, len);
if (colonPos == Bry_find_.Not_found) {
// Nothing to find!
return Bry_find_.Not_found;
}
int ltPos = XophpString.strpos(str, Byte_ascii.Angle_bgn, 0, len);
int ltPos = XophpString_.strpos(str, Byte_ascii.Angle_bgn, 0, len);
if (ltPos == Bry_find_.Not_found || ltPos > colonPos) {
// Easy; no tag nesting to worry about
// XOMW: MW passes before / after by reference; XO: changes member and depends on callers to update
find_colon_no_links__before = XophpString.substr(str, 0, colonPos);
find_colon_no_links__after = XophpString.substr(str, colonPos + 1);
find_colon_no_links__before = XophpString_.substr(str, 0, colonPos);
find_colon_no_links__after = XophpString_.substr(str, colonPos + 1);
return colonPos;
}
@ -512,25 +512,25 @@ public class XomwBlockLevelPass {
case Byte_ascii.Colon:
if (level == 0) {
// We found it!
find_colon_no_links__before = XophpString.substr(str, 0, i);
find_colon_no_links__after = XophpString.substr(str, i + 1);
find_colon_no_links__before = XophpString_.substr(str, 0, i);
find_colon_no_links__after = XophpString_.substr(str, i + 1);
return i;
}
// Embedded in a tag; don't break it.
break;
default:
// Skip ahead looking for something interesting
colonPos = XophpString.strpos(str, Byte_ascii.Colon, i, len);
colonPos = XophpString_.strpos(str, Byte_ascii.Colon, i, len);
if (colonPos == Bry_find_.Not_found) {
// Nothing else interesting
return Bry_find_.Not_found;
}
ltPos = XophpString.strpos(str, Byte_ascii.Angle_bgn, i, len);
ltPos = XophpString_.strpos(str, Byte_ascii.Angle_bgn, i, len);
if (level == 0) {
if (ltPos == Bry_find_.Not_found || colonPos < ltPos) {
// We found it!
find_colon_no_links__before = XophpString.substr(str, 0, colonPos);
find_colon_no_links__after = XophpString.substr(str, colonPos + 1);
find_colon_no_links__before = XophpString_.substr(str, 0, colonPos);
find_colon_no_links__after = XophpString_.substr(str, colonPos + 1);
return i;
}
}

@ -431,11 +431,11 @@ public abstract class XomwPreprocessor {
}
else {
// Search backwards for leading whitespace
int ws_bgn = i > 0 ? i - XophpString.strspn_bwd__space_or_tab(src, i, -1) : 0;
int ws_bgn = i > 0 ? i - XophpString_.strspn_bwd__space_or_tab(src, i, -1) : 0;
// Search forwards for trailing whitespace
// $wsEnd will be the position of the last space (or the '>' if there's none)
int ws_end = end_pos + 2 + XophpString.strspn_fwd__space_or_tab(src, end_pos + 3, -1, src_len);
int ws_end = end_pos + 2 + XophpString_.strspn_fwd__space_or_tab(src, end_pos + 3, -1, src_len);
// Keep looking forward as long as we're finding more
// comments.
@ -446,7 +446,7 @@ public abstract class XomwPreprocessor {
if (cur_char_pos == Bry_find_.Not_found) {
break;
}
cur_char_pos = cur_char_pos + 2 + XophpString.strspn_fwd__space_or_tab(src, cur_char_pos + 3, -1, src_len);
cur_char_pos = cur_char_pos + 2 + XophpString_.strspn_fwd__space_or_tab(src, cur_char_pos + 3, -1, src_len);
comments_list.Add(new int[] {ws_end + 1, cur_char_pos});
ws_end = cur_char_pos;
}
@ -609,7 +609,7 @@ public abstract class XomwPreprocessor {
i++;
}
int count = XophpString.strspn_fwd__byte(src, Byte_ascii.Eq, i, 6, src_len);
int count = XophpString_.strspn_fwd__byte(src, Byte_ascii.Eq, i, 6, src_len);
if (count == 1 && findEquals) { // EX: "{{a|\n=b=\n"
// DWIM: This looks kind of like a name/value separator.
// Let's let the equals handler have it and break the
@ -619,7 +619,7 @@ public abstract class XomwPreprocessor {
}
else if (count > 0) {
XomwPPDStackElement piece = Factory__stack_element(Factory__part(), String_.Nl, String_.Nl, count, i, false);
piece.addPart(XophpString.str_repeat("=", count));
piece.addPart(XophpString_.str_repeat("=", count));
stack.push(piece);
accum = this.Accum__set(stack.getAccum());
XomwPPDStackElementFlags flags = stack.getFlags();
@ -638,7 +638,7 @@ public abstract class XomwPreprocessor {
// Search back through the input to see if it has a proper close.
// Do this using the reversed String since the other solutions
// (end anchor, etc.) are inefficient.
int ws_len = XophpString.strspn_bwd__space_or_tab(src, src_len - i, -1);
int ws_len = XophpString_.strspn_bwd__space_or_tab(src, src_len - i, -1);
int search_bgn = i - ws_len;
if (part.commentEnd != -1 && search_bgn -1 == part.commentEnd) {
@ -646,10 +646,10 @@ public abstract class XomwPreprocessor {
// Search for equals signs before the comment
search_bgn = part.visualEnd;
search_bgn = Bry_find_.Find_bwd__while_space_or_tab(src, search_bgn, 0);
search_bgn -= XophpString.strspn_bwd__space_or_tab(src, search_bgn, -1);
search_bgn -= XophpString_.strspn_bwd__space_or_tab(src, search_bgn, -1);
}
int count = piece.count;
int eq_len = XophpString.strspn_bwd__byte(src, Byte_ascii.Eq, search_bgn, -1);
int eq_len = XophpString_.strspn_bwd__byte(src, Byte_ascii.Eq, search_bgn, -1);
Xomw_prepro_accum element = null;
if (eq_len > 0) {
@ -702,7 +702,7 @@ public abstract class XomwPreprocessor {
}
else if (found == Found__open) {
// count opening brace characters
int count = XophpString.strspn_fwd__byte(src, cur_char[0], i, -1, src_len); // NOTE: don't know how MediaWiki will handle "-{"
int count = XophpString_.strspn_fwd__byte(src, cur_char[0], i, -1, src_len); // NOTE: don't know how MediaWiki will handle "-{"
// we need to add to stack only if opening brace count is enough for one of the rules
if (count >= rule.min) {
@ -726,7 +726,7 @@ public abstract class XomwPreprocessor {
XomwPPDStackElement piece = stack.top;
// lets check if there are enough characters for closing brace
int max_count = piece.count;
int count = XophpString.strspn_fwd__byte(src, cur_char[0], i, max_count, src_len);
int count = XophpString_.strspn_fwd__byte(src, cur_char[0], i, max_count, src_len);
// check for maximum matching characters (if there are 5 closing characters, we will probably need only 3 - depending on the rules)
rule = Get_rule(piece.open);
@ -785,7 +785,7 @@ public abstract class XomwPreprocessor {
this.accum = this.Accum__set(stack.getAccum());
}
else {
this.preprocessToObj_literal(Bry_.new_u8(XophpString.str_repeat(piece.open, piece.count)));
this.preprocessToObj_literal(Bry_.new_u8(XophpString_.str_repeat(piece.open, piece.count)));
}
}

@ -57,7 +57,7 @@ class XomwPreprocessor_DOM extends XomwPreprocessor { private final Bry_bfr
@Override protected void preprocessToObj_removeLeadingWhitespaceFromEnd(int ws_len) {
int accum_dom_len = accum_dom.Len();
if ( ws_len > 0
&& XophpString.strspn_fwd__space_or_tab(accum_dom.Bfr_bry(), accum_dom_len - ws_len, -1, accum_dom_len) == ws_len) {
&& XophpString_.strspn_fwd__space_or_tab(accum_dom.Bfr_bry(), accum_dom_len - ws_len, -1, accum_dom_len) == ws_len) {
accum_dom.Del_at_end(ws_len);
}
}

@ -46,13 +46,13 @@ class XomwPreprocessor_Hash extends XomwPreprocessor { private XophpArray accum
@Override protected void preprocessToObj_root() {} // NOTE: deliberately empty;
@Override protected void preprocessToObj_ignore(byte[] src, int bgn, int end) {
accum.Add(XophpArray.New("ignore", XophpArray.New(XophpString.substr(src, bgn, end - bgn))));
accum.Add(XophpArray.New("ignore", XophpArray.New(XophpString_.substr(src, bgn, end - bgn))));
}
@Override protected void preprocessToObj_literal(byte[] src, int bgn, int end) {
addLiteral(accum, XophpString.substr(src, bgn, end - bgn));
addLiteral(accum, XophpString_.substr(src, bgn, end - bgn));
}
@Override protected void preprocessToObj_comment(byte[] src, int bgn, int end) {
accum.Add(XophpArray.New("comment", XophpArray.New(XophpString.substr(src, bgn, end - bgn))));
accum.Add(XophpArray.New("comment", XophpArray.New(XophpString_.substr(src, bgn, end - bgn))));
}
@Override protected void preprocessToObj_removeLeadingWhitespaceFromEnd(int ws_len) {
int endIndex = accum.Len() - 1;
@ -61,8 +61,8 @@ class XomwPreprocessor_Hash extends XomwPreprocessor { private XophpArray accum
Object itm_obj = accum.Get_at(endIndex);
if (XophpTypeUtl.is_string(itm_obj)) {
byte[] itm = Bry_.new_u8((String)itm_obj);
if (XophpString.strspn_fwd__space_or_tab(itm, itm.length - ws_len, -1, itm.length) == ws_len) {
accum.Set(endIndex, XophpString.substr(itm, 0, -ws_len));
if (XophpString_.strspn_fwd__space_or_tab(itm, itm.length - ws_len, -1, itm.length) == ws_len) {
accum.Set(endIndex, XophpString_.substr(itm, 0, -ws_len));
}
}
}
@ -103,7 +103,7 @@ class XomwPreprocessor_Hash extends XomwPreprocessor { private XophpArray accum
@Override protected Xomw_prepro_accum preprocessToObj_text(XomwPPDStackElement piece, byte[] rule_end, int matching_count) {
XophpArray array = (XophpArray)((XomwPPDStackElement)piece).breakSyntax(matching_count);
addLiteral(array, XophpString.str_repeat(String_.new_u8(rule_end), matching_count));
addLiteral(array, XophpString_.str_repeat(String_.new_u8(rule_end), matching_count));
return new Xomw_prepro_accum__hash(array);
}
@Override protected Xomw_prepro_accum preprocessToObj_xml(XomwPPDStackElement piece, byte[] name_bry, int max_count, int matching_count) {

@ -224,12 +224,12 @@ public class Xomw_magiclinks_wkr {
// XO.MW: if (strpos($url, '(') === false) {$sep .= ')';}
url_separators[Byte_ascii.Paren_end] = Bry_find_.Find_fwd(url, Byte_ascii.Paren_bgn, 0, url_len) == Bry_find_.Not_found;
int num_sep_chars = XophpString.strspn_bwd__ary(url, url_separators, url_len, -1);
int num_sep_chars = XophpString_.strspn_bwd__ary(url, url_separators, url_len, -1);
// Don't break a trailing HTML entity by moving the ; into $trail
// This is in hot code, so use substr_compare to avoid having to
// create a new String Object for the comparison
// XO.MW.NOTE: ignore semic if part of entity; EX: "http://a.org&apos;!."
if (num_sep_chars > 0 && XophpString.substr_byte(url, -num_sep_chars) == Byte_ascii.Semic) {
if (num_sep_chars > 0 && XophpString_.substr_byte(url, -num_sep_chars) == Byte_ascii.Semic) {
// more optimization: instead of running preg_match with a $
// anchor, which can be slow, do the match on the reversed
// String starting at the desired offset.
@ -241,8 +241,8 @@ public class Xomw_magiclinks_wkr {
}
if (num_sep_chars > 0) {
trail = Bry_.Add(XophpString.substr(url, -num_sep_chars), trail);
url = XophpString.substr(url, 0, -num_sep_chars);
trail = Bry_.Add(XophpString_.substr(url, -num_sep_chars), trail);
url = XophpString_.substr(url, 0, -num_sep_chars);
}
// Verify that we still have a real URL after trail removal, and

@ -106,7 +106,7 @@ public class XomwPPDStackElement {
if (openingCount == -1) {
openingCount = this.count;
}
bfr.Add_str(XophpString.str_repeat(this.open, openingCount));
bfr.Add_str(XophpString_.str_repeat(this.open, openingCount));
boolean first = true;
int parts_len = parts.Len();
for (int i = 0; i < parts_len; i++) {

@ -39,7 +39,7 @@ public class XomwPPDStackElement_Hash extends XomwPPDStackElement { public Xomw
if (openingCount == -1) {
openingCount = this.count;
}
accum = XophpArray.New(XophpString.str_repeat(this.open, openingCount));
accum = XophpArray.New(XophpString_.str_repeat(this.open, openingCount));
int lastIndex = 0;
boolean first = true;
int parts_len = parts.Len();

@ -64,7 +64,7 @@ class XomwPPFrame_Hash extends XomwPPFrame { /**
this.preprocessor = preprocessor;
this.parser = preprocessor.Parser();
this.title = this.parser.mTitle;
this.titleCache = XophpArray.New().Add(XophpObject.is_true(this.title) ? this.title.getPrefixedDBkeyStr() : XophpString_.False);
this.titleCache = XophpArray.New().Add(XophpObject.is_true(this.title) ? this.title.getPrefixedDBkeyStr() : XophpString_.Null);
this.loopCheckHash = XophpArray.New();
this.depth = 0;
this.childExpansionCache = XophpArray.New();
@ -153,7 +153,7 @@ class XomwPPFrame_Hash extends XomwPPFrame { /**
* @return String
*/
public String expand(Object root, int flags) {
if (XophpString.is_string(root)) {
if (XophpString_.is_string(root)) {
return (String)root;
}
@ -214,12 +214,12 @@ class XomwPPFrame_Hash extends XomwPPFrame { /**
}
Object newIterator = XophpObject.False;
String contextName = XophpString_.False;
String contextName = XophpString_.Null;
XophpArray contextChildren = XophpArray.False;
if (contextNode == XophpObject.False) {
// nothing to do
} else if (XophpString.is_string(contextNode)) {
} else if (XophpString_.is_string(contextNode)) {
outItm += (String)contextNode;
} else if (Type_.Eq_by_obj(contextNode, XomwPPNode_Hash_Array.class)) {
newIterator = contextNode;
@ -245,7 +245,7 @@ class XomwPPFrame_Hash extends XomwPPFrame { /**
}
// Handle node descriptor array or tree Object
if (contextName == XophpString_.False) {
if (!XophpString_.is_true(contextName)) {
// Not a node, already handled above
} else if (String_.CharAt(contextName, 0) == '@') {
// Attribute: no output
@ -346,7 +346,7 @@ class XomwPPFrame_Hash extends XomwPPFrame { /**
// this.parser.mHeadings[] = [titleText, bits['i']];
// serial = count(this.parser.mHeadings) - 1;
String marker = XomwParser.MARKER_PREFIX + "-h-serial-" + XomwParser.MARKER_SUFFIX;
s = XophpString.substr(s, 0, bits.Get_by_int("level")) + marker + XophpString.substr(s, bits.Get_by_int("level"));
s = XophpString_.substr(s, 0, bits.Get_by_int("level")) + marker + XophpString_.substr(s, bits.Get_by_int("level"));
// this.parser.mStripState.addGeneral(marker, '');
outItm += s;
} else {
@ -523,7 +523,7 @@ class XomwPPFrame_Hash extends XomwPPFrame { /**
return this.title.getPrefixedDBkeyStr();
} else {
// return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
return this.titleCache.Count() > 0 ? ((String)this.titleCache.Get_at(0)) : XophpString_.False;
return this.titleCache.Count() > 0 ? ((String)this.titleCache.Get_at(0)) : XophpString_.Null;
}
}
@ -562,7 +562,7 @@ class XomwPPFrame_Hash extends XomwPPFrame { /**
* @return boolean Always false in this implementation.
*/
@Override public String getArgument(String name) {
return XophpString_.False;
return XophpString_.Null;
}
/**

@ -35,7 +35,7 @@ public class XomwPPNode_Hash_Attr extends XomwPPNode { public String name, valu
if (!(String_.CharAt(descriptor_name, 0) == '@')) {
throw Err_.new_wo_type("XomwPPNode_Hash_Attr.CTOR: invalid name in attribute descriptor");
}
this.name = String_.new_u8(XophpString.substr(Bry_.new_u8(descriptor_name), 1));
this.name = String_.new_u8(XophpString_.substr(Bry_.new_u8(descriptor_name), 1));
XophpArray descriptor_children = (XophpArray)descriptor.Get_at(XomwPPNode_Hash_Tree.CHILDREN);
Object value_obj = descriptor_children.Get_at(0);
this.value = Type_.Eq_by_obj(value_obj, byte[].class) ? String_.new_u8((byte[])value_obj): value_obj.toString();

@ -110,8 +110,8 @@ public class Xomw_quote_wkr {// THREAD.UNSAFE: caching for repeated calls
for (int i = 1; i < arr_len; i += 2) {
if (arr[i].length == 3) {
byte[] prv = arr[i - 1];
byte prv__last_char = XophpString.substr_byte(prv, -1);
byte prv__last_minus_1_char = XophpString.substr_byte(prv, -2, 1);
byte prv__last_char = XophpString_.substr_byte(prv, -1);
byte prv__last_minus_1_char = XophpString_.substr_byte(prv, -2, 1);
if (prv__last_char == Byte_ascii.Space) { // NOTE: prv ends in space; EX: "''prv '''"
if (prv_ends_w_space == -1) {
prv_ends_w_space = i;

@ -44,7 +44,7 @@ public class XomwMediaWikiSite extends XomwSite { private static final String PA
* @return String
*/
public byte[] toDBKey(byte[] title) {
return XophpString.str_replace(Byte_ascii.Space_bry, Byte_ascii.Underline_bry, title);
return XophpString_.str_replace(Byte_ascii.Space_bry, Byte_ascii.Underline_bry, title);
}
/**

@ -371,7 +371,7 @@ public class XomwSite {
}
if (pageName != null) {
url = String_.new_u8(XophpString.str_replace(Bry_.new_a7("$1"), XophpEncode.rawurlencode(Bry_.new_u8(pageName)), Bry_.new_u8(url)));
url = String_.new_u8(XophpString_.str_replace(Bry_.new_a7("$1"), XophpEncode.rawurlencode(Bry_.new_u8(pageName)), Bry_.new_u8(url)));
}
return url;

@ -239,7 +239,7 @@ public class XomwMediaWikiTitleCodec implements XomwTitleFormatter {
*/
private final byte[][] tmpPrefixRegex = new byte[2][];
public XomwMediaWikiTitleCodecParts splitTitleString(byte[] text, int defaultNamespace) {
byte[] dbkey = XophpString.str_replace(Byte_ascii.Space, Byte_ascii.Underline, text);
byte[] dbkey = XophpString_.str_replace(Byte_ascii.Space, Byte_ascii.Underline, text);
// Initialisation
XomwMediaWikiTitleCodecParts parts = new XomwMediaWikiTitleCodecParts(dbkey, defaultNamespace);
@ -339,7 +339,7 @@ public class XomwMediaWikiTitleCodec implements XomwTitleFormatter {
// resets the default namespace
if (dbkey != Bry_.Empty && dbkey[0] == Byte_ascii.Colon) {
parts.ns = XomwDefines.NS_MAIN;
dbkey = XophpString.substr(dbkey, 1);
dbkey = XophpString_.substr(dbkey, 1);
}
}
// If there's no recognized interwiki or namespace,
@ -348,10 +348,10 @@ public class XomwMediaWikiTitleCodec implements XomwTitleFormatter {
break;
} while (true);
byte[] fragment = XophpString.strstr(dbkey, Byte_ascii.Hash_bry);
byte[] fragment = XophpString_.strstr(dbkey, Byte_ascii.Hash_bry);
if (null != fragment) {
parts.fragment = XophpString.str_replace(Byte_ascii.Underline, Byte_ascii.Space, XophpString.substr(fragment, 1));
dbkey = XophpString.substr(dbkey, 0, XophpString.strlen(dbkey) - XophpString.strlen(fragment));
parts.fragment = XophpString_.str_replace(Byte_ascii.Underline, Byte_ascii.Space, XophpString_.substr(fragment, 1));
dbkey = XophpString_.substr(dbkey, 0, XophpString_.strlen(dbkey) - XophpString_.strlen(fragment));
// remove whitespace again: prevents "Foo_bar_#"
// becoming "Foo_bar_"
dbkey = Bry_.Replace(dbkey, Byte_ascii.Underline_bry, Bry_.Empty);

File diff suppressed because it is too large Load Diff

@ -0,0 +1,70 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.languages; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*;
import gplx.core.tests.*;
import gplx.xowa.langs.*;
import gplx.xowa.mediawiki.includes.cache.localisation.*;
public class XomwLanguage_fxt {
private XomwLanguage lang;
private final Xoae_app app;
public XomwLanguage_fxt() {
this.app = Xoa_app_fxt.Make__app__edit();
this.Init__lang("en");
}
public void Init_digitGroupingPattern(String digitGroupingPattern) {
lang.setDigitGroupingPattern(Bry_.new_u8(digitGroupingPattern));
}
public void Init__lang(String lang_code) {
Xol_lang_itm xoLang = app.Lang_mgr().Get_by_or_load(Bry_.new_a7(lang_code));
this.lang = new XomwLanguage(xoLang);
}
public void Test_commafy(String raw, String expd) {
Gftest.Eq__str(expd, lang.commafy(Bry_.new_u8(raw)));
}
public void Test__handleExplicitPluralForms__string(String count, XophpArray forms, String expd) {
Gftest.Eq__str(expd, (String)lang.handleExplicitPluralForms(count, forms));
}
public void Test__handleExplicitPluralForms__array(String count, XophpArray forms, XophpArray expd) {
Gftest.Eq__ary(expd.To_ary(), ((XophpArray)lang.handleExplicitPluralForms(count, forms)).To_ary());
}
public void Init__pluralRulesXml(String... ary) {
String xml = String_.Replace(String_.Concat_lines_nl
( "<supplementalData>"
, " <version number='$Revision: 10807 $'/>"
, " <generation date='$Date: 2014-08-14 14:43:27 -0500 (Thu, 14 Aug 2014) $'/>"
, " <plurals type='cardinal'>"
, String_.Concat_lines_nl(ary)
, " </plurals>"
, "</supplementalData>"
), "'", "\"");
XomwLocalisationCacheForXowa.Init_ip(app.Fsys_mgr().Root_dir());
Io_mgr.Instance.SaveFilStr(app.Fsys_mgr().Root_dir().GenSubFil_nest("languages", "data", "plurals.xml"), xml);
}
public void Init__pluralRulesXml__en() {
this.Init__pluralRulesXml
( "<pluralRules locales='ast ca de en et fi fy gl it ji nl sv sw ur yi'>"
, " <pluralRule count='one'>i = 1 and v = 0 @integer 1</pluralRule>"
, " <pluralRule count='other'> @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, <20> @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, <20></pluralRule>"
, "</pluralRules>"
);
}
public void Test__getPluralRuleIndexNumber(int expd, String... ary) {
for (String itm : ary) {
Gftest.Eq__int(expd, lang.getPluralRuleIndexNumber(itm), itm);
}
}
}

@ -16,6 +16,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
package gplx.xowa.mediawiki.languages; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*;
import org.junit.*; import gplx.core.tests.*;
import gplx.xowa.langs.*;
import gplx.xowa.mediawiki.includes.cache.localisation.*;
public class XomwLanguage_tst {
private final XomwLanguage_fxt fxt = new XomwLanguage_fxt();
@Test public void Commafy_standard() {
@ -112,18 +113,34 @@ public class XomwLanguage_tst {
fxt.Test_commafy("-123456789" , "-12,34,56,789");
fxt.Test_commafy("-1234567890" , "-1,23,45,67,890");
}
}
class XomwLanguage_fxt {
private final XomwLanguage lang;
public XomwLanguage_fxt() {
Xoae_app app = Xoa_app_fxt.Make__app__edit();
Xol_lang_itm xoLang = app.Lang_mgr().Get_by_or_load(Bry_.new_a7("en"));
this.lang = new XomwLanguage(xoLang);
}
public void Init_digitGroupingPattern(String digitGroupingPattern) {
lang.setDigitGroupingPattern(Bry_.new_u8(digitGroupingPattern));
@Test public void handleExplicitPluralForms() {
fxt.Test__handleExplicitPluralForms__string("1", XophpArray.New().Add("1=one"), "one");
fxt.Test__handleExplicitPluralForms__array("1", XophpArray.New().Add("no_match"), XophpArray.New().Add(0, "no_match"));
}
public void Test_commafy(String raw, String expd) {
Gftest.Eq__str(expd, lang.commafy(Bry_.new_u8(raw)));
@Test public void getPluralRuleIndexNumber() {
fxt.Init__pluralRulesXml
( "<pluralRules locales='ast ca de en et fi fy gl it ji nl sv sw ur yi'>"
, " <pluralRule count='one'>i = 1 and v = 0 @integer 1</pluralRule>"
, " <pluralRule count='other'> @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>"
, "</pluralRules>"
, "<pluralRules locales='ru uk'>"
, " <pluralRule count='one'>v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …</pluralRule>"
, " <pluralRule count='few'>v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …</pluralRule>"
, " <pluralRule count='many'>v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …</pluralRule>"
, " <pluralRule count='other'> @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>"
, "</pluralRules>"
);
fxt.Init__lang("qqq");
fxt.Test__getPluralRuleIndexNumber(0, "0", "1", "2", "1.1");
fxt.Init__lang("en");
fxt.Test__getPluralRuleIndexNumber(0, "1");
fxt.Test__getPluralRuleIndexNumber(1, "2", "1.1", "3");
fxt.Init__lang("ru");
fxt.Test__getPluralRuleIndexNumber(0, "1");
fxt.Test__getPluralRuleIndexNumber(1, "2");
fxt.Test__getPluralRuleIndexNumber(2, "0", "5", "12", "19", "100");
fxt.Test__getPluralRuleIndexNumber(3, "0.0", "1.5", "10.0");
}
}

@ -0,0 +1,45 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src.Converter; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.vendor.*; import gplx.xowa.mediawiki.vendor.wikimedia.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src.*;
// MW.SRC:1.33.1
/**
* Helper for Converter.
* An expression Object, representing a region of the input String (for error
* messages), the RPN notation used to evaluate it, and the result type for
* validation.
*/
public class XomwExpression extends XomwFragment { /** @var String */
public String type;
/** @var String */
public String rpn;
public XomwExpression(XomwConverter parser, String type, String rpn, int pos, int length) {super(parser, pos, length);
this.type = type;
this.rpn = rpn;
}
public boolean isType(String type) {
if (String_.Eq(type, "range") && (String_.Eq(this.type, "range") || String_.Eq(this.type, "number"))) {
return true;
}
if (String_.Eq(type, this.type)) {
return true;
}
return false;
}
}

@ -0,0 +1,41 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src.Converter; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.vendor.*; import gplx.xowa.mediawiki.vendor.wikimedia.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src.*;
// MW.SRC:1.33.1
/**
* Helper for Converter.
* The super class for operators and expressions, describing a region of the input String.
*/
public class XomwFragment {
public XomwConverter parser;
public int pos, length, end;
public XomwFragment(XomwConverter parser, int pos, int length) {
this.parser = parser;
this.pos = pos;
this.length = length;
this.end = pos + length;
}
public void error(String message) {
String text = this.getText();
throw XomwError.New__fmt("$message at position " + Int_.To_str(this.pos + 1) + ": \"$text\"", message, text);
}
public String getText() {
return XophpString_.substr(this.parser.rule, this.pos, this.length);
}
}

@ -0,0 +1,118 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src.Converter; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.vendor.*; import gplx.xowa.mediawiki.vendor.wikimedia.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src.*;
// MW.SRC:1.33.1
/**
* Helper for Converter.
* An operator Object, representing a region of the input String (for error
* messages), and the binary operator at that location.
*/
public class XomwOperator extends XomwFragment { /** @var String The name */
public String name;
/**
* Each op type has three characters: left operand type, right operand type and result type
*
* b = boolean
* n = number
* r = range
*
* A number is a kind of range.
*
* @var array
*/
private static XophpArray opTypes = XophpArray.New()
.Add("or", "bbb")
.Add("and", "bbb")
.Add("is", "nnb")
.Add("is-not", "nnb")
.Add("in", "nrb")
.Add("not-in", "nrb")
.Add("within", "nrb")
.Add("not-within", "nrb")
.Add("mod", "nnn")
.Add(",", "rrr")
.Add("..", "nnr")
;
/**
* Map converting from the abbrevation to the full form.
*
* @var array
*/
private static XophpArray typeSpecMap = XophpArray.New()
.Add("b", "boolean")
.Add("n", "number")
.Add("r", "range")
;
/**
* Map for converting the new operators introduced in Rev 33 to the old forms
*/
private static XophpArray aliasMap = XophpArray.New()
.Add("%", "mod")
.Add("!=", "not-in")
.Add("=", "in")
;
/**
* Initialize a new instance of a CLDRPluralRuleConverterOperator Object
*
* @param Converter parser The parser
* @param String name The operator name
* @param int pos The length
* @param int length
*/
public XomwOperator(XomwConverter parser, String name, int pos, int length) {super(parser, pos, length);
if (XomwOperator.aliasMap.isset(name)) {
name = XomwOperator.aliasMap.Get_by_str(name);
}
this.name = name;
}
/**
* Compute the operation
*
* @param Expression left The left part of the expression
* @param Expression right The right part of the expression
* @return Expression The result of the operation
*/
public XomwExpression operate(XomwExpression left, XomwExpression right) {
String typeSpec = XomwOperator.opTypes.Get_by_str(this.name);
String leftType = XomwOperator.typeSpecMap.Get_by_str(String_.CharAt(typeSpec, 0));
String rightType = XomwOperator.typeSpecMap.Get_by_str(String_.CharAt(typeSpec, 1));
String resultType = XomwOperator.typeSpecMap.Get_by_str(String_.CharAt(typeSpec, 2));
int start = XophpMath.min_many(this.pos, left.pos, right.pos);
int end = XophpMath.max_many(this.end, left.end, right.end);
int length = end - start;
XomwExpression newExpr = new XomwExpression(this.parser, resultType,
left.rpn + " " + right.rpn + " " + this.name,
start, length);
if (!left.isType(leftType)) {
newExpr.error("invalid type for left operand: expected leftType, got {left.type}");
}
if (!right.isType(rightType)) {
newExpr.error("invalid type for right operand: expected rightType, got {right.type}");
}
return newExpr;
}
}

@ -0,0 +1,335 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.vendor.*; import gplx.xowa.mediawiki.vendor.wikimedia.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.*;
import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src.Converter.*;
import gplx.langs.regxs.*;
// MW.SRC:1.33.1
/**
* Helper class for converting rules to reverse polish notation (RPN).
*/
public class XomwConverter {
/**
* The input String
*
* @var String
*/
public String rule;
/**
* The current position
*
* @var int
*/
public int pos;
/**
* The past-the-end position
*
* @var int
*/
public int end;
/**
* The operator stack
*
* @var array
*/
public XophpArray operators = XophpArray.New();
/**
* The operand stack
*
* @var array
*/
public XophpArray operands = XophpArray.New();
/**
* Precedence levels. Note that there's no need to worry about associativity
* for the level 4 operators, since they return boolean and don't accept
* boolean inputs.
*/
private static XophpArray precedence = XophpArray.New()
.Add("or", 2)
.Add("and", 3)
.Add("is", 4)
.Add("is-not", 4)
.Add("in", 4)
.Add("not-in", 4)
.Add("within", 4)
.Add("not-within", 4)
.Add("mod", 5)
.Add(",", 6)
.Add("..", 7)
;
/**
* A character list defining whitespace, for use in strspn() etc.
*/
private static final Hash_adp WHITESPACE_CLASS = XophpString_.strspn_hash(" \t\r\n");
/**
* Same for digits. Note that the grammar given in UTS #35 doesn't allow
* negative numbers or decimal separators.
*/
private static final Hash_adp NUMBER_CLASS = XophpString_.strspn_hash("0123456789");
/**
* A character list of symbolic operands.
*/
private static final String OPERAND_SYMBOLS = "nivwft";
/**
* An anchored regular expression which matches a word at the current offset.
*/
private static final Regx_adp WORD_REGEX = Regx_adp_.new_("[a-zA-Z@]+");
/**
* Convert a rule to RPN. This is the only public entry point.
*
* @param String rule The rule to convert
* @return String The RPN representation of the rule
*/
public static String convert(String rule) {
XomwConverter parser = new XomwConverter(rule);
return parser.doConvert();
}
/**
* Private constructor.
* @param String rule
*/
protected XomwConverter(String rule) {
this.rule = rule;
this.pos = 0;
this.end = XophpString_.strlen(rule);
}
/**
* Do the operation.
*
* @return String The RPN representation of the rule (e.g. "5 3 mod n is")
*/
protected String doConvert() {
boolean expectOperator = true;
// Iterate through all tokens, saving the operators and operands to a
// stack per Dijkstra's shunting yard algorithm.
/** @var Operator token */
XomwFragment token;
while (null != (token = this.nextToken())) {
// In this grammar, there are only binary operators, so every valid
// rule String will alternate between operator and operand tokens.
expectOperator = !expectOperator;
if (Type_.Is_assignable_from_by_obj(token, XomwExpression.class)) {
// Operand
if (expectOperator) {
token.error("unexpected operand");
}
this.operands.Add(token);
continue;
} else {
// Operator
if (!expectOperator) {
token.error("unexpected operator");
}
// Resolve higher precedence levels
XomwOperator lastOp = (XomwOperator)this.operators.end();
while (lastOp != null && Int_.Cast(XomwConverter.precedence.Get_by(((XomwOperator)token).name)) <= Int_.Cast(XomwConverter.precedence.Get_by(((XomwOperator)lastOp).name))) {
this.doOperation(lastOp, this.operands);
this.operators.pop();
lastOp = (XomwOperator)this.operators.end();
}
this.operators.Add(token);
}
}
// Finish off the stack
XomwOperator op = null;
while (null != (op = (XomwOperator)this.operators.pop())) {
this.doOperation(op, this.operands);
}
// Make sure the result is sane. The first case is possible for an empty
// String input, the second should be unreachable.
if (!this.operands.Count_bool()) {
this.error("condition expected");
} else if (this.operands.Count() > 1) {
this.error("missing operator or too many operands");
}
XomwExpression value = (XomwExpression)this.operands.Get_at(0);
if (!String_.Eq(value.type, "boolean")) {
this.error("the result must have a boolean type");
}
return ((XomwExpression)this.operands.Get_at(0)).rpn;
}
/**
* Fetch the next token from the input String.
*
* @return Fragment The next token
*/
protected XomwFragment nextToken() {
if (this.pos >= this.end) {
return null;
}
// Whitespace
int length = XophpString_.strspn(this.rule, XomwConverter.WHITESPACE_CLASS, this.pos);
this.pos += length;
if (this.pos >= this.end) {
return null;
}
// Number
length = XophpString_.strspn(this.rule, XomwConverter.NUMBER_CLASS, this.pos);
if (length != 0) {
XomwFragment token = this.newNumber(XophpString_.substr(this.rule, this.pos, length), this.pos);
this.pos += length;
return token;
}
// Two-character operators
String op2 = XophpString_.substr(this.rule, this.pos, 2);
if (String_.Eq(op2, "..") || String_.Eq(op2, "!=")) {
XomwFragment token = this.newOperator(op2, this.pos, 2);
this.pos += 2;
return token;
}
// Single-character operators
String op1 = Char_.To_str(String_.CharAt(this.rule, this.pos));
if (String_.Eq(op1, ",") || String_.Eq(op1, "=") || String_.Eq(op1, "%")) {
XomwFragment token = this.newOperator(op1, this.pos, 1);
this.pos++;
return token;
}
// Word
XophpArray m = XophpArray.New();
if (!XophpRegex_.preg_match_bool(XomwConverter.WORD_REGEX, this.rule, m, 0, this.pos)) {
this.error("unexpected character \"" + String_.CharAt(this.rule, this.pos) + "\"");
}
String word1 = XophpString_.strtolower(m.Get_at_str(0));
String word2 = "";
int nextTokenPos = this.pos + XophpString_.strlen(word1);
if (String_.Eq(word1, "not") || String_.Eq(word1, "is")) {
// Look ahead one word
nextTokenPos += XophpString_.strspn(this.rule, XomwConverter.WHITESPACE_CLASS, nextTokenPos);
m = XophpArray.New();
if (nextTokenPos < this.end
&& XophpRegex_.preg_match_bool(XomwConverter.WORD_REGEX, this.rule, m, 0, nextTokenPos)
) {
word2 = XophpString_.strtolower(m.Get_at_str(0));
nextTokenPos += XophpString_.strlen(word2);
}
}
// Two-word operators like "is not" take precedence over single-word operators like "is"
if (String_.Eq(word2, "")) {
String bothWords = word1 + "-" + word2;
if (XomwConverter.precedence.isset(bothWords)) {
XomwFragment token = this.newOperator(bothWords, this.pos, nextTokenPos - this.pos);
this.pos = nextTokenPos;
return token;
}
}
// Single-word operators
if (XomwConverter.precedence.isset(word1)) {
XomwFragment token = this.newOperator(word1, this.pos, XophpString_.strlen(word1));
this.pos += XophpString_.strlen(word1);
return token;
}
// The single-character operand symbols
if (XophpString_.strpos(XomwConverter.OPERAND_SYMBOLS, word1) != String_.Pos_neg1) {
XomwFragment token = this.newNumber(word1, this.pos);
this.pos++;
return token;
}
// Samples
if (String_.Eq(word1, "@integer") || String_.Eq(word1, "@decimal")) {
// Samples are like comments, they have no effect on rule evaluation.
// They run from the first sample indicator to the end of the String.
this.pos = this.end;
return null;
}
this.error("unrecognised word");
return null;
}
/**
* For the binary operator op, pop its operands off the stack and push
* a fragment with rpn and type members describing the result of that
* operation.
*
* @param Operator op
*/
protected void doOperation(XomwOperator op, Object ignore) { // NOTE: MW passes 2 args, but method only has 1
if (this.operands.Count() < 2) {
op.error("missing operand");
}
XomwExpression right = (XomwExpression)this.operands.pop();
XomwExpression left = (XomwExpression)this.operands.pop();
XomwExpression result = op.operate(left, right);
this.operands.Add(result);
}
/**
* Create a numerical expression Object
*
* @param String text
* @param int pos
* @return Expression The numerical expression
*/
protected XomwExpression newNumber(String text, int pos) {
return new XomwExpression(this, "number", text, pos, XophpString_.strlen(text));
}
/**
* Create a binary operator
*
* @param String type
* @param int pos
* @param int length
* @return Operator The operator
*/
protected XomwOperator newOperator(String type, int pos, int length) {
return new XomwOperator(this, type, pos, length);
}
/**
* Throw an error
* @param String message
*/
private void error(String message) {
throw new XomwError(message);
}
}

@ -0,0 +1,29 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.vendor.*; import gplx.xowa.mediawiki.vendor.wikimedia.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.*;
import gplx.xowa.mediawiki.includes.exception.*;
// MW.SRC:1.33.1
/**
* The exception cl+ass for all the cl+asses in this file. This will be thrown
* back to the caller if there is any validation error.
*/
public class XomwError extends Err { public XomwError(String msg) {super(true, "", "", msg);
}
public static XomwError New(String msg) {return new XomwError("CLDR plural rule error: " + msg);}
public static XomwError New__fmt(String msg, Object... args) {
return new XomwError("CLDR plural rule error: " + XophpString_.Fmt(msg, args));
}
}

@ -0,0 +1,246 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.vendor.*; import gplx.xowa.mediawiki.vendor.wikimedia.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.*;
import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src.Converter.*;
// MW.SRC:1.33.1
public class XomwEvaluator {
/**
* Evaluate a number against a set of plural rules. If a rule passes,
* return the index of plural rule.
*
* @param int number The number to be evaluated against the rules
* @param array rules The associative array of plural rules in pluralform => rule format.
* @return int The index of the plural form which passed the evaluation
*/
public static int evaluate(String number, XophpArray rules) {
rules = XomwEvaluator.compile(rules);
return XomwEvaluator.evaluateCompiled(number, rules);
}
/**
* Convert a set of rules to a compiled form which is optimised for
* fast evaluation. The result will be an array of strings, and may be cached.
*
* @param array rules The rules to compile
* @return array An array of compile rules.
*/
public static XophpArray compile(XophpArray rules) {
XophpArray rv = XophpArray.New();
// We can't use array_map() for this because it generates a warning if
// there is an exception.
int rules_len = rules.Len();
for (int i = 0; i < rules_len; i++) {
String rule = rules.Get_at_str(i);
rule = XomwConverter.convert(rule);
rv.Add(rule);
}
return rv;
}
/**
* Evaluate a compiled set of rules returned by compile(). Do not allow
* the user to edit the compiled form, or else PHP errors may result.
*
* @param String number The number to be evaluated against the rules, in English, or it
* may be a type convertible to String.
* @param array rules The associative array of plural rules in pluralform => rule format.
* @return int The index of the plural form which passed the evaluation
*/
public static int evaluateCompiled(String number_str, XophpArray rules) {
// Calculate the values of the operand symbols
// String number_str = XophpInt_.strval(number);
// NOTE: '/^ -? ( ([0-9]+) (?: \. ([0-9]+) )? )$/x'
XophpArray m = XophpArray.New();
if (!XophpRegex_.preg_match_bool(gplx.langs.regxs.Regx_adp_.new_("^-?(([0-9]+)(?:\\.([0-9]+))?)"), number_str, m, 0, 0)) {
XomwLog_.wfDebug_by_method("evaluateCompiled", ": invalid number input, returning \"other\"\n");
return rules.Count();
}
XophpArray operandSymbols = null;
if (!m.isset(3)) {
operandSymbols = XophpArray.New()
.Add("n", Decimal_adp_.int_(XophpInt_.intval(m.Get_at_str(1))))
.Add("i", Decimal_adp_.int_(XophpInt_.intval(m.Get_at_str(1))))
.Add("v", Decimal_adp_.Zero)
.Add("w", Decimal_adp_.Zero)
.Add("f", Decimal_adp_.Zero)
.Add("t", Decimal_adp_.Zero)
;
} else {
String absValStr = m.Get_at_str(1);
String intStr = m.Get_at_str(2);
String fracStr = m.Get_at_str(3);
operandSymbols = XophpArray.New()
.Add("n", Decimal_adp_.double_(XophpFloat_.floatval(absValStr)))
.Add("i", Decimal_adp_.int_(XophpInt_.intval(intStr)))
.Add("v", Decimal_adp_.int_(XophpString_.strlen(fracStr)))
.Add("w", Decimal_adp_.int_(XophpString_.strlen(XophpString_.rtrim(fracStr, "0"))))
.Add("f", Decimal_adp_.int_(XophpInt_.intval(fracStr)))
.Add("t", Decimal_adp_.int_(XophpInt_.intval(XophpString_.rtrim(fracStr, "0"))))
;
}
// The compiled form is RPN, with tokens strictly delimited by
// spaces, so this is a simple RPN evaluator.
int rules_len = rules.Len();
for (int i = 0; i < rules_len; i++) {
String rule = rules.Get_at_str(i);
XophpArray stack = XophpArray.New();
int zero = XophpString_.ord("0");
int nine = XophpString_.ord("9");
String[] tokens = XophpString_.explode(" ", rule);
for (String token : tokens) {
int ord = XophpString_.ord(token);
if (operandSymbols.isset(token)) {
stack.Add(XomwStackItem.New__number((Decimal_adp)operandSymbols.Get_by(token)));
} else if (ord >= zero && ord <= nine) {
stack.Add(XomwStackItem.New__number(Decimal_adp_.int_(XophpInt_.intval(token))));
} else {
XomwStackItem right = (XomwStackItem)stack.pop();
XomwStackItem left = (XomwStackItem)stack.pop();
XomwStackItem result = XomwEvaluator.doOperation(token, left, right);
stack.Add(result);
}
}
if (((XomwStackItem)stack.Get_at(0)).Tid() == XomwStackItem.Tid__bool && ((XomwStackItem)stack.Get_at(0)).As_bool()) {
return i;
}
}
// None of the provided rules match. The number belongs to category
// "other", which comes last.
return rules.Count();
}
/**
* Do a single operation
*
* @param String token The token String
* @param mixed left The left operand. If it is an Object, its state may be destroyed.
* @param mixed right The right operand
* @throws Error
* @return mixed The operation result
*/
// XO: left / right can be boolean, Decimal, Range
private static final XophpArray doOperationTokens = XophpArray.New().Add_as_key_and_val_many("in", "not-in", "within", "not-within");
private static XomwStackItem doOperation(String token, XomwStackItem left, XomwStackItem right) {
if (doOperationTokens.Has(token)) {
if (right.Tid() != XomwStackItem.Tid__range) {
right = XomwStackItem.New__range(new XomwRange(right.As_num(), null));
}
}
if (String_.Eq(token, "or")) {
return XomwStackItem.New__bool(left.As_bool() || right.As_bool());
}
else if (String_.Eq(token, "and")) {
return XomwStackItem.New__bool(left.As_bool() && right.As_bool());
}
else if (String_.Eq(token, "is")) {
return XomwStackItem.New__bool(left.As_bool() == right.As_bool());
}
else if (String_.Eq(token, "is-not")) {
return XomwStackItem.New__bool(left.As_bool() != right.As_bool());
}
else if (String_.Eq(token, "in")) {
return XomwStackItem.New__bool(right.As_range().isNumberIn(left.As_num()));
}
else if (String_.Eq(token, "not-in")) {
return XomwStackItem.New__bool(!right.As_range().isNumberIn(left.As_num()));
}
else if (String_.Eq(token, "within")) {
return XomwStackItem.New__bool(right.As_range().isNumberWithin(left.As_num()));
}
else if (String_.Eq(token, "not-within")) {
return XomwStackItem.New__bool(!right.As_range().isNumberWithin(left.As_num()));
}
else if (String_.Eq(token, "mod")) {
if (left.Tid() == XomwStackItem.Tid__number) {
return XomwStackItem.New__number(XophpMath.fmod_decimal(left.As_num(), right.As_num()));
}
return XomwStackItem.New__number(XophpMath.fmod_decimal(left.As_num(), right.As_num()));
}
else if (String_.Eq(token, ",")) {
XomwRange range = null;
if (left.Tid() == XomwStackItem.Tid__range) {
range = left.As_range();
} else {
range = new XomwRange(left.As_num(), null);
}
range.add(right.As_obj());
return XomwStackItem.New__range(range);
}
else if (String_.Eq(token, "..")) {
return XomwStackItem.New__range(new XomwRange(left.As_num(), right.As_num()));
}
else {
throw new XomwError("Invalid RPN token");
}
}
}
class XomwStackItem {
XomwStackItem(int tid, boolean val__bool, Decimal_adp val__number, XomwRange val__range) {
this.tid = tid;
this.val__bool = val__bool;
this.val__number = val__number;
this.val__range = val__range;
}
public int Tid() {return tid;} private final int tid;
public boolean As_bool() {
if (tid != Tid__bool) Fail_bc_wrong_type(Tid__bool);
return val__bool;
} private final boolean val__bool;
public Decimal_adp As_num() {
if (tid != Tid__number) Fail_bc_wrong_type(Tid__number);
return val__number;
} private final Decimal_adp val__number;
public XomwRange As_range() {
if (tid != Tid__range) Fail_bc_wrong_type(Tid__range);
return val__range;
} private final XomwRange val__range;
public Object As_obj() {
switch (tid) {
case Tid__bool: return val__bool;
case Tid__number: return val__number;
case Tid__range: return val__range;
default: throw Err_.new_unhandled_default(tid);
}
}
private void Fail_bc_wrong_type(int expd) {
throw new XomwError("wrong type; expd=" + Tid_to_str(expd) + "; actl=" + Tid_to_str(tid));
}
private String Tid_to_str(int v) {
switch (tid) {
case Tid__bool: return "boolean";
case Tid__number: return "number";
case Tid__range: return "range";
default: throw Err_.new_unhandled_default(tid);
}
}
public static XomwStackItem New__bool(boolean v) {return new XomwStackItem(Tid__bool, v, null, null);}
public static XomwStackItem New__number(Decimal_adp v) {return new XomwStackItem(Tid__number, false, v, null);}
public static XomwStackItem New__range(XomwRange v) {return new XomwStackItem(Tid__range, false, null, v);}
public static final int
Tid__bool = 0
, Tid__number = 1
, Tid__range = 2;
}

@ -0,0 +1,178 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.vendor.*; import gplx.xowa.mediawiki.vendor.wikimedia.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.*;
import org.junit.*; import gplx.core.tests.*;
public class XomwEvaluator_tst {
// REF: https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules
private final XomwEvaluator_fxt fxt = new XomwEvaluator_fxt();
@Test public void Rule__n() { // "absolute value of the source number (integer and decimals)."
fxt.Init__rule("n = 1");
fxt.Test__match__y("1", "-1");
fxt.Test__match__y("1.0", "-1.0"); // not sure if this is correct, but "'n' => floatval( $absValStr )", and "echo(floatval("1.00"));" -> "1"
fxt.Test__match__n("2", "1.1");
}
@Test public void Rule__i() { // "integer digits of n."
fxt.Init__rule("i = 1");
fxt.Test__match__y("1");
fxt.Test__match__n("0", "2");
}
@Test public void Rule__v() { // "number of visible fraction digits in n, with trailing zeros."
fxt.Init__rule("v = 1");
fxt.Test__match__y("2.3");
fxt.Test__match__n("2", "2.30");
}
@Test public void Rule__w() { // "number of visible fraction digits in n, without trailing zeros."
fxt.Init__rule("w = 1");
fxt.Test__match__y("2.30", "2.3");
fxt.Test__match__n("2");
}
@Test public void Rule__f() { // "visible fractional digits in n, with trailing zeros."
fxt.Init__rule("f = 1");
fxt.Test__match__y("2.1");
fxt.Test__match__n("2", "2.10");
}
@Test public void Rule__t() { // "visible fractional digits in n, without trailing zeros."
fxt.Init__rule("t = 1");
fxt.Test__match__y("2.1", "2.10");
fxt.Test__match__n("2");
}
@Test public void Rule__sample() { // MW ignores samples
fxt.Init__rule("n = 1 @integer f = 1"); // embed fake rule for "1.1" after @integer
fxt.Test__match__y("1");
}
@Test public void Lang__0() {
// NOTE: never parse "other" rule; "// Don't record "other" rules, which have an empty condition"; REF.MW:/includes/cache/localization/LocalizationCache.php
// fxt.Init__rule("@integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …");
fxt.Init__rule();
fxt.Test__match(0, "1", "1.2", "-1"); // basically, anything
}
@Test public void Lang__1__am() {
fxt.Init__rule("i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04");
fxt.Test__match(0, "0", "0.1", "-0.1", "1", "-1");
fxt.Test__match(1, "2", "1.1", "-1.1");
}
@Test public void Lang__1__ff__fr() {
fxt.Init__rule("i = 0,1 @integer 0, 1 @decimal 0.0~1.5");
fxt.Test__match(0, "0", "0.1", "1", "1.0", "1.9");
fxt.Test__match(1, "2");
}
@Test public void Lang__1__ast__de__fr() {
fxt.Init__rule("i = 1 and v = 0 @integer 1");
fxt.Test__match(0, "1", "-1");
fxt.Test__match(1, "1.1", "-1.1", "2");
}
@Test public void Lang__1__si() {
fxt.Init__rule("n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000");
fxt.Test__match(0, "0", "1", "-1", "0.1", "-0.1");
fxt.Test__match(1, "1.1", "0.11", "2");
}
@Test public void Lang__1__ak__bh() {
fxt.Init__rule("n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000");
fxt.Test__match(0, "0", "1", "-1");
fxt.Test__match(1, "0.123", "-0.123", "1.1", "2");
}
@Test public void Lang__1__tzm() {
fxt.Init__rule("n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0");
fxt.Test__match(0, "0", "1", "11", "21", "99");
fxt.Test__match(1, "0.123", "-0.123", "1.1", "2", "11.1", "99.1");
}
@Test public void Lang__1__pt() {
fxt.Init__rule("n = 0..2 and n != 2 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000");
fxt.Test__match(0, "0", "1", "0.0", "1.0", "-1");
fxt.Test__match(1, "2", "1.1", "-2");
}
@Test public void Lang__1__da() {
fxt.Init__rule("n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6");
fxt.Test__match(0, "0.2", "1", "-1", "1.2");
fxt.Test__match(1, "0", "2");
}
@Test public void Lang__1__is() {
fxt.Init__rule("t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 1000.1, …");
fxt.Test__match(0, "1", "21", "101", "0.1", "1.1", "10.1");
fxt.Test__match(1, "0", "2", "11", "100", "0.0", "10", "10.0");
}
@Test public void Lang__3__he__iw() {
fxt.Init__rule
( "i = 1 and v = 0 @integer 1"
, "i = 2 and v = 0 @integer 2"
, "v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …"
);
fxt.Test__match(0, "1", "-1");
fxt.Test__match(1, "2", "-2");
fxt.Test__match(2, "20", "30", "100", "110", "1000");
fxt.Test__match(3
, "1.2", "-1.2"
, "2.3", "-2.3"
, "3", "9", "-3", "11", "19", "101"
);
}
@Test public void Lang__4__br() {
fxt.Init__rule
( "n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 101.0, 1001.0, …"
, "n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 102.0, 1002.0, …"
, "n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, …"
, "n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1000000.00, 1000000.000, …"
);
fxt.Test__match(0, "1", "21", "1.0", "21.0", "-1");
fxt.Test__match(1, "2", "22", "2.0", "22.0", "-2");
fxt.Test__match(2, "3", "4", "9", "23", "103", "3.0", "103.0", "-3");
fxt.Test__match(3, "1000000", "1000000.0");
fxt.Test__match(4
, "1.1", "11"
, "2.1"
, "3.1", "5", "6", "7", "8", "10", "19", "70", "79"
, "1000000.1" // NOTE: fails in C#
, "60"
);
}
@Test public void Lang__5__ar() {
fxt.Init__rule
( "n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000"
, "n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000"
, "n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000"
, "n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …"
, "n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …"
);
fxt.Test__match(0, "0", "0.00");
fxt.Test__match(1, "1", "-1", "1.0");
fxt.Test__match(2, "2", "-2", "2.0");
fxt.Test__match(3, "3", "4", "10", "103", "1003", "-3", "3.0");
fxt.Test__match(4, "11", "99", "111", "1011", "-11", "11.0");
fxt.Test__match(5
, "0.1", "-0.1"
, "1.1", "-1.1"
, "2.1", "-2.1"
, "3.1", "10.1"
, "100", "102", "200", "1000"
);
}
}
class XomwEvaluator_fxt {
private final XophpArray rules = XophpArray.New();
public void Init__rule(String... ary) {
rules.Clear();
for (String itm : ary)
rules.Add(itm);
}
public void Test__match__y(String... ary) {Test__match(0, ary);}
public void Test__match__n(String... ary) {Test__match(1, ary);}
public void Test__match(int expd, String... ary) {
for (String itm : ary) {
int actl = XomwEvaluator.evaluate(itm, rules);
Gftest.Eq__int(expd, actl, itm);
}
}
}

@ -0,0 +1,125 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.src; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.vendor.*; import gplx.xowa.mediawiki.vendor.wikimedia.*; import gplx.xowa.mediawiki.vendor.wikimedia.cldr_plural_rule_parser.*;
// MW.SRC:1.33.1
/**
* Evaluator helper class representing a range list.
*/
class XomwRange {
/**
* The parts
*
* @var array
*/
public XophpArray parts = XophpArray.New();
/**
* Initialize a new instance of Range
*
* @param int start The start of the range
* @param int|boolean end The end of the range, or false if the range is not bounded.
*/
public XomwRange(Decimal_adp start, Decimal_adp end) {
if (end == null) {
this.parts.Add(start);
} else {
this.parts.Add(XophpArray.New().Add(start).Add(end));
}
}
/**
* Determine if the given number is inside the range.
*
* @param int number The number to check
* @param boolean integerConstraint If true, also asserts the number is an integer;
* otherwise, number simply has to be inside the range.
* @return boolean True if the number is inside the range; otherwise, false.
*/
public boolean isNumberIn(Decimal_adp number) {return isNumberIn(number, true);}
public boolean isNumberIn(Decimal_adp number, boolean integerConstraint) {
int parts_len = parts.Len();
for (int i = 0; i < parts_len; i++) {
Object part_obj = this.parts.Get_at(i);
if (XophpArray.is_array(part_obj)) {
XophpArray part = (XophpArray)part_obj;
if ((!integerConstraint || number.Floor().Eq(number))
&& number.Comp_gte((Decimal_adp)part.Get_at(0)) && number.Comp_lte((Decimal_adp)part.Get_at(1))
) {
return true;
}
} else {
Decimal_adp part_decimal = (Decimal_adp)part_obj;
if (part_decimal == null) part_decimal = number; // if "new XomwRange(start, null)", then range is just "start, start"
if (number.Eq(part_decimal)) {
return true;
}
}
}
return false;
}
/**
* Readable alias for isNumberIn(number, false), and the implementation
* of the "within" operator.
*
* @param int number The number to check
* @return boolean True if the number is inside the range; otherwise, false.
*/
public boolean isNumberWithin(Decimal_adp number) {
return this.isNumberIn(number, false);
}
/**
* Add another part to this range.
*
* @param Range|int other The part to add, either
* a range Object itself or a single number.
*/
public void add(Object otherObj) {
if (Type_.Eq_by_obj(otherObj, XomwRange.class)) {
this.parts = XophpArrayUtl.array_merge(this.parts, ((XomwRange)otherObj).parts);
} else {
this.parts.Add(otherObj);
}
}
/**
* Returns the String representation of the rule evaluator range.
* The purpose of this method is to help debugging.
*
* @return String The String representation of the rule evaluator range
*/
@Override public String toString() {
String s = "Range(";
int parts_len = this.parts.Len();
for (int i = 0; i < parts_len; i++) {
Object part_obj = this.parts.Get_at(i);
if (i > 0) {
s += ", ";
}
if (XophpArray.is_array(part_obj)) {
XophpArray part = (XophpArray)part_obj;
s += Int_.To_str(part.Get_at_int(0)) + ".." + Int_.To_str(part.Get_at_int(1));
} else {
s += Int_.To_str(Int_.Cast(part_obj));
}
}
s += ")";
return s;
}
}

@ -0,0 +1,27 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.xml; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*;
import gplx.langs.xmls.*;
public class XophpDOMDocument {
private XmlDoc xdoc;
public void loadXML(String xml) {
this.xdoc = XmlDoc_.parse(xml);
}
public XophpDOMNodeList getElementsByTagName(String tagName) {
XmlNdeList list = XmlDoc_.Select_tags(xdoc.Root(), tagName);
return new XophpDOMNodeList(list);
}
}

@ -0,0 +1,32 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.xml; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*;
import gplx.langs.xmls.*;
public class XophpDOMNode {
private final XmlNde xnde;
public String nodeValue = null;
public XophpDOMNode(XmlNde xnde) {
this.xnde = xnde;
// TODO.PHP:implement edge cases for nodeValue; https://stackoverflow.com/questions/12380919/php-dom-textcontent-vs-nodevalue
this.nodeValue = xnde.Text_inner();
}
public String getAttribute(String attribName) {
return xnde.Atrs().Get_by(attribName).Value();
}
public XophpDOMNodeList getElementsByTagName(String tagName) {
return new XophpDOMNodeList(XmlDoc_.Select_tags(xnde, tagName));
}
}

@ -0,0 +1,29 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.mediawiki.xml; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*;
import gplx.langs.xmls.*;
public class XophpDOMNodeList {
private final List_adp list = List_adp_.New();
public XophpDOMNodeList(XmlNdeList nde_list) {
int len = nde_list.Count();
for (int i = 0; i < len; i++) {
XmlNde nde = nde_list.Get_at(i);
list.Add(new XophpDOMNode(nde));
}
}
public int count() {return list.Count();}
public XophpDOMNode item(int i) {return (XophpDOMNode)list.Get_at(i);}
}

@ -20,54 +20,3 @@ public interface Xot_fmtr {
void Reg_tmpl(Xop_ctx ctx, byte[] src, Xop_tkn_itm name_tkn, int args_len, Arg_nde_tkn[] args);
void Reg_arg(Xop_ctx ctx, byte[] src, int arg_idx, Arg_nde_tkn self_tkn);
}
class Xot_fmtr_prm implements Xot_fmtr {
public Xot_fmtr_prm Caller_(Xot_invk v) {this.caller = v; return this;} private Xot_invk caller;
public Xot_fmtr_prm NewLineArgs_(boolean v) {this.newLineArgs = v; return this;} private boolean newLineArgs = false;
public void Reg_ary(Xop_ctx ctx, byte[] src, boolean tmpl_static, int src_bgn, int src_end, int subs_len, Xop_tkn_itm[] subs) {
if (tmpl_static && src_bgn != -1) trg.Add_mid(src, src_bgn, src_end); // HACK: fails for {{IPA-de|l|lang|De-Ludwig_van_Beethoven.ogg}}
for (int i = 0; i < subs_len; i++)
subs[i].Tmpl_fmt(ctx, src, this);
}
public void Reg_prm(Xop_ctx ctx, byte[] src, Xot_prm_tkn self, int prm_idx, byte[] prm_key, Xop_tkn_itm dflt_tkn) {
if (caller == null) { // raw mode
trg.Add(Bry_bgn);
if (prm_idx == -1) {if (prm_key != null) trg.Add(prm_key);}
else trg.Add_int_variable(prm_idx);
if (dflt_tkn != null) {
trg.Add_byte(Byte_ascii.Pipe);
dflt_tkn.Tmpl_fmt(ctx, src, this);
}
trg.Add(Bry_end);
}
else // invk mode
self.Tmpl_evaluate(ctx, src, caller, trg);
} private static final byte[] Bry_bgn = new byte[] {Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn}, Bry_end = new byte[] {Byte_ascii.Curly_end, Byte_ascii.Curly_end, Byte_ascii.Curly_end};
public void Reg_tmpl(Xop_ctx ctx, byte[] src, Xop_tkn_itm name_tkn, int args_len, Arg_nde_tkn[] args) {
trg.Add(Xop_curly_bgn_lxr.Hook);
++depth;
name_tkn.Tmpl_fmt(ctx, src, this);
for (int i = 0; i < args_len; i++) {
if (depth == 1 && newLineArgs) trg.Add_byte_nl();
trg.Add_byte(Byte_ascii.Pipe);
args[i].Tmpl_fmt(ctx, src, this);
}
--depth;
trg.Add(Xop_curly_end_lxr.Hook);
}
public void Write(byte b) {trg.Add_byte(b);}
public void Reg_arg(Xop_ctx ctx, byte[] src, int arg_idx, Arg_nde_tkn self_tkn) {
self_tkn.Key_tkn().Tmpl_fmt(ctx, src, this);
if (self_tkn.KeyTkn_exists()) {
if (arg_idx == 0) {
if (self_tkn.Eq_tkn().Tkn_tid() == Xop_tkn_itm_.Tid_colon)
trg.Add_byte(Byte_ascii.Colon);
}
else
trg.Add_byte(Byte_ascii.Eq);
}
self_tkn.Val_tkn().Tmpl_fmt(ctx, src, this);
}
public void Print(Bry_bfr bb) {bb.Add_bfr_and_preserve(trg); trg.Clear(); depth = 0;}
Bry_bfr trg = Bry_bfr_.New(); int depth = 0;
public static final Xot_fmtr_prm Instance = new Xot_fmtr_prm();
}

@ -0,0 +1,67 @@
/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012-2017 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.xowa.parsers.tmpls; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
public class Xot_fmtr_prm implements Xot_fmtr {
public Xot_fmtr_prm Caller_(Xot_invk v) {this.caller = v; return this;} private Xot_invk caller;
public Xot_fmtr_prm NewLineArgs_(boolean v) {this.newLineArgs = v; return this;} private boolean newLineArgs = false;
public void Reg_ary(Xop_ctx ctx, byte[] src, boolean tmpl_static, int src_bgn, int src_end, int subs_len, Xop_tkn_itm[] subs) {
if (tmpl_static && src_bgn != -1) trg.Add_mid(src, src_bgn, src_end); // HACK: fails for {{IPA-de|l|lang|De-Ludwig_van_Beethoven.ogg}}
for (int i = 0; i < subs_len; i++)
subs[i].Tmpl_fmt(ctx, src, this);
}
public void Reg_prm(Xop_ctx ctx, byte[] src, Xot_prm_tkn self, int prm_idx, byte[] prm_key, Xop_tkn_itm dflt_tkn) {
if (caller == null) { // raw mode
trg.Add(Bry_bgn);
if (prm_idx == -1) {if (prm_key != null) trg.Add(prm_key);}
else trg.Add_int_variable(prm_idx);
if (dflt_tkn != null) {
trg.Add_byte(Byte_ascii.Pipe);
dflt_tkn.Tmpl_fmt(ctx, src, this);
}
trg.Add(Bry_end);
}
else // invk mode
self.Tmpl_evaluate(ctx, src, caller, trg);
} private static final byte[] Bry_bgn = new byte[] {Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn, Byte_ascii.Curly_bgn}, Bry_end = new byte[] {Byte_ascii.Curly_end, Byte_ascii.Curly_end, Byte_ascii.Curly_end};
public void Reg_tmpl(Xop_ctx ctx, byte[] src, Xop_tkn_itm name_tkn, int args_len, Arg_nde_tkn[] args) {
trg.Add(Xop_curly_bgn_lxr.Hook);
++depth;
name_tkn.Tmpl_fmt(ctx, src, this);
for (int i = 0; i < args_len; i++) {
if (depth == 1 && newLineArgs) trg.Add_byte_nl();
trg.Add_byte(Byte_ascii.Pipe);
args[i].Tmpl_fmt(ctx, src, this);
}
--depth;
trg.Add(Xop_curly_end_lxr.Hook);
}
public void Write(byte b) {trg.Add_byte(b);}
public void Reg_arg(Xop_ctx ctx, byte[] src, int arg_idx, Arg_nde_tkn self_tkn) {
self_tkn.Key_tkn().Tmpl_fmt(ctx, src, this);
if (self_tkn.KeyTkn_exists()) {
if (arg_idx == 0) {
if (self_tkn.Eq_tkn().Tkn_tid() == Xop_tkn_itm_.Tid_colon)
trg.Add_byte(Byte_ascii.Colon);
}
else
trg.Add_byte(Byte_ascii.Eq);
}
self_tkn.Val_tkn().Tmpl_fmt(ctx, src, this);
}
public void Print(Bry_bfr bb) {bb.Add_bfr_and_preserve(trg); trg.Clear(); depth = 0;}
Bry_bfr trg = Bry_bfr_.New(); int depth = 0;
public static final Xot_fmtr_prm Instance = new Xot_fmtr_prm();
}

@ -19,6 +19,7 @@ import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*; import gplx.xowa.langs.
import gplx.xowa.xtns.pfuncs.ifs.*; import gplx.xowa.xtns.pfuncs.times.*; import gplx.xowa.xtns.pfuncs.numbers.*; import gplx.xowa.xtns.pfuncs.ttls.*; import gplx.xowa.xtns.pfuncs.langs.*; import gplx.xowa.xtns.pfuncs.strings.*; import gplx.xowa.xtns.pfuncs.tags.*; import gplx.xowa.xtns.pfuncs.stringutils.*; import gplx.xowa.xtns.pfuncs.pages.*; import gplx.xowa.xtns.pfuncs.wikis.*;
import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*;
import gplx.xowa.wikis.domains.*;
import gplx.xowa.mediawiki.*;
public class Pf_func_ {
public static final byte Name_dlm = Byte_ascii.Colon;
public static boolean Eval_arg_to_kvp(byte[][] rslt, Xop_ctx ctx, byte[] src, Xot_invk caller, Xot_invk self, int self_args_len, Bry_bfr tmp_bfr, int i) {
@ -106,6 +107,42 @@ public class Pf_func_ {
}
return Ary_nonwmf;
}
// convert XO template to MW array; EX: {{name:argx|key1=val1|key2=val2}} comes in as a Xot_invk obj; convert it to an array of [(key1, val1), (key2, val2)]
public static XophpArray Convert_xo_tmpl_to_mw_ary(Xop_ctx ctx, Xot_invk caller, Xot_invk tmpl, byte[] src) {
// init
XophpArray rv = XophpArray.New();
Bry_bfr rv_bfr = Bry_bfr_.New();
Bry_bfr tmp_bfr = Bry_bfr_.New();
int args_len = tmpl.Args_len();
// loop
for (int i = 0; i < args_len; i++) {
// clear bfrs
rv_bfr.Clear();
tmp_bfr.Clear();
// get arg
Arg_nde_tkn arg = tmpl.Args_get_by_idx(i);
// eval key; NOTE: could be recursive
arg.Key_tkn().Tmpl_evaluate(ctx, src, caller, tmp_bfr);
rv_bfr.Add_bfr_and_clear(tmp_bfr);
// add eq
if (rv_bfr.Len_gt_0())
rv_bfr.Add_byte_eq();
// eval val; NOTE: could be recursive
arg.Val_tkn().Tmpl_evaluate(ctx, src, caller, tmp_bfr);
rv_bfr.Add_bfr_and_clear(tmp_bfr);
// add to rv
rv.Add(rv_bfr.To_str_and_clear());
}
return rv;
}
private static int[] Ary_nonwmf = null;
private static final int[] Ary_wmf = new int[]
{ Xol_kwd_grp_.Id_utc_year

@ -16,16 +16,31 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
package gplx.xowa.xtns.pfuncs.langs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.pfuncs.*;
import gplx.xowa.langs.*; import gplx.xowa.langs.kwds.*;
import gplx.xowa.parsers.*; import gplx.xowa.parsers.tmpls.*;
import gplx.xowa.mediawiki.*;
public class Pfunc_plural extends Pf_func_base {
@Override public boolean Func_require_colon_arg() {return true;}
@Override public void Func_evaluate(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src) {// REF.MW: CoreParserFunctions.php
byte[] number = Eval_argx(ctx, src, caller, self);
int self_args_len = self.Args_len();
int arg_idx = Pf_func_.Eq(ctx, number, Ary_Num_1) ? 0 : 1;
if (arg_idx == 1 && self_args_len == 1) arg_idx = 0; // number is plural, but plural_arg not present; use singular; see test
byte[] word = Pf_func_.Eval_arg_or_empty(ctx, src, caller, self, self_args_len, arg_idx);
bfr.Add(word);
} private static final byte[] Ary_Num_1 = new byte[] {Byte_ascii.Num_1};
@Override public int Id() {return Xol_kwd_grp_.Id_i18n_plural;}
@Override public Pf_func New(int id, byte[] name) {return new Pfunc_plural().Name_(name);}
}
@Override public boolean Func_require_colon_arg() {return true;}
// EX: {{plural:1|one|many}}
@Override public void Func_evaluate(Bry_bfr bfr, Xop_ctx ctx, Xot_invk caller, Xot_invk self, byte[] src) {// REF.MW: CoreParserFunctions.php
// convert xo_tmpl to mw_ary
XophpArray forms = Pf_func_.Convert_xo_tmpl_to_mw_ary(ctx, caller, self, src);
// get number from argx (EX: 1)
String number_str = String_.new_u8(Eval_argx(ctx, src, caller, self));
// if number matches explicit key, use it; EX: {{plural:3|2=two|3=three|one|many}} -> three
Object result = ctx.Lang().Mw_lang().handleExplicitPluralForms(number_str, forms);
if (Type_.Eq_by_obj(result, String.class)) {
bfr.Add_str_u8((String)result);
return;
}
// no match for explicit key; take results (which has removed all explicit keys) and get plural rule index; EX: {{plural:1|2=two|3=three|one|many}} -> {{plural:?|one|many}}
XophpArray resultArray = (XophpArray)result;
int idx = ctx.Lang().Mw_lang().getPluralRuleIndexNumber(number_str);
if (idx >= resultArray.Count()) // bound-check; EX: {{plural:2|wiki}} -> idx = 1 -> idx = 0
idx = resultArray.Count() - 1;
bfr.Add_str_u8(resultArray.Get_at_str(idx));
}
}

@ -15,11 +15,22 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.xtns.pfuncs.langs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.pfuncs.*;
import org.junit.*;
import gplx.xowa.mediawiki.languages.*;
public class Pfunc_plural_tst {
private final Xop_fxt fxt = new Xop_fxt();
@Before public void init() {fxt.Reset();}
@Test public void Singular() {fxt.Test_parse_tmpl_str_test("{{plural:1|wiki|wikis}}" , "{{test}}" , "wiki");}
@Test public void Plural() {fxt.Test_parse_tmpl_str_test("{{plural:2|wiki|wikis}}" , "{{test}}" , "wikis");}
@Test public void Plural_but_one_arg() {fxt.Test_parse_tmpl_str_test("{{plural:2|wiki}}" , "{{test}}" , "wiki");}
@Test public void Null() {fxt.Test_parse_tmpl_str_test("{{plural:|wiki|wikis}}" , "{{test}}" , "wikis");}
private final Xop_fxt fxt = new Xop_fxt();
@Before public void init() {
fxt.Reset();
XomwLanguage_fxt lang_fxt = new XomwLanguage_fxt();
lang_fxt.Init__pluralRulesXml__en();
}
@Test public void Singular() {fxt.Test__parse__tmpl_to_html("{{plural:1|wiki|wikis}}" , "wiki");}
@Test public void Plural() {fxt.Test__parse__tmpl_to_html("{{plural:2|wiki|wikis}}" , "wikis");}
@Test public void Plural_but_one_arg() {fxt.Test__parse__tmpl_to_html("{{plural:2|wiki}}" , "wiki");}
@Test public void Null() {fxt.Test__parse__tmpl_to_html("{{plural:|wiki|wikis}}" , "wikis");}
@Test public void handleExplicitPluralForms() {
fxt.Test__parse__tmpl_to_html("{{plural:1|2=two|3=three|one|many}}", "one");
fxt.Test__parse__tmpl_to_html("{{plural:2|2=two|3=three|one|many}}", "two");
fxt.Test__parse__tmpl_to_html("{{plural:3|2=two|3=three|one|many}}", "three");
fxt.Test__parse__tmpl_to_html("{{plural:9|2=two|3=three|one|many}}", "many");
}
}

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<supplementalData>
<plurals>
<!-- Belarusian in Taraškievica orthography (Беларуская тарашкевіца). Copied from "be" -->
<pluralRules locales="be-tarask">
<pluralRule count="one">n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …</pluralRule>
<pluralRule count="few">n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0, 32.0, 33.0, 102.0, 1002.0, …</pluralRule>
<pluralRule count="many">n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
<pluralRule count="other"> @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …</pluralRule>
</pluralRules>
<!-- Old Church Slavonic (Ѩзыкъ словѣньскъ). Not present in CLDR -->
<pluralRules locales="cu">
<pluralRule count="one">n % 10 = 1 @integer 1, 11, 21, 31, …</pluralRule>
<pluralRule count="two">n % 10 = 2 @integer 2, 12, 22, 32, …</pluralRule>
<pluralRule count="few">n % 10 = 3..4 @integer 3~4, 13~14, 23~24, …</pluralRule>
<pluralRule count="other"> @integer 5, 6, 7, 8, 9, 10, 15, 105, 206, 307, …</pluralRule>
</pluralRules>
<!-- Copied from "bh" -->
<pluralRules locales="bho">
<pluralRule count="one">n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000</pluralRule>
<pluralRule count="other"> @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<!-- Samogitian. Not present in CLDR -->
<pluralRules locales="sgs">
<pluralRule count="one">n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 91, 101, 121, …</pluralRule>
<pluralRule count="two">n % 10 = 2 and n % 100 != 12 @integer 2, 22, 32, 42, 52, 62, 72, 82, 92, 102, 122, …</pluralRule>
<pluralRule count="few">n = 0 or n % 100 = 0 or n % 100 = 10..19 @integer 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 100, 111,112, …</pluralRule>
<pluralRule count="other"> @integer 3, 4, 5, 6, 7, 8, 9, 20, 103, 104, …</pluralRule>
</pluralRules>
<pluralRules locales="sr-el sr-ec">
<pluralRule count="one">v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …</pluralRule>
<pluralRule count="few">v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …</pluralRule>
<pluralRule count="other"> @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
</plurals>
</supplementalData>

@ -0,0 +1,231 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
<!--
Copyright © 1991-2013 Unicode, Inc.
CLDR data files are interpreted according to the LDML specification (https://unicode.org/reports/tr35/)
For terms of use, see https://www.unicode.org/copyright.html
-->
<supplementalData>
<version number="$Revision: 10807 $"/>
<generation date="$Date: 2014-08-14 14:43:27 -0500 (Thu, 14 Aug 2014) $"/>
<plurals type="cardinal">
<!-- For a canonicalized list, use GeneratedPluralSamples -->
<!-- 1: other -->
<pluralRules locales="bm bo dz id ig ii in ja jbo jv jw kde kea km ko lkt lo ms my nqo root sah ses sg th to vi wo yo zh">
<pluralRule count="other"> @integer 0~15, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<!-- 2: one,other -->
<pluralRules locales="am bn fa gu hi kn mr zu">
<pluralRule count="one">i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04</pluralRule>
<pluralRule count="other"> @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~2.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="ff fr hy kab">
<pluralRule count="one">i = 0,1 @integer 0, 1 @decimal 0.0~1.5</pluralRule>
<pluralRule count="other"> @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="ast ca de en et fi fy gl it ji nl sv sw ur yi">
<pluralRule count="one">i = 1 and v = 0 @integer 1</pluralRule>
<pluralRule count="other"> @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="si">
<pluralRule count="one">n = 0,1 or i = 0 and f = 1 @integer 0, 1 @decimal 0.0, 0.1, 1.0, 0.00, 0.01, 1.00, 0.000, 0.001, 1.000, 0.0000, 0.0001, 1.0000</pluralRule>
<pluralRule count="other"> @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.2~0.9, 1.1~1.8, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="ak bh guw ln mg nso pa ti wa">
<pluralRule count="one">n = 0..1 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000</pluralRule>
<pluralRule count="other"> @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="tzm">
<pluralRule count="one">n = 0..1 or n = 11..99 @integer 0, 1, 11~24 @decimal 0.0, 1.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0</pluralRule>
<pluralRule count="other"> @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="pt">
<pluralRule count="one">n = 0..2 and n != 2 @integer 0, 1 @decimal 0.0, 1.0, 0.00, 1.00, 0.000, 1.000, 0.0000, 1.0000</pluralRule>
<pluralRule count="other"> @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="af asa az bem bez bg brx cgg chr ckb dv ee el eo es eu fo fur gsw ha haw hu jgo jmc ka kaj kcg kk kkj kl ks ksb ku ky lb lg mas mgo ml mn nah nb nd ne nn nnh no nr ny nyn om or os pap ps rm rof rwk saq seh sn so sq ss ssy st syr ta te teo tig tk tn tr ts ug uz ve vo vun wae xh xog">
<pluralRule count="one">n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000</pluralRule>
<pluralRule count="other"> @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="pt_PT">
<pluralRule count="one">n = 1 and v = 0 @integer 1</pluralRule>
<pluralRule count="other"> @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="da">
<pluralRule count="one">n = 1 or t != 0 and i = 0,1 @integer 1 @decimal 0.1~1.6</pluralRule>
<pluralRule count="other"> @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0~3.4, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="is">
<pluralRule count="one">t = 0 and i % 10 = 1 and i % 100 != 11 or t != 0 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1~1.6, 10.1, 100.1, 1000.1, …</pluralRule>
<pluralRule count="other"> @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="mk">
<pluralRule count="one">v = 0 and i % 10 = 1 or f % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …</pluralRule>
<pluralRule count="other"> @integer 0, 2~10, 12~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.2~1.0, 1.2~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="fil tl">
<pluralRule count="one">v = 0 and i = 1,2,3 or v = 0 and i % 10 != 4,6,9 or v != 0 and f % 10 != 4,6,9 @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.3, 0.5, 0.7, 0.8, 1.0~1.3, 1.5, 1.7, 1.8, 2.0, 2.1, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
<pluralRule count="other"> @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, … @decimal 0.4, 0.6, 0.9, 1.4, 1.6, 1.9, 2.4, 2.6, 10.4, 100.4, 1000.4, …</pluralRule>
</pluralRules>
<!-- 3: zero,one,other -->
<pluralRules locales="lv prg">
<pluralRule count="zero">n % 10 = 0 or n % 100 = 11..19 or v = 2 and f % 100 = 11..19 @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
<pluralRule count="one">n % 10 = 1 and n % 100 != 11 or v = 2 and f % 10 = 1 and f % 100 != 11 or v != 2 and f % 10 = 1 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.0, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …</pluralRule>
<pluralRule count="other"> @integer 2~9, 22~29, 102, 1002, … @decimal 0.2~0.9, 1.2~1.9, 10.2, 100.2, 1000.2, …</pluralRule>
</pluralRules>
<pluralRules locales="lag">
<pluralRule count="zero">n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000</pluralRule>
<pluralRule count="one">i = 0,1 and n != 0 @integer 1 @decimal 0.1~1.6</pluralRule>
<pluralRule count="other"> @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="ksh">
<pluralRule count="zero">n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000</pluralRule>
<pluralRule count="one">n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000</pluralRule>
<pluralRule count="other"> @integer 2~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<!-- 3: one,two,other -->
<pluralRules locales="iu kw naq se sma smi smj smn sms">
<pluralRule count="one">n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000</pluralRule>
<pluralRule count="two">n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000</pluralRule>
<pluralRule count="other"> @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<!-- 3: one,few,other -->
<pluralRules locales="shi">
<pluralRule count="one">i = 0 or n = 1 @integer 0, 1 @decimal 0.0~1.0, 0.00~0.04</pluralRule>
<pluralRule count="few">n = 2..10 @integer 2~10 @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00</pluralRule>
<pluralRule count="other"> @integer 11~26, 100, 1000, 10000, 100000, 1000000, … @decimal 1.1~1.9, 2.1~2.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="mo ro">
<pluralRule count="one">i = 1 and v = 0 @integer 1</pluralRule>
<pluralRule count="few">v != 0 or n = 0 or n != 1 and n % 100 = 1..19 @integer 0, 2~16, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
<pluralRule count="other"> @integer 20~35, 100, 1000, 10000, 100000, 1000000, …</pluralRule>
</pluralRules>
<pluralRules locales="bs hr sh sr">
<pluralRule count="one">v = 0 and i % 10 = 1 and i % 100 != 11 or f % 10 = 1 and f % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …</pluralRule>
<pluralRule count="few">v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 0.2~0.4, 1.2~1.4, 2.2~2.4, 3.2~3.4, 4.2~4.4, 5.2, 10.2, 100.2, 1000.2, …</pluralRule>
<pluralRule count="other"> @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<!-- 4: one,two,few,other -->
<pluralRules locales="gd">
<pluralRule count="one">n = 1,11 @integer 1, 11 @decimal 1.0, 11.0, 1.00, 11.00, 1.000, 11.000, 1.0000</pluralRule>
<pluralRule count="two">n = 2,12 @integer 2, 12 @decimal 2.0, 12.0, 2.00, 12.00, 2.000, 12.000, 2.0000</pluralRule>
<pluralRule count="few">n = 3..10,13..19 @integer 3~10, 13~19 @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 3.00</pluralRule>
<pluralRule count="other"> @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="sl">
<pluralRule count="one">v = 0 and i % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, …</pluralRule>
<pluralRule count="two">v = 0 and i % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, …</pluralRule>
<pluralRule count="few">v = 0 and i % 100 = 3..4 or v != 0 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
<pluralRule count="other"> @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …</pluralRule>
</pluralRules>
<pluralRules locales="dsb hsb">
<pluralRule count="one">v = 0 and i % 100 = 1 or f % 100 = 1 @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, … @decimal 0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 10.1, 100.1, 1000.1, …</pluralRule>
<pluralRule count="two">v = 0 and i % 100 = 2 or f % 100 = 2 @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, … @decimal 0.2, 1.2, 2.2, 3.2, 4.2, 5.2, 6.2, 7.2, 10.2, 100.2, 1000.2, …</pluralRule>
<pluralRule count="few">v = 0 and i % 100 = 3..4 or f % 100 = 3..4 @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, … @decimal 0.3, 0.4, 1.3, 1.4, 2.3, 2.4, 3.3, 3.4, 4.3, 4.4, 5.3, 5.4, 6.3, 6.4, 7.3, 7.4, 10.3, 100.3, 1000.3, …</pluralRule>
<pluralRule count="other"> @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 0.5~1.0, 1.5~2.0, 2.5~2.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<!-- 4: one,two,many,other -->
<pluralRules locales="he iw">
<pluralRule count="one">i = 1 and v = 0 @integer 1</pluralRule>
<pluralRule count="two">i = 2 and v = 0 @integer 2</pluralRule>
<pluralRule count="many">v = 0 and n != 0..10 and n % 10 = 0 @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …</pluralRule>
<pluralRule count="other"> @integer 0, 3~17, 101, 1001, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<!-- 4: one,few,many,other -->
<pluralRules locales="cs sk">
<pluralRule count="one">i = 1 and v = 0 @integer 1</pluralRule>
<pluralRule count="few">i = 2..4 and v = 0 @integer 2~4</pluralRule>
<pluralRule count="many">v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
<pluralRule count="other"> @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …</pluralRule>
</pluralRules>
<pluralRules locales="pl">
<pluralRule count="one">i = 1 and v = 0 @integer 1</pluralRule>
<pluralRule count="few">v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …</pluralRule>
<pluralRule count="many">v = 0 and i != 1 and i % 10 = 0..1 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 12..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …</pluralRule>
<pluralRule count="other"> @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="be">
<pluralRule count="one">n % 10 = 1 and n % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …</pluralRule>
<pluralRule count="few">n % 10 = 2..4 and n % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, … @decimal 2.0, 3.0, 4.0, 22.0, 23.0, 24.0, 32.0, 33.0, 102.0, 1002.0, …</pluralRule>
<pluralRule count="many">n % 10 = 0 or n % 10 = 5..9 or n % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
<pluralRule count="other"> @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …</pluralRule>
</pluralRules>
<pluralRules locales="lt">
<pluralRule count="one">n % 10 = 1 and n % 100 != 11..19 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 71.0, 81.0, 101.0, 1001.0, …</pluralRule>
<pluralRule count="few">n % 10 = 2..9 and n % 100 != 11..19 @integer 2~9, 22~29, 102, 1002, … @decimal 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 22.0, 102.0, 1002.0, …</pluralRule>
<pluralRule count="many">f != 0 @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.1, 1000.1, …</pluralRule>
<pluralRule count="other"> @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="mt">
<pluralRule count="one">n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000</pluralRule>
<pluralRule count="few">n = 0 or n % 100 = 2..10 @integer 0, 2~10, 102~107, 1002, … @decimal 0.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, 102.0, 1002.0, …</pluralRule>
<pluralRule count="many">n % 100 = 11..19 @integer 11~19, 111~117, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …</pluralRule>
<pluralRule count="other"> @integer 20~35, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="ru uk">
<pluralRule count="one">v = 0 and i % 10 = 1 and i % 100 != 11 @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …</pluralRule>
<pluralRule count="few">v = 0 and i % 10 = 2..4 and i % 100 != 12..14 @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …</pluralRule>
<pluralRule count="many">v = 0 and i % 10 = 0 or v = 0 and i % 10 = 5..9 or v = 0 and i % 100 = 11..14 @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …</pluralRule>
<pluralRule count="other"> @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<!-- 5: one,two,few,many,other -->
<pluralRules locales="br">
<pluralRule count="one">n % 10 = 1 and n % 100 != 11,71,91 @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, … @decimal 1.0, 21.0, 31.0, 41.0, 51.0, 61.0, 81.0, 101.0, 1001.0, …</pluralRule>
<pluralRule count="two">n % 10 = 2 and n % 100 != 12,72,92 @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, … @decimal 2.0, 22.0, 32.0, 42.0, 52.0, 62.0, 82.0, 102.0, 1002.0, …</pluralRule>
<pluralRule count="few">n % 10 = 3..4,9 and n % 100 != 10..19,70..79,90..99 @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, … @decimal 3.0, 4.0, 9.0, 23.0, 24.0, 29.0, 33.0, 34.0, 103.0, 1003.0, …</pluralRule>
<pluralRule count="many">n != 0 and n % 1000000 = 0 @integer 1000000, … @decimal 1000000.0, 1000000.00, 1000000.000, …</pluralRule>
<pluralRule count="other"> @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, … @decimal 0.0~0.9, 1.1~1.6, 10.0, 100.0, 1000.0, 10000.0, 100000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="ga">
<pluralRule count="one">n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000</pluralRule>
<pluralRule count="two">n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000</pluralRule>
<pluralRule count="few">n = 3..6 @integer 3~6 @decimal 3.0, 4.0, 5.0, 6.0, 3.00, 4.00, 5.00, 6.00, 3.000, 4.000, 5.000, 6.000, 3.0000, 4.0000, 5.0000, 6.0000</pluralRule>
<pluralRule count="many">n = 7..10 @integer 7~10 @decimal 7.0, 8.0, 9.0, 10.0, 7.00, 8.00, 9.00, 10.00, 7.000, 8.000, 9.000, 10.000, 7.0000, 8.0000, 9.0000, 10.0000</pluralRule>
<pluralRule count="other"> @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~0.9, 1.1~1.6, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="gv">
<pluralRule count="one">v = 0 and i % 10 = 1 @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, …</pluralRule>
<pluralRule count="two">v = 0 and i % 10 = 2 @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, …</pluralRule>
<pluralRule count="few">v = 0 and i % 100 = 0,20,40,60,80 @integer 0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000, …</pluralRule>
<pluralRule count="many">v != 0 @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
<pluralRule count="other"> @integer 3~10, 13~19, 23, 103, 1003, …</pluralRule>
</pluralRules>
<!-- 6: zero,one,two,few,many,other -->
<pluralRules locales="ar">
<pluralRule count="zero">n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000</pluralRule>
<pluralRule count="one">n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000</pluralRule>
<pluralRule count="two">n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000</pluralRule>
<pluralRule count="few">n % 100 = 3..10 @integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …</pluralRule>
<pluralRule count="many">n % 100 = 11..99 @integer 11~26, 111, 1011, … @decimal 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 111.0, 1011.0, …</pluralRule>
<pluralRule count="other"> @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.1, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
<pluralRules locales="cy">
<pluralRule count="zero">n = 0 @integer 0 @decimal 0.0, 0.00, 0.000, 0.0000</pluralRule>
<pluralRule count="one">n = 1 @integer 1 @decimal 1.0, 1.00, 1.000, 1.0000</pluralRule>
<pluralRule count="two">n = 2 @integer 2 @decimal 2.0, 2.00, 2.000, 2.0000</pluralRule>
<pluralRule count="few">n = 3 @integer 3 @decimal 3.0, 3.00, 3.000, 3.0000</pluralRule>
<pluralRule count="many">n = 6 @integer 6 @decimal 6.0, 6.00, 6.000, 6.0000</pluralRule>
<pluralRule count="other"> @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, … @decimal 0.1~0.9, 1.1~1.7, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …</pluralRule>
</pluralRules>
</plurals>
</supplementalData>
Loading…
Cancel
Save