From 54d74b0acd748662831cacd075d27118e225c6ce Mon Sep 17 00:00:00 2001 From: gnosygnu Date: Sun, 18 Feb 2018 08:16:43 -0500 Subject: [PATCH] JsonConfig: Add Localize function for tabular data --- 100_core/src/gplx/Bry_find_.java | 4 + 100_core/src/gplx/Object_.java | 9 + 100_core/src/gplx/Type_ids_.java | 1 + 400_xowa/src/gplx/xowa/Xoa_app_.java | 4 +- .../xowa/htmls/core/htmls/Xoh_html_wtr.java | 11 +- .../htmls/core/wkrs/hdrs/Xoh_hdr_html.java | 2 +- .../src/gplx/xowa/langs/Xol_lang_itm.java | 4 + .../gplx/xowa/mediawiki/XophpStringTest.java | 0 400_xowa/src/gplx/xowa/parsers/Xop_lxr_.java | 2 +- .../src/gplx/xowa/parsers/Xop_lxr_mgr.java | 3 +- .../src/gplx/xowa/parsers/Xop_tkn_itm_.java | 6 +- .../hdrs/sections/Xop_section_list.java | 2 +- .../hdrs/sections/Xop_section_mgr.java | 2 +- .../gplx/xowa/parsers/htmls/Mwh_atr_itm.java | 21 +- .../gplx/xowa/parsers/tblws/Xop_tblw_wkr.java | 3 + .../tblws/Xop_tblw_wkr__uncommon_tst.java | 2 +- .../gplx/xowa/parsers/tmpls/Xot_tmpl_wtr.java | 6 +- .../gplx/xowa/parsers/uniqs/Xop_uniq_itm.java | 30 + .../gplx/xowa/parsers/uniqs/Xop_uniq_lxr.java | 41 + .../gplx/xowa/parsers/uniqs/Xop_uniq_mgr.java | 119 +- .../xowa/parsers/uniqs/Xop_uniq_mgr__tst.java | 4 +- .../gplx/xowa/parsers/uniqs/Xop_uniq_tkn.java | 26 + .../parsers/xndes/Xop_xatr_whitelist_mgr.java | 3 +- .../xndes/Xop_xatr_whitelist_mgr_tst.java | 24 +- .../gplx/xowa/parsers/xndes/Xop_xnde_tkn.java | 2 +- .../gplx/xowa/parsers/xndes/Xop_xnde_wkr.java | 7 + .../xndes/Xop_xnde_wkr__nowiki_tst.java | 2 +- .../xowa/xtns/gallery/Gallery_mgr_wtr.java | 2 +- .../scribunto/Jscfg_localizer.java | 151 +++ .../scribunto/Jscfg_scrib_lib.java | 5 +- .../scribunto/Jscfg_scrib_lib_tst.java | 138 +- .../jsonConfigs/scribunto/Jscfg_xtn_mgr.java | 12 + .../gplx/xowa/xtns/math/Xomath_html_wtr.java | 2 +- .../xowa/xtns/pfuncs/strings/Pfunc_tag.java | 2 +- .../xtns/scribunto/libs/Scrib_lib_text.java | 27 +- .../libs/Scrib_lib_text__nowiki_util.java | 90 ++ .../libs/Scrib_lib_text__nowiki_util_tst.java | 55 + .../scribunto/libs/Scrib_lib_text_tst.java | 3 + .../src/gplx/xowa/mediawiki/XophpArray.java | 31 +- .../gplx/xowa/mediawiki/XophpArrayUtl.java | 54 + .../gplx/xowa/mediawiki/XophpClassBldr.java | 20 + .../gplx/xowa/mediawiki/XophpClassBldrs.java | 25 + .../src/gplx/xowa/mediawiki/XophpEnv.java | 22 + .../gplx/xowa/mediawiki/XophpStdClass.java | 81 ++ .../src/gplx/xowa/mediawiki/Xophp_ary.java | 90 ++ .../gplx/xowa/mediawiki/Xophp_ary_itm.java | 43 + .../gplx/xowa/mediawiki/Xophp_ary_tst.java | 137 ++ .../JsonConfig/includes/JCContent.java | 226 ++++ .../JsonConfig/includes/JCContentHandler.java | 138 ++ .../JsonConfig/includes/JCDataContent.java | 150 +++ .../JsonConfig/includes/JCObjContent.java | 566 ++++++++ .../JsonConfig/includes/JCSingleton.java | 995 ++++++++++++++ .../JsonConfig/includes/JCSingleton_tst.java | 66 + .../JsonConfig/includes/JCTabularContent.java | 209 +++ .../includes/JCTabularContentFactory.java | 28 + .../JsonConfig/includes/JCUtils.java | 56 + .../JsonConfig/includes/JCValue.java | 252 ++++ .../JsonConfig/includes/XomwStatus.java | 21 + .../includes/Xomw_page_fetcher.java | 31 + .../xowa/mediawiki/includes/XomwDefines.java | 28 +- .../includes/content/ContentHandler.java | 1172 +++++++++++++++++ .../includes/content/TextContent.java | 321 +++++ .../includes/content/TextContentHandler.java | 145 ++ .../interwiki/XomwInterwikiLookupAdapter.java | 4 +- .../parsers/tables/Xomw_table_wkr.java | 36 +- .../mediawiki/includes/site/XomwSiteList.java | 16 +- xowa.home.version | 2 +- 67 files changed, 5592 insertions(+), 200 deletions(-) rename {gplx.xowa.mediawiki => 400_xowa}/src/gplx/xowa/mediawiki/XophpStringTest.java (100%) create mode 100644 400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_itm.java create mode 100644 400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_lxr.java create mode 100644 400_xowa/src/gplx/xowa/parsers/uniqs/Xop_uniq_tkn.java create mode 100644 400_xowa/src/gplx/xowa/xtns/jsonConfigs/scribunto/Jscfg_localizer.java create mode 100644 400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text__nowiki_util.java create mode 100644 400_xowa/src/gplx/xowa/xtns/scribunto/libs/Scrib_lib_text__nowiki_util_tst.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpArrayUtl.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpClassBldr.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpClassBldrs.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpEnv.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/XophpStdClass.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary_itm.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/Xophp_ary_tst.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCContent.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCContentHandler.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCDataContent.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCObjContent.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCSingleton.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCSingleton_tst.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCTabularContent.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCTabularContentFactory.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCUtils.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/JCValue.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/XomwStatus.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/extensions/JsonConfig/includes/Xomw_page_fetcher.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/ContentHandler.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/TextContent.java create mode 100644 gplx.xowa.mediawiki/src/gplx/xowa/mediawiki/includes/content/TextContentHandler.java 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: "== \delta ==" - 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("', $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("\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("\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