diff --git a/100_core/src/gplx/Bry_find_.java b/100_core/src/gplx/Bry_find_.java
index 702fe13b2..896bea26e 100644
--- a/100_core/src/gplx/Bry_find_.java
+++ b/100_core/src/gplx/Bry_find_.java
@@ -40,6 +40,10 @@ public class Bry_find_ {
if (src[i] == lkp) return i;
return Bry_find_.Not_found;
}
+ public static int Find_bwd_or(byte[] src, byte lkp, int cur, int end, int or) {
+ int rv = Find_bwd(src, lkp, cur, end);
+ return rv == Bry_find_.Not_found ? or : rv;
+ }
public static int Move_fwd(byte[] src, byte lkp, int cur, int end) {
int rv = Find_fwd(src, lkp, cur, src.length);
return rv == Bry_find_.Not_found ? rv : rv + 1;
diff --git a/100_core/src/gplx/Object_.java b/100_core/src/gplx/Object_.java
index 00e8017bf..517278e37 100644
--- a/100_core/src/gplx/Object_.java
+++ b/100_core/src/gplx/Object_.java
@@ -30,6 +30,15 @@ public class Object_ {
rv[i] = rhs[i - lhs_len];
return rv;
}
+ public static Object[] Ary_add_one(Object[] lhs, Object rhs) {
+ int lhs_len = lhs.length, rhs_len = 1;
+ int rv_len = lhs_len + rhs_len;
+ Object[] rv = new Object[rv_len];
+ for (int i = 0; i < lhs_len; ++i)
+ rv[i] = lhs[i];
+ rv[rv_len - 1] = rhs;
+ return rv;
+ }
public static boolean Eq(Object lhs, Object rhs) {
if (lhs == null && rhs == null) return true;
else if (lhs == null || rhs == null) return false;
diff --git a/100_core/src/gplx/Type_ids_.java b/100_core/src/gplx/Type_ids_.java
index 183c0e677..452f539aa 100644
--- a/100_core/src/gplx/Type_ids_.java
+++ b/100_core/src/gplx/Type_ids_.java
@@ -30,6 +30,7 @@ public class Type_ids_ {//RF:2017-10-08
, Id__bry = 11
, Id__date = 12
, Id__decimal = 13
+ , Id__array = 14
;
public static int To_id_by_obj(Object o) {
diff --git a/400_xowa/src/gplx/xowa/Xoa_app_.java b/400_xowa/src/gplx/xowa/Xoa_app_.java
index b3835dc42..2b78c184d 100644
--- a/400_xowa/src/gplx/xowa/Xoa_app_.java
+++ b/400_xowa/src/gplx/xowa/Xoa_app_.java
@@ -30,8 +30,8 @@ public class Xoa_app_ {
}
}
public static final String Name = "xowa";
- public static final int Version_id = 540;
- public static final String Version = "4.5.18.1711";
+ public static final int Version_id = 542;
+ public static final String Version = "4.5.20.1801";
public static String Build_date = "2012-12-30 00:00:00";
public static String Build_date_fmt = "yyyy-MM-dd HH:mm:ss";
public static String Op_sys_str;
diff --git a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr.java b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr.java
index 010fb2522..5a63f767d 100644
--- a/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr.java
+++ b/400_xowa/src/gplx/xowa/htmls/core/htmls/Xoh_html_wtr.java
@@ -18,7 +18,7 @@ import gplx.core.btries.*;
import gplx.langs.htmls.*; import gplx.xowa.langs.kwds.*; import gplx.langs.htmls.entitys.*;
import gplx.xowa.htmls.core.wkrs.hdrs.*; import gplx.xowa.htmls.core.wkrs.lnkes.*;
import gplx.xowa.wikis.domains.*;
-import gplx.xowa.parsers.*; import gplx.xowa.parsers.apos.*; import gplx.xowa.parsers.amps.*; import gplx.xowa.parsers.lnkes.*; import gplx.xowa.parsers.lists.*; import gplx.xowa.htmls.core.wkrs.lnkis.htmls.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.paras.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.miscs.*; import gplx.xowa.parsers.htmls.*;
+import gplx.xowa.parsers.*; import gplx.xowa.parsers.apos.*; import gplx.xowa.parsers.amps.*; import gplx.xowa.parsers.lnkes.*; import gplx.xowa.parsers.lists.*; import gplx.xowa.htmls.core.wkrs.lnkis.htmls.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.paras.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.miscs.*; import gplx.xowa.parsers.htmls.*; import gplx.xowa.parsers.uniqs.*;
import gplx.xowa.xtns.*; import gplx.xowa.xtns.cites.*; import gplx.xowa.parsers.hdrs.*;
public class Xoh_html_wtr {
private final Xoae_app app; private final Xowe_wiki wiki; private final Xow_html_mgr html_mgr; private final Xop_xatr_whitelist_mgr whitelist_mgr;
@@ -90,6 +90,7 @@ public class Xoh_html_wtr {
case Xop_tkn_itm_.Tid_tblw_tc: Tblw (bfr, ctx, hctx, src, (Xop_tblw_tkn)tkn, Gfh_tag_.Caption_lhs_bgn , Gfh_tag_.Caption_rhs, false); break;
case Xop_tkn_itm_.Tid_newLine: New_line (bfr, ctx, hctx, src, (Xop_nl_tkn)tkn); break;
case Xop_tkn_itm_.Tid_bry: Bry (bfr, ctx, hctx, src, (Xop_bry_tkn)tkn); break;
+ case Xop_tkn_itm_.Tid_uniq: Uniq (bfr, ctx, hctx, src, (Xop_uniq_tkn)tkn); break;
case Xop_tkn_itm_.Tid_lnki: lnki_wtr.Write_lnki(bfr, hctx, src, (Xop_lnki_tkn)tkn); break;
case Xop_tkn_itm_.Tid_lnke: wkr__lnke.Write_html(bfr, html_mgr, this, hctx, ctx, src, (Xop_lnke_tkn)tkn); break;
case Xop_tkn_itm_.Tid_hdr: wkr__hdr.Write_html(bfr, this, wiki, page, ctx, hctx, cfg, grp, sub_idx, src, (Xop_hdr_tkn)tkn); break;
@@ -232,6 +233,10 @@ public class Xoh_html_wtr {
private void Bry(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_bry_tkn bry) {
bfr.Add(bry.Val());
}
+ private void Uniq(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_uniq_tkn tkn) {
+ byte[] val = wiki.Parser_mgr().Uniq_mgr().Get(tkn.Key());
+ Xoh_html_wtr_escaper.Escape(app.Parser_amp_mgr(), bfr, val, 0, val.length, true, false);
+ }
private void Under(Bry_bfr bfr, Xop_ctx ctx, Xoh_wtr_ctx hctx, byte[] src, Xop_under_tkn under) {
if (hctx.Mode_is_alt()) return;
switch (under.Under_tid()) {
@@ -436,7 +441,7 @@ public class Xoh_html_wtr {
for (int i = 0; i < ary_len; i++) {
Mwh_atr_itm atr = ary[i];
if (atr.Invalid()) continue;
- if (!whitelist_mgr.Chk(tag_id, src, atr)) continue;
+ if (!whitelist_mgr.Chk(tag_id, atr)) continue;
Xnde_atr_write(bfr, app, hctx, src, atr);
}
}
@@ -463,7 +468,7 @@ public class Xoh_html_wtr {
}
else {
if (atr.Val_bry() == null)
- bfr.Add_mid(src, atr.Val_bgn(), atr.Val_end());
+ bfr.Add_mid(atr.Src(), atr.Val_bgn(), atr.Val_end());
else
bfr.Add(atr.Val_bry());
}
diff --git a/400_xowa/src/gplx/xowa/htmls/core/wkrs/hdrs/Xoh_hdr_html.java b/400_xowa/src/gplx/xowa/htmls/core/wkrs/hdrs/Xoh_hdr_html.java
index 4d543aa65..7dd86bea4 100644
--- a/400_xowa/src/gplx/xowa/htmls/core/wkrs/hdrs/Xoh_hdr_html.java
+++ b/400_xowa/src/gplx/xowa/htmls/core/wkrs/hdrs/Xoh_hdr_html.java
@@ -29,7 +29,7 @@ public class Xoh_hdr_html {
// register hdr with TOC
byte[] hdr_text_bry = Bld_hdr_html(hdr_text_bfr, wtr, page, ctx, hctx, src, hdr);
- hdr_text_bry = wiki.Parser_mgr().Uniq_mgr().Convert(hdr_text_bry); // need for math; DATE:2016-12-09
+ hdr_text_bry = wiki.Parser_mgr().Uniq_mgr().Parse(hdr_text_bry); // need for math; DATE:2016-12-09
Xoh_toc_itm toc_itm = hdr_is_valid && hdr_text_bry.length > 0
? page.Html_data().Toc_mgr().Add(hdr_num, hdr_text_bry)
: invalid_toc_itm;
diff --git a/400_xowa/src/gplx/xowa/langs/Xol_lang_itm.java b/400_xowa/src/gplx/xowa/langs/Xol_lang_itm.java
index 63f7db49d..f841fe551 100644
--- a/400_xowa/src/gplx/xowa/langs/Xol_lang_itm.java
+++ b/400_xowa/src/gplx/xowa/langs/Xol_lang_itm.java
@@ -59,9 +59,13 @@ public class Xol_lang_itm implements Gfo_invk {
public Xol_lang_itm Fallback_bry_(byte[] v) {
fallback_bry = v;
fallback_bry_ary = Fallbacy_bry_ary__bld(v);
+ for (byte[] key : fallback_bry_ary) {
+ fallback_hash.Add_as_key_and_val(String_.new_u8(key));
+ }
return this;
} private byte[] fallback_bry;
public byte[][] Fallback_bry_ary() {return fallback_bry_ary;} private byte[][] fallback_bry_ary = Bry_.Ary_empty;
+ public Ordered_hash Fallback_hash() {return fallback_hash;} private final Ordered_hash fallback_hash = Ordered_hash_.New();
public boolean Dir_ltr() {return dir_ltr;} private boolean dir_ltr = true;
public void Dir_ltr_(boolean v) {
dir_ltr = v;
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpStringTest.java b/400_xowa/src/gplx/xowa/mediawiki/XophpStringTest.java
similarity index 100%
rename from gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpStringTest.java
rename to 400_xowa/src/gplx/xowa/mediawiki/XophpStringTest.java
diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java b/400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java
index 00441dcf9..c345926e1 100644
--- a/400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java
+++ b/400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java
@@ -20,6 +20,6 @@ public class Xop_lxr_ {
, Tid_list = 10, Tid_hdr = 11, Tid_hr = 12, Tid_xnde = 13, Tid_lnke_bgn = 14, Tid_lnke_end = 15, Tid_tblw = 16, Tid_pre = 17, Tid_under = 18, Tid_comment = 19
, Tid_eq = 20, Tid_curly_bgn = 21, Tid_curly_end = 22, Tid_brack_bgn = 23, Tid_brack_end = 24, Tid_poem = 25
, Tid_tvar = 26, Tid_vnt_bgn = 27, Tid_vnt_end = 28, Tid_vnt_eqgt = 29, Tid_vnt_tmpl_bgn = 30, Tid_word = 31, Tid_nl_poem = 32, Tid_cr = 33
- , Tid_brack_end_lnki = 34, Tid_nl_tab = 35, Tid_escape = 36
+ , Tid_brack_end_lnki = 34, Tid_nl_tab = 35, Tid_escape = 36, Tid_uniq = 37
;
}
diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_lxr_mgr.java b/400_xowa/src/gplx/xowa/parsers/Xop_lxr_mgr.java
index daf9b7d8a..5d4ef19c4 100644
--- a/400_xowa/src/gplx/xowa/parsers/Xop_lxr_mgr.java
+++ b/400_xowa/src/gplx/xowa/parsers/Xop_lxr_mgr.java
@@ -16,7 +16,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
package gplx.xowa.parsers; import gplx.*; import gplx.xowa.*;
import gplx.core.btries.*;
import gplx.xowa.langs.*;
-import gplx.xowa.parsers.apos.*; import gplx.xowa.parsers.amps.*; import gplx.xowa.parsers.lnkes.*; import gplx.xowa.parsers.hdrs.*; import gplx.xowa.parsers.lists.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.paras.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.tmpls.*; import gplx.xowa.parsers.miscs.*;
+import gplx.xowa.parsers.apos.*; import gplx.xowa.parsers.amps.*; import gplx.xowa.parsers.lnkes.*; import gplx.xowa.parsers.hdrs.*; import gplx.xowa.parsers.lists.*; import gplx.xowa.parsers.tblws.*; import gplx.xowa.parsers.paras.*; import gplx.xowa.parsers.xndes.*; import gplx.xowa.parsers.lnkis.*; import gplx.xowa.parsers.tmpls.*; import gplx.xowa.parsers.miscs.*; import gplx.xowa.parsers.uniqs.*;
public class Xop_lxr_mgr {
private final Xop_lxr[] ary;
private final List_adp page_lxr_list = List_adp_.New();
@@ -77,6 +77,7 @@ public class Xop_lxr_mgr {
, Xop_pre_lxr.Instance, Xop_nl_tab_lxr.Instance
, Xop_comm_lxr.Instance
, Xop_under_lxr.Instance
+ // , Xop_uniq_lxr.Instance // NOWIKI;DATE:2018-01-16
});
}
public static Xop_lxr_mgr new_anchor_encoder() {
diff --git a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_.java b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_.java
index 2a5cc21c8..ac1c970d7 100644
--- a/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_.java
+++ b/400_xowa/src/gplx/xowa/parsers/Xop_tkn_itm_.java
@@ -15,7 +15,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.parsers; import gplx.*; import gplx.xowa.*;
public class Xop_tkn_itm_ {
- public static final Xop_tkn_itm[] Ary_empty = new Xop_tkn_itm[0];
+ public static final Xop_tkn_itm[] Ary_empty = new Xop_tkn_itm[0];
public static final byte
Tid_null = 0
, Tid_root = 1
@@ -69,8 +69,9 @@ public class Xop_tkn_itm_ {
, Tid_vnt_eqgt = 49
, Tid_cr = 50
, Tid_escape = 51
+, Tid_uniq = 52
;
-public static final String[] Tid__names
+public static final String[] Tid__names
= new String[]
{ "null"
, "root"
@@ -124,5 +125,6 @@ public static final String[] Tid__names
, "vnt_eqgt"
, "cr"
, "escape"
+, "uniq"
};
}
diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list.java b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list.java
index ee0fe6f11..e66352846 100644
--- a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list.java
+++ b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_list.java
@@ -104,7 +104,7 @@ class Xop_section_list implements Xomw_heading_cbk {
key = wiki.Parser_mgr().Main().Parse_text_to_html(wiki.Parser_mgr().Ctx(), key);
// handle math; EX: "== =="
- key = wiki.Parser_mgr().Uniq_mgr().Convert(key);
+ key = wiki.Parser_mgr().Uniq_mgr().Parse(key);
// convert key to toc_text to handle (a) XML ("a" -> "a"); (b) dupes ("text" -> "text_2")
int num = wkr.Hdr_num();
diff --git a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_mgr.java b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_mgr.java
index 427cbe0ca..0b4771d76 100644
--- a/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_mgr.java
+++ b/400_xowa/src/gplx/xowa/parsers/hdrs/sections/Xop_section_mgr.java
@@ -71,7 +71,7 @@ public class Xop_section_mgr implements Gfo_invk {
this.fmt__edit_hint.Fmt_(String_.new_u8(wiki.Msg_mgr().Val_by_key_obj("editsectionhint")));
}
- section_key = wiki.Parser_mgr().Uniq_mgr().Convert(section_key); // need to swap out uniqs for Math; DATE:2016-12-09
+ section_key = wiki.Parser_mgr().Uniq_mgr().Parse(section_key); // need to swap out uniqs for Math; DATE:2016-12-09
byte[] edit_hint = fmt__edit_hint.Bld_many_to_bry(tmp_bfr, section_hint);
fmt__section_editable.Bld_many(bfr, page_ttl, section_key, edit_hint, bry__edit_text);
}
diff --git a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_atr_itm.java b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_atr_itm.java
index 35da23d18..a7a60baaf 100644
--- a/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_atr_itm.java
+++ b/400_xowa/src/gplx/xowa/parsers/htmls/Mwh_atr_itm.java
@@ -28,22 +28,22 @@ public class Mwh_atr_itm {
this.val_bgn = val_bgn; this.val_end = val_end; this.val_bry = val_bry;
this.eql_pos = eql_pos; this.qte_tid = qte_tid;
}
- public byte[] Src() {return src;} private final byte[] src;
- public boolean Valid() {return valid;} private final boolean valid;
- public boolean Key_exists() {return key_exists;} private final boolean key_exists;
- public boolean Repeated() {return repeated;} private final boolean repeated;
+ public byte[] Src() {return src;} private final byte[] src;
+ public boolean Valid() {return valid;} private final boolean valid;
+ public boolean Key_exists() {return key_exists;} private final boolean key_exists;
+ public boolean Repeated() {return repeated;} private final boolean repeated;
public boolean Invalid() {return repeated || !valid;}
public int Atr_bgn() {return atr_bgn;} private int atr_bgn;
public int Atr_end() {return atr_end;} private int atr_end;
- public int Key_bgn() {return key_bgn;} private final int key_bgn;
- public int Key_end() {return key_end;} private final int key_end;
+ public int Key_bgn() {return key_bgn;} private final int key_bgn;
+ public int Key_end() {return key_end;} private final int key_end;
public byte[] Key_bry() {return key_bry;} private byte[] key_bry;
public byte Key_tid() {return key_tid;} public Mwh_atr_itm Key_tid_(byte v) {key_tid = v; return this;} private byte key_tid;
- public int Val_bgn() {return val_bgn;} private final int val_bgn;
- public int Val_end() {return val_end;} private final int val_end;
+ public int Val_bgn() {return val_bgn;} private final int val_bgn;
+ public int Val_end() {return val_end;} private final int val_end;
public byte[] Val_bry() {return val_bry;} private byte[] val_bry;
- public int Eql_pos() {return eql_pos;} private final int eql_pos;
- public int Qte_tid() {return qte_tid;} private final int qte_tid;
+ public int Eql_pos() {return eql_pos;} private final int eql_pos;
+ public int Qte_tid() {return qte_tid;} private final int qte_tid;
public byte Qte_byte() {
switch (qte_tid) {
case Mwh_atr_itm_.Qte_tid__none: return Byte_ascii.Null;
@@ -53,7 +53,6 @@ public class Mwh_atr_itm {
}
}
public Mwh_atr_itm Atr_rng(int bgn, int end) {this.atr_bgn = bgn; this.atr_end = end; return this;}
- public void Key_bry_(byte[] v) {this.key_bry = v;}
public void Val_bry_(byte[] v) {this.val_bry = v;}
public String Val_as_str() {return String_.new_u8(Val_as_bry());}
public byte[] Val_as_bry() {if (val_bry == null) val_bry = Bry_.Mid(src, val_bgn, val_end); return val_bry;} // NOTE: val_bry is cached
diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr.java
index a9811a09e..ccab69cdc 100644
--- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr.java
+++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr.java
@@ -493,6 +493,9 @@ public class Xop_tblw_wkr implements Xop_ctx_wkr {
atrs_bgn = Bry_find_.Find_fwd_while(src, atrs_bgn, src.length, Byte_ascii.Dash);
prv_tblw.Atrs_rng_set(atrs_bgn, atrs_end);
if (ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt && atrs_bgn != -1) {
+ // NOWIKI;DATE:2018-01-16
+ // byte[] converted = ctx.Wiki().Parser_mgr().Uniq_mgr().Parse(Bool_.N, Bry_.Mid(src, atrs_bgn, atrs_end));
+ // Mwh_atr_itm[] atrs = ctx.App().Parser_mgr().Xnde__parse_atrs(converted, 0, converted.length);
Mwh_atr_itm[] atrs = ctx.App().Parser_mgr().Xnde__parse_atrs_for_tblw(src, atrs_bgn, atrs_end);
prv_tblw.Atrs_ary_as_tblw_(atrs);
}
diff --git a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__uncommon_tst.java b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__uncommon_tst.java
index 1aaeb9dcf..46186e8fa 100644
--- a/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__uncommon_tst.java
+++ b/400_xowa/src/gplx/xowa/parsers/tblws/Xop_tblw_wkr__uncommon_tst.java
@@ -16,7 +16,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
package gplx.xowa.parsers.tblws; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
import org.junit.*;
public class Xop_tblw_wkr__uncommon_tst {
- @Before public void init() {fxt.Reset(); fxt.Init_para_y_();} private final Xop_fxt fxt = new Xop_fxt();
+ @Before public void init() {fxt.Reset(); fxt.Init_para_y_();} private final Xop_fxt fxt = new Xop_fxt();
@After public void term() {fxt.Init_para_n_();}
@Test public void Tr_pops_entire_stack() { // PURPOSE: in strange cases, tr will pop entire stack; PAGE:en.w:Turks_in_Denmark; DATE:2014-03-02
fxt.Test_parse_page_all_str(String_.Concat_lines_nl_skip_last
diff --git a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_tmpl_wtr.java b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_tmpl_wtr.java
index 78ad2fc6d..e9a9c90a4 100644
--- a/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_tmpl_wtr.java
+++ b/400_xowa/src/gplx/xowa/parsers/tmpls/Xot_tmpl_wtr.java
@@ -62,8 +62,12 @@ public class Xot_tmpl_wtr {
if (xnde.Tag_close_bgn() == Int_.Min_value)
rslt_bfr.Add_mid(src, tkn.Src_bgn(), tkn.Src_end()); // write src from bgn/end
else { // NOTE: if nowiki then "deactivate" all xndes by swapping out < for < nowiki_xnde_frag; DATE:2013-01-27
- Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_k004();
+ // NOWIKI;DATE:2018-01-16
+ // byte[] uniq = ctx.Wiki().Parser_mgr().Uniq_mgr().Add(Bool_.N, Bry_.Empty, Bry_.Mid(src, xnde.Tag_open_end(), xnde.Tag_close_bgn()));
+ // rslt_bfr.Add(uniq);
+
int nowiki_content_bgn = xnde.Tag_open_end(), nowiki_content_end = xnde.Tag_close_bgn();
+ Bry_bfr tmp_bfr = ctx.Wiki().Utl__bfr_mkr().Get_k004();
boolean escaped = gplx.xowa.parsers.tmpls.Nowiki_escape_itm.Escape(tmp_bfr, src, nowiki_content_bgn, nowiki_content_end);
rslt_bfr.Add_bfr_or_mid(escaped, tmp_bfr, src, nowiki_content_bgn, nowiki_content_end);
tmp_bfr.Mkr_rls();
diff --git a/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_itm.java b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_itm.java
new file mode 100644
index 000000000..f39d637ff
--- /dev/null
+++ b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_itm.java
@@ -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.parsers.uniqs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
+class Xop_uniq_itm {
+ public Xop_uniq_itm(boolean expand_after_template_parsing, byte[] type, int idx, byte[] key, byte[] val) {
+ this.expand_after_template_parsing = expand_after_template_parsing;
+ this.type = type;
+ this.idx = idx;
+ this.key = key;
+ this.val = val;
+ }
+ public boolean Expand_after_template_parsing() {return expand_after_template_parsing;} private final boolean expand_after_template_parsing;
+ public byte[] Type() {return type;} private final byte[] type;
+ public int Idx() {return idx;} private final int idx;
+ public byte[] Key() {return key;} private final byte[] key;
+ public byte[] Val() {return val;} private final byte[] val;
+}
diff --git a/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_lxr.java b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_lxr.java
new file mode 100644
index 000000000..1889eacf0
--- /dev/null
+++ b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_lxr.java
@@ -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.parsers.uniqs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
+import gplx.core.btries.*; import gplx.xowa.langs.*;
+// EX: "\u007fUNIQ-item-1-QINU\u007f"
+public class Xop_uniq_lxr implements Xop_lxr {
+ public int Lxr_tid() {return Xop_lxr_.Tid_uniq;}
+ public void Init_by_wiki(Xowe_wiki wiki, Btrie_fast_mgr core_trie) {
+ core_trie.Add(Xop_uniq_mgr.Bry__uniq__bgn_w_dash, this);
+ }
+ public void Init_by_lang(Xol_lang_itm lang, Btrie_fast_mgr core_trie) {}
+ public void Term(Btrie_fast_mgr core_trie) {}
+ public int Make_tkn(Xop_ctx ctx, Xop_tkn_mkr tkn_mkr, Xop_root_tkn root, byte[] src, int src_len, int bgn_pos, int cur_pos) {
+ // find end
+ int rhs_bgn = Bry_find_.Find_fwd(src, Xop_uniq_mgr.Bry__uniq__add__end, cur_pos);
+ if (rhs_bgn == Bry_find_.Not_found) {
+ Gfo_usr_dlg_.Instance.Warn_many("", "", "uniq_mgr:unable to find uniq; src=~{0}", src);
+ return ctx.Lxr_make_txt_(cur_pos);
+ }
+ int rhs_end = rhs_bgn + Xop_uniq_mgr.Bry__uniq__add__end.length;
+
+ byte[] key = Bry_.Mid(src, bgn_pos, rhs_end);
+ Xop_uniq_tkn uniq_tkn = new Xop_uniq_tkn(bgn_pos, rhs_end, key);
+ ctx.Subs_add(root, uniq_tkn);
+ return rhs_end;
+ }
+ public static final Xop_uniq_lxr Instance = new Xop_uniq_lxr(); Xop_uniq_lxr() {}
+}
diff --git a/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr.java b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr.java
index 5effe2d1f..c8016f1ad 100644
--- a/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr.java
+++ b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr.java
@@ -17,88 +17,73 @@ package gplx.xowa.parsers.uniqs; import gplx.*; import gplx.xowa.*; import gplx.
import gplx.core.btries.*;
public class Xop_uniq_mgr { // REF.MW:/parser/StripState.php
private final Btrie_slim_mgr general_trie = Btrie_slim_mgr.cs(); private final Btrie_rv trv = new Btrie_rv();
- private final Bry_bfr key_bfr = Bry_bfr_.New_w_size(32);
- private int idx = -1;
- public void Clear() {idx = -1; general_trie.Clear();}
- public byte[] Get(byte[] key) {return (byte[])general_trie.Match_exact(key, 0, key.length);}
- public byte[] Add(byte[] type, byte[] val) {// "" -> "\u007fUNIQ-item-1--QINU\u007f"
- byte[] key = key_bfr
- .Add(Bry__uniq__bgn_w_dash)
- .Add(type).Add_byte(Byte_ascii.Dash) // EX: "ref-"
- .Add_int_variable(++idx)
- .Add(Bry__uniq__add__end).To_bry_and_clear();
- general_trie.Add_bry_bry(key, val);
- return key;
+ private final Bry_bfr tmp_bfr = Bry_bfr_.New_w_size(32);
+ private int nxt_idx = -1;
+ public void Clear() {
+ nxt_idx = -1;
+ general_trie.Clear();
}
- public byte[] Convert(byte[] src) {
- if (general_trie.Count() == 0) return src;
-
- Bry_bfr dirty_bfr = null;
- int cur = 0;
- int len = src.length;
- while (cur < len) {
- // find "\u007fUNIQ-"
- int uniq_bgn = Bry_find_.Find_fwd(src, Bry__uniq__bgn_w_dash, cur);
- if (uniq_bgn == Bry_find_.Not_found) break;
-
- // find "-"; EX: ref-
- int tmp_pos = uniq_bgn;
- tmp_pos = Bry_find_.Find_fwd(src, Byte_ascii.Dash, tmp_pos, len);
- if (tmp_pos == Bry_find_.Not_found) {
- Gfo_usr_dlg_.Instance.Warn_many("", "", "uniq_mgr:unable to find 2nd dash; src=~{0}", src);
- return src;
- }
-
- // find end
- int uniq_end = Bry_find_.Find_fwd(src, Bry__uniq__add__end, tmp_pos);
- if (uniq_end == Bry_find_.Not_found) {
- Gfo_usr_dlg_.Instance.Warn_many("", "", "uniq_mgr:unable to convert uniq; src=~{0}", src);
- return src;
- }
- uniq_end += Bry__uniq__add__end.length;
-
- // add to bfr
- if (dirty_bfr == null) dirty_bfr = key_bfr;
- dirty_bfr.Add_mid(src, cur, uniq_bgn);
- dirty_bfr.Add((byte[])general_trie.Match_exact(src, uniq_bgn, uniq_end));
- cur = uniq_end;
- }
-
- if (dirty_bfr != null) {
- dirty_bfr.Add_mid(src, cur, len);
- }
- return dirty_bfr == null ? src : dirty_bfr.To_bry_and_clear();
+ public byte[] Get(byte[] key) {
+ Xop_uniq_itm itm = (Xop_uniq_itm)general_trie.Match_exact(key, 0, key.length);
+ return itm.Val();
+ }
+ public byte[] Add(boolean expand_after_template_parsing, byte[] type, byte[] val) {// "" -> "\u007fUNIQ-item-1-QINU\u007f"
+ int idx = ++nxt_idx;
+ byte[] key = tmp_bfr
+ .Add(Bry__uniq__bgn_w_dash) // "\u007f'\"`UNIQ-"
+ .Add(type).Add_byte(Byte_ascii.Dash) // "ref-"
+ .Add_int_variable(idx) // "1"
+ .Add(Bry__uniq__add__end) // "-QINU`\"'\u007f"
+ .To_bry_and_clear();
+ Xop_uniq_itm itm = new Xop_uniq_itm(expand_after_template_parsing, type, idx, key, val);
+ general_trie.Add_obj(key, itm);
+ return key;
}
public void Parse(Bry_bfr bfr) {
if (general_trie.Count() == 0) return;
- byte[] rv = Parse(key_bfr, general_trie, bfr.To_bry_and_clear());
+ byte[] rv = Parse_recurse(Bool_.Y, tmp_bfr, bfr.To_bry_and_clear());
bfr.Add(rv);
}
- public byte[] Parse(byte[] src) {return Parse(key_bfr, general_trie, src);}
- private byte[] Parse(Bry_bfr bfr, Btrie_slim_mgr trie, byte[] src) {
+ public byte[] Parse(boolean template_parsing, byte[] src) {return Parse_recurse(template_parsing, tmp_bfr, src);}
+ public byte[] Parse(byte[] src) {return Parse_recurse(Bool_.Y, tmp_bfr, src);}
+ private byte[] Parse_recurse(boolean template_parsing, Bry_bfr bfr, byte[] src) {
int src_len = src.length;
int pos = 0;
- int mark_bgn = 0;
+ int prv_bgn = 0;
boolean dirty = false;
+
while (true) {
boolean is_last = pos == src_len;
byte b = is_last ? Byte_ascii.Null : src[pos];
- Object o = trie.Match_at_w_b0(trv, b, src, pos, src_len);
+ Object o = general_trie.Match_at_w_b0(trv, b, src, pos, src_len);
+
+ // match not found for "\x7fUNIQ"; move on to next
if (o == null)
- ++pos;
+ pos++;
+ // match found
else {
- byte[] val = (byte[])o;
- int new_pos = trv.Pos(); // NOTE: since trie is reused, must capture pos here
- val = Parse(Bry_bfr_.New(), trie, val);
+ Xop_uniq_itm itm = (Xop_uniq_itm)o;
+ int itm_end = trv.Pos(); // NOTE: must capture pos since trv is reused in the recursive call below
+ // skip if template_parsing
+ if (template_parsing
+ && !itm.Expand_after_template_parsing()) {
+ pos = itm_end;
+ continue;
+ }
+
+ // add everything from prv_bgn up to UNIQ
+ bfr.Add_mid(src, prv_bgn, pos);
+
+ // expand UNIQ (can be recursive)
+ byte[] val = Parse_recurse(template_parsing, Bry_bfr_.New(), itm.Val());
// val = gplx.xowa.parsers.xndes.Xop_xnde_tkn.Hack_ctx.Wiki().Parser_mgr().Main().Parse_text_to_html(gplx.xowa.parsers.xndes.Xop_xnde_tkn.Hack_ctx, val); // CHART
- bfr.Add_mid(src, mark_bgn, pos);
bfr.Add(val);
dirty = true;
- pos = mark_bgn = new_pos;
+ pos = prv_bgn = itm_end;
}
if (is_last) {
if (dirty)
- bfr.Add_mid(src, mark_bgn, src_len);
+ bfr.Add_mid(src, prv_bgn, src_len);
break;
}
}
@@ -111,19 +96,19 @@ public class Xop_uniq_mgr { // REF.MW:/parser/StripState.php
}
public void Random_int_ary_(int... v) {random_int_ary = v;} private int[] random_int_ary; // TEST:
public byte[] Random_bry_new(int len) {
- Bry_bfr key_bfr = Bry_bfr_.New();
+ Bry_bfr tmp_bfr = Bry_bfr_.New();
RandomAdp random_gen = RandomAdp_.new_();
for (int i = 0; i < len; i += 7) {
int rand = random_int_ary == null ? random_gen.Next(Int_.Max_value) : random_int_ary[i / 7];
String rand_str = Int_.To_str_hex(Bool_.N, Bool_.Y, rand & 0xfffffff); // limits value to 268435455
- key_bfr.Add_str_a7(rand_str);
+ tmp_bfr.Add_str_a7(rand_str);
}
- byte[] rv = key_bfr.To_bry(0, len);
- key_bfr.Clear();
+ byte[] rv = tmp_bfr.To_bry(0, len);
+ tmp_bfr.Clear();
return rv;
}
- private static final byte[]
+ public static final byte[]
Bry__uniq__bgn = Bry_.new_a7("\u007f'\"`UNIQ-")
, Bry__uniq__bgn_w_dash = Bry_.Add(Bry__uniq__bgn, Byte_ascii.Dash_bry)
, Bry__uniq__add__end = Bry_.new_a7("-QINU`\"'\u007f")
diff --git a/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr__tst.java b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr__tst.java
index 8ed2a6cf7..4f5ae25d4 100644
--- a/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr__tst.java
+++ b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_mgr__tst.java
@@ -65,7 +65,7 @@ class Xop_uniq_mgr__fxt {
Gftest.Eq__str(expd, String_.new_a7(mgr.Uniq_bry_new()), "unique_bry");
}
public void Test__add(String raw, String expd) {
- Gftest.Eq__str(expd, String_.new_a7(mgr.Add(Bry_.new_a7("item"), Bry_.new_a7(raw))), "add");
+ Gftest.Eq__str(expd, String_.new_a7(mgr.Add(Bool_.Y, Bry_.new_a7("item"), Bry_.new_a7(raw))), "add");
}
public void Test__get(String key, String expd) {
Gftest.Eq__str(expd, String_.new_a7(mgr.Get(Bry_.new_a7(key))), "get");
@@ -74,6 +74,6 @@ class Xop_uniq_mgr__fxt {
Gftest.Eq__str(expd, String_.new_a7(mgr.Parse(Bry_.new_a7(raw))), "parse");
}
public void Test__convert(String raw, String expd) {
- Gftest.Eq__str(expd, String_.new_a7(mgr.Convert(Bry_.new_a7(raw))), "convert");
+ Gftest.Eq__str(expd, String_.new_a7(mgr.Parse(Bry_.new_a7(raw))), "convert");
}
}
diff --git a/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_tkn.java b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_tkn.java
new file mode 100644
index 000000000..82407a3af
--- /dev/null
+++ b/400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_tkn.java
@@ -0,0 +1,26 @@
+/*
+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.uniqs; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
+import gplx.langs.htmls.entitys.*;
+// EX: "\u007fUNIQ-item-1--QINU\u007f"
+public class Xop_uniq_tkn extends Xop_tkn_itm_base {
+ public Xop_uniq_tkn(int bgn, int end, byte[] key) {
+ this.Tkn_ini_pos(false, bgn, end);
+ this.key = key;
+ }
+ @Override public byte Tkn_tid() {return Xop_tkn_itm_.Tid_uniq;}
+ public byte[] Key() {return key;} private final byte[] key;
+}
diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xatr_whitelist_mgr.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xatr_whitelist_mgr.java
index 27a40660b..1f24105d1 100644
--- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xatr_whitelist_mgr.java
+++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xatr_whitelist_mgr.java
@@ -18,7 +18,8 @@ import gplx.core.primitives.*; import gplx.core.btries.*; import gplx.xowa.parse
public class Xop_xatr_whitelist_mgr {
private final Hash_adp_bry grp_hash = Hash_adp_bry.cs();
private final Btrie_rv trv = new Btrie_rv();
- public boolean Chk(int tag_id, byte[] src, Mwh_atr_itm xatr) {
+ public boolean Chk(int tag_id, Mwh_atr_itm xatr) {
+ byte[] src = xatr.Src();
byte[] key_bry = xatr.Key_bry();
byte[] chk_bry; int chk_bgn, chk_end;
if (key_bry == null) {
diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xatr_whitelist_mgr_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xatr_whitelist_mgr_tst.java
index bc5272929..9fb8408a0 100644
--- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xatr_whitelist_mgr_tst.java
+++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xatr_whitelist_mgr_tst.java
@@ -16,7 +16,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
package gplx.xowa.parsers.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
import org.junit.*; import gplx.xowa.parsers.htmls.*;
public class Xop_xatr_whitelist_mgr_tst {
- private final Xop_xatr_whitelist_fxt fxt = new Xop_xatr_whitelist_fxt();
+ private final Xop_xatr_whitelist_fxt fxt = new Xop_xatr_whitelist_fxt();
@Before public void init() {fxt.Clear();}
@Test public void Basic() {
fxt.Whitelist(Xop_xnde_tag_.Tid__div , "style" , true);
@@ -45,29 +45,29 @@ public class Xop_xatr_whitelist_mgr_tst {
}
class Xop_xatr_whitelist_fxt {
private Xop_xatr_whitelist_mgr whitelist_mgr;
- private Mwh_atr_itm atr_itm = new Mwh_atr_itm(null, false, false, false, -1, -1, -1, -1, null, -1, -1, null, -1, 0);
public void Clear() {
if (whitelist_mgr == null) whitelist_mgr = new Xop_xatr_whitelist_mgr().Ini();
}
public void Whitelist(int tag_id, String key_str, boolean expd) {
- byte[] key_bry = Bry_.new_a7(key_str);
- // atr_itm.Key_rng_(0, key_bry.length);
- atr_itm.Key_bry_(key_bry);
- Tfds.Eq(expd, whitelist_mgr.Chk(tag_id, key_bry, atr_itm), key_str);
+ Mwh_atr_itm atr_itm = New_atr_itm(key_str, null);
+ Tfds.Eq(expd, whitelist_mgr.Chk(tag_id, atr_itm), key_str);
}
public void Whitelist(int tag_id, String key_str, String val_str, boolean expd) {
- byte[] key_bry = Bry_.new_a7(key_str);
- // atr_itm.Key_rng_(0, key_bry.length);
- atr_itm.Key_bry_(key_bry);
- atr_itm.Val_bry_(Bry_.new_a7(val_str));
- Tfds.Eq(expd, whitelist_mgr.Chk(tag_id, key_bry, atr_itm), key_str);
+ Mwh_atr_itm atr_itm = New_atr_itm(key_str, val_str);
+ Tfds.Eq(expd, whitelist_mgr.Chk(tag_id, atr_itm), key_str);
}
public void Scrub_style_pass(String style_val_str) {Scrub_style(style_val_str, style_val_str);}
public void Scrub_style_fail(String val_str) {Scrub_style(val_str, "");}
public void Scrub_style(String val_str, String expd) {
byte[] val_bry = Bry_.new_a7(val_str);
- atr_itm.Val_bry_(val_bry);
+ Mwh_atr_itm atr_itm = New_atr_itm(null, val_str);
whitelist_mgr.Scrub_style(atr_itm, val_bry);
Tfds.Eq(expd, String_.new_a7(atr_itm.Val_bry()));
}
+ private static Mwh_atr_itm New_atr_itm(String key_str, String val_str) {
+ byte[] key_bry = key_str == null ? null : Bry_.new_u8(key_str);
+ byte[] val_bry = val_str == null ? null : Bry_.new_u8(val_str);
+ Mwh_atr_itm rv = new Mwh_atr_itm(key_bry, false, false, false, -1, -1, -1, -1, key_bry, -1, -1, val_bry, -1, 0);
+ return rv;
+ }
}
diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tkn.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tkn.java
index 29ef3540e..8616b2065 100644
--- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tkn.java
+++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_tkn.java
@@ -132,7 +132,7 @@ public class Xop_xnde_tkn extends Xop_tkn_itm_base implements Xop_tblw_tkn {
// UNIQ; DATE:2017-03-31
if (is_tmpl_mode) {
byte[] val = cur_bfr.To_bry_and_clear();
- byte[] key = ctx.Wiki().Parser_mgr().Uniq_mgr().Add(tag.Name_bry(), val);
+ byte[] key = ctx.Wiki().Parser_mgr().Uniq_mgr().Add(Bool_.Y, tag.Name_bry(), val);
bfr.Add(key);
}
}
diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr.java
index 36dda2dcc..c7d88e77d 100644
--- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr.java
+++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr.java
@@ -302,6 +302,13 @@ public class Xop_xnde_wkr implements Xop_ctx_wkr {
}
Mwh_atr_itm[] atrs = null;
if (ctx.Parse_tid() == Xop_parser_tid_.Tid__wtxt) {
+ // NOWIKI;DATE:2018-01-16
+ // if (atrs_bgn < atrs_end) {
+ // byte[] converted = ctx.Wiki().Parser_mgr().Uniq_mgr().Parse(Bool_.N, Bry_.Mid(src, atrs_bgn, atrs_end));
+ // atrs = ctx.App().Parser_mgr().Xnde__parse_atrs(converted, 0, converted.length);
+ // }
+ // else
+ // atrs = ctx.App().Parser_mgr().Xnde__parse_atrs(src, atrs_bgn, atrs_end);
atrs = ctx.App().Parser_mgr().Xnde__parse_atrs(src, atrs_bgn, atrs_end);
}
if (( ( tag.Xtn()
diff --git a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__nowiki_tst.java b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__nowiki_tst.java
index 95b1c5580..454532400 100644
--- a/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__nowiki_tst.java
+++ b/400_xowa/src/gplx/xowa/parsers/xndes/Xop_xnde_wkr__nowiki_tst.java
@@ -16,7 +16,7 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
package gplx.xowa.parsers.xndes; import gplx.*; import gplx.xowa.*; import gplx.xowa.parsers.*;
import org.junit.*;
public class Xop_xnde_wkr__nowiki_tst {
- private final Xop_fxt fxt = new Xop_fxt();
+ private final Xop_fxt fxt = new Xop_fxt();
@After public void term() {fxt.Init_para_n_();}
@Test public void Basic() {
fxt.Test_parse_page_wiki_str
diff --git a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_wtr.java b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_wtr.java
index d9618c76a..bb5560e20 100644
--- a/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_wtr.java
+++ b/400_xowa/src/gplx/xowa/xtns/gallery/Gallery_mgr_wtr.java
@@ -175,7 +175,7 @@ public class Gallery_mgr_wtr {
int len = xatr_list.Count();
for (int i = 0; i < len; i++) {
Mwh_atr_itm xatr = (Mwh_atr_itm)xatr_list.Get_at(i);
- if (!whitelist_mgr.Chk(Xop_xnde_tag_.Tid__ul, src, xatr)) continue;
+ if (!whitelist_mgr.Chk(Xop_xnde_tag_.Tid__ul, xatr)) continue;
byte[] key = xatr.Key_bry();
byte[] val = xatr.Val_as_bry();
Gfh_wtr.Write_atr_bry(bfr, key, val);
diff --git a/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_localizer.java b/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_localizer.java
new file mode 100644
index 000000000..ebd23a29f
--- /dev/null
+++ b/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_localizer.java
@@ -0,0 +1,151 @@
+/*
+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.xtns.jsonConfigs.scribunto; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.jsonConfigs.*;
+import gplx.xowa.langs.*;
+class Jscfg_localizer {
+ public Keyval[] Localize(Xol_lang_itm lang, byte[] page, Keyval[] root) {
+ if (lang == null) return root; // if no lang, return original
+
+ int len = root.length;
+ for (int i = 0; i < len; i++) {
+ Keyval nde = root[i];
+ String nde_key = nde.Key();
+ if (String_.Eq(nde_key, Id__root__license)) {
+ }
+ else if (String_.Eq(nde_key, Id__root__description)) {
+ root[i] = pickLocalizedString(lang, page, nde);
+ }
+ else if (String_.Eq(nde_key, Id__root__schema)) {
+ nde = Localize_schema(lang, page, nde);
+ }
+ else if (String_.Eq(nde_key, Id__root__data)) {
+ nde = Localize_data(lang, page, nde);
+ }
+ }
+
+ return root;
+ }
+ private Keyval Localize_schema(Xol_lang_itm lang, byte[] page, Keyval schema) {
+ Keyval[] schemas = Cast_to_kvary_or_null(page, schema) ; if (schemas == null) return schema;
+ Keyval[] fields = Cast_to_kvary_or_null(page, schemas[0]); if (fields == null) return schema;
+ for (Keyval field : fields) {
+ Keyval[] atrs = (Keyval[])field.Val();
+ int atrs_len = atrs.length;
+ for (int i = 0; i < atrs_len; i++) {
+ Keyval atr = atrs[i];
+ if (String_.Eq(atr.Key(), Id__fld__title)) {
+ atrs[i] = pickLocalizedString(lang, page, atr);
+ }
+ }
+ }
+ return schema;
+ }
+ private Keyval Localize_data(Xol_lang_itm lang, byte[] page, Keyval data) {
+ Keyval[] rows = Cast_to_kvary_or_null(page, data); if (rows == null) return data;
+ for (Keyval row : rows) {
+ Object[] vals = (Object[])row.Val();
+ int len = vals.length;
+ for (int i = 0; i < len; i++) {
+ Object val = vals[i];
+ if (Type_.Eq_by_obj(val, Keyval[].class)) {
+ Keyval val_as_kv = pickLocalizedString(lang, Int_.To_str(i), (Keyval[])val);
+ vals[i] = val_as_kv.Val();
+ }
+ }
+ }
+ return data;
+ }
+ private static Keyval pickLocalizedString(Xol_lang_itm lang, byte[] page, Keyval kv) {
+ Keyval[] kvs = Cast_to_kvary_or_null(page, kv.Key(), kv.Val());
+ Keyval rv = pickLocalizedString(lang, kv.Key(), kvs);
+ return rv == null ? kv : rv;
+ }
+ public static Keyval pickLocalizedString(Xol_lang_itm lang, String key, Keyval[] ary) {
+ // local vars for conditional logic
+ Object val_lang = null, val_en = null, val_1st = null;
+ Object[] val_fallbacks = null;
+
+ // local vars for lang
+ String langCode = lang.Key_str();
+ Ordered_hash fallback_hash = lang.Fallback_hash();
+
+ // loop ary to populate local vars
+ for (Keyval itm : ary) {
+ String itm_key = itm.Key();
+ Object itm_val = itm.Val();
+ if (val_1st == null) {
+ val_1st = itm_val;
+ }
+ if (String_.Eq(itm_key, langCode)) {
+ val_lang = itm_val;
+ }
+ else if (fallback_hash.Has(itm_key)) {
+ if (val_fallbacks == null) {
+ val_fallbacks = new Object[fallback_hash.Len()];
+ }
+ int idx = fallback_hash.Idx_of(itm_key);
+ val_fallbacks[idx] = itm_val;
+ }
+ else if (String_.Eq(itm_key, "en")) {
+ val_en = itm_val;
+ }
+
+ }
+
+ if (val_lang != null) {
+ return Keyval_.new_(key, val_lang);
+ }
+
+ if (val_fallbacks != null) {
+ for (Object v : val_fallbacks) {
+ if (v != null)
+ return Keyval_.new_(key, v);
+ }
+ }
+
+ // If fallbacks fail, check if english is defined
+ if (val_en != null) {
+ return Keyval_.new_(key, val_en);
+ }
+
+ // We have a custom default, return that
+ // if (defaultValue != null) {
+ // return null;
+ // }
+
+ // Return first available value, or an empty String
+ // There might be a better way to get the first value from an Object
+ Object val = val_1st == null ? "" : val_1st;
+ return Keyval_.new_(key, val);
+ }
+ private static Keyval[] Cast_to_kvary_or_null(byte[] page, Keyval kv) {return Cast_to_kvary_or_null(page, kv.Key(), kv.Val());}
+ private static Keyval[] Cast_to_kvary_or_null(byte[] page, String key, Object val) {
+ if (Type_.Eq_by_obj(val, Keyval[].class)) {
+ return (Keyval[])val;
+ }
+ else {
+ Gfo_usr_dlg_.Instance.Warn_many("", "", "could not cast to kvary; page=~{0} key=~{1}", key);
+ return null;
+ }
+ }
+ private static final String
+ Id__root__schema = "schema"
+ , Id__root__data = "data"
+ , Id__root__description = "description"
+ , Id__root__license = "license"
+ , Id__fld__title = "title"
+ ;
+}
diff --git a/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_scrib_lib.java b/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_scrib_lib.java
index efec50c1d..d4011dc36 100644
--- a/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_scrib_lib.java
+++ b/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_scrib_lib.java
@@ -18,6 +18,7 @@ import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.scribunto.libs.*; impor
import gplx.xowa.wikis.domains.*;
public class Jscfg_scrib_lib implements Scrib_lib {
private final Scrib_lib_text__json_util json_util = new Scrib_lib_text__json_util();
+ private final Jscfg_localizer localizer = new Jscfg_localizer();
private Scrib_core core;
public Scrib_lua_mod Mod() {return mod;} private Scrib_lua_mod mod;
public Scrib_lib Init() {procs.Init_by_lib(this, Proc_names); return this;}
@@ -57,6 +58,8 @@ public class Jscfg_scrib_lib implements Scrib_lib {
throw Err_.new_wo_type("bad argument #1 to 'get' (not a valid title) " + String_.new_u8(ttl_bry));
}
- return Scrib_lib_text.JsonDecodeStatic(args, rslt, core, json_util, page, Scrib_lib_text__json_util.Opt__force_assoc, Scrib_lib_text__json_util.Flag__none);
+ Keyval[] rv = Scrib_lib_text.JsonDecodeStatic(args, core, json_util, page, Scrib_lib_text__json_util.Opt__force_assoc, Scrib_lib_text__json_util.Flag__none);
+ rv = localizer.Localize(core.Wiki().Lang(), page, rv);
+ return rslt.Init_obj(rv);
}
}
diff --git a/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_scrib_lib_tst.java b/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_scrib_lib_tst.java
index a6ef7a2c6..557c708d8 100644
--- a/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_scrib_lib_tst.java
+++ b/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_scrib_lib_tst.java
@@ -14,18 +14,14 @@ GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.xtns.jsonConfigs.scribunto; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.jsonConfigs.*;
-import org.junit.*;
+import org.junit.*; import gplx.core.tests.*;
import gplx.xowa.xtns.scribunto.*; import gplx.xowa.xtns.scribunto.libs.*;
+import gplx.xowa.langs.*;
+import gplx.langs.jsons.*;
public class Jscfg_scrib_lib_tst {
- @Before public void init() {
- fxt.Clear_for_lib();
- lib = new Jscfg_scrib_lib();
- lib.Init();
- lib.Core_(fxt.Core());
- } private Scrib_invoke_func_fxt fxt = new Scrib_invoke_func_fxt(); private Jscfg_scrib_lib lib;
+ private final Jscfg_scrib_lib_fxt fxt = new Jscfg_scrib_lib_fxt();
@Test public void Get() {
- Xowe_wiki commons_wiki = fxt.Parser_fxt().Wiki().Appe().Wiki_mgr().Get_by_or_make(gplx.xowa.wikis.domains.Xow_domain_itm_.Bry__commons).Init_assert();
- fxt.Parser_fxt().Init_page_create(commons_wiki, "Data:Test.tab", gplx.langs.jsons.Json_doc.Make_str_by_apos
+ fxt.Init__page("Data:Test.tab", Json_doc.Make_str_by_apos
( "{"
, " 'data':"
, " ["
@@ -41,7 +37,7 @@ public class Jscfg_scrib_lib_tst {
, " ]"
, "}"
));
- fxt.Test_scrib_proc_str_ary(lib, Jscfg_scrib_lib.Invk_get, Keyval_.Ary(Keyval_.int_(1, "Test.tab")), String_.Concat_lines_nl_skip_last
+ fxt.Test__get("Test.tab", String_.Concat_lines_nl_skip_last
( "1="
, " data="
, " 1="
@@ -52,4 +48,126 @@ public class Jscfg_scrib_lib_tst {
, " 2=Data:Q2"
));
}
+ @Test public void Get_localize() {
+ fxt.Init__page("Data:Test_localize.tab", Json_doc.Make_str_by_apos
+ ( "{"
+ , " 'license': 'CC0-1.0',"
+ , " 'description': {"
+ , " 'de': 'Objekttabelle',"
+ , " 'en': 'Object table'"
+ , " },"
+ , " 'sources': 'Objects in Data:Data.tab completed by [https://www.wikidata.org Wikidata]',"
+ , " 'schema': {"
+ , " 'fields': ["
+ , " {"
+ , " 'name': 'wikidataID',"
+ , " 'type': 'String',"
+ , " 'title': {"
+ , " 'de': 'Wikidata-Item',"
+ , " 'en': 'Wikidata item'"
+ , " }"
+ , " },"
+ , " {"
+ , " 'name': 'wikidataLabel',"
+ , " 'type': 'localized',"
+ , " 'title': {"
+ , " 'de': 'Wikidata-Label',"
+ , " 'en': 'Wikidata label'"
+ , " }"
+ , " }"
+ , " ]"
+ , " },"
+ , " 'data': ["
+ , " ["
+ , " 'Q183',"
+ , " {"
+ , " 'de': 'Deutschland',"
+ , " 'en': 'Germany'"
+ , " }"
+ , " ],"
+ , " ["
+ , " 'Q61912',"
+ , " {"
+ , " 'de': 'Wertheim',"
+ , " 'en': 'Wertheim am Main'"
+ , " }"
+ , " ]"
+ , " ]"
+ , "}"
+ ));
+ fxt.Test__get( "Test_localize.tab", String_.Concat_lines_nl_skip_last
+ ( "1="
+ , " license=CC0-1.0"
+ , " description=Object table"
+ , " sources=Objects in Data:Data.tab completed by [https://www.wikidata.org Wikidata]"
+ , " schema="
+ , " fields="
+ , " 1="
+ , " name=wikidataID"
+ , " type=String"
+ , " title=Wikidata item"
+ , " 2="
+ , " name=wikidataLabel"
+ , " type=localized"
+ , " title=Wikidata label"
+ , " data="
+ , " 1="
+ , " 1=Q183"
+ , " 2=Germany"
+ , " 2="
+ , " 1=Q61912"
+ , " 2=Wertheim am Main"
+ ));
+ }
+ @Test public void pickLocalizedString() {
+ Xol_lang_itm lang = fxt.Init__lang("zh-cn", "zh1,zh0");
+
+ // match key
+ fxt.Test__pickLocalizedString(lang, fxt.Init__picklocalizedStringKvs("fr", "zh-cn"), "zh-cn");
+
+ // match fallback; note that zh1 is higher in fallback list, but lower in kvs
+ fxt.Test__pickLocalizedString(lang, fxt.Init__picklocalizedStringKvs("zh0", "zh1"), "zh1");
+
+ // match en if no key or fallbacks
+ fxt.Test__pickLocalizedString(lang, fxt.Init__picklocalizedStringKvs("fr", "en"), "en");
+
+ // pick 1st if no match
+ fxt.Test__pickLocalizedString(lang, fxt.Init__picklocalizedStringKvs("fr", "de"), "fr");
+ }
}
+class Jscfg_scrib_lib_fxt {
+ private final Scrib_invoke_func_fxt fxt = new Scrib_invoke_func_fxt();
+ private final Jscfg_scrib_lib lib;
+ private final Xowe_wiki commons_wiki;
+ public Jscfg_scrib_lib_fxt() {
+ fxt.Clear_for_lib();
+ lib = new Jscfg_scrib_lib();
+ lib.Init();
+ lib.Core_(fxt.Core());
+ this.commons_wiki = fxt.Parser_fxt().Wiki().Appe().Wiki_mgr().Get_by_or_make(gplx.xowa.wikis.domains.Xow_domain_itm_.Bry__commons).Init_assert();
+ }
+ public void Init__page(String page, String text) {
+ fxt.Parser_fxt().Init_page_create(commons_wiki, page, text);
+ }
+ public Xol_lang_itm Init__lang(String key, String fallbacks) {
+ Xol_lang_itm lang = new Xol_lang_itm(fxt.Core().App().Lang_mgr(), Bry_.new_u8(key));
+ lang.Fallback_bry_(Bry_.new_a7(fallbacks));
+ return lang;
+ }
+ public Keyval[] Init__picklocalizedStringKvs(String... vals) {
+ int len = vals.length;
+ Keyval[] rv = new Keyval[len];
+ for (int i = 0; i < len; i++) {
+ String val = vals[i];
+ rv[i] = Keyval_.new_(val, val);
+ }
+ return rv;
+ }
+ public void Test__get(String page, String expd) {
+ fxt.Test_scrib_proc_str_ary(lib, Jscfg_scrib_lib.Invk_get, Keyval_.Ary(Keyval_.int_(1, page)), expd);
+ }
+ public void Test__pickLocalizedString(Xol_lang_itm lang, Keyval[] kv_ary, String expd) {
+ Keyval actl_kv = Jscfg_localizer.pickLocalizedString(lang, "key", kv_ary);
+ Gftest.Eq__obj_or_null(expd, actl_kv.Val());
+ }
+}
diff --git a/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_xtn_mgr.java b/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_xtn_mgr.java
index ed267c274..d2612b690 100644
--- a/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_xtn_mgr.java
+++ b/400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_xtn_mgr.java
@@ -15,11 +15,23 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.xtns.jsonConfigs.scribunto; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.jsonConfigs.*;
import gplx.xowa.xtns.scribunto.*;
+import gplx.xowa.mediawiki.*;
public class Jscfg_xtn_mgr extends Xox_mgr_base {
@Override public byte[] Xtn_key() {return XTN_KEY;} public static final byte[] XTN_KEY = Bry_.new_a7("JsonConfig");
@Override public void Xtn_init_by_wiki(Xowe_wiki wiki) {
Scrib_xtn_mgr scrib_xtn = (Scrib_xtn_mgr)wiki.Xtn_mgr().Get_or_fail(Scrib_xtn_mgr.XTN_KEY);
scrib_xtn.Lib_mgr().Add(new Jscfg_scrib_lib());
}
+ /*
+ @Override public void Xtn_ctor_by_app(Xoae_app app) {
+ Init_xtn();
+ }
+ public void Init_xtn() {
+ JCSingleton singleton = new JCSingleton();
+ singleton.ConfigModels().Add(JCTabularContent.Model_id, JCTabularContent.Model_id);
+ XophpEnv.Instance.Singletons().Add(JCSingleton.Singleton_Id, singleton);
+ XophpEnv.Instance.ClassBldrs().Add(JCTabularContent.Model_id, new JCTabularContentFactory());
+ }
+ */
@Override public Xox_mgr Xtn_clone_new() {return new Jscfg_xtn_mgr();}
}
diff --git a/400_xowa/src/gplx/xowa/xtns/math/Xomath_html_wtr.java b/400_xowa/src/gplx/xowa/xtns/math/Xomath_html_wtr.java
index 9af4c23be..0b86937bf 100644
--- a/400_xowa/src/gplx/xowa/xtns/math/Xomath_html_wtr.java
+++ b/400_xowa/src/gplx/xowa/xtns/math/Xomath_html_wtr.java
@@ -67,7 +67,7 @@ class Xomath_html_wtr {
}
// write html: math_expr
- byte[] unique_bry = wiki.Parser_mgr().Uniq_mgr().Add(Bry__math, math_bry);
+ byte[] unique_bry = wiki.Parser_mgr().Uniq_mgr().Add(Bool_.Y, Bry__math, math_bry);
Bry_fmt fmt = is_latex ? fmt__latex : fmt__mathjax;
fmt.Bld_many(tmp_bfr, uid, unique_bry);
bfr.Add_bfr_and_clear(tmp_bfr);
diff --git a/400_xowa/src/gplx/xowa/xtns/pfuncs/strings/Pfunc_tag.java b/400_xowa/src/gplx/xowa/xtns/pfuncs/strings/Pfunc_tag.java
index 6aba0094b..8aca3ed8a 100644
--- a/400_xowa/src/gplx/xowa/xtns/pfuncs/strings/Pfunc_tag.java
+++ b/400_xowa/src/gplx/xowa/xtns/pfuncs/strings/Pfunc_tag.java
@@ -52,7 +52,7 @@ public class Pfunc_tag extends Pf_func_base {// REF:CoreParserFunctions.php
// add to UNIQ hash; DATE:2017-03-31
byte[] val = tmp_bfr.To_bry_and_clear();
- byte[] key = ctx.Wiki().Parser_mgr().Uniq_mgr().Add(tag_name, val);
+ byte[] key = ctx.Wiki().Parser_mgr().Uniq_mgr().Add(Bool_.Y, tag_name, val);
bfr.Add(key);
}
finally {tmp_bfr.Mkr_rls();}
diff --git a/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text.java b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text.java
index a0adc4d7d..08cd713e6 100644
--- a/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text.java
+++ b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text.java
@@ -14,12 +14,18 @@ GPLv3 License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-GPLv3.txt
Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.xtns.scribunto.libs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.scribunto.*;
-import gplx.core.bits.*;
+import gplx.core.bits.*; import gplx.core.btries.*;
import gplx.xowa.langs.msgs.*;
import gplx.xowa.xtns.scribunto.procs.*;
public class Scrib_lib_text implements Scrib_lib {
private final Scrib_lib_text__json_util json_util = new Scrib_lib_text__json_util();
- public Scrib_lib_text(Scrib_core core) {this.core = core;} private Scrib_core core;
+ private final Scrib_lib_text__nowiki_util nowiki_util = new Scrib_lib_text__nowiki_util();
+ private final Scrib_core core;
+ private final Btrie_slim_mgr trie;
+ public Scrib_lib_text(Scrib_core core) {
+ this.core = core;
+ this.trie = nowiki_util.Make_trie(gplx.xowa.parsers.xndes.Xop_xnde_tag_.Tag__nowiki.Name_bry());
+ }
public Scrib_lua_mod Mod() {return mod;} private Scrib_lua_mod mod;
public Scrib_lib Init() {procs.Init_by_lib(this, Proc_names); return this;}
public Scrib_lib Clone_lib(Scrib_core core) {return new Scrib_lib_text(core);}
@@ -47,7 +53,11 @@ public class Scrib_lib_text implements Scrib_lib {
, Invk_init_text_for_wiki = "init_text_for_wiki", Invk_jsonEncode = "jsonEncode", Invk_jsonDecode = "jsonDecode";
private static final String[] Proc_names = String_.Ary(Invk_unstrip, Invk_unstripNoWiki, Invk_killMarkers, Invk_getEntityTable, Invk_init_text_for_wiki, Invk_jsonEncode, Invk_jsonDecode);
public boolean Unstrip(Scrib_proc_args args, Scrib_proc_rslt rslt) {return rslt.Init_obj(args.Pull_str(0));} // NOTE: XOWA does not use MediaWiki strip markers; just return original; DATE:2015-01-20
- public boolean UnstripNoWiki(Scrib_proc_args args, Scrib_proc_rslt rslt) {return rslt.Init_obj(args.Pull_str(0));} // NOTE: XOWA does not use MediaWiki strip markers; just return original; DATE:2015-01-20
+ public boolean UnstripNoWiki(Scrib_proc_args args, Scrib_proc_rslt rslt) {
+ // NOTE: XOWA does not use MediaWiki strip markers; just return original; DATE:2015-01-20
+ byte[] src = args.Pull_bry(0);
+ return rslt.Init_obj(nowiki_util.Strip_tag(core.Page().Url_bry_safe(), src, trie));
+ }
public boolean KillMarkers(Scrib_proc_args args, Scrib_proc_rslt rslt) {return rslt.Init_obj(args.Pull_str(0));} // NOTE: XOWA does not use MediaWiki strip markers; just return original; DATE:2015-01-20
public boolean GetEntityTable(Scrib_proc_args args, Scrib_proc_rslt rslt) {
if (html_entities == null) html_entities = Scrib_lib_text_html_entities.new_();
@@ -108,10 +118,11 @@ public class Scrib_lib_text implements Scrib_lib {
if (Bitmask_.Has_int(flags, Scrib_lib_text__json_util.Flag__try_fixing))
opts = Bitmask_.Add_int(opts, Scrib_lib_text__json_util.Flag__try_fixing);
- return JsonDecodeStatic(args, rslt, core, json_util, json, opts, flags);
+ Keyval[] rv = JsonDecodeStatic(args, core, json_util, json, opts, flags);
+ return rslt.Init_obj(rv);
}
- public static boolean JsonDecodeStatic
- ( Scrib_proc_args args, Scrib_proc_rslt rslt, Scrib_core core, Scrib_lib_text__json_util json_util
+ public static Keyval[] JsonDecodeStatic
+ ( Scrib_proc_args args, Scrib_core core, Scrib_lib_text__json_util json_util
, byte[] json, int opts, int flags) {
// decode json to Object; note that Bool_.Y means ary and Bool_.N means ary
byte rv_tid = json_util.Decode(core.App().Utl__json_parser(), json, opts);
@@ -125,10 +136,10 @@ public class Scrib_lib_text implements Scrib_lib {
json_util.Reindex_arrays(reindex_data, rv_as_kvy, false);
rv_as_kvy = reindex_data.Rv_is_kvy() ? (Keyval[])reindex_data.Rv_as_kvy() : (Keyval[])reindex_data.Rv_as_ary();
}
- return rslt.Init_obj(rv_as_kvy);
+ return rv_as_kvy;
}
else
- return rslt.Init_obj(json_util.Decode_rslt_as_ary());
+ return json_util.Decode_rslt_as_ary();
}
public void Notify_wiki_changed() {if (notify_wiki_changed_fnc != null) core.Interpreter().CallFunction(notify_wiki_changed_fnc.Id(), Keyval_.Ary_empty);}
diff --git a/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text__nowiki_util.java b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text__nowiki_util.java
new file mode 100644
index 000000000..974c4b02c
--- /dev/null
+++ b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text__nowiki_util.java
@@ -0,0 +1,90 @@
+/*
+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.xtns.scribunto.libs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.scribunto.*;
+import gplx.core.btries.*;
+import gplx.core.primitives.*;
+public class Scrib_lib_text__nowiki_util {
+ public Btrie_slim_mgr Make_trie(byte[] tag) {
+ Bry_bfr tmp = Bry_bfr_.New();
+ Btrie_slim_mgr rv = Btrie_slim_mgr.ci_u8();
+ byte[] lhs_bry = tmp.Add_bry_many(Byte_ascii.Angle_bgn_bry, tag, Byte_ascii.Angle_end_bry).To_bry_and_clear();
+ byte[] rhs_bry = tmp.Add_bry_many(Byte_ascii.Angle_bgn_bry, Byte_ascii.Slash_bry, tag, Byte_ascii.Angle_end_bry).To_bry_and_clear();
+ rv.Add_obj(lhs_bry, Bool_obj_val.True);
+ rv.Add_obj(rhs_bry, Bool_obj_val.False);
+ return rv;
+ }
+ public byte[] Strip_tag(byte[] page, byte[] src, Btrie_slim_mgr trie) {
+ Btrie_rv trv = new Btrie_rv();
+ Bry_bfr tmp = null;
+ int bgn = 0;
+ int end = src.length;
+
+ // main loop
+ boolean lhs_found = false;
+ int pos = bgn;
+ int rhs_end = pos, lhs_bgn = pos, lhs_end = pos;
+ while (pos < end) {
+ // check byte against trie
+ Object o = trie.Match_at_w_b0(trv, src[pos], src, pos, end);
+
+ // no match; increment and continue;
+ if (o == null) {
+ pos++;
+ continue;
+ }
+
+ // match found
+ Bool_obj_val tag_marker = (Bool_obj_val)o;
+
+ // match is open tag; EX:
+ if (tag_marker.Val()) {
+ // set lhs_bgn and lhs_end; note that if there are multiple open tags, it will only keep the first
+ if (!lhs_found) {
+ lhs_found = true;
+ lhs_bgn = pos;
+ lhs_end = trv.Pos();
+ }
+ }
+ // match is close tag; EX:
+ else {
+ // only splice if open tag exists; avoids dangling rhs; EX: "ab"
+ if (lhs_found) {
+ lhs_found = false;
+ if (tmp == null) tmp = Bry_bfr_.New();
+
+ // add text from previous to current ;
+ tmp.Add_mid(src, rhs_end, lhs_bgn);
+
+ // add text between and ;
+ tmp.Add_mid(src, lhs_end, pos);
+
+ // update pos
+ rhs_end = trv.Pos();
+ }
+ }
+
+ // update pos to after match
+ pos = trv.Pos();
+ }
+
+ // add remaining text to bfr
+ if (tmp != null) {
+ tmp.Add_mid(src, rhs_end, end);
+ }
+
+ return tmp == null ? src : tmp.To_bry_and_clear();
+ }
+}
diff --git a/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text__nowiki_util_tst.java b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text__nowiki_util_tst.java
new file mode 100644
index 000000000..df035af7b
--- /dev/null
+++ b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text__nowiki_util_tst.java
@@ -0,0 +1,55 @@
+/*
+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.xtns.scribunto.libs; import gplx.*; import gplx.xowa.*; import gplx.xowa.xtns.*; import gplx.xowa.xtns.scribunto.*;
+import org.junit.*; import gplx.core.tests.*;
+import gplx.core.btries.*;
+import gplx.xowa.parsers.xndes.*;
+public class Scrib_lib_text__nowiki_util_tst {
+ private final Scrib_lib_text__nowiki_util_fxt fxt = new Scrib_lib_text__nowiki_util_fxt();
+ @Test public void Basic() {
+ // noop
+ fxt.Test__Strip_tag("abc", "abc");
+
+ // one
+ fxt.Test__Strip_tag("abc", "abc");
+
+ // mixed case
+ fxt.Test__Strip_tag("abc", "abc");
+
+ // multiple: consecutive
+ fxt.Test__Strip_tag("abcde", "abcde");
+
+ // dangling: left
+ fxt.Test__Strip_tag("abcd", "abcd");
+
+ // dangling: right
+ fxt.Test__Strip_tag( "abcd", "abcd");
+
+ // nested
+ fxt.Test__Strip_tag("abcde", "abcde");
+ }
+}
+class Scrib_lib_text__nowiki_util_fxt {
+ private final Scrib_lib_text__nowiki_util util = new Scrib_lib_text__nowiki_util();
+ private final Btrie_slim_mgr trie;
+ public Scrib_lib_text__nowiki_util_fxt() {
+ this.trie = util.Make_trie(Xop_xnde_tag_.Tag__nowiki.Name_bry());
+ }
+ public void Test__Strip_tag(String src, String expd) {
+ byte[] actl = util.Strip_tag(Bry_.new_a7("Page"), Bry_.new_u8(src), trie);
+ Gftest.Eq__str(expd, actl);
+ }
+}
diff --git a/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text_tst.java b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text_tst.java
index 4d048a79c..f730335fc 100644
--- a/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text_tst.java
+++ b/400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text_tst.java
@@ -25,6 +25,9 @@ public class Scrib_lib_text_tst {
@Test public void Unstrip() {
fxt.Test_scrib_proc_str(lib, Scrib_lib_text.Invk_unstrip, Object_.Ary("a"), "a");
}
+ @Test public void UnstripNoWiki() {
+ fxt.Test_scrib_proc_str(lib, Scrib_lib_text.Invk_unstripNoWiki, Object_.Ary("abc"), "abc");
+ }
@Test public void GetEntityTable() {
Keyval[] actl = fxt.Test_scrib_proc_rv_as_kv_ary(lib, Scrib_lib_text.Invk_getEntityTable, Object_.Ary());
Tfds.Eq(1510, actl.length); // large result; only test # of entries
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpArray.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpArray.java
index 7d4101a52..a7cfd039e 100644
--- a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpArray.java
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpArray.java
@@ -15,31 +15,10 @@ Apache License: https://github.com/gnosygnu/xowa/blob/master/LICENSE-APACHE2.txt
*/
package gplx.xowa.mediawiki; import gplx.*; import gplx.xowa.*;
public class XophpArray {
- public static boolean popBoolOrN(List_adp list) {return Bool_.Cast(List_adp_.Pop_or(list, false));}
- public static byte[] popBryOrNull(List_adp list) {return (byte[])List_adp_.Pop_or(list, null);}
- public static String[] array_keys_str(Ordered_hash array) {
- int len = array.Len();
- String[] rv = new String[len];
- for (int i = 0; i < len; i++) {
- rv[i] = (String)array.Get_at(i);
- }
- return rv;
- }
- public static byte[][] array_keys_bry(Ordered_hash array) {
- int len = array.Len();
- byte[][] rv = new byte[len][];
- for (int i = 0; i < len; i++) {
- rv[i] = (byte[])array.Get_at(i);
- }
- return rv;
- }
- public static boolean array_key_exists(int key, Ordered_hash array) {return array.Has(key);}
- public static boolean array_key_exists(String key, Ordered_hash array) {return array.Has(key);}
- public static boolean array_key_exists(byte[] key, Ordered_hash array) {return array.Has(key);}
- public static boolean array_is_empty(Ordered_hash array) {
- return array.Len() == 0;
- }
- public static void unset(Ordered_hash array, Object key) {
- array.Del(key);
+ public static boolean in_array(String needle, String[] haystack) {
+ for (String hay : haystack)
+ if (String_.Eq(hay, needle))
+ return true;
+ return false;
}
}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpArrayUtl.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpArrayUtl.java
new file mode 100644
index 000000000..a3b33c63d
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpArrayUtl.java
@@ -0,0 +1,54 @@
+/*
+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 XophpArrayUtl {
+ public static boolean popBoolOrN(List_adp list) {return Bool_.Cast(List_adp_.Pop_or(list, false));}
+ public static byte[] popBryOrNull(List_adp list) {return (byte[])List_adp_.Pop_or(list, null);}
+ public static String[] array_keys_str(Ordered_hash array) {
+ int len = array.Len();
+ String[] rv = new String[len];
+ for (int i = 0; i < len; i++) {
+ rv[i] = (String)array.Get_at(i);
+ }
+ return rv;
+ }
+ public static byte[][] array_keys_bry(Ordered_hash array) {
+ int len = array.Len();
+ byte[][] rv = new byte[len][];
+ for (int i = 0; i < len; i++) {
+ rv[i] = (byte[])array.Get_at(i);
+ }
+ return rv;
+ }
+ public static boolean array_key_exists(int key, Ordered_hash array) {return array.Has(key);}
+ public static boolean array_key_exists(String key, Ordered_hash array) {return array.Has(key);}
+ public static boolean array_key_exists(byte[] key, Ordered_hash array) {return array.Has(key);}
+ public static boolean array_is_empty(Ordered_hash array) {
+ return array.Len() == 0;
+ }
+ public static void unset(Ordered_hash array, Object key) {
+ array.Del(key);
+ }
+ public static Object[] unset_by_idx(Object[] ary, int idx) {
+ int ary_len = ary.length;
+ Object[] rv = new Object[ary_len];
+ for (int i = 0; i < idx; i++)
+ rv[i] = ary[i];
+ for (int i = idx + 1; i < ary_len; i++)
+ rv[i - 1] = ary[i];
+ return rv;
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpClassBldr.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpClassBldr.java
new file mode 100644
index 000000000..20c5f7f55
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpClassBldr.java
@@ -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 XophpClassBldr {
+ String Id();
+ Object Make(Object... args);
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpClassBldrs.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpClassBldrs.java
new file mode 100644
index 000000000..d298ff4a7
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpClassBldrs.java
@@ -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 XophpClassBldrs {
+ private final Ordered_hash hash = Ordered_hash_.New();
+ public void Add(String id, XophpClassBldr bldr) {
+ hash.Add(id, bldr);
+ }
+ public XophpClassBldr Get_by_or_null(String id) {
+ return (XophpClassBldr)hash.Get_by(id);
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpEnv.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpEnv.java
new file mode 100644
index 000000000..d2eba51c0
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpEnv.java
@@ -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 XophpEnv {
+ public XophpClassBldrs ClassBldrs() {return classBldrs;} private final XophpClassBldrs classBldrs = new XophpClassBldrs();
+ public Ordered_hash Singletons() {return singletons;} private final Ordered_hash singletons = Ordered_hash_.New();
+
+ public static final XophpEnv Instance = new XophpEnv();
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpStdClass.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpStdClass.java
new file mode 100644
index 000000000..bae2c118d
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpStdClass.java
@@ -0,0 +1,81 @@
+/*
+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 XophpStdClass {
+ private final List_adp list = List_adp_.New();
+ private final Ordered_hash hash = Ordered_hash_.New();
+ public int Len() {return list.Len();}
+ public boolean Has(String key) {return hash.Has(key);}
+ public void Add_at_as_itm(XophpStdClass itm) {
+ list.Add(itm);
+ }
+ public void Add_by_as_obj(String key, Object itm) {
+ list.Add(itm);
+ hash.Add(key, itm);
+ }
+ public XophpStdClass Get_at_as_itm(int idx) {return (XophpStdClass)list.Get_at(idx);}
+ public Object Get_by_as_obj(String key) {return hash.Get_by(key);}
+ public XophpStdClass Get_by_as_itm(String key) {return (XophpStdClass)hash.Get_by(key);}
+ public String Get_by_as_str(String key) {return (String)hash.Get_by(key);}
+ public String Get_at_as_str(int idx) {return (String)list.Get_at(idx);}
+ public XophpStdClass Get_by_ary_as_itm(String... keys) {
+ return (XophpStdClass)Get_by_ary_or_null(false, keys, keys.length - 1, 0);
+ }
+ public boolean Comp_str(String key, String expd) {
+ String actl = Get_by_as_str(key);
+ return String_.Eq(expd, actl);
+ }
+ public void Set_by_as_itm(String key, XophpStdClass itm) {
+ hash.Add_if_dupe_use_nth(key, itm);
+ }
+ public void Set_by_as_itm(String[] keys, XophpStdClass rv) {
+ int keys_last_idx = keys.length - 1;
+ XophpStdClass itm = (XophpStdClass)Get_by_ary_or_null(true, keys, keys_last_idx - 1, 0);
+ itm.Set_by_as_itm(keys[keys_last_idx], rv);
+ }
+ public void Set_by_as_str(String key, String val) {
+ hash.Add_if_dupe_use_nth(key, val);
+ }
+ public void Set_at_as_str(int idx, String val) {
+ list.Del_at(idx);
+ list.Add_at(idx, val);
+ }
+ public void Del_by(String key) {
+ Object itm = hash.Get_by(key);
+ hash.Del(key);
+ list.Del(itm);
+ }
+ private Object Get_by_ary_or_null(boolean create, String[] keys, int keys_idx_last, int keys_idx) {
+ if (keys_idx == keys_idx_last) {
+ return hash.Get_by(keys[keys_idx_last]);
+ }
+
+ String key = keys[keys_idx];
+ XophpStdClass itm = Get_by_as_itm(key);
+ if (itm == null) {
+ // set
+ if (create) {
+ itm = new XophpStdClass();
+ Set_by_as_itm(key, itm);
+ }
+ // get
+ else {
+ return null;
+ }
+ }
+ return itm.Get_by_ary_or_null(create, keys, keys_idx_last, keys_idx + 1);
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary.java
new file mode 100644
index 000000000..d36cc7f22
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary.java
@@ -0,0 +1,90 @@
+/*
+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.brys.*;
+public class Xophp_ary implements Bry_bfr_able {
+ private final Ordered_hash hash = Ordered_hash_.New();
+ private int nxt_idx;
+ public Xophp_ary Add(Object val) {
+ int key = nxt_idx++;
+ Xophp_ary_itm itm = Xophp_ary_itm.New(key, val);
+ hash.Add_if_dupe_use_nth(key, itm);
+ return this;
+ }
+ public Xophp_ary Add(int key, Object val) {
+ nxt_idx = key + 1;
+ Xophp_ary_itm itm = Xophp_ary_itm.New(key, val);
+ hash.Add_if_dupe_use_nth(key, itm);
+ return this;
+ }
+ public Xophp_ary Add(double key, Object val) {
+ int key_as_int = (int)key;
+ nxt_idx = key_as_int + 1;
+ Xophp_ary_itm itm = Xophp_ary_itm.New(key_as_int, val);
+ hash.Add_if_dupe_use_nth(key_as_int, itm);
+ return this;
+ }
+ public Xophp_ary Add(boolean key, Object val) {
+ int key_as_int = key ? 1 : 0;
+ nxt_idx = key_as_int + 1;
+ Xophp_ary_itm itm = Xophp_ary_itm.New(key_as_int, val);
+ hash.Add_if_dupe_use_nth(key_as_int, itm);
+ return this;
+ }
+ public Xophp_ary Add(String key, Object val) {
+ Xophp_ary_itm itm = null;
+ int key_as_int = Int_.Parse_or(key, Int_.Min_value);
+ if (key_as_int != Int_.Min_value) {
+ itm = Xophp_ary_itm.New(key_as_int, val);
+ nxt_idx = key_as_int + 1;
+ hash.Add_if_dupe_use_nth(key_as_int, itm);
+ }
+ else {
+ itm = Xophp_ary_itm.New(key, val);
+ hash.Add_if_dupe_use_nth(key, itm);
+ }
+ return this;
+ }
+ public Object Get(Object key) {
+ Xophp_ary_itm itm = (Xophp_ary_itm)hash.Get_by(key);
+ return itm.Val();
+ }
+ public void Unset(Object key) {
+ hash.Del(key);
+ }
+ public boolean Has(Object key) {
+ return hash.Has(key);
+ }
+ public Xophp_ary Values() {
+ Xophp_ary rv = new Xophp_ary();
+ int len = hash.Len();
+ for (int i = 0; i < len; i++) {
+ Xophp_ary_itm old_itm = (Xophp_ary_itm)hash.Get_at(i);
+ rv.Add(i, old_itm.Val());
+ }
+ return rv;
+ }
+ public Xophp_ary_itm[] To_ary() {
+ return (Xophp_ary_itm[])hash.To_ary(Xophp_ary_itm.class);
+ }
+ public void To_bfr(Bry_bfr bfr) {
+ Xophp_ary_itm[] itms = To_ary();
+ for (Xophp_ary_itm itm : itms) {
+ itm.To_bfr(bfr);
+ }
+ }
+ public static Xophp_ary New() {return new Xophp_ary();}
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary_itm.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary_itm.java
new file mode 100644
index 000000000..bd8752b3d
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary_itm.java
@@ -0,0 +1,43 @@
+/*
+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.brys.*;
+public class Xophp_ary_itm implements Bry_bfr_able {
+ public Xophp_ary_itm(int key_as_int, String key_as_str, Object val) {
+ this.key_as_int = key_as_int;
+ this.key_as_str = key_as_str;
+ this.val = val;
+ }
+ public int Key_as_int() {return key_as_int;} private final int key_as_int;
+ public String Key_as_str() {return key_as_str;} private final String key_as_str;
+ public Object Val() {return val;} private final Object val;
+ public void To_bfr(Bry_bfr bfr) {
+ String key = key_as_str == null ? Int_.To_str(key_as_int) : key_as_str;
+ bfr.Add_str_u8(key).Add_byte_eq();
+
+ if (Type_.Type_by_obj(val) == Xophp_ary.class) {
+ Xophp_ary sub_ary = (Xophp_ary)val;
+ bfr.Add_byte_nl();
+ sub_ary.To_bfr(bfr);
+ }
+ else {
+ bfr.Add_obj(val);
+ }
+ }
+
+ public static Xophp_ary_itm New(int key, Object val) {return new Xophp_ary_itm(key, null, val);}
+ public static Xophp_ary_itm New(String key, Object val) {return new Xophp_ary_itm(-1 , key , val);}
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary_tst.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary_tst.java
new file mode 100644
index 000000000..ec6f29c31
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary_tst.java
@@ -0,0 +1,137 @@
+/*
+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 Xophp_ary_tst { // REF: http://php.net/manual/en/language.types.array.php
+ private final XophpArray_fxt fxt = new XophpArray_fxt();
+ @Test public void array__kvs() {
+ // $array = array("foo" => "bar", "bar" => "foo",);
+ fxt.Test__array
+ ( Xophp_ary.New()
+ . Add("foo", "bar")
+ . Add("bar", "foo")
+ , Xophp_ary_itm.New("foo", "bar")
+ , Xophp_ary_itm.New("bar", "foo")
+ );
+ }
+ @Test public void array__casting() {
+ // $array = array(1 => "a", "1" => "b", 1.5 => "c", true => "d",);
+ fxt.Test__array
+ ( Xophp_ary.New()
+ . Add(1 , "a")
+ . Add("1" , "b")
+ . Add(1.5 , "c")
+ . Add(true, "d")
+ , Xophp_ary_itm.New(1, "d"));
+ }
+ @Test public void array__mixed() {
+ // $array = array("foo" => "bar", "bar" => "foo", 100 => -100, -100 => 100);
+ fxt.Test__array
+ ( Xophp_ary.New()
+ . Add("foo", "bar")
+ . Add("bar", "foo")
+ . Add(100, -100)
+ . Add(-100, 100)
+ , Xophp_ary_itm.New("foo", "bar")
+ , Xophp_ary_itm.New("bar", "foo")
+ , Xophp_ary_itm.New(100, -100)
+ , Xophp_ary_itm.New(-100, 100)
+ );
+ }
+ @Test public void array__objs() {
+ // $array = array("foo", "bar", "hello", "world");
+ fxt.Test__array
+ ( Xophp_ary.New()
+ . Add("foo")
+ . Add("bar")
+ . Add("hello")
+ . Add("world")
+ , Xophp_ary_itm.New(0, "foo")
+ , Xophp_ary_itm.New(1, "bar")
+ , Xophp_ary_itm.New(2, "hello")
+ , Xophp_ary_itm.New(3, "world")
+ );
+ }
+ @Test public void array__unkeyed() {
+ // $array = array("a", "b", 6 => "c", "d");
+ fxt.Test__array
+ ( Xophp_ary.New()
+ . Add("a")
+ . Add("b")
+ . Add(6, "c")
+ . Add("d")
+ , Xophp_ary_itm.New(0, "a")
+ , Xophp_ary_itm.New(1, "b")
+ , Xophp_ary_itm.New(6, "c")
+ , Xophp_ary_itm.New(7, "d")
+ );
+ }
+ @Test public void array__multidimensional() {
+ /*
+ $array = array(
+ "foo" => "bar",
+ 42 => 24,
+ "multi" => array(
+ "dimensional" => array(
+ "array" => "foo"
+ )
+ )
+ );
+ */
+ fxt.Test__array
+ ( Xophp_ary.New()
+ . Add("foo" , "bar")
+ . Add(42 , 24)
+ . Add("multi" , Xophp_ary.New()
+ . Add("dimensional", Xophp_ary.New()
+ . Add("array", "foo")
+ ))
+ , Xophp_ary_itm.New("foo", "bar")
+ , Xophp_ary_itm.New(42, "24")
+ , Xophp_ary_itm.New("multi", Xophp_ary.New()
+ . Add("dimensional", Xophp_ary.New()
+ . Add("array", "foo")
+ ))
+ );
+ }
+ @Test public void array__unset() {
+ Xophp_ary ary = Xophp_ary.New();
+ ary.Add(0, "a").Add(1, "b");
+
+ // delete all
+ ary.Unset(0);
+ ary.Unset(1);
+ fxt.Test__array(ary);
+
+ // add new and assert idx is 2
+ ary.Add("c");
+ fxt.Test__array(ary, Xophp_ary_itm.New(2, "c"));
+
+ ary = ary.Values();
+ ary.Add("d");
+ fxt.Test__array(ary, Xophp_ary_itm.New(0, "c"), Xophp_ary_itm.New(1, "d"));
+ }
+}
+class XophpArray_fxt {
+ public void Test__array(Xophp_ary ary, Xophp_ary_itm... expd) {
+ Xophp_ary_itm[] actl = ary.To_ary();
+ Gftest.Eq__ary(expd, actl);
+ }
+ public void Test__unset(Xophp_ary ary, int idx, Xophp_ary_itm... expd) {
+ Xophp_ary_itm[] actl = ary.To_ary();
+ Gftest.Eq__ary(expd, actl);
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCContent.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCContent.java
new file mode 100644
index 000000000..dba426980
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCContent.java
@@ -0,0 +1,226 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.content.*;
+public class JCContent extends TextContent { /** @var array */
+ private Object rawData = null;
+ /** @var stdClass|array */
+ protected XophpStdClass data = null;
+ /** @var Status */
+ private XomwStatus status;
+ /** @var boolean */
+ private boolean thoroughVar;
+ /** @var JCContentView|null contains an instance of the view class */
+ // private Object view = null;
+
+ /**
+ * @param String text Json configuration. If null, default content will be inserted instead
+ * @param String $modelId
+ * @param boolean thorough True if extra validation should be performed
+ */
+ public void __construct(byte[] text, String modelId, boolean thorough) {
+ if (text == null) {
+ // text = this.getView($modelId).getDefault($modelId);
+ }
+ super.__construct(text, modelId);
+ this.thoroughVar = thorough;
+ this.status = new XomwStatus();
+ this.parse();
+ }
+
+ /**
+ * Get validated data
+ * @return stdClass|stdClass[]
+ */
+ public XophpStdClass getData() {
+ return this.data;
+ }
+
+ /**
+ * Returns data after sanitization, suitable for third-party use
+ *
+ * @param stdClass|stdClass[] data
+ * @return stdClass|stdClass[]
+ */
+ public XophpStdClass getSafeData(XophpStdClass data) {
+ return data;
+ }
+
+ /**
+ * Returns JSON Object as resulted from parsing initial text,
+ * before any validation/modifications took place
+ * @return mixed
+ */
+ public Object getRawData() {
+ return this.rawData;
+ }
+
+ /**
+ * Get content status Object
+ * @return Status
+ */
+ public XomwStatus getStatus() {
+ return this.status;
+ }
+
+ /**
+ * @return boolean False if this configuration has parsing or validation errors
+ */
+ public boolean isValid() {
+ return this.status.isGood();
+ }
+
+ private static final byte[] Bry__ary__empty = Bry_.new_a7("{}");
+ public boolean isEmpty() {
+ byte[] text = Bry_.Trim(this.getNativeData());
+ return Bry_.Len_eq_0(text) || Bry_.Eq(text, Bry__ary__empty);
+ }
+
+ /**
+ * Determines whether this content should be considered a "page" for statistics
+ * In our case, just making sure it's not empty or a redirect
+ * @param boolean $hasLinks
+ * @return boolean
+ */
+ public boolean isCountable(boolean hasLinks) {
+ return !this.isEmpty() && !this.isRedirect();
+ }
+
+ /**
+ * Returns true if the text is in JSON format.
+ * @return boolean
+ */
+ public boolean isValidJson() {
+ return this.rawData != null;
+ }
+
+ /**
+ * @return boolean true if thorough validation may be needed -
+ * e.g. rendering HTML or saving new value
+ */
+ public boolean thorough() {
+ return this.thoroughVar;
+ }
+
+ /**
+ * Override this method to perform additional data validation
+ * @param mixed data
+ * @return mixed
+ */
+ public XophpStdClass validate(XophpStdClass data) {
+ return data;
+ }
+
+ /**
+ * Perform initial json parsing and validation
+ */
+ private void parse() {
+// String rawText = this.getNativeData();
+// parseOpts = FormatJson::STRIP_COMMENTS + FormatJson::TRY_FIXING;
+// status = FormatJson::parse(rawText, parseOpts);
+// if (!status.isOK()) {
+// this.status = status;
+// return;
+// }
+// data = status.getValue();
+// // @fixme: HACK - need a deep clone of the data
+// // @fixme: but doing (Object)(array)data will re-encode empty [] as {}
+// // @performance: re-encoding is likely faster than stripping comments in PHP twice
+//// this.rawData = FormatJson::decode(
+//// FormatJson::encode(data, FormatJson::ALL_OK), true
+//// );
+// this.data = this.validate(data);
+ }
+
+// /**
+// * Beautifies JSON prior to save.
+// * @param Title $title Title
+// * @param \User $user User
+// * @param \ParserOptions $popts
+// * @return JCContent
+// */
+// public function preSaveTransform(Title $title, \User $user, \ParserOptions $popts) {
+// if (!this.isValidJson()) {
+// return this; // Invalid JSON - can't do anything with it
+// }
+// $formatted = FormatJson::encode(this.getData(), false, FormatJson::ALL_OK);
+// if (this.getNativeData() !== $formatted) {
+// return new static($formatted, this.getModel(), this.thorough());
+// }
+// return this;
+// }
+//
+// protected function fillParserOutput(Title $title, $revId, ParserOptions $options,
+// $generateHtml, ParserOutput &$output) {
+// if (!$generateHtml) {
+// return;
+// }
+//
+// status = this.getStatus();
+// if (!status.isGood()) {
+// // Use user's language, and split parser cache. This should not have a big
+// // impact because data namespace is rarely viewed, but viewing it localized
+// // will be valuable
+// $lang = $options.getUserLangObj();
+// $html = status.getHTML(false, false, $lang);
+// } else {
+// $html = '';
+// }
+//
+// if (status.isOK()) {
+// $html .= this
+// .getView(this.getModel())
+// .valueToHtml(this, $title, $revId, $options, $generateHtml, $output);
+// }
+//
+// $output.setText($html);
+// }
+//
+// /**
+// * Get a view Object for this content Object
+// * @param String $modelId is required here because parent ctor might not have ran yet
+// * @return JCContentView
+// */
+// protected function getView($modelId) {
+// global $wgJsonConfigModels;
+// view = this.view;
+// if (view === null) {
+// $configModels = \ExtensionRegistry::getInstance().getAttribute('JsonConfigModels')
+// + $wgJsonConfigModels;
+// if (array_key_exists($modelId, $configModels)) {
+// $value = $configModels[$modelId];
+// if (is_array($value) && array_key_exists('view', $value)) {
+// $class = $value['view'];
+// view = new $class();
+// }
+// }
+// if (view === null) {
+// view = this.createDefaultView();
+// }
+// this.view = view;
+// }
+// return view;
+// }
+//
+// /**
+// * In case view is not associated with the model for this class, this function will instantiate
+// * a default. Override may instantiate a more appropriate view
+// * @return JCContentView
+// */
+// protected function createDefaultView() {
+// return new JCDefaultContentView();
+// }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCContentHandler.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCContentHandler.java
new file mode 100644
index 000000000..416d30a2c
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCContentHandler.java
@@ -0,0 +1,138 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*; import gplx.xowa.mediawiki.includes.content.*;
+class JCContentHandler extends TextContentHandler { /**
+ * Internal format to force pretty-printed json serialization
+ */
+ private static final String CONTENT_FORMAT_JSON_PRETTY = "application/json+pretty";
+
+ private JCSingleton singleton;
+ /**
+ * @param String $modelId
+ */
+ public void __construct(String modelId, JCSingleton singleton) {
+ super.__construct(modelId, XomwDefines.CONTENT_FORMAT_JSON, CONTENT_FORMAT_JSON_PRETTY);
+ this.singleton = singleton;
+ }
+
+// /**
+// * Returns the content's text as-is.
+// *
+// * @param \Content|JCContent $content This is actually a Content Object
+// * @param String|null $format
+// * @return mixed
+// */
+// public function serializeContent(\Content $content, $format = null) {
+// this.checkFormat($format);
+// $status = $content->getStatus();
+// if ($status->isGood()) {
+// $data = $content->getData(); // There are no errors, normalize data
+// } elseif ($status->isOK()) {
+// $data = $content->getRawData(); // JSON is valid, but the data has errors
+// } else {
+// return $content->getNativeData(); // Invalid JSON - can't do anything with it
+// }
+//
+// return FormatJson::encode($data, $format === self::CONTENT_FORMAT_JSON_PRETTY,
+// FormatJson::ALL_OK);
+// }
+//
+// /**
+// * @param \Content|JCContent $oldContent
+// * @param \Content|JCContent $myContent
+// * @param \Content|JCContent $yourContent
+// * @return boolean|JCContent
+// */
+// public function merge3(\Content $oldContent, \Content $myContent, \Content $yourContent) {
+// // Almost identical clone of the parent's merge3, except that we use pretty-printed merge,
+// // thus allowing much more lenient line-based merging.
+//
+// this.checkModelID($oldContent->getModel());
+// this.checkModelID($myContent->getModel());
+// this.checkModelID($yourContent->getModel());
+//
+// $format = self::CONTENT_FORMAT_JSON_PRETTY;
+//
+// $old = this.serializeContent($oldContent, $format);
+// $mine = this.serializeContent($myContent, $format);
+// $yours = this.serializeContent($yourContent, $format);
+//
+// $ok = wfMerge($old, $mine, $yours, $result);
+//
+// if (!$ok) {
+// return false;
+// }
+//
+// if (!$result) {
+// return this.makeEmptyContent();
+// }
+//
+// $mergedContent = this.unserializeContent($result, $format);
+//
+// return $mergedContent;
+// }
+//
+// /**
+// * Returns the name of the diff engine to use.
+// *
+// * @since 1.21
+// *
+// * @return String
+// */
+// protected function getDiffEngineClass() {
+// return JCJsonDifferenceEngine::class;
+// }
+//
+ /**
+ * Unserializes a JsonSchemaContent Object.
+ *
+ * @param String $text Serialized form of the content
+ * @param null|String $format The format used for serialization
+ * @param boolean $isSaving Perform extra validation
+ * @return JCContent the JsonSchemaContent Object wrapping $text
+ */
+ public JCContent unserializeContent(byte[] text) {return unserializeContent(text, null, true);}
+ public JCContent unserializeContent(byte[] text, String format, boolean isSaving) {
+ this.checkFormat(format);
+ String modelId = this.getModelID();
+ XophpClassBldr factory = singleton.getContentClass(modelId);
+ return (JCContent)factory.Make(text, modelId, isSaving);
+ }
+
+// /**
+// * Returns the name of the associated Content class, to
+// * be used when creating new objects. Override expected
+// * by subclasses.
+// *
+// * @return String
+// */
+// protected function getContentClass() {
+// $modelId = this.getModelID();
+// return JCSingleton::getContentClass($modelId);
+// }
+//
+// /**
+// * Creates an empty JsonSchemaContent Object.
+// *
+// * @return JCContent
+// */
+// public function makeEmptyContent() {
+// // Each model could have its own default JSON value
+// // null notifies that default should be used
+// return this.unserializeContent(null);
+// }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCDataContent.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCDataContent.java
new file mode 100644
index 000000000..998311c4d
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCDataContent.java
@@ -0,0 +1,150 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.xowa.mediawiki.*;
+import gplx.xowa.langs.*; import gplx.xowa.langs.msgs.*;
+public class JCDataContent extends JCObjContent { // /**
+// * Derived classes must implement this method to perform custom validation
+// * using the check(...) calls
+// */
+// public function validateContent() {
+// if (!$this.thorough()) {
+// // We are not doing any modifications to the original, so no need to validate it
+// return;
+// }
+//
+// $this.test('license', JCValidators::isStringLine(), self::isValidLicense());
+// $this.testOptional('description', [ 'en' => '' ], JCValidators::isLocalizedString());
+// $this.testOptional('sources', '', JCValidators::isString());
+// }
+//
+// /** Returns a validator function to check if the value is a valid String
+// * @return callable
+// */
+// public static function isValidLicense() {
+// return function (JCValue $v, array $path) {
+// global $wgJsonConfigAllowedLicenses, $wgLang;
+// if (!in_array($v.getValue(), $wgJsonConfigAllowedLicenses, true)) {
+// $v.error('jsonconfig-err-license', $path,
+// $wgLang.commaList($wgJsonConfigAllowedLicenses));
+// return false;
+// }
+// return true;
+// };
+// }
+
+ /**
+ * Get data as localized for the given language
+ * @param Language $lang
+ * @return mixed
+ */
+ public XophpStdClass getLocalizedData(Xol_lang_itm lang) {
+ if (!this.isValid()) {
+ return null;
+ }
+ XophpStdClass result = new XophpStdClass();
+ this.localizeData(result, lang);
+ return result;
+ }
+
+ /**
+ * Resolve @Override any specific localizations, and add it to $result
+ * @param Object $result
+ * @param Language $lang
+ */
+ @gplx.Virtual protected void localizeData(XophpStdClass result, Xol_lang_itm lang) {
+ XophpStdClass data = this.getData();
+ if (data.Has("description")) {
+ result.Set_by_as_str("description", JCUtils.pickLocalizedString(data.Get_by_as_itm("description"), lang));
+ }
+ XophpStdClass license = this.getLicenseObject();
+ if (license != null) {
+// Xol_msg_itm msg = license.Get_by_as_obj("text");
+// String text = msg.inLanguage($lang).plain();
+// $result.license = (Object)[
+// 'code' => $license['code'],
+// 'text' => $text,
+// 'url' => $license['url'].inLanguage($lang).plain(),
+// ];
+ }
+ if (data.Has("sources")) {
+ result.Set_by_as_itm("sources", data.Get_by_as_itm("sources"));
+ }
+ }
+//
+// public function renderDescription( $lang ) {
+// $description = $this->getField( 'description' );
+//
+// if ( $description && !$description->error() ) {
+// $description = JCUtils::pickLocalizedString( $description->getValue(), $lang );
+// $html = Html::element( 'p', [ 'class' => 'mw-jsonconfig-description' ], $description );
+// } else {
+// $html = '';
+// }
+//
+// return $html;
+// }
+//
+// /**
+// * Renders license HTML, including optional "or later version" clause
+// * Creative Commons 1.0, or later version
+// * @return String
+// */
+// public function renderLicense() {
+// $license = $this->getLicenseObject();
+// if ( $license ) {
+// $text = Html::element( 'a', [
+// 'href' => $license['url']->plain()
+// ], $license['text']->plain() );
+//
+// $text = wfMessage( 'jsonconfig-license' )->rawParams( $text )->parse();
+//
+// $html = Html::rawElement( 'p', [ 'class' => 'mw-jsonconfig-license' ], $text );
+// } else {
+// $html = '';
+// }
+//
+// return $html;
+// }
+
+ private XophpStdClass getLicenseObject() {
+// XophpStdClass license = this.getField("license");
+// if ( $license && !$license->error() ) {
+// $code = $license->getValue();
+//
+// return [
+// 'code' => $code,
+// 'text' => wfMessage( 'jsonconfig-license-name-' . $code ),
+// 'url' => wfMessage( 'jsonconfig-license-url-' . $code ),
+// ];
+// }
+ return null;
+ }
+
+// public function renderSources( Parser $parser, Title $title, $revId, ParserOptions $options ) {
+// $sources = $this->getField( 'sources' );
+//
+// if ( $sources && !$sources->error() ) {
+// $markup = $sources->getValue();
+// $html = Html::rawElement( 'p', [ 'class' => 'mw-jsonconfig-sources' ],
+// $parser->parse( $markup, $title, $options, true, true, $revId )->getRawText() );
+// } else {
+// $html = '';
+// }
+//
+// return $html;
+// }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCObjContent.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCObjContent.java
new file mode 100644
index 000000000..fb149b7a6
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCObjContent.java
@@ -0,0 +1,566 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.xowa.mediawiki.*;
+public class JCObjContent extends JCContent { // /**
+// * @var boolean if false, prevents multiple fields from having identical names that differ
+// * only by casing
+// */
+// protected $isCaseSensitive = false;
+//
+// /** @var boolean if false, ensure the root to be an stdClass, otherwise - an array */
+// protected $isRootArray = false;
+//
+// /**
+// * @var JCValue contains raw validation results. At first it is a parsed JSON value, with the
+// * root element wrapped into JCValue. As validation progresses, all visited values become
+// * wrapped with JCValue.
+// */
+// protected $validationData;
+//
+// /** @var mixed */
+// protected $dataWithDefaults;
+//
+// /** @var boolean|null validation status - null=before, true=during, false=done */
+// protected $isValidating = null;
+//
+// /**
+// * Override default behavior to include defaults if validation succeeded.
+// *
+// * @return String|boolean The raw text, or false if the conversion failed.
+// */
+// public function getWikitextForTransclusion() {
+// if ( !$this->getStatus()->isGood() ) {
+// // If validation failed, return original text
+// return parent::getWikitextForTransclusion();
+// }
+// if ( !$this->thorough() && $this->validationData !== null ) {
+// // ensure that data is sorted in the right order
+// self::markUnchecked( $this->validationData );
+// }
+// return \FormatJson::encode( $this->getDataWithDefaults(), true, \FormatJson::ALL_OK );
+// }
+//
+// protected function createDefaultView() {
+// return new JCDefaultObjContentView();
+// }
+//
+// /**
+// * Get configuration data with custom defaults
+// * @throws \Exception in case validation is not complete
+// * @return mixed
+// */
+// public function getDataWithDefaults() {
+// if ( $this->isValidating !== false ) {
+// throw new Exception( 'This method may only be called after validation is complete' );
+// }
+// if ( $this->dataWithDefaults === null ) {
+// $this->dataWithDefaults = JCUtils::sanitize( $this->validationData );
+// }
+// return $this->dataWithDefaults;
+// }
+//
+// /**
+// * Get status array that recursively describes dataWithDefaults
+// * @throws \Exception
+// * @return JCValue
+// */
+// public function getValidationData() {
+// if ( $this->isValidating === null ) {
+// throw new Exception(
+// 'This method may only be called during or after validation has started'
+// );
+// }
+// return $this->validationData;
+// }
+//
+// /**
+// * Call this function before performing data validation inside the derived validate()
+// * @param array|Object $data
+// * @throws \Exception
+// * @return boolean if true, validation should be performed, otherwise all checks will be ignored
+// */
+// protected function initValidation( $data ) {
+// if ( $this->isValidating !== null ) {
+// throw new Exception( 'This method may only be called before validation has started' );
+// }
+// $this->isValidating = true;
+// if ( !$this->isRootArray && !is_object( $data ) ) {
+// $this->getStatus()->fatal( 'jsonconfig-err-root-Object-expected' );
+// } elseif ( $this->isRootArray && !is_array( $data ) ) {
+// $this->getStatus()->fatal( 'jsonconfig-err-root-array-expected' );
+// } else {
+// $this->validationData = new JCValue( JCValue::UNCHECKED, $data );
+// return true;
+// }
+// return false;
+// }
+//
+// /**
+// * Derived validate() must return the result of this function
+// * @throws \Exception
+// * @return array
+// */
+// protected function finishValidation() {
+// if ( !$this->getStatus()->isGood() ) {
+// return $this->getRawData(); // validation failed, do not modify
+// }
+// return null; // Data will be filter-cloned on demand inside self::getData()
+// }
+//
+// /**
+// * Populate this data on-demand for efficiency
+// * @return array
+// */
+// public function getData() {
+// if ( $this->data === null ) {
+// $this->data = JCUtils::sanitize( $this->validationData, true );
+// }
+// return $this->data;
+// }
+//
+// public function validate( $data ) {
+// if ( $this->initValidation( $data ) ) {
+// $this->validateContent();
+// $data = $this->finishValidation();
+// }
+// if ( $this->thorough() && $this->validationData !== null ) {
+// self::markUnchecked( $this->validationData );
+// }
+// $this->isValidating = false;
+// return $data;
+// }
+//
+// /**
+// * Derived classes must implement this method to perform custom validation
+// * using the test(...) calls
+// */
+// abstract public function validateContent();
+//
+// /**
+// * Use this function to test a value, or if the value is missing, use the default value.
+// * The value will be tested with validator(s) if provided, even if it was the default.
+// * @param String|array $path name of the root field to check, or a path to the field in a nested
+// * structure. Nested path should be in the form of
+// * [ 'field-level1', 'field-level2', ... ]. For example, if client needs to check
+// * validity of the 'value1' in the structure {'key':{'sub-key':['value0','value1']}},
+// * $field should be set to [ 'key', 'sub-key', 1 ].
+// * @param mixed $default value to be used in case field is not found. $default is passed to the
+// * validator if validation fails. If validation of the default passes,
+// * the value is considered optional.
+// * @param callable $validator callback function as defined in JCValidators::run(). More than one
+// * validator may be given. If validators are not provided, any value is accepted
+// * @return boolean true if ok, false otherwise
+// * @throws \Exception if $this->initValidation() was not called.
+// */
+// public function testOptional( $path, $default, $validator = null ) {
+// $vld = self::convertValidators( $validator, func_get_args(), 2 );
+// // first validator will replace missing with the default
+// array_unshift( $vld, JCValidators::useDefault( $default ) );
+// return $this->testInt( $path, $vld );
+// }
+//
+// /**
+// * Use this function to test a field in the data. If missing, the validator(s) will receive
+// * JCMissing singleton as a value, and it will be up to the validator(s) to accept it or not.
+// * @param String|array $path name of the root field to check, or a path to the field in a nested
+// * structure. Nested path should be in the form of
+// * [ 'field-level1', 'field-level2', ... ]. For example, if client needs to check
+// * validity of the 'value1' in the structure {'key':{'sub-key':['value0','value1']}},
+// * $field should be set to [ 'key', 'sub-key', 1 ].
+// * @param callable $validator callback function as defined in JCValidators::run().
+// * More than one validator may be given.
+// * If validators are not provided, any value is accepted
+// * @throws \Exception
+// * @return boolean true if ok, false otherwise
+// */
+// public function test( $path, $validator /*...*/ ) {
+// $vld = self::convertValidators( $validator, func_get_args(), 1 );
+// return $this->testInt( $path, $vld );
+// }
+//
+// /**
+// * Use this function to test all values inside an array or an Object at a given path.
+// * All validators will be called for each of the sub-values. If there is no value
+// * at the given $path, or it is not a container, no action will be taken and no errors reported
+// * @param String|array $path path to the container field in a nested structure.
+// * Nested path should be in the form of [ 'field-level1', 'field-level2', ... ].
+// * For example, if client needs to check validity of the 'value1' in the structure
+// * {'key':{'sub-key':['value0','value1']}},
+// * $field should be set to [ 'key', 'sub-key', 1 ].
+// * @param callable $validator callback function as defined in JCValidators::run().
+// * More than one validator may be given.
+// * If validators are not provided, any value is accepted
+// * @throws \Exception
+// * @return boolean true if all values tested ok, false otherwise
+// */
+// public function testEach( $path, $validator = null /*...*/ ) {
+// $vld = self::convertValidators( $validator, func_get_args(), 1 );
+// $isOk = true;
+// $path = (array)$path;
+// $containerField = $this->getField( $path );
+// if ( $containerField ) {
+// $container = $containerField->getValue();
+// if ( is_array( $container ) || is_object( $container ) ) {
+// $lastIdx = count( $path );
+// if ( is_object( $container ) ) {
+// $container = get_object_vars( $container );
+// }
+// foreach ( array_keys( $container ) as $k ) {
+// $path[$lastIdx] = $k;
+// $isOk &= $this->testInt( $path, $vld );
+// }
+// }
+// }
+// return $isOk;
+// }
+//
+// /**
+// * @param array|String $path
+// * @param array $validators
+// * @return boolean
+// * @throws \Exception
+// */
+// private function testInt( $path, $validators ) {
+// if ( !$this->getStatus()->isOK() ) {
+// return false; // skip all validation in case of a fatal error
+// }
+// if ( $this->isValidating !== true ) {
+// throw new Exception(
+// 'This function should only be called inside the validateContent() override'
+// );
+// }
+// return $this->testRecursive( (array)$path, [], $this->validationData, $validators );
+// }
+//
+// /**
+// * @param array $path
+// * @param array $fldPath For error reporting, path to the current field
+// * @param JCValue $jcv
+// * @param mixed $validators
+// * @throws \Exception
+// * @@gplx.Internal protected param JCValue $status
+// * @return boolean
+// */
+// private function testRecursive( array $path, array $fldPath, JCValue $jcv, $validators ) {
+// // Go recursively through all fields in path until empty, and validate last
+// if ( !$path ) {
+// // keep this branch here since we allow validation of the whole Object ($path==[])
+// return $this->testValue( $fldPath, $jcv, $validators );
+// }
+// $fld = array_shift( $path );
+// if ( is_array( $jcv->getValue() ) && ctype_digit( $fld ) ) {
+// $fld = (int)$fld;
+// }
+// if ( !is_int( $fld ) && !is_string( $fld ) ) {
+// throw new Exception( 'Unexpected field type, only strings and integers are allowed' );
+// }
+// $fldPath[] = $fld;
+//
+// $subJcv = $this->getField( $fld, $jcv );
+// if ( $subJcv === null ) {
+// $msg =
+// is_int( $fld ) && !is_array( $jcv->getValue() ) ? 'jsonconfig-err-array-expected'
+// : 'jsonconfig-err-Object-expected';
+// $this->addValidationError( wfMessage( $msg, JCUtils::fieldPathToString( $fldPath ) ) );
+// return false;
+// }
+//
+// /** @var boolean $reposition - should the field be deleted and re-added at the end
+// * this is only needed for viewing and saving */
+// $reposition = $this->thorough() && is_string( $fld ) && $subJcv !== false;
+// if ( $subJcv === false || $subJcv->isUnchecked() ) {
+// // We never went down this path before
+// // Check that field exists, and is not case-duplicated
+// if ( is_int( $fld ) ) {
+// if ( count( $jcv->getValue() ) < $fld ) {
+// // Allow existing index or index+1 for appending last item
+// throw new Exception( "List index is too large at '" .
+// JCUtils::fieldPathToString( $fldPath ) .
+// "'. Index may not exceed list size." );
+// }
+// } elseif ( !$this->isCaseSensitive ) {
+// // if we didn't find it before, it could have been misnamed
+// $norm = $this->normalizeField( $jcv, $fld, $fldPath );
+// if ( $norm === null ) {
+// return false;
+// } elseif ( $norm ) {
+// $subJcv = $this->getField( $fld, $jcv );
+// $reposition = false; // normalization already does that
+// }
+// }
+// if ( $subJcv === null ) {
+// throw new Exception( 'Logic error - subJcv must be valid here' );
+// } elseif ( $subJcv === false ) {
+// // field does not exist
+// $initValue = !$path ? null : ( is_string( $path[0] ) ? new stdClass() : [] );
+// $subJcv = new JCValue( JCValue::MISSING, $initValue );
+// }
+// }
+// $isOk = $this->testRecursive( $path, $fldPath, $subJcv, $validators );
+//
+// // Always remove and re-append the field
+// if ( $subJcv->isMissing() ) {
+// $jcv->deleteField( $fld );
+// } else {
+// if ( $reposition ) {
+// $jcv->deleteField( $fld );
+// }
+// $jcv->setField( $fld, $subJcv );
+// if ( $jcv->isMissing() || $jcv->isUnchecked() ) {
+// $jcv->status( JCValue::VISITED );
+// }
+// }
+// return $isOk;
+// }
+//
+// /**
+// * @param array $fldPath
+// * @param JCValue $jcv
+// * @param array $validators
+// * @return boolean
+// */
+// private function testValue( array $fldPath, JCValue $jcv, $validators ) {
+// // We have reached the last level of the path, test the actual value
+// if ( $validators !== null ) {
+// $isRequired = $jcv->defaultUsed();
+// JCValidators::run( $validators, $jcv, $fldPath, $this );
+// $err = $jcv->error();
+// if ( $err ) {
+// if ( is_object( $err ) ) {
+// // if ( !$isRequired ) {
+// // // User supplied value, so we don't know if the value is required or not
+// // // if $default passes validation, original value was optional
+// // $isRequired = !JCValidators::run(
+// // $validators, $fldPath, JCValue::getMissing(), $this
+// // );
+// // }
+// $this->addValidationError( $err, !$isRequired );
+// }
+// return false;
+// } elseif ( $jcv->isUnchecked() ) {
+// $jcv->status( JCValue::CHECKED );
+// }
+// }
+// // if ( $this->thorough() && $jcv->status() === JCValue::CHECKED ) {
+// // // Check if the value is the same as default - use a cast to array
+// // // hack to compare objects
+// // $isRequired = (boolean)JCValidators::run( $validators, $fldPath, JCMissing::get(), $this );
+// // if ( ( is_object( $jcv ) && is_object( $default ) && (array)$jcv === (array)$default )
+// // || ( !is_object( $default ) && $jcv === $default )
+// // ) {
+// // $newStatus = JCValue::SAME_AS_DEFAULT;
+// // }
+// // }
+// return true;
+// }
+//
+// /**
+// * Recursively reorder all sub-elements - checked first, followed by unchecked.
+// * Also, convert all sub-elements to JCValue(UNCHECKED) if at least one of them was JCValue
+// * This is useful for HTML rendering to indicate unchecked items
+// * @param JCValue $data
+// */
+// private static function markUnchecked( JCValue $data ) {
+// $val = $data->getValue();
+// $isObject = is_object( $val );
+// if ( !$isObject && !is_array( $val ) ) {
+// return;
+// }
+// $result = null;
+// $firstPass = true;
+// $hasJcv = false;
+// // Two pass loop - first pass moves all checked values to the result,
+// // second pass moves the rest of of the values, possibly converting them to JCValue
+// while ( true ) {
+// foreach ( $val as $key => $subVal ) {
+// /** @var JCValue|mixed $subVal */
+// $isJcv = is_a( $subVal, '\JsonConfig\JCValue' );
+// if ( $firstPass && $isJcv ) {
+// // On the first pass, recursively process subelements if they were visited
+// self::markUnchecked( $subVal );
+// $move = $isObject && !$subVal->isUnchecked();
+// $hasJcv = true;
+// } else {
+// $move = false;
+// }
+// if ( $move || !$firstPass ) {
+// if ( !$isJcv ) {
+// $subVal = new JCValue( JCValue::UNCHECKED, $subVal );
+// }
+// if ( $result === null ) {
+// $result = $isObject ? new stdClass() : [];
+// }
+// if ( $isObject ) {
+// $result->$key = $subVal;
+// unset( $val->$key );
+// } else {
+// // No need to unset - all values in an array are moved in the second pass
+// $result[] = $subVal;
+// }
+// }
+// }
+//
+// if ( ( $result === null && !$hasJcv ) || !$firstPass ) {
+// // either nothing was found, or we are done with the second pass
+// if ( $result !== null ) {
+// $data->setValue( $result );
+// }
+// return;
+// }
+// $firstPass = false;
+// }
+// }
+//
+// /**
+// * @param Message $error
+// * @param boolean $isOptional
+// */
+// public function addValidationError( Message $error, $isOptional = false ) {
+// $text = $error->plain();
+// // @TODO fixme - need to re-enable optional field detection & reporting
+// // if ( $isOptional ) {
+// // $text .= ' ' . wfMessage( 'jsonconfig-optional-field' )->plain();
+// // }
+// $this->getStatus()->error( $text );
+// }
+
+ /** Get field from data Object/array
+ * @param String|int|array $field
+ * @param stdClass|array|JCValue $data
+ * @throws \Exception
+ * @return false|null|JCValue search result:
+ * false if not found
+ * null if error (argument type does not match storage)
+ * JCValue if the value is found
+ */
+ public JCValue getField(int field) {return getFieldWkr(field, null, null, null);}
+ public JCValue getField(int field, Object data) {return getFieldWkr(field, null, null, data);}
+ public JCValue getField(String field) {return getFieldWkr(-1, field, null, null);}
+ public JCValue getField(String field, Object data) {return getFieldWkr(-1, field, null, data);}
+ public JCValue getField(String[] fields) {return getFieldWkr(-1, null, fields, null);}
+ public JCValue getField(String[] fields, Object data) {return getFieldWkr(-1, null, fields, data);}
+ public JCValue getFieldWkr(int fldInt, String fldStr, String[] fldAry, Object data) {
+ if (data == null) {
+// data = this.getValidationData();
+ }
+ if (fldAry == null) {
+ data = getFieldByItem(fldInt, fldStr, data);
+ if (data == null)
+ return null;
+ }
+ else {
+ for (String fld : fldAry) {
+ data = getFieldByItem(-1, fld, data);
+ if (data == null)
+ return null;
+ }
+ }
+ if (Type_.Eq_by_obj(data, JCValue.class)) {
+ return (JCValue)data;
+ } else {
+// return new JCValue(JCValue.UNCHECKED, data);
+ return null;
+ }
+ }
+ private Object getFieldByItem(int fldInt, String fldStr, Object data) {
+ if (fldInt == -1 && fldStr == null) {
+ throw Err_.new_wo_type("Field must be either int or String");
+ }
+
+ if (Type_.Eq_by_obj(data, JCValue.class)) {
+ data = ((JCValue)data).getValue();
+ }
+ int typeId = XomwTypeUtl.To_type_id(data);
+ boolean isObject = typeId == Type_ids_.Id__obj;
+ boolean isArray = typeId == Type_ids_.Id__array;
+ if (fldStr != null ? !(isObject || isArray) : !isArray) {
+ return null;
+ }
+
+ if (isObject) {
+ XophpStdClass dataAsMap = (XophpStdClass)data;
+ return dataAsMap.Get_by_as_itm(fldStr);
+ } else if (isArray) {
+ Object dataAsAry = Array_.cast(data);
+ if (fldInt < Array_.Len(dataAsAry))
+ return Array_.Get_at(dataAsAry, fldInt);
+ }
+ return null;
+ }
+
+// /**
+// * @param JCValue $jcv
+// * @param int|String $fld
+// * @param array $fldPath
+// * @throws \Exception
+// * @return boolean|null true if renamed, false if not found or original unchanged,
+// * null if duplicate (error)
+// */
+// private function normalizeField( JCValue $jcv, $fld, array $fldPath ) {
+// $valueRef = $jcv->getValue();
+// $foundFld = false;
+// $isError = false;
+// foreach ( $valueRef as $k => $v ) {
+// if ( 0 === strcasecmp( $k, $fld ) ) {
+// if ( $foundFld !== false ) {
+// $isError = true;
+// break;
+// }
+// $foundFld = $k;
+// }
+// }
+// if ( $isError ) {
+// $this->addValidationError( wfMessage( 'jsonconfig-duplicate-field',
+// JCUtils::fieldPathToString( $fldPath ) ) );
+// if ( $this->thorough() ) {
+// // Mark all duplicate fields as errors
+// foreach ( $valueRef as $k => $v ) {
+// if ( 0 === strcasecmp( $k, $fld ) ) {
+// if ( !is_a( $v, '\JsonConfig\JCValue' ) ) {
+// $v = new JCValue( JCValue::UNCHECKED, $v );
+// $jcv->setField( $k, $v );
+// }
+// $v->error( true );
+// }
+// }
+// }
+// return null;
+// } elseif ( $foundFld !== false && $foundFld !== $fld ) {
+// // key had different casing, rename it to canonical
+// $jcv->setField( $fld, $jcv->deleteField( $foundFld ) );
+// return true;
+// }
+// return false;
+// }
+//
+// /**
+// * @param null|callable|array $param first validator parameter
+// * @param array $funcArgs result of func_get_args() call
+// * @param int $skipArgs how many non-validator arguments to remove
+// * from the beginning of the $funcArgs
+// * @return array of validators
+// */
+// private static function convertValidators( $param, $funcArgs, $skipArgs ) {
+// if ( $param === null ) {
+// return []; // no validators given
+// } elseif ( is_array( $param ) && !is_callable( $param, true ) ) {
+// return $param; // first argument is an array of validators
+// } else {
+// return array_slice( $funcArgs, $skipArgs ); // remove fixed params from the beginning
+// }
+// }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCSingleton.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCSingleton.java
new file mode 100644
index 000000000..a1f1fc388
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCSingleton.java
@@ -0,0 +1,995 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
+public class JCSingleton {
+// /**
+// * @var array describes how a title should be handled by JsonConfig extension.
+// * The structure is an array of array of ...:
+// * { int_namespace => { name => { allows-sub-namespaces => configuration_array } } }
+// */
+// public static $titleMap = [];
+//
+// /**
+// * @var String[]|false[] containing all the namespaces handled by JsonConfig
+// * Maps namespace id (int) => namespace name (String).
+// * If false, presumes the namespace has been registered by core or another extension
+// */
+// public static $namespaces = [];
+//
+// /**
+// * @var MapCacheLRU[] contains a cache of recently resolved JCTitle's
+// * as namespace => MapCacheLRU
+// */
+// public static $titleMapCacheLru = [];
+
+ /**
+ * @var MapCacheLRU[] contains a cache of recently requested content objects
+ * as namespace => MapCacheLRU
+ */
+ private final Ordered_hash mapCacheLru = Ordered_hash_.New();
+
+ public Xomw_page_fetcher Store() {return store;} public void Store_(Xomw_page_fetcher v) {this.store = v;} private Xomw_page_fetcher store;
+ public Xophp_ary ConfigModels() {return configModels;} private final Xophp_ary configModels = new Xophp_ary();
+// /**
+// * @var TitleParser cached invariant title parser
+// */
+// public static $titleParser;
+//
+// /**
+// * Initializes singleton state by parsing $wgJsonConfig* values
+// * @throws Exception
+// */
+// private static function init() {
+// static $isInitialized = false;
+// if ($isInitialized) {
+// return;
+// }
+// $isInitialized = true;
+// global $wgNamespaceContentModels, $wgContentHandlers, $wgJsonConfigs, $wgJsonConfigModels;
+// list(self::$titleMap, self::$namespaces) = self::parseConfiguration(
+// $wgNamespaceContentModels,
+// $wgContentHandlers,
+// array_replace_recursive(
+// \ExtensionRegistry::getInstance()->getAttribute('JsonConfigs'), $wgJsonConfigs
+// ),
+// array_replace_recursive(
+// \ExtensionRegistry::getInstance()->getAttribute('JsonConfigModels'),
+// $wgJsonConfigModels
+// )
+// );
+// }
+//
+// /**
+// * @param array $namespaceContentModels $wgNamespaceContentModels
+// * @param array $contentHandlers $wgContentHandlers
+// * @param array $configs $wgJsonConfigs
+// * @param array $models $wgJsonConfigModels
+// * @param boolean $warn if true, calls wfLogWarning() for all errors
+// * @return array [ $titleMap, $namespaces ]
+// */
+// public static function parseConfiguration(
+// array $namespaceContentModels, array $contentHandlers,
+// array $configs, array $models, $warn = true
+// ) {
+// $defaultModelId = 'JsonConfig';
+// // @codingStandardsIgnoreStart - T154789
+// $warnFunc = $warn ? 'wfLogWarning' : function() {};
+// // @codingStandardsIgnoreEnd
+//
+// $namespaces = [];
+// $titleMap = [];
+// foreach ($configs as $confId => &$conf) {
+// if (!is_string($confId)) {
+// $warnFunc(
+// "JsonConfig: Invalid \$wgJsonConfigs['$confId'], the key must be a String"
+// );
+// continue;
+// }
+// if (null == self::getConfObject($warnFunc, $conf, $confId)) {
+// continue; // warned inside the function
+// }
+//
+// $modelId = property_exists($conf, 'model')
+// ? ($conf->model ? : $defaultModelId) : $confId;
+// if (!array_key_exists($modelId, $models)) {
+// if ($modelId == $defaultModelId) {
+// $models[$defaultModelId] = null;
+// } else {
+// $warnFunc("JsonConfig: Invalid \$wgJsonConfigs['$confId']: " .
+// "Model '$modelId' is not defined in \$wgJsonConfigModels");
+// continue;
+// }
+// }
+// if (array_key_exists($modelId, $contentHandlers)) {
+// $warnFunc("JsonConfig: Invalid \$wgJsonConfigs['$confId']: Model '$modelId' is " .
+// "already registered in \$contentHandlers to {$contentHandlers[$modelId]}");
+// continue;
+// }
+// $conf->model = $modelId;
+//
+// $ns = self::getConfVal($conf, 'namespace', NS_CONFIG);
+// if (!is_int($ns) || $ns % 2 !== 0) {
+// $warnFunc("JsonConfig: Invalid \$wgJsonConfigs['$confId']: " .
+// "Namespace $ns should be an even number");
+// continue;
+// }
+// // Even though we might be able to override default content model for namespace,
+// // lets keep things clean
+// if (array_key_exists($ns, $namespaceContentModels)) {
+// $warnFunc("JsonConfig: Invalid \$wgJsonConfigs['$confId']: Namespace $ns is " .
+// "already set to handle model '$namespaceContentModels[$ns]'");
+// continue;
+// }
+//
+// // nsName & nsTalk are handled later
+// self::getConfVal($conf, 'pattern', '');
+// self::getConfVal($conf, 'cacheExp', 24 * 60 * 60);
+// self::getConfVal($conf, 'cacheKey', '');
+// self::getConfVal($conf, 'flaggedRevs', false);
+// self::getConfVal($conf, 'license', false);
+// $islocal = self::getConfVal($conf, 'isLocal', true);
+//
+// // Decide if matching configs should be stored on this wiki
+// $storeHere = $islocal || property_exists($conf, 'store');
+// if (!$storeHere) {
+// // 'store' does not exist, use it as a flag to indicate remote storage
+// $conf->store = false;
+// $remote = self::getConfObject($warnFunc, $conf, 'remote', $confId, 'url');
+// if (null == $remote) {
+// continue; // warned inside the function
+// }
+// if (self::getConfVal($remote, 'url', '') == '') {
+// $warnFunc("JsonConfig: Invalid \$wgJsonConfigs['$confId']['remote']['url']: " .
+// "API URL is not set, and this config is not being stored locally");
+// continue;
+// }
+// self::getConfVal($remote, 'username', '');
+// self::getConfVal($remote, 'password', '');
+// } else {
+// if (property_exists($conf, 'remote')) {
+// // non-fatal -- simply ignore the 'remote' setting
+// $warnFunc("JsonConfig: In \$wgJsonConfigs['$confId']['remote'] is set for " .
+// "the config that will be stored on this wiki. " .
+// "'remote' parameter will be ignored."
+// );
+// }
+// $conf->remote = null;
+// $store = self::getConfObject($warnFunc, $conf, 'store', $confId);
+// if (null == $store) {
+// continue; // warned inside the function
+// }
+// self::getConfVal($store, 'cacheNewValue', true);
+// self::getConfVal($store, 'notifyUrl', '');
+// self::getConfVal($store, 'notifyUsername', '');
+// self::getConfVal($store, 'notifyPassword', '');
+// }
+//
+// // Too lazy to write proper error messages for all parameters.
+// if ((isset($conf->nsTalk) && !is_string($conf->nsTalk)) ||
+// !is_string($conf->pattern) ||
+// !is_bool($islocal) || !is_int($conf->cacheExp) || !is_string($conf->cacheKey)
+// || !is_bool($conf->flaggedRevs)
+// ) {
+// $warnFunc("JsonConfig: Invalid type of one of the parameters in " .
+// "\$wgJsonConfigs['$confId'], please check documentation");
+// continue;
+// }
+// if (isset($remote)) {
+// if (!is_string($remote->url) || !is_string($remote->username) ||
+// !is_string($remote->password)
+// ) {
+// $warnFunc("JsonConfig: Invalid type of one of the parameters in " .
+// "\$wgJsonConfigs['$confId']['remote'], please check documentation");
+// continue;
+// }
+// }
+// if (isset($store)) {
+// if (!is_bool($store->cacheNewValue) || !is_string($store->notifyUrl) ||
+// !is_string($store->notifyUsername) || !is_string($store->notifyPassword)
+// ) {
+// $warnFunc("JsonConfig: Invalid type of one of the parameters in " .
+// " \$wgJsonConfigs['$confId']['store'], please check documentation");
+// continue;
+// }
+// }
+// if ($storeHere) {
+// // If nsName is given, add it to the list, together with the talk page
+// // Otherwise, create a placeholder for it
+// if (property_exists($conf, 'nsName')) {
+// if ($conf->nsName == false) {
+// // Non JC-specific namespace, don't register it
+// if (!array_key_exists($ns, $namespaces)) {
+// $namespaces[$ns] = false;
+// }
+// } elseif ($ns == NS_CONFIG) {
+// $warnFunc("JsonConfig: Parameter 'nsName' in \$wgJsonConfigs['$confId'] " .
+// "is not supported for namespace == NS_CONFIG ($ns)");
+// } else {
+// $nsName = $conf->nsName;
+// $nsTalk = isset($conf->nsTalk) ? $conf->nsTalk : ($nsName . '_talk');
+// if (!is_string($nsName) || $nsName == '') {
+// $warnFunc("JsonConfig: Invalid \$wgJsonConfigs['$confId']: " .
+// "if given, nsName must be a String");
+// continue;
+// } elseif (array_key_exists($ns, $namespaces) &&
+// $namespaces[$ns] !== null
+// ) {
+// if ($namespaces[$ns] !== $nsName ||
+// $namespaces[$ns + 1] !== $nsTalk
+// ) {
+// $warnFunc("JsonConfig: \$wgJsonConfigs['$confId'] - " .
+// "nsName has already been set for namespace $ns");
+// }
+// } else {
+// $namespaces[$ns] = $nsName;
+// $namespaces[$ns + 1] =
+// isset($conf->nsTalk) ? $conf->nsTalk : ($nsName . '_talk');
+// }
+// }
+// } elseif (!array_key_exists($ns, $namespaces) || $namespaces[$ns] == false) {
+// $namespaces[$ns] = null;
+// }
+// }
+//
+// if (!array_key_exists($ns, $titleMap)) {
+// $titleMap[$ns] = [ $conf ];
+// } else {
+// $titleMap[$ns][] = $conf;
+// }
+// }
+//
+// // Add all undeclared namespaces
+// $missingNs = 1;
+// foreach ($namespaces as $ns => $nsName) {
+// if ($nsName == null) {
+// $nsName = 'Config';
+// if ($ns !== NS_CONFIG) {
+// $nsName .= $missingNs;
+// $warnFunc(
+// "JsonConfig: Namespace $ns does not have 'nsName' defined, using '$nsName'"
+// );
+// $missingNs += 1;
+// }
+// $namespaces[$ns] = $nsName;
+// $namespaces[$ns + 1] = $nsName . '_talk';
+// }
+// }
+//
+// return [ $titleMap, $namespaces ];
+// }
+//
+// /**
+// * Helper function to check if configuration has a field set, and if not, set it to default
+// * @param stdClass $conf
+// * @param String $field
+// * @param mixed $default
+// * @return mixed
+// */
+// private static function getConfVal(& $conf, $field, $default) {
+// if (property_exists($conf, $field)) {
+// return $conf->$field;
+// }
+// $conf->$field = $default;
+// return $default;
+// }
+//
+// /**
+// * Helper function to check if configuration has a field set, and if not, set it to default
+// * @param $warnFunc
+// * @param $value
+// * @param String $field
+// * @param String $confId
+// * @param String $treatAsField
+// * @return null|Object|stdClass
+// */
+// private static function getConfObject(
+// $warnFunc, & $value, $field, $confId = null, $treatAsField = null
+// ) {
+// if (!$confId) {
+// $val = & $value;
+// } else {
+// if (!property_exists($value, $field)) {
+// $value->$field = null;
+// }
+// $val = & $value->$field;
+// }
+// if ($val == null || $val == true) {
+// $val = new stdClass();
+// } elseif (is_array($val)) {
+// $val = (Object)$val;
+// } elseif (is_string($val) && $treatAsField !== null) {
+// // treating this String value as a sub-field
+// $val = (Object)[ $treatAsField => $val ];
+// } elseif (!is_object($val)) {
+// $warnFunc("JsonConfig: Invalid \$wgJsonConfigs" . ($confId ? "['$confId']" : "") .
+// "['$field'], the value must be either an array or an Object");
+// return null;
+// }
+// return $val;
+// }
+
+ /**
+ * Get content Object from the local LRU cache, or null if doesn't exist
+ * @param TitleValue $titleValue
+ * @return null|JCContent
+ */
+ public JCContent getContentFromLocalCache(String wiki, String ns, String page) {
+ // Some of the titleValues are remote, and their namespace might not be declared
+ // in the current wiki. Since TitleValue is a content Object, it does not validate
+ // the existence of namespace, hence we use it as a simple storage.
+ // Producing an artificial String key by appending (namespaceID . ':' . titleDbKey)
+ // seems wasteful and redundant, plus most of the time there will be just a single
+ // namespace declared, so this structure seems efficient and easy enough.
+ String mapCacheLruKey = String_.Concat(wiki, "|", ns);
+ Ordered_hash cache = (Ordered_hash)mapCacheLru.Get_by(mapCacheLruKey);
+ if (cache == null) {
+ cache = Ordered_hash_.New();
+ mapCacheLru.Add(mapCacheLruKey, cache);
+ }
+
+ return (JCContent)cache.Get_by(page);
+ }
+
+ /**
+ * Get content Object for the given title.
+ * Namespace ID does not need to be defined in the current wiki,
+ * as long as it is defined in $wgJsonConfigs.
+ * @param TitleValue|JCTitle $titleValue
+ * @return boolean|JCContent Returns false if the title is not handled by the settings
+ */
+ public JCContent getContent(String wiki, String ns, String page) {
+ JCContent content = getContentFromLocalCache(wiki, ns, page);
+
+ if (content == null) {
+ byte[] content_bry = store.Get_wtxt(Bry_.new_u8(wiki), Bry_.new_u8(page));
+ if (content_bry != null) {
+ JCContentHandler handler = new JCContentHandler();
+ handler.__construct(JCTabularContent.Model_id, this);
+ content = handler.unserializeContent(content_bry, null, false);
+ }
+// $jct = self::parseTitle($titleValue);
+// if ($jct) {
+// $store = new JCCache($jct);
+// $content = $store->get();
+// if (is_string($content)) {
+// // Convert String to the content Object if needed
+// $handler = new JCContentHandler($jct->getConfig()->model);
+// $content = $handler->unserializeContent($content, null, false);
+// }
+// } else {
+// $content = false;
+// }
+// self::mapCacheLru[$titleValue->getNamespace()]
+// ->set($titleValue->getDBkey(), $content);
+ }
+
+ return content;
+ }
+
+// /**
+// * Parse json text into a content Object for the given title.
+// * Namespace ID does not need to be defined in the current wiki,
+// * as long as it is defined in $wgJsonConfigs.
+// * @param TitleValue $titleValue
+// * @param String $jsonText json content
+// * @param boolean $isSaving if true, performs extensive validation during unserialization
+// * @return boolean|JCContent Returns false if the title is not handled by the settings
+// * @throws Exception
+// */
+// public static function parseContent(TitleValue $titleValue, $jsonText, $isSaving = false) {
+// $jct = self::parseTitle($titleValue);
+// if ($jct) {
+// $handler = new JCContentHandler($jct->getConfig()->model);
+// return $handler->unserializeContent($jsonText, null, $isSaving);
+// }
+//
+// return false;
+// }
+//
+// /**
+// * Mostly for debugging purposes, this function returns initialized @gplx.Internal protected JsonConfig settings
+// * @return array[] map of namespaceIDs to list of configurations
+// */
+// public static function getTitleMap() {
+// self::init();
+// return self::$titleMap;
+// }
+
+ /**
+ * Get the name of the class for a given content model
+ * @param String $modelId
+ * @return null|String
+ */
+ public XophpClassBldr getContentClass(String modelId) {
+// global $wgJsonConfigModels;
+// $configModels = array_replace_recursive(
+// \ExtensionRegistry::getInstance()->getAttribute('JsonConfigModels'),
+// $wgJsonConfigModels
+// );
+ String clz = null;
+ if (configModels.Has(modelId)) {
+ Object val = configModels.Get(modelId);
+ if (Type_.Type_by_obj(val) == Xophp_ary.class) {
+ Xophp_ary val_as_ary = (Xophp_ary)val;
+ if (val_as_ary.Has("class")) {
+ Gfo_usr_dlg_.Instance.Warn_many("", "", "JsonConfig: Invalid +$wgJsonConfigModels['modelId'] array " + "value, 'cl"+ "ass' not found");
+ } else {
+ clz = (String)val_as_ary.Get("class");
+ }
+ } else {
+ clz = (String)val;
+ }
+ }
+ if (clz == null) {
+ clz = "JCContent"; // __NAMESPACE__ . '\JCContent';
+ }
+ return XophpEnv.Instance.ClassBldrs().Get_by_or_null(clz);
+ }
+
+// /**
+// * Given a title (either a user-given String, or as an Object), return JCTitle
+// * @param Title|TitleValue|String $value
+// * @param int|null $namespace Only used when title is a String
+// * @return JCTitle|null|false false if unrecognized namespace,
+// * and null if namespace is handled but does not match this title
+// * @throws Exception
+// */
+// public static function parseTitle($value, $namespace = null) {
+// if ($value == null || $value == '' || $value == false) {
+// // In some weird cases $value is null
+// return false;
+// } elseif ($value instanceof JCTitle) {
+// // Nothing to do
+// return $value;
+// } elseif ($namespace !== null && !is_integer($namespace)) {
+// throw new Exception('$namespace parameter must be either null or an integer');
+// }
+//
+// // figure out the namespace ID (int) - we don't need to parse the String if ns is unknown
+// if ($value instanceof LinkTarget) {
+// if ($namespace == null) {
+// $namespace = $value->getNamespace();
+// }
+// } elseif (is_string($value)) {
+// if ($namespace == null) {
+// throw new Exception('$namespace parameter is missing for String $value');
+// }
+// } else {
+// wfLogWarning('Unexpected title param type ' . gettype($value));
+// return false;
+// }
+//
+// // Search title map for the matching configuration
+// $map = self::getTitleMap();
+// if (array_key_exists($namespace, $map)) {
+// // Get appropriate LRU cache Object
+// if (!array_key_exists($namespace, self::$titleMapCacheLru)) {
+// self::$titleMapCacheLru[$namespace] = $cache = new MapCacheLRU(20);
+// } else {
+// $cache = self::$titleMapCacheLru[$namespace];
+// }
+//
+// // Parse String if needed
+// // TODO: should the String parsing also be cached?
+// if (is_string($value)) {
+// $language = Language::factory('en');
+// if (!self::$titleParser) {
+// self::$titleParser =
+// new MediaWikiTitleCodec(
+// $language,
+// new GenderCache(),
+// [],
+// new FauxInterwikiLookup());
+// }
+// // Interwiki prefixes are a special case for title parsing:
+// // first letter is not capitalized, namespaces are not resolved, etc.
+// // So we prepend an interwiki prefix to fool title codec, and later remove it.
+// try {
+// $value = FauxInterwikiLookup::INTERWIKI_PREFIX . ':' . $value;
+// $parts = self::$titleParser->splitTitleString($value);
+//
+// // Defensive coding - ensure the parsing has proceeded as expected
+// if ($parts['dbkey'] == '' || $parts['namespace'] !== 0 ||
+// $parts['fragment'] !== '' || $parts['local_interwiki'] !== false ||
+// $parts['interwiki'] !== FauxInterwikiLookup::INTERWIKI_PREFIX
+// ) {
+// return null;
+// }
+// } catch (MalformedTitleException $e) {
+// return null;
+// }
+//
+// // At this point, only support wiki namespaces that capitalize title's first char,
+// // but do not enable sub-pages.
+// // This way data can already be stored on Mediawiki namespace everywhere, or
+// // places like commons and zerowiki.
+// // Another implicit limitation: there might be an issue if data is stored on a wiki
+// // with the non-default ucfirst(), e.g. az, kaa, kk, tr -- they convert "i" to "I"
+// $dbKey = $language->ucfirst($parts['dbkey']);
+// } else {
+// $dbKey = $value->getDBkey();
+// }
+//
+// // A bit weird here: cache will store JCTitle objects or false if the namespace
+// // is known to JsonConfig but the dbkey does not match. But in case the title is not
+// // handled, this function returns null instead of false if the namespace is known,
+// // and false otherwise
+// $result = $cache->get($dbKey);
+// if ($result == null) {
+// $result = false;
+// foreach ($map[$namespace] as $conf) {
+// $re = $conf->pattern;
+// if (!$re || preg_match($re, $dbKey)) {
+// $result = new JCTitle($namespace, $dbKey, $conf);
+// break;
+// }
+// }
+//
+// $cache->set($dbKey, $result);
+// }
+//
+// // return null if the given namespace is mentioned in the config,
+// // but title doesn't match
+// return $result ?: null;
+//
+// } else {
+// // return false if JC doesn't know anything about this namespace
+// return false;
+// }
+// }
+//
+// /**
+// * Returns an array with settings if the $titleValue Object is handled by the JsonConfig
+// * extension, false if unrecognized namespace,
+// * and null if namespace is handled but not this title
+// * @param TitleValue $titleValue
+// * @return stdClass|false|null
+// * @deprecated use JCSingleton::parseTitle() instead
+// */
+// public static function getMetadata($titleValue) {
+// $jct = self::parseTitle($titleValue);
+// return $jct ? $jct->getConfig() : $jct;
+// }
+//
+// /**
+// * Only register NS_CONFIG if running on the MediaWiki instance which houses
+// * the JSON configs (i.e. META)
+// * @TODO FIXME: Always return true
+// * @param array &$namespaces
+// * @return true|void
+// */
+// public static function onCanonicalNamespaces(array &$namespaces) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// self::init();
+// foreach (self::$namespaces as $ns => $name) {
+// if ($name == false) { // must be already declared
+// if (!array_key_exists($ns, $namespaces)) {
+// wfLogWarning("JsonConfig: Invalid \$wgJsonConfigs: Namespace $ns " .
+// "has not been declared by core or other extensions");
+// }
+// } elseif (array_key_exists($ns, $namespaces)) {
+// wfLogWarning("JsonConfig: Invalid \$wgJsonConfigs: Namespace $ns => '$name' " .
+// "is already declared as '$namespaces[$ns]'");
+// } else {
+// $key = array_search($name, $namespaces);
+// if ($key !== false) {
+// wfLogWarning("JsonConfig: Invalid \$wgJsonConfigs: Namespace $ns => '$name' " .
+// "has identical name with the namespace #$key");
+// } else {
+// $namespaces[$ns] = $name;
+// }
+// }
+// }
+// }
+//
+// /**
+// * Initialize state
+// * @param Title $title
+// * @param String &$modelId
+// * @return boolean
+// */
+// public static function onContentHandlerDefaultModelFor($title, &$modelId) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// $jct = self::parseTitle($title);
+// if ($jct) {
+// $modelId = $jct->getConfig()->model;
+// return false;
+// }
+// return true;
+// }
+//
+// /**
+// * Instantiate JCContentHandler if we can handle this modelId
+// * @param String $modelId
+// * @param \ContentHandler &$handler
+// * @return boolean
+// */
+// public static function onContentHandlerForModelID($modelId, &$handler) {
+// global $wgJsonConfigModels;
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// self::init();
+// $models = array_replace_recursive(
+// \ExtensionRegistry::getInstance()->getAttribute('JsonConfigModels'),
+// $wgJsonConfigModels
+// );
+// if (array_key_exists($modelId, $models)) {
+// // This is one of our model IDs
+// $handler = new JCContentHandler($modelId);
+// return false;
+// }
+// return true;
+// }
+//
+// /**
+// * CustomEditor hook handler
+// * @see https://www.mediawiki.org/wiki/Manual:Hooks/CustomEditor
+// *
+// * @param Article $article
+// * @param User $user
+// * @return boolean
+// */
+// public static function onCustomEditor($article, $user) {
+// if (!$article || !self::jsonConfigIsStorage()) {
+// return true;
+// }
+// $jct = self::parseTitle($article->getTitle());
+// if (!$jct) {
+// return true;
+// }
+//
+// $editor = new \EditPage($article);
+// $editor->contentFormat = JCContentHandler::CONTENT_FORMAT_JSON_PRETTY;
+// $editor->edit();
+//
+// return false;
+// }
+//
+// /**
+// * Declares JSON as the code editor language for Config: pages.
+// * This hook only runs if the CodeEditor extension is enabled.
+// * @param Title $title
+// * @param String &$lang Page language.
+// * @return boolean
+// */
+// public static function onCodeEditorGetPageLanguage($title, &$lang) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// // todo/fixme? We should probably add 'json' lang to only those pages that pass parseTitle()
+// $handler = ContentHandler::getForModelID($title->getContentModel());
+// if ($handler->getDefaultFormat() == CONTENT_FORMAT_JSON || self::parseTitle($title)) {
+// $lang = 'json';
+// }
+// return true;
+// }
+//
+// /**
+// * Validates that the revised contents are valid JSON.
+// * If not valid, rejects edit with error message.
+// * @param \IContextSource $context
+// * @param JCContent $content
+// * @param \Status $status
+// * @param String $summary Edit summary provided for edit.
+// * @param \User $user
+// * @param boolean $minoredit
+// * @return boolean
+// */
+// public static function onEditFilterMergedContent(
+// /** @noinspection PhpUnusedParameterInspection */
+// $context, $content, $status, $summary, $user, $minoredit
+// ) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// if (is_a($content, 'JsonConfig\JCContent')) {
+// $status->merge($content->getStatus());
+// if (!$status->isGood()) {
+// $status->setResult(false, $status->getValue());
+// }
+// }
+// return true;
+// }
+//
+// /**
+// * Override a per-page specific edit page copyright warning
+// *
+// * @param Title $title
+// * @param String[] &$msg
+// *
+// * @return boolean
+// */
+// public static function onEditPageCopyrightWarning($title, &$msg) {
+// if (self::jsonConfigIsStorage()) {
+// $jct = self::parseTitle($title);
+// if ($jct) {
+// $code = $jct->getConfig()->license;
+// if ($code) {
+// $msg = [ 'jsonconfig-license-copyrightwarning-' . $code ];
+// return false; // Do not allow any other hook handler to override this
+// }
+// }
+// }
+// return true;
+// }
+//
+// /**
+// * Display a page-specific edit notice
+// *
+// * @param Title $title
+// * @param int $oldid
+// * @param array &$notices
+// * @return boolean
+// */
+// public static function onTitleGetEditNotices(Title $title, $oldid, array &$notices) {
+// if (self::jsonConfigIsStorage()) {
+// $jct = self::parseTitle($title);
+// if ($jct) {
+// $code = $jct->getConfig()->license;
+// if ($code) {
+// $noticeText = wfMessage('jsonconfig-license-notice-' . $code)->parse();
+// $notices['jsonconfig'] =
+// wfMessage('jsonconfig-license-notice-box-' . $code)
+// ->rawParams($noticeText)
+// ->parseAsBlock();
+// }
+// }
+// }
+// return true;
+// }
+//
+// /**
+// * Override with per-page specific copyright message
+// *
+// * @param Title $title
+// * @param String $type
+// * @param String &$msg
+// * @param String &$link
+// *
+// * @return boolean
+// */
+// public static function onSkinCopyrightFooter($title, $type, &$msg, &$link) {
+// if (self::jsonConfigIsStorage()) {
+// $jct = self::parseTitle($title);
+// if ($jct) {
+// $code = $jct->getConfig()->license;
+// if ($code) {
+// $msg = 'jsonconfig-license';
+// $link = Html::element('a', [
+// 'href' => wfMessage('jsonconfig-license-url-' . $code)->plain()
+// ], wfMessage('jsonconfig-license-name-' . $code)->plain());
+// return false;
+// }
+// }
+// }
+// return true;
+// }
+//
+// /**
+// * Adds CSS for pretty-printing configuration on NS_CONFIG pages.
+// * @param \OutputPage &$out
+// * @param \Skin &$skin
+// * @return boolean
+// */
+// public static function onBeforePageDisplay(
+// /** @noinspection PhpUnusedParameterInspection */ &$out, &$skin
+// ) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// $title = $out->getTitle();
+// // todo/fixme? We should probably add ext.jsonConfig style to only those pages
+// // that pass parseTitle()
+// $handler = ContentHandler::getForModelID($title->getContentModel());
+// if ($handler->getDefaultFormat() == CONTENT_FORMAT_JSON ||
+// self::parseTitle($title)
+// ) {
+// $out->addModuleStyles('ext.jsonConfig');
+// }
+// return true;
+// }
+//
+// public static function onMovePageIsValidMove(
+// Title $oldTitle, Title $newTitle, Status $status
+// ) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// $jctOld = self::parseTitle($oldTitle);
+// if ($jctOld) {
+// $jctNew = self::parseTitle($newTitle);
+// if (!$jctNew) {
+// $status->fatal('jsonconfig-move-aborted-ns');
+// return false;
+// } elseif ($jctOld->getConfig()->model !== $jctNew->getConfig()->model) {
+// $status->fatal('jsonconfig-move-aborted-model', $jctOld->getConfig()->model,
+// $jctNew->getConfig()->model);
+// return false;
+// }
+// }
+//
+// return true;
+// }
+//
+// public static function onAbortMove(
+// /** @noinspection PhpUnusedParameterInspection */
+// Title $title, Title $newTitle, $wgUser, &$err, $reason
+// ) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// $status = new \Status();
+// self::onMovePageIsValidMove($title, $newTitle, $status);
+// if (!$status->isOK()) {
+// $err = $status->getHTML();
+// return false;
+// }
+//
+// return true;
+// }
+//
+// /**
+// * Conditionally load API module 'jsondata' depending on whether or not
+// * this wiki stores any jsonconfig data
+// *
+// * @param ApiModuleManager $moduleManager Module manager instance
+// * @return boolean
+// */
+// public static function onApiMainModuleManager(ApiModuleManager $moduleManager) {
+// global $wgJsonConfigEnableLuaSupport;
+// if ($wgJsonConfigEnableLuaSupport) {
+// $moduleManager->addModule('jsondata', 'action', 'JsonConfig\\JCDataApi');
+// }
+// return true;
+// }
+//
+// public static function onPageContentSaveComplete(
+// /** @noinspection PhpUnusedParameterInspection */
+// \WikiPage $wikiPage, $user, $content, $summary, $isMinor, $isWatch,
+// $section, $flags, $revision, $status, $baseRevId
+// ) {
+// return self::onArticleChangeComplete($wikiPage, $content);
+// }
+//
+// public static function onArticleDeleteComplete(
+// /** @noinspection PhpUnusedParameterInspection */
+// $article, &$user, $reason, $id, $content, $logEntry
+// ) {
+// return self::onArticleChangeComplete($article);
+// }
+//
+// public static function onArticleUndelete(
+// /** @noinspection PhpUnusedParameterInspection */
+// $title, $created, $comment, $oldPageId
+// ) {
+// return self::onArticleChangeComplete($title);
+// }
+//
+// public static function onTitleMoveComplete(
+// /** @noinspection PhpUnusedParameterInspection */
+// $title, $newTitle, $wgUser, $pageid, $redirid, $reason
+// ) {
+// return self::onArticleChangeComplete($title) ||
+// self::onArticleChangeComplete($newTitle);
+// }
+//
+// /**
+// * Prohibit creation of the pages that are part of our namespaces but have not been explicitly
+// * allowed. Bad capitalization is due to "userCan" hook name
+// * @param Title &$title
+// * @param User &$user
+// * @param String $action
+// * @param null &$result
+// * @return boolean
+// */
+// public static function onuserCan(
+// /** @noinspection PhpUnusedParameterInspection */
+// &$title, &$user, $action, &$result = null
+// ) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// if ($action == 'create' && self::parseTitle($title) == null) {
+// // prohibit creation of the pages for the namespace that we handle,
+// // if the title is not matching declared rules
+// $result = false;
+// return false;
+// }
+// return true;
+// }
+//
+// /**
+// * @param Object $value
+// * @param JCContent $content
+// * @return boolean
+// */
+// private static function onArticleChangeComplete($value, $content = null) {
+// if (!self::jsonConfigIsStorage()) {
+// return true;
+// }
+//
+// if ($value && (!$content || is_a($content, 'JsonConfig\JCContent'))) {
+// if (method_exists($value, 'getTitle')) {
+// $value = $value->getTitle();
+// }
+// $jct = self::parseTitle($value);
+// if ($jct && $jct->getConfig()->store) {
+// $store = new JCCache($jct, $content);
+// $store->resetCache();
+//
+// // Handle remote site notification
+// $store = $jct->getConfig()->store;
+// if ($store->notifyUrl) {
+// $req =
+// JCUtils::initApiRequestObj($store->notifyUrl, $store->notifyUsername,
+// $store->notifyPassword);
+// if ($req) {
+// $query = [
+// 'format' => 'json',
+// 'action' => 'jsonconfig',
+// 'command' => 'reload',
+// 'title' => $jct->getNamespace() . ':' . $jct->getDBkey(),
+// ];
+// JCUtils::callApi($req, $query, 'notify remote JsonConfig client');
+// }
+// }
+// }
+// }
+// return true;
+// }
+//
+// /**
+// * Quick check if the current wiki will store any configurations.
+// * Faster than doing a full parsing of the $wgJsonConfigs in the JCSingleton::init()
+// * @return boolean
+// */
+// private static function jsonConfigIsStorage() {
+// static $isStorage = null;
+// if ($isStorage == null) {
+// global $wgJsonConfigs;
+// $isStorage = false;
+// $configs = array_replace_recursive(
+// \ExtensionRegistry::getInstance()->getAttribute('JsonConfigs'),
+// $wgJsonConfigs
+// );
+// foreach ($configs as $jc) {
+// if ((!array_key_exists('isLocal', $jc) || $jc['isLocal']) ||
+// (array_key_exists('store', $jc))
+// ) {
+// $isStorage = true;
+// break;
+// }
+// }
+// }
+// return $isStorage;
+// }
+ public static String Singleton_Id = "JCSingleton";
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCSingleton_tst.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCSingleton_tst.java
new file mode 100644
index 000000000..cc6755f75
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCSingleton_tst.java
@@ -0,0 +1,66 @@
+/*
+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
+*/
+//namespace gplx.xowa.mediawiki.extensions.JsonConfig.includes {
+// import org.junit.*;
+// using gplx.langs.jsons;
+// using gplx.xowa.mediawiki;
+// using gplx.xowa.xtns.jsonConfigs.scribunto;
+// public class JCSingleton_tst {
+// private final JCSingleton_fxt fxt = new JCSingleton_fxt();
+// @Test public void Get() {
+// fxt.Init__store("en.wikipedia.org", "Page1"
+// , Json_doc.Make_str_by_apos
+// ( "{"
+// , " 'data':"
+// , " ["
+// , " ["
+// , " 'Q1'"
+// , " , 'Data:Q1'"
+// , " ]"
+// , " ,"
+// , " ["
+// , " 'Q2'"
+// , " , 'Data:Q2'"
+// , " ]"
+// , " ]"
+// , "}"
+// ));
+// JCContent actl = fxt.Exec__getContent("en.wikipedia.org", "Page1");
+// Object o = ((JCTabularContent)actl).getField("data");
+// Tfds.Write(o);
+// /*
+// fxt.Test__get(actl, "data", "Q1")
+// */
+// }
+// }
+// class JCSingleton_fxt {
+// private final JCSingleton singleton;
+// private final Xomw_page_fetcher__mock store = new Xomw_page_fetcher__mock();
+// public JCSingleton_fxt() {
+// Jscfg_xtn_mgr xtn_mgr = new Jscfg_xtn_mgr();
+// xtn_mgr.Init_xtn();
+//
+// singleton = (JCSingleton)XophpEnv.Instance.Singletons().Get_by(JCSingleton.Singleton_Id);
+// singleton.Store_(store);
+// }
+// public void Init__store(String wiki, String page, String json) {
+// store.Set_wtxt(Bry_.new_u8(wiki), Bry_.new_u8(page), Bry_.new_u8(json));
+// }
+// public JCContent Exec__getContent(String wiki, String page) {
+// return singleton.getContent(wiki, "unknown_ns", page);
+// }
+// }
+//}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCTabularContent.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCTabularContent.java
new file mode 100644
index 000000000..75421fd5c
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCTabularContent.java
@@ -0,0 +1,209 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.core.primitives.*;
+import gplx.xowa.mediawiki.*;
+import gplx.xowa.langs.*;
+public class JCTabularContent extends JCDataContent {// protected function createDefaultView() {
+// return new JCTabularContentView();
+// }
+
+// /**
+// * Returns wiki-table representation of the tabular data
+// *
+// * @return String|boolean The raw text, or false if the conversion failed.
+// */
+// public function getWikitextForTransclusion() {
+// $toWiki = function ( $value ) {
+// if ( is_object( $value ) ) {
+// global $wgLang;
+// $value = JCUtils::pickLocalizedString( $value, $wgLang );
+// }
+// if ( preg_match( '/^[ .\pL\pN]*$/i', $value ) ) {
+// // Optimization: spaces, letters, numbers, and dots are returned without
+// return $value;
+// }
+// return '' . htmlspecialchars( $value ) . '';
+// };
+//
+// $data = $this->getData();
+// $result = "{| class='wikitable sortable'\n";
+//
+// // Create header
+// $result .= '!' . implode( "!!",
+// array_map(
+// function ( $field ) use ( $toWiki ) {
+// return $toWiki( $field->title ? : $field->name );
+// },
+// $data->schema->fields
+// )
+// ) . "\n";
+//
+// // Create table content
+// foreach ( $data->data as $row ) {
+// $result .= "|-\n|" . implode( '||', array_map( $toWiki, $row ) ) . "\n";
+// }
+//
+// $result .= "\n|}\n";
+//
+// return $result;
+// }
+
+ /**
+ * Derived classes must implement this method to perform custom validation
+ * using the check(...) calls
+ */
+ public void validateContent() {
+// parent::validateContent();
+//
+// $validators = [ JCValidators::isList() ];
+// $typeValidators = [];
+// $fieldsPath = [ 'schema', 'fields' ];
+// if ( $this->test( 'schema', JCValidators::isDictionary() ) &&
+// $this->test( $fieldsPath, JCValidators::isList() ) &&
+// $this->testEach( $fieldsPath, JCValidators::isDictionary() )
+// ) {
+// $hasError = false;
+// $allHeaders = [];
+// $fieldCount = count( $this->getField( $fieldsPath )->getValue() );
+// for ( $idx = 0; $idx < $fieldCount; $idx++ ) {
+// $header = false;
+// $hasError |= !$this->test( [ 'schema', 'fields', $idx, 'name' ],
+// JCValidators::isHeaderString( $allHeaders ),
+// function ( JCValue $jcv ) use ( &$header ) {
+// $header = $jcv->getValue();
+// return true;
+// } );
+// $hasError |= !$this->test( [ 'schema', 'fields', $idx, 'type' ],
+// JCValidators::validateDataType( $typeValidators ) );
+// if ( $header ) {
+// $hasError |= !$this->testOptional( [ 'schema', 'fields', $idx, 'title' ],
+// function () use ( $header ) {
+// return (Object)[ 'en' => $header ];
+// }, JCValidators::isLocalizedString() );
+// }
+// }
+// $countValidator = JCValidators::checkListSize( $fieldCount, 'schema/fields' );
+// $validators[] = $countValidator;
+//
+// if ( !$hasError ) {
+// $this->testEach( $fieldsPath, JCValidators::noExtraValues() );
+// }
+// }
+// $this->test( 'schema', JCValidators::noExtraValues() );
+//
+// if ( !$this->thorough() ) {
+// // We are not doing any modifications to the data, so no need to validate it
+// return;
+// }
+//
+// $this->test( 'data', JCValidators::isList() );
+// $this->test( [], JCValidators::noExtraValues() );
+// $this->testEach( 'data', $validators );
+// if ( $typeValidators ) {
+// /** @noinspection PhpUnusedParameterInspection */
+// $this->testEach( 'data', function ( JCValue $v, array $path ) use ( $typeValidators ) {
+// $isOk = true;
+// $lastIdx = count( $path );
+// foreach ( array_keys( $typeValidators ) as $k ) {
+// $path[$lastIdx] = $k;
+// $isOk &= $this->test( $path, $typeValidators[$k] );
+// }
+// return $isOk;
+// } );
+// }
+ }
+
+ /**
+ * Resolve @Override any specific localizations, and add it to $result
+ * @param Object $result
+ * @param Language $lang
+ */
+ @Override protected void localizeData(XophpStdClass result, Xol_lang_itm lang) {
+ super.localizeData(result, lang);
+
+ XophpStdClass data = this.getData();
+ JCLocalizeItmFunc localize = new JCLocalizeItmFunc(lang);
+
+ Int_list isLocalized = new Int_list();
+ result.Set_by_as_itm("schema", new XophpStdClass());
+
+ XophpStdClass result_schema_flds = new XophpStdClass();
+ result.Set_by_as_itm(String_.Ary("schema", "fields"), result_schema_flds);
+ XophpStdClass flds = data.Get_by_ary_as_itm("schema", "fields");
+ int flds_len = flds.Len();
+ for (int ind = 0; ind < flds_len; ind++) {
+ XophpStdClass fld = flds.Get_at_as_itm(ind);
+ if (fld.Comp_str("type", "localized")) {
+ isLocalized.Add(ind);
+ }
+ XophpStdClass rslt_fld = new XophpStdClass();
+ rslt_fld.Set_by_as_str("name", fld.Get_by_as_str("name"));
+ rslt_fld.Set_by_as_str("type", fld.Get_by_as_str("type"));
+ rslt_fld.Set_by_as_str("title", fld.Has("title") ? localize.Localize(fld.Get_by_as_itm("title")) : fld.Get_by_as_str("name"));
+ result_schema_flds.Add_at_as_itm(rslt_fld);
+ }
+
+ if (isLocalized.Len() == 0) {
+ // There are no localized strings in the data, optimize
+ result.Set_by_as_itm("data", data.Get_by_as_itm("data"));
+ }
+ else {
+ JCArrayFunc array_map_func = new JCArrayFunc();
+ result.Set_by_as_itm("data", array_map_func.Array_map(new JCLocalizeAryFunc(localize, isLocalized), data.Get_by_as_itm("data")));
+ }
+ }
+ public static final String Model_id = "JCTabularContent";
+}
+class JCArrayFunc {
+ public XophpStdClass Array_map(JCLocalizeAryFunc func, XophpStdClass src) {
+ XophpStdClass trg = new XophpStdClass();
+ int len = src.Len();
+ for (int i = 0; i < len; i++) {
+ XophpStdClass src_sub = src.Get_at_as_itm(i);
+ XophpStdClass trg_sub = func.Array_map(src_sub);
+ trg.Add_at_as_itm(trg_sub);
+ }
+ return trg;
+ }
+}
+class JCLocalizeAryFunc {
+ private final JCLocalizeItmFunc localize;
+ private final Int_list isLocalized;
+ public JCLocalizeAryFunc(JCLocalizeItmFunc localize, Int_list isLocalized) {
+ this.localize = localize;
+ this.isLocalized = isLocalized;
+ }
+ public XophpStdClass Array_map(XophpStdClass row) {
+ int len = isLocalized.Len();
+ for (int ind = 0; ind < len; ind++) {
+ XophpStdClass val = row.Get_at_as_itm(ind);
+ if (val != null) {
+ row.Set_at_as_str(ind, localize.Localize(val)); // NOTE: will reduce a map to a String; EX: name={en='a',fr='b'} => name={'a'}
+ }
+ }
+ return row;
+ }
+}
+class JCLocalizeItmFunc {
+ private final Xol_lang_itm lang;
+ public JCLocalizeItmFunc(Xol_lang_itm lang) {
+ this.lang = lang;
+ }
+ public String Localize(XophpStdClass val) {
+ return JCUtils.pickLocalizedString(val, lang);
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCTabularContentFactory.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCTabularContentFactory.java
new file mode 100644
index 000000000..b166eb32f
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCTabularContentFactory.java
@@ -0,0 +1,28 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.xowa.mediawiki.*;
+public class JCTabularContentFactory implements XophpClassBldr {
+ public String Id() {return JCTabularContent.Model_id;}
+ public Object Make(Object... args) {
+ JCTabularContent rv = new JCTabularContent();
+ byte[] text = (byte[])args[0];
+ String modelId = (String)args[1];
+ boolean thorough = Bool_.Cast(args[2]);
+ rv.__construct(text, modelId, thorough);
+ return rv;
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCUtils.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCUtils.java
new file mode 100644
index 000000000..62af3efe9
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCUtils.java
@@ -0,0 +1,56 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.xowa.langs.*;
+import gplx.xowa.mediawiki.*;
+class JCUtils {
+ /**
+ * Find a message in a dictionary for the given language,
+ * or use language fallbacks if message is not defined.
+ * @param stdClass map Dictionary of languageCode => String
+ * @param Language|StubUserLang lang language Object
+ * @param boolean|String $defaultValue if non-false, use this value in case no fallback and no 'en'
+ * @return String message from the dictionary or "" if nothing found
+ */
+ public static String pickLocalizedString(XophpStdClass map, Xol_lang_itm lang) {return pickLocalizedString(map, lang, null);}
+ public static String pickLocalizedString(XophpStdClass map, Xol_lang_itm lang, String defaultValue) {
+ String langCode = lang.Key_str();
+ if (map.Has(langCode)) {
+ return map.Get_by_as_str(langCode);
+ }
+ /*
+ for+each (lang.getFallbackLanguages() as l) {
+ if (property_exists(map, l)) {
+ return map.l;
+ }
+ }
+ */
+
+ // If fallbacks fail, check if english is defined
+ if (map.Has("en") ) {
+ return map.Get_by_as_str("en");
+ }
+
+ // We have a custom default, return that
+ if (defaultValue != null) {
+ return null;
+ }
+
+ // Return first available value, or an empty String
+ // There might be a better way to get the first value from an Object
+ return map.Len() == 0 ? "" : map.Get_at_as_str(0);
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCValue.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCValue.java
new file mode 100644
index 000000000..897d0a39b
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCValue.java
@@ -0,0 +1,252 @@
+/*
+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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+import gplx.xowa.xtns.scribunto.*;
+import gplx.xowa.mediawiki.*;
+public class JCValue {
+ private Ordered_hash value = Ordered_hash_.New();
+ private int statusVal;
+ private XophpStdClass value_as_obj;
+ private Xophp_ary value_as_ary;
+ private Object value_as_prim;
+ private int value_tid;
+ private boolean sameAsDefaultVal = false;
+ private boolean defaultUsedVal = false;
+ private Object errorVal = null;
+
+ private static final int NULL = -1;
+
+ /** Value has not been checked */
+ public static final int UNCHECKED = 0;
+ /** Value was explicitly checked (might be an error) */
+// private static final int CHECKED = 1;
+ /** field is missing in the data, but is being explicitly tested for.
+ * This value should never be stored in JCObjContent::validationData.
+ * Setting this value for any field in validator will delete it. */
+ private static final int MISSING = 2;
+ /** field was not explicitly tested, but it was listed as a parent of one of the tested fields */
+// private static final int VISITED = 3;
+
+ private static final int Value_tid__obj = 1, Value_tid__ary = 2, Value_tid__prim = 3;
+
+ /** @param int status
+ * @param mixed value
+ */
+ public JCValue(int status, XophpStdClass value_as_obj, Xophp_ary value_as_ary, Object value_as_prim) {
+ this.statusVal = status;
+ this.value_as_obj = value_as_obj;
+ this.value_as_ary = value_as_ary;
+ this.value_as_prim = value_as_prim;
+ if (value_as_obj != null) {
+ value_tid = Value_tid__obj;
+ }
+ else if (value_as_ary != null) {
+ value_tid = Value_tid__ary;
+ }
+ else {
+ value_tid = Value_tid__prim;
+ }
+ }
+
+ /** @return mixed */
+ public Object getValue() {
+ switch (value_tid) {
+ case Value_tid__obj:
+ return value_as_obj;
+ case Value_tid__ary:
+ return value_as_ary;
+ case Value_tid__prim:
+ return value_as_prim;
+ default:
+ throw Err_.new_unhandled_default(value_tid);
+ }
+ }
+
+ public void setValue(Ordered_hash value) {this.setValue(value, NULL);}
+ public void setValue(Ordered_hash value, int status) {
+ this.value = value;
+ if (status != NULL) {
+ this.status(status);
+ } else if (this.isMissing()) {
+ // Convenience - if we are setting a new value, assume we are setting a default
+ this.status(JCValue.UNCHECKED);
+ this.defaultUsed(true);
+ }
+ }
+
+ public int status() {return status(NULL);}
+ public int status(int o) {
+ int val = this.statusVal;
+ if (o != NULL) {
+ this.statusVal = o;
+ }
+ return val;
+ }
+
+ public boolean sameAsDefault() {return sameAsDefault(null);}
+ public boolean sameAsDefault(Object o) {
+ boolean val = this.sameAsDefaultVal;
+ if (o != null) {
+ this.sameAsDefaultVal = Bool_.Cast(o);
+ }
+ return val;
+ }
+
+ public boolean defaultUsed() {return defaultUsed(null);}
+ public boolean defaultUsed(Object o) {
+ boolean val = this.defaultUsedVal;
+ if (o != null) {
+ this.defaultUsedVal = Bool_.Cast(o);
+ }
+ return val;
+ }
+
+ public boolean isMissing() {
+ return this.statusVal == JCValue.MISSING;
+ }
+
+ public boolean isUnchecked() {
+ return this.statusVal == JCValue.UNCHECKED;
+ }
+
+ /** Helper function - same arguments as wfMessage, or true if message was already added.
+ * false clears this message status, and null returns current state without changing it
+ * @param null|boolean|String $key message id, or if boolean, sets/removes error status
+ * @param array $fieldPath path to the erroneous field. Will be converted to a a/b/c[0]/d style
+ * @return boolean|Message
+ */
+// public String error($key = null, $fieldPath = null /*...*/)
+ public Object error(Object key, String... fieldPath) {
+ if (Type_.Type_by_obj(key) == Bool_.Cls_ref_type) {
+ this.errorVal = Bool_.Cast(key);
+ }
+ else if (key != null) {
+// $args = func_get_args();
+// if (is_array($fieldPath)) {
+// // Convert field path to a printable String
+// $args[1] = JCUtils::fieldPathToString($fieldPath);
+// }
+// $this.errorVal = call_user_func_array('wfMessage', $args);
+ }
+ return this.errorVal;
+ }
+
+ /**
+ * @param String|int $fld
+ * @param mixed value
+ * @throws Exception
+ */
+ public void setField(Object fld, Object o) {
+ int fld_type = To_type_id(fld);
+ if (value_tid == Value_tid__obj && fld_type == Type_ids_.Id__str) {
+ value_as_obj.Add_by_as_obj((String)fld, o);
+ }
+ else if (value_tid == Value_tid__ary && (fld_type == Type_ids_.Id__str || fld_type == Type_ids_.Id__int)) {
+ if (fld_type == Type_ids_.Id__str)
+ value_as_ary.Add((String)fld, o);
+ else
+ value_as_ary.Add(Int_.Cast(fld), o);
+ }
+ else {
+ throw Err_.new_wo_type("Type mismatch for field " + fld);
+ }
+ }
+
+ /**
+ * @param String|int $fld
+ * @throws \Exception
+ * @return mixed
+ */
+ public Object deleteField(Object fld) {
+ int fld_type = To_type_id(fld);
+ Object tmp = null;
+ if (value_tid == Value_tid__obj && fld_type == Type_ids_.Id__str) {
+ String key = (String)fld;
+ tmp = value_as_obj.Get_by_as_obj(key);
+ value_as_obj.Del_by(key);
+ }
+ else if (value_tid == Value_tid__ary && (fld_type == Type_ids_.Id__str || fld_type == Type_ids_.Id__int)) {
+ tmp = value_as_ary.Get(fld);
+ if (fld_type == Type_ids_.Id__str)
+ value_as_ary.Unset((String)fld);
+ else
+ value_as_ary.Unset(Int_.Cast(fld));
+ value.Del(fld);
+ }
+ else {
+ throw Err_.new_wo_type("Type mismatch for field " + fld);
+ }
+ return tmp;
+ }
+
+ /**
+ * @param String|int $fld
+ * @throws \Exception
+ * @return boolean
+ */
+ public boolean fieldExists(Object fld) {
+ int fld_type = To_type_id(fld);
+ if (value_tid == Value_tid__obj && fld_type == Type_ids_.Id__str) {
+ return value_as_obj.Has((String)fld);
+ }
+ else if (value_tid == Value_tid__ary && (fld_type == Type_ids_.Id__str || fld_type == Type_ids_.Id__int)) {
+ return value_as_ary.Has(fld);
+ }
+ throw Err_.new_wo_type("Type mismatch for field " + fld);
+ }
+
+ /**
+ * @param String|int $fld
+ * @throws \Exception
+ * @return mixed
+ */
+ public Object getField(Object fld) {
+ int fld_type = To_type_id(fld);
+ if (value_tid == Value_tid__obj && fld_type == Type_ids_.Id__str) {
+ return value_as_obj.Get_by_as_obj((String)fld);
+ }
+ else if (value_tid == Value_tid__ary && (fld_type == Type_ids_.Id__str || fld_type == Type_ids_.Id__int)) {
+ return value_as_ary.Get(fld);
+ }
+ throw Err_.new_wo_type("Type mismatch for field " + fld);
+ }
+
+ public static int To_type_id(Object o) {
+ Class> type = Type_.Type_by_obj(o);
+ if (Type_.Eq(type, String.class))
+ return Type_ids_.Id__str;
+ else if (Type_.Eq(type, int.class))
+ return Type_ids_.Id__int;
+ else
+ return Type_ids_.Id__null;
+ }
+}
+class XomwTypeUtl {
+ public static int To_type_id(Object o) {
+ if (o == null)
+ return Type_ids_.Id__null;
+ Class> type = Type_.Type_by_obj(o);
+ if (Type_.Eq(type, String.class))
+ return Type_ids_.Id__str;
+ else if (Type_.Eq(type, int.class))
+ return Type_ids_.Id__int;
+ else if (Type_.Is_array(type))
+ return Type_ids_.Id__array;
+ else
+ return Type_ids_.Id__obj;
+
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/XomwStatus.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/XomwStatus.java
new file mode 100644
index 000000000..4c3965ec0
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/XomwStatus.java
@@ -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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+public class XomwStatus {
+ public boolean isGood() {
+ return false;
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/Xomw_page_fetcher.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/Xomw_page_fetcher.java
new file mode 100644
index 000000000..6319b1131
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/Xomw_page_fetcher.java
@@ -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.extensions.JsonConfig.includes; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.extensions.*; import gplx.xowa.mediawiki.extensions.JsonConfig.*;
+public interface Xomw_page_fetcher {
+ byte[] Get_wtxt(byte[] wiki, byte[] page);
+}
+class Xomw_page_fetcher__mock implements Xomw_page_fetcher {
+ private final Ordered_hash hash = Ordered_hash_.New_bry();
+ public void Set_wtxt(byte[] wiki, byte[] page, byte[] wtxt) {
+ hash.Add(Make_key(wiki, page), wtxt);
+ }
+ public byte[] Get_wtxt(byte[] wiki, byte[] page) {
+ return (byte[])hash.Get_by(Make_key(wiki, page));
+ }
+ private static byte[] Make_key(byte[] wiki, byte[] page) {
+ return Bry_.Add(wiki, Byte_ascii.Pipe_bry, page);
+ }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/XomwDefines.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/XomwDefines.java
index b465e058c..03d8b92dc 100644
--- a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/XomwDefines.java
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/XomwDefines.java
@@ -261,20 +261,20 @@ public class XomwDefines {
// define( 'PROTO_INTERNAL', 2 );
// /**@}*/
//
-// /**@{
-// * Content model ids, used by Content and ContentHandler.
-// * These IDs will be exposed in the API and XML dumps.
-// *
-// * Extensions that define their own content model IDs should take
-// * care to avoid conflicts. Using the extension name as a prefix is recommended,
-// * for example 'myextension-somecontent'.
-// */
-// define( 'CONTENT_MODEL_WIKITEXT', 'wikitext' );
-// define( 'CONTENT_MODEL_JAVASCRIPT', 'javascript' );
-// define( 'CONTENT_MODEL_CSS', 'css' );
-// define( 'CONTENT_MODEL_TEXT', 'text' );
-// define( 'CONTENT_MODEL_JSON', 'json' );
-// /**@}*/
+ /**@{
+ * Content model ids, used by Content and ContentHandler.
+ * These IDs will be exposed in the API and XML dumps.
+ *
+ * Extensions that define their own content model IDs should take
+ * care to avoid conflicts. Using the extension name as a prefix is recommended,
+ * for example 'myextension-somecontent'.
+ */
+ public static final String CONTENT_MODEL_WIKITEXT = "wikitext";
+ public static final String CONTENT_MODEL_JAVASCRIPT = "javascript";
+ public static final String CONTENT_MODEL_CSS = "css";
+ public static final String CONTENT_MODEL_TEXT = "text";
+ public static final String CONTENT_MODEL_JSON = "json";
+ /**@}*/
/**@{
* Content formats, used by Content and ContentHandler.
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/ContentHandler.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/ContentHandler.java
new file mode 100644
index 000000000..1c9615fe4
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/ContentHandler.java
@@ -0,0 +1,1172 @@
+/*
+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.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
+import gplx.core.brys.*;
+public abstract class ContentHandler {
+// /**
+// * Convenience function for getting flat text from a Content Object. This
+// * should only be used in the context of backwards compatibility with code
+// * that is not yet able to handle Content objects!
+// *
+// * If $content is null, this method returns the empty String.
+// *
+// * If $content is an instance of TextContent, this method returns the flat
+// * text as returned by $content->getNativeData().
+// *
+// * If $content is not a TextContent Object, the behavior of this method
+// * depends on the global $wgContentHandlerTextFallback:
+// * - If $wgContentHandlerTextFallback is 'fail' and $content is not a
+// * TextContent Object, an MWException is thrown.
+// * - If $wgContentHandlerTextFallback is 'serialize' and $content is not a
+// * TextContent Object, $content->serialize() is called to get a String
+// * form of the content.
+// * - If $wgContentHandlerTextFallback is 'ignore' and $content is not a
+// * TextContent Object, this method returns null.
+// * - otherwise, the behavior is undefined.
+// *
+// * @since 1.21
+// *
+// * @param Content $content
+// *
+// * @throws MWException If the content is not an instance of TextContent and
+// * wgContentHandlerTextFallback was set to 'fail'.
+// * @return String|null Textual form of the content, if available.
+// */
+// public static function getContentText(Content $content = null) {
+// global $wgContentHandlerTextFallback;
+//
+// if (is_null($content)) {
+// return '';
+// }
+//
+// if ($content instanceof TextContent) {
+// return $content->getNativeData();
+// }
+//
+// wfDebugLog('ContentHandler', 'Accessing ' . $content->getModel() . ' content as text!');
+//
+// if ($wgContentHandlerTextFallback == 'fail') {
+// throw new MWException(
+// "Attempt to get text from Content with model " .
+// $content->getModel()
+// );
+// }
+//
+// if ($wgContentHandlerTextFallback == 'serialize') {
+// return $content->serialize();
+// }
+//
+// return null;
+// }
+//
+// /**
+// * Convenience function for creating a Content Object from a given textual
+// * representation.
+// *
+// * $text will be deserialized into a Content Object of the model specified
+// * by $modelId (or, if that is not given, $title->getContentModel()) using
+// * the given format.
+// *
+// * @since 1.21
+// *
+// * @param String $text The textual representation, will be
+// * unserialized to create the Content Object
+// * @param Title $title The title of the page this text belongs to.
+// * Required if $modelId is not provided.
+// * @param String $modelId The model to deserialize to. If not provided,
+// * $title->getContentModel() is used.
+// * @param String $format The format to use for deserialization. If not
+// * given, the model's default format is used.
+// *
+// * @throws MWException If model ID or format is not supported or if the text can not be
+// * unserialized using the format.
+// * @return Content A Content Object representing the text.
+// */
+// public static function makeContent($text, Title $title = null,
+// $modelId = null, $format = null) {
+// if (is_null($modelId)) {
+// if (is_null($title)) {
+// throw new MWException("Must provide a Title Object or a content model ID.");
+// }
+//
+// $modelId = $title->getContentModel();
+// }
+//
+// $handler = ContentHandler::getForModelID($modelId);
+//
+// return $handler->unserializeContent($text, $format);
+// }
+//
+// /**
+// * Returns the name of the default content model to be used for the page
+// * with the given title.
+// *
+// * Note: There should rarely be need to call this method directly.
+// * To determine the actual content model for a given page, use
+// * Title::getContentModel().
+// *
+// * Which model is to be used by default for the page is determined based
+// * on several factors:
+// * - The global setting $wgNamespaceContentModels specifies a content model
+// * per namespace.
+// * - The hook ContentHandlerDefaultModelFor may be used to override the page's default
+// * model.
+// * - Pages in NS_MEDIAWIKI and NS_USER default to the CSS or JavaScript
+// * model if they end in .js or .css, respectively.
+// * - Pages in NS_MEDIAWIKI default to the wikitext model otherwise.
+// * - The hook TitleIsCssOrJsPage may be used to force a page to use the CSS
+// * or JavaScript model. This is a compatibility feature. The ContentHandlerDefaultModelFor
+// * hook should be used instead if possible.
+// * - The hook TitleIsWikitextPage may be used to force a page to use the
+// * wikitext model. This is a compatibility feature. The ContentHandlerDefaultModelFor
+// * hook should be used instead if possible.
+// *
+// * If none of the above applies, the wikitext model is used.
+// *
+// * Note: this is used by, and may thus not use, Title::getContentModel()
+// *
+// * @since 1.21
+// *
+// * @param Title $title
+// *
+// * @return String Default model name for the page given by $title
+// */
+// public static function getDefaultModelFor(Title $title) {
+// // NOTE: this method must not rely on $title->getContentModel() directly or indirectly,
+// // because it is used to initialize the mContentModel member.
+//
+// $ns = $title->getNamespace();
+//
+// $ext = false;
+// $m = null;
+// $model = MWNamespace::getNamespaceContentModel($ns);
+//
+// // Hook can determine default model
+// if (!Hooks::run('ContentHandlerDefaultModelFor', [ $title, &$model ])) {
+// if (!is_null($model)) {
+// return $model;
+// }
+// }
+//
+// // Could this page contain code based on the title?
+// $isCodePage = NS_MEDIAWIKI == $ns && preg_match('!\.(css|js|json)$!u', $title->getText(), $m);
+// if ($isCodePage) {
+// $ext = $m[1];
+// }
+//
+// // Is this a user subpage containing code?
+// $isCodeSubpage = NS_USER == $ns
+// && !$isCodePage
+// && preg_match("/\\/.*\\.(js|css|json)$/", $title->getText(), $m);
+// if ($isCodeSubpage) {
+// $ext = $m[1];
+// }
+//
+// // Is this wikitext, according to $wgNamespaceContentModels or the DefaultModelFor hook?
+// $isWikitext = is_null($model) || $model == CONTENT_MODEL_WIKITEXT;
+// $isWikitext = $isWikitext && !$isCodePage && !$isCodeSubpage;
+//
+// if (!$isWikitext) {
+// switch ($ext) {
+// case 'js':
+// return CONTENT_MODEL_JAVASCRIPT;
+// case 'css':
+// return CONTENT_MODEL_CSS;
+// case 'json':
+// return CONTENT_MODEL_JSON;
+// default:
+// return is_null($model) ? CONTENT_MODEL_TEXT : $model;
+// }
+// }
+//
+// // We established that it must be wikitext
+//
+// return CONTENT_MODEL_WIKITEXT;
+// }
+//
+// /**
+// * Returns the appropriate ContentHandler singleton for the given title.
+// *
+// * @since 1.21
+// *
+// * @param Title $title
+// *
+// * @return ContentHandler
+// */
+// public static function getForTitle(Title $title) {
+// $modelId = $title->getContentModel();
+//
+// return ContentHandler::getForModelID($modelId);
+// }
+//
+// /**
+// * Returns the appropriate ContentHandler singleton for the given Content
+// * Object.
+// *
+// * @since 1.21
+// *
+// * @param Content $content
+// *
+// * @return ContentHandler
+// */
+// public static function getForContent(Content $content) {
+// $modelId = $content->getModel();
+//
+// return ContentHandler::getForModelID($modelId);
+// }
+//
+// /**
+// * @var array A Cache of ContentHandler instances by model id
+// */
+// protected static $handlers;
+//
+// /**
+// * Returns the ContentHandler singleton for the given model ID. Use the
+// * CONTENT_MODEL_XXX constants to identify the desired content model.
+// *
+// * ContentHandler singletons are taken from the global $wgContentHandlers
+// * array. Keys in that array are model names, the values are either
+// * ContentHandler singleton objects, or strings specifying the appropriate
+// * subclass of ContentHandler.
+// *
+// * If a class name is encountered when looking up the singleton for a given
+// * model name, the class is instantiated and the class name is replaced by
+// * the resulting singleton in $wgContentHandlers.
+// *
+// * If no ContentHandler is defined for the desired $modelId, the
+// * ContentHandler may be provided by the ContentHandlerForModelID hook.
+// * If no ContentHandler can be determined, an MWException is raised.
+// *
+// * @since 1.21
+// *
+// * @param String $modelId The ID of the content model for which to get a
+// * handler. Use CONTENT_MODEL_XXX constants.
+// *
+// * @throws MWException For @gplx.Internal protected errors and problems in the configuration.
+// * @throws MWUnknownContentModelException If no handler is known for the model ID.
+// * @return ContentHandler The ContentHandler singleton for handling the model given by the ID.
+// */
+// public static function getForModelID($modelId) {
+// global $wgContentHandlers;
+//
+// if (isset(ContentHandler::$handlers[$modelId])) {
+// return ContentHandler::$handlers[$modelId];
+// }
+//
+// if (empty($wgContentHandlers[$modelId])) {
+// $handler = null;
+//
+// Hooks::run('ContentHandlerForModelID', [ $modelId, &$handler ]);
+//
+// if ($handler === null) {
+// throw new MWUnknownContentModelException($modelId);
+// }
+//
+// if (!($handler instanceof ContentHandler)) {
+// throw new MWException("ContentHandlerForModelID must supply a ContentHandler instance");
+// }
+// } else {
+// $classOrCallback = $wgContentHandlers[$modelId];
+//
+// if (is_callable($classOrCallback)) {
+// $handler = call_user_func($classOrCallback, $modelId);
+// } else {
+// $handler = new $classOrCallback($modelId);
+// }
+//
+// if (!($handler instanceof ContentHandler)) {
+// throw new MWException("$classOrCallback from \$wgContentHandlers is not " .
+// "compatible with ContentHandler");
+// }
+// }
+//
+// wfDebugLog('ContentHandler', 'Created handler for ' . $modelId
+// . ': ' . get_class($handler));
+//
+// ContentHandler::$handlers[$modelId] = $handler;
+//
+// return ContentHandler::$handlers[$modelId];
+// }
+//
+// /**
+// * Returns the localized name for a given content model.
+// *
+// * Model names are localized using system messages. Message keys
+// * have the form content-model-$name, where $name is getContentModelName($id).
+// *
+// * @param String $name The content model ID, as given by a CONTENT_MODEL_XXX
+// * constant or returned by Revision::getContentModel().
+// * @param Language|null $lang The language to parse the message in (since 1.26)
+// *
+// * @throws MWException If the model ID isn't known.
+// * @return String The content model's localized name.
+// */
+// public static function getLocalizedName($name, Language $lang = null) {
+// // Messages: content-model-wikitext, content-model-text,
+// // content-model-javascript, content-model-css
+// $key = "content-model-$name";
+//
+// $msg = wfMessage($key);
+// if ($lang) {
+// $msg->inLanguage($lang);
+// }
+//
+// return $msg->exists() ? $msg->plain() : $name;
+// }
+//
+// public static function getContentModels() {
+// global $wgContentHandlers;
+//
+// return array_keys($wgContentHandlers);
+// }
+//
+// public static function getAllContentFormats() {
+// global $wgContentHandlers;
+//
+// $formats = [];
+//
+// foreach ($wgContentHandlers as $model => $class) {
+// $handler = ContentHandler::getForModelID($model);
+// $formats = array_merge($formats, $handler->getSupportedFormats());
+// }
+//
+// $formats = array_unique($formats);
+//
+// return $formats;
+// }
+//
+// // ------------------------------------------------------------------------
+
+ /**
+ * @var String
+ */
+ protected String mModelID;
+
+ /**
+ * @var String[]
+ */
+ protected String[] mSupportedFormats;
+
+ /**
+ * Constructor, initializing the ContentHandler instance with its model ID
+ * and a list of supported formats. Values for the parameters are typically
+ * provided as literals by subclass's constructors.
+ *
+ * @param String $modelId (use CONTENT_MODEL_XXX constants).
+ * @param String[] $formats List for supported serialization formats
+ * (typically as MIME types)
+ */
+ @gplx.Virtual public void __construct(String modelId, String... formats) {
+ this.mModelID = modelId;
+ this.mSupportedFormats = formats;
+ }
+
+// /**
+// * Serializes a Content Object of the type supported by this ContentHandler.
+// *
+// * @since 1.21
+// *
+// * @param Content $content The Content Object to serialize
+// * @param String $format The desired serialization format
+// *
+// * @return String Serialized form of the content
+// */
+// abstract public function serializeContent(Content $content, $format = null);
+//
+// /**
+// * Applies transformations on export (returns the blob unchanged per default).
+// * Subclasses may override this to perform transformations such as conversion
+// * of legacy formats or filtering of @gplx.Internal protected meta-data.
+// *
+// * @param String $blob The blob to be exported
+// * @param String|null $format The blob's serialization format
+// *
+// * @return String
+// */
+// public function exportTransform($blob, $format = null) {
+// return $blob;
+// }
+//
+// /**
+// * Unserializes a Content Object of the type supported by this ContentHandler.
+// *
+// * @since 1.21
+// *
+// * @param String $blob Serialized form of the content
+// * @param String $format The format used for serialization
+// *
+// * @return Content The Content Object created by deserializing $blob
+// */
+// abstract public function unserializeContent($blob, $format = null);
+//
+// /**
+// * Apply import transformation (per default, returns $blob unchanged).
+// * This gives subclasses an opportunity to transform data blobs on import.
+// *
+// * @since 1.24
+// *
+// * @param String $blob
+// * @param String|null $format
+// *
+// * @return String
+// */
+// public function importTransform($blob, $format = null) {
+// return $blob;
+// }
+//
+// /**
+// * Creates an empty Content Object of the type supported by this
+// * ContentHandler.
+// *
+// * @since 1.21
+// *
+// * @return Content
+// */
+// abstract public function makeEmptyContent();
+//
+// /**
+// * Creates a new Content Object that acts as a redirect to the given page,
+// * or null if redirects are not supported by this content model.
+// *
+// * This default implementation always returns null. Subclasses supporting redirects
+// * must override this method.
+// *
+// * Note that subclasses that override this method to return a Content Object
+// * should also override supportsRedirects() to return true.
+// *
+// * @since 1.21
+// *
+// * @param Title $destination The page to redirect to.
+// * @param String $text Text to include in the redirect, if possible.
+// *
+// * @return Content Always null.
+// */
+// public function makeRedirectContent(Title $destination, $text = '') {
+// return null;
+// }
+
+ /**
+ * Returns the model id that identifies the content model this
+ * ContentHandler can handle. Use with the CONTENT_MODEL_XXX constants.
+ *
+ * @since 1.21
+ *
+ * @return String The model ID
+ */
+ public String getModelID() {
+ return this.mModelID;
+ }
+
+// /**
+// * @since 1.21
+// *
+// * @param String $model_id The model to check
+// *
+// * @throws MWException If the model ID is not the ID of the content model supported by this
+// * ContentHandler.
+// */
+// protected function checkModelID($model_id) {
+// if ($model_id !== this.mModelID) {
+// throw new MWException("Bad content model: " .
+// "expected {this.mModelID} " .
+// "but got $model_id.");
+// }
+// }
+
+ /**
+ * Returns a list of serialization formats supported by the
+ * serializeContent() and unserializeContent() methods of this
+ * ContentHandler.
+ *
+ * @since 1.21
+ *
+ * @return String[] List of serialization formats as MIME type like strings
+ */
+ public String[] getSupportedFormats() {
+ return this.mSupportedFormats;
+ }
+
+// /**
+// * The format used for serialization/deserialization by default by this
+// * ContentHandler.
+// *
+// * This default implementation will return the first element of the array
+// * of formats that was passed to the constructor.
+// *
+// * @since 1.21
+// *
+// * @return String The name of the default serialization format as a MIME type
+// */
+// public function getDefaultFormat() {
+// return this.mSupportedFormats[0];
+// }
+
+ /**
+ * Returns true if $format is a serialization format supported by this
+ * ContentHandler, and false otherwise.
+ *
+ * Note that if $format is null, this method always returns true, because
+ * null means "use the default format".
+ *
+ * @since 1.21
+ *
+ * @param String $format The serialization format to check
+ *
+ * @return boolean
+ */
+ public boolean isSupportedFormat(String format) {
+ if (format == null) {
+ return true; // this means "use the default"
+ }
+
+ return XophpArray.in_array(format, this.mSupportedFormats);
+ }
+
+ /**
+ * Convenient for checking whether a format provided as a parameter is actually supported.
+ *
+ * @param String $format The serialization format to check
+ *
+ * @throws MWException If the format is not supported by this content handler.
+ */
+ protected void checkFormat(String format) {
+ if (!this.isSupportedFormat(format)) {
+ throw Err_.new_wo_type(
+ "Format $format is not supported for content model "
+ + this.getModelID()
+ );
+ }
+ }
+
+// /**
+// * Returns overrides for action handlers.
+// * Classes listed here will be used instead of the default one when
+// * (and only when) $wgActions[$action] === true. This allows subclasses
+// * to override the default action handlers.
+// *
+// * @since 1.21
+// *
+// * @return array An array mapping action names (typically "view", "edit", "history" etc.) to
+// * either the full qualified class name of an Action class, a callable taking (Page $page,
+// * IContextSource $context = null) as parameters and returning an Action Object, or an actual
+// * Action Object. An empty array in this default implementation.
+// *
+// * @see Action::factory
+// */
+// public function getActionOverrides() {
+// return [];
+// }
+//
+// /**
+// * Factory for creating an appropriate DifferenceEngine for this content model.
+// *
+// * @since 1.21
+// *
+// * @param IContextSource $context Context to use, anything else will be ignored.
+// * @param int $old Revision ID we want to show and diff with.
+// * @param int|String $new Either a revision ID or one of the strings 'cur', 'prev' or 'next'.
+// * @param int $rcid FIXME: Deprecated, no longer used. Defaults to 0.
+// * @param boolean $refreshCache If set, refreshes the diff cache. Defaults to false.
+// * @param boolean $unhide If set, allow viewing deleted revs. Defaults to false.
+// *
+// * @return DifferenceEngine
+// */
+// public function createDifferenceEngine(IContextSource $context, $old = 0, $new = 0,
+// $rcid = 0, // FIXME: Deprecated, no longer used
+// $refreshCache = false, $unhide = false) {
+//
+// // hook: get difference engine
+// $differenceEngine = null;
+// if (!Hooks::run('GetDifferenceEngine',
+// [ $context, $old, $new, $refreshCache, $unhide, &$differenceEngine ]
+// )) {
+// return $differenceEngine;
+// }
+// $diffEngineClass = this.getDiffEngineClass();
+// return new $diffEngineClass($context, $old, $new, $rcid, $refreshCache, $unhide);
+// }
+//
+// /**
+// * Get the language in which the content of the given page is written.
+// *
+// * This default implementation just returns $wgContLang (except for pages
+// * in the MediaWiki namespace)
+// *
+// * Note that the pages language is not cacheable, since it may in some
+// * cases depend on user settings.
+// *
+// * Also note that the page language may or may not depend on the actual content of the page,
+// * that is, this method may load the content in order to determine the language.
+// *
+// * @since 1.21
+// *
+// * @param Title $title The page to determine the language for.
+// * @param Content $content The page's content, if you have it handy, to avoid reloading it.
+// *
+// * @return Language The page's language
+// */
+// public function getPageLanguage(Title $title, Content $content = null) {
+// global $wgContLang, $wgLang;
+// $pageLang = $wgContLang;
+//
+// if ($title->getNamespace() == NS_MEDIAWIKI) {
+// // Parse mediawiki messages with correct target language
+// list(/* $unused */, $lang) = MessageCache::singleton()->figureMessage($title->getText());
+// $pageLang = Language::factory($lang);
+// }
+//
+// Hooks::run('PageContentLanguage', [ $title, &$pageLang, $wgLang ]);
+//
+// return wfGetLangObj($pageLang);
+// }
+//
+// /**
+// * Get the language in which the content of this page is written when
+// * viewed by user. Defaults to this.getPageLanguage(), but if the user
+// * specified a preferred variant, the variant will be used.
+// *
+// * This default implementation just returns this.getPageLanguage($title, $content) unless
+// * the user specified a preferred variant.
+// *
+// * Note that the pages view language is not cacheable, since it depends on user settings.
+// *
+// * Also note that the page language may or may not depend on the actual content of the page,
+// * that is, this method may load the content in order to determine the language.
+// *
+// * @since 1.21
+// *
+// * @param Title $title The page to determine the language for.
+// * @param Content $content The page's content, if you have it handy, to avoid reloading it.
+// *
+// * @return Language The page's language for viewing
+// */
+// public function getPageViewLanguage(Title $title, Content $content = null) {
+// $pageLang = this.getPageLanguage($title, $content);
+//
+// if ($title->getNamespace() !== NS_MEDIAWIKI) {
+// // If the user chooses a variant, the content is actually
+// // in a language whose code is the variant code.
+// $variant = $pageLang->getPreferredVariant();
+// if ($pageLang->getCode() !== $variant) {
+// $pageLang = Language::factory($variant);
+// }
+// }
+//
+// return $pageLang;
+// }
+//
+// /**
+// * Determines whether the content type handled by this ContentHandler
+// * can be used on the given page.
+// *
+// * This default implementation always returns true.
+// * Subclasses may override this to restrict the use of this content model to specific locations,
+// * typically based on the namespace or some other aspect of the title, such as a special suffix
+// * (e.g. ".svg" for SVG content).
+// *
+// * @note this calls the ContentHandlerCanBeUsedOn hook which may be used to override which
+// * content model can be used where.
+// *
+// * @param Title $title The page's title.
+// *
+// * @return boolean True if content of this kind can be used on the given page, false otherwise.
+// */
+// public function canBeUsedOn(Title $title) {
+// $ok = true;
+//
+// Hooks::run('ContentModelCanBeUsedOn', [ this.getModelID(), $title, &$ok ]);
+//
+// return $ok;
+// }
+//
+// /**
+// * Returns the name of the diff engine to use.
+// *
+// * @since 1.21
+// *
+// * @return String
+// */
+// protected function getDiffEngineClass() {
+// return DifferenceEngine::class;
+// }
+//
+// /**
+// * Attempts to merge differences between three versions. Returns a new
+// * Content Object for a clean merge and false for failure or a conflict.
+// *
+// * This default implementation always returns false.
+// *
+// * @since 1.21
+// *
+// * @param Content $oldContent The page's previous content.
+// * @param Content $myContent One of the page's conflicting contents.
+// * @param Content $yourContent One of the page's conflicting contents.
+// *
+// * @return Content|boolean Always false.
+// */
+// public function merge3(Content $oldContent, Content $myContent, Content $yourContent) {
+// return false;
+// }
+//
+// /**
+// * Return an applicable auto-summary if one exists for the given edit.
+// *
+// * @since 1.21
+// *
+// * @param Content $oldContent The previous text of the page.
+// * @param Content $newContent The submitted text of the page.
+// * @param int $flags Bit mask: a bit mask of flags submitted for the edit.
+// *
+// * @return String An appropriate auto-summary, or an empty String.
+// */
+// public function getAutosummary(Content $oldContent = null, Content $newContent = null,
+// $flags) {
+// // Decide what kind of auto-summary is needed.
+//
+// // Redirect auto-summaries
+//
+// /**
+// * @var $ot Title
+// * @var $rt Title
+// */
+//
+// $ot = !is_null($oldContent) ? $oldContent->getRedirectTarget() : null;
+// $rt = !is_null($newContent) ? $newContent->getRedirectTarget() : null;
+//
+// if (is_object($rt)) {
+// if (!is_object($ot)
+// || !$rt->equals($ot)
+// || $ot->getFragment() != $rt->getFragment()
+// ) {
+// $truncatedtext = $newContent->getTextForSummary(
+// 250
+// - strlen(wfMessage('autoredircomment')->inContentLanguage()->text())
+// - strlen($rt->getFullText()));
+//
+// return wfMessage('autoredircomment', $rt->getFullText())
+// ->rawParams($truncatedtext)->inContentLanguage()->text();
+// }
+// }
+//
+// // New page auto-summaries
+// if ($flags & EDIT_NEW && $newContent->getSize() > 0) {
+// // If they're making a new article, give its text, truncated, in
+// // the summary.
+//
+// $truncatedtext = $newContent->getTextForSummary(
+// 200 - strlen(wfMessage('autosumm-new')->inContentLanguage()->text()));
+//
+// return wfMessage('autosumm-new')->rawParams($truncatedtext)
+// ->inContentLanguage()->text();
+// }
+//
+// // Blanking auto-summaries
+// if (!empty($oldContent) && $oldContent->getSize() > 0 && $newContent->getSize() == 0) {
+// return wfMessage('autosumm-blank')->inContentLanguage()->text();
+// } elseif (!empty($oldContent)
+// && $oldContent->getSize() > 10 * $newContent->getSize()
+// && $newContent->getSize() < 500
+// ) {
+// // Removing more than 90% of the article
+//
+// $truncatedtext = $newContent->getTextForSummary(
+// 200 - strlen(wfMessage('autosumm-replace')->inContentLanguage()->text()));
+//
+// return wfMessage('autosumm-replace')->rawParams($truncatedtext)
+// ->inContentLanguage()->text();
+// }
+//
+// // New blank article auto-summary
+// if ($flags & EDIT_NEW && $newContent->isEmpty()) {
+// return wfMessage('autosumm-newblank')->inContentLanguage()->text();
+// }
+//
+// // If we reach this point, there's no applicable auto-summary for our
+// // case, so our auto-summary is empty.
+// return '';
+// }
+//
+// /**
+// * Auto-generates a deletion reason
+// *
+// * @since 1.21
+// *
+// * @param Title $title The page's title
+// * @param boolean &$hasHistory Whether the page has a history
+// *
+// * @return mixed String containing deletion reason or empty String, or
+// * boolean false if no revision occurred
+// *
+// * @todo &$hasHistory is extremely ugly, it's here because
+// * WikiPage::getAutoDeleteReason() and Article::generateReason()
+// * have it / want it.
+// */
+// public function getAutoDeleteReason(Title $title, &$hasHistory) {
+// $dbr = wfGetDB(DB_REPLICA);
+//
+// // Get the last revision
+// $rev = Revision::newFromTitle($title);
+//
+// if (is_null($rev)) {
+// return false;
+// }
+//
+// // Get the article's contents
+// $content = $rev->getContent();
+// $blank = false;
+//
+// // If the page is blank, use the text from the previous revision,
+// // which can only be blank if there's a move/import/protect dummy
+// // revision involved
+// if (!$content || $content->isEmpty()) {
+// $prev = $rev->getPrevious();
+//
+// if ($prev) {
+// $rev = $prev;
+// $content = $rev->getContent();
+// $blank = true;
+// }
+// }
+//
+// this.checkModelID($rev->getContentModel());
+//
+// // Find out if there was only one contributor
+// // Only scan the last 20 revisions
+// $res = $dbr->select('revision', 'rev_user_text',
+// [
+// 'rev_page' => $title->getArticleID(),
+// $dbr->bitAnd('rev_deleted', Revision::DELETED_USER) . ' = 0'
+// ],
+// __METHOD__,
+// [ 'LIMIT' => 20 ]
+// );
+//
+// if ($res === false) {
+// // This page has no revisions, which is very weird
+// return false;
+// }
+//
+// $hasHistory = ($res->numRows() > 1);
+// $row = $dbr->fetchObject($res);
+//
+// if ($row) { // $row is false if the only contributor is hidden
+// $onlyAuthor = $row->rev_user_text;
+// // Try to find a second contributor
+// foreach ($res as $row) {
+// if ($row->rev_user_text != $onlyAuthor) { // Bug 22999
+// $onlyAuthor = false;
+// break;
+// }
+// }
+// } else {
+// $onlyAuthor = false;
+// }
+//
+// // Generate the summary with a '$1' placeholder
+// if ($blank) {
+// // The current revision is blank and the one before is also
+// // blank. It's just not our lucky day
+// $reason = wfMessage('exbeforeblank', '$1')->inContentLanguage()->text();
+// } else {
+// if ($onlyAuthor) {
+// $reason = wfMessage(
+// 'excontentauthor',
+// '$1',
+// $onlyAuthor
+// )->inContentLanguage()->text();
+// } else {
+// $reason = wfMessage('excontent', '$1')->inContentLanguage()->text();
+// }
+// }
+//
+// if ($reason == '-') {
+// // Allow these UI messages to be blanked out cleanly
+// return '';
+// }
+//
+// // Max content length = max comment length - length of the comment (excl. $1)
+// $text = $content ? $content->getTextForSummary(255 - (strlen($reason) - 2)) : '';
+//
+// // Now replace the '$1' placeholder
+// $reason = str_replace('$1', $text, $reason);
+//
+// return $reason;
+// }
+//
+// /**
+// * Get the Content Object that needs to be saved in order to undo all revisions
+// * between $undo and $undoafter. Revisions must belong to the same page,
+// * must exist and must not be deleted.
+// *
+// * @since 1.21
+// *
+// * @param Revision $current The current text
+// * @param Revision $undo The revision to undo
+// * @param Revision $undoafter Must be an earlier revision than $undo
+// *
+// * @return mixed String on success, false on failure
+// */
+// public function getUndoContent(Revision $current, Revision $undo, Revision $undoafter) {
+// $cur_content = $current->getContent();
+//
+// if (empty($cur_content)) {
+// return false; // no page
+// }
+//
+// $undo_content = $undo->getContent();
+// $undoafter_content = $undoafter->getContent();
+//
+// if (!$undo_content || !$undoafter_content) {
+// return false; // no content to undo
+// }
+//
+// try {
+// this.checkModelID($cur_content->getModel());
+// this.checkModelID($undo_content->getModel());
+// if ($current->getId() !== $undo->getId()) {
+// // If we are undoing the most recent revision,
+// // its ok to revert content model changes. However
+// // if we are undoing a revision in the middle, then
+// // doing that will be confusing.
+// this.checkModelID($undoafter_content->getModel());
+// }
+// } catch (MWException $e) {
+// // If the revisions have different content models
+// // just return false
+// return false;
+// }
+//
+// if ($cur_content->equals($undo_content)) {
+// // No use doing a merge if it's just a straight revert.
+// return $undoafter_content;
+// }
+//
+// $undone_content = this.merge3($undo_content, $undoafter_content, $cur_content);
+//
+// return $undone_content;
+// }
+//
+// /**
+// * Get parser options suitable for rendering and caching the article
+// *
+// * @param IContextSource|User|String $context One of the following:
+// * - IContextSource: Use the User and the Language of the provided
+// * context
+// * - User: Use the provided User Object and $wgLang for the language,
+// * so use an IContextSource Object if possible.
+// * - 'canonical': Canonical options (anonymous user with default
+// * preferences and content language).
+// *
+// * @throws MWException
+// * @return ParserOptions
+// */
+// public function makeParserOptions($context) {
+// global $wgContLang, $wgEnableParserLimitReporting;
+//
+// if ($context instanceof IContextSource) {
+// $options = ParserOptions::newFromContext($context);
+// } elseif ($context instanceof User) { // settings per user (even anons)
+// $options = ParserOptions::newFromUser($context);
+// } elseif ($context === 'canonical') { // canonical settings
+// $options = ParserOptions::newFromUserAndLang(new User, $wgContLang);
+// } else {
+// throw new MWException("Bad context for parser options: $context");
+// }
+//
+// $options->enableLimitReport($wgEnableParserLimitReporting); // show inclusion/loop reports
+// $options->setTidy(true); // fix bad HTML
+//
+// return $options;
+// }
+//
+// /**
+// * Returns true for content models that support caching using the
+// * ParserCache mechanism. See WikiPage::shouldCheckParserCache().
+// *
+// * @since 1.21
+// *
+// * @return boolean Always false.
+// */
+// public function isParserCacheSupported() {
+// return false;
+// }
+//
+// /**
+// * Returns true if this content model supports sections.
+// * This default implementation returns false.
+// *
+// * Content models that return true here should also implement
+// * Content::getSection, Content::replaceSection, etc. to handle sections..
+// *
+// * @return boolean Always false.
+// */
+// public function supportsSections() {
+// return false;
+// }
+//
+// /**
+// * Returns true if this content model supports categories.
+// * The default implementation returns true.
+// *
+// * @return boolean Always true.
+// */
+// public function supportsCategories() {
+// return true;
+// }
+//
+// /**
+// * Returns true if this content model supports redirects.
+// * This default implementation returns false.
+// *
+// * Content models that return true here should also implement
+// * ContentHandler::makeRedirectContent to return a Content Object.
+// *
+// * @return boolean Always false.
+// */
+// public function supportsRedirects() {
+// return false;
+// }
+//
+// /**
+// * Return true if this content model supports direct editing, such as via EditPage.
+// *
+// * @return boolean Default is false, and true for TextContent and it's derivatives.
+// */
+// public function supportsDirectEditing() {
+// return false;
+// }
+//
+// /**
+// * Whether or not this content model supports direct editing via ApiEditPage
+// *
+// * @return boolean Default is false, and true for TextContent and derivatives.
+// */
+// public function supportsDirectApiEditing() {
+// return this.supportsDirectEditing();
+// }
+//
+// /**
+// * Get fields definition for search index
+// *
+// * @todo Expose title, redirect, namespace, text, source_text, text_bytes
+// * field mappings here. (see T142670 and T143409)
+// *
+// * @param SearchEngine $engine
+// * @return SearchIndexField[] List of fields this content handler can provide.
+// * @since 1.28
+// */
+// public function getFieldsForSearchIndex(SearchEngine $engine) {
+// $fields['category'] = $engine->makeSearchFieldMapping(
+// 'category',
+// SearchIndexField::INDEX_TYPE_TEXT
+// );
+//
+// $fields['category']->setFlag(SearchIndexField::FLAG_CASEFOLD);
+//
+// $fields['external_link'] = $engine->makeSearchFieldMapping(
+// 'external_link',
+// SearchIndexField::INDEX_TYPE_KEYWORD
+// );
+//
+// $fields['outgoing_link'] = $engine->makeSearchFieldMapping(
+// 'outgoing_link',
+// SearchIndexField::INDEX_TYPE_KEYWORD
+// );
+//
+// $fields['template'] = $engine->makeSearchFieldMapping(
+// 'template',
+// SearchIndexField::INDEX_TYPE_KEYWORD
+// );
+//
+// $fields['template']->setFlag(SearchIndexField::FLAG_CASEFOLD);
+//
+// return $fields;
+// }
+//
+// /**
+// * Add new field definition to array.
+// * @param SearchIndexField[] $fields
+// * @param SearchEngine $engine
+// * @param String $name
+// * @param int $type
+// * @return SearchIndexField[] new field defs
+// * @since 1.28
+// */
+// protected function addSearchField(&$fields, SearchEngine $engine, $name, $type) {
+// $fields[$name] = $engine->makeSearchFieldMapping($name, $type);
+// return $fields;
+// }
+//
+// /**
+// * Return fields to be indexed by search engine
+// * as representation of this document.
+// * Overriding class should call parent function or take care of calling
+// * the SearchDataForIndex hook.
+// * @param WikiPage $page Page to index
+// * @param ParserOutput $output
+// * @param SearchEngine $engine Search engine for which we are indexing
+// * @return array Map of name=>value for fields
+// * @since 1.28
+// */
+// public function getDataForSearchIndex(WikiPage $page, ParserOutput $output,
+// SearchEngine $engine) {
+// $fieldData = [];
+// $content = $page->getContent();
+//
+// if ($content) {
+// $searchDataExtractor = new ParserOutputSearchDataExtractor();
+//
+// $fieldData['category'] = $searchDataExtractor->getCategories($output);
+// $fieldData['external_link'] = $searchDataExtractor->getExternalLinks($output);
+// $fieldData['outgoing_link'] = $searchDataExtractor->getOutgoingLinks($output);
+// $fieldData['template'] = $searchDataExtractor->getTemplates($output);
+//
+// $text = $content->getTextForSearchIndex();
+//
+// $fieldData['text'] = $text;
+// $fieldData['source_text'] = $text;
+// $fieldData['text_bytes'] = $content->getSize();
+// }
+//
+// Hooks::run('SearchDataForIndex', [ &$fieldData, $this, $page, $output, $engine ]);
+// return $fieldData;
+// }
+//
+// /**
+// * Produce page output suitable for indexing.
+// *
+// * Specific content handlers may override it if they need different content handling.
+// *
+// * @param WikiPage $page
+// * @param ParserCache $cache
+// * @return ParserOutput
+// */
+// public function getParserOutputForIndexing(WikiPage $page, ParserCache $cache = null) {
+// $parserOptions = $page->makeParserOptions('canonical');
+// $revId = $page->getRevision()->getId();
+// if ($cache) {
+// $parserOutput = $cache->get($page, $parserOptions);
+// }
+// if (empty($parserOutput)) {
+// $parserOutput =
+// $page->getContent()->getParserOutput($page->getTitle(), $revId, $parserOptions);
+// if ($cache) {
+// $cache->save($parserOutput, $page, $parserOptions);
+// }
+// }
+// return $parserOutput;
+// }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/TextContent.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/TextContent.java
new file mode 100644
index 000000000..b7b1d155c
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/TextContent.java
@@ -0,0 +1,321 @@
+/*
+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.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
+public class TextContent {
+ // AbstractContent
+ /**
+ * @since 1.21
+ *
+ * @return boolean
+ *
+ * @see Content::isRedirect
+ */
+// public function isRedirect() {
+// return $this->getRedirectTarget() !== null;
+// }
+ public boolean isRedirect() {
+ return false;
+ }
+
+ private byte[] mText;
+ /**
+ * @param String $text
+ * @param String $model_id
+ * @throws MWException
+ */
+ public void __construct(byte[] text, String model_id) { // = CONTENT_MODEL_TEXT
+// parent::__construct( $model_id );
+//
+// if ( $text === null || $text === false ) {
+// wfWarn( "TextContent constructed with \$text = " . var_export( $text, true ) . "! "
+// . "This may indicate an error in the caller's scope.", 2 );
+//
+// $text = '';
+// }
+//
+// if ( !is_string( $text ) ) {
+// throw new MWException( "TextContent expects a String in the constructor." );
+// }
+//
+ this.mText = text;
+ }
+//
+// /**
+// * @note Mutable subclasses MUST override this to return a copy!
+// *
+// * @return Content this
+// */
+// public function copy() {
+// return this; # NOTE: this is ok since TextContent are immutable.
+// }
+//
+// public function getTextForSummary( $maxlength = 250 ) {
+// global $wgContLang;
+//
+// $text = this.getNativeData();
+//
+// $truncatedtext = $wgContLang.truncate(
+// preg_replace( "/[\n\r]/", ' ', $text ),
+// max( 0, $maxlength ) );
+//
+// return $truncatedtext;
+// }
+//
+// /**
+// * Returns the text's size in bytes.
+// *
+// * @return int
+// */
+// public function getSize() {
+// $text = this.getNativeData();
+//
+// return strlen( $text );
+// }
+//
+// /**
+// * Returns true if this content is not a redirect, and $wgArticleCountMethod
+// * is "any".
+// *
+// * @param boolean|null $hasLinks If it is known whether this content contains links,
+// * provide this information here, to avoid redundant parsing to find out.
+// *
+// * @return boolean
+// */
+// public function isCountable( $hasLinks = null ) {
+// global $wgArticleCountMethod;
+//
+// if ( this.isRedirect() ) {
+// return false;
+// }
+//
+// if ( $wgArticleCountMethod === 'any' ) {
+// return true;
+// }
+//
+// return false;
+// }
+
+ /**
+ * Returns the text represented by this Content Object, as a String.
+ *
+ * @return String The raw text.
+ */
+ public byte[] getNativeData() {
+ return this.mText;
+ }
+
+// /**
+// * Returns the text represented by this Content Object, as a String.
+// *
+// * @return String The raw text.
+// */
+// public function getTextForSearchIndex() {
+// return this.getNativeData();
+// }
+//
+// /**
+// * Returns attempts to convert this content Object to wikitext,
+// * and then returns the text String. The conversion may be lossy.
+// *
+// * @note this allows any text-based content to be transcluded as if it was wikitext.
+// *
+// * @return String|boolean The raw text, or false if the conversion failed.
+// */
+// public function getWikitextForTransclusion() {
+// $wikitext = this.convert( CONTENT_MODEL_WIKITEXT, 'lossy' );
+//
+// if ( $wikitext ) {
+// return $wikitext.getNativeData();
+// } else {
+// return false;
+// }
+// }
+//
+// /**
+// * Do a "\r\n" . "\n" and "\r" . "\n" transformation
+// * as well as trim trailing whitespace
+// *
+// * This was formerly part of Parser::preSaveTransform, but
+// * for non-wikitext content models they probably still want
+// * to normalize line endings without all of the other PST
+// * changes.
+// *
+// * @since 1.28
+// * @param $text
+// * @return String
+// */
+// public static function normalizeLineEndings( $text ) {
+// return str_replace( [ "\r\n", "\r" ], "\n", rtrim( $text ) );
+// }
+//
+// /**
+// * Returns a Content Object with pre-save transformations applied.
+// *
+// * At a minimum, subclasses should make sure to call TextContent::normalizeLineEndings()
+// * either directly or part of Parser::preSaveTransform().
+// *
+// * @param Title $title
+// * @param User $user
+// * @param ParserOptions $popts
+// *
+// * @return Content
+// */
+// public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) {
+// $text = this.getNativeData();
+// $pst = self::normalizeLineEndings( $text );
+//
+// return ( $text === $pst ) ? this : new static( $pst, this.getModel() );
+// }
+//
+// /**
+// * Diff this content Object with another content Object.
+// *
+// * @since 1.21
+// *
+// * @param Content $that The other content Object to compare this content Object to.
+// * @param Language $lang The language Object to use for text segmentation.
+// * If not given, $wgContentLang is used.
+// *
+// * @return Diff A diff representing the changes that would have to be
+// * made to this content Object to make it equal to $that.
+// */
+// public function diff( Content $that, Language $lang = null ) {
+// global $wgContLang;
+//
+// this.checkModelID( $that.getModel() );
+//
+// // @todo could implement this in DifferenceEngine and just delegate here?
+//
+// if ( !$lang ) {
+// $lang = $wgContLang;
+// }
+//
+// $otext = this.getNativeData();
+// $ntext = $that.getNativeData();
+//
+// # Note: Use native PHP diff, external engines don't give us abstract output
+// $ota = explode( "\n", $lang.segmentForDiff( $otext ) );
+// $nta = explode( "\n", $lang.segmentForDiff( $ntext ) );
+//
+// $diff = new Diff( $ota, $nta );
+//
+// return $diff;
+// }
+//
+// /**
+// * Fills the provided ParserOutput Object with information derived from the content.
+// * Unless $generateHtml was false, this includes an HTML representation of the content
+// * provided by getHtml().
+// *
+// * For content models listed in $wgTextModelsToParse, this method will call the MediaWiki
+// * wikitext parser on the text to extract any (wikitext) links, magic words, etc.
+// *
+// * Subclasses may override this to provide custom content processing.
+// * For custom HTML generation alone, it is sufficient to override getHtml().
+// *
+// * @param Title $title Context title for parsing
+// * @param int $revId Revision ID (for {{REVISIONID}})
+// * @param ParserOptions $options Parser options
+// * @param boolean $generateHtml Whether or not to generate HTML
+// * @param ParserOutput $output The output Object to fill (reference).
+// */
+// protected function fillParserOutput( Title $title, $revId,
+// ParserOptions $options, $generateHtml, ParserOutput &$output
+// ) {
+// global $wgParser, $wgTextModelsToParse;
+//
+// if ( in_array( this.getModel(), $wgTextModelsToParse ) ) {
+// // parse just to get links etc into the database, HTML is replaced below.
+// $output = $wgParser.parse( this.getNativeData(), $title, $options, true, true, $revId );
+// }
+//
+// if ( $generateHtml ) {
+// $html = this.getHtml();
+// } else {
+// $html = '';
+// }
+//
+// $output.setText( $html );
+// }
+//
+// /**
+// * Generates an HTML version of the content, for display. Used by
+// * fillParserOutput() to provide HTML for the ParserOutput Object.
+// *
+// * Subclasses may override this to provide a custom HTML rendering.
+// * If further information is to be derived from the content (such as
+// * categories), the fillParserOutput() method can be overridden instead.
+// *
+// * For backwards-compatibility, this default implementation just calls
+// * getHighlightHtml().
+// *
+// * @return String An HTML representation of the content
+// */
+// protected function getHtml() {
+// return this.getHighlightHtml();
+// }
+//
+// /**
+// * Generates an HTML version of the content, for display.
+// *
+// * This default implementation returns an HTML-escaped version
+// * of the raw text content.
+// *
+// * @note The functionality of this method should really be implemented
+// * in getHtml(), and subclasses should override getHtml() if needed.
+// * getHighlightHtml() is kept around for backward compatibility with
+// * extensions that already override it.
+// *
+// * @deprecated since 1.24. Use getHtml() instead. In particular, subclasses overriding
+// * getHighlightHtml() should override getHtml() instead.
+// *
+// * @return String An HTML representation of the content
+// */
+// protected function getHighlightHtml() {
+// return htmlspecialchars( this.getNativeData() );
+// }
+//
+// /**
+// * This implementation provides lossless conversion between content models based
+// * on TextContent.
+// *
+// * @param String $toModel The desired content model, use the CONTENT_MODEL_XXX flags.
+// * @param String $lossy Flag, set to "lossy" to allow lossy conversion. If lossy conversion is not
+// * allowed, full round-trip conversion is expected to work without losing information.
+// *
+// * @return Content|boolean A content Object with the content model $toModel, or false if that
+// * conversion is not supported.
+// *
+// * @see Content::convert()
+// */
+// public function convert( $toModel, $lossy = '' ) {
+// $converted = parent::convert( $toModel, $lossy );
+//
+// if ( $converted !== false ) {
+// return $converted;
+// }
+//
+// $toHandler = ContentHandler::getForModelID( $toModel );
+//
+// if ( $toHandler instanceof TextContentHandler ) {
+// // NOTE: ignore content serialization format - it's just text anyway.
+// $text = this.getNativeData();
+// $converted = $toHandler.unserializeContent( $text );
+// }
+//
+// return $converted;
+// }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/TextContentHandler.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/TextContentHandler.java
new file mode 100644
index 000000000..0cd14f6e4
--- /dev/null
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/TextContentHandler.java
@@ -0,0 +1,145 @@
+/*
+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.content; import gplx.*; import gplx.xowa.*; import gplx.xowa.mediawiki.*; import gplx.xowa.mediawiki.includes.*;
+public class TextContentHandler extends ContentHandler { // @codingStandardsIgnoreStart bug 57585
+ public void __construct() {this.__construct(XomwDefines.CONTENT_MODEL_TEXT, XomwDefines.CONTENT_FORMAT_TEXT);}
+ @Override public void __construct(String modelId, String... formats) {
+ super.__construct(modelId, formats);
+ }
+ // @codingStandardsIgnoreEnd
+//
+// /**
+// * Returns the content's text as-is.
+// *
+// * @param Content $content
+// * @param String $format The serialization format to check
+// *
+// * @return mixed
+// */
+// public function serializeContent(Content $content, $format = null) {
+// $this->checkFormat($format);
+//
+// return $content->getNativeData();
+// }
+//
+// /**
+// * Attempts to merge differences between three versions. Returns a new
+// * Content Object for a clean merge and false for failure or a conflict.
+// *
+// * All three Content objects passed as parameters must have the same
+// * content model.
+// *
+// * This text-based implementation uses wfMerge().
+// *
+// * @param Content $oldContent The page's previous content.
+// * @param Content $myContent One of the page's conflicting contents.
+// * @param Content $yourContent One of the page's conflicting contents.
+// *
+// * @return Content|boolean
+// */
+// public function merge3(Content $oldContent, Content $myContent, Content $yourContent) {
+// $this->checkModelID($oldContent->getModel());
+// $this->checkModelID($myContent->getModel());
+// $this->checkModelID($yourContent->getModel());
+//
+// $format = $this->getDefaultFormat();
+//
+// $old = $this->serializeContent($oldContent, $format);
+// $mine = $this->serializeContent($myContent, $format);
+// $yours = $this->serializeContent($yourContent, $format);
+//
+// $ok = wfMerge($old, $mine, $yours, $result);
+//
+// if (!$ok) {
+// return false;
+// }
+//
+// if (!$result) {
+// return $this->makeEmptyContent();
+// }
+//
+// $mergedContent = $this->unserializeContent($result, $format);
+//
+// return $mergedContent;
+// }
+//
+// /**
+// * Returns the name of the associated Content class, to
+// * be used when creating new objects. Override expected
+// * by subclasses.
+// *
+// * @since 1.24
+// *
+// * @return String
+// */
+// protected function getContentClass() {
+// return TextContent::class;
+// }
+//
+// /**
+// * Unserializes a Content Object of the type supported by this ContentHandler.
+// *
+// * @since 1.21
+// *
+// * @param String $text Serialized form of the content
+// * @param String $format The format used for serialization
+// *
+// * @return Content The TextContent Object wrapping $text
+// */
+// public function unserializeContent($text, $format = null) {
+// $this->checkFormat($format);
+//
+// $class = $this->getContentClass();
+// return new $class($text);
+// }
+//
+// /**
+// * Creates an empty TextContent Object.
+// *
+// * @since 1.21
+// *
+// * @return Content A new TextContent Object with empty text.
+// */
+// public function makeEmptyContent() {
+// $class = $this->getContentClass();
+// return new $class('');
+// }
+//
+// /**
+// * @see ContentHandler::supportsDirectEditing
+// *
+// * @return boolean Default is true for TextContent and derivatives.
+// */
+// public function supportsDirectEditing() {
+// return true;
+// }
+//
+// public function getFieldsForSearchIndex(SearchEngine $engine) {
+// $fields = parent::getFieldsForSearchIndex($engine);
+// $fields['language'] =
+// $engine->makeSearchFieldMapping('language', SearchIndexField::INDEX_TYPE_KEYWORD);
+//
+// return $fields;
+// }
+//
+// public function getDataForSearchIndex(WikiPage $page, ParserOutput $output,
+// SearchEngine $engine) {
+// $fields = parent::getDataForSearchIndex($page, $output, $engine);
+// $fields['language'] =
+// $this->getPageLanguage($page->getTitle(), $page->getContent())->getCode();
+// return $fields;
+// }
+}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/interwiki/XomwInterwikiLookupAdapter.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/interwiki/XomwInterwikiLookupAdapter.java
index e3d7927e2..8c339c1b3 100644
--- a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/interwiki/XomwInterwikiLookupAdapter.java
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/interwiki/XomwInterwikiLookupAdapter.java
@@ -41,7 +41,7 @@ public class XomwInterwikiLookupAdapter implements XomwInterwikiLookup {
* @return boolean Whether it exists
*/
public boolean isValidInterwiki(byte[] prefix) {
- return XophpArray.array_key_exists(prefix, this.getInterwikiMap());
+ return XophpArrayUtl.array_key_exists(prefix, this.getInterwikiMap());
}
/**
@@ -71,7 +71,7 @@ public class XomwInterwikiLookupAdapter implements XomwInterwikiLookup {
*/
public byte[][] getAllPrefixes(boolean local) {
if (!local) {
- XophpArray.array_keys_bry(this.getInterwikiMap());
+ XophpArrayUtl.array_keys_bry(this.getInterwikiMap());
}
List_adp res = List_adp_.New();
Ordered_hash hash = this.getInterwikiMap();
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/parsers/tables/Xomw_table_wkr.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/parsers/tables/Xomw_table_wkr.java
index 4e897ac32..cb0359928 100644
--- a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/parsers/tables/Xomw_table_wkr.java
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/parsers/tables/Xomw_table_wkr.java
@@ -48,13 +48,13 @@ public class Xomw_table_wkr implements gplx.core.brys.Bry_split_wkr {// THREAD.U
// Closing open td, tr && table
while (td_history.Len() > 0) {
- if (XophpArray.popBoolOrN(td_history)) {
+ if (XophpArrayUtl.popBoolOrN(td_history)) {
bfr.Add_str_a7("\n");
}
- if (XophpArray.popBoolOrN(tr_history)) {
+ if (XophpArrayUtl.popBoolOrN(tr_history)) {
bfr.Add_str_a7("\n");
}
- if (!XophpArray.popBoolOrN(has_opened_tr)) {
+ if (!XophpArrayUtl.popBoolOrN(has_opened_tr)) {
bfr.Add_str_a7(" |
\n");
}
bfr.Add_str_a7("\n");
@@ -123,20 +123,20 @@ public class Xomw_table_wkr implements gplx.core.brys.Bry_split_wkr {// THREAD.U
else if (Bry_.Eq(first_2, Wtxt__tb__end)) {
// We are ending a table
line = tmp.Add_str_a7("").Add_mid(line, 2, line.length).To_bry_and_clear();
- byte[] last_tag = XophpArray.popBryOrNull(last_tag_history);
+ byte[] last_tag = XophpArrayUtl.popBryOrNull(last_tag_history);
- if (!XophpArray.popBoolOrN(has_opened_tr)) {
+ if (!XophpArrayUtl.popBoolOrN(has_opened_tr)) {
line = tmp.Add_str_a7(" |
").Add(line).To_bry_and_clear();
}
- if (XophpArray.popBoolOrN(tr_history)) {
+ if (XophpArrayUtl.popBoolOrN(tr_history)) {
line = tmp.Add_str_a7("").Add(line).To_bry_and_clear();
}
- if (XophpArray.popBoolOrN(td_history)) {
+ if (XophpArrayUtl.popBoolOrN(td_history)) {
line = tmp.Add_str_a7("").Add(last_tag).Add_byte(Byte_ascii.Angle_end).Add(line).To_bry_and_clear();
}
- XophpArray.popBryOrNull(tr_attributes);
+ XophpArrayUtl.popBryOrNull(tr_attributes);
// PORTED:$outLine = $line . str_repeat( '', $indent_level );
tmp.Add(line);
for (int j = 0; j < indent_level; j++)
@@ -152,19 +152,19 @@ public class Xomw_table_wkr implements gplx.core.brys.Bry_split_wkr {// THREAD.U
sanitizer.fixTagAttributes(tmp, Name__tr, atrs);
atrs = tmp.To_bry_and_clear();
- XophpArray.popBryOrNull(tr_attributes);
+ XophpArrayUtl.popBryOrNull(tr_attributes);
tr_attributes.Add(atrs);
line = Bry_.Empty;
- byte[] last_tag = XophpArray.popBryOrNull(last_tag_history);
- XophpArray.popBoolOrN(has_opened_tr);
+ byte[] last_tag = XophpArrayUtl.popBryOrNull(last_tag_history);
+ XophpArrayUtl.popBoolOrN(has_opened_tr);
has_opened_tr.Add(true);
- if (XophpArray.popBoolOrN(tr_history)) {
+ if (XophpArrayUtl.popBoolOrN(tr_history)) {
line = Html__tr__end;
}
- if (XophpArray.popBoolOrN(td_history)) {
+ if (XophpArrayUtl.popBoolOrN(td_history)) {
line = tmp.Add_str_a7("").Add(last_tag).Add_byte(Byte_ascii.Gt).Add(line).To_bry_and_clear();
}
@@ -205,19 +205,19 @@ public class Xomw_table_wkr implements gplx.core.brys.Bry_split_wkr {// THREAD.U
byte[] cell = cells[j];
previous = Bry_.Empty;
if (first_char != Byte_ascii.Plus) {
- byte[] tr_after = XophpArray.popBryOrNull(tr_attributes);
- if (!XophpArray.popBoolOrN(tr_history)) {
+ byte[] tr_after = XophpArrayUtl.popBryOrNull(tr_attributes);
+ if (!XophpArrayUtl.popBoolOrN(tr_history)) {
previous = tmp.Add_str_a7("\n").To_bry_and_clear();
}
tr_history.Add(true);
tr_attributes.Add(Bry_.Empty);
- XophpArray.popBoolOrN(has_opened_tr);
+ XophpArrayUtl.popBoolOrN(has_opened_tr);
has_opened_tr.Add(true);
}
- byte[] last_tag = XophpArray.popBryOrNull(last_tag_history);
+ byte[] last_tag = XophpArrayUtl.popBryOrNull(last_tag_history);
- if (XophpArray.popBoolOrN(td_history)) {
+ if (XophpArrayUtl.popBoolOrN(td_history)) {
previous = tmp.Add_str_a7("").Add(last_tag).Add_str_a7(">\n").Add(previous).To_bry_and_clear();
}
diff --git a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/site/XomwSiteList.java b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/site/XomwSiteList.java
index 113304251..8f725b81a 100644
--- a/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/site/XomwSiteList.java
+++ b/gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/site/XomwSiteList.java
@@ -103,14 +103,14 @@ public class XomwSiteList extends XomwGenericArrayObject { public int Len() {ret
*/
XomwSite site = (XomwSite)this.offsetGet(index);
- XophpArray.unset(this.byGlobalId, site.getGlobalId());
- XophpArray.unset(this.byInternalId, site.getInternalId());
+ XophpArrayUtl.unset(this.byGlobalId, site.getGlobalId());
+ XophpArrayUtl.unset(this.byInternalId, site.getInternalId());
Ordered_hash ids = site.getNavigationIds();
int len = ids.Len();
for (int i = 0; i < len; i++) {
int navId = Int_.Cast(ids.Get_at(i));
- XophpArray.unset(this.byNavigationId, navId);
+ XophpArrayUtl.unset(this.byNavigationId, navId);
}
}
@@ -126,7 +126,7 @@ public class XomwSiteList extends XomwGenericArrayObject { public int Len() {ret
* @return array
*/
public String[] getGlobalIdentifiers() {
- return XophpArray.array_keys_str(this.byGlobalId);
+ return XophpArrayUtl.array_keys_str(this.byGlobalId);
}
/**
@@ -137,7 +137,7 @@ public class XomwSiteList extends XomwGenericArrayObject { public int Len() {ret
* @return boolean
*/
public boolean hasSite(String globalSiteId) {
- return XophpArray.array_key_exists(globalSiteId, this.byGlobalId);
+ return XophpArrayUtl.array_key_exists(globalSiteId, this.byGlobalId);
}
/**
@@ -174,7 +174,7 @@ public class XomwSiteList extends XomwGenericArrayObject { public int Len() {ret
* @return boolean
*/
@Override public boolean isEmpty() {
- return XophpArray.array_is_empty(this.byGlobalId);
+ return XophpArrayUtl.array_is_empty(this.byGlobalId);
}
/**
@@ -185,7 +185,7 @@ public class XomwSiteList extends XomwGenericArrayObject { public int Len() {ret
* @return boolean
*/
public boolean hasInternalId(int id) {
- return XophpArray.array_key_exists(id, this.byInternalId);
+ return XophpArrayUtl.array_key_exists(id, this.byInternalId);
}
/**
@@ -222,7 +222,7 @@ public class XomwSiteList extends XomwGenericArrayObject { public int Len() {ret
* @return boolean
*/
public boolean hasNavigationId(String id) {
- return XophpArray.array_key_exists(id, this.byNavigationId);
+ return XophpArrayUtl.array_key_exists(id, this.byNavigationId);
}
/**
diff --git a/xowa.home.version b/xowa.home.version
index 4e0c2665f..b80e5de49 100644
--- a/xowa.home.version
+++ b/xowa.home.version
@@ -1 +1 @@
-wikidata
+v4.5.19.1801